diff --git a/examples/c/reflection/basics_json/src/main.c b/examples/c/reflection/basics_json/src/main.c index c2d8e2c130..ebc95646f4 100644 --- a/examples/c/reflection/basics_json/src/main.c +++ b/examples/c/reflection/basics_json/src/main.c @@ -30,7 +30,7 @@ int main(int argc, char *argv[]) { ecs_os_free(str); // Convert entity & all its components to json - str = ecs_entity_to_json(ecs, ent); + str = ecs_entity_to_json(ecs, ent, NULL); printf("ent = %s\n", str); ecs_os_free(str); diff --git a/flecs.c b/flecs.c index 4a07a54a9f..8894646003 100644 --- a/flecs.c +++ b/flecs.c @@ -71,7 +71,6 @@ extern "C" { #endif #endif - /** * @file bitset.h * @brief Bitset datastructure. @@ -152,7 +151,6 @@ void flecs_bitset_swap( #endif #endif - /** * @file sparse.h * @brief Sparse set datastructure. @@ -449,7 +447,6 @@ void* _ecs_sparse_get( #endif #endif - /** * @file switch_list.h * @brief Interleaved linked list for storing mutually exclusive values. @@ -590,7 +587,6 @@ extern "C" { #endif - #define ECS_MAX_JOBS_PER_WORKER (16) #define ECS_MAX_ADD_REMOVE (32) @@ -952,6 +948,9 @@ typedef struct ecs_event_id_record_t { /* Triggers for SuperSet, SubSet */ ecs_map_t *set_triggers; /* map */ + /* Triggers for Self with non-This subject */ + ecs_map_t *entity_triggers; /* map */ + /* Number of active triggers for (component) id */ int32_t trigger_count; } ecs_event_id_record_t; @@ -1230,7 +1229,6 @@ struct ecs_world_t { }; #endif - /** * @file table_cache.h * @brief Data structure for fast table iteration/lookups. @@ -1324,7 +1322,6 @@ void _ecs_table_cache_fini_delete_all( #endif - //////////////////////////////////////////////////////////////////////////////// //// Core bootstrap functions //////////////////////////////////////////////////////////////////////////////// @@ -2120,42 +2117,106 @@ void _assert_func( #endif +static +uint64_t ids_hash(const void *ptr) { + const ecs_ids_t *type = ptr; + ecs_id_t *ids = type->array; + int32_t count = type->count; + uint64_t hash = flecs_hash(ids, count * ECS_SIZEOF(ecs_id_t)); + return hash; +} -/* Count number of switch columns */ static -int32_t switch_column_count( - ecs_table_t *table) +int ids_compare(const void *ptr_1, const void *ptr_2) { + const ecs_ids_t *type_1 = ptr_1; + const ecs_ids_t *type_2 = ptr_2; + + int32_t count_1 = type_1->count; + int32_t count_2 = type_2->count; + + if (count_1 != count_2) { + return (count_1 > count_2) - (count_1 < count_2); + } + + const ecs_id_t *ids_1 = type_1->array; + const ecs_id_t *ids_2 = type_2->array; + + int32_t i; + for (i = 0; i < count_1; i ++) { + ecs_id_t id_1 = ids_1[i]; + ecs_id_t id_2 = ids_2[i]; + + if (id_1 != id_2) { + return (id_1 > id_2) - (id_1 < id_2); + } + } + + return 0; +} + +ecs_hashmap_t flecs_table_hashmap_new(void) { + return flecs_hashmap_new(ecs_ids_t, ecs_table_t*, ids_hash, ids_compare); +} + +const EcsComponent* flecs_component_from_id( + const ecs_world_t *world, + ecs_entity_t e) { - int32_t i, sw_count = 0, count = ecs_vector_count(table->type); - ecs_id_t *ids = ecs_vector_first(table->type, ecs_id_t); + ecs_entity_t pair = 0; - for (i = 0; i < count; i ++) { - ecs_id_t id = ids[i]; - if (ECS_HAS_ROLE(id, SWITCH)) { - if (!sw_count) { - table->sw_column_offset = i; - } - sw_count ++; + /* If this is a pair, get the pair component from the identifier */ + if (ECS_HAS_ROLE(e, PAIR)) { + pair = e; + e = ecs_get_alive(world, ECS_PAIR_RELATION(e)); + + if (ecs_has_id(world, e, EcsTag)) { + return NULL; } } - return sw_count; + if (e & ECS_ROLE_MASK) { + return NULL; + } + + const EcsComponent *component = ecs_get(world, e, EcsComponent); + if ((!component || !component->size) && pair) { + /* If this is a pair column and the pair is not a component, use + * the component type of the component the pair is applied to. */ + e = ECS_PAIR_OBJECT(pair); + + /* Because generations are not stored in the pair, get the currently + * alive id */ + e = ecs_get_alive(world, e); + + /* If a pair is used with a not alive id, the pair is not valid */ + ecs_assert(e != 0, ECS_INTERNAL_ERROR, NULL); + + component = ecs_get(world, e, EcsComponent); + } + + return component; } -/* Count number of bitset columns */ +/* Ensure the ids used in the columns exist */ static -int32_t bitset_column_count( +int32_t ensure_columns( + ecs_world_t *world, ecs_table_t *table) { int32_t count = 0; ecs_vector_each(table->type, ecs_entity_t, c_ptr, { ecs_entity_t component = *c_ptr; - if (ECS_HAS_ROLE(component, DISABLED)) { - if (!count) { - table->bs_column_offset = c_ptr_i; - } - count ++; + if (ECS_HAS_ROLE(component, PAIR)) { + ecs_entity_t rel = ECS_PAIR_RELATION(component); + ecs_entity_t obj = ECS_PAIR_OBJECT(component); + ecs_ensure(world, rel); + ecs_ensure(world, obj); + } else if (component & ECS_ROLE_MASK) { + ecs_entity_t e = ECS_PAIR_OBJECT(component); + ecs_ensure(world, e); + } else { + ecs_ensure(world, component); } }); @@ -2163,2330 +2224,1899 @@ int32_t bitset_column_count( } static -void init_storage_map( - ecs_table_t *table) +ecs_type_t ids_to_type( + ecs_ids_t *entities) { - ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); - if (!table->storage_table) { - return; + if (entities->count) { + ecs_vector_t *result = NULL; + ecs_vector_set_count(&result, ecs_entity_t, entities->count); + ecs_entity_t *array = ecs_vector_first(result, ecs_entity_t); + ecs_os_memcpy_n(array, entities->array, ecs_entity_t, entities->count); + return result; + } else { + return NULL; } +} - ecs_id_t *ids = ecs_vector_first(table->type, ecs_id_t); - int32_t t, ids_count = ecs_vector_count(table->type); - ecs_id_t *storage_ids = ecs_vector_first(table->storage_type, ecs_id_t); - int32_t s, storage_ids_count = ecs_vector_count(table->storage_type); - - if (!ids_count) { - table->storage_map = NULL; - return; +static +ecs_edge_t* get_edge( + ecs_graph_edges_t *edges, + ecs_id_t id) +{ + if (id < ECS_HI_COMPONENT_ID) { + if (!edges->lo) { + return NULL; + } + return &edges->lo[id]; + } else { + if (!edges->hi) { + return NULL; + } + return ecs_map_get(edges->hi, ecs_edge_t, id); } +} - table->storage_map = ecs_os_malloc_n( - int32_t, ids_count + storage_ids_count); - - int32_t *t2s = table->storage_map; - int32_t *s2t = &table->storage_map[ids_count]; - - for (s = 0, t = 0; (t < ids_count) && (s < storage_ids_count); ) { - ecs_id_t id = ids[t]; - ecs_id_t storage_id = storage_ids[s]; - - if (id == storage_id) { - t2s[t] = s; - s2t[s] = t; - } else { - t2s[t] = -1; +static +ecs_edge_t* ensure_edge( + ecs_graph_edges_t *edges, + ecs_id_t id) +{ + if (id < ECS_HI_COMPONENT_ID) { + if (!edges->lo) { + edges->lo = ecs_os_calloc_n(ecs_edge_t, ECS_HI_COMPONENT_ID); } - - /* Ids can never get ahead of storage id, as ids are a superset of the - * storage ids */ - ecs_assert(id <= storage_id, ECS_INTERNAL_ERROR, NULL); - - t += (id <= storage_id); - s += (id == storage_id); + return &edges->lo[id]; + } else { + if (!edges->hi) { + edges->hi = ecs_map_new(ecs_edge_t, 1); + } + return ecs_map_ensure(edges->hi, ecs_edge_t, id); } +} - /* Storage ids is always a subset of ids, so all should be iterated */ - ecs_assert(s == storage_ids_count, ECS_INTERNAL_ERROR, NULL); +static +void init_edges( + ecs_graph_edges_t *edges) +{ + edges->lo = NULL; + edges->hi = NULL; +} - /* Initialize remainder of type -> storage_type map */ - for (; (t < ids_count); t ++) { - t2s[t] = -1; - } +static +void init_node( + ecs_graph_node_t *node) +{ + init_edges(&node->add); + init_edges(&node->remove); } static -void init_storage_table( +void init_flags( ecs_world_t *world, ecs_table_t *table) { - int32_t i, count = ecs_vector_count(table->type); ecs_id_t *ids = ecs_vector_first(table->type, ecs_id_t); - ecs_ids_t storage_ids = { - .array = ecs_os_alloca_n(ecs_id_t, count) - }; - + int32_t count = ecs_vector_count(table->type); + + /* Iterate components to initialize table flags */ + int32_t i; for (i = 0; i < count; i ++) { ecs_id_t id = ids[i]; - if ((id == ecs_id(EcsComponent)) || - (ECS_PAIR_RELATION(id) == ecs_id(EcsIdentifier))) - { - storage_ids.array[storage_ids.count ++] = id; - continue; + /* As we're iterating over the table components, also set the table + * flags. These allow us to quickly determine if the table contains + * data that needs to be handled in a special way, like prefabs or + * containers */ + if (id <= EcsLastInternalComponentId) { + table->flags |= EcsTableHasBuiltins; } - const EcsComponent *comp = flecs_component_from_id(world, id); - if (!comp || !comp->size) { - continue; + if (id == EcsModule) { + table->flags |= EcsTableHasBuiltins; + table->flags |= EcsTableHasModule; } - storage_ids.array[storage_ids.count ++] = id; - } - - if (storage_ids.count && storage_ids.count != count) { - table->storage_table = flecs_table_find_or_create(world, &storage_ids); - table->storage_type = table->storage_table->type; - ecs_assert(table->storage_table != NULL, ECS_INTERNAL_ERROR, NULL); - } else if (storage_ids.count) { - table->storage_table = table; - table->storage_type = table->storage_table->type; - ecs_assert(table->storage_table != NULL, ECS_INTERNAL_ERROR, NULL); - } + if (id == EcsPrefab) { + table->flags |= EcsTableIsPrefab; + } - if (!table->storage_map) { - init_storage_map(table); - } -} + /* If table contains disabled entities, mark it as disabled */ + if (id == EcsDisabled) { + table->flags |= EcsTableIsDisabled; + } -void flecs_table_init_data( - ecs_world_t *world, - ecs_table_t *table) -{ - init_storage_table(world, table); + /* Does table have exclusive or columns */ + if (ECS_HAS_ROLE(id, XOR)) { + table->flags |= EcsTableHasXor; + } - int32_t sw_count = table->sw_column_count = switch_column_count(table); - int32_t bs_count = table->bs_column_count = bitset_column_count(table); + /* Does table have IsA relations */ + if (ECS_HAS_RELATION(id, EcsIsA)) { + table->flags |= EcsTableHasIsA; + } - ecs_data_t *storage = &table->storage; - ecs_type_t type = table->storage_type; + /* Does table have switch columns */ + if (ECS_HAS_ROLE(id, SWITCH)) { + table->flags |= EcsTableHasSwitch; + } - int32_t i, count = ecs_vector_count(type); + /* Does table support component disabling */ + if (ECS_HAS_ROLE(id, DISABLED)) { + table->flags |= EcsTableHasDisabled; + } - /* Root tables don't have columns */ - if (!count && !sw_count && !bs_count) { - storage->columns = NULL; + /* Does table have ChildOf relations */ + if (ECS_HAS_RELATION(id, EcsChildOf)) { + ecs_entity_t obj = ecs_pair_object(world, id); + if (obj == EcsFlecs || obj == EcsFlecsCore || + ecs_has_id(world, obj, EcsModule)) + { + /* If table contains entities that are inside one of the builtin + * modules, it contains builtin entities */ + table->flags |= EcsTableHasBuiltins; + table->flags |= EcsTableHasModule; + } + } } +} - if (count) { - ecs_entity_t *ids = ecs_vector_first(type, ecs_entity_t); - storage->columns = ecs_os_calloc_n(ecs_column_t, count); +static +void init_table( + ecs_world_t *world, + ecs_table_t *table, + ecs_ids_t *entities) +{ + table->type = ids_to_type(entities); + table->c_info = NULL; + table->flags = 0; + table->dirty_state = NULL; + table->alloc_count = 0; + table->lock = 0; - for (i = 0; i < count; i ++) { - ecs_entity_t id = ids[i]; + /* Ensure the component ids for the table exist */ + ensure_columns(world, table); - /* Bootstrap components */ - if (id == ecs_id(EcsComponent)) { - storage->columns[i].size = ECS_SIZEOF(EcsComponent); - storage->columns[i].alignment = ECS_ALIGNOF(EcsComponent); - continue; - } else if (ECS_PAIR_RELATION(id) == ecs_id(EcsIdentifier)) { - storage->columns[i].size = ECS_SIZEOF(EcsIdentifier); - storage->columns[i].alignment = ECS_ALIGNOF(EcsIdentifier); - continue; - } + table->queries = NULL; - const EcsComponent *component = flecs_component_from_id(world, id); - ecs_assert(component != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(component->size != 0, ECS_INTERNAL_ERROR, NULL); + init_node(&table->node); + init_flags(world, table); - storage->columns[i].size = flecs_itoi16(component->size); - storage->columns[i].alignment = flecs_itoi16(component->alignment); - } - } + flecs_register_table(world, table); + flecs_table_init_data(world, table); - if (sw_count) { - ecs_entity_t *ids = ecs_vector_first(table->type, ecs_entity_t); - int32_t sw_offset = table->sw_column_offset; - storage->sw_columns = ecs_os_calloc_n(ecs_sw_column_t, sw_count); + /* Register component info flags for all columns */ + flecs_table_notify(world, table, &(ecs_table_event_t){ + .kind = EcsTableComponentInfo + }); +} - for (i = 0; i < sw_count; i ++) { - ecs_entity_t e = ids[i + sw_offset]; - ecs_assert(ECS_HAS_ROLE(e, SWITCH), ECS_INTERNAL_ERROR, NULL); - e = e & ECS_COMPONENT_MASK; - const EcsType *type_ptr = ecs_get(world, e, EcsType); - ecs_assert(type_ptr != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_type_t sw_type = type_ptr->normalized; +static +ecs_table_t *create_table( + ecs_world_t *world, + ecs_ids_t *entities, + flecs_hashmap_result_t table_elem) +{ + ecs_table_t *result = flecs_sparse_add(world->store.tables, ecs_table_t); + ecs_assert(result != NULL, ECS_INTERNAL_ERROR, NULL); + result->id = flecs_sparse_last_id(world->store.tables); + init_table(world, result, entities); - ecs_entity_t *sw_array = ecs_vector_first(sw_type, ecs_entity_t); - int32_t sw_array_count = ecs_vector_count(sw_type); +#ifndef NDEBUG + char *expr = ecs_type_str(world, result->type); + ecs_dbg_2("#[green]table#[normal] [%s] created with id %d", expr, result->id); + ecs_os_free(expr); +#endif + ecs_log_push(); - ecs_switch_t *sw = flecs_switch_new( - sw_array[0], - sw_array[sw_array_count - 1], - 0); + /* Store table in table hashmap */ + *(ecs_table_t**)table_elem.value = result; - storage->sw_columns[i].data = sw; - storage->sw_columns[i].type = sw_type; - } - } + /* Set keyvalue to one that has the same lifecycle as the table */ + ecs_ids_t key = { + .array = ecs_vector_first(result->type, ecs_id_t), + .count = ecs_vector_count(result->type) + }; + *(ecs_ids_t*)table_elem.key = key; - if (bs_count) { - storage->bs_columns = ecs_os_calloc_n(ecs_bs_column_t, bs_count); - for (i = 0; i < bs_count; i ++) { - flecs_bitset_init(&storage->bs_columns[i].data); - } - } + flecs_notify_queries(world, &(ecs_query_event_t) { + .kind = EcsQueryTableMatch, + .table = result + }); + + ecs_log_pop(); + + return result; } static -ecs_flags32_t get_component_action_flags( - const ecs_type_info_t *c_info) +void add_id_to_ids( + ecs_type_t type, + ecs_entity_t add, + ecs_ids_t *out, + ecs_entity_t r_exclusive) { - ecs_flags32_t flags = 0; + int32_t count = ecs_vector_count(type); + ecs_id_t *array = ecs_vector_first(type, ecs_id_t); + bool added = false; - if (c_info->lifecycle.ctor) { - flags |= EcsTableHasCtors; - } - if (c_info->lifecycle.dtor) { - flags |= EcsTableHasDtors; + int32_t i, el = 0; + for (i = 0; i < count; i ++) { + ecs_id_t e = array[i]; + + if (!added) { + if (r_exclusive && ECS_HAS_ROLE(e, PAIR)) { + if (ECS_PAIR_RELATION(e) == r_exclusive) { + out->array[el ++] = add; + added = true; + continue; /* don't add original element */ + } + } + + if (e >= add) { + if (e != add) { + out->array[el ++] = add; + } + added = true; + } + } + + out->array[el ++] = e; + ecs_assert(el <= out->count, ECS_INTERNAL_ERROR, NULL); } - if (c_info->lifecycle.copy) { - flags |= EcsTableHasCopy; + + if (!added) { + out->array[el ++] = add; } - if (c_info->lifecycle.move) { - flags |= EcsTableHasMove; - } - return flags; + out->count = el; } -/* Check if table has instance of component, including pairs */ static -bool has_component( - ecs_world_t *world, +void remove_id_from_ids( ecs_type_t type, - ecs_entity_t component) + ecs_entity_t remove, + ecs_ids_t *out) { - ecs_entity_t *entities = ecs_vector_first(type, ecs_entity_t); - int32_t i, count = ecs_vector_count(type); + int32_t count = ecs_vector_count(type); + ecs_id_t *array = ecs_vector_first(type, ecs_id_t); + int32_t i, el = 0; for (i = 0; i < count; i ++) { - if (component == ecs_get_typeid(world, entities[i])) { - return true; + ecs_id_t e = array[i]; + if (e != remove) { + out->array[el ++] = e; + ecs_assert(el <= count, ECS_INTERNAL_ERROR, NULL); } } - - return false; + + out->count = el; } -static -void notify_component_info( - ecs_world_t *world, - ecs_table_t *table, - ecs_entity_t component) +int32_t flecs_table_switch_from_case( + const ecs_world_t *world, + const ecs_table_t *table, + ecs_entity_t add) { - ecs_type_t table_type = table->storage_type; - if (!component || has_component(world, table_type, component)){ - int32_t column_count = ecs_vector_count(table_type); - ecs_assert(!component || column_count != 0, ECS_INTERNAL_ERROR, NULL); + ecs_type_t type = table->type; + ecs_entity_t *array = ecs_vector_first(type, ecs_entity_t); - if (!column_count) { - return; - } - - if (!table->c_info) { - table->c_info = ecs_os_calloc( - ECS_SIZEOF(ecs_type_info_t*) * column_count); - } + int32_t i, count = table->sw_column_count; + ecs_assert(count != 0, ECS_INTERNAL_ERROR, NULL); - /* Reset lifecycle flags before recomputing */ - table->flags &= ~EcsTableHasLifecycle; + add = add & ECS_COMPONENT_MASK; - /* Recompute lifecycle flags */ - ecs_entity_t *array = ecs_vector_first(table_type, ecs_entity_t); - int32_t i; - for (i = 0; i < column_count; i ++) { - ecs_id_t id = array[i]; - ecs_entity_t c; + ecs_sw_column_t *sw_columns = NULL; - /* Hardcode components used in bootstrap */ - if (id == ecs_id(EcsComponent)) { - c = id; - } else if (ECS_PAIR_RELATION(id) == ecs_id(EcsIdentifier)) { - c = ecs_id(EcsIdentifier); - } else { - c = ecs_get_typeid(world, array[i]); - } - ecs_assert(c != 0, ECS_INTERNAL_ERROR, NULL); - - const ecs_type_info_t *c_info = flecs_get_c_info(world, c); - if (c_info) { - ecs_flags32_t flags = get_component_action_flags(c_info); - table->flags |= flags; + if ((sw_columns = table->storage.sw_columns)) { + /* Fast path, we can get the switch type from the column data */ + for (i = 0; i < count; i ++) { + ecs_type_t sw_type = sw_columns[i].type; + if (ecs_type_has_id(world, sw_type, add, true)) { + return i; } + } + } else { + /* Slow path, table is empty, so we'll have to get the switch types by + * actually inspecting the switch type entities. */ + for (i = 0; i < count; i ++) { + ecs_entity_t e = array[i + table->sw_column_offset]; + ecs_assert(ECS_HAS_ROLE(e, SWITCH), ECS_INTERNAL_ERROR, NULL); + e = e & ECS_COMPONENT_MASK; - /* Store pointer to c_info for fast access */ - table->c_info[i] = (ecs_type_info_t*)c_info; - } + const EcsType *type_ptr = ecs_get(world, e, EcsType); + ecs_assert(type_ptr != NULL, ECS_INTERNAL_ERROR, NULL); + + if (ecs_type_has_id( + world, type_ptr->normalized, add, true)) + { + return i; + } + } } -} -static -void notify_trigger( - ecs_world_t *world, - ecs_table_t *table, - ecs_entity_t event) -{ - (void)world; + /* If a table was not found, this is an invalid switch case */ + ecs_abort(ECS_TYPE_INVALID_CASE, NULL); - if (event == EcsOnAdd) { - table->flags |= EcsTableHasOnAdd; - } else if (event == EcsOnRemove) { - table->flags |= EcsTableHasOnRemove; - } else if (event == EcsOnSet) { - table->flags |= EcsTableHasOnSet; - } else if (event == EcsUnSet) { - table->flags |= EcsTableHasUnSet; - } + return -1; } static -void run_on_remove( - ecs_world_t *world, - ecs_table_t *table, - ecs_data_t *data) +void ids_append( + ecs_ids_t *ids, + ecs_id_t id) { - int32_t count = ecs_vector_count(data->entities); - if (count) { - ecs_ids_t removed = { - .array = ecs_vector_first(table->type, ecs_id_t), - .count = ecs_vector_count(table->type) - }; - - ecs_table_diff_t diff = { - .removed = removed, - .un_set = removed - }; - - flecs_notify_on_remove(world, table, NULL, 0, count, &diff); - } + ids->array = ecs_os_realloc_n(ids->array, ecs_id_t, ids->count + 1); + ids->array[ids->count ++] = id; } -/* -- Private functions -- */ - -/* If table goes from 0 to >0 entities or from >0 entities to 0 entities notify - * queries. This allows systems associated with queries to move inactive tables - * out of the main loop. */ static -void table_activate( +void diff_insert_isa( ecs_world_t *world, ecs_table_t *table, - bool activate) + ecs_table_diff_t *base_diff, + ecs_ids_t *append_to, + ecs_ids_t *append_from, + ecs_id_t add) { - ecs_vector_t *queries = table->queries; - ecs_query_t **buffer = ecs_vector_first(queries, ecs_query_t*); - int32_t i, count = ecs_vector_count(queries); + ecs_entity_t base = ecs_pair_object(world, add); + ecs_table_t *base_table = ecs_get_table(world, base); + if (!base_table) { + return; + } + + ecs_type_t base_type = base_table->type, type = table->type; + ecs_table_t *table_wo_base = base_table; + /* If the table does not have a component from the base, it should + * trigger an OnSet */ + ecs_id_t *ids = ecs_vector_first(base_type, ecs_id_t); + int32_t j, i, count = ecs_vector_count(base_type); for (i = 0; i < count; i ++) { - flecs_query_notify(world, buffer[i], &(ecs_query_event_t) { - .kind = activate ? EcsQueryTableNonEmpty : EcsQueryTableEmpty, - .table = table - }); - } + ecs_id_t id = ids[i]; - flecs_table_set_empty(world, table); + if (ECS_HAS_RELATION(id, EcsIsA)) { + /* The base has an IsA relation. Find table without the base, which + * gives us the list of ids the current base inherits and doesn't + * override. This saves us from having to recursively check for each + * base in the hierarchy whether the component is overridden. */ + table_wo_base = flecs_table_traverse_remove( + world, table_wo_base, &id, base_diff); + + /* Because we removed, the ids are stored in un_set vs. on_set */ + for (j = 0; j < append_from->count; j ++) { + ecs_id_t base_id = append_from->array[j]; + /* We still have to make sure the id isn't overridden by the + * current table */ + if (ecs_type_match(world, table, type, 0, base_id, 0, 0, 0, + NULL, NULL, NULL) == -1) + { + ids_append(append_to, base_id); + } + } + + continue; + } + + /* Identifiers are not inherited */ + if (ECS_HAS_RELATION(id, ecs_id(EcsIdentifier))) { + continue; + } + + if (!ecs_get_typeid(world, id)) { + continue; + } + + if (ecs_type_match(world, table, type, 0, id, 0, 0, 0, + NULL, NULL, NULL) == -1) + { + ids_append(append_to, id); + } + } } -/* This function is called when a query is matched with a table. A table keeps - * a list of tables that match so that they can be notified when the table - * becomes empty / non-empty. */ static -void register_query( +void diff_insert_added_isa( ecs_world_t *world, ecs_table_t *table, - ecs_query_t *query) + ecs_table_diff_t *diff, + ecs_id_t id) { - (void)world; -#ifndef NDEBUG - /* Sanity check if query has already been added */ - int32_t i, count = ecs_vector_count(table->queries); - for (i = 0; i < count; i ++) { - ecs_query_t **q = ecs_vector_get(table->queries, ecs_query_t*, i); - ecs_assert(*q != query, ECS_INTERNAL_ERROR, NULL); - } -#endif - - ecs_query_t **q = ecs_vector_add(&table->queries, ecs_query_t*); - if (q) *q = query; + ecs_table_diff_t base_diff; + diff_insert_isa(world, table, &base_diff, &diff->on_set, + &base_diff.un_set, id); } -/* This function is called when a query is unmatched with a table. This can - * happen for queries that have shared components expressions in their signature - * and those shared components changed (for example, a base removed a comp). */ static -void unregister_query( +void diff_insert_removed_isa( ecs_world_t *world, ecs_table_t *table, - ecs_query_t *query) + ecs_table_diff_t *diff, + ecs_id_t id) { - (void)world; - - int32_t i, count = ecs_vector_count(table->queries); - for (i = 0; i < count; i ++) { - ecs_query_t **q = ecs_vector_get(table->queries, ecs_query_t*, i); - if (*q == query) { - break; - } - } - - /* Query must have been registered with table */ - ecs_assert(i != count, ECS_INTERNAL_ERROR, NULL); - - /* Remove query */ - ecs_vector_remove(table->queries, ecs_query_t*, i); + ecs_table_diff_t base_diff; + diff_insert_isa(world, table, &base_diff, &diff->un_set, + &base_diff.un_set, id); } static -void ctor_component( +void diff_insert_added( ecs_world_t *world, - ecs_type_info_t *cdata, - ecs_column_t *column, - ecs_entity_t *entities, - int32_t row, - int32_t count) + ecs_table_t *table, + ecs_table_diff_t *diff, + ecs_id_t id) { - /* A new component is constructed */ - ecs_xtor_t ctor; - if (cdata && (ctor = cdata->lifecycle.ctor)) { - void *ctx = cdata->lifecycle.ctx; - int16_t size = column->size; - int16_t alignment = column->alignment; - - void *ptr = ecs_vector_get_t(column->data, size, alignment, row); + diff->added.array[diff->added.count ++] = id; - ctor(world, cdata->component, entities, ptr, - flecs_itosize(size), count, ctx); + if (ECS_HAS_RELATION(id, EcsIsA)) { + diff_insert_added_isa(world, table, diff, id); } } static -void dtor_component( +void diff_insert_removed( ecs_world_t *world, - ecs_type_info_t *cdata, - ecs_column_t *column, - ecs_entity_t *entities, - int32_t row, - int32_t count) + ecs_table_t *table, + ecs_table_diff_t *diff, + ecs_id_t id) { - if (!count) { + diff->removed.array[diff->removed.count ++] = id; + + if (ECS_HAS_RELATION(id, EcsIsA)) { + /* Removing an IsA relation also "removes" all components from the + * instance. Any id from base that's not overridden should be UnSet. */ + diff_insert_removed_isa(world, table, diff, id); return; } - - /* An old component is destructed */ - ecs_xtor_t dtor; - if (cdata && (dtor = cdata->lifecycle.dtor)) { - void *ctx = cdata->lifecycle.ctx; - int16_t size = column->size; - int16_t alignment = column->alignment; - ecs_assert(column->data != NULL, ECS_INTERNAL_ERROR, NULL); - void *ptr = ecs_vector_get_t(column->data, size, alignment, row); - ecs_assert(ptr != NULL, ECS_INTERNAL_ERROR, NULL); + if (table->flags & EcsTableHasIsA) { + if (!ecs_get_typeid(world, id)) { + /* Do nothing if id is not a component */ + return; + } - dtor(world, cdata->component, &entities[row], ptr, - flecs_itosize(size), count, ctx); + /* If next table has a base and component is removed, check if + * the removed component was an override. Removed overrides reexpose the + * base component, thus "changing" the value which requires an OnSet. */ + if (ecs_type_match(world, table, table->type, 0, id, EcsIsA, + 1, 0, NULL, NULL, NULL) != -1) + { + ids_append(&diff->on_set, id); + return; + } + } + + if (ecs_get_typeid(world, id) != 0) { + ids_append(&diff->un_set, id); } } static -void dtor_all_components( +void compute_table_diff( ecs_world_t *world, - ecs_table_t *table, - ecs_data_t *data, - int32_t row, - int32_t count, - bool update_entity_index, - bool is_delete) + ecs_table_t *node, + ecs_table_t *next, + ecs_edge_t *edge, + ecs_id_t id) { - /* Can't delete and not update the entity index */ - ecs_assert(!is_delete || update_entity_index, ECS_INTERNAL_ERROR, NULL); - - ecs_record_t **records = ecs_vector_first(data->record_ptrs, ecs_record_t*); - ecs_entity_t *entities = ecs_vector_first(data->entities, ecs_entity_t); - int32_t i, c, end = row + count; - int32_t column_count = ecs_vector_count(table->storage_type); - - (void)records; - - /* If table has components with destructors, iterate component columns */ - if (table->flags & EcsTableHasDtors) { - /* Prevent the storage from getting modified while deleting */ - ecs_defer_begin(world); + if (node == next) { + return; + } - /* Throw up a lock just to be sure */ - table->lock = true; + ecs_type_t node_type = node->type; + ecs_type_t next_type = next->type; - /* Iterate entities first, then components. This ensures that only one - * entity is invalidated at a time, which ensures that destructors can - * safely access other entities. */ - for (i = row; i < end; i ++) { - for (c = 0; c < column_count; c++) { - ecs_column_t *column = &data->columns[c]; - dtor_component(world, table->c_info[c], column, entities, i, 1); - } + ecs_id_t *ids_node = ecs_vector_first(node_type, ecs_id_t); + ecs_id_t *ids_next = ecs_vector_first(next_type, ecs_id_t); + int32_t i_node = 0, node_count = ecs_vector_count(node_type); + int32_t i_next = 0, next_count = ecs_vector_count(next_type); + int32_t added_count = 0; + int32_t removed_count = 0; + bool trivial_edge = !ECS_HAS_RELATION(id, EcsIsA) && + !(node->flags & EcsTableHasIsA) && !(next->flags & EcsTableHasIsA); - /* Update entity index after invoking destructors so that entity can - * be safely used in destructor callbacks. */ - if (update_entity_index) { - ecs_entity_t e = entities[i]; - ecs_assert(!e || ecs_is_valid(world, e), - ECS_INTERNAL_ERROR, NULL); - ecs_assert(!e || records[i] == ecs_eis_get(world, e), - ECS_INTERNAL_ERROR, NULL); - ecs_assert(!e || records[i]->table == table, - ECS_INTERNAL_ERROR, NULL); + /* First do a scan to see how big the diff is, so we don't have to realloc + * or alloc more memory than required. */ + for (; i_node < node_count && i_next < next_count; ) { + ecs_id_t id_node = ids_node[i_node]; + ecs_id_t id_next = ids_next[i_next]; - if (is_delete) { - ecs_eis_delete(world, e); - ecs_assert(ecs_is_valid(world, e) == false, - ECS_INTERNAL_ERROR, NULL); - } else { - // If this is not a delete, clear the entity index record - ecs_record_t r = {NULL, 0}; - ecs_eis_set(world, e, &r); - } - } else { - /* This should only happen in rare cases, such as when the data - * cleaned up is not part of the world (like with snapshots) */ - } - } + bool added = id_next < id_node; + bool removed = id_node < id_next; - table->lock = false; - - ecs_defer_end(world); + trivial_edge &= !added || id_next == id; + trivial_edge &= !removed || id_node == id; - /* If table does not have destructors, just update entity index */ - } else if (update_entity_index) { - if (is_delete) { - for (i = row; i < end; i ++) { - ecs_entity_t e = entities[i]; - ecs_assert(!e || ecs_is_valid(world, e), ECS_INTERNAL_ERROR, NULL); - ecs_assert(!e || records[i] == ecs_eis_get(world, e), - ECS_INTERNAL_ERROR, NULL); - ecs_assert(!e || records[i]->table == table, - ECS_INTERNAL_ERROR, NULL); + added_count += added; + removed_count += removed; - ecs_eis_delete(world, e); - ecs_assert(!ecs_is_valid(world, e), ECS_INTERNAL_ERROR, NULL); - } - } else { - for (i = row; i < end; i ++) { - ecs_entity_t e = entities[i]; - ecs_assert(!e || ecs_is_valid(world, e), ECS_INTERNAL_ERROR, NULL); - ecs_assert(!e || records[i] == ecs_eis_get(world, e), - ECS_INTERNAL_ERROR, NULL); - ecs_assert(!e || records[i]->table == table, - ECS_INTERNAL_ERROR, NULL); - ecs_record_t r = {NULL, 0}; - ecs_eis_set(world, e, &r); - } - } + i_node += id_node <= id_next; + i_next += id_next <= id_node; } -} -static -void fini_data( - ecs_world_t *world, - ecs_table_t *table, - ecs_data_t *data, - bool do_on_remove, - bool update_entity_index, - bool is_delete, - bool deactivate) -{ - ecs_assert(!table->lock, ECS_LOCKED_STORAGE, NULL); + added_count += next_count - i_next; + removed_count += node_count - i_node; - if (!data) { + trivial_edge &= (added_count + removed_count) <= 1; + + if (trivial_edge) { + /* If edge is trivial there's no need to create a diff element for it. + * Encode in the id whether the id is a tag or not, so that wen can + * still tell whether an UnSet handler should be called or not. */ + edge->diff_index = -1 * (ecs_get_typeid(world, id) == 0); return; } - if (do_on_remove) { - run_on_remove(world, table, data); + ecs_table_diff_t *diff = ecs_vector_add(&node->node.diffs, ecs_table_diff_t); + ecs_os_memset_t(diff, 0, ecs_table_diff_t); + edge->diff_index = ecs_vector_count(node->node.diffs); + if (added_count) { + diff->added.array = ecs_os_malloc_n(ecs_id_t, added_count); + diff->added.count = 0; + diff->added.size = added_count; } - - int32_t count = flecs_table_data_count(data); - if (count) { - dtor_all_components(world, table, data, 0, count, - update_entity_index, is_delete); + if (removed_count) { + diff->removed.array = ecs_os_malloc_n(ecs_id_t, removed_count); + diff->removed.count = 0; + diff->removed.size = removed_count; } - /* Sanity check */ - ecs_assert(ecs_vector_count(data->record_ptrs) == - ecs_vector_count(data->entities), ECS_INTERNAL_ERROR, NULL); - - ecs_column_t *columns = data->columns; - if (columns) { - int32_t c, column_count = ecs_vector_count(table->storage_type); - for (c = 0; c < column_count; c ++) { - /* Sanity check */ - ecs_assert(!columns[c].data || (ecs_vector_count(columns[c].data) == - ecs_vector_count(data->entities)), ECS_INTERNAL_ERROR, NULL); + for (i_node = 0, i_next = 0; i_node < node_count && i_next < next_count; ) { + ecs_id_t id_node = ids_node[i_node]; + ecs_id_t id_next = ids_next[i_next]; - ecs_vector_free(columns[c].data); + if (id_next < id_node) { + diff_insert_added(world, node, diff, id_next); + } else if (id_node < id_next) { + diff_insert_removed(world, next, diff, id_node); } - ecs_os_free(columns); - data->columns = NULL; - } - ecs_sw_column_t *sw_columns = data->sw_columns; - if (sw_columns) { - int32_t c, column_count = table->sw_column_count; - for (c = 0; c < column_count; c ++) { - flecs_switch_free(sw_columns[c].data); - } - ecs_os_free(sw_columns); - data->sw_columns = NULL; + i_node += id_node <= id_next; + i_next += id_next <= id_node; } - ecs_bs_column_t *bs_columns = data->bs_columns; - if (bs_columns) { - int32_t c, column_count = table->bs_column_count; - for (c = 0; c < column_count; c ++) { - flecs_bitset_deinit(&bs_columns[c].data); - } - ecs_os_free(bs_columns); - data->bs_columns = NULL; + for (; i_next < next_count; i_next ++) { + diff_insert_added(world, node, diff, ids_next[i_next]); } - - ecs_vector_free(data->entities); - ecs_vector_free(data->record_ptrs); - - data->entities = NULL; - data->record_ptrs = NULL; - - if (deactivate && count) { - table_activate(world, table, false); + for (; i_node < node_count; i_node ++) { + diff_insert_removed(world, next, diff, ids_node[i_node]); } -} -/* Cleanup, no OnRemove, don't update entity index, don't deactivate table */ -void flecs_table_clear_data( - ecs_world_t *world, - ecs_table_t *table, - ecs_data_t *data) -{ - fini_data(world, table, data, false, false, false, false); + ecs_assert(diff->added.count == added_count, ECS_INTERNAL_ERROR, NULL); + ecs_assert(diff->removed.count == removed_count, ECS_INTERNAL_ERROR, NULL); } -/* Cleanup, no OnRemove, clear entity index, deactivate table */ -void flecs_table_clear_entities_silent( +static +ecs_table_t* find_or_create_table_with_id( ecs_world_t *world, - ecs_table_t *table) + ecs_table_t *node, + ecs_entity_t id) { - fini_data(world, table, &table->storage, false, true, false, true); -} + /* If table has one or more switches and this is a case, return self */ + if (ECS_HAS_ROLE(id, CASE)) { + ecs_assert((node->flags & EcsTableHasSwitch) != 0, + ECS_TYPE_INVALID_CASE, NULL); + return node; + } else { + ecs_type_t type = node->type; + int32_t count = ecs_vector_count(type); + ecs_entity_t r_exclusive = 0; -/* Cleanup, run OnRemove, clear entity index, deactivate table */ -void flecs_table_clear_entities( - ecs_world_t *world, - ecs_table_t *table) -{ - fini_data(world, table, &table->storage, true, true, false, true); -} + if (ECS_HAS_ROLE(id, PAIR)) { + ecs_entity_t r = ecs_pair_relation(world, id); + if (ecs_has_id(world, r, EcsExclusive)) { + r_exclusive = (uint32_t)r; + } + } -/* Cleanup, run OnRemove, delete from entity index, deactivate table */ -void flecs_table_delete_entities( - ecs_world_t *world, - ecs_table_t *table) -{ - fini_data(world, table, &table->storage, true, true, true, true); -} + ecs_ids_t ids = { + .array = ecs_os_alloca_n(ecs_id_t, count + 1), + .count = count + 1 + }; -/* Unset all components in table. This function is called before a table is - * deleted, and invokes all UnSet handlers, if any */ -void flecs_table_remove_actions( - ecs_world_t *world, - ecs_table_t *table) -{ - (void)world; - run_on_remove(world, table, &table->storage); + add_id_to_ids(type, id, &ids, r_exclusive); + + return flecs_table_find_or_create(world, &ids); + } } -/* Free table resources. */ -void flecs_table_free( +static +ecs_table_t* find_or_create_table_without_id( ecs_world_t *world, - ecs_table_t *table) + ecs_table_t *node, + ecs_entity_t id) { - ecs_assert(!table->lock, ECS_LOCKED_STORAGE, NULL); - (void)world; - -#ifndef NDEBUG - char *expr = ecs_type_str(world, table->type); - ecs_dbg_2("#[green]table#[reset] [%s] deleted", expr); - ecs_os_free(expr); -#endif - - /* Cleanup data, no OnRemove, delete from entity index, don't deactivate */ - fini_data(world, table, &table->storage, false, true, true, false); - - flecs_table_clear_edges(world, table); + /* If table has one or more switches and this is a case, return self */ + if (ECS_HAS_ROLE(id, CASE)) { + ecs_assert((node->flags & EcsTableHasSwitch) != 0, + ECS_TYPE_INVALID_CASE, NULL); + return node; + } else { + ecs_type_t type = node->type; + int32_t count = ecs_vector_count(type); - flecs_unregister_table(world, table); + ecs_ids_t ids = { + .array = ecs_os_alloca_n(ecs_id_t, count), + .count = count + }; - ecs_vector_free(table->queries); - ecs_os_free(table->dirty_state); - ecs_os_free(table->storage_map); + remove_id_from_ids(type, id, &ids); - if (table->c_info) { - ecs_os_free(table->c_info); + return flecs_table_find_or_create(world, &ids);; } - - table->id = 0; -} - -/* Free table type. Do this separately from freeing the table as types can be - * in use by application destructors. */ -void flecs_table_free_type( - ecs_table_t *table) -{ - ecs_vector_free((ecs_vector_t*)table->type); } -/* Reset a table to its initial state. */ -void flecs_table_reset( +static +ecs_table_t* find_or_create_table_with_isa( ecs_world_t *world, - ecs_table_t *table) + ecs_table_t *node, + ecs_entity_t base) { - ecs_assert(!table->lock, ECS_LOCKED_STORAGE, NULL); - flecs_table_clear_edges(world, table); -} + ecs_type_t base_type = ecs_get_type(world, base); + ecs_id_t *ids = ecs_vector_first(base_type, ecs_id_t); + int32_t i, count = ecs_vector_count(base_type); -static -void mark_table_dirty( - ecs_table_t *table, - int32_t index) -{ - if (table->dirty_state) { - table->dirty_state[index] ++; + /* Start from back, as roles have high ids */ + for (i = count - 1; i >= 0; i --) { + ecs_id_t id = ids[i]; + if (!(id & ECS_ROLE_MASK)) { /* early out if we found everything */ + break; + } + + if (ECS_HAS_RELATION(id, EcsIsA)) { + ecs_entity_t base_of_base = ecs_pair_object(world, id); + node = find_or_create_table_with_isa(world, node, base_of_base); + } + + if (ECS_HAS_ROLE(id, OVERRIDE)) { + /* Override found, add it to table */ + id &= ECS_COMPONENT_MASK; + node = flecs_table_traverse_add(world, node, &id, NULL); + } } + + return node; } -void flecs_table_mark_dirty( +static +ecs_table_t* find_or_create_table_without( ecs_world_t *world, - ecs_table_t *table, - ecs_entity_t component) + ecs_table_t *node, + ecs_edge_t *edge, + ecs_id_t id) { - ecs_assert(!table->lock, ECS_LOCKED_STORAGE, NULL); - ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_table_t *next = find_or_create_table_without_id(world, node, id); - if (table->dirty_state) { - int32_t index = ecs_type_match(world, table->storage_table, - table->storage_type, 0, component, 0, 0, 0, NULL, NULL, NULL); - ecs_assert(index != -1, ECS_INTERNAL_ERROR, NULL); - table->dirty_state[index + 1] ++; + edge->next = next; + + compute_table_diff(world, node, next, edge, id); + + if (node != next) { + flecs_register_remove_ref(world, node, id); } + + return next; } static -void move_switch_columns( - ecs_table_t *new_table, - ecs_data_t *new_data, - int32_t new_index, - ecs_table_t *old_table, - ecs_data_t *old_data, - int32_t old_index, - int32_t count) +ecs_table_t* find_or_create_table_with( + ecs_world_t *world, + ecs_table_t *node, + ecs_edge_t *edge, + ecs_id_t id) { - int32_t i_old = 0, old_column_count = old_table->sw_column_count; - int32_t i_new = 0, new_column_count = new_table->sw_column_count; + ecs_table_t *next = find_or_create_table_with_id(world, node, id); - if (!old_column_count || !new_column_count) { - return; + if (ECS_HAS_ROLE(id, PAIR) && ECS_PAIR_RELATION(id) == EcsIsA) { + ecs_entity_t base = ecs_pair_object(world, id); + next = find_or_create_table_with_isa(world, next, base); } - ecs_sw_column_t *old_columns = old_data->sw_columns; - ecs_sw_column_t *new_columns = new_data->sw_columns; - - ecs_type_t new_type = new_table->type; - ecs_type_t old_type = old_table->type; + edge->next = next; - int32_t offset_new = new_table->sw_column_offset; - int32_t offset_old = old_table->sw_column_offset; + compute_table_diff(world, node, next, edge, id); - ecs_id_t *new_ids = ecs_vector_first(new_type, ecs_id_t); - ecs_id_t *old_ids = ecs_vector_first(old_type, ecs_id_t); + if (node != next) { + flecs_register_add_ref(world, node, id); + } - for (; (i_new < new_column_count) && (i_old < old_column_count);) { - ecs_entity_t new_id = new_ids[i_new + offset_new]; - ecs_entity_t old_id = old_ids[i_old + offset_old]; + return next; +} - if (new_id == old_id) { - ecs_switch_t *old_switch = old_columns[i_old].data; - ecs_switch_t *new_switch = new_columns[i_new].data; +static +void populate_diff( + ecs_table_t *table, + ecs_edge_t *edge, + ecs_id_t *add_ptr, + ecs_id_t *remove_ptr, + ecs_table_diff_t *out) +{ + if (out) { + int32_t di = edge->diff_index; + if (di > 0) { + ecs_assert(!add_ptr || !ECS_HAS_ROLE(add_ptr[0], CASE), + ECS_INTERNAL_ERROR, NULL); + ecs_assert(!remove_ptr || !ECS_HAS_ROLE(remove_ptr[0], CASE), + ECS_INTERNAL_ERROR, NULL); + *out = ecs_vector_first(table->node.diffs, ecs_table_diff_t)[di - 1]; + } else { + out->on_set.count = 0; - flecs_switch_ensure(new_switch, new_index + count); + if (add_ptr) { + out->added.array = add_ptr; + out->added.count = 1; + } else { + out->added.count = 0; + } - int i; - for (i = 0; i < count; i ++) { - uint64_t value = flecs_switch_get(old_switch, old_index + i); - flecs_switch_set(new_switch, new_index + i, value); + if (remove_ptr) { + out->removed.array = remove_ptr; + out->removed.count = 1; + if (di == 0) { + out->un_set.array = remove_ptr; + out->un_set.count = 1; + } else { + out->un_set.count = 0; + } + } else { + out->removed.count = 0; + out->un_set.count = 0; } } - - i_new += new_id <= old_id; - i_old += new_id >= old_id; } } -static -void move_bitset_columns( - ecs_table_t *new_table, - ecs_data_t *new_data, - int32_t new_index, - ecs_table_t *old_table, - ecs_data_t *old_data, - int32_t old_index, - int32_t count) +ecs_table_t* flecs_table_traverse_remove( + ecs_world_t *world, + ecs_table_t *node, + ecs_id_t *id_ptr, + ecs_table_diff_t *diff) { - int32_t i_old = 0, old_column_count = old_table->bs_column_count; - int32_t i_new = 0, new_column_count = new_table->bs_column_count; - - if (!old_column_count || !new_column_count) { - return; - } - - ecs_bs_column_t *old_columns = old_data->bs_columns; - ecs_bs_column_t *new_columns = new_data->bs_columns; - - ecs_type_t new_type = new_table->type; - ecs_type_t old_type = old_table->type; - - int32_t offset_new = new_table->bs_column_offset; - int32_t offset_old = old_table->bs_column_offset; + ecs_poly_assert(world, ecs_world_t); - ecs_entity_t *new_components = ecs_vector_first(new_type, ecs_entity_t); - ecs_entity_t *old_components = ecs_vector_first(old_type, ecs_entity_t); + node = node ? node : &world->store.root; - for (; (i_new < new_column_count) && (i_old < old_column_count);) { - ecs_entity_t new_component = new_components[i_new + offset_new]; - ecs_entity_t old_component = old_components[i_old + offset_old]; + /* Removing 0 from an entity is not valid */ + ecs_check(id_ptr != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(id_ptr[0] != 0, ECS_INVALID_PARAMETER, NULL); - if (new_component == old_component) { - ecs_bitset_t *old_bs = &old_columns[i_old].data; - ecs_bitset_t *new_bs = &new_columns[i_new].data; + ecs_id_t id = id_ptr[0]; + ecs_edge_t *edge = ensure_edge(&node->node.remove, id); + ecs_table_t *next = edge->next; - flecs_bitset_ensure(new_bs, new_index + count); + if (!next) { + next = find_or_create_table_without(world, node, edge, id); + ecs_assert(next != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(edge->next != NULL, ECS_INTERNAL_ERROR, NULL); + } - int i; - for (i = 0; i < count; i ++) { - uint64_t value = flecs_bitset_get(old_bs, old_index + i); - flecs_bitset_set(new_bs, new_index + i, value); - } - } + populate_diff(node, edge, NULL, id_ptr, diff); - i_new += new_component <= old_component; - i_old += new_component >= old_component; - } + return next; +error: + return NULL; } -static -void grow_column( +ecs_table_t* flecs_table_traverse_add( ecs_world_t *world, - ecs_entity_t *entities, - ecs_column_t *column, - ecs_type_info_t *c_info, - int32_t to_add, - int32_t new_size, - bool construct) + ecs_table_t *node, + ecs_id_t *id_ptr, + ecs_table_diff_t *diff) { - ecs_vector_t *vec = column->data; - int16_t alignment = column->alignment; + ecs_poly_assert(world, ecs_world_t); - int32_t size = column->size; - int32_t count = ecs_vector_count(vec); - int32_t old_size = ecs_vector_size(vec); - int32_t new_count = count + to_add; - bool can_realloc = new_size != old_size; + node = node ? node : &world->store.root; - ecs_assert(new_size >= new_count, ECS_INTERNAL_ERROR, NULL); + /* Adding 0 to an entity is not valid */ + ecs_check(id_ptr != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(id_ptr[0] != 0, ECS_INVALID_PARAMETER, NULL); - /* If the array could possibly realloc and the component has a move action - * defined, move old elements manually */ - ecs_move_t move; - if (c_info && count && can_realloc && (move = c_info->lifecycle.move)) { - ecs_xtor_t ctor = c_info->lifecycle.ctor; - ecs_assert(ctor != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_id_t id = id_ptr[0]; + ecs_edge_t *edge = ensure_edge(&node->node.add, id); + ecs_table_t *next = edge->next; - /* Create new vector */ - ecs_vector_t *new_vec = ecs_vector_new_t(size, alignment, new_size); - ecs_vector_set_count_t(&new_vec, size, alignment, new_count); + if (!next) { + next = find_or_create_table_with(world, node, edge, id); + ecs_assert(next != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(edge->next != NULL, ECS_INTERNAL_ERROR, NULL); + } - void *old_buffer = ecs_vector_first_t( - vec, size, alignment); + populate_diff(node, edge, id_ptr, NULL, diff); - void *new_buffer = ecs_vector_first_t( - new_vec, size, alignment); + return next; +error: + return NULL; +} - /* First construct elements (old and new) in new buffer */ - ctor(world, c_info->component, entities, new_buffer, - flecs_itosize(size), construct ? new_count : count, - c_info->lifecycle.ctx); - - /* Move old elements */ - move(world, c_info->component, entities, entities, - new_buffer, old_buffer, flecs_itosize(size), count, - c_info->lifecycle.ctx); +static +bool ecs_entity_array_is_ordered( + const ecs_ids_t *entities) +{ + ecs_entity_t prev = 0; + ecs_entity_t *array = entities->array; + int32_t i, count = entities->count; - /* Free old vector */ - ecs_vector_free(vec); - column->data = new_vec; - } else { - /* If array won't realloc or has no move, simply add new elements */ - if (can_realloc) { - ecs_vector_set_size_t(&vec, size, alignment, new_size); + for (i = 0; i < count; i ++) { + if (!array[i] && !prev) { + continue; + } + if (array[i] <= prev) { + return false; } + prev = array[i]; + } - void *elem = ecs_vector_addn_t(&vec, size, alignment, to_add); + return true; +} - ecs_xtor_t ctor; - if (construct && c_info && (ctor = c_info->lifecycle.ctor)) { - /* If new elements need to be constructed and component has a - * constructor, construct */ - ctor(world, c_info->component, &entities[count], elem, - flecs_itosize(size), to_add, c_info->lifecycle.ctx); +static +int32_t ecs_entity_array_dedup( + ecs_entity_t *array, + int32_t count) +{ + int32_t j, k; + ecs_entity_t prev = array[0]; + + for (k = j = 1; k < count; j ++, k++) { + ecs_entity_t e = array[k]; + if (e == prev) { + k ++; } - column->data = vec; + array[j] = e; + prev = e; } - ecs_assert(ecs_vector_size(column->data) == new_size, - ECS_INTERNAL_ERROR, NULL); + return count - (k - j); } static -int32_t grow_data( +ecs_table_t* find_or_create( ecs_world_t *world, - ecs_table_t *table, - ecs_data_t *data, - int32_t to_add, - int32_t size, - const ecs_entity_t *ids) -{ - ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(data != NULL, ECS_INTERNAL_ERROR, NULL); + const ecs_ids_t *ids) +{ + ecs_poly_assert(world, ecs_world_t); - int32_t cur_count = flecs_table_data_count(data); - int32_t column_count = ecs_vector_count(table->storage_type); - int32_t sw_column_count = table->sw_column_count; - int32_t bs_column_count = table->bs_column_count; - ecs_column_t *columns = data->columns; - ecs_sw_column_t *sw_columns = data->sw_columns; - ecs_bs_column_t *bs_columns = data->bs_columns; + /* Make sure array is ordered and does not contain duplicates */ + int32_t type_count = ids->count; + ecs_id_t *ordered = NULL; - /* Add record to record ptr array */ - ecs_vector_set_size(&data->record_ptrs, ecs_record_t*, size); - ecs_record_t **r = ecs_vector_addn(&data->record_ptrs, ecs_record_t*, to_add); - ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); - if (ecs_vector_size(data->record_ptrs) > size) { - size = ecs_vector_size(data->record_ptrs); + if (!type_count) { + return &world->store.root; } - /* Add entity to column with entity ids */ - ecs_vector_set_size(&data->entities, ecs_entity_t, size); - ecs_entity_t *e = ecs_vector_addn(&data->entities, ecs_entity_t, to_add); - ecs_assert(e != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(ecs_vector_size(data->entities) == size, ECS_INTERNAL_ERROR, NULL); - - /* Initialize entity ids and record ptrs */ - int32_t i; - if (ids) { - for (i = 0; i < to_add; i ++) { - e[i] = ids[i]; - } + if (!ecs_entity_array_is_ordered(ids)) { + ecs_size_t size = ECS_SIZEOF(ecs_entity_t) * type_count; + ordered = ecs_os_alloca(size); + ecs_os_memcpy(ordered, ids->array, size); + qsort(ordered, (size_t)type_count, sizeof(ecs_entity_t), + flecs_entity_compare_qsort); + type_count = ecs_entity_array_dedup(ordered, type_count); } else { - ecs_os_memset(e, 0, ECS_SIZEOF(ecs_entity_t) * to_add); - } - ecs_os_memset(r, 0, ECS_SIZEOF(ecs_record_t*) * to_add); - - /* Add elements to each column array */ - ecs_type_info_t **c_info_array = table->c_info; - ecs_entity_t *entities = ecs_vector_first(data->entities, ecs_entity_t); - for (i = 0; i < column_count; i ++) { - ecs_column_t *column = &columns[i]; - ecs_assert(column->size != 0, ECS_INTERNAL_ERROR, NULL); - - ecs_type_info_t *c_info = NULL; - if (c_info_array) { - c_info = c_info_array[i]; - } - - grow_column(world, entities, column, c_info, to_add, size, true); - ecs_assert(ecs_vector_size(columns[i].data) == size, - ECS_INTERNAL_ERROR, NULL); + ordered = ids->array; } - /* Add elements to each switch column */ - for (i = 0; i < sw_column_count; i ++) { - ecs_switch_t *sw = sw_columns[i].data; - flecs_switch_addn(sw, to_add); - } + ecs_ids_t ordered_ids = { + .array = ordered, + .count = type_count + }; - /* Add elements to each bitset column */ - for (i = 0; i < bs_column_count; i ++) { - ecs_bitset_t *bs = &bs_columns[i].data; - flecs_bitset_addn(bs, to_add); + ecs_table_t *table; + flecs_hashmap_result_t elem = flecs_hashmap_ensure( + world->store.table_map, &ordered_ids, ecs_table_t*); + if ((table = *(ecs_table_t**)elem.value)) { + return table; } - /* If the table is monitored indicate that there has been a change */ - mark_table_dirty(table, 0); - - if (!world->is_readonly && !cur_count) { - table_activate(world, table, true); - } + /* If we get here, table needs to be created which is only allowed when the + * application is not currently in progress */ + ecs_assert(!world->is_readonly, ECS_INTERNAL_ERROR, NULL); - table->alloc_count ++; + /* If we get here, the table has not been found, so create it. */ + ecs_table_t *result = create_table(world, &ordered_ids, elem); + + ecs_assert(ordered_ids.count == ecs_vector_count(result->type), + ECS_INTERNAL_ERROR, NULL); - /* Return index of first added entity */ - return cur_count; + return result; } -static -void fast_append( - ecs_column_t *columns, - int32_t column_count) +ecs_table_t* flecs_table_find_or_create( + ecs_world_t *world, + const ecs_ids_t *components) { - /* Add elements to each column array */ - int32_t i; - for (i = 0; i < column_count; i ++) { - ecs_column_t *column = &columns[i]; - int16_t size = column->size; - if (size) { - int16_t alignment = column->alignment; - ecs_vector_add_t(&column->data, size, alignment); - } - } + ecs_poly_assert(world, ecs_world_t); + return find_or_create(world, components); } -int32_t flecs_table_append( - ecs_world_t *world, - ecs_table_t *table, - ecs_data_t *data, - ecs_entity_t entity, - ecs_record_t *record, - bool construct) +void flecs_init_root_table( + ecs_world_t *world) { - ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(data != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(!table->lock, ECS_LOCKED_STORAGE, NULL); + ecs_poly_assert(world, ecs_world_t); - /* Get count & size before growing entities array. This tells us whether the - * arrays will realloc */ - int32_t count = ecs_vector_count(data->entities); - int32_t size = ecs_vector_size(data->entities); - int32_t column_count = ecs_vector_count(table->storage_type); - ecs_column_t *columns = table->storage.columns; - - /* Grow buffer with entity ids, set new element to new entity */ - ecs_entity_t *e = ecs_vector_add(&data->entities, ecs_entity_t); - ecs_assert(e != NULL, ECS_INTERNAL_ERROR, NULL); - *e = entity; + ecs_ids_t entities = { + .array = NULL, + .count = 0 + }; - /* Keep track of alloc count. This allows references to check if cached - * pointers need to be updated. */ - table->alloc_count += (count == size); + init_table(world, &world->store.root, &entities); - /* Add record ptr to array with record ptrs */ - ecs_record_t **r = ecs_vector_add(&data->record_ptrs, ecs_record_t*); - ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); - *r = record; - - /* If the table is monitored indicate that there has been a change */ - mark_table_dirty(table, 0); + /* Ensure table indices start at 1, as 0 is reserved for the root */ + uint64_t new_id = flecs_sparse_new_id(world->store.tables); + ecs_assert(new_id == 0, ECS_INTERNAL_ERROR, NULL); + (void)new_id; +} - /* If this is the first entity in this table, signal queries so that the - * table moves from an inactive table to an active table. */ - if (!world->is_readonly && !count) { - table_activate(world, table, true); - } +void flecs_table_clear_edges( + ecs_world_t *world, + ecs_table_t *table) +{ + (void)world; + ecs_poly_assert(world, ecs_world_t); - ecs_assert(count >= 0, ECS_INTERNAL_ERROR, NULL); + int32_t i; + ecs_graph_node_t *node = &table->node; - /* Fast path: no switch columns, no lifecycle actions */ - if (!(table->flags & EcsTableIsComplex)) { - fast_append(columns, column_count); - return count; + ecs_os_free(node->add.lo); + ecs_os_free(node->remove.lo); + ecs_map_free(node->add.hi); + ecs_map_free(node->remove.hi); + node->add.lo = NULL; + node->remove.lo = NULL; + node->add.hi = NULL; + node->remove.hi = NULL; + + int32_t count = ecs_vector_count(node->diffs); + ecs_table_diff_t *diffs = ecs_vector_first(node->diffs, ecs_table_diff_t); + for (i = 0; i < count; i ++) { + ecs_table_diff_t *diff = &diffs[i]; + ecs_os_free(diff->added.array); + ecs_os_free(diff->removed.array); + ecs_os_free(diff->on_set.array); + ecs_os_free(diff->un_set.array); } - int32_t sw_column_count = table->sw_column_count; - int32_t bs_column_count = table->bs_column_count; - ecs_sw_column_t *sw_columns = table->storage.sw_columns; - ecs_bs_column_t *bs_columns = table->storage.bs_columns; + ecs_vector_free(node->diffs); + node->diffs = NULL; +} - ecs_type_info_t **c_info_array = table->c_info; - ecs_entity_t *entities = ecs_vector_first( - data->entities, ecs_entity_t); +void flecs_table_clear_add_edge( + ecs_table_t *table, + ecs_id_t id) +{ + ecs_edge_t *edge = get_edge(&table->node.add, id); + if (edge) { + edge->next = NULL; + edge->diff_index = 0; + } +} - /* Reobtain size to ensure that the columns have the same size as the - * entities and record vectors. This keeps reasoning about when allocations - * occur easier. */ - size = ecs_vector_size(data->entities); +void flecs_table_clear_remove_edge( + ecs_table_t *table, + ecs_id_t id) +{ + ecs_edge_t *edge = get_edge(&table->node.remove, id); + if (edge) { + edge->next = NULL; + edge->diff_index = 0; + } +} - /* Grow component arrays with 1 element */ +/* Public convenience functions for traversing table graph */ +ecs_table_t* ecs_table_add_id( + ecs_world_t *world, + ecs_table_t *table, + ecs_id_t id) +{ + return flecs_table_traverse_add(world, table, &id, NULL); +} + +ecs_table_t* ecs_table_remove_id( + ecs_world_t *world, + ecs_table_t *table, + ecs_id_t id) +{ + return flecs_table_traverse_remove(world, table, &id, NULL); +} + +static +int32_t count_events( + const ecs_entity_t *events) +{ int32_t i; - for (i = 0; i < column_count; i ++) { - ecs_column_t *column = &columns[i]; - ecs_assert(column->size != 0, ECS_INTERNAL_ERROR, NULL); - ecs_type_info_t *c_info = NULL; - if (c_info_array) { - c_info = c_info_array[i]; + for (i = 0; i < ECS_TRIGGER_DESC_EVENT_COUNT_MAX; i ++) { + if (!events[i]) { + break; } - - grow_column(world, entities, column, c_info, 1, size, construct); - - ecs_assert( - ecs_vector_size(columns[i].data) == ecs_vector_size(data->entities), - ECS_INTERNAL_ERROR, NULL); - - ecs_assert( - ecs_vector_count(columns[i].data) == ecs_vector_count(data->entities), - ECS_INTERNAL_ERROR, NULL); } - /* Add element to each switch column */ - for (i = 0; i < sw_column_count; i ++) { - ecs_assert(sw_columns != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_switch_t *sw = sw_columns[i].data; - flecs_switch_add(sw); - } + return i; +} - /* Add element to each bitset column */ - for (i = 0; i < bs_column_count; i ++) { - ecs_assert(bs_columns != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_bitset_t *bs = &bs_columns[i].data; - flecs_bitset_addn(bs, 1); - } +static +ecs_entity_t get_actual_event( + ecs_trigger_t *trigger, + ecs_entity_t event) +{ + /* If operator is Not, reverse the event */ + if (trigger->term.oper == EcsNot) { + if (event == EcsOnAdd) { + event = EcsOnRemove; + } else if (event == EcsOnRemove) { + event = EcsOnAdd; + } + } - return count; + return event; } static -void fast_delete_last( - ecs_column_t *columns, - int32_t column_count) +void unregister_event_trigger( + ecs_event_record_t *evt, + ecs_id_t id) { - int i; - for (i = 0; i < column_count; i ++) { - ecs_column_t *column = &columns[i]; - ecs_vector_remove_last(column->data); + if (ecs_map_remove(evt->event_ids, id) == 0) { + ecs_map_free(evt->event_ids); + evt->event_ids = NULL; } } static -void fast_delete( - ecs_column_t *columns, - int32_t column_count, - int32_t index) +void inc_trigger_count( + ecs_world_t *world, + ecs_entity_t event, + ecs_event_record_t *evt, + ecs_id_t id, + int32_t value) { - int i; - for (i = 0; i < column_count; i ++) { - ecs_column_t *column = &columns[i]; - int16_t size = column->size; - ecs_assert(size != 0, ECS_INTERNAL_ERROR, NULL); + ecs_event_id_record_t *idt = ecs_map_ensure( + evt->event_ids, ecs_event_id_record_t, id); + ecs_assert(idt != NULL, ECS_INTERNAL_ERROR, NULL); + + int32_t result = idt->trigger_count + value; + if (result == 1) { + /* Notify framework that there are triggers for the event/id. This + * allows parts of the code to skip event evaluation early */ + flecs_notify_tables(world, id, &(ecs_table_event_t){ + .kind = EcsTableTriggersForId, + .event = event + }); + } else if (result == 0) { + /* Ditto, but the reverse */ + flecs_notify_tables(world, id, &(ecs_table_event_t){ + .kind = EcsTableNoTriggersForId, + .event = event + }); - int16_t alignment = column->alignment; - ecs_vector_remove_t(column->data, size, alignment, index); + /* Remove admin for id for event */ + if (!idt->triggers && !idt->set_triggers) { + unregister_event_trigger(evt, id); + } } } -void flecs_table_delete( +static +void register_trigger_for_id( ecs_world_t *world, - ecs_table_t *table, - ecs_data_t *data, - int32_t index, - bool destruct) + ecs_observable_t *observable, + ecs_trigger_t *trigger, + ecs_id_t id, + size_t triggers_offset) { - ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(data != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(!table->lock, ECS_LOCKED_STORAGE, NULL); - - ecs_vector_t *v_entities = data->entities; - int32_t count = ecs_vector_count(v_entities); + ecs_sparse_t *events = observable->events; + ecs_assert(events != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_id_t term_id = trigger->term.id; - ecs_assert(count > 0, ECS_INTERNAL_ERROR, NULL); - count --; - ecs_assert(index <= count, ECS_INTERNAL_ERROR, NULL); + int i; + for (i = 0; i < trigger->event_count; i ++) { + ecs_entity_t event = get_actual_event(trigger, trigger->events[i]); - /* Move last entity id to index */ - ecs_entity_t *entities = ecs_vector_first(v_entities, ecs_entity_t); - ecs_entity_t entity_to_move = entities[count]; - ecs_entity_t entity_to_delete = entities[index]; - entities[index] = entity_to_move; - ecs_vector_remove_last(v_entities); + /* Get triggers for event */ + ecs_event_record_t *evt = flecs_sparse_ensure( + events, ecs_event_record_t, event); + ecs_assert(evt != NULL, ECS_INTERNAL_ERROR, NULL); - /* Move last record ptr to index */ - ecs_vector_t *v_records = data->record_ptrs; - ecs_assert(count < ecs_vector_count(v_records), ECS_INTERNAL_ERROR, NULL); + if (!evt->event_ids) { + evt->event_ids = ecs_map_new(ecs_event_id_record_t, 1); + } - ecs_record_t **records = ecs_vector_first(v_records, ecs_record_t*); - ecs_record_t *record_to_move = records[count]; - records[index] = record_to_move; - ecs_vector_remove_last(v_records); + /* Get triggers for (component) id for event */ + ecs_event_id_record_t *idt = ecs_map_ensure( + evt->event_ids, ecs_event_id_record_t, id); + ecs_assert(idt != NULL, ECS_INTERNAL_ERROR, NULL); - /* Update record of moved entity in entity index */ - if (index != count) { - if (record_to_move) { - uint32_t row_flags = record_to_move->row & ECS_ROW_FLAGS_MASK; - record_to_move->row = ECS_ROW_TO_RECORD(index, row_flags); - ecs_assert(record_to_move->table != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(record_to_move->table == table, ECS_INTERNAL_ERROR, NULL); + ecs_map_t **triggers = ECS_OFFSET(idt, triggers_offset); + if (!triggers[0]) { + triggers[0] = ecs_map_new(ecs_trigger_t*, 1); } - } - /* If the table is monitored indicate that there has been a change */ - mark_table_dirty(table, 0); + ecs_map_ensure(triggers[0], ecs_trigger_t*, trigger->id)[0] = trigger; - /* If table is empty, deactivate it */ - if (!count) { - table_activate(world, table, false); + inc_trigger_count(world, event, evt, term_id, 1); } +} - /* Destruct component data */ - ecs_type_info_t **c_info_array = table->c_info; - ecs_column_t *columns = data->columns; - int32_t column_count = ecs_vector_count(table->storage_type); - int32_t i; - - /* If this is a table without lifecycle callbacks or special columns, take - * fast path that just remove an element from the array(s) */ - if (!(table->flags & EcsTableIsComplex)) { - if (index == count) { - fast_delete_last(columns, column_count); +static +void register_trigger( + ecs_world_t *world, + ecs_observable_t *observable, + ecs_trigger_t *trigger) +{ + ecs_term_t *term = &trigger->term; + if (term->subj.set.mask & EcsSelf) { + if (term->subj.entity == EcsThis) { + register_trigger_for_id(world, observable, trigger, term->id, + offsetof(ecs_event_id_record_t, triggers)); } else { - fast_delete(columns, column_count, index); + register_trigger_for_id(world, observable, trigger, term->id, + offsetof(ecs_event_id_record_t, entity_triggers)); } - - return; } + if (trigger->term.subj.set.mask & EcsSuperSet) { + ecs_id_t pair = ecs_pair(term->subj.set.relation, EcsWildcard); + register_trigger_for_id(world, observable, trigger, pair, + offsetof(ecs_event_id_record_t, set_triggers)); + } +} - /* Last element, destruct & remove */ - if (index == count) { - /* If table has component destructors, invoke */ - if (destruct && (table->flags & EcsTableHasDtors)) { - ecs_assert(c_info_array != NULL, ECS_INTERNAL_ERROR, NULL); - - for (i = 0; i < column_count; i ++) { - ecs_type_info_t *c_info = c_info_array[i]; - ecs_xtor_t dtor; - if (c_info && (dtor = c_info->lifecycle.dtor)) { - ecs_size_t size = c_info->size; - ecs_size_t alignment = c_info->alignment; - dtor(world, c_info->component, &entity_to_delete, - ecs_vector_last_t(columns[i].data, size, alignment), - flecs_itosize(size), 1, c_info->lifecycle.ctx); - } - } - } +static +void unregister_trigger_for_id( + ecs_world_t *world, + ecs_observable_t *observable, + ecs_trigger_t *trigger, + ecs_id_t id, + size_t triggers_offset) +{ + ecs_sparse_t *events = observable->events; + ecs_assert(events != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_id_t term_id = trigger->term.id; - fast_delete_last(columns, column_count); + int i; + for (i = 0; i < trigger->event_count; i ++) { + ecs_entity_t event = get_actual_event(trigger, trigger->events[i]); - /* Not last element, move last element to deleted element & destruct */ - } else { - /* If table has component destructors, invoke */ - if (destruct && (table->flags & (EcsTableHasDtors | EcsTableHasMove))) { - ecs_assert(c_info_array != NULL, ECS_INTERNAL_ERROR, NULL); + /* Get triggers for event */ + ecs_event_record_t *evt = flecs_sparse_get( + events, ecs_event_record_t, event); + ecs_assert(evt != NULL, ECS_INTERNAL_ERROR, NULL); - for (i = 0; i < column_count; i ++) { - ecs_column_t *column = &columns[i]; - ecs_size_t size = column->size; - ecs_size_t align = column->alignment; - ecs_vector_t *vec = column->data; - void *dst = ecs_vector_get_t(vec, size, align, index); - void *src = ecs_vector_last_t(vec, size, align); - - ecs_type_info_t *c_info = c_info_array[i]; - ecs_move_ctor_t move_dtor; - if (c_info && (move_dtor = c_info->lifecycle.move_dtor)) { - move_dtor(world, c_info->component, &c_info->lifecycle, - &entity_to_move, &entity_to_delete, dst, src, - flecs_itosize(size), 1, c_info->lifecycle.ctx); - } else { - ecs_os_memcpy(dst, src, size); - } + /* Get triggers for (component) id */ + ecs_event_id_record_t *idt = ecs_map_get( + evt->event_ids, ecs_event_id_record_t, id); + ecs_assert(idt != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_vector_remove_last(vec); - } + ecs_map_t **id_triggers = ECS_OFFSET(idt, triggers_offset); - } else { - fast_delete(columns, column_count, index); + if (ecs_map_remove(id_triggers[0], trigger->id) == 0) { + ecs_map_free(id_triggers[0]); + id_triggers[0] = NULL; } - } - /* Remove elements from switch columns */ - ecs_sw_column_t *sw_columns = data->sw_columns; - int32_t sw_column_count = table->sw_column_count; - for (i = 0; i < sw_column_count; i ++) { - flecs_switch_remove(sw_columns[i].data, index); + inc_trigger_count(world, event, evt, term_id, -1); + + if (id != term_id) { + /* Id is different from term_id in case of a set trigger. If they're + * the same, inc_trigger_count could already have done cleanup */ + if (!idt->triggers && !idt->set_triggers && !idt->trigger_count) { + unregister_event_trigger(evt, id); + } + } } +} - /* Remove elements from bitset columns */ - ecs_bs_column_t *bs_columns = data->bs_columns; - int32_t bs_column_count = table->bs_column_count; - for (i = 0; i < bs_column_count; i ++) { - flecs_bitset_remove(&bs_columns[i].data, index); +static +void unregister_trigger( + ecs_world_t *world, + ecs_observable_t *observable, + ecs_trigger_t *trigger) +{ + ecs_term_t *term = &trigger->term; + if (term->subj.set.mask & EcsSelf) { + if (term->subj.entity == EcsThis) { + unregister_trigger_for_id(world, observable, trigger, term->id, + offsetof(ecs_event_id_record_t, triggers)); + } else { + unregister_trigger_for_id(world, observable, trigger, term->id, + offsetof(ecs_event_id_record_t, entity_triggers)); + } + } else { + ecs_id_t pair = ecs_pair(term->subj.set.relation, EcsWildcard); + unregister_trigger_for_id(world, observable, trigger, pair, + offsetof(ecs_event_id_record_t, set_triggers)); } } static -void fast_move( - ecs_table_t *new_table, - ecs_data_t *new_data, - int32_t new_index, - ecs_table_t *old_table, - ecs_data_t *old_data, - int32_t old_index) +ecs_map_t* get_triggers_for_event( + const ecs_observable_t *observable, + ecs_entity_t event) { - ecs_type_t new_type = new_table->storage_type; - ecs_type_t old_type = old_table->storage_type; - - int32_t i_new = 0, new_column_count = ecs_vector_count(new_table->storage_type); - int32_t i_old = 0, old_column_count = ecs_vector_count(old_table->storage_type); - ecs_entity_t *new_components = ecs_vector_first(new_type, ecs_entity_t); - ecs_entity_t *old_components = ecs_vector_first(old_type, ecs_entity_t); - - ecs_column_t *old_columns = old_data->columns; - ecs_column_t *new_columns = new_data->columns; - - - for (; (i_new < new_column_count) && (i_old < old_column_count);) { - ecs_entity_t new_component = new_components[i_new]; - ecs_entity_t old_component = old_components[i_old]; + ecs_check(observable != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(event != 0, ECS_INTERNAL_ERROR, NULL); - if (new_component == old_component) { - ecs_column_t *new_column = &new_columns[i_new]; - ecs_column_t *old_column = &old_columns[i_old]; - int16_t size = new_column->size; - ecs_assert(size != 0, ECS_INTERNAL_ERROR, NULL); + ecs_sparse_t *events = observable->events; + ecs_assert(events != NULL, ECS_INTERNAL_ERROR, NULL); - int16_t alignment = new_column->alignment; - void *dst = ecs_vector_get_t( - new_column->data, size, alignment, new_index); - void *src = ecs_vector_get_t( - old_column->data, size, alignment, old_index); + const ecs_event_record_t *evt = flecs_sparse_get( + events, ecs_event_record_t, event); + + if (evt) { + return evt->event_ids; + } - ecs_assert(dst != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(src != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_os_memcpy(dst, src, size); - } +error: + return NULL; +} - i_new += new_component <= old_component; - i_old += new_component >= old_component; - } +static +ecs_event_id_record_t* get_triggers_for_id( + const ecs_map_t *evt, + ecs_id_t id) +{ + return ecs_map_get(evt, ecs_event_id_record_t, id); } -void flecs_table_move( - ecs_world_t *world, - ecs_entity_t dst_entity, - ecs_entity_t src_entity, - ecs_table_t *new_table, - ecs_data_t *new_data, - int32_t new_index, - ecs_table_t *old_table, - ecs_data_t *old_data, - int32_t old_index, - bool construct) +ecs_event_id_record_t* flecs_triggers_for_id( + const ecs_poly_t *object, + ecs_id_t id, + ecs_entity_t event) { - ecs_assert(new_table != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(old_table != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(!new_table->lock, ECS_LOCKED_STORAGE, NULL); - ecs_assert(!old_table->lock, ECS_LOCKED_STORAGE, NULL); - - ecs_assert(old_index >= 0, ECS_INTERNAL_ERROR, NULL); - ecs_assert(new_index >= 0, ECS_INTERNAL_ERROR, NULL); + ecs_observable_t *observable = ecs_get_observable(object); + const ecs_map_t *evt = get_triggers_for_event(observable, event); + if (!evt) { + return NULL; + } - ecs_assert(old_data != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(new_data != NULL, ECS_INTERNAL_ERROR, NULL); + return get_triggers_for_id(evt, id); +} - if (!((new_table->flags | old_table->flags) & EcsTableIsComplex)) { - fast_move(new_table, new_data, new_index, old_table, old_data, old_index); +static +void init_iter( + ecs_iter_t *it, + bool *iter_set) +{ + ecs_assert(it != NULL, ECS_INTERNAL_ERROR, NULL); + + if (*iter_set) { return; } - move_switch_columns( - new_table, new_data, new_index, old_table, old_data, old_index, 1); + flecs_iter_init(it); - move_bitset_columns( - new_table, new_data, new_index, old_table, old_data, old_index, 1); + *iter_set = true; - bool same_entity = dst_entity == src_entity; + it->ids[0] = it->event_id; - ecs_type_t new_type = new_table->storage_type; - ecs_type_t old_type = old_table->storage_type; + ecs_assert(it->table != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(!it->world->is_readonly, ECS_INTERNAL_ERROR, NULL); + ecs_assert(it->count > 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(it->offset < ecs_table_count(it->table), + ECS_INTERNAL_ERROR, NULL); + ecs_assert((it->offset + it->count) <= ecs_table_count(it->table), + ECS_INTERNAL_ERROR, NULL); - int32_t i_new = 0, new_column_count = ecs_vector_count(new_table->storage_type); - int32_t i_old = 0, old_column_count = ecs_vector_count(old_table->storage_type); - ecs_entity_t *new_components = ecs_vector_first(new_type, ecs_entity_t); - ecs_entity_t *old_components = ecs_vector_first(old_type, ecs_entity_t); + int32_t index = ecs_type_match(it->world, it->table, it->type, 0, + it->event_id, EcsIsA, 0, 0, it->subjects, NULL, NULL); + + if (index == -1) { + it->columns[0] = 0; + } else if (it->subjects[0]) { + it->columns[0] = -index - 1; + } else { + it->columns[0] = index + 1; + } - ecs_column_t *old_columns = old_data->columns; - ecs_column_t *new_columns = new_data->columns; + ecs_term_t term = { + .id = it->event_id + }; - for (; (i_new < new_column_count) && (i_old < old_column_count);) { - ecs_entity_t new_component = new_components[i_new]; - ecs_entity_t old_component = old_components[i_old]; + it->terms = &term; + it->term_count = 1; + flecs_iter_populate_data(it->world, it, it->table, it->offset, + it->count, it->ptrs, it->sizes); +} - if (new_component == old_component) { - ecs_column_t *new_column = &new_columns[i_new]; - ecs_column_t *old_column = &old_columns[i_old]; - int16_t size = new_column->size; - int16_t alignment = new_column->alignment; +static +bool ignore_table( + ecs_trigger_t *t, + ecs_table_t *table) +{ + if (!table) { + return false; + } - ecs_assert(size != 0, ECS_INTERNAL_ERROR, NULL); + if (!t->match_prefab && (table->flags & EcsTableIsPrefab)) { + return true; + } + if (!t->match_disabled && (table->flags & EcsTableIsDisabled)) { + return true; + } + + return false; +} - void *dst = ecs_vector_get_t( - new_column->data, size, alignment, new_index); - void *src = ecs_vector_get_t( - old_column->data, size, alignment, old_index); +static +void notify_self_triggers( + ecs_iter_t *it, + const ecs_map_t *triggers) +{ + ecs_assert(triggers != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(dst != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(src != NULL, ECS_INTERNAL_ERROR, NULL); + void **ptrs = it->ptrs; + ecs_size_t *sizes = it->sizes; - ecs_type_info_t *cdata = new_table->c_info[i_new]; - if (same_entity) { - ecs_move_ctor_t callback; - if (cdata && (callback = cdata->lifecycle.ctor_move_dtor)) { - void *ctx = cdata->lifecycle.ctx; - /* ctor + move + dtor */ - callback(world, new_component, &cdata->lifecycle, - &dst_entity, &src_entity, - dst, src, flecs_itosize(size), 1, ctx); - } else { - ecs_os_memcpy(dst, src, size); - } - } else { - ecs_copy_ctor_t copy; - if (cdata && (copy = cdata->lifecycle.copy_ctor)) { - void *ctx = cdata->lifecycle.ctx; - copy(world, new_component, &cdata->lifecycle, - &dst_entity, &src_entity, - dst, src, flecs_itosize(size), 1, ctx); - } else { - ecs_os_memcpy(dst, src, size); - } - } - } else { - if (new_component < old_component) { - if (construct) { - ctor_component(world, new_table->c_info[i_new], - &new_columns[i_new], &dst_entity, new_index, 1); - } - } else { - dtor_component(world, old_table->c_info[i_old], - &old_columns[i_old], &src_entity, old_index, 1); - } + ecs_map_iter_t mit = ecs_map_iter(triggers); + ecs_trigger_t *t; + while ((t = ecs_map_next_ptr(&mit, ecs_trigger_t*, NULL))) { + if (ignore_table(t, it->table)) { + continue; } - i_new += new_component <= old_component; - i_old += new_component >= old_component; - } + bool is_filter = t->term.inout == EcsInOutFilter; - if (construct) { - for (; (i_new < new_column_count); i_new ++) { - ctor_component(world, new_table->c_info[i_new], - &new_columns[i_new], &dst_entity, new_index, 1); + it->system = t->entity; + it->self = t->self; + it->ctx = t->ctx; + it->binding_ctx = t->binding_ctx; + it->term_index = t->term.index; + it->terms = &t->term; + it->is_filter = is_filter; + + if (is_filter) { + it->ptrs = NULL; + it->sizes = NULL; } - } - for (; (i_old < old_column_count); i_old ++) { - dtor_component(world, old_table->c_info[i_old], - &old_columns[i_old], &src_entity, old_index, 1); + t->action(it); + + it->ptrs = ptrs; + it->sizes = sizes; } } -int32_t flecs_table_appendn( - ecs_world_t *world, - ecs_table_t *table, - ecs_data_t *data, - int32_t to_add, - const ecs_entity_t *ids) +static +void notify_entity_triggers( + ecs_iter_t *it, + const ecs_map_t *triggers) { - ecs_assert(!table->lock, ECS_LOCKED_STORAGE, NULL); + ecs_assert(triggers != NULL, ECS_INTERNAL_ERROR, NULL); - int32_t cur_count = flecs_table_data_count(data); - return grow_data(world, table, data, to_add, cur_count + to_add, ids); -} + void **ptrs = it->ptrs; + ecs_size_t *sizes = it->sizes; -void flecs_table_set_size( - ecs_world_t *world, - ecs_table_t *table, - ecs_data_t *data, - int32_t size) -{ - ecs_assert(!table->lock, ECS_LOCKED_STORAGE, NULL); + ecs_map_iter_t mit = ecs_map_iter(triggers); + ecs_trigger_t *t; + int32_t offset = it->offset, count = it->count; + ecs_entity_t *entities = it->entities; + + ecs_entity_t dummy = 0; + it->entities = &dummy; - int32_t cur_count = flecs_table_data_count(data); + while ((t = ecs_map_next_ptr(&mit, ecs_trigger_t*, NULL))) { + if (ignore_table(t, it->table)) { + continue; + } - if (cur_count < size) { - grow_data(world, table, data, 0, size, NULL); - } -} + int32_t i, entity_count = it->count; + for (i = 0; i < entity_count; i ++) { + if (entities[i] != t->term.subj.entity) { + continue; + } + + bool is_filter = t->term.inout == EcsInOutFilter; -int32_t flecs_table_data_count( - const ecs_data_t *data) -{ - return data ? ecs_vector_count(data->entities) : 0; -} + it->system = t->entity; + it->self = t->self; + it->ctx = t->ctx; + it->binding_ctx = t->binding_ctx; + it->term_index = t->term.index; + it->terms = &t->term; + it->is_filter = is_filter; -static -void swap_switch_columns( - ecs_table_t *table, - ecs_data_t *data, - int32_t row_1, - int32_t row_2) -{ - int32_t i = 0, column_count = table->sw_column_count; - if (!column_count) { - return; - } + if (is_filter) { + it->ptrs = NULL; + it->sizes = NULL; + } - ecs_sw_column_t *columns = data->sw_columns; + it->offset = i; + it->count = 1; + it->subjects[0] = entities[i]; + t->action(it); - for (i = 0; i < column_count; i ++) { - ecs_switch_t *sw = columns[i].data; - flecs_switch_swap(sw, row_1, row_2); + it->ptrs = ptrs; + it->sizes = sizes; + } } + + it->offset = offset; + it->count = count; + it->entities = entities; + it->subjects[0] = 0; } static -void swap_bitset_columns( - ecs_table_t *table, - ecs_data_t *data, - int32_t row_1, - int32_t row_2) +void notify_set_base_triggers( + ecs_iter_t *it, + const ecs_map_t *triggers) { - int32_t i = 0, column_count = table->bs_column_count; - if (!column_count) { - return; - } + ecs_assert(triggers != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(it->count != 0, ECS_INTERNAL_ERROR, NULL); - ecs_bs_column_t *columns = data->bs_columns; + ecs_map_iter_t mit = ecs_map_iter(triggers); + ecs_trigger_t *t; + while ((t = ecs_map_next_ptr(&mit, ecs_trigger_t*, NULL))) { + if (ignore_table(t, it->table)) { + continue; + } - for (i = 0; i < column_count; i ++) { - ecs_bitset_t *bs = &columns[i].data; - flecs_bitset_swap(bs, row_1, row_2); - } -} + if (flecs_term_match_table(it->world, &t->term, it->table, it->type, + it->ids, it->columns, it->subjects, NULL, true)) + { + if (!it->subjects[0]) { + /* Do not match owned components */ + continue; + } -void flecs_table_swap( - ecs_world_t *world, - ecs_table_t *table, - ecs_data_t *data, - int32_t row_1, - int32_t row_2) -{ - (void)world; + it->event_id = t->term.id; + it->ids[0] = t->term.id; + it->system = t->entity; + it->self = t->self; + it->ctx = t->ctx; + it->binding_ctx = t->binding_ctx; + it->term_index = t->term.index; + it->terms = &t->term; - ecs_assert(!table->lock, ECS_LOCKED_STORAGE, NULL); - ecs_assert(data != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(row_1 >= 0, ECS_INTERNAL_ERROR, NULL); - ecs_assert(row_2 >= 0, ECS_INTERNAL_ERROR, NULL); - - if (row_1 == row_2) { - return; + t->action(it); + } } +} - /* If the table is monitored indicate that there has been a change */ - mark_table_dirty(table, 0); +static +void notify_set_triggers( + ecs_iter_t *it, + const ecs_map_t *triggers) +{ + ecs_assert(triggers != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(it->count != 0, ECS_INTERNAL_ERROR, NULL); - ecs_entity_t *entities = ecs_vector_first(data->entities, ecs_entity_t); - ecs_entity_t e1 = entities[row_1]; - ecs_entity_t e2 = entities[row_2]; + ecs_map_iter_t mit = ecs_map_iter(triggers); + ecs_trigger_t *t; + while ((t = ecs_map_next_ptr(&mit, ecs_trigger_t*, NULL))) { + if (ignore_table(t, it->table)) { + continue; + } - ecs_record_t **record_ptrs = ecs_vector_first(data->record_ptrs, ecs_record_t*); - ecs_record_t *record_ptr_1 = record_ptrs[row_1]; - ecs_record_t *record_ptr_2 = record_ptrs[row_2]; + /* Make sure trigger id matches event id */ + if (!ecs_id_match(it->event_id, t->term.id)) { + continue; + } - ecs_assert(record_ptr_1 != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(record_ptr_2 != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_entity_t subj = it->entities[0]; + int32_t i, count = it->count; + ecs_entity_t term_subj = t->term.subj.entity; - /* Keep track of whether entity is watched */ - uint32_t flags_1 = ECS_RECORD_TO_ROW_FLAGS(record_ptr_1->row); - uint32_t flags_2 = ECS_RECORD_TO_ROW_FLAGS(record_ptr_2->row); + /* If trigger is for a specific entity, make sure it is in the table + * being triggered for */ + if (term_subj != EcsThis) { + for (i = 0; i < count; i ++) { + if (it->entities[i] == term_subj) { + break; + } + } - /* Swap entities & records */ - entities[row_1] = e2; - entities[row_2] = e1; - record_ptr_1->row = ECS_ROW_TO_RECORD(row_2, flags_1); - record_ptr_2->row = ECS_ROW_TO_RECORD(row_1, flags_2); - record_ptrs[row_1] = record_ptr_2; - record_ptrs[row_2] = record_ptr_1; + if (i == count) { + continue; + } - swap_switch_columns(table, data, row_1, row_2); - swap_bitset_columns(table, data, row_1, row_2); + /* If the entity matches, trigger for no other entities */ + it->entities[0] = 0; + it->count = 1; + } - ecs_column_t *columns = data->columns; - if (!columns) { - return; - } + if (flecs_term_match_table(it->world, &t->term, it->table, it->type, + it->ids, it->columns, it->subjects, NULL, true)) + { + if (!it->subjects[0]) { + /* Do not match owned components */ + continue; + } - /* Swap columns */ - int32_t i, column_count = ecs_vector_count(table->storage_type); - - for (i = 0; i < column_count; i ++) { - int16_t size = columns[i].size; - int16_t alignment = columns[i].alignment; + ecs_entity_t event_id = it->event_id; + it->event_id = t->term.id; - ecs_assert(size != 0, ECS_INTERNAL_ERROR, NULL); + it->ids[0] = t->term.id; + it->system = t->entity; + it->self = t->self; + it->ctx = t->ctx; + it->binding_ctx = t->binding_ctx; + it->term_index = t->term.index; + it->terms = &t->term; - void *ptr = ecs_vector_first_t(columns[i].data, size, alignment); - void *tmp = ecs_os_alloca(size); + /* Triggers for supersets can be instanced */ + if (it->count == 1 || t->instanced || !it->sizes[0]) { + it->is_instanced = t->instanced; + t->action(it); + it->is_instanced = false; + } else { + ecs_entity_t *entities = it->entities; + it->count = 1; + for (i = 0; i < count; i ++) { + it->entities = &entities[i]; + t->action(it); + } + it->entities = entities; + } - void *el_1 = ECS_OFFSET(ptr, size * row_1); - void *el_2 = ECS_OFFSET(ptr, size * row_2); + it->event_id = event_id; + } - ecs_os_memcpy(tmp, el_1, size); - ecs_os_memcpy(el_1, el_2, size); - ecs_os_memcpy(el_2, tmp, size); - } + it->entities[0] = subj; + it->count = count; + } } static -void merge_vector( - ecs_vector_t **dst_out, - ecs_vector_t *src, - int16_t size, - int16_t alignment) +void notify_triggers_for_id( + const ecs_map_t *evt, + ecs_id_t event_id, + ecs_iter_t *it, + bool *iter_set) { - ecs_vector_t *dst = *dst_out; - int32_t dst_count = ecs_vector_count(dst); - - if (!dst_count) { - if (dst) { - ecs_vector_free(dst); + const ecs_event_id_record_t *idt = get_triggers_for_id(evt, event_id); + if (idt) { + if (idt->triggers) { + init_iter(it, iter_set); + notify_self_triggers(it, idt->triggers); } + if (idt->entity_triggers) { + init_iter(it, iter_set); + notify_entity_triggers(it, idt->entity_triggers); + } + if (idt->set_triggers) { + init_iter(it, iter_set); + notify_set_base_triggers(it, idt->set_triggers); + } + } +} - *dst_out = src; - - /* If the new table is not empty, copy the contents from the - * src into the dst. */ - } else { - int32_t src_count = ecs_vector_count(src); - ecs_vector_set_count_t(&dst, size, alignment, dst_count + src_count); - - void *dst_ptr = ecs_vector_first_t(dst, size, alignment); - void *src_ptr = ecs_vector_first_t(src, size, alignment); - - dst_ptr = ECS_OFFSET(dst_ptr, size * dst_count); - - ecs_os_memcpy(dst_ptr, src_ptr, size * src_count); - - ecs_vector_free(src); - *dst_out = dst; +static +void notify_set_triggers_for_id( + const ecs_map_t *evt, + ecs_iter_t *it, + bool *iter_set, + ecs_id_t set_id) +{ + const ecs_event_id_record_t *idt = get_triggers_for_id(evt, set_id); + if (idt && idt->set_triggers) { + init_iter(it, iter_set); + notify_set_triggers(it, idt->set_triggers); } } static -void merge_column( +void trigger_yield_existing( ecs_world_t *world, - ecs_table_t *table, - ecs_data_t *data, - int32_t column_id, - ecs_vector_t *src) + ecs_trigger_t *trigger) { - ecs_entity_t *entities = ecs_vector_first(data->entities, ecs_entity_t); - ecs_type_info_t *c_info = table->c_info[column_id]; - ecs_column_t *column = &data->columns[column_id]; - ecs_vector_t *dst = column->data; - int16_t size = column->size; - int16_t alignment = column->alignment; - int32_t dst_count = ecs_vector_count(dst); + ecs_iter_action_t callback = trigger->action; - if (!dst_count) { - if (dst) { - ecs_vector_free(dst); + /* If yield existing is enabled, trigger for each thing that matches + * the event, if the event is iterable. */ + int i, count = trigger->event_count; + for (i = 0; i < count; i ++) { + ecs_entity_t evt = trigger->events[i]; + const EcsIterable *iterable = ecs_get(world, evt, EcsIterable); + if (!iterable) { + continue; } - column->data = src; - - /* If the new table is not empty, copy the contents from the - * src into the dst. */ - } else { - int32_t src_count = ecs_vector_count(src); - ecs_vector_set_count_t(&dst, size, alignment, dst_count + src_count); - column->data = dst; + ecs_iter_t it; + iterable->init(world, world, &it, &trigger->term); + it.system = trigger->entity; + it.ctx = trigger->ctx; + it.binding_ctx = trigger->binding_ctx; - /* Construct new values */ - if (c_info) { - ctor_component( - world, c_info, column, entities, dst_count, src_count); - } - - void *dst_ptr = ecs_vector_first_t(dst, size, alignment); - void *src_ptr = ecs_vector_first_t(src, size, alignment); - - dst_ptr = ECS_OFFSET(dst_ptr, size * dst_count); - - /* Move values into column */ - ecs_move_t move; - if (c_info && (move = c_info->lifecycle.move)) { - move(world, c_info->component, entities, entities, - dst_ptr, src_ptr, flecs_itosize(size), src_count, - c_info->lifecycle.ctx); - } else { - ecs_os_memcpy(dst_ptr, src_ptr, size * src_count); + ecs_iter_next_action_t next = it.next; + ecs_assert(next != NULL, ECS_INTERNAL_ERROR, NULL); + while (next(&it)) { + callback(&it); } - - ecs_vector_free(src); } } -static -void merge_table_data( - ecs_world_t *world, - ecs_table_t *new_table, - ecs_table_t *old_table, - int32_t old_count, - int32_t new_count, - ecs_data_t *old_data, - ecs_data_t *new_data) +void flecs_triggers_notify( + ecs_iter_t *it, + ecs_observable_t *observable, + ecs_ids_t *ids, + ecs_entity_t event) { - ecs_type_t new_type = new_table->storage_type; - ecs_type_t old_type = old_table->storage_type; - int32_t i_new = 0, new_column_count = ecs_vector_count(new_type); - int32_t i_old = 0, old_column_count = ecs_vector_count(old_type); - ecs_entity_t *new_components = ecs_vector_first(new_type, ecs_entity_t); - ecs_entity_t *old_components = ecs_vector_first(old_type, ecs_entity_t); - - ecs_column_t *old_columns = old_data->columns; - ecs_column_t *new_columns = new_data->columns; + ecs_assert(ids != NULL && ids->count != 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(ids->array != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_entity_t events[2] = {event, EcsWildcard}; + int32_t e, i, ids_count = ids->count; + ecs_id_t *ids_array = ids->array; - if (!new_columns && !new_data->entities) { - new_columns = new_data->columns; - } - - ecs_assert(!new_column_count || new_columns, ECS_INTERNAL_ERROR, NULL); + for (e = 0; e < 2; e ++) { + event = events[e]; + const ecs_map_t *evt = get_triggers_for_event(observable, event); + if (!evt) { + continue; + } - if (!old_count) { - return; - } + it->event = event; - /* Merge entities */ - merge_vector(&new_data->entities, old_data->entities, ECS_SIZEOF(ecs_entity_t), - ECS_ALIGNOF(ecs_entity_t)); - old_data->entities = NULL; - ecs_entity_t *entities = ecs_vector_first(new_data->entities, ecs_entity_t); + for (i = 0; i < ids_count; i ++) { + ecs_id_t id = ids_array[i]; + bool iter_set = false; - ecs_assert(ecs_vector_count(new_data->entities) == old_count + new_count, - ECS_INTERNAL_ERROR, NULL); + it->event_id = id; - /* Merge entity index record pointers */ - merge_vector(&new_data->record_ptrs, old_data->record_ptrs, - ECS_SIZEOF(ecs_record_t*), ECS_ALIGNOF(ecs_record_t*)); - old_data->record_ptrs = NULL; + notify_triggers_for_id(evt, id, it, &iter_set); - for (; (i_new < new_column_count) && (i_old < old_column_count); ) { - ecs_entity_t new_component = new_components[i_new]; - ecs_entity_t old_component = old_components[i_old]; - int16_t size = new_columns[i_new].size; - int16_t alignment = new_columns[i_new].alignment; - ecs_assert(size != 0, ECS_INTERNAL_ERROR, NULL); + if (ECS_HAS_ROLE(id, PAIR)) { + ecs_entity_t pred = ECS_PAIR_RELATION(id); + ecs_entity_t obj = ECS_PAIR_OBJECT(id); - if (new_component == old_component) { - merge_column(world, new_table, new_data, i_new, - old_columns[i_old].data); - old_columns[i_old].data = NULL; + notify_triggers_for_id(evt, ecs_pair(pred, EcsWildcard), + it, &iter_set); - /* Mark component column as dirty */ - mark_table_dirty(new_table, i_new + 1); - - i_new ++; - i_old ++; - } else if (new_component < old_component) { - /* New column does not occur in old table, make sure vector is large - * enough. */ - ecs_column_t *column = &new_columns[i_new]; - ecs_vector_set_count_t(&column->data, size, alignment, - old_count + new_count); + notify_triggers_for_id(evt, ecs_pair(EcsWildcard, obj), + it, &iter_set); - /* Construct new values */ - ecs_type_info_t *c_info = new_table->c_info[i_new]; - if (c_info) { - ctor_component(world, c_info, column, - entities, 0, old_count + new_count); - } - - i_new ++; - } else if (new_component > old_component) { - ecs_column_t *column = &old_columns[i_old]; - - /* Destruct old values */ - ecs_type_info_t *c_info = old_table->c_info[i_old]; - if (c_info) { - dtor_component(world, c_info, column, - entities, 0, old_count); + notify_triggers_for_id(evt, ecs_pair(EcsWildcard, EcsWildcard), + it, &iter_set); + } else { + notify_triggers_for_id(evt, EcsWildcard, it, &iter_set); } - - /* Old column does not occur in new table, remove */ - ecs_vector_free(column->data); - column->data = NULL; - - i_old ++; } } +} - move_switch_columns( - new_table, new_data, new_count, old_table, old_data, 0, old_count); - - /* Initialize remaining columns */ - for (; i_new < new_column_count; i_new ++) { - ecs_column_t *column = &new_columns[i_new]; - int16_t size = column->size; - int16_t alignment = column->alignment; - ecs_assert(size != 0, ECS_INTERNAL_ERROR, NULL); - - ecs_vector_set_count_t(&column->data, size, alignment, - old_count + new_count); +void flecs_set_triggers_notify( + ecs_iter_t *it, + ecs_observable_t *observable, + ecs_ids_t *ids, + ecs_entity_t event, + ecs_id_t set_id) +{ + ecs_assert(ids != NULL && ids->count != 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(ids->array != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_entity_t events[2] = {event, EcsWildcard}; + int32_t e, i, ids_count = ids->count; + ecs_id_t *ids_array = ids->array; - /* Construct new values */ - ecs_type_info_t *c_info = new_table->c_info[i_new]; - if (c_info) { - ctor_component(world, c_info, column, - entities, 0, old_count + new_count); + for (e = 0; e < 2; e ++) { + event = events[e]; + const ecs_map_t *evt = get_triggers_for_event(observable, event); + if (!evt) { + continue; } - } - - /* Destroy remaining columns */ - for (; i_old < old_column_count; i_old ++) { - ecs_column_t *column = &old_columns[i_old]; - /* Destruct old values */ - ecs_type_info_t *c_info = old_table->c_info[i_old]; - if (c_info) { - dtor_component(world, c_info, column, entities, - 0, old_count); - } + it->event = event; - /* Old column does not occur in new table, remove */ - ecs_vector_free(column->data); - column->data = NULL; - } + for (i = 0; i < ids_count; i ++) { + ecs_id_t id = ids_array[i]; + bool iter_set = false; - /* Mark entity column as dirty */ - mark_table_dirty(new_table, 0); -} + it->event_id = id; -int32_t ecs_table_count( - const ecs_table_t *table) -{ - ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); - return flecs_table_data_count(&table->storage); + notify_set_triggers_for_id(evt, it, &iter_set, set_id); + } + } } -void flecs_table_merge( +ecs_entity_t ecs_trigger_init( ecs_world_t *world, - ecs_table_t *new_table, - ecs_table_t *old_table, - ecs_data_t *new_data, - ecs_data_t *old_data) + const ecs_trigger_desc_t *desc) { - ecs_assert(old_table != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(!old_table->lock, ECS_LOCKED_STORAGE, NULL); + char *name = NULL; - bool move_data = false; + ecs_poly_assert(world, ecs_world_t); + ecs_check(!world->is_readonly, ECS_INVALID_OPERATION, NULL); + ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(!world->is_fini, ECS_INVALID_OPERATION, NULL); + + const char *expr = desc->expr; - /* If there is nothing to merge to, just clear the old table */ - if (!new_table) { - flecs_table_clear_data(world, old_table, old_data); - return; - } else { - ecs_assert(!new_table->lock, ECS_LOCKED_STORAGE, NULL); + ecs_observable_t *observable = desc->observable; + if (!observable) { + observable = ecs_get_observable(world); } - /* If there is no data to merge, drop out */ - if (!old_data) { - return; - } + /* If entity is provided, create it */ + ecs_entity_t existing = desc->entity.entity; + ecs_entity_t entity = ecs_entity_init(world, &desc->entity); - if (!new_data) { - new_data = &new_table->storage; - if (new_table == old_table) { - move_data = true; - } - } + bool added = false; + EcsTrigger *comp = ecs_get_mut(world, entity, EcsTrigger, &added); + if (added) { + ecs_check(desc->callback != NULL, ECS_INVALID_PARAMETER, NULL); + + /* Something went wrong with the construction of the entity */ + ecs_check(entity != 0, ECS_INVALID_PARAMETER, NULL); + name = ecs_get_fullpath(world, entity); - ecs_entity_t *old_entities = ecs_vector_first(old_data->entities, ecs_entity_t); - int32_t old_count = ecs_vector_count(old_data->entities); - int32_t new_count = ecs_vector_count(new_data->entities); + ecs_term_t term; + if (expr) { + #ifdef FLECS_PARSER + const char *ptr = ecs_parse_term(world, name, expr, expr, &term); + if (!ptr) { + goto error; + } - ecs_record_t **old_records = ecs_vector_first( - old_data->record_ptrs, ecs_record_t*); + if (!ecs_term_is_initialized(&term)) { + ecs_parser_error( + name, expr, 0, "invalid empty trigger expression"); + goto error; + } - /* First, update entity index so old entities point to new type */ - int32_t i; - for(i = 0; i < old_count; i ++) { - ecs_record_t *record; - if (new_table != old_table) { - record = old_records[i]; - ecs_assert(record != NULL, ECS_INTERNAL_ERROR, NULL); + if (ptr[0]) { + ecs_parser_error(name, expr, 0, + "too many terms in trigger expression (expected 1)"); + goto error; + } + #else + ecs_abort(ECS_UNSUPPORTED, "parser addon is not available"); + #endif } else { - record = ecs_eis_ensure(world, old_entities[i]); + term = ecs_term_copy(&desc->term); } - uint32_t flags = ECS_RECORD_TO_ROW_FLAGS(record->row); - record->row = ECS_ROW_TO_RECORD(new_count + i, flags); - record->table = new_table; - } + if (ecs_term_finalize(world, name, &term)) { + goto error; + } - /* Merge table columns */ - if (move_data) { - *new_data = *old_data; - } else { - merge_table_data(world, new_table, old_table, old_count, new_count, - old_data, new_data); - } + ecs_trigger_t *trigger = flecs_sparse_add(world->triggers, ecs_trigger_t); + trigger->id = flecs_sparse_last_id(world->triggers); + trigger->term = ecs_term_move(&term); + trigger->action = desc->callback; + trigger->ctx = desc->ctx; + trigger->binding_ctx = desc->binding_ctx; + trigger->ctx_free = desc->ctx_free; + trigger->binding_ctx_free = desc->binding_ctx_free; + trigger->event_count = count_events(desc->events); + ecs_os_memcpy(trigger->events, desc->events, + trigger->event_count * ECS_SIZEOF(ecs_entity_t)); + trigger->entity = entity; + trigger->self = desc->self; + trigger->observable = observable; + trigger->match_prefab = desc->match_prefab; + trigger->match_disabled = desc->match_disabled; + trigger->instanced = desc->instanced; - new_table->alloc_count ++; + if (trigger->term.id == EcsPrefab) { + trigger->match_prefab = true; + } + if (trigger->term.id == EcsDisabled) { + trigger->match_disabled = true; + } - if (!new_count && old_count) { - table_activate(world, new_table, true); - } -} + comp->trigger = trigger; -void flecs_table_replace_data( - ecs_world_t *world, - ecs_table_t *table, - ecs_data_t *data) -{ - int32_t prev_count = 0; - ecs_data_t *table_data = &table->storage; - ecs_assert(!data || data != table_data, ECS_INTERNAL_ERROR, NULL); - ecs_assert(!table->lock, ECS_LOCKED_STORAGE, NULL); + /* Trigger must have at least one event */ + ecs_check(trigger->event_count != 0, ECS_INVALID_PARAMETER, NULL); - prev_count = ecs_vector_count(table_data->entities); - run_on_remove(world, table, table_data); - flecs_table_clear_data(world, table, table_data); + register_trigger(world, observable, trigger); - if (data) { - table->storage = *data; - } else { - flecs_table_init_data(world, table); - } + ecs_term_fini(&term); - int32_t count = ecs_table_count(table); + if (desc->entity.name) { + ecs_trace("#[green]trigger#[reset] %s created", + ecs_get_name(world, entity)); + } - if (!prev_count && count) { - table_activate(world, table, true); - } else if (prev_count && !count) { - table_activate(world, table, false); + if (desc->yield_existing) { + trigger_yield_existing(world, trigger); + } + } else { + ecs_assert(comp->trigger != NULL, ECS_INTERNAL_ERROR, NULL); + + /* If existing entity handle was provided, override existing params */ + if (existing) { + if (desc->callback) { + ((ecs_trigger_t*)comp->trigger)->action = desc->callback; + } + if (desc->ctx) { + ((ecs_trigger_t*)comp->trigger)->ctx = desc->ctx; + } + if (desc->binding_ctx) { + ((ecs_trigger_t*)comp->trigger)->binding_ctx = desc->binding_ctx; + } + } } - table->alloc_count ++; + ecs_os_free(name); + return entity; +error: + ecs_os_free(name); + return 0; } -int32_t* flecs_table_get_dirty_state( - ecs_table_t *table) +void* ecs_get_trigger_ctx( + const ecs_world_t *world, + ecs_entity_t trigger) { - ecs_assert(!table->lock, ECS_LOCKED_STORAGE, NULL); - - if (!table->dirty_state) { - int32_t column_count = ecs_vector_count(table->storage_type); - table->dirty_state = ecs_os_calloc_n( int32_t, column_count + 1); - ecs_assert(table->dirty_state != NULL, ECS_INTERNAL_ERROR, NULL); - } - return table->dirty_state; + const EcsTrigger *t = ecs_get(world, trigger, EcsTrigger); + if (t) { + return t->trigger->ctx; + } else { + return NULL; + } } -int32_t* flecs_table_get_monitor( - ecs_table_t *table) +void* ecs_get_trigger_binding_ctx( + const ecs_world_t *world, + ecs_entity_t trigger) { - int32_t *dirty_state = flecs_table_get_dirty_state(table); - ecs_assert(dirty_state != NULL, ECS_INTERNAL_ERROR, NULL); - - int32_t column_count = ecs_vector_count(table->storage_type); - return ecs_os_memdup(dirty_state, (column_count + 1) * ECS_SIZEOF(int32_t)); + const EcsTrigger *t = ecs_get(world, trigger, EcsTrigger); + if (t) { + return t->trigger->binding_ctx; + } else { + return NULL; + } } -void flecs_table_notify( +void flecs_trigger_fini( ecs_world_t *world, - ecs_table_t *table, - ecs_table_event_t *event) -{ - if (world->is_fini) { - return; + ecs_trigger_t *trigger) +{ + unregister_trigger(world, trigger->observable, trigger); + ecs_term_fini(&trigger->term); + + if (trigger->ctx_free) { + trigger->ctx_free(trigger->ctx); } - switch(event->kind) { - case EcsTableQueryMatch: - register_query(world, table, event->query); - break; - case EcsTableQueryUnmatch: - unregister_query(world, table, event->query); - break; - case EcsTableComponentInfo: - notify_component_info(world, table, event->component); - break; - case EcsTableTriggersForId: - notify_trigger(world, table, event->event); - break; - case EcsTableNoTriggersForId: - break; + if (trigger->binding_ctx_free) { + trigger->binding_ctx_free(trigger->binding_ctx); } + + flecs_sparse_remove(world->triggers, trigger->id); } -void ecs_table_lock( + +static +void flecs_notify_on_add( ecs_world_t *world, - ecs_table_t *table) -{ - if (ecs_poly_is(world, ecs_world_t) && !world->is_readonly) { - table->lock ++; - } -} + ecs_table_t *table, + ecs_table_t *other_table, + ecs_data_t *data, + int32_t row, + int32_t count, + ecs_table_diff_t *diff, + bool run_on_set); -void ecs_table_unlock( +static +const ecs_entity_t* new_w_data( ecs_world_t *world, - ecs_table_t *table) -{ - if (ecs_poly_is(world, ecs_world_t) && !world->is_readonly) { - table->lock --; - ecs_assert(table->lock >= 0, ECS_INVALID_OPERATION, NULL); - } -} + ecs_table_t *table, + const ecs_entity_t *entities, + ecs_ids_t *component_ids, + int32_t count, + void **c_info, + bool move, + int32_t *row_out, + ecs_table_diff_t *diff); -bool ecs_table_has_module( - ecs_table_t *table) +static +void* get_component_w_index( + ecs_table_t *table, + int32_t column_index, + int32_t row) { - return table->flags & EcsTableHasModule; -} + ecs_check(column_index < ecs_table_storage_count(table), + ECS_NOT_A_COMPONENT, NULL); -ecs_column_t *ecs_table_column_for_id( - const ecs_world_t *world, - const ecs_table_t *table, - ecs_id_t id) -{ - ecs_table_t *storage_table = table->storage_table; - if (!storage_table) { - return NULL; - } + ecs_column_t *column = &table->storage.columns[column_index]; - ecs_table_record_t *tr = flecs_get_table_record(world, storage_table, id); - if (tr) { - return &table->storage.columns[tr->column]; - } + /* If size is 0, component does not have a value. This is likely caused by + * an application trying to call ecs_get with a tag. */ + int32_t size = column->size; + ecs_check(size != 0, ECS_INVALID_PARAMETER, NULL); - return NULL; -} - -ecs_type_t ecs_table_get_type( - const ecs_table_t *table) -{ - return table->type; -} - -ecs_type_t ecs_table_get_storage_type( - const ecs_table_t *table) -{ - return table->storage_type; -} - -int32_t ecs_table_storage_count( - const ecs_table_t *table) -{ - return ecs_vector_count(table->storage_type); -} - -int32_t ecs_table_type_to_storage_index( - const ecs_table_t *table, - int32_t index) -{ - ecs_check(index < ecs_vector_count(table->type), - ECS_INVALID_PARAMETER, NULL); - int32_t *storage_map = table->storage_map; - if (storage_map) { - return storage_map[index]; - } -error: - return -1; -} - -int32_t ecs_table_storage_to_type_index( - const ecs_table_t *table, - int32_t index) -{ - ecs_check(index < ecs_vector_count(table->storage_type), - ECS_INVALID_PARAMETER, NULL); - ecs_check(table->storage_map != NULL, ECS_INVALID_PARAMETER, NULL); - int32_t offset = ecs_vector_count(table->type); - return table->storage_map[offset + index]; -error: - return -1; -} - -ecs_record_t* ecs_record_find( - ecs_world_t *world, - ecs_entity_t entity) -{ - ecs_poly_assert(world, ecs_world_t); - ecs_check(entity != 0, ECS_INVALID_PARAMETER, NULL); - ecs_record_t *r = ecs_eis_get(world, entity); - if (r) { - return r; - } -error: - return NULL; -} - -void* ecs_record_get_column( - ecs_record_t *r, - int32_t column, - size_t c_size) -{ - (void)c_size; - ecs_table_t *table = r->table; - - ecs_check(column < ecs_vector_count(table->storage_type), - ECS_INVALID_PARAMETER, NULL); - - ecs_column_t *c = &table->storage.columns[column]; - ecs_assert(c != NULL, ECS_INTERNAL_ERROR, NULL); - - ecs_check(!flecs_utosize(c_size) || - flecs_utosize(c_size) == c->size, - ECS_INVALID_PARAMETER, NULL); - - return ecs_vector_get_t(c->data, c->size, c->alignment, - ECS_RECORD_TO_ROW(r->row)); -error: - return NULL; -} - - -static const char* mixin_kind_str[] = { - [EcsMixinBase] = "base (should never be requested by application)", - [EcsMixinWorld] = "world", - [EcsMixinObservable] = "observable", - [EcsMixinIterable] = "iterable", - [EcsMixinMax] = "max (should never be requested by application)" -}; - -ecs_mixins_t ecs_world_t_mixins = { - .type_name = "ecs_world_t", - .elems = { - [EcsMixinWorld] = offsetof(ecs_world_t, self), - [EcsMixinObservable] = offsetof(ecs_world_t, observable), - [EcsMixinIterable] = offsetof(ecs_world_t, iterable) - } -}; - -ecs_mixins_t ecs_stage_t_mixins = { - .type_name = "ecs_stage_t", - .elems = { - [EcsMixinBase] = offsetof(ecs_stage_t, world), - [EcsMixinWorld] = offsetof(ecs_stage_t, world) - } -}; - -ecs_mixins_t ecs_query_t_mixins = { - .type_name = "ecs_query_t", - .elems = { - [EcsMixinWorld] = offsetof(ecs_query_t, world), - [EcsMixinIterable] = offsetof(ecs_query_t, iterable) - } -}; - -ecs_mixins_t ecs_filter_t_mixins = { - .type_name = "ecs_filter_t", - .elems = { - [EcsMixinIterable] = offsetof(ecs_filter_t, iterable) - } -}; - -static -void* get_mixin( - const ecs_poly_t *poly, - ecs_mixin_kind_t kind) -{ - ecs_assert(poly != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_assert(kind < EcsMixinMax, ECS_INVALID_PARAMETER, NULL); - - const ecs_header_t *hdr = poly; - ecs_assert(hdr != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_assert(hdr->magic == ECS_OBJECT_MAGIC, ECS_INVALID_PARAMETER, NULL); - - const ecs_mixins_t *mixins = hdr->mixins; - if (!mixins) { - /* Object has no mixins */ - goto not_found; - } - - ecs_size_t offset = mixins->elems[kind]; - if (offset == 0) { - /* Object has mixins but not the requested one. Try to find the mixin - * in the poly's base */ - goto find_in_base; - } - - /* Object has mixin, return its address */ - return ECS_OFFSET(hdr, offset); - -find_in_base: - if (offset) { - /* If the poly has a base, try to find the mixin in the base */ - ecs_poly_t *base = *(ecs_poly_t**)ECS_OFFSET(hdr, offset); - if (base) { - return get_mixin(base, kind); - } - } - -not_found: - /* Mixin wasn't found for poly */ - return NULL; -} - -static -void* assert_mixin( - const ecs_poly_t *poly, - ecs_mixin_kind_t kind) -{ - void *ptr = get_mixin(poly, kind); - if (!ptr) { - const ecs_header_t *header = poly; - const ecs_mixins_t *mixins = header->mixins; - ecs_err("%s not available for type %s", - mixin_kind_str[kind], - mixins ? mixins->type_name : "unknown"); - ecs_os_abort(); - } - - return ptr; -} - -void* _ecs_poly_init( - ecs_poly_t *poly, - int32_t type, - ecs_size_t size, - ecs_mixins_t *mixins) -{ - ecs_assert(poly != NULL, ECS_INVALID_PARAMETER, NULL); - - ecs_header_t *hdr = poly; - ecs_os_memset(poly, 0, size); - - hdr->magic = ECS_OBJECT_MAGIC; - hdr->type = type; - hdr->mixins = mixins; - - return poly; -} - -void _ecs_poly_fini( - ecs_poly_t *poly, - int32_t type) -{ - ecs_assert(poly != NULL, ECS_INVALID_PARAMETER, NULL); - (void)type; - - ecs_header_t *hdr = poly; - - /* Don't deinit poly that wasn't initialized */ - ecs_assert(hdr->magic == ECS_OBJECT_MAGIC, ECS_INVALID_PARAMETER, NULL); - ecs_assert(hdr->type == type, ECS_INVALID_PARAMETER, NULL); - hdr->magic = 0; -} - -#define assert_object(cond, file, line)\ - _ecs_assert((cond), ECS_INVALID_PARAMETER, #cond, file, line, NULL);\ - assert(cond) - -#ifndef NDEBUG -void _ecs_poly_assert( - const ecs_poly_t *poly, - int32_t type, - const char *file, - int32_t line) -{ - assert_object(poly != NULL, file, line); - - const ecs_header_t *hdr = poly; - assert_object(hdr->magic == ECS_OBJECT_MAGIC, file, line); - assert_object(hdr->type == type, file, line); -} -#endif - -bool _ecs_poly_is( - const ecs_poly_t *poly, - int32_t type) -{ - ecs_assert(poly != NULL, ECS_INVALID_PARAMETER, NULL); - - const ecs_header_t *hdr = poly; - ecs_assert(hdr->magic == ECS_OBJECT_MAGIC, ECS_INVALID_PARAMETER, NULL); - return hdr->type == type; -} - -ecs_iterable_t* ecs_get_iterable( - const ecs_poly_t *poly) -{ - return (ecs_iterable_t*)assert_mixin(poly, EcsMixinIterable); -} - -ecs_observable_t* ecs_get_observable( - const ecs_poly_t *poly) -{ - return (ecs_observable_t*)assert_mixin(poly, EcsMixinObservable); -} - -const ecs_world_t* ecs_get_world( - const ecs_poly_t *poly) -{ - return *(ecs_world_t**)assert_mixin(poly, EcsMixinWorld); -} - - - -static -void flecs_notify_on_add( - ecs_world_t *world, - ecs_table_t *table, - ecs_table_t *other_table, - ecs_data_t *data, - int32_t row, - int32_t count, - ecs_table_diff_t *diff, - bool run_on_set); - -static -const ecs_entity_t* new_w_data( - ecs_world_t *world, - ecs_table_t *table, - const ecs_entity_t *entities, - ecs_ids_t *component_ids, - int32_t count, - void **c_info, - bool move, - int32_t *row_out, - ecs_table_diff_t *diff); - -static -void* get_component_w_index( - ecs_table_t *table, - int32_t column_index, - int32_t row) -{ - ecs_check(column_index < ecs_table_storage_count(table), - ECS_NOT_A_COMPONENT, NULL); - - ecs_column_t *column = &table->storage.columns[column_index]; - - /* If size is 0, component does not have a value. This is likely caused by - * an application trying to call ecs_get with a tag. */ - int32_t size = column->size; - ecs_check(size != 0, ECS_INVALID_PARAMETER, NULL); - - void *ptr = ecs_vector_first_t(column->data, size, column->alignment); - return ECS_OFFSET(ptr, size * row); -error: + void *ptr = ecs_vector_first_t(column->data, size, column->alignment); + return ECS_OFFSET(ptr, size * row); +error: return NULL; } @@ -8346,1940 +7976,1760 @@ bool flecs_defer_purge( return false; } +#ifndef _MSC_VER +#pragma GCC diagnostic ignored "-Wimplicit-fallthrough" +#endif -static -ecs_defer_op_t* new_defer_op(ecs_stage_t *stage) { - ecs_defer_op_t *result = ecs_vector_add(&stage->defer_queue, ecs_defer_op_t); - ecs_os_memset(result, 0, ECS_SIZEOF(ecs_defer_op_t)); - return result; -} +/* See explanation below. The hashing function may read beyond the memory passed + * into the hashing function, but only at word boundaries. This should be safe, + * but trips up address sanitizers and valgrind. + * This ensures clean valgrind logs in debug mode & the best perf in release */ +#if !defined(NDEBUG) || defined(ADDRESS_SANITIZER) +#ifndef VALGRIND +#define VALGRIND +#endif +#endif -static -bool defer_add_remove( - ecs_world_t *world, - ecs_stage_t *stage, - ecs_defer_op_kind_t op_kind, - ecs_entity_t entity, - ecs_id_t id) -{ - if (stage->defer) { - if (!id) { - return true; - } +/* +------------------------------------------------------------------------------- +lookup3.c, by Bob Jenkins, May 2006, Public Domain. + http://burtleburtle.net/bob/c/lookup3.c +------------------------------------------------------------------------------- +*/ - ecs_defer_op_t *op = new_defer_op(stage); - op->kind = op_kind; - op->id = id; - op->is._1.entity = entity; +#ifdef _MSC_VER +//FIXME +#else +#include /* attempt to define endianness */ +#endif +#ifdef linux +# include /* attempt to define endianness */ +#endif - if (op_kind == EcsOpNew) { - world->new_count ++; - } else if (op_kind == EcsOpAdd) { - world->add_count ++; - } else if (op_kind == EcsOpRemove) { - world->remove_count ++; - } +/* + * My best guess at if you are big-endian or little-endian. This may + * need adjustment. + */ +#if (defined(__BYTE_ORDER) && defined(__LITTLE_ENDIAN) && \ + __BYTE_ORDER == __LITTLE_ENDIAN) || \ + (defined(i386) || defined(__i386__) || defined(__i486__) || \ + defined(__i586__) || defined(__i686__) || defined(vax) || defined(MIPSEL)) +# define HASH_LITTLE_ENDIAN 1 +#elif (defined(__BYTE_ORDER) && defined(__BIG_ENDIAN) && \ + __BYTE_ORDER == __BIG_ENDIAN) || \ + (defined(sparc) || defined(POWERPC) || defined(mc68000) || defined(sel)) +# define HASH_LITTLE_ENDIAN 0 +#else +# define HASH_LITTLE_ENDIAN 0 +#endif - return true; - } else { - stage->defer ++; - } - - return false; +#define rot(x,k) (((x)<<(k)) | ((x)>>(32-(k)))) + +/* +------------------------------------------------------------------------------- +mix -- mix 3 32-bit values reversibly. +This is reversible, so any information in (a,b,c) before mix() is +still in (a,b,c) after mix(). +If four pairs of (a,b,c) inputs are run through mix(), or through +mix() in reverse, there are at least 32 bits of the output that +are sometimes the same for one pair and different for another pair. +This was tested for: +* pairs that differed by one bit, by two bits, in any combination + of top bits of (a,b,c), or in any combination of bottom bits of + (a,b,c). +* "differ" is defined as +, -, ^, or ~^. For + and -, I transformed + the output delta to a Gray code (a^(a>>1)) so a string of 1's (as + is commonly produced by subtraction) look like a single 1-bit + difference. +* the base values were pseudorandom, all zero but one bit set, or + all zero plus a counter that starts at zero. +Some k values for my "a-=c; a^=rot(c,k); c+=b;" arrangement that +satisfy this are + 4 6 8 16 19 4 + 9 15 3 18 27 15 + 14 9 3 7 17 3 +Well, "9 15 3 18 27 15" didn't quite get 32 bits diffing +for "differ" defined as + with a one-bit base and a two-bit delta. I +used http://burtleburtle.net/bob/hash/avalanche.html to choose +the operations, constants, and arrangements of the variables. +This does not achieve avalanche. There are input bits of (a,b,c) +that fail to affect some output bits of (a,b,c), especially of a. The +most thoroughly mixed value is c, but it doesn't really even achieve +avalanche in c. +This allows some parallelism. Read-after-writes are good at doubling +the number of bits affected, so the goal of mixing pulls in the opposite +direction as the goal of parallelism. I did what I could. Rotates +seem to cost as much as shifts on every machine I could lay my hands +on, and rotates are much kinder to the top and bottom bits, so I used +rotates. +------------------------------------------------------------------------------- +*/ +#define mix(a,b,c) \ +{ \ + a -= c; a ^= rot(c, 4); c += b; \ + b -= a; b ^= rot(a, 6); a += c; \ + c -= b; c ^= rot(b, 8); b += a; \ + a -= c; a ^= rot(c,16); c += b; \ + b -= a; b ^= rot(a,19); a += c; \ + c -= b; c ^= rot(b, 4); b += a; \ +} + +/* +------------------------------------------------------------------------------- +final -- final mixing of 3 32-bit values (a,b,c) into c +Pairs of (a,b,c) values differing in only a few bits will usually +produce values of c that look totally different. This was tested for +* pairs that differed by one bit, by two bits, in any combination + of top bits of (a,b,c), or in any combination of bottom bits of + (a,b,c). +* "differ" is defined as +, -, ^, or ~^. For + and -, I transformed + the output delta to a Gray code (a^(a>>1)) so a string of 1's (as + is commonly produced by subtraction) look like a single 1-bit + difference. +* the base values were pseudorandom, all zero but one bit set, or + all zero plus a counter that starts at zero. +These constants passed: + 14 11 25 16 4 14 24 + 12 14 25 16 4 14 24 +and these came close: + 4 8 15 26 3 22 24 + 10 8 15 26 3 22 24 + 11 8 15 26 3 22 24 +------------------------------------------------------------------------------- +*/ +#define final(a,b,c) \ +{ \ + c ^= b; c -= rot(b,14); \ + a ^= c; a -= rot(c,11); \ + b ^= a; b -= rot(a,25); \ + c ^= b; c -= rot(b,16); \ + a ^= c; a -= rot(c,4); \ + b ^= a; b -= rot(a,14); \ + c ^= b; c -= rot(b,24); \ } + +/* + * hashlittle2: return 2 32-bit hash values + * + * This is identical to hashlittle(), except it returns two 32-bit hash + * values instead of just one. This is good enough for hash table + * lookup with 2^^64 buckets, or if you want a second hash if you're not + * happy with the first, or if you want a probably-unique 64-bit ID for + * the key. *pc is better mixed than *pb, so use *pc first. If you want + * a 64-bit value do something like "*pc + (((uint64_t)*pb)<<32)". + */ static -void merge_stages( - ecs_world_t *world, - bool force_merge) +void hashlittle2( + const void *key, /* the key to hash */ + size_t length, /* length of the key */ + uint32_t *pc, /* IN: primary initval, OUT: primary hash */ + uint32_t *pb) /* IN: secondary initval, OUT: secondary hash */ { - bool is_stage = ecs_poly_is(world, ecs_stage_t); - ecs_stage_t *stage = flecs_stage_from_world(&world); + uint32_t a,b,c; /* internal state */ + union { const void *ptr; size_t i; } u; /* needed for Mac Powerbook G4 */ - bool measure_frame_time = world->measure_frame_time; + /* Set up the internal state */ + a = b = c = 0xdeadbeef + ((uint32_t)length) + *pc; + c += *pb; - ecs_time_t t_start; - if (measure_frame_time) { - ecs_os_get_time(&t_start); - } + u.ptr = key; + if (HASH_LITTLE_ENDIAN && ((u.i & 0x3) == 0)) { + const uint32_t *k = (const uint32_t *)key; /* read 32-bit chunks */ + const uint8_t *k8; + (void)k8; - if (is_stage) { - /* Check for consistency if force_merge is enabled. In practice this - * function will never get called with force_merge disabled for just - * a single stage. */ - if (force_merge || stage->auto_merge) { - ecs_defer_end((ecs_world_t*)stage); - } - } else { - /* Merge stages. Only merge if the stage has auto_merging turned on, or - * if this is a forced merge (like when ecs_merge is called) */ - int32_t i, count = ecs_get_stage_count(world); - for (i = 0; i < count; i ++) { - ecs_stage_t *s = (ecs_stage_t*)ecs_get_stage(world, i); - ecs_poly_assert(s, ecs_stage_t); - if (force_merge || s->auto_merge) { - ecs_defer_end((ecs_world_t*)s); - } - } + /*------ all but last block: aligned reads and affect 32 bits of (a,b,c) */ + while (length > 12) + { + a += k[0]; + b += k[1]; + c += k[2]; + mix(a,b,c); + length -= 12; + k += 3; } - flecs_eval_component_monitors(world); + /*----------------------------- handle the last (probably partial) block */ + /* + * "k[2]&0xffffff" actually reads beyond the end of the string, but + * then masks off the part it's not allowed to read. Because the + * string is aligned, the masked-off tail is in the same word as the + * rest of the string. Every machine with memory protection I've seen + * does it on word boundaries, so is OK with this. But VALGRIND will + * still catch it and complain. The masking trick does make the hash + * noticably faster for short strings (like English words). + */ +#ifndef VALGRIND - if (measure_frame_time) { - world->stats.merge_time_total += (float)ecs_time_measure(&t_start); + switch(length) + { + case 12: c+=k[2]; b+=k[1]; a+=k[0]; break; + case 11: c+=k[2]&0xffffff; b+=k[1]; a+=k[0]; break; + case 10: c+=k[2]&0xffff; b+=k[1]; a+=k[0]; break; + case 9 : c+=k[2]&0xff; b+=k[1]; a+=k[0]; break; + case 8 : b+=k[1]; a+=k[0]; break; + case 7 : b+=k[1]&0xffffff; a+=k[0]; break; + case 6 : b+=k[1]&0xffff; a+=k[0]; break; + case 5 : b+=k[1]&0xff; a+=k[0]; break; + case 4 : a+=k[0]; break; + case 3 : a+=k[0]&0xffffff; break; + case 2 : a+=k[0]&0xffff; break; + case 1 : a+=k[0]&0xff; break; + case 0 : *pc=c; *pb=b; return; /* zero length strings require no mixing */ } - world->stats.merge_count_total ++; +#else /* make valgrind happy */ - /* If stage is asynchronous, deferring is always enabled */ - if (stage->asynchronous) { - ecs_defer_begin((ecs_world_t*)stage); + k8 = (const uint8_t *)k; + switch(length) + { + case 12: c+=k[2]; b+=k[1]; a+=k[0]; break; + case 11: c+=((uint32_t)k8[10])<<16; /* fall through */ + case 10: c+=((uint32_t)k8[9])<<8; /* fall through */ + case 9 : c+=k8[8]; /* fall through */ + case 8 : b+=k[1]; a+=k[0]; break; + case 7 : b+=((uint32_t)k8[6])<<16; /* fall through */ + case 6 : b+=((uint32_t)k8[5])<<8; /* fall through */ + case 5 : b+=k8[4]; /* fall through */ + case 4 : a+=k[0]; break; + case 3 : a+=((uint32_t)k8[2])<<16; /* fall through */ + case 2 : a+=((uint32_t)k8[1])<<8; /* fall through */ + case 1 : a+=k8[0]; break; + case 0 : *pc=c; *pb=b; return; /* zero length strings require no mixing */ } -} - -static -void do_auto_merge( - ecs_world_t *world) -{ - merge_stages(world, false); -} -static -void do_manual_merge( - ecs_world_t *world) -{ - merge_stages(world, true); -} +#endif /* !valgrind */ -bool flecs_defer_none( - ecs_world_t *world, - ecs_stage_t *stage) -{ - (void)world; - return (++ stage->defer) == 1; -} + } else if (HASH_LITTLE_ENDIAN && ((u.i & 0x1) == 0)) { + const uint16_t *k = (const uint16_t *)key; /* read 16-bit chunks */ + const uint8_t *k8; -bool flecs_defer_modified( - ecs_world_t *world, - ecs_stage_t *stage, - ecs_entity_t entity, - ecs_id_t id) -{ - (void)world; - if (stage->defer) { - ecs_defer_op_t *op = new_defer_op(stage); - op->kind = EcsOpModified; - op->id = id; - op->is._1.entity = entity; - return true; - } else { - stage->defer ++; + /*--------------- all but last block: aligned reads and different mixing */ + while (length > 12) + { + a += k[0] + (((uint32_t)k[1])<<16); + b += k[2] + (((uint32_t)k[3])<<16); + c += k[4] + (((uint32_t)k[5])<<16); + mix(a,b,c); + length -= 12; + k += 6; } - - return false; -} -bool flecs_defer_clone( - ecs_world_t *world, - ecs_stage_t *stage, - ecs_entity_t entity, - ecs_entity_t src, - bool clone_value) -{ - (void)world; - if (stage->defer) { - ecs_defer_op_t *op = new_defer_op(stage); - op->kind = EcsOpClone; - op->id = src; - op->is._1.entity = entity; - op->is._1.clone_value = clone_value; - return true; - } else { - stage->defer ++; + /*----------------------------- handle the last (probably partial) block */ + k8 = (const uint8_t *)k; + switch(length) + { + case 12: c+=k[4]+(((uint32_t)k[5])<<16); + b+=k[2]+(((uint32_t)k[3])<<16); + a+=k[0]+(((uint32_t)k[1])<<16); + break; + case 11: c+=((uint32_t)k8[10])<<16; /* fall through */ + case 10: c+=k[4]; + b+=k[2]+(((uint32_t)k[3])<<16); + a+=k[0]+(((uint32_t)k[1])<<16); + break; + case 9 : c+=k8[8]; /* fall through */ + case 8 : b+=k[2]+(((uint32_t)k[3])<<16); + a+=k[0]+(((uint32_t)k[1])<<16); + break; + case 7 : b+=((uint32_t)k8[6])<<16; /* fall through */ + case 6 : b+=k[2]; + a+=k[0]+(((uint32_t)k[1])<<16); + break; + case 5 : b+=k8[4]; /* fall through */ + case 4 : a+=k[0]+(((uint32_t)k[1])<<16); + break; + case 3 : a+=((uint32_t)k8[2])<<16; /* fall through */ + case 2 : a+=k[0]; + break; + case 1 : a+=k8[0]; + break; + case 0 : *pc=c; *pb=b; return; /* zero length strings require no mixing */ } - - return false; -} -bool flecs_defer_delete( - ecs_world_t *world, - ecs_stage_t *stage, - ecs_entity_t entity) -{ - (void)world; - if (stage->defer) { - ecs_defer_op_t *op = new_defer_op(stage); - op->kind = EcsOpDelete; - op->is._1.entity = entity; - world->delete_count ++; - return true; - } else { - stage->defer ++; - } - return false; -} + } else { /* need to read the key one byte at a time */ + const uint8_t *k = (const uint8_t *)key; -bool flecs_defer_clear( - ecs_world_t *world, - ecs_stage_t *stage, - ecs_entity_t entity) -{ - (void)world; - if (stage->defer) { - ecs_defer_op_t *op = new_defer_op(stage); - op->kind = EcsOpClear; - op->is._1.entity = entity; - world->clear_count ++; - return true; - } else { - stage->defer ++; + /*--------------- all but the last block: affect some 32 bits of (a,b,c) */ + while (length > 12) + { + a += k[0]; + a += ((uint32_t)k[1])<<8; + a += ((uint32_t)k[2])<<16; + a += ((uint32_t)k[3])<<24; + b += k[4]; + b += ((uint32_t)k[5])<<8; + b += ((uint32_t)k[6])<<16; + b += ((uint32_t)k[7])<<24; + c += k[8]; + c += ((uint32_t)k[9])<<8; + c += ((uint32_t)k[10])<<16; + c += ((uint32_t)k[11])<<24; + mix(a,b,c); + length -= 12; + k += 12; } - return false; -} -bool flecs_defer_on_delete_action( - ecs_world_t *world, - ecs_stage_t *stage, - ecs_id_t id, - ecs_entity_t action) -{ - (void)world; - if (stage->defer) { - ecs_defer_op_t *op = new_defer_op(stage); - op->kind = EcsOpOnDeleteAction; - op->id = id; - op->is._1.entity = action; - world->clear_count ++; - return true; - } else { - stage->defer ++; + /*-------------------------------- last block: affect all 32 bits of (c) */ + switch(length) /* all the case statements fall through */ + { + case 12: c+=((uint32_t)k[11])<<24; + case 11: c+=((uint32_t)k[10])<<16; + case 10: c+=((uint32_t)k[9])<<8; + case 9 : c+=k[8]; + case 8 : b+=((uint32_t)k[7])<<24; + case 7 : b+=((uint32_t)k[6])<<16; + case 6 : b+=((uint32_t)k[5])<<8; + case 5 : b+=k[4]; + case 4 : a+=((uint32_t)k[3])<<24; + case 3 : a+=((uint32_t)k[2])<<16; + case 2 : a+=((uint32_t)k[1])<<8; + case 1 : a+=k[0]; + break; + case 0 : *pc=c; *pb=b; return; /* zero length strings require no mixing */ } - return false; -} + } -bool flecs_defer_enable( - ecs_world_t *world, - ecs_stage_t *stage, - ecs_entity_t entity, - ecs_id_t id, - bool enable) -{ - (void)world; - if (stage->defer) { - ecs_defer_op_t *op = new_defer_op(stage); - op->kind = enable ? EcsOpEnable : EcsOpDisable; - op->is._1.entity = entity; - op->id = id; - return true; - } else { - stage->defer ++; - } - return false; + final(a,b,c); + *pc=c; *pb=b; } -bool flecs_defer_bulk_new( - ecs_world_t *world, - ecs_stage_t *stage, - int32_t count, - ecs_id_t id, - const ecs_entity_t **ids_out) +uint64_t flecs_hash( + const void *data, + ecs_size_t length) { - if (stage->defer) { - ecs_entity_t *ids = ecs_os_malloc(count * ECS_SIZEOF(ecs_entity_t)); - world->bulk_new_count ++; + uint32_t h_1 = 0; + uint32_t h_2 = 0; - /* Use ecs_new_id as this is thread safe */ - int i; - for (i = 0; i < count; i ++) { - ids[i] = ecs_new_id(world); - } + hashlittle2( + data, + flecs_ito(size_t, length), + &h_1, + &h_2); - *ids_out = ids; + return h_1 | ((uint64_t)h_2 << 32); +} - /* Store data in op */ - ecs_defer_op_t *op = new_defer_op(stage); - op->kind = EcsOpBulkNew; - op->id = id; - op->is._n.entities = ids; - op->is._n.count = count; - - return true; - } else { - stage->defer ++; - } - - return false; -} - -bool flecs_defer_new( - ecs_world_t *world, - ecs_stage_t *stage, - ecs_entity_t entity, - ecs_id_t id) -{ - return defer_add_remove(world, stage, EcsOpNew, entity, id); -} - -bool flecs_defer_add( - ecs_world_t *world, - ecs_stage_t *stage, - ecs_entity_t entity, - ecs_id_t id) -{ - return defer_add_remove(world, stage, EcsOpAdd, entity, id); -} +/** The number of elements in a single chunk */ +#define CHUNK_COUNT (4096) -bool flecs_defer_remove( - ecs_world_t *world, - ecs_stage_t *stage, - ecs_entity_t entity, - ecs_id_t id) -{ - return defer_add_remove(world, stage, EcsOpRemove, entity, id); -} +/** Compute the chunk index from an id by stripping the first 12 bits */ +#define CHUNK(index) ((int32_t)((uint32_t)index >> 12)) -bool flecs_defer_set( - ecs_world_t *world, - ecs_stage_t *stage, - ecs_defer_op_kind_t op_kind, - ecs_entity_t entity, - ecs_id_t id, - ecs_size_t size, - const void *value, - void **value_out, - bool *is_added) -{ - if (stage->defer) { - world->set_count ++; - if (!size) { - const EcsComponent *cptr = flecs_component_from_id(world, id); - ecs_check(cptr != NULL, ECS_INVALID_PARAMETER, NULL); - size = cptr->size; - } +/** This computes the offset of an index inside a chunk */ +#define OFFSET(index) ((int32_t)index & 0xFFF) - ecs_defer_op_t *op = new_defer_op(stage); - op->kind = op_kind; - op->id = id; - op->is._1.entity = entity; - op->is._1.size = size; - op->is._1.value = ecs_os_malloc(size); +/* Utility to get a pointer to the payload */ +#define DATA(array, size, offset) (ECS_OFFSET(array, size * offset)) - if (!value) { - value = ecs_get_id(world, entity, id); - if (is_added) { - *is_added = value == NULL; - } - } +typedef struct chunk_t { + int32_t *sparse; /* Sparse array with indices to dense array */ + void *data; /* Store data in sparse array to reduce + * indirection and provide stable pointers. */ +} chunk_t; - const ecs_type_info_t *c_info = NULL; - ecs_entity_t real_id = ecs_get_typeid(world, id); - if (real_id) { - c_info = flecs_get_c_info(world, real_id); - } +struct ecs_sparse_t { + ecs_vector_t *dense; /* Dense array with indices to sparse array. The + * dense array stores both alive and not alive + * sparse indices. The 'count' member keeps + * track of which indices are alive. */ - if (value) { - ecs_copy_ctor_t copy; - if (c_info && (copy = c_info->lifecycle.copy_ctor)) { - copy(world, id, &c_info->lifecycle, &entity, &entity, - op->is._1.value, value, flecs_itosize(size), 1, - c_info->lifecycle.ctx); - } else { - ecs_os_memcpy(op->is._1.value, value, size); - } - } else { - ecs_xtor_t ctor; - if (c_info && (ctor = c_info->lifecycle.ctor)) { - ctor(world, id, &entity, op->is._1.value, - flecs_itosize(size), 1, c_info->lifecycle.ctx); - } - } + ecs_vector_t *chunks; /* Chunks with sparse arrays & data */ + ecs_size_t size; /* Element size */ + int32_t count; /* Number of alive entries */ + uint64_t max_id_local; /* Local max index (if no global is set) */ + uint64_t *max_id; /* Maximum issued sparse index */ +}; - if (value_out) { - *value_out = op->is._1.value; - } +static +chunk_t* chunk_new( + ecs_sparse_t *sparse, + int32_t chunk_index) +{ + int32_t count = ecs_vector_count(sparse->chunks); + chunk_t *chunks; - return true; + if (count <= chunk_index) { + ecs_vector_set_count(&sparse->chunks, chunk_t, chunk_index + 1); + chunks = ecs_vector_first(sparse->chunks, chunk_t); + ecs_os_memset(&chunks[count], 0, (1 + chunk_index - count) * ECS_SIZEOF(chunk_t)); } else { - stage->defer ++; + chunks = ecs_vector_first(sparse->chunks, chunk_t); } -error: - return false; -} + ecs_assert(chunks != NULL, ECS_INTERNAL_ERROR, NULL); -void flecs_stage_merge_post_frame( - ecs_world_t *world, - ecs_stage_t *stage) -{ - /* Execute post frame actions */ - ecs_vector_each(stage->post_frame_actions, ecs_action_elem_t, action, { - action->action(world, action->ctx); - }); + chunk_t *result = &chunks[chunk_index]; + ecs_assert(result->sparse == NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(result->data == NULL, ECS_INTERNAL_ERROR, NULL); - ecs_vector_free(stage->post_frame_actions); - stage->post_frame_actions = NULL; -} + /* Initialize sparse array with zero's, as zero is used to indicate that the + * sparse element has not been paired with a dense element. Use zero + * as this means we can take advantage of calloc having a possibly better + * performance than malloc + memset. */ + result->sparse = ecs_os_calloc(ECS_SIZEOF(int32_t) * CHUNK_COUNT); -void flecs_stage_init( - ecs_world_t *world, - ecs_stage_t *stage) -{ - ecs_poly_assert(world, ecs_world_t); + /* Initialize the data array with zero's to guarantee that data is + * always initialized. When an entry is removed, data is reset back to + * zero. Initialize now, as this can take advantage of calloc. */ + result->data = ecs_os_calloc(sparse->size * CHUNK_COUNT); - ecs_poly_init(stage, ecs_stage_t); + ecs_assert(result->sparse != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(result->data != NULL, ECS_INTERNAL_ERROR, NULL); - stage->world = world; - stage->thread_ctx = world; - stage->auto_merge = true; - stage->asynchronous = false; + return result; } -void flecs_stage_deinit( - ecs_world_t *world, - ecs_stage_t *stage) +static +void chunk_free( + chunk_t *chunk) { - (void)world; - ecs_poly_assert(world, ecs_world_t); - ecs_poly_assert(stage, ecs_stage_t); - - /* Make sure stage has no unmerged data */ - ecs_assert(ecs_vector_count(stage->defer_queue) == 0, - ECS_INTERNAL_ERROR, NULL); - - ecs_poly_fini(stage, ecs_stage_t); - - ecs_vector_free(stage->defer_queue); + ecs_os_free(chunk->sparse); + ecs_os_free(chunk->data); } -void ecs_set_stages( - ecs_world_t *world, - int32_t stage_count) +static +chunk_t* get_chunk( + const ecs_sparse_t *sparse, + int32_t chunk_index) { - ecs_poly_assert(world, ecs_world_t); - - ecs_stage_t *stages; - int32_t i, count = ecs_vector_count(world->worker_stages); - - if (count && count != stage_count) { - stages = ecs_vector_first(world->worker_stages, ecs_stage_t); - - for (i = 0; i < count; i ++) { - /* If stage contains a thread handle, ecs_set_threads was used to - * create the stages. ecs_set_threads and ecs_set_stages should not - * be mixed. */ - ecs_poly_assert(&stages[i], ecs_stage_t); - ecs_check(stages[i].thread == 0, ECS_INVALID_OPERATION, NULL); - flecs_stage_deinit(world, &stages[i]); - } - - ecs_vector_free(world->worker_stages); + if (!sparse->chunks) { + return NULL; } - - if (stage_count) { - world->worker_stages = ecs_vector_new(ecs_stage_t, stage_count); - - for (i = 0; i < stage_count; i ++) { - ecs_stage_t *stage = ecs_vector_add( - &world->worker_stages, ecs_stage_t); - flecs_stage_init(world, stage); - stage->id = 1 + i; /* 0 is reserved for main/temp stage */ - - /* Set thread_ctx to stage, as this stage might be used in a - * multithreaded context */ - stage->thread_ctx = (ecs_world_t*)stage; - } - } else { - /* Set to NULL to prevent double frees */ - world->worker_stages = NULL; + if (chunk_index >= ecs_vector_count(sparse->chunks)) { + return NULL; } - /* Regardless of whether the stage was just initialized or not, when the - * ecs_set_stages function is called, all stages inherit the auto_merge - * property from the world */ - for (i = 0; i < stage_count; i ++) { - ecs_stage_t *stage = (ecs_stage_t*)ecs_get_stage(world, i); - stage->auto_merge = world->stage.auto_merge; + /* If chunk_index is below zero, application used an invalid entity id */ + ecs_assert(chunk_index >= 0, ECS_INVALID_PARAMETER, NULL); + chunk_t *result = ecs_vector_get(sparse->chunks, chunk_t, chunk_index); + if (result && !result->sparse) { + return NULL; } -error: - return; -} -int32_t ecs_get_stage_count( - const ecs_world_t *world) -{ - world = ecs_get_world(world); - return ecs_vector_count(world->worker_stages); + return result; } -int32_t ecs_get_stage_id( - const ecs_world_t *world) +static +chunk_t* get_or_create_chunk( + ecs_sparse_t *sparse, + int32_t chunk_index) { - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + chunk_t *chunk = get_chunk(sparse, chunk_index); + if (chunk) { + return chunk; + } - if (ecs_poly_is(world, ecs_stage_t)) { - ecs_stage_t *stage = (ecs_stage_t*)world; + return chunk_new(sparse, chunk_index); +} - /* Index 0 is reserved for main stage */ - return stage->id - 1; - } else if (ecs_poly_is(world, ecs_world_t)) { - return 0; - } else { - ecs_throw(ECS_INTERNAL_ERROR, NULL); - } -error: - return 0; +static +void grow_dense( + ecs_sparse_t *sparse) +{ + ecs_vector_add(&sparse->dense, uint64_t); } -ecs_world_t* ecs_get_stage( - const ecs_world_t *world, - int32_t stage_id) +static +uint64_t strip_generation( + uint64_t *index_out) { - ecs_poly_assert(world, ecs_world_t); - ecs_check(ecs_vector_count(world->worker_stages) > stage_id, + uint64_t index = *index_out; + uint64_t gen = index & ECS_GENERATION_MASK; + /* Make sure there's no junk in the id */ + ecs_assert(gen == (index & (0xFFFFFFFFull << 32)), ECS_INVALID_PARAMETER, NULL); - - return (ecs_world_t*)ecs_vector_get( - world->worker_stages, ecs_stage_t, stage_id); -error: - return NULL; + *index_out -= gen; + return gen; } -bool ecs_staging_begin( - ecs_world_t *world) +static +void assign_index( + chunk_t * chunk, + uint64_t * dense_array, + uint64_t index, + int32_t dense) { - ecs_poly_assert(world, ecs_world_t); - - int32_t i, count = ecs_get_stage_count(world); - for (i = 0; i < count; i ++) { - ecs_defer_begin(ecs_get_stage(world, i)); - } - - bool is_readonly = world->is_readonly; - - /* From this point on, the world is "locked" for mutations, and it is only - * allowed to enqueue commands from stages */ - world->is_readonly = true; - - ecs_dbg_3("staging: begin"); - - return is_readonly; + /* Initialize sparse-dense pair. This assigns the dense index to the sparse + * array, and the sparse index to the dense array .*/ + chunk->sparse[OFFSET(index)] = dense; + dense_array[dense] = index; } -void ecs_staging_end( - ecs_world_t *world) +static +uint64_t inc_gen( + uint64_t index) { - ecs_poly_assert(world, ecs_world_t); - ecs_check(world->is_readonly == true, ECS_INVALID_OPERATION, NULL); + /* When an index is deleted, its generation is increased so that we can do + * liveliness checking while recycling ids */ + return ECS_GENERATION_INC(index); +} - /* After this it is safe again to mutate the world directly */ - world->is_readonly = false; +static +uint64_t inc_id( + ecs_sparse_t *sparse) +{ + /* Generate a new id. The last issued id could be stored in an external + * variable, such as is the case with the last issued entity id, which is + * stored on the world. */ + return ++ (sparse->max_id[0]); +} - ecs_dbg_3("staging: end"); +static +uint64_t get_id( + const ecs_sparse_t *sparse) +{ + return sparse->max_id[0]; +} - do_auto_merge(world); -error: - return; +static +void set_id( + ecs_sparse_t *sparse, + uint64_t value) +{ + /* Sometimes the max id needs to be assigned directly, which typically + * happens when the API calls get_or_create for an id that hasn't been + * issued before. */ + sparse->max_id[0] = value; } -void ecs_merge( - ecs_world_t *world) +/* Pair dense id with new sparse id */ +static +uint64_t create_id( + ecs_sparse_t *sparse, + int32_t dense) { - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(ecs_poly_is(world, ecs_world_t) || - ecs_poly_is(world, ecs_stage_t), ECS_INVALID_PARAMETER, NULL); - do_manual_merge(world); -error: - return; + uint64_t index = inc_id(sparse); + grow_dense(sparse); + + chunk_t *chunk = get_or_create_chunk(sparse, CHUNK(index)); + ecs_assert(chunk->sparse[OFFSET(index)] == 0, ECS_INTERNAL_ERROR, NULL); + + uint64_t *dense_array = ecs_vector_first(sparse->dense, uint64_t); + assign_index(chunk, dense_array, index, dense); + + return index; } -void ecs_set_automerge( - ecs_world_t *world, - bool auto_merge) +/* Create new id */ +static +uint64_t new_index( + ecs_sparse_t *sparse) { - /* If a world is provided, set auto_merge globally for the world. This - * doesn't actually do anything (the main stage never merges) but it serves - * as the default for when stages are created. */ - if (ecs_poly_is(world, ecs_world_t)) { - world->stage.auto_merge = auto_merge; + ecs_vector_t *dense = sparse->dense; + int32_t dense_count = ecs_vector_count(dense); + int32_t count = sparse->count ++; - /* Propagate change to all stages */ - int i, stage_count = ecs_get_stage_count(world); - for (i = 0; i < stage_count; i ++) { - ecs_stage_t *stage = (ecs_stage_t*)ecs_get_stage(world, i); - stage->auto_merge = auto_merge; - } + ecs_assert(count <= dense_count, ECS_INTERNAL_ERROR, NULL); - /* If a stage is provided, override the auto_merge value for the individual - * stage. This allows an application to control per-stage which stage should - * be automatically merged and which one shouldn't */ + if (count < dense_count) { + /* If there are unused elements in the dense array, return first */ + uint64_t *dense_array = ecs_vector_first(dense, uint64_t); + return dense_array[count]; } else { - ecs_poly_assert(world, ecs_stage_t); - ecs_stage_t *stage = (ecs_stage_t*)world; - stage->auto_merge = auto_merge; + return create_id(sparse, count); } } -bool ecs_stage_is_readonly( - const ecs_world_t *stage) -{ - const ecs_world_t *world = ecs_get_world(stage); +/* Try obtaining a value from the sparse set, don't care about whether the + * provided index matches the current generation count. */ +static +void* try_sparse_any( + const ecs_sparse_t *sparse, + uint64_t index) +{ + strip_generation(&index); - if (ecs_poly_is(stage, ecs_stage_t)) { - if (((ecs_stage_t*)stage)->asynchronous) { - return false; - } + chunk_t *chunk = get_chunk(sparse, CHUNK(index)); + if (!chunk) { + return NULL; } - if (world->is_readonly) { - if (ecs_poly_is(stage, ecs_world_t)) { - return true; - } - } else { - if (ecs_poly_is(stage, ecs_stage_t)) { - return true; - } + int32_t offset = OFFSET(index); + int32_t dense = chunk->sparse[offset]; + bool in_use = dense && (dense < sparse->count); + if (!in_use) { + return NULL; } - return false; + ecs_assert(dense == chunk->sparse[offset], ECS_INTERNAL_ERROR, NULL); + return DATA(chunk->data, sparse->size, offset); } -ecs_world_t* ecs_async_stage_new( - ecs_world_t *world) +/* Try obtaining a value from the sparse set, make sure it's alive. */ +static +void* try_sparse( + const ecs_sparse_t *sparse, + uint64_t index) { - ecs_stage_t *stage = ecs_os_calloc(sizeof(ecs_stage_t)); - flecs_stage_init(world, stage); + chunk_t *chunk = get_chunk(sparse, CHUNK(index)); + if (!chunk) { + return NULL; + } - stage->id = -1; - stage->auto_merge = false; - stage->asynchronous = true; + int32_t offset = OFFSET(index); + int32_t dense = chunk->sparse[offset]; + bool in_use = dense && (dense < sparse->count); + if (!in_use) { + return NULL; + } - ecs_defer_begin((ecs_world_t*)stage); + uint64_t gen = strip_generation(&index); + uint64_t *dense_array = ecs_vector_first(sparse->dense, uint64_t); + uint64_t cur_gen = dense_array[dense] & ECS_GENERATION_MASK; - return (ecs_world_t*)stage; -} + if (cur_gen != gen) { + return NULL; + } -void ecs_async_stage_free( - ecs_world_t *world) -{ - ecs_poly_assert(world, ecs_stage_t); - ecs_stage_t *stage = (ecs_stage_t*)world; - ecs_check(stage->asynchronous == true, ECS_INVALID_PARAMETER, NULL); - flecs_stage_deinit(stage->world, stage); - ecs_os_free(stage); -error: - return; + ecs_assert(dense == chunk->sparse[offset], ECS_INTERNAL_ERROR, NULL); + return DATA(chunk->data, sparse->size, offset); } -bool ecs_stage_is_async( - ecs_world_t *stage) -{ - if (!stage) { - return false; - } +/* Get value from sparse set when it is guaranteed that the value exists. This + * function is used when values are obtained using a dense index */ +static +void* get_sparse( + const ecs_sparse_t *sparse, + int32_t dense, + uint64_t index) +{ + strip_generation(&index); + chunk_t *chunk = get_chunk(sparse, CHUNK(index)); + int32_t offset = OFFSET(index); - if (!ecs_poly_is(stage, ecs_stage_t)) { - return false; - } - - return ((ecs_stage_t*)stage)->asynchronous; -} + ecs_assert(chunk != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(dense == chunk->sparse[offset], ECS_INTERNAL_ERROR, NULL); + (void)dense; -bool ecs_is_deferred( - const ecs_world_t *world) -{ - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - const ecs_stage_t *stage = flecs_stage_from_readonly_world(world); - return stage->defer != 0; -error: - return false; + return DATA(chunk->data, sparse->size, offset); } - -/** Resize the vector buffer */ +/* Swap dense elements. A swap occurs when an element is removed, or when a + * removed element is recycled. */ static -ecs_vector_t* resize( - ecs_vector_t *vector, - int16_t offset, - int32_t size) +void swap_dense( + ecs_sparse_t * sparse, + chunk_t * chunk_a, + int32_t a, + int32_t b) { - ecs_vector_t *result = ecs_os_realloc(vector, offset + size); - ecs_assert(result != NULL, ECS_OUT_OF_MEMORY, 0); - return result; -} + ecs_assert(a != b, ECS_INTERNAL_ERROR, NULL); + uint64_t *dense_array = ecs_vector_first(sparse->dense, uint64_t); + uint64_t index_a = dense_array[a]; + uint64_t index_b = dense_array[b]; -/* -- Public functions -- */ + chunk_t *chunk_b = get_or_create_chunk(sparse, CHUNK(index_b)); + assign_index(chunk_a, dense_array, index_a, b); + assign_index(chunk_b, dense_array, index_b, a); +} -ecs_vector_t* _ecs_vector_new( - ecs_size_t elem_size, - int16_t offset, - int32_t elem_count) +ecs_sparse_t* _flecs_sparse_new( + ecs_size_t size) { - ecs_assert(elem_size != 0, ECS_INTERNAL_ERROR, NULL); - - ecs_vector_t *result = - ecs_os_malloc(offset + elem_size * elem_count); + ecs_sparse_t *result = ecs_os_calloc(ECS_SIZEOF(ecs_sparse_t)); ecs_assert(result != NULL, ECS_OUT_OF_MEMORY, NULL); + result->size = size; + result->max_id_local = UINT64_MAX; + result->max_id = &result->max_id_local; + + /* Consume first value in dense array as 0 is used in the sparse array to + * indicate that a sparse element hasn't been paired yet. */ + uint64_t *first = ecs_vector_add(&result->dense, uint64_t); + *first = 0; + + result->count = 1; - result->count = 0; - result->size = elem_count; -#ifndef NDEBUG - result->elem_size = elem_size; -#endif return result; } -ecs_vector_t* _ecs_vector_from_array( - ecs_size_t elem_size, - int16_t offset, - int32_t elem_count, - void *array) +void flecs_sparse_set_id_source( + ecs_sparse_t * sparse, + uint64_t * id_source) { - ecs_assert(elem_size != 0, ECS_INTERNAL_ERROR, NULL); - - ecs_vector_t *result = - ecs_os_malloc(offset + elem_size * elem_count); - ecs_assert(result != NULL, ECS_OUT_OF_MEMORY, NULL); + ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); + sparse->max_id = id_source; +} - ecs_os_memcpy(ECS_OFFSET(result, offset), array, elem_size * elem_count); +void flecs_sparse_clear( + ecs_sparse_t *sparse) +{ + ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); - result->count = elem_count; - result->size = elem_count; -#ifndef NDEBUG - result->elem_size = elem_size; -#endif - return result; + ecs_vector_each(sparse->chunks, chunk_t, chunk, { + chunk_free(chunk); + }); + + ecs_vector_free(sparse->chunks); + ecs_vector_set_count(&sparse->dense, uint64_t, 1); + + sparse->chunks = NULL; + sparse->count = 1; + sparse->max_id_local = 0; } -void ecs_vector_free( - ecs_vector_t *vector) +void flecs_sparse_free( + ecs_sparse_t *sparse) { - ecs_os_free(vector); + if (sparse) { + flecs_sparse_clear(sparse); + ecs_vector_free(sparse->dense); + ecs_os_free(sparse); + } } -void ecs_vector_clear( - ecs_vector_t *vector) +uint64_t flecs_sparse_new_id( + ecs_sparse_t *sparse) { - if (vector) { - vector->count = 0; + ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); + return new_index(sparse); +} + +const uint64_t* flecs_sparse_new_ids( + ecs_sparse_t *sparse, + int32_t new_count) +{ + ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); + int32_t dense_count = ecs_vector_count(sparse->dense); + int32_t count = sparse->count; + int32_t remaining = dense_count - count; + int32_t i, to_create = new_count - remaining; + + if (to_create > 0) { + flecs_sparse_set_size(sparse, dense_count + to_create); + uint64_t *dense_array = ecs_vector_first(sparse->dense, uint64_t); + + for (i = 0; i < to_create; i ++) { + uint64_t index = create_id(sparse, count + i); + dense_array[dense_count + i] = index; + } } + + sparse->count += new_count; + + return ecs_vector_get(sparse->dense, uint64_t, count); } -void _ecs_vector_zero( - ecs_vector_t *vector, - ecs_size_t elem_size, - int16_t offset) +void* _flecs_sparse_add( + ecs_sparse_t *sparse, + ecs_size_t size) { - void *array = ECS_OFFSET(vector, offset); - ecs_os_memset(array, 0, elem_size * vector->count); + ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(!size || size == sparse->size, ECS_INVALID_PARAMETER, NULL); + uint64_t index = new_index(sparse); + chunk_t *chunk = get_chunk(sparse, CHUNK(index)); + ecs_assert(chunk != NULL, ECS_INTERNAL_ERROR, NULL); + return DATA(chunk->data, size, OFFSET(index)); } -void ecs_vector_assert_size( - ecs_vector_t *vector, - ecs_size_t elem_size) +uint64_t flecs_sparse_last_id( + const ecs_sparse_t *sparse) { - (void)elem_size; - - if (vector) { - ecs_dbg_assert(vector->elem_size == elem_size, ECS_INTERNAL_ERROR, NULL); - } + ecs_assert(sparse != NULL, ECS_INTERNAL_ERROR, NULL); + uint64_t *dense_array = ecs_vector_first(sparse->dense, uint64_t); + return dense_array[sparse->count - 1]; } -void* _ecs_vector_addn( - ecs_vector_t **array_inout, - ecs_size_t elem_size, - int16_t offset, - int32_t elem_count) +void* _flecs_sparse_ensure( + ecs_sparse_t *sparse, + ecs_size_t size, + uint64_t index) { - ecs_assert(array_inout != NULL, ECS_INTERNAL_ERROR, NULL); - - if (elem_count == 1) { - return _ecs_vector_add(array_inout, elem_size, offset); - } - - ecs_vector_t *vector = *array_inout; - if (!vector) { - vector = _ecs_vector_new(elem_size, offset, 1); - *array_inout = vector; - } + ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(!size || size == sparse->size, ECS_INVALID_PARAMETER, NULL); + ecs_assert(ecs_vector_count(sparse->dense) > 0, ECS_INTERNAL_ERROR, NULL); + (void)size; - ecs_dbg_assert(vector->elem_size == elem_size, ECS_INTERNAL_ERROR, NULL); + uint64_t gen = strip_generation(&index); + chunk_t *chunk = get_or_create_chunk(sparse, CHUNK(index)); + int32_t offset = OFFSET(index); + int32_t dense = chunk->sparse[offset]; - int32_t max_count = vector->size; - int32_t old_count = vector->count; - int32_t new_count = old_count + elem_count; + if (dense) { + /* Check if element is alive. If element is not alive, update indices so + * that the first unused dense element points to the sparse element. */ + int32_t count = sparse->count; + if (dense == count) { + /* If dense is the next unused element in the array, simply increase + * the count to make it part of the alive set. */ + sparse->count ++; + } else if (dense > count) { + /* If dense is not alive, swap it with the first unused element. */ + swap_dense(sparse, chunk, dense, count); - if ((new_count - 1) >= max_count) { - if (!max_count) { - max_count = elem_count; + /* First unused element is now last used element */ + sparse->count ++; } else { - while (max_count < new_count) { - max_count *= 2; - } + /* Dense is already alive, nothing to be done */ } - vector = resize(vector, offset, max_count * elem_size); - vector->size = max_count; - *array_inout = vector; - } - - vector->count = new_count; - - return ECS_OFFSET(vector, offset + elem_size * old_count); -} + /* Ensure provided generation matches current. Only allow mismatching + * generations if the provided generation count is 0. This allows for + * using the ensure function in combination with ids that have their + * generation stripped. */ + ecs_vector_t *dense_vector = sparse->dense; + uint64_t *dense_array = ecs_vector_first(dense_vector, uint64_t); + ecs_assert(!gen || dense_array[dense] == (index | gen), ECS_INTERNAL_ERROR, NULL); + (void)dense_vector; + (void)dense_array; + } else { + /* Element is not paired yet. Must add a new element to dense array */ + grow_dense(sparse); -void* _ecs_vector_add( - ecs_vector_t **array_inout, - ecs_size_t elem_size, - int16_t offset) -{ - ecs_assert(array_inout != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_vector_t *vector = *array_inout; - int32_t count, size; + ecs_vector_t *dense_vector = sparse->dense; + uint64_t *dense_array = ecs_vector_first(dense_vector, uint64_t); + int32_t dense_count = ecs_vector_count(dense_vector) - 1; + int32_t count = sparse->count ++; - if (vector) { - ecs_dbg_assert(vector->elem_size == elem_size, ECS_INTERNAL_ERROR, NULL); - count = vector->count; - size = vector->size; + /* If index is larger than max id, update max id */ + if (index >= get_id(sparse)) { + set_id(sparse, index + 1); + } - if (count >= size) { - size *= 2; - if (!size) { - size = 2; - } - vector = resize(vector, offset, size * elem_size); - *array_inout = vector; - vector->size = size; + if (count < dense_count) { + /* If there are unused elements in the list, move the first unused + * element to the end of the list */ + uint64_t unused = dense_array[count]; + chunk_t *unused_chunk = get_or_create_chunk(sparse, CHUNK(unused)); + assign_index(unused_chunk, dense_array, unused, dense_count); } - vector->count = count + 1; - return ECS_OFFSET(vector, offset + elem_size * count); + assign_index(chunk, dense_array, index, count); + dense_array[count] |= gen; } - vector = _ecs_vector_new(elem_size, offset, 2); - *array_inout = vector; - vector->count = 1; - vector->size = 2; - return ECS_OFFSET(vector, offset); + return DATA(chunk->data, sparse->size, offset); } -int32_t _ecs_vector_move_index( - ecs_vector_t **dst, - ecs_vector_t *src, +void* _flecs_sparse_set( + ecs_sparse_t * sparse, ecs_size_t elem_size, - int16_t offset, - int32_t index) -{ - if (dst && *dst) { - ecs_dbg_assert((*dst)->elem_size == elem_size, ECS_INTERNAL_ERROR, NULL); - } - ecs_dbg_assert(src->elem_size == elem_size, ECS_INTERNAL_ERROR, NULL); - - void *dst_elem = _ecs_vector_add(dst, elem_size, offset); - void *src_elem = _ecs_vector_get(src, elem_size, offset, index); - - ecs_os_memcpy(dst_elem, src_elem, elem_size); - return _ecs_vector_remove(src, elem_size, offset, index); -} - -void ecs_vector_remove_last( - ecs_vector_t *vector) + uint64_t index, + void* value) { - if (vector && vector->count) vector->count --; + void *ptr = _flecs_sparse_ensure(sparse, elem_size, index); + ecs_os_memcpy(ptr, value, elem_size); + return ptr; } -bool _ecs_vector_pop( - ecs_vector_t *vector, - ecs_size_t elem_size, - int16_t offset, - void *value) +void* _flecs_sparse_remove_get( + ecs_sparse_t *sparse, + ecs_size_t size, + uint64_t index) { - if (!vector) { - return false; - } + ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(!size || size == sparse->size, ECS_INVALID_PARAMETER, NULL); + (void)size; - ecs_dbg_assert(vector->elem_size == elem_size, ECS_INTERNAL_ERROR, NULL); + chunk_t *chunk = get_or_create_chunk(sparse, CHUNK(index)); + uint64_t gen = strip_generation(&index); + int32_t offset = OFFSET(index); + int32_t dense = chunk->sparse[offset]; - int32_t count = vector->count; - if (!count) { - return false; - } + if (dense) { + uint64_t *dense_array = ecs_vector_first(sparse->dense, uint64_t); + uint64_t cur_gen = dense_array[dense] & ECS_GENERATION_MASK; + if (gen != cur_gen) { + /* Generation doesn't match which means that the provided entity is + * already not alive. */ + return NULL; + } - void *elem = ECS_OFFSET(vector, offset + (count - 1) * elem_size); + /* Increase generation */ + dense_array[dense] = index | inc_gen(cur_gen); + + int32_t count = sparse->count; + if (dense == (count - 1)) { + /* If dense is the last used element, simply decrease count */ + sparse->count --; + } else if (dense < count) { + /* If element is alive, move it to unused elements */ + swap_dense(sparse, chunk, dense, count - 1); + sparse->count --; + } else { + /* Element is not alive, nothing to be done */ + return NULL; + } - if (value) { - ecs_os_memcpy(value, elem, elem_size); + /* Reset memory to zero on remove */ + return DATA(chunk->data, sparse->size, offset); + } else { + /* Element is not paired and thus not alive, nothing to be done */ + return NULL; } +} - ecs_vector_remove_last(vector); - - return true; +void flecs_sparse_remove( + ecs_sparse_t *sparse, + uint64_t index) +{ + void *ptr = _flecs_sparse_remove_get(sparse, 0, index); + if (ptr) { + ecs_os_memset(ptr, 0, sparse->size); + } } -int32_t _ecs_vector_remove( - ecs_vector_t *vector, - ecs_size_t elem_size, - int16_t offset, - int32_t index) +void flecs_sparse_set_generation( + ecs_sparse_t *sparse, + uint64_t index) { - ecs_dbg_assert(vector->elem_size == elem_size, ECS_INTERNAL_ERROR, NULL); + ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); + chunk_t *chunk = get_or_create_chunk(sparse, CHUNK(index)); - int32_t count = vector->count; - void *buffer = ECS_OFFSET(vector, offset); - void *elem = ECS_OFFSET(buffer, index * elem_size); - - ecs_assert(index < count, ECS_INVALID_PARAMETER, NULL); + uint64_t index_w_gen = index; + strip_generation(&index); + int32_t offset = OFFSET(index); + int32_t dense = chunk->sparse[offset]; - count --; - if (index != count) { - void *last_elem = ECS_OFFSET(buffer, elem_size * count); - ecs_os_memcpy(elem, last_elem, elem_size); + if (dense) { + /* Increase generation */ + uint64_t *dense_array = ecs_vector_first(sparse->dense, uint64_t); + dense_array[dense] = index_w_gen; + } else { + /* Element is not paired and thus not alive, nothing to be done */ } +} - vector->count = count; +bool flecs_sparse_exists( + const ecs_sparse_t *sparse, + uint64_t index) +{ + ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); + chunk_t *chunk = get_chunk(sparse, CHUNK(index)); + if (!chunk) { + return false; + } + + strip_generation(&index); + int32_t offset = OFFSET(index); + int32_t dense = chunk->sparse[offset]; - return count; + return dense != 0; } -void _ecs_vector_reclaim( - ecs_vector_t **array_inout, - ecs_size_t elem_size, - int16_t offset) +void* _flecs_sparse_get_dense( + const ecs_sparse_t *sparse, + ecs_size_t size, + int32_t dense_index) { - ecs_vector_t *vector = *array_inout; + ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(!size || size == sparse->size, ECS_INVALID_PARAMETER, NULL); + ecs_assert(dense_index < sparse->count, ECS_INVALID_PARAMETER, NULL); + (void)size; - ecs_dbg_assert(vector->elem_size == elem_size, ECS_INTERNAL_ERROR, NULL); - - int32_t size = vector->size; - int32_t count = vector->count; + dense_index ++; - if (count < size) { - size = count; - vector = resize(vector, offset, size * elem_size); - vector->size = size; - *array_inout = vector; - } + uint64_t *dense_array = ecs_vector_first(sparse->dense, uint64_t); + return get_sparse(sparse, dense_index, dense_array[dense_index]); } -int32_t ecs_vector_count( - const ecs_vector_t *vector) +bool flecs_sparse_is_alive( + const ecs_sparse_t *sparse, + uint64_t index) { - if (!vector) { - return 0; - } - return vector->count; + return try_sparse(sparse, index) != NULL; } -int32_t ecs_vector_size( - const ecs_vector_t *vector) +uint64_t flecs_sparse_get_alive( + const ecs_sparse_t *sparse, + uint64_t index) { - if (!vector) { + chunk_t *chunk = get_chunk(sparse, CHUNK(index)); + if (!chunk) { return 0; } - return vector->size; -} -int32_t _ecs_vector_set_size( - ecs_vector_t **array_inout, - ecs_size_t elem_size, - int16_t offset, - int32_t elem_count) -{ - ecs_vector_t *vector = *array_inout; + int32_t offset = OFFSET(index); + int32_t dense = chunk->sparse[offset]; + uint64_t *dense_array = ecs_vector_first(sparse->dense, uint64_t); - if (!vector) { - *array_inout = _ecs_vector_new(elem_size, offset, elem_count); - return elem_count; - } else { - ecs_dbg_assert(vector->elem_size == elem_size, ECS_INTERNAL_ERROR, NULL); + /* If dense is 0 (tombstone) this will return 0 */ + return dense_array[dense]; +} - int32_t result = vector->size; - - if (elem_count < vector->count) { - elem_count = vector->count; - } - - if (result < elem_count) { - elem_count = flecs_next_pow_of_2(elem_count); - vector = resize(vector, offset, elem_count * elem_size); - vector->size = elem_count; - *array_inout = vector; - result = elem_count; - } - - return result; - } +void* _flecs_sparse_get( + const ecs_sparse_t *sparse, + ecs_size_t size, + uint64_t index) +{ + ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(!size || size == sparse->size, ECS_INVALID_PARAMETER, NULL); + (void)size; + return try_sparse(sparse, index); } -int32_t _ecs_vector_grow( - ecs_vector_t **array_inout, - ecs_size_t elem_size, - int16_t offset, - int32_t elem_count) +void* _flecs_sparse_get_any( + ecs_sparse_t *sparse, + ecs_size_t size, + uint64_t index) { - int32_t current = ecs_vector_count(*array_inout); - return _ecs_vector_set_size(array_inout, elem_size, offset, current + elem_count); + ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(!size || size == sparse->size, ECS_INVALID_PARAMETER, NULL); + (void)size; + return try_sparse_any(sparse, index); } -int32_t _ecs_vector_set_count( - ecs_vector_t **array_inout, - ecs_size_t elem_size, - int16_t offset, - int32_t elem_count) +int32_t flecs_sparse_count( + const ecs_sparse_t *sparse) { - if (!*array_inout) { - *array_inout = _ecs_vector_new(elem_size, offset, elem_count); + if (!sparse) { + return 0; } - ecs_dbg_assert((*array_inout)->elem_size == elem_size, ECS_INTERNAL_ERROR, NULL); - - (*array_inout)->count = elem_count; - ecs_size_t size = _ecs_vector_set_size(array_inout, elem_size, offset, elem_count); - return size; + return sparse->count - 1; } -void* _ecs_vector_first( - const ecs_vector_t *vector, - ecs_size_t elem_size, - int16_t offset) +int32_t flecs_sparse_size( + const ecs_sparse_t *sparse) { - (void)elem_size; - - ecs_dbg_assert(!vector || vector->elem_size == elem_size, ECS_INTERNAL_ERROR, NULL); - if (vector && vector->size) { - return ECS_OFFSET(vector, offset); - } else { - return NULL; + if (!sparse) { + return 0; } + + return ecs_vector_count(sparse->dense) - 1; } -void* _ecs_vector_get( - const ecs_vector_t *vector, - ecs_size_t elem_size, - int16_t offset, - int32_t index) -{ - ecs_assert(vector != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_dbg_assert(vector->elem_size == elem_size, ECS_INTERNAL_ERROR, NULL); - ecs_assert(index >= 0, ECS_INTERNAL_ERROR, NULL); - ecs_assert(index < vector->count, ECS_INTERNAL_ERROR, NULL); - - return ECS_OFFSET(vector, offset + elem_size * index); -} - -void* _ecs_vector_last( - const ecs_vector_t *vector, - ecs_size_t elem_size, - int16_t offset) +const uint64_t* flecs_sparse_ids( + const ecs_sparse_t *sparse) { - if (vector) { - ecs_dbg_assert(vector->elem_size == elem_size, ECS_INTERNAL_ERROR, NULL); - int32_t count = vector->count; - if (!count) { - return NULL; - } else { - return ECS_OFFSET(vector, offset + elem_size * (count - 1)); - } - } else { - return NULL; - } + ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); + return &(ecs_vector_first(sparse->dense, uint64_t)[1]); } -int32_t _ecs_vector_set_min_size( - ecs_vector_t **vector_inout, - ecs_size_t elem_size, - int16_t offset, +void flecs_sparse_set_size( + ecs_sparse_t *sparse, int32_t elem_count) { - if (!*vector_inout || (*vector_inout)->size < elem_count) { - return _ecs_vector_set_size(vector_inout, elem_size, offset, elem_count); - } else { - return (*vector_inout)->size; - } + ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_vector_set_size(&sparse->dense, uint64_t, elem_count); } -int32_t _ecs_vector_set_min_count( - ecs_vector_t **vector_inout, - ecs_size_t elem_size, - int16_t offset, - int32_t elem_count) +static +void sparse_copy( + ecs_sparse_t * dst, + const ecs_sparse_t * src) { - _ecs_vector_set_min_size(vector_inout, elem_size, offset, elem_count); + flecs_sparse_set_size(dst, flecs_sparse_size(src)); + const uint64_t *indices = flecs_sparse_ids(src); + + ecs_size_t size = src->size; + int32_t i, count = src->count; - ecs_vector_t *v = *vector_inout; - if (v && v->count < elem_count) { - v->count = elem_count; + for (i = 0; i < count - 1; i ++) { + uint64_t index = indices[i]; + void *src_ptr = _flecs_sparse_get(src, size, index); + void *dst_ptr = _flecs_sparse_ensure(dst, size, index); + flecs_sparse_set_generation(dst, index); + ecs_os_memcpy(dst_ptr, src_ptr, size); } - return v->count; + set_id(dst, get_id(src)); + + ecs_assert(src->count == dst->count, ECS_INTERNAL_ERROR, NULL); } -void _ecs_vector_sort( - ecs_vector_t *vector, - ecs_size_t elem_size, - int16_t offset, - ecs_comparator_t compare_action) +ecs_sparse_t* flecs_sparse_copy( + const ecs_sparse_t *src) { - if (!vector) { - return; + if (!src) { + return NULL; } - ecs_dbg_assert(vector->elem_size == elem_size, ECS_INTERNAL_ERROR, NULL); + ecs_sparse_t *dst = _flecs_sparse_new(src->size); + sparse_copy(dst, src); - int32_t count = vector->count; - void *buffer = ECS_OFFSET(vector, offset); + return dst; +} - if (count > 1) { - qsort(buffer, (size_t)count, (size_t)elem_size, compare_action); +void flecs_sparse_restore( + ecs_sparse_t * dst, + const ecs_sparse_t * src) +{ + ecs_assert(dst != NULL, ECS_INVALID_PARAMETER, NULL); + dst->count = 1; + if (src) { + sparse_copy(dst, src); } } -void _ecs_vector_memory( - const ecs_vector_t *vector, - ecs_size_t elem_size, - int16_t offset, +void flecs_sparse_memory( + ecs_sparse_t *sparse, int32_t *allocd, int32_t *used) { - if (!vector) { - return; - } - - ecs_dbg_assert(vector->elem_size == elem_size, ECS_INTERNAL_ERROR, NULL); - - if (allocd) { - *allocd += vector->size * elem_size + offset; - } - if (used) { - *used += vector->count * elem_size; - } + (void)sparse; + (void)allocd; + (void)used; } -ecs_vector_t* _ecs_vector_copy( - const ecs_vector_t *src, - ecs_size_t elem_size, - int16_t offset) +ecs_sparse_t* _ecs_sparse_new( + ecs_size_t elem_size) { - if (!src) { - return NULL; - } - - ecs_vector_t *dst = _ecs_vector_new(elem_size, offset, src->size); - ecs_os_memcpy(dst, src, offset + elem_size * src->count); - return dst; + return _flecs_sparse_new(elem_size); } +void* _ecs_sparse_add( + ecs_sparse_t *sparse, + ecs_size_t elem_size) +{ + return _flecs_sparse_add(sparse, elem_size); +} -/** The number of elements in a single chunk */ -#define CHUNK_COUNT (4096) +uint64_t ecs_sparse_last_id( + const ecs_sparse_t *sparse) +{ + return flecs_sparse_last_id(sparse); +} -/** Compute the chunk index from an id by stripping the first 12 bits */ -#define CHUNK(index) ((int32_t)((uint32_t)index >> 12)) +int32_t ecs_sparse_count( + const ecs_sparse_t *sparse) +{ + return flecs_sparse_count(sparse); +} -/** This computes the offset of an index inside a chunk */ -#define OFFSET(index) ((int32_t)index & 0xFFF) +void* _ecs_sparse_get_dense( + const ecs_sparse_t *sparse, + ecs_size_t elem_size, + int32_t index) +{ + return _flecs_sparse_get_dense(sparse, elem_size, index); +} -/* Utility to get a pointer to the payload */ -#define DATA(array, size, offset) (ECS_OFFSET(array, size * offset)) +void* _ecs_sparse_get( + const ecs_sparse_t *sparse, + ecs_size_t elem_size, + uint64_t id) +{ + return _flecs_sparse_get(sparse, elem_size, id); +} -typedef struct chunk_t { - int32_t *sparse; /* Sparse array with indices to dense array */ - void *data; /* Store data in sparse array to reduce - * indirection and provide stable pointers. */ -} chunk_t; +ecs_sparse_iter_t _flecs_sparse_iter( + ecs_sparse_t *sparse, + ecs_size_t elem_size) +{ + ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(elem_size == sparse->size, ECS_INVALID_PARAMETER, NULL); + ecs_sparse_iter_t result; + result.sparse = sparse; + result.ids = flecs_sparse_ids(sparse); + result.size = elem_size; + result.i = 0; + result.count = sparse->count - 1; + return result; +} -struct ecs_sparse_t { - ecs_vector_t *dense; /* Dense array with indices to sparse array. The - * dense array stores both alive and not alive - * sparse indices. The 'count' member keeps - * track of which indices are alive. */ +/** + * stm32tpl -- STM32 C++ Template Peripheral Library + * Visit https://github.com/antongus/stm32tpl for new versions + * + * Copyright (c) 2011-2020 Anton B. Gusev aka AHTOXA + */ - ecs_vector_t *chunks; /* Chunks with sparse arrays & data */ - ecs_size_t size; /* Element size */ - int32_t count; /* Number of alive entries */ - uint64_t max_id_local; /* Local max index (if no global is set) */ - uint64_t *max_id; /* Maximum issued sparse index */ +#define MAX_PRECISION (10) +static const double rounders[MAX_PRECISION + 1] = +{ + 0.5, // 0 + 0.05, // 1 + 0.005, // 2 + 0.0005, // 3 + 0.00005, // 4 + 0.000005, // 5 + 0.0000005, // 6 + 0.00000005, // 7 + 0.000000005, // 8 + 0.0000000005, // 9 + 0.00000000005 // 10 }; static -chunk_t* chunk_new( - ecs_sparse_t *sparse, - int32_t chunk_index) +int ecs_strbuf_ftoa( + ecs_strbuf_t *out, + double f, + int precision) { - int32_t count = ecs_vector_count(sparse->chunks); - chunk_t *chunks; + char buf[64]; + char * ptr = buf; + char * p1; + char c; + int64_t intPart; - if (count <= chunk_index) { - ecs_vector_set_count(&sparse->chunks, chunk_t, chunk_index + 1); - chunks = ecs_vector_first(sparse->chunks, chunk_t); - ecs_os_memset(&chunks[count], 0, (1 + chunk_index - count) * ECS_SIZEOF(chunk_t)); - } else { - chunks = ecs_vector_first(sparse->chunks, chunk_t); + if (precision > MAX_PRECISION) { + precision = MAX_PRECISION; } - ecs_assert(chunks != NULL, ECS_INTERNAL_ERROR, NULL); + if (f < 0) { + f = -f; + *ptr++ = '-'; + } - chunk_t *result = &chunks[chunk_index]; - ecs_assert(result->sparse == NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(result->data == NULL, ECS_INTERNAL_ERROR, NULL); + if (precision < 0) { + if (f < 1.0) precision = 6; + else if (f < 10.0) precision = 5; + else if (f < 100.0) precision = 4; + else if (f < 1000.0) precision = 3; + else if (f < 10000.0) precision = 2; + else if (f < 100000.0) precision = 1; + else precision = 0; + } - /* Initialize sparse array with zero's, as zero is used to indicate that the - * sparse element has not been paired with a dense element. Use zero - * as this means we can take advantage of calloc having a possibly better - * performance than malloc + memset. */ - result->sparse = ecs_os_calloc(ECS_SIZEOF(int32_t) * CHUNK_COUNT); + if (precision) { + f += rounders[precision]; + } - /* Initialize the data array with zero's to guarantee that data is - * always initialized. When an entry is removed, data is reset back to - * zero. Initialize now, as this can take advantage of calloc. */ - result->data = ecs_os_calloc(sparse->size * CHUNK_COUNT); + intPart = (int64_t)f; + f -= (double)intPart; - ecs_assert(result->sparse != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(result->data != NULL, ECS_INTERNAL_ERROR, NULL); + if (!intPart) { + *ptr++ = '0'; + } else { + char *p = ptr; + while (intPart) { + *p++ = (char)('0' + intPart % 10); + intPart /= 10; + } - return result; + p1 = p; + + while (p > ptr) { + c = *--p; + *p = *ptr; + *ptr++ = c; + } + ptr = p1; + } + + if (precision) { + *ptr++ = '.'; + while (precision--) { + f *= 10.0; + c = (char)f; + *ptr++ = (char)('0' + c); + f -= c; + } + } + *ptr = 0; + + return ecs_strbuf_appendstrn(out, buf, (int32_t)(ptr - buf)); } + +/* Add an extra element to the buffer */ static -void chunk_free( - chunk_t *chunk) +void ecs_strbuf_grow( + ecs_strbuf_t *b) { - ecs_os_free(chunk->sparse); - ecs_os_free(chunk->data); + /* Allocate new element */ + ecs_strbuf_element_embedded *e = ecs_os_malloc_t(ecs_strbuf_element_embedded); + b->size += b->current->pos; + b->current->next = (ecs_strbuf_element*)e; + b->current = (ecs_strbuf_element*)e; + b->elementCount ++; + e->super.buffer_embedded = true; + e->super.buf = e->buf; + e->super.pos = 0; + e->super.next = NULL; } +/* Add an extra dynamic element */ static -chunk_t* get_chunk( - const ecs_sparse_t *sparse, - int32_t chunk_index) +void ecs_strbuf_grow_str( + ecs_strbuf_t *b, + char *str, + char *alloc_str, + int32_t size) { - if (!sparse->chunks) { - return NULL; - } - if (chunk_index >= ecs_vector_count(sparse->chunks)) { - return NULL; - } - - /* If chunk_index is below zero, application used an invalid entity id */ - ecs_assert(chunk_index >= 0, ECS_INVALID_PARAMETER, NULL); - chunk_t *result = ecs_vector_get(sparse->chunks, chunk_t, chunk_index); - if (result && !result->sparse) { - return NULL; - } - - return result; + /* Allocate new element */ + ecs_strbuf_element_str *e = ecs_os_malloc_t(ecs_strbuf_element_str); + b->size += b->current->pos; + b->current->next = (ecs_strbuf_element*)e; + b->current = (ecs_strbuf_element*)e; + b->elementCount ++; + e->super.buffer_embedded = false; + e->super.pos = size ? size : (int32_t)ecs_os_strlen(str); + e->super.next = NULL; + e->super.buf = str; + e->alloc_str = alloc_str; } static -chunk_t* get_or_create_chunk( - ecs_sparse_t *sparse, - int32_t chunk_index) +char* ecs_strbuf_ptr( + ecs_strbuf_t *b) { - chunk_t *chunk = get_chunk(sparse, chunk_index); - if (chunk) { - return chunk; + if (b->buf) { + return &b->buf[b->current->pos]; + } else { + return &b->current->buf[b->current->pos]; } - - return chunk_new(sparse, chunk_index); } +/* Compute the amount of space left in the current element */ static -void grow_dense( - ecs_sparse_t *sparse) +int32_t ecs_strbuf_memLeftInCurrentElement( + ecs_strbuf_t *b) { - ecs_vector_add(&sparse->dense, uint64_t); + if (b->current->buffer_embedded) { + return ECS_STRBUF_ELEMENT_SIZE - b->current->pos; + } else { + return 0; + } } +/* Compute the amount of space left */ static -uint64_t strip_generation( - uint64_t *index_out) +int32_t ecs_strbuf_memLeft( + ecs_strbuf_t *b) { - uint64_t index = *index_out; - uint64_t gen = index & ECS_GENERATION_MASK; - /* Make sure there's no junk in the id */ - ecs_assert(gen == (index & (0xFFFFFFFFull << 32)), - ECS_INVALID_PARAMETER, NULL); - *index_out -= gen; - return gen; + if (b->max) { + return b->max - b->size - b->current->pos; + } else { + return INT_MAX; + } } static -void assign_index( - chunk_t * chunk, - uint64_t * dense_array, - uint64_t index, - int32_t dense) +void ecs_strbuf_init( + ecs_strbuf_t *b) { - /* Initialize sparse-dense pair. This assigns the dense index to the sparse - * array, and the sparse index to the dense array .*/ - chunk->sparse[OFFSET(index)] = dense; - dense_array[dense] = index; + /* Initialize buffer structure only once */ + if (!b->elementCount) { + b->size = 0; + b->firstElement.super.next = NULL; + b->firstElement.super.pos = 0; + b->firstElement.super.buffer_embedded = true; + b->firstElement.super.buf = b->firstElement.buf; + b->elementCount ++; + b->current = (ecs_strbuf_element*)&b->firstElement; + } } +/* Append a format string to a buffer */ static -uint64_t inc_gen( - uint64_t index) +bool vappend( + ecs_strbuf_t *b, + const char* str, + va_list args) { - /* When an index is deleted, its generation is increased so that we can do - * liveliness checking while recycling ids */ - return ECS_GENERATION_INC(index); -} + bool result = true; + va_list arg_cpy; -static -uint64_t inc_id( - ecs_sparse_t *sparse) -{ - /* Generate a new id. The last issued id could be stored in an external - * variable, such as is the case with the last issued entity id, which is - * stored on the world. */ - return ++ (sparse->max_id[0]); -} + if (!str) { + return result; + } -static -uint64_t get_id( - const ecs_sparse_t *sparse) -{ - return sparse->max_id[0]; -} + ecs_strbuf_init(b); -static -void set_id( - ecs_sparse_t *sparse, - uint64_t value) -{ - /* Sometimes the max id needs to be assigned directly, which typically - * happens when the API calls get_or_create for an id that hasn't been - * issued before. */ - sparse->max_id[0] = value; -} + int32_t memLeftInElement = ecs_strbuf_memLeftInCurrentElement(b); + int32_t memLeft = ecs_strbuf_memLeft(b); -/* Pair dense id with new sparse id */ -static -uint64_t create_id( - ecs_sparse_t *sparse, - int32_t dense) -{ - uint64_t index = inc_id(sparse); - grow_dense(sparse); + if (!memLeft) { + return false; + } - chunk_t *chunk = get_or_create_chunk(sparse, CHUNK(index)); - ecs_assert(chunk->sparse[OFFSET(index)] == 0, ECS_INTERNAL_ERROR, NULL); - - uint64_t *dense_array = ecs_vector_first(sparse->dense, uint64_t); - assign_index(chunk, dense_array, index, dense); - - return index; -} + /* Compute the memory required to add the string to the buffer. If user + * provided buffer, use space left in buffer, otherwise use space left in + * current element. */ + int32_t max_copy = b->buf ? memLeft : memLeftInElement; + int32_t memRequired; -/* Create new id */ -static -uint64_t new_index( - ecs_sparse_t *sparse) -{ - ecs_vector_t *dense = sparse->dense; - int32_t dense_count = ecs_vector_count(dense); - int32_t count = sparse->count ++; + va_copy(arg_cpy, args); + memRequired = vsnprintf( + ecs_strbuf_ptr(b), (size_t)(max_copy + 1), str, args); - ecs_assert(count <= dense_count, ECS_INTERNAL_ERROR, NULL); + ecs_assert(memRequired != -1, ECS_INTERNAL_ERROR, NULL); - if (count < dense_count) { - /* If there are unused elements in the dense array, return first */ - uint64_t *dense_array = ecs_vector_first(dense, uint64_t); - return dense_array[count]; - } else { - return create_id(sparse, count); - } -} + if (memRequired <= memLeftInElement) { + /* Element was large enough to fit string */ + b->current->pos += memRequired; + } else if ((memRequired - memLeftInElement) < memLeft) { + /* If string is a format string, a new buffer of size memRequired is + * needed to re-evaluate the format string and only use the part that + * wasn't already copied to the previous element */ + if (memRequired <= ECS_STRBUF_ELEMENT_SIZE) { + /* Resulting string fits in standard-size buffer. Note that the + * entire string needs to fit, not just the remainder, as the + * format string cannot be partially evaluated */ + ecs_strbuf_grow(b); -/* Try obtaining a value from the sparse set, don't care about whether the - * provided index matches the current generation count. */ -static -void* try_sparse_any( - const ecs_sparse_t *sparse, - uint64_t index) -{ - strip_generation(&index); + /* Copy entire string to new buffer */ + ecs_os_vsprintf(ecs_strbuf_ptr(b), str, arg_cpy); - chunk_t *chunk = get_chunk(sparse, CHUNK(index)); - if (!chunk) { - return NULL; - } + /* Ignore the part of the string that was copied into the + * previous buffer. The string copied into the new buffer could + * be memmoved so that only the remainder is left, but that is + * most likely more expensive than just keeping the entire + * string. */ - int32_t offset = OFFSET(index); - int32_t dense = chunk->sparse[offset]; - bool in_use = dense && (dense < sparse->count); - if (!in_use) { - return NULL; + /* Update position in buffer */ + b->current->pos += memRequired; + } else { + /* Resulting string does not fit in standard-size buffer. + * Allocate a new buffer that can hold the entire string. */ + char *dst = ecs_os_malloc(memRequired + 1); + ecs_os_vsprintf(dst, str, arg_cpy); + ecs_strbuf_grow_str(b, dst, dst, memRequired); + } } - ecs_assert(dense == chunk->sparse[offset], ECS_INTERNAL_ERROR, NULL); - return DATA(chunk->data, sparse->size, offset); + va_end(arg_cpy); + + return ecs_strbuf_memLeft(b) > 0; } -/* Try obtaining a value from the sparse set, make sure it's alive. */ static -void* try_sparse( - const ecs_sparse_t *sparse, - uint64_t index) +bool appendstr( + ecs_strbuf_t *b, + const char* str, + int n) { - chunk_t *chunk = get_chunk(sparse, CHUNK(index)); - if (!chunk) { - return NULL; + ecs_strbuf_init(b); + + int32_t memLeftInElement = ecs_strbuf_memLeftInCurrentElement(b); + int32_t memLeft = ecs_strbuf_memLeft(b); + if (memLeft <= 0) { + return false; } - int32_t offset = OFFSET(index); - int32_t dense = chunk->sparse[offset]; - bool in_use = dense && (dense < sparse->count); - if (!in_use) { - return NULL; + /* Never write more than what the buffer can store */ + if (n > memLeft) { + n = memLeft; } - uint64_t gen = strip_generation(&index); - uint64_t *dense_array = ecs_vector_first(sparse->dense, uint64_t); - uint64_t cur_gen = dense_array[dense] & ECS_GENERATION_MASK; + if (n <= memLeftInElement) { + /* Element was large enough to fit string */ + ecs_os_strncpy(ecs_strbuf_ptr(b), str, n); + b->current->pos += n; + } else if ((n - memLeftInElement) < memLeft) { + ecs_os_strncpy(ecs_strbuf_ptr(b), str, memLeftInElement); - if (cur_gen != gen) { - return NULL; + /* Element was not large enough, but buffer still has space */ + b->current->pos += memLeftInElement; + n -= memLeftInElement; + + /* Current element was too small, copy remainder into new element */ + if (n < ECS_STRBUF_ELEMENT_SIZE) { + /* A standard-size buffer is large enough for the new string */ + ecs_strbuf_grow(b); + + /* Copy the remainder to the new buffer */ + if (n) { + /* If a max number of characters to write is set, only a + * subset of the string should be copied to the buffer */ + ecs_os_strncpy( + ecs_strbuf_ptr(b), + str + memLeftInElement, + (size_t)n); + } else { + ecs_os_strcpy(ecs_strbuf_ptr(b), str + memLeftInElement); + } + + /* Update to number of characters copied to new buffer */ + b->current->pos += n; + } else { + char *remainder = ecs_os_strdup(str); + ecs_strbuf_grow_str(b, remainder, remainder, n); + } + } else { + /* Buffer max has been reached */ + return false; } - ecs_assert(dense == chunk->sparse[offset], ECS_INTERNAL_ERROR, NULL); - return DATA(chunk->data, sparse->size, offset); + return ecs_strbuf_memLeft(b) > 0; } -/* Get value from sparse set when it is guaranteed that the value exists. This - * function is used when values are obtained using a dense index */ static -void* get_sparse( - const ecs_sparse_t *sparse, - int32_t dense, - uint64_t index) +bool appendch( + ecs_strbuf_t *b, + char ch) { - strip_generation(&index); - chunk_t *chunk = get_chunk(sparse, CHUNK(index)); - int32_t offset = OFFSET(index); - - ecs_assert(chunk != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(dense == chunk->sparse[offset], ECS_INTERNAL_ERROR, NULL); - (void)dense; + ecs_strbuf_init(b); - return DATA(chunk->data, sparse->size, offset); -} + int32_t memLeftInElement = ecs_strbuf_memLeftInCurrentElement(b); + int32_t memLeft = ecs_strbuf_memLeft(b); + if (memLeft <= 0) { + return false; + } -/* Swap dense elements. A swap occurs when an element is removed, or when a - * removed element is recycled. */ -static -void swap_dense( - ecs_sparse_t * sparse, - chunk_t * chunk_a, - int32_t a, - int32_t b) -{ - ecs_assert(a != b, ECS_INTERNAL_ERROR, NULL); - uint64_t *dense_array = ecs_vector_first(sparse->dense, uint64_t); - uint64_t index_a = dense_array[a]; - uint64_t index_b = dense_array[b]; + if (memLeftInElement) { + /* Element was large enough to fit string */ + ecs_strbuf_ptr(b)[0] = ch; + b->current->pos ++; + } else { + ecs_strbuf_grow(b); + ecs_strbuf_ptr(b)[0] = ch; + b->current->pos ++; + } - chunk_t *chunk_b = get_or_create_chunk(sparse, CHUNK(index_b)); - assign_index(chunk_a, dense_array, index_a, b); - assign_index(chunk_b, dense_array, index_b, a); + return ecs_strbuf_memLeft(b) > 0; } -ecs_sparse_t* _flecs_sparse_new( - ecs_size_t size) +bool ecs_strbuf_vappend( + ecs_strbuf_t *b, + const char* fmt, + va_list args) { - ecs_sparse_t *result = ecs_os_calloc(ECS_SIZEOF(ecs_sparse_t)); - ecs_assert(result != NULL, ECS_OUT_OF_MEMORY, NULL); - result->size = size; - result->max_id_local = UINT64_MAX; - result->max_id = &result->max_id_local; + ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(fmt != NULL, ECS_INVALID_PARAMETER, NULL); + return vappend(b, fmt, args); +} - /* Consume first value in dense array as 0 is used in the sparse array to - * indicate that a sparse element hasn't been paired yet. */ - uint64_t *first = ecs_vector_add(&result->dense, uint64_t); - *first = 0; +bool ecs_strbuf_append( + ecs_strbuf_t *b, + const char* fmt, + ...) +{ + ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(fmt != NULL, ECS_INVALID_PARAMETER, NULL); - result->count = 1; + va_list args; + va_start(args, fmt); + bool result = vappend(b, fmt, args); + va_end(args); return result; } -void flecs_sparse_set_id_source( - ecs_sparse_t * sparse, - uint64_t * id_source) +bool ecs_strbuf_appendstrn( + ecs_strbuf_t *b, + const char* str, + int32_t len) { - ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); - sparse->max_id = id_source; + ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(str != NULL, ECS_INVALID_PARAMETER, NULL); + return appendstr(b, str, len); } -void flecs_sparse_clear( - ecs_sparse_t *sparse) +bool ecs_strbuf_appendch( + ecs_strbuf_t *b, + char ch) { - ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); - - ecs_vector_each(sparse->chunks, chunk_t, chunk, { - chunk_free(chunk); - }); + ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); + return appendch(b, ch); +} - ecs_vector_free(sparse->chunks); - ecs_vector_set_count(&sparse->dense, uint64_t, 1); +bool ecs_strbuf_appendflt( + ecs_strbuf_t *b, + double flt) +{ + ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); + return ecs_strbuf_ftoa(b, flt, 2); +} - sparse->chunks = NULL; - sparse->count = 1; - sparse->max_id_local = 0; +bool ecs_strbuf_appendstr_zerocpy( + ecs_strbuf_t *b, + char* str) +{ + ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(str != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_strbuf_init(b); + ecs_strbuf_grow_str(b, str, str, 0); + return true; } -void flecs_sparse_free( - ecs_sparse_t *sparse) +bool ecs_strbuf_appendstr_zerocpy_const( + ecs_strbuf_t *b, + const char* str) { - if (sparse) { - flecs_sparse_clear(sparse); - ecs_vector_free(sparse->dense); - ecs_os_free(sparse); - } + ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(str != NULL, ECS_INVALID_PARAMETER, NULL); + /* Removes const modifier, but logic prevents changing / delete string */ + ecs_strbuf_init(b); + ecs_strbuf_grow_str(b, (char*)str, NULL, 0); + return true; } -uint64_t flecs_sparse_new_id( - ecs_sparse_t *sparse) +bool ecs_strbuf_appendstr( + ecs_strbuf_t *b, + const char* str) { - ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); - return new_index(sparse); + ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(str != NULL, ECS_INVALID_PARAMETER, NULL); + return appendstr(b, str, ecs_os_strlen(str)); } -const uint64_t* flecs_sparse_new_ids( - ecs_sparse_t *sparse, - int32_t new_count) +bool ecs_strbuf_mergebuff( + ecs_strbuf_t *dst_buffer, + ecs_strbuf_t *src_buffer) { - ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); - int32_t dense_count = ecs_vector_count(sparse->dense); - int32_t count = sparse->count; - int32_t remaining = dense_count - count; - int32_t i, to_create = new_count - remaining; + if (src_buffer->elementCount) { + if (src_buffer->buf) { + return ecs_strbuf_appendstr(dst_buffer, src_buffer->buf); + } else { + ecs_strbuf_element *e = (ecs_strbuf_element*)&src_buffer->firstElement; - if (to_create > 0) { - flecs_sparse_set_size(sparse, dense_count + to_create); - uint64_t *dense_array = ecs_vector_first(sparse->dense, uint64_t); + /* Copy first element as it is inlined in the src buffer */ + ecs_strbuf_appendstrn(dst_buffer, e->buf, e->pos); - for (i = 0; i < to_create; i ++) { - uint64_t index = create_id(sparse, count + i); - dense_array[dense_count + i] = index; + while ((e = e->next)) { + dst_buffer->current->next = ecs_os_malloc(sizeof(ecs_strbuf_element)); + *dst_buffer->current->next = *e; + } } - } - sparse->count += new_count; + *src_buffer = ECS_STRBUF_INIT; + } - return ecs_vector_get(sparse->dense, uint64_t, count); + return true; } -void* _flecs_sparse_add( - ecs_sparse_t *sparse, - ecs_size_t size) +char* ecs_strbuf_get( + ecs_strbuf_t *b) { - ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_assert(!size || size == sparse->size, ECS_INVALID_PARAMETER, NULL); - uint64_t index = new_index(sparse); - chunk_t *chunk = get_chunk(sparse, CHUNK(index)); - ecs_assert(chunk != NULL, ECS_INTERNAL_ERROR, NULL); - return DATA(chunk->data, size, OFFSET(index)); -} + ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); -uint64_t flecs_sparse_last_id( - const ecs_sparse_t *sparse) -{ - ecs_assert(sparse != NULL, ECS_INTERNAL_ERROR, NULL); - uint64_t *dense_array = ecs_vector_first(sparse->dense, uint64_t); - return dense_array[sparse->count - 1]; -} + char* result = NULL; + if (b->elementCount) { + if (b->buf) { + b->buf[b->current->pos] = '\0'; + result = ecs_os_strdup(b->buf); + } else { + void *next = NULL; + int32_t len = b->size + b->current->pos + 1; -void* _flecs_sparse_ensure( - ecs_sparse_t *sparse, - ecs_size_t size, - uint64_t index) -{ - ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_assert(!size || size == sparse->size, ECS_INVALID_PARAMETER, NULL); - ecs_assert(ecs_vector_count(sparse->dense) > 0, ECS_INTERNAL_ERROR, NULL); - (void)size; + ecs_strbuf_element *e = (ecs_strbuf_element*)&b->firstElement; - uint64_t gen = strip_generation(&index); - chunk_t *chunk = get_or_create_chunk(sparse, CHUNK(index)); - int32_t offset = OFFSET(index); - int32_t dense = chunk->sparse[offset]; + result = ecs_os_malloc(len); + char* ptr = result; - if (dense) { - /* Check if element is alive. If element is not alive, update indices so - * that the first unused dense element points to the sparse element. */ - int32_t count = sparse->count; - if (dense == count) { - /* If dense is the next unused element in the array, simply increase - * the count to make it part of the alive set. */ - sparse->count ++; - } else if (dense > count) { - /* If dense is not alive, swap it with the first unused element. */ - swap_dense(sparse, chunk, dense, count); + do { + ecs_os_memcpy(ptr, e->buf, e->pos); + ptr += e->pos; + next = e->next; + if (e != &b->firstElement.super) { + if (!e->buffer_embedded) { + ecs_os_free(((ecs_strbuf_element_str*)e)->alloc_str); + } + ecs_os_free(e); + } + } while ((e = next)); - /* First unused element is now last used element */ - sparse->count ++; - } else { - /* Dense is already alive, nothing to be done */ + result[len - 1] = '\0'; + b->length = len; } - - /* Ensure provided generation matches current. Only allow mismatching - * generations if the provided generation count is 0. This allows for - * using the ensure function in combination with ids that have their - * generation stripped. */ - ecs_vector_t *dense_vector = sparse->dense; - uint64_t *dense_array = ecs_vector_first(dense_vector, uint64_t); - ecs_assert(!gen || dense_array[dense] == (index | gen), ECS_INTERNAL_ERROR, NULL); - (void)dense_vector; - (void)dense_array; } else { - /* Element is not paired yet. Must add a new element to dense array */ - grow_dense(sparse); - - ecs_vector_t *dense_vector = sparse->dense; - uint64_t *dense_array = ecs_vector_first(dense_vector, uint64_t); - int32_t dense_count = ecs_vector_count(dense_vector) - 1; - int32_t count = sparse->count ++; - - /* If index is larger than max id, update max id */ - if (index >= get_id(sparse)) { - set_id(sparse, index + 1); - } + result = NULL; + } - if (count < dense_count) { - /* If there are unused elements in the list, move the first unused - * element to the end of the list */ - uint64_t unused = dense_array[count]; - chunk_t *unused_chunk = get_or_create_chunk(sparse, CHUNK(unused)); - assign_index(unused_chunk, dense_array, unused, dense_count); - } + b->elementCount = 0; - assign_index(chunk, dense_array, index, count); - dense_array[count] |= gen; - } + b->content = result; - return DATA(chunk->data, sparse->size, offset); + return result; } -void* _flecs_sparse_set( - ecs_sparse_t * sparse, - ecs_size_t elem_size, - uint64_t index, - void* value) +char *ecs_strbuf_get_small( + ecs_strbuf_t *b) { - void *ptr = _flecs_sparse_ensure(sparse, elem_size, index); - ecs_os_memcpy(ptr, value, elem_size); - return ptr; + ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); + + int32_t written = ecs_strbuf_written(b); + ecs_assert(written <= ECS_STRBUF_ELEMENT_SIZE, ECS_INVALID_OPERATION, NULL); + char *buf = b->firstElement.buf; + buf[written] = '\0'; + return buf; } -void* _flecs_sparse_remove_get( - ecs_sparse_t *sparse, - ecs_size_t size, - uint64_t index) -{ - ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_assert(!size || size == sparse->size, ECS_INVALID_PARAMETER, NULL); - (void)size; - - chunk_t *chunk = get_or_create_chunk(sparse, CHUNK(index)); - uint64_t gen = strip_generation(&index); - int32_t offset = OFFSET(index); - int32_t dense = chunk->sparse[offset]; - - if (dense) { - uint64_t *dense_array = ecs_vector_first(sparse->dense, uint64_t); - uint64_t cur_gen = dense_array[dense] & ECS_GENERATION_MASK; - if (gen != cur_gen) { - /* Generation doesn't match which means that the provided entity is - * already not alive. */ - return NULL; - } - - /* Increase generation */ - dense_array[dense] = index | inc_gen(cur_gen); - - int32_t count = sparse->count; - if (dense == (count - 1)) { - /* If dense is the last used element, simply decrease count */ - sparse->count --; - } else if (dense < count) { - /* If element is alive, move it to unused elements */ - swap_dense(sparse, chunk, dense, count - 1); - sparse->count --; - } else { - /* Element is not alive, nothing to be done */ - return NULL; - } - - /* Reset memory to zero on remove */ - return DATA(chunk->data, sparse->size, offset); - } else { - /* Element is not paired and thus not alive, nothing to be done */ - return NULL; - } -} - -void flecs_sparse_remove( - ecs_sparse_t *sparse, - uint64_t index) -{ - void *ptr = _flecs_sparse_remove_get(sparse, 0, index); - if (ptr) { - ecs_os_memset(ptr, 0, sparse->size); - } -} - -void flecs_sparse_set_generation( - ecs_sparse_t *sparse, - uint64_t index) -{ - ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); - chunk_t *chunk = get_or_create_chunk(sparse, CHUNK(index)); - - uint64_t index_w_gen = index; - strip_generation(&index); - int32_t offset = OFFSET(index); - int32_t dense = chunk->sparse[offset]; - - if (dense) { - /* Increase generation */ - uint64_t *dense_array = ecs_vector_first(sparse->dense, uint64_t); - dense_array[dense] = index_w_gen; - } else { - /* Element is not paired and thus not alive, nothing to be done */ - } -} - -bool flecs_sparse_exists( - const ecs_sparse_t *sparse, - uint64_t index) -{ - ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); - chunk_t *chunk = get_chunk(sparse, CHUNK(index)); - if (!chunk) { - return false; - } - - strip_generation(&index); - int32_t offset = OFFSET(index); - int32_t dense = chunk->sparse[offset]; - - return dense != 0; -} - -void* _flecs_sparse_get_dense( - const ecs_sparse_t *sparse, - ecs_size_t size, - int32_t dense_index) -{ - ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_assert(!size || size == sparse->size, ECS_INVALID_PARAMETER, NULL); - ecs_assert(dense_index < sparse->count, ECS_INVALID_PARAMETER, NULL); - (void)size; - - dense_index ++; - - uint64_t *dense_array = ecs_vector_first(sparse->dense, uint64_t); - return get_sparse(sparse, dense_index, dense_array[dense_index]); -} - -bool flecs_sparse_is_alive( - const ecs_sparse_t *sparse, - uint64_t index) +void ecs_strbuf_reset( + ecs_strbuf_t *b) { - return try_sparse(sparse, index) != NULL; -} + ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); -uint64_t flecs_sparse_get_alive( - const ecs_sparse_t *sparse, - uint64_t index) -{ - chunk_t *chunk = get_chunk(sparse, CHUNK(index)); - if (!chunk) { - return 0; + if (b->elementCount && !b->buf) { + void *next = NULL; + ecs_strbuf_element *e = (ecs_strbuf_element*)&b->firstElement; + do { + next = e->next; + if (e != (ecs_strbuf_element*)&b->firstElement) { + ecs_os_free(e); + } + } while ((e = next)); } - int32_t offset = OFFSET(index); - int32_t dense = chunk->sparse[offset]; - uint64_t *dense_array = ecs_vector_first(sparse->dense, uint64_t); - - /* If dense is 0 (tombstone) this will return 0 */ - return dense_array[dense]; -} - -void* _flecs_sparse_get( - const ecs_sparse_t *sparse, - ecs_size_t size, - uint64_t index) -{ - ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_assert(!size || size == sparse->size, ECS_INVALID_PARAMETER, NULL); - (void)size; - return try_sparse(sparse, index); -} - -void* _flecs_sparse_get_any( - ecs_sparse_t *sparse, - ecs_size_t size, - uint64_t index) -{ - ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_assert(!size || size == sparse->size, ECS_INVALID_PARAMETER, NULL); - (void)size; - return try_sparse_any(sparse, index); + *b = ECS_STRBUF_INIT; } -int32_t flecs_sparse_count( - const ecs_sparse_t *sparse) +void ecs_strbuf_list_push( + ecs_strbuf_t *b, + const char *list_open, + const char *separator) { - if (!sparse) { - return 0; - } + ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(list_open != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(separator != NULL, ECS_INVALID_PARAMETER, NULL); - return sparse->count - 1; -} + b->list_sp ++; + b->list_stack[b->list_sp].count = 0; + b->list_stack[b->list_sp].separator = separator; -int32_t flecs_sparse_size( - const ecs_sparse_t *sparse) -{ - if (!sparse) { - return 0; + if (list_open) { + ecs_strbuf_appendstr(b, list_open); } - - return ecs_vector_count(sparse->dense) - 1; } -const uint64_t* flecs_sparse_ids( - const ecs_sparse_t *sparse) -{ - ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); - return &(ecs_vector_first(sparse->dense, uint64_t)[1]); -} - -void flecs_sparse_set_size( - ecs_sparse_t *sparse, - int32_t elem_count) +void ecs_strbuf_list_pop( + ecs_strbuf_t *b, + const char *list_close) { - ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_vector_set_size(&sparse->dense, uint64_t, elem_count); -} + ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(list_close != NULL, ECS_INVALID_PARAMETER, NULL); -static -void sparse_copy( - ecs_sparse_t * dst, - const ecs_sparse_t * src) -{ - flecs_sparse_set_size(dst, flecs_sparse_size(src)); - const uint64_t *indices = flecs_sparse_ids(src); + b->list_sp --; - ecs_size_t size = src->size; - int32_t i, count = src->count; - - for (i = 0; i < count - 1; i ++) { - uint64_t index = indices[i]; - void *src_ptr = _flecs_sparse_get(src, size, index); - void *dst_ptr = _flecs_sparse_ensure(dst, size, index); - flecs_sparse_set_generation(dst, index); - ecs_os_memcpy(dst_ptr, src_ptr, size); + if (list_close) { + ecs_strbuf_appendstr(b, list_close); } - - set_id(dst, get_id(src)); - - ecs_assert(src->count == dst->count, ECS_INTERNAL_ERROR, NULL); } -ecs_sparse_t* flecs_sparse_copy( - const ecs_sparse_t *src) +void ecs_strbuf_list_next( + ecs_strbuf_t *b) { - if (!src) { - return NULL; - } - - ecs_sparse_t *dst = _flecs_sparse_new(src->size); - sparse_copy(dst, src); - - return dst; -} + ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); -void flecs_sparse_restore( - ecs_sparse_t * dst, - const ecs_sparse_t * src) -{ - ecs_assert(dst != NULL, ECS_INVALID_PARAMETER, NULL); - dst->count = 1; - if (src) { - sparse_copy(dst, src); + int32_t list_sp = b->list_sp; + if (b->list_stack[list_sp].count != 0) { + ecs_strbuf_appendstr(b, b->list_stack[list_sp].separator); } + b->list_stack[list_sp].count ++; } -void flecs_sparse_memory( - ecs_sparse_t *sparse, - int32_t *allocd, - int32_t *used) -{ - (void)sparse; - (void)allocd; - (void)used; -} - -ecs_sparse_t* _ecs_sparse_new( - ecs_size_t elem_size) +bool ecs_strbuf_list_append( + ecs_strbuf_t *b, + const char *fmt, + ...) { - return _flecs_sparse_new(elem_size); -} + ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(fmt != NULL, ECS_INVALID_PARAMETER, NULL); -void* _ecs_sparse_add( - ecs_sparse_t *sparse, - ecs_size_t elem_size) -{ - return _flecs_sparse_add(sparse, elem_size); -} + ecs_strbuf_list_next(b); -uint64_t ecs_sparse_last_id( - const ecs_sparse_t *sparse) -{ - return flecs_sparse_last_id(sparse); -} + va_list args; + va_start(args, fmt); + bool result = vappend(b, fmt, args); + va_end(args); -int32_t ecs_sparse_count( - const ecs_sparse_t *sparse) -{ - return flecs_sparse_count(sparse); + return result; } -void* _ecs_sparse_get_dense( - const ecs_sparse_t *sparse, - ecs_size_t elem_size, - int32_t index) +bool ecs_strbuf_list_appendstr( + ecs_strbuf_t *b, + const char *str) { - return _flecs_sparse_get_dense(sparse, elem_size, index); -} + ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(str != NULL, ECS_INVALID_PARAMETER, NULL); -void* _ecs_sparse_get( - const ecs_sparse_t *sparse, - ecs_size_t elem_size, - uint64_t id) -{ - return _flecs_sparse_get(sparse, elem_size, id); + ecs_strbuf_list_next(b); + return ecs_strbuf_appendstr(b, str); } -ecs_sparse_iter_t _flecs_sparse_iter( - ecs_sparse_t *sparse, - ecs_size_t elem_size) +int32_t ecs_strbuf_written( + const ecs_strbuf_t *b) { - ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_assert(elem_size == sparse->size, ECS_INVALID_PARAMETER, NULL); - ecs_sparse_iter_t result; - result.sparse = sparse; - result.ids = flecs_sparse_ids(sparse); - result.size = elem_size; - result.i = 0; - result.count = sparse->count - 1; - return result; + ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); + return b->size + b->current->pos; } - #ifdef FLECS_SANITIZE static void verify_nodes( @@ -10638,1607 +10088,1131 @@ int32_t flecs_switch_next( return nodes[element].next; } +/** Resize the vector buffer */ +static +ecs_vector_t* resize( + ecs_vector_t *vector, + int16_t offset, + int32_t size) +{ + ecs_vector_t *result = ecs_os_realloc(vector, offset + size); + ecs_assert(result != NULL, ECS_OUT_OF_MEMORY, 0); + return result; +} -#ifndef _MSC_VER -#pragma GCC diagnostic ignored "-Wimplicit-fallthrough" -#endif - -/* See explanation below. The hashing function may read beyond the memory passed - * into the hashing function, but only at word boundaries. This should be safe, - * but trips up address sanitizers and valgrind. - * This ensures clean valgrind logs in debug mode & the best perf in release */ -#if !defined(NDEBUG) || defined(ADDRESS_SANITIZER) -#ifndef VALGRIND -#define VALGRIND -#endif -#endif +/* -- Public functions -- */ -/* -------------------------------------------------------------------------------- -lookup3.c, by Bob Jenkins, May 2006, Public Domain. - http://burtleburtle.net/bob/c/lookup3.c -------------------------------------------------------------------------------- -*/ +ecs_vector_t* _ecs_vector_new( + ecs_size_t elem_size, + int16_t offset, + int32_t elem_count) +{ + ecs_assert(elem_size != 0, ECS_INTERNAL_ERROR, NULL); + + ecs_vector_t *result = + ecs_os_malloc(offset + elem_size * elem_count); + ecs_assert(result != NULL, ECS_OUT_OF_MEMORY, NULL); -#ifdef _MSC_VER -//FIXME -#else -#include /* attempt to define endianness */ -#endif -#ifdef linux -# include /* attempt to define endianness */ + result->count = 0; + result->size = elem_count; +#ifndef NDEBUG + result->elem_size = elem_size; #endif + return result; +} -/* - * My best guess at if you are big-endian or little-endian. This may - * need adjustment. - */ -#if (defined(__BYTE_ORDER) && defined(__LITTLE_ENDIAN) && \ - __BYTE_ORDER == __LITTLE_ENDIAN) || \ - (defined(i386) || defined(__i386__) || defined(__i486__) || \ - defined(__i586__) || defined(__i686__) || defined(vax) || defined(MIPSEL)) -# define HASH_LITTLE_ENDIAN 1 -#elif (defined(__BYTE_ORDER) && defined(__BIG_ENDIAN) && \ - __BYTE_ORDER == __BIG_ENDIAN) || \ - (defined(sparc) || defined(POWERPC) || defined(mc68000) || defined(sel)) -# define HASH_LITTLE_ENDIAN 0 -#else -# define HASH_LITTLE_ENDIAN 0 -#endif +ecs_vector_t* _ecs_vector_from_array( + ecs_size_t elem_size, + int16_t offset, + int32_t elem_count, + void *array) +{ + ecs_assert(elem_size != 0, ECS_INTERNAL_ERROR, NULL); + + ecs_vector_t *result = + ecs_os_malloc(offset + elem_size * elem_count); + ecs_assert(result != NULL, ECS_OUT_OF_MEMORY, NULL); -#define rot(x,k) (((x)<<(k)) | ((x)>>(32-(k)))) + ecs_os_memcpy(ECS_OFFSET(result, offset), array, elem_size * elem_count); -/* -------------------------------------------------------------------------------- -mix -- mix 3 32-bit values reversibly. -This is reversible, so any information in (a,b,c) before mix() is -still in (a,b,c) after mix(). -If four pairs of (a,b,c) inputs are run through mix(), or through -mix() in reverse, there are at least 32 bits of the output that -are sometimes the same for one pair and different for another pair. -This was tested for: -* pairs that differed by one bit, by two bits, in any combination - of top bits of (a,b,c), or in any combination of bottom bits of - (a,b,c). -* "differ" is defined as +, -, ^, or ~^. For + and -, I transformed - the output delta to a Gray code (a^(a>>1)) so a string of 1's (as - is commonly produced by subtraction) look like a single 1-bit - difference. -* the base values were pseudorandom, all zero but one bit set, or - all zero plus a counter that starts at zero. -Some k values for my "a-=c; a^=rot(c,k); c+=b;" arrangement that -satisfy this are - 4 6 8 16 19 4 - 9 15 3 18 27 15 - 14 9 3 7 17 3 -Well, "9 15 3 18 27 15" didn't quite get 32 bits diffing -for "differ" defined as + with a one-bit base and a two-bit delta. I -used http://burtleburtle.net/bob/hash/avalanche.html to choose -the operations, constants, and arrangements of the variables. -This does not achieve avalanche. There are input bits of (a,b,c) -that fail to affect some output bits of (a,b,c), especially of a. The -most thoroughly mixed value is c, but it doesn't really even achieve -avalanche in c. -This allows some parallelism. Read-after-writes are good at doubling -the number of bits affected, so the goal of mixing pulls in the opposite -direction as the goal of parallelism. I did what I could. Rotates -seem to cost as much as shifts on every machine I could lay my hands -on, and rotates are much kinder to the top and bottom bits, so I used -rotates. -------------------------------------------------------------------------------- -*/ -#define mix(a,b,c) \ -{ \ - a -= c; a ^= rot(c, 4); c += b; \ - b -= a; b ^= rot(a, 6); a += c; \ - c -= b; c ^= rot(b, 8); b += a; \ - a -= c; a ^= rot(c,16); c += b; \ - b -= a; b ^= rot(a,19); a += c; \ - c -= b; c ^= rot(b, 4); b += a; \ + result->count = elem_count; + result->size = elem_count; +#ifndef NDEBUG + result->elem_size = elem_size; +#endif + return result; } -/* -------------------------------------------------------------------------------- -final -- final mixing of 3 32-bit values (a,b,c) into c -Pairs of (a,b,c) values differing in only a few bits will usually -produce values of c that look totally different. This was tested for -* pairs that differed by one bit, by two bits, in any combination - of top bits of (a,b,c), or in any combination of bottom bits of - (a,b,c). -* "differ" is defined as +, -, ^, or ~^. For + and -, I transformed - the output delta to a Gray code (a^(a>>1)) so a string of 1's (as - is commonly produced by subtraction) look like a single 1-bit - difference. -* the base values were pseudorandom, all zero but one bit set, or - all zero plus a counter that starts at zero. -These constants passed: - 14 11 25 16 4 14 24 - 12 14 25 16 4 14 24 -and these came close: - 4 8 15 26 3 22 24 - 10 8 15 26 3 22 24 - 11 8 15 26 3 22 24 -------------------------------------------------------------------------------- -*/ -#define final(a,b,c) \ -{ \ - c ^= b; c -= rot(b,14); \ - a ^= c; a -= rot(c,11); \ - b ^= a; b -= rot(a,25); \ - c ^= b; c -= rot(b,16); \ - a ^= c; a -= rot(c,4); \ - b ^= a; b -= rot(a,14); \ - c ^= b; c -= rot(b,24); \ +void ecs_vector_free( + ecs_vector_t *vector) +{ + ecs_os_free(vector); } - -/* - * hashlittle2: return 2 32-bit hash values - * - * This is identical to hashlittle(), except it returns two 32-bit hash - * values instead of just one. This is good enough for hash table - * lookup with 2^^64 buckets, or if you want a second hash if you're not - * happy with the first, or if you want a probably-unique 64-bit ID for - * the key. *pc is better mixed than *pb, so use *pc first. If you want - * a 64-bit value do something like "*pc + (((uint64_t)*pb)<<32)". - */ -static -void hashlittle2( - const void *key, /* the key to hash */ - size_t length, /* length of the key */ - uint32_t *pc, /* IN: primary initval, OUT: primary hash */ - uint32_t *pb) /* IN: secondary initval, OUT: secondary hash */ +void ecs_vector_clear( + ecs_vector_t *vector) { - uint32_t a,b,c; /* internal state */ - union { const void *ptr; size_t i; } u; /* needed for Mac Powerbook G4 */ + if (vector) { + vector->count = 0; + } +} - /* Set up the internal state */ - a = b = c = 0xdeadbeef + ((uint32_t)length) + *pc; - c += *pb; +void _ecs_vector_zero( + ecs_vector_t *vector, + ecs_size_t elem_size, + int16_t offset) +{ + void *array = ECS_OFFSET(vector, offset); + ecs_os_memset(array, 0, elem_size * vector->count); +} - u.ptr = key; - if (HASH_LITTLE_ENDIAN && ((u.i & 0x3) == 0)) { - const uint32_t *k = (const uint32_t *)key; /* read 32-bit chunks */ - const uint8_t *k8; - (void)k8; +void ecs_vector_assert_size( + ecs_vector_t *vector, + ecs_size_t elem_size) +{ + (void)elem_size; + + if (vector) { + ecs_dbg_assert(vector->elem_size == elem_size, ECS_INTERNAL_ERROR, NULL); + } +} - /*------ all but last block: aligned reads and affect 32 bits of (a,b,c) */ - while (length > 12) - { - a += k[0]; - b += k[1]; - c += k[2]; - mix(a,b,c); - length -= 12; - k += 3; +void* _ecs_vector_addn( + ecs_vector_t **array_inout, + ecs_size_t elem_size, + int16_t offset, + int32_t elem_count) +{ + ecs_assert(array_inout != NULL, ECS_INTERNAL_ERROR, NULL); + + if (elem_count == 1) { + return _ecs_vector_add(array_inout, elem_size, offset); + } + + ecs_vector_t *vector = *array_inout; + if (!vector) { + vector = _ecs_vector_new(elem_size, offset, 1); + *array_inout = vector; } - /*----------------------------- handle the last (probably partial) block */ - /* - * "k[2]&0xffffff" actually reads beyond the end of the string, but - * then masks off the part it's not allowed to read. Because the - * string is aligned, the masked-off tail is in the same word as the - * rest of the string. Every machine with memory protection I've seen - * does it on word boundaries, so is OK with this. But VALGRIND will - * still catch it and complain. The masking trick does make the hash - * noticably faster for short strings (like English words). - */ -#ifndef VALGRIND + ecs_dbg_assert(vector->elem_size == elem_size, ECS_INTERNAL_ERROR, NULL); - switch(length) - { - case 12: c+=k[2]; b+=k[1]; a+=k[0]; break; - case 11: c+=k[2]&0xffffff; b+=k[1]; a+=k[0]; break; - case 10: c+=k[2]&0xffff; b+=k[1]; a+=k[0]; break; - case 9 : c+=k[2]&0xff; b+=k[1]; a+=k[0]; break; - case 8 : b+=k[1]; a+=k[0]; break; - case 7 : b+=k[1]&0xffffff; a+=k[0]; break; - case 6 : b+=k[1]&0xffff; a+=k[0]; break; - case 5 : b+=k[1]&0xff; a+=k[0]; break; - case 4 : a+=k[0]; break; - case 3 : a+=k[0]&0xffffff; break; - case 2 : a+=k[0]&0xffff; break; - case 1 : a+=k[0]&0xff; break; - case 0 : *pc=c; *pb=b; return; /* zero length strings require no mixing */ - } + int32_t max_count = vector->size; + int32_t old_count = vector->count; + int32_t new_count = old_count + elem_count; -#else /* make valgrind happy */ + if ((new_count - 1) >= max_count) { + if (!max_count) { + max_count = elem_count; + } else { + while (max_count < new_count) { + max_count *= 2; + } + } - k8 = (const uint8_t *)k; - switch(length) - { - case 12: c+=k[2]; b+=k[1]; a+=k[0]; break; - case 11: c+=((uint32_t)k8[10])<<16; /* fall through */ - case 10: c+=((uint32_t)k8[9])<<8; /* fall through */ - case 9 : c+=k8[8]; /* fall through */ - case 8 : b+=k[1]; a+=k[0]; break; - case 7 : b+=((uint32_t)k8[6])<<16; /* fall through */ - case 6 : b+=((uint32_t)k8[5])<<8; /* fall through */ - case 5 : b+=k8[4]; /* fall through */ - case 4 : a+=k[0]; break; - case 3 : a+=((uint32_t)k8[2])<<16; /* fall through */ - case 2 : a+=((uint32_t)k8[1])<<8; /* fall through */ - case 1 : a+=k8[0]; break; - case 0 : *pc=c; *pb=b; return; /* zero length strings require no mixing */ + vector = resize(vector, offset, max_count * elem_size); + vector->size = max_count; + *array_inout = vector; } -#endif /* !valgrind */ + vector->count = new_count; - } else if (HASH_LITTLE_ENDIAN && ((u.i & 0x1) == 0)) { - const uint16_t *k = (const uint16_t *)key; /* read 16-bit chunks */ - const uint8_t *k8; + return ECS_OFFSET(vector, offset + elem_size * old_count); +} - /*--------------- all but last block: aligned reads and different mixing */ - while (length > 12) - { - a += k[0] + (((uint32_t)k[1])<<16); - b += k[2] + (((uint32_t)k[3])<<16); - c += k[4] + (((uint32_t)k[5])<<16); - mix(a,b,c); - length -= 12; - k += 6; - } +void* _ecs_vector_add( + ecs_vector_t **array_inout, + ecs_size_t elem_size, + int16_t offset) +{ + ecs_assert(array_inout != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_vector_t *vector = *array_inout; + int32_t count, size; - /*----------------------------- handle the last (probably partial) block */ - k8 = (const uint8_t *)k; - switch(length) - { - case 12: c+=k[4]+(((uint32_t)k[5])<<16); - b+=k[2]+(((uint32_t)k[3])<<16); - a+=k[0]+(((uint32_t)k[1])<<16); - break; - case 11: c+=((uint32_t)k8[10])<<16; /* fall through */ - case 10: c+=k[4]; - b+=k[2]+(((uint32_t)k[3])<<16); - a+=k[0]+(((uint32_t)k[1])<<16); - break; - case 9 : c+=k8[8]; /* fall through */ - case 8 : b+=k[2]+(((uint32_t)k[3])<<16); - a+=k[0]+(((uint32_t)k[1])<<16); - break; - case 7 : b+=((uint32_t)k8[6])<<16; /* fall through */ - case 6 : b+=k[2]; - a+=k[0]+(((uint32_t)k[1])<<16); - break; - case 5 : b+=k8[4]; /* fall through */ - case 4 : a+=k[0]+(((uint32_t)k[1])<<16); - break; - case 3 : a+=((uint32_t)k8[2])<<16; /* fall through */ - case 2 : a+=k[0]; - break; - case 1 : a+=k8[0]; - break; - case 0 : *pc=c; *pb=b; return; /* zero length strings require no mixing */ - } + if (vector) { + ecs_dbg_assert(vector->elem_size == elem_size, ECS_INTERNAL_ERROR, NULL); + count = vector->count; + size = vector->size; - } else { /* need to read the key one byte at a time */ - const uint8_t *k = (const uint8_t *)key; + if (count >= size) { + size *= 2; + if (!size) { + size = 2; + } + vector = resize(vector, offset, size * elem_size); + *array_inout = vector; + vector->size = size; + } - /*--------------- all but the last block: affect some 32 bits of (a,b,c) */ - while (length > 12) - { - a += k[0]; - a += ((uint32_t)k[1])<<8; - a += ((uint32_t)k[2])<<16; - a += ((uint32_t)k[3])<<24; - b += k[4]; - b += ((uint32_t)k[5])<<8; - b += ((uint32_t)k[6])<<16; - b += ((uint32_t)k[7])<<24; - c += k[8]; - c += ((uint32_t)k[9])<<8; - c += ((uint32_t)k[10])<<16; - c += ((uint32_t)k[11])<<24; - mix(a,b,c); - length -= 12; - k += 12; + vector->count = count + 1; + return ECS_OFFSET(vector, offset + elem_size * count); } - /*-------------------------------- last block: affect all 32 bits of (c) */ - switch(length) /* all the case statements fall through */ - { - case 12: c+=((uint32_t)k[11])<<24; - case 11: c+=((uint32_t)k[10])<<16; - case 10: c+=((uint32_t)k[9])<<8; - case 9 : c+=k[8]; - case 8 : b+=((uint32_t)k[7])<<24; - case 7 : b+=((uint32_t)k[6])<<16; - case 6 : b+=((uint32_t)k[5])<<8; - case 5 : b+=k[4]; - case 4 : a+=((uint32_t)k[3])<<24; - case 3 : a+=((uint32_t)k[2])<<16; - case 2 : a+=((uint32_t)k[1])<<8; - case 1 : a+=k[0]; - break; - case 0 : *pc=c; *pb=b; return; /* zero length strings require no mixing */ + vector = _ecs_vector_new(elem_size, offset, 2); + *array_inout = vector; + vector->count = 1; + vector->size = 2; + return ECS_OFFSET(vector, offset); +} + +int32_t _ecs_vector_move_index( + ecs_vector_t **dst, + ecs_vector_t *src, + ecs_size_t elem_size, + int16_t offset, + int32_t index) +{ + if (dst && *dst) { + ecs_dbg_assert((*dst)->elem_size == elem_size, ECS_INTERNAL_ERROR, NULL); } - } + ecs_dbg_assert(src->elem_size == elem_size, ECS_INTERNAL_ERROR, NULL); - final(a,b,c); - *pc=c; *pb=b; + void *dst_elem = _ecs_vector_add(dst, elem_size, offset); + void *src_elem = _ecs_vector_get(src, elem_size, offset, index); + + ecs_os_memcpy(dst_elem, src_elem, elem_size); + return _ecs_vector_remove(src, elem_size, offset, index); } -uint64_t flecs_hash( - const void *data, - ecs_size_t length) +void ecs_vector_remove_last( + ecs_vector_t *vector) { - uint32_t h_1 = 0; - uint32_t h_2 = 0; + if (vector && vector->count) vector->count --; +} - hashlittle2( - data, - flecs_ito(size_t, length), - &h_1, - &h_2); +bool _ecs_vector_pop( + ecs_vector_t *vector, + ecs_size_t elem_size, + int16_t offset, + void *value) +{ + if (!vector) { + return false; + } - return h_1 | ((uint64_t)h_2 << 32); -} + ecs_dbg_assert(vector->elem_size == elem_size, ECS_INTERNAL_ERROR, NULL); + int32_t count = vector->count; + if (!count) { + return false; + } + void *elem = ECS_OFFSET(vector, offset + (count - 1) * elem_size); -static -void ensure( - ecs_bitset_t *bs, - ecs_size_t size) -{ - if (!bs->size) { - int32_t new_size = ((size - 1) / 64 + 1) * ECS_SIZEOF(uint64_t); - bs->size = ((size - 1) / 64 + 1) * 64; - bs->data = ecs_os_calloc(new_size); - } else if (size > bs->size) { - int32_t prev_size = ((bs->size - 1) / 64 + 1) * ECS_SIZEOF(uint64_t); - bs->size = ((size - 1) / 64 + 1) * 64; - int32_t new_size = ((size - 1) / 64 + 1) * ECS_SIZEOF(uint64_t); - bs->data = ecs_os_realloc(bs->data, new_size); - ecs_os_memset(ECS_OFFSET(bs->data, prev_size), 0, new_size - prev_size); + if (value) { + ecs_os_memcpy(value, elem, elem_size); } -} -void flecs_bitset_init( - ecs_bitset_t* bs) -{ - bs->size = 0; - bs->count = 0; - bs->data = NULL; + ecs_vector_remove_last(vector); + + return true; } -void flecs_bitset_ensure( - ecs_bitset_t *bs, - int32_t count) +int32_t _ecs_vector_remove( + ecs_vector_t *vector, + ecs_size_t elem_size, + int16_t offset, + int32_t index) { - if (count > bs->count) { - bs->count = count; - ensure(bs, count); + ecs_dbg_assert(vector->elem_size == elem_size, ECS_INTERNAL_ERROR, NULL); + + int32_t count = vector->count; + void *buffer = ECS_OFFSET(vector, offset); + void *elem = ECS_OFFSET(buffer, index * elem_size); + + ecs_assert(index < count, ECS_INVALID_PARAMETER, NULL); + + count --; + if (index != count) { + void *last_elem = ECS_OFFSET(buffer, elem_size * count); + ecs_os_memcpy(elem, last_elem, elem_size); } + + vector->count = count; + + return count; } -void flecs_bitset_deinit( - ecs_bitset_t *bs) +void _ecs_vector_reclaim( + ecs_vector_t **array_inout, + ecs_size_t elem_size, + int16_t offset) { - ecs_os_free(bs->data); + ecs_vector_t *vector = *array_inout; + + ecs_dbg_assert(vector->elem_size == elem_size, ECS_INTERNAL_ERROR, NULL); + + int32_t size = vector->size; + int32_t count = vector->count; + + if (count < size) { + size = count; + vector = resize(vector, offset, size * elem_size); + vector->size = size; + *array_inout = vector; + } } -void flecs_bitset_addn( - ecs_bitset_t *bs, - int32_t count) +int32_t ecs_vector_count( + const ecs_vector_t *vector) { - int32_t elem = bs->count += count; - ensure(bs, elem); + if (!vector) { + return 0; + } + return vector->count; } -void flecs_bitset_set( - ecs_bitset_t *bs, - int32_t elem, - bool value) +int32_t ecs_vector_size( + const ecs_vector_t *vector) { - ecs_check(elem < bs->count, ECS_INVALID_PARAMETER, NULL); - int32_t hi = elem >> 6; - int32_t lo = elem & 0x3F; - uint64_t v = bs->data[hi]; - bs->data[hi] = (v & ~((uint64_t)1 << lo)) | ((uint64_t)value << lo); -error: - return; + if (!vector) { + return 0; + } + return vector->size; } -bool flecs_bitset_get( - const ecs_bitset_t *bs, - int32_t elem) +int32_t _ecs_vector_set_size( + ecs_vector_t **array_inout, + ecs_size_t elem_size, + int16_t offset, + int32_t elem_count) { - ecs_check(elem < bs->count, ECS_INVALID_PARAMETER, NULL); - return !!(bs->data[elem >> 6] & ((uint64_t)1 << ((uint64_t)elem & 0x3F))); -error: - return false; + ecs_vector_t *vector = *array_inout; + + if (!vector) { + *array_inout = _ecs_vector_new(elem_size, offset, elem_count); + return elem_count; + } else { + ecs_dbg_assert(vector->elem_size == elem_size, ECS_INTERNAL_ERROR, NULL); + + int32_t result = vector->size; + + if (elem_count < vector->count) { + elem_count = vector->count; + } + + if (result < elem_count) { + elem_count = flecs_next_pow_of_2(elem_count); + vector = resize(vector, offset, elem_count * elem_size); + vector->size = elem_count; + *array_inout = vector; + result = elem_count; + } + + return result; + } } -int32_t flecs_bitset_count( - const ecs_bitset_t *bs) +int32_t _ecs_vector_grow( + ecs_vector_t **array_inout, + ecs_size_t elem_size, + int16_t offset, + int32_t elem_count) { - return bs->count; + int32_t current = ecs_vector_count(*array_inout); + return _ecs_vector_set_size(array_inout, elem_size, offset, current + elem_count); } -void flecs_bitset_remove( - ecs_bitset_t *bs, - int32_t elem) +int32_t _ecs_vector_set_count( + ecs_vector_t **array_inout, + ecs_size_t elem_size, + int16_t offset, + int32_t elem_count) { - ecs_check(elem < bs->count, ECS_INVALID_PARAMETER, NULL); - int32_t last = bs->count - 1; - bool last_value = flecs_bitset_get(bs, last); - flecs_bitset_set(bs, elem, last_value); - bs->count --; -error: - return; + if (!*array_inout) { + *array_inout = _ecs_vector_new(elem_size, offset, elem_count); + } + + ecs_dbg_assert((*array_inout)->elem_size == elem_size, ECS_INTERNAL_ERROR, NULL); + + (*array_inout)->count = elem_count; + ecs_size_t size = _ecs_vector_set_size(array_inout, elem_size, offset, elem_count); + return size; } -void flecs_bitset_swap( - ecs_bitset_t *bs, - int32_t elem_a, - int32_t elem_b) +void* _ecs_vector_first( + const ecs_vector_t *vector, + ecs_size_t elem_size, + int16_t offset) { - ecs_check(elem_a < bs->count, ECS_INVALID_PARAMETER, NULL); - ecs_check(elem_b < bs->count, ECS_INVALID_PARAMETER, NULL); + (void)elem_size; - bool a = flecs_bitset_get(bs, elem_a); - bool b = flecs_bitset_get(bs, elem_b); - flecs_bitset_set(bs, elem_a, b); - flecs_bitset_set(bs, elem_b, a); -error: - return; + ecs_dbg_assert(!vector || vector->elem_size == elem_size, ECS_INTERNAL_ERROR, NULL); + if (vector && vector->size) { + return ECS_OFFSET(vector, offset); + } else { + return NULL; + } } +void* _ecs_vector_get( + const ecs_vector_t *vector, + ecs_size_t elem_size, + int16_t offset, + int32_t index) +{ + ecs_assert(vector != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_dbg_assert(vector->elem_size == elem_size, ECS_INTERNAL_ERROR, NULL); + ecs_assert(index >= 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(index < vector->count, ECS_INTERNAL_ERROR, NULL); -/** - * stm32tpl -- STM32 C++ Template Peripheral Library - * Visit https://github.com/antongus/stm32tpl for new versions - * - * Copyright (c) 2011-2020 Anton B. Gusev aka AHTOXA - */ + return ECS_OFFSET(vector, offset + elem_size * index); +} -#define MAX_PRECISION (10) -static const double rounders[MAX_PRECISION + 1] = -{ - 0.5, // 0 - 0.05, // 1 - 0.005, // 2 - 0.0005, // 3 - 0.00005, // 4 - 0.000005, // 5 - 0.0000005, // 6 - 0.00000005, // 7 - 0.000000005, // 8 - 0.0000000005, // 9 - 0.00000000005 // 10 -}; +void* _ecs_vector_last( + const ecs_vector_t *vector, + ecs_size_t elem_size, + int16_t offset) +{ + if (vector) { + ecs_dbg_assert(vector->elem_size == elem_size, ECS_INTERNAL_ERROR, NULL); + int32_t count = vector->count; + if (!count) { + return NULL; + } else { + return ECS_OFFSET(vector, offset + elem_size * (count - 1)); + } + } else { + return NULL; + } +} -static -int ecs_strbuf_ftoa( - ecs_strbuf_t *out, - double f, - int precision) +int32_t _ecs_vector_set_min_size( + ecs_vector_t **vector_inout, + ecs_size_t elem_size, + int16_t offset, + int32_t elem_count) { - char buf[64]; - char * ptr = buf; - char * p1; - char c; - int64_t intPart; + if (!*vector_inout || (*vector_inout)->size < elem_count) { + return _ecs_vector_set_size(vector_inout, elem_size, offset, elem_count); + } else { + return (*vector_inout)->size; + } +} - if (precision > MAX_PRECISION) { - precision = MAX_PRECISION; +int32_t _ecs_vector_set_min_count( + ecs_vector_t **vector_inout, + ecs_size_t elem_size, + int16_t offset, + int32_t elem_count) +{ + _ecs_vector_set_min_size(vector_inout, elem_size, offset, elem_count); + + ecs_vector_t *v = *vector_inout; + if (v && v->count < elem_count) { + v->count = elem_count; } - if (f < 0) { - f = -f; - *ptr++ = '-'; - } + return v->count; +} - if (precision < 0) { - if (f < 1.0) precision = 6; - else if (f < 10.0) precision = 5; - else if (f < 100.0) precision = 4; - else if (f < 1000.0) precision = 3; - else if (f < 10000.0) precision = 2; - else if (f < 100000.0) precision = 1; - else precision = 0; - } +void _ecs_vector_sort( + ecs_vector_t *vector, + ecs_size_t elem_size, + int16_t offset, + ecs_comparator_t compare_action) +{ + if (!vector) { + return; + } - if (precision) { - f += rounders[precision]; + ecs_dbg_assert(vector->elem_size == elem_size, ECS_INTERNAL_ERROR, NULL); + + int32_t count = vector->count; + void *buffer = ECS_OFFSET(vector, offset); + + if (count > 1) { + qsort(buffer, (size_t)count, (size_t)elem_size, compare_action); } +} - intPart = (int64_t)f; - f -= (double)intPart; +void _ecs_vector_memory( + const ecs_vector_t *vector, + ecs_size_t elem_size, + int16_t offset, + int32_t *allocd, + int32_t *used) +{ + if (!vector) { + return; + } - if (!intPart) { - *ptr++ = '0'; - } else { - char *p = ptr; - while (intPart) { - *p++ = (char)('0' + intPart % 10); - intPart /= 10; - } + ecs_dbg_assert(vector->elem_size == elem_size, ECS_INTERNAL_ERROR, NULL); - p1 = p; + if (allocd) { + *allocd += vector->size * elem_size + offset; + } + if (used) { + *used += vector->count * elem_size; + } +} - while (p > ptr) { - c = *--p; - *p = *ptr; - *ptr++ = c; - } - ptr = p1; - } +ecs_vector_t* _ecs_vector_copy( + const ecs_vector_t *src, + ecs_size_t elem_size, + int16_t offset) +{ + if (!src) { + return NULL; + } - if (precision) { - *ptr++ = '.'; - while (precision--) { - f *= 10.0; - c = (char)f; - *ptr++ = (char)('0' + c); - f -= c; - } - } - *ptr = 0; - - return ecs_strbuf_appendstrn(out, buf, (int32_t)(ptr - buf)); + ecs_vector_t *dst = _ecs_vector_new(elem_size, offset, src->size); + ecs_os_memcpy(dst, src, offset + elem_size * src->count); + return dst; } +/* The ratio used to determine whether the map should rehash. If + * (element_count * LOAD_FACTOR) > bucket_count, bucket count is increased. */ +#define LOAD_FACTOR (1.5f) +#define KEY_SIZE (ECS_SIZEOF(ecs_map_key_t)) +#define GET_ELEM(array, elem_size, index) \ + ECS_OFFSET(array, (elem_size) * (index)) + +typedef struct ecs_bucket_t { + ecs_map_key_t *keys; /* Array with keys */ + void *payload; /* Payload array */ + int32_t count; /* Number of elements in bucket */ +} ecs_bucket_t; -/* Add an extra element to the buffer */ +struct ecs_map_t { + ecs_bucket_t *buckets; + int32_t elem_size; + int32_t bucket_count; + int32_t count; +}; + +/* Get bucket count for number of elements */ static -void ecs_strbuf_grow( - ecs_strbuf_t *b) +int32_t get_bucket_count( + int32_t element_count) { - /* Allocate new element */ - ecs_strbuf_element_embedded *e = ecs_os_malloc_t(ecs_strbuf_element_embedded); - b->size += b->current->pos; - b->current->next = (ecs_strbuf_element*)e; - b->current = (ecs_strbuf_element*)e; - b->elementCount ++; - e->super.buffer_embedded = true; - e->super.buf = e->buf; - e->super.pos = 0; - e->super.next = NULL; + return flecs_next_pow_of_2((int32_t)((float)element_count * LOAD_FACTOR)); } -/* Add an extra dynamic element */ +/* Get bucket index for provided map key */ static -void ecs_strbuf_grow_str( - ecs_strbuf_t *b, - char *str, - char *alloc_str, - int32_t size) +int32_t get_bucket_id( + int32_t bucket_count, + ecs_map_key_t key) { - /* Allocate new element */ - ecs_strbuf_element_str *e = ecs_os_malloc_t(ecs_strbuf_element_str); - b->size += b->current->pos; - b->current->next = (ecs_strbuf_element*)e; - b->current = (ecs_strbuf_element*)e; - b->elementCount ++; - e->super.buffer_embedded = false; - e->super.pos = size ? size : (int32_t)ecs_os_strlen(str); - e->super.next = NULL; - e->super.buf = str; - e->alloc_str = alloc_str; + ecs_assert(bucket_count > 0, ECS_INTERNAL_ERROR, NULL); + int32_t result = (int32_t)(key & ((uint64_t)bucket_count - 1)); + ecs_assert(result < INT32_MAX, ECS_INTERNAL_ERROR, NULL); + return result; } +/* Get bucket for key */ static -char* ecs_strbuf_ptr( - ecs_strbuf_t *b) +ecs_bucket_t* get_bucket( + const ecs_map_t *map, + ecs_map_key_t key) { - if (b->buf) { - return &b->buf[b->current->pos]; - } else { - return &b->current->buf[b->current->pos]; + int32_t bucket_count = map->bucket_count; + if (!bucket_count) { + return NULL; } + + int32_t bucket_id = get_bucket_id(bucket_count, key); + ecs_assert(bucket_id < bucket_count, ECS_INTERNAL_ERROR, NULL); + + return &map->buckets[bucket_id]; } -/* Compute the amount of space left in the current element */ +/* Ensure that map has at least new_count buckets */ static -int32_t ecs_strbuf_memLeftInCurrentElement( - ecs_strbuf_t *b) +void ensure_buckets( + ecs_map_t *map, + int32_t new_count) { - if (b->current->buffer_embedded) { - return ECS_STRBUF_ELEMENT_SIZE - b->current->pos; - } else { - return 0; + int32_t bucket_count = map->bucket_count; + new_count = flecs_next_pow_of_2(new_count); + if (new_count && new_count > bucket_count) { + map->buckets = ecs_os_realloc(map->buckets, new_count * ECS_SIZEOF(ecs_bucket_t)); + map->bucket_count = new_count; + + ecs_os_memset( + ECS_OFFSET(map->buckets, bucket_count * ECS_SIZEOF(ecs_bucket_t)), + 0, (new_count - bucket_count) * ECS_SIZEOF(ecs_bucket_t)); } } -/* Compute the amount of space left */ +/* Free contents of bucket */ static -int32_t ecs_strbuf_memLeft( - ecs_strbuf_t *b) +void clear_bucket( + ecs_bucket_t *bucket) { - if (b->max) { - return b->max - b->size - b->current->pos; - } else { - return INT_MAX; - } + ecs_os_free(bucket->keys); + ecs_os_free(bucket->payload); + bucket->keys = NULL; + bucket->payload = NULL; + bucket->count = 0; } +/* Clear all buckets */ static -void ecs_strbuf_init( - ecs_strbuf_t *b) +void clear_buckets( + ecs_map_t *map) { - /* Initialize buffer structure only once */ - if (!b->elementCount) { - b->size = 0; - b->firstElement.super.next = NULL; - b->firstElement.super.pos = 0; - b->firstElement.super.buffer_embedded = true; - b->firstElement.super.buf = b->firstElement.buf; - b->elementCount ++; - b->current = (ecs_strbuf_element*)&b->firstElement; + ecs_bucket_t *buckets = map->buckets; + int32_t i, count = map->bucket_count; + for (i = 0; i < count; i ++) { + clear_bucket(&buckets[i]); } + ecs_os_free(buckets); + map->buckets = NULL; + map->bucket_count = 0; } -/* Append a format string to a buffer */ +/* Find or create bucket for specified key */ static -bool vappend( - ecs_strbuf_t *b, - const char* str, - va_list args) +ecs_bucket_t* ensure_bucket( + ecs_map_t *map, + ecs_map_key_t key) { - bool result = true; - va_list arg_cpy; - - if (!str) { - return result; + if (!map->bucket_count) { + ensure_buckets(map, 2); } - ecs_strbuf_init(b); + int32_t bucket_id = get_bucket_id(map->bucket_count, key); + ecs_assert(bucket_id >= 0, ECS_INTERNAL_ERROR, NULL); + return &map->buckets[bucket_id]; +} - int32_t memLeftInElement = ecs_strbuf_memLeftInCurrentElement(b); - int32_t memLeft = ecs_strbuf_memLeft(b); +/* Add element to bucket */ +static +int32_t add_to_bucket( + ecs_bucket_t *bucket, + ecs_size_t elem_size, + ecs_map_key_t key, + const void *payload) +{ + int32_t index = bucket->count ++; + int32_t bucket_count = index + 1; - if (!memLeft) { - return false; - } + bucket->keys = ecs_os_realloc(bucket->keys, KEY_SIZE * bucket_count); + bucket->payload = ecs_os_realloc(bucket->payload, elem_size * bucket_count); + bucket->keys[index] = key; - /* Compute the memory required to add the string to the buffer. If user - * provided buffer, use space left in buffer, otherwise use space left in - * current element. */ - int32_t max_copy = b->buf ? memLeft : memLeftInElement; - int32_t memRequired; + if (payload) { + void *elem = GET_ELEM(bucket->payload, elem_size, index); + ecs_os_memcpy(elem, payload, elem_size); + } - va_copy(arg_cpy, args); - memRequired = vsnprintf( - ecs_strbuf_ptr(b), (size_t)(max_copy + 1), str, args); + return index; +} - ecs_assert(memRequired != -1, ECS_INTERNAL_ERROR, NULL); +/* Remove element from bucket */ +static +void remove_from_bucket( + ecs_bucket_t *bucket, + ecs_size_t elem_size, + ecs_map_key_t key, + int32_t index) +{ + (void)key; - if (memRequired <= memLeftInElement) { - /* Element was large enough to fit string */ - b->current->pos += memRequired; - } else if ((memRequired - memLeftInElement) < memLeft) { - /* If string is a format string, a new buffer of size memRequired is - * needed to re-evaluate the format string and only use the part that - * wasn't already copied to the previous element */ - if (memRequired <= ECS_STRBUF_ELEMENT_SIZE) { - /* Resulting string fits in standard-size buffer. Note that the - * entire string needs to fit, not just the remainder, as the - * format string cannot be partially evaluated */ - ecs_strbuf_grow(b); + ecs_assert(bucket->count != 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(index < bucket->count, ECS_INTERNAL_ERROR, NULL); + + int32_t bucket_count = -- bucket->count; - /* Copy entire string to new buffer */ - ecs_os_vsprintf(ecs_strbuf_ptr(b), str, arg_cpy); + if (index != bucket->count) { + ecs_assert(key == bucket->keys[index], ECS_INTERNAL_ERROR, NULL); + bucket->keys[index] = bucket->keys[bucket_count]; - /* Ignore the part of the string that was copied into the - * previous buffer. The string copied into the new buffer could - * be memmoved so that only the remainder is left, but that is - * most likely more expensive than just keeping the entire - * string. */ + ecs_map_key_t *elem = GET_ELEM(bucket->payload, elem_size, index); + ecs_map_key_t *last_elem = GET_ELEM(bucket->payload, elem_size, bucket->count); - /* Update position in buffer */ - b->current->pos += memRequired; - } else { - /* Resulting string does not fit in standard-size buffer. - * Allocate a new buffer that can hold the entire string. */ - char *dst = ecs_os_malloc(memRequired + 1); - ecs_os_vsprintf(dst, str, arg_cpy); - ecs_strbuf_grow_str(b, dst, dst, memRequired); - } + ecs_os_memcpy(elem, last_elem, elem_size); } +} - va_end(arg_cpy); +/* Get payload pointer for key from bucket */ +static +void* get_from_bucket( + ecs_bucket_t *bucket, + ecs_map_key_t key, + ecs_size_t elem_size) +{ + ecs_map_key_t *keys = bucket->keys; + int32_t i, count = bucket->count; - return ecs_strbuf_memLeft(b) > 0; + for (i = 0; i < count; i ++) { + if (keys[i] == key) { + return GET_ELEM(bucket->payload, elem_size, i); + } + } + return NULL; } +/* Grow number of buckets */ static -bool appendstr( - ecs_strbuf_t *b, - const char* str, - int n) +void rehash( + ecs_map_t *map, + int32_t bucket_count) { - ecs_strbuf_init(b); + ecs_assert(bucket_count != 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(bucket_count > map->bucket_count, ECS_INTERNAL_ERROR, NULL); - int32_t memLeftInElement = ecs_strbuf_memLeftInCurrentElement(b); - int32_t memLeft = ecs_strbuf_memLeft(b); - if (memLeft <= 0) { - return false; - } + ecs_size_t elem_size = map->elem_size; - /* Never write more than what the buffer can store */ - if (n > memLeft) { - n = memLeft; - } + ensure_buckets(map, bucket_count); - if (n <= memLeftInElement) { - /* Element was large enough to fit string */ - ecs_os_strncpy(ecs_strbuf_ptr(b), str, n); - b->current->pos += n; - } else if ((n - memLeftInElement) < memLeft) { - ecs_os_strncpy(ecs_strbuf_ptr(b), str, memLeftInElement); + ecs_bucket_t *buckets = map->buckets; + ecs_assert(buckets != NULL, ECS_INTERNAL_ERROR, NULL); + + int32_t bucket_id; - /* Element was not large enough, but buffer still has space */ - b->current->pos += memLeftInElement; - n -= memLeftInElement; + /* Iterate backwards as elements could otherwise be moved to existing + * buckets which could temporarily cause the number of elements in a + * bucket to exceed BUCKET_COUNT. */ + for (bucket_id = bucket_count - 1; bucket_id >= 0; bucket_id --) { + ecs_bucket_t *bucket = &buckets[bucket_id]; - /* Current element was too small, copy remainder into new element */ - if (n < ECS_STRBUF_ELEMENT_SIZE) { - /* A standard-size buffer is large enough for the new string */ - ecs_strbuf_grow(b); + int i, count = bucket->count; + ecs_map_key_t *key_array = bucket->keys; + void *payload_array = bucket->payload; - /* Copy the remainder to the new buffer */ - if (n) { - /* If a max number of characters to write is set, only a - * subset of the string should be copied to the buffer */ - ecs_os_strncpy( - ecs_strbuf_ptr(b), - str + memLeftInElement, - (size_t)n); - } else { - ecs_os_strcpy(ecs_strbuf_ptr(b), str + memLeftInElement); + for (i = 0; i < count; i ++) { + ecs_map_key_t key = key_array[i]; + void *elem = GET_ELEM(payload_array, elem_size, i); + int32_t new_bucket_id = get_bucket_id(bucket_count, key); + + if (new_bucket_id != bucket_id) { + ecs_bucket_t *new_bucket = &buckets[new_bucket_id]; + + add_to_bucket(new_bucket, elem_size, key, elem); + remove_from_bucket(bucket, elem_size, key, i); + + count --; + i --; } + } - /* Update to number of characters copied to new buffer */ - b->current->pos += n; - } else { - char *remainder = ecs_os_strdup(str); - ecs_strbuf_grow_str(b, remainder, remainder, n); + if (!bucket->count) { + clear_bucket(bucket); } - } else { - /* Buffer max has been reached */ - return false; } - - return ecs_strbuf_memLeft(b) > 0; } -static -bool appendch( - ecs_strbuf_t *b, - char ch) +ecs_map_t* _ecs_map_new( + ecs_size_t elem_size, + int32_t element_count) { - ecs_strbuf_init(b); + ecs_map_t *result = ecs_os_calloc(ECS_SIZEOF(ecs_map_t) * 1); + ecs_assert(result != NULL, ECS_OUT_OF_MEMORY, NULL); - int32_t memLeftInElement = ecs_strbuf_memLeftInCurrentElement(b); - int32_t memLeft = ecs_strbuf_memLeft(b); - if (memLeft <= 0) { - return false; - } + int32_t bucket_count = get_bucket_count(element_count); - if (memLeftInElement) { - /* Element was large enough to fit string */ - ecs_strbuf_ptr(b)[0] = ch; - b->current->pos ++; - } else { - ecs_strbuf_grow(b); - ecs_strbuf_ptr(b)[0] = ch; - b->current->pos ++; - } + result->count = 0; + result->elem_size = elem_size; - return ecs_strbuf_memLeft(b) > 0; + ensure_buckets(result, bucket_count); + + return result; } -bool ecs_strbuf_vappend( - ecs_strbuf_t *b, - const char* fmt, - va_list args) +void ecs_map_free( + ecs_map_t *map) { - ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_assert(fmt != NULL, ECS_INVALID_PARAMETER, NULL); - return vappend(b, fmt, args); + if (map) { + clear_buckets(map); + ecs_os_free(map); + } } -bool ecs_strbuf_append( - ecs_strbuf_t *b, - const char* fmt, - ...) +void* _ecs_map_get( + const ecs_map_t *map, + ecs_size_t elem_size, + ecs_map_key_t key) { - ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_assert(fmt != NULL, ECS_INVALID_PARAMETER, NULL); + (void)elem_size; - va_list args; - va_start(args, fmt); - bool result = vappend(b, fmt, args); - va_end(args); + if (!map) { + return NULL; + } - return result; -} + ecs_assert(elem_size == map->elem_size, ECS_INVALID_PARAMETER, NULL); -bool ecs_strbuf_appendstrn( - ecs_strbuf_t *b, - const char* str, - int32_t len) -{ - ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_assert(str != NULL, ECS_INVALID_PARAMETER, NULL); - return appendstr(b, str, len); -} + ecs_bucket_t * bucket = get_bucket(map, key); + if (!bucket) { + return NULL; + } -bool ecs_strbuf_appendch( - ecs_strbuf_t *b, - char ch) -{ - ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); - return appendch(b, ch); + return get_from_bucket(bucket, key, elem_size); } -bool ecs_strbuf_appendflt( - ecs_strbuf_t *b, - double flt) +void* _ecs_map_get_ptr( + const ecs_map_t *map, + ecs_map_key_t key) { - ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); - return ecs_strbuf_ftoa(b, flt, 2); -} + void* ptr_ptr = _ecs_map_get(map, ECS_SIZEOF(void*), key); -bool ecs_strbuf_appendstr_zerocpy( - ecs_strbuf_t *b, - char* str) -{ - ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_assert(str != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_strbuf_init(b); - ecs_strbuf_grow_str(b, str, str, 0); - return true; + if (ptr_ptr) { + return *(void**)ptr_ptr; + } else { + return NULL; + } } -bool ecs_strbuf_appendstr_zerocpy_const( - ecs_strbuf_t *b, - const char* str) +bool ecs_map_has( + const ecs_map_t *map, + ecs_map_key_t key) { - ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_assert(str != NULL, ECS_INVALID_PARAMETER, NULL); - /* Removes const modifier, but logic prevents changing / delete string */ - ecs_strbuf_init(b); - ecs_strbuf_grow_str(b, (char*)str, NULL, 0); - return true; + if (!map) { + return false; + } + + ecs_bucket_t * bucket = get_bucket(map, key); + if (!bucket) { + return false; + } + + return get_from_bucket(bucket, key, 0) != NULL; } -bool ecs_strbuf_appendstr( - ecs_strbuf_t *b, - const char* str) +void* _ecs_map_ensure( + ecs_map_t *map, + ecs_size_t elem_size, + ecs_map_key_t key) { - ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_assert(str != NULL, ECS_INVALID_PARAMETER, NULL); - return appendstr(b, str, ecs_os_strlen(str)); + void *result = _ecs_map_get(map, elem_size, key); + if (!result) { + result = _ecs_map_set(map, elem_size, key, NULL); + ecs_assert(result != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_os_memset(result, 0, elem_size); + } + + return result; } -bool ecs_strbuf_mergebuff( - ecs_strbuf_t *dst_buffer, - ecs_strbuf_t *src_buffer) +void* _ecs_map_set( + ecs_map_t *map, + ecs_size_t elem_size, + ecs_map_key_t key, + const void *payload) { - if (src_buffer->elementCount) { - if (src_buffer->buf) { - return ecs_strbuf_appendstr(dst_buffer, src_buffer->buf); - } else { - ecs_strbuf_element *e = (ecs_strbuf_element*)&src_buffer->firstElement; + ecs_assert(map != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(elem_size == map->elem_size, ECS_INVALID_PARAMETER, NULL); - /* Copy first element as it is inlined in the src buffer */ - ecs_strbuf_appendstrn(dst_buffer, e->buf, e->pos); + ecs_bucket_t *bucket = ensure_bucket(map, key); + ecs_assert(bucket != NULL, ECS_INTERNAL_ERROR, NULL); - while ((e = e->next)) { - dst_buffer->current->next = ecs_os_malloc(sizeof(ecs_strbuf_element)); - *dst_buffer->current->next = *e; - } - } + void *elem = get_from_bucket(bucket, key, elem_size); + if (!elem) { + int32_t index = add_to_bucket(bucket, elem_size, key, payload); + + int32_t map_count = ++map->count; + int32_t target_bucket_count = get_bucket_count(map_count); + int32_t map_bucket_count = map->bucket_count; - *src_buffer = ECS_STRBUF_INIT; + if (target_bucket_count > map_bucket_count) { + rehash(map, target_bucket_count); + bucket = ensure_bucket(map, key); + return get_from_bucket(bucket, key, elem_size); + } else { + return GET_ELEM(bucket->payload, elem_size, index); + } + } else { + if (payload) { + ecs_os_memcpy(elem, payload, elem_size); + } + return elem; } - - return true; } -char* ecs_strbuf_get( - ecs_strbuf_t *b) +int32_t ecs_map_remove( + ecs_map_t *map, + ecs_map_key_t key) { - ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); - - char* result = NULL; - if (b->elementCount) { - if (b->buf) { - b->buf[b->current->pos] = '\0'; - result = ecs_os_strdup(b->buf); - } else { - void *next = NULL; - int32_t len = b->size + b->current->pos + 1; - - ecs_strbuf_element *e = (ecs_strbuf_element*)&b->firstElement; - - result = ecs_os_malloc(len); - char* ptr = result; + ecs_assert(map != NULL, ECS_INVALID_PARAMETER, NULL); - do { - ecs_os_memcpy(ptr, e->buf, e->pos); - ptr += e->pos; - next = e->next; - if (e != &b->firstElement.super) { - if (!e->buffer_embedded) { - ecs_os_free(((ecs_strbuf_element_str*)e)->alloc_str); - } - ecs_os_free(e); - } - } while ((e = next)); + ecs_bucket_t * bucket = get_bucket(map, key); + if (!bucket) { + return map->count; + } - result[len - 1] = '\0'; - b->length = len; + int32_t i, bucket_count = bucket->count; + for (i = 0; i < bucket_count; i ++) { + if (bucket->keys[i] == key) { + remove_from_bucket(bucket, map->elem_size, key, i); + return --map->count; } - } else { - result = NULL; } - b->elementCount = 0; + return map->count; +} - b->content = result; +int32_t ecs_map_count( + const ecs_map_t *map) +{ + return map ? map->count : 0; +} - return result; +int32_t ecs_map_bucket_count( + const ecs_map_t *map) +{ + return map ? map->bucket_count : 0; } -char *ecs_strbuf_get_small( - ecs_strbuf_t *b) +void ecs_map_clear( + ecs_map_t *map) { - ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(map != NULL, ECS_INVALID_PARAMETER, NULL); + clear_buckets(map); + map->count = 0; +} - int32_t written = ecs_strbuf_written(b); - ecs_assert(written <= ECS_STRBUF_ELEMENT_SIZE, ECS_INVALID_OPERATION, NULL); - char *buf = b->firstElement.buf; - buf[written] = '\0'; - return buf; +ecs_map_iter_t ecs_map_iter( + const ecs_map_t *map) +{ + return (ecs_map_iter_t){ + .map = map, + .bucket = NULL, + .bucket_index = 0, + .element_index = 0 + }; } -void ecs_strbuf_reset( - ecs_strbuf_t *b) +void* _ecs_map_next( + ecs_map_iter_t *iter, + ecs_size_t elem_size, + ecs_map_key_t *key_out) { - ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); + const ecs_map_t *map = iter->map; + if (!map) { + return NULL; + } + + ecs_assert(!elem_size || elem_size == map->elem_size, ECS_INVALID_PARAMETER, NULL); + + ecs_bucket_t *bucket = iter->bucket; + int32_t element_index = iter->element_index; + elem_size = map->elem_size; - if (b->elementCount && !b->buf) { - void *next = NULL; - ecs_strbuf_element *e = (ecs_strbuf_element*)&b->firstElement; - do { - next = e->next; - if (e != (ecs_strbuf_element*)&b->firstElement) { - ecs_os_free(e); + do { + if (!bucket) { + int32_t bucket_index = iter->bucket_index; + ecs_bucket_t *buckets = map->buckets; + if (bucket_index < map->bucket_count) { + bucket = &buckets[bucket_index]; + iter->bucket = bucket; + + element_index = 0; + iter->element_index = 0; + } else { + return NULL; } - } while ((e = next)); + } + + if (element_index < bucket->count) { + iter->element_index = element_index + 1; + break; + } else { + bucket = NULL; + iter->bucket_index ++; + } + } while (true); + + if (key_out) { + *key_out = bucket->keys[element_index]; } - *b = ECS_STRBUF_INIT; + return GET_ELEM(bucket->payload, elem_size, element_index); } -void ecs_strbuf_list_push( - ecs_strbuf_t *b, - const char *list_open, - const char *separator) +void* _ecs_map_next_ptr( + ecs_map_iter_t *iter, + ecs_map_key_t *key_out) { - ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_assert(list_open != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_assert(separator != NULL, ECS_INVALID_PARAMETER, NULL); - - b->list_sp ++; - b->list_stack[b->list_sp].count = 0; - b->list_stack[b->list_sp].separator = separator; - - if (list_open) { - ecs_strbuf_appendstr(b, list_open); + void *result = _ecs_map_next(iter, ECS_SIZEOF(void*), key_out); + if (result) { + return *(void**)result; + } else { + return NULL; } } -void ecs_strbuf_list_pop( - ecs_strbuf_t *b, - const char *list_close) +void ecs_map_grow( + ecs_map_t *map, + int32_t element_count) { - ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_assert(list_close != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(map != NULL, ECS_INVALID_PARAMETER, NULL); + int32_t target_count = map->count + element_count; + int32_t bucket_count = get_bucket_count(target_count); - b->list_sp --; - - if (list_close) { - ecs_strbuf_appendstr(b, list_close); + if (bucket_count > map->bucket_count) { + rehash(map, bucket_count); } } -void ecs_strbuf_list_next( - ecs_strbuf_t *b) -{ - ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); +void ecs_map_set_size( + ecs_map_t *map, + int32_t element_count) +{ + ecs_assert(map != NULL, ECS_INVALID_PARAMETER, NULL); + int32_t bucket_count = get_bucket_count(element_count); - int32_t list_sp = b->list_sp; - if (b->list_stack[list_sp].count != 0) { - ecs_strbuf_appendstr(b, b->list_stack[list_sp].separator); + if (bucket_count) { + rehash(map, bucket_count); } - b->list_stack[list_sp].count ++; } -bool ecs_strbuf_list_append( - ecs_strbuf_t *b, - const char *fmt, - ...) +ecs_map_t* ecs_map_copy( + ecs_map_t *map) { - ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_assert(fmt != NULL, ECS_INVALID_PARAMETER, NULL); + if (!map) { + return NULL; + } - ecs_strbuf_list_next(b); + ecs_size_t elem_size = map->elem_size; + ecs_map_t *result = _ecs_map_new(map->elem_size, ecs_map_count(map)); - va_list args; - va_start(args, fmt); - bool result = vappend(b, fmt, args); - va_end(args); + ecs_map_iter_t it = ecs_map_iter(map); + ecs_map_key_t key; + void *ptr; + while ((ptr = _ecs_map_next(&it, elem_size, &key))) { + _ecs_map_set(result, elem_size, key, ptr); + } return result; } -bool ecs_strbuf_list_appendstr( - ecs_strbuf_t *b, - const char *str) +void ecs_map_memory( + ecs_map_t *map, + int32_t *allocd, + int32_t *used) { - ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_assert(str != NULL, ECS_INVALID_PARAMETER, NULL); - - ecs_strbuf_list_next(b); - return ecs_strbuf_appendstr(b, str); -} + ecs_assert(map != NULL, ECS_INVALID_PARAMETER, NULL); -int32_t ecs_strbuf_written( - const ecs_strbuf_t *b) -{ - ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); - return b->size + b->current->pos; -} + if (used) { + *used = map->count * map->elem_size; + } + if (allocd) { + *allocd += ECS_SIZEOF(ecs_map_t); -/* The ratio used to determine whether the map should rehash. If - * (element_count * LOAD_FACTOR) > bucket_count, bucket count is increased. */ -#define LOAD_FACTOR (1.5f) -#define KEY_SIZE (ECS_SIZEOF(ecs_map_key_t)) -#define GET_ELEM(array, elem_size, index) \ - ECS_OFFSET(array, (elem_size) * (index)) + int i, bucket_count = map->bucket_count; + for (i = 0; i < bucket_count; i ++) { + ecs_bucket_t *bucket = &map->buckets[i]; + *allocd += KEY_SIZE * bucket->count; + *allocd += map->elem_size * bucket->count; + } -typedef struct ecs_bucket_t { - ecs_map_key_t *keys; /* Array with keys */ - void *payload; /* Payload array */ - int32_t count; /* Number of elements in bucket */ -} ecs_bucket_t; + *allocd += ECS_SIZEOF(ecs_bucket_t) * bucket_count; + } +} -struct ecs_map_t { - ecs_bucket_t *buckets; - int32_t elem_size; - int32_t bucket_count; - int32_t count; -}; -/* Get bucket count for number of elements */ static -int32_t get_bucket_count( - int32_t element_count) +void ensure( + ecs_bitset_t *bs, + ecs_size_t size) { - return flecs_next_pow_of_2((int32_t)((float)element_count * LOAD_FACTOR)); + if (!bs->size) { + int32_t new_size = ((size - 1) / 64 + 1) * ECS_SIZEOF(uint64_t); + bs->size = ((size - 1) / 64 + 1) * 64; + bs->data = ecs_os_calloc(new_size); + } else if (size > bs->size) { + int32_t prev_size = ((bs->size - 1) / 64 + 1) * ECS_SIZEOF(uint64_t); + bs->size = ((size - 1) / 64 + 1) * 64; + int32_t new_size = ((size - 1) / 64 + 1) * ECS_SIZEOF(uint64_t); + bs->data = ecs_os_realloc(bs->data, new_size); + ecs_os_memset(ECS_OFFSET(bs->data, prev_size), 0, new_size - prev_size); + } } -/* Get bucket index for provided map key */ -static -int32_t get_bucket_id( - int32_t bucket_count, - ecs_map_key_t key) +void flecs_bitset_init( + ecs_bitset_t* bs) { - ecs_assert(bucket_count > 0, ECS_INTERNAL_ERROR, NULL); - int32_t result = (int32_t)(key & ((uint64_t)bucket_count - 1)); - ecs_assert(result < INT32_MAX, ECS_INTERNAL_ERROR, NULL); - return result; + bs->size = 0; + bs->count = 0; + bs->data = NULL; } -/* Get bucket for key */ -static -ecs_bucket_t* get_bucket( - const ecs_map_t *map, - ecs_map_key_t key) +void flecs_bitset_ensure( + ecs_bitset_t *bs, + int32_t count) { - int32_t bucket_count = map->bucket_count; - if (!bucket_count) { - return NULL; + if (count > bs->count) { + bs->count = count; + ensure(bs, count); } - - int32_t bucket_id = get_bucket_id(bucket_count, key); - ecs_assert(bucket_id < bucket_count, ECS_INTERNAL_ERROR, NULL); - - return &map->buckets[bucket_id]; } -/* Ensure that map has at least new_count buckets */ -static -void ensure_buckets( - ecs_map_t *map, - int32_t new_count) +void flecs_bitset_deinit( + ecs_bitset_t *bs) { - int32_t bucket_count = map->bucket_count; - new_count = flecs_next_pow_of_2(new_count); - if (new_count && new_count > bucket_count) { - map->buckets = ecs_os_realloc(map->buckets, new_count * ECS_SIZEOF(ecs_bucket_t)); - map->bucket_count = new_count; - - ecs_os_memset( - ECS_OFFSET(map->buckets, bucket_count * ECS_SIZEOF(ecs_bucket_t)), - 0, (new_count - bucket_count) * ECS_SIZEOF(ecs_bucket_t)); - } + ecs_os_free(bs->data); } -/* Free contents of bucket */ -static -void clear_bucket( - ecs_bucket_t *bucket) -{ - ecs_os_free(bucket->keys); - ecs_os_free(bucket->payload); - bucket->keys = NULL; - bucket->payload = NULL; - bucket->count = 0; -} - -/* Clear all buckets */ -static -void clear_buckets( - ecs_map_t *map) -{ - ecs_bucket_t *buckets = map->buckets; - int32_t i, count = map->bucket_count; - for (i = 0; i < count; i ++) { - clear_bucket(&buckets[i]); - } - ecs_os_free(buckets); - map->buckets = NULL; - map->bucket_count = 0; -} - -/* Find or create bucket for specified key */ -static -ecs_bucket_t* ensure_bucket( - ecs_map_t *map, - ecs_map_key_t key) -{ - if (!map->bucket_count) { - ensure_buckets(map, 2); - } - - int32_t bucket_id = get_bucket_id(map->bucket_count, key); - ecs_assert(bucket_id >= 0, ECS_INTERNAL_ERROR, NULL); - return &map->buckets[bucket_id]; -} - -/* Add element to bucket */ -static -int32_t add_to_bucket( - ecs_bucket_t *bucket, - ecs_size_t elem_size, - ecs_map_key_t key, - const void *payload) -{ - int32_t index = bucket->count ++; - int32_t bucket_count = index + 1; - - bucket->keys = ecs_os_realloc(bucket->keys, KEY_SIZE * bucket_count); - bucket->payload = ecs_os_realloc(bucket->payload, elem_size * bucket_count); - bucket->keys[index] = key; - - if (payload) { - void *elem = GET_ELEM(bucket->payload, elem_size, index); - ecs_os_memcpy(elem, payload, elem_size); - } - - return index; -} - -/* Remove element from bucket */ -static -void remove_from_bucket( - ecs_bucket_t *bucket, - ecs_size_t elem_size, - ecs_map_key_t key, - int32_t index) -{ - (void)key; - - ecs_assert(bucket->count != 0, ECS_INTERNAL_ERROR, NULL); - ecs_assert(index < bucket->count, ECS_INTERNAL_ERROR, NULL); - - int32_t bucket_count = -- bucket->count; - - if (index != bucket->count) { - ecs_assert(key == bucket->keys[index], ECS_INTERNAL_ERROR, NULL); - bucket->keys[index] = bucket->keys[bucket_count]; - - ecs_map_key_t *elem = GET_ELEM(bucket->payload, elem_size, index); - ecs_map_key_t *last_elem = GET_ELEM(bucket->payload, elem_size, bucket->count); - - ecs_os_memcpy(elem, last_elem, elem_size); - } -} - -/* Get payload pointer for key from bucket */ -static -void* get_from_bucket( - ecs_bucket_t *bucket, - ecs_map_key_t key, - ecs_size_t elem_size) -{ - ecs_map_key_t *keys = bucket->keys; - int32_t i, count = bucket->count; - - for (i = 0; i < count; i ++) { - if (keys[i] == key) { - return GET_ELEM(bucket->payload, elem_size, i); - } - } - return NULL; -} - -/* Grow number of buckets */ -static -void rehash( - ecs_map_t *map, - int32_t bucket_count) -{ - ecs_assert(bucket_count != 0, ECS_INTERNAL_ERROR, NULL); - ecs_assert(bucket_count > map->bucket_count, ECS_INTERNAL_ERROR, NULL); - - ecs_size_t elem_size = map->elem_size; - - ensure_buckets(map, bucket_count); - - ecs_bucket_t *buckets = map->buckets; - ecs_assert(buckets != NULL, ECS_INTERNAL_ERROR, NULL); - - int32_t bucket_id; - - /* Iterate backwards as elements could otherwise be moved to existing - * buckets which could temporarily cause the number of elements in a - * bucket to exceed BUCKET_COUNT. */ - for (bucket_id = bucket_count - 1; bucket_id >= 0; bucket_id --) { - ecs_bucket_t *bucket = &buckets[bucket_id]; - - int i, count = bucket->count; - ecs_map_key_t *key_array = bucket->keys; - void *payload_array = bucket->payload; - - for (i = 0; i < count; i ++) { - ecs_map_key_t key = key_array[i]; - void *elem = GET_ELEM(payload_array, elem_size, i); - int32_t new_bucket_id = get_bucket_id(bucket_count, key); - - if (new_bucket_id != bucket_id) { - ecs_bucket_t *new_bucket = &buckets[new_bucket_id]; - - add_to_bucket(new_bucket, elem_size, key, elem); - remove_from_bucket(bucket, elem_size, key, i); - - count --; - i --; - } - } - - if (!bucket->count) { - clear_bucket(bucket); - } - } -} - -ecs_map_t* _ecs_map_new( - ecs_size_t elem_size, - int32_t element_count) -{ - ecs_map_t *result = ecs_os_calloc(ECS_SIZEOF(ecs_map_t) * 1); - ecs_assert(result != NULL, ECS_OUT_OF_MEMORY, NULL); - - int32_t bucket_count = get_bucket_count(element_count); - - result->count = 0; - result->elem_size = elem_size; - - ensure_buckets(result, bucket_count); - - return result; -} - -void ecs_map_free( - ecs_map_t *map) -{ - if (map) { - clear_buckets(map); - ecs_os_free(map); - } -} - -void* _ecs_map_get( - const ecs_map_t *map, - ecs_size_t elem_size, - ecs_map_key_t key) -{ - (void)elem_size; - - if (!map) { - return NULL; - } - - ecs_assert(elem_size == map->elem_size, ECS_INVALID_PARAMETER, NULL); - - ecs_bucket_t * bucket = get_bucket(map, key); - if (!bucket) { - return NULL; - } - - return get_from_bucket(bucket, key, elem_size); -} - -void* _ecs_map_get_ptr( - const ecs_map_t *map, - ecs_map_key_t key) -{ - void* ptr_ptr = _ecs_map_get(map, ECS_SIZEOF(void*), key); - - if (ptr_ptr) { - return *(void**)ptr_ptr; - } else { - return NULL; - } -} - -bool ecs_map_has( - const ecs_map_t *map, - ecs_map_key_t key) -{ - if (!map) { - return false; - } - - ecs_bucket_t * bucket = get_bucket(map, key); - if (!bucket) { - return false; - } - - return get_from_bucket(bucket, key, 0) != NULL; -} - -void* _ecs_map_ensure( - ecs_map_t *map, - ecs_size_t elem_size, - ecs_map_key_t key) -{ - void *result = _ecs_map_get(map, elem_size, key); - if (!result) { - result = _ecs_map_set(map, elem_size, key, NULL); - ecs_assert(result != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_os_memset(result, 0, elem_size); - } - - return result; -} - -void* _ecs_map_set( - ecs_map_t *map, - ecs_size_t elem_size, - ecs_map_key_t key, - const void *payload) -{ - ecs_assert(map != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_assert(elem_size == map->elem_size, ECS_INVALID_PARAMETER, NULL); - - ecs_bucket_t *bucket = ensure_bucket(map, key); - ecs_assert(bucket != NULL, ECS_INTERNAL_ERROR, NULL); - - void *elem = get_from_bucket(bucket, key, elem_size); - if (!elem) { - int32_t index = add_to_bucket(bucket, elem_size, key, payload); - - int32_t map_count = ++map->count; - int32_t target_bucket_count = get_bucket_count(map_count); - int32_t map_bucket_count = map->bucket_count; - - if (target_bucket_count > map_bucket_count) { - rehash(map, target_bucket_count); - bucket = ensure_bucket(map, key); - return get_from_bucket(bucket, key, elem_size); - } else { - return GET_ELEM(bucket->payload, elem_size, index); - } - } else { - if (payload) { - ecs_os_memcpy(elem, payload, elem_size); - } - return elem; - } -} - -int32_t ecs_map_remove( - ecs_map_t *map, - ecs_map_key_t key) -{ - ecs_assert(map != NULL, ECS_INVALID_PARAMETER, NULL); - - ecs_bucket_t * bucket = get_bucket(map, key); - if (!bucket) { - return map->count; - } - - int32_t i, bucket_count = bucket->count; - for (i = 0; i < bucket_count; i ++) { - if (bucket->keys[i] == key) { - remove_from_bucket(bucket, map->elem_size, key, i); - return --map->count; - } - } - - return map->count; -} - -int32_t ecs_map_count( - const ecs_map_t *map) -{ - return map ? map->count : 0; -} - -int32_t ecs_map_bucket_count( - const ecs_map_t *map) -{ - return map ? map->bucket_count : 0; -} - -void ecs_map_clear( - ecs_map_t *map) -{ - ecs_assert(map != NULL, ECS_INVALID_PARAMETER, NULL); - clear_buckets(map); - map->count = 0; -} - -ecs_map_iter_t ecs_map_iter( - const ecs_map_t *map) +void flecs_bitset_addn( + ecs_bitset_t *bs, + int32_t count) { - return (ecs_map_iter_t){ - .map = map, - .bucket = NULL, - .bucket_index = 0, - .element_index = 0 - }; + int32_t elem = bs->count += count; + ensure(bs, elem); } -void* _ecs_map_next( - ecs_map_iter_t *iter, - ecs_size_t elem_size, - ecs_map_key_t *key_out) +void flecs_bitset_set( + ecs_bitset_t *bs, + int32_t elem, + bool value) { - const ecs_map_t *map = iter->map; - if (!map) { - return NULL; - } - - ecs_assert(!elem_size || elem_size == map->elem_size, ECS_INVALID_PARAMETER, NULL); - - ecs_bucket_t *bucket = iter->bucket; - int32_t element_index = iter->element_index; - elem_size = map->elem_size; - - do { - if (!bucket) { - int32_t bucket_index = iter->bucket_index; - ecs_bucket_t *buckets = map->buckets; - if (bucket_index < map->bucket_count) { - bucket = &buckets[bucket_index]; - iter->bucket = bucket; - - element_index = 0; - iter->element_index = 0; - } else { - return NULL; - } - } - - if (element_index < bucket->count) { - iter->element_index = element_index + 1; - break; - } else { - bucket = NULL; - iter->bucket_index ++; - } - } while (true); - - if (key_out) { - *key_out = bucket->keys[element_index]; - } - - return GET_ELEM(bucket->payload, elem_size, element_index); + ecs_check(elem < bs->count, ECS_INVALID_PARAMETER, NULL); + int32_t hi = elem >> 6; + int32_t lo = elem & 0x3F; + uint64_t v = bs->data[hi]; + bs->data[hi] = (v & ~((uint64_t)1 << lo)) | ((uint64_t)value << lo); +error: + return; } -void* _ecs_map_next_ptr( - ecs_map_iter_t *iter, - ecs_map_key_t *key_out) +bool flecs_bitset_get( + const ecs_bitset_t *bs, + int32_t elem) { - void *result = _ecs_map_next(iter, ECS_SIZEOF(void*), key_out); - if (result) { - return *(void**)result; - } else { - return NULL; - } + ecs_check(elem < bs->count, ECS_INVALID_PARAMETER, NULL); + return !!(bs->data[elem >> 6] & ((uint64_t)1 << ((uint64_t)elem & 0x3F))); +error: + return false; } -void ecs_map_grow( - ecs_map_t *map, - int32_t element_count) +int32_t flecs_bitset_count( + const ecs_bitset_t *bs) { - ecs_assert(map != NULL, ECS_INVALID_PARAMETER, NULL); - int32_t target_count = map->count + element_count; - int32_t bucket_count = get_bucket_count(target_count); - - if (bucket_count > map->bucket_count) { - rehash(map, bucket_count); - } -} - -void ecs_map_set_size( - ecs_map_t *map, - int32_t element_count) -{ - ecs_assert(map != NULL, ECS_INVALID_PARAMETER, NULL); - int32_t bucket_count = get_bucket_count(element_count); - - if (bucket_count) { - rehash(map, bucket_count); - } + return bs->count; } -ecs_map_t* ecs_map_copy( - ecs_map_t *map) +void flecs_bitset_remove( + ecs_bitset_t *bs, + int32_t elem) { - if (!map) { - return NULL; - } - - ecs_size_t elem_size = map->elem_size; - ecs_map_t *result = _ecs_map_new(map->elem_size, ecs_map_count(map)); - - ecs_map_iter_t it = ecs_map_iter(map); - ecs_map_key_t key; - void *ptr; - while ((ptr = _ecs_map_next(&it, elem_size, &key))) { - _ecs_map_set(result, elem_size, key, ptr); - } - - return result; + ecs_check(elem < bs->count, ECS_INVALID_PARAMETER, NULL); + int32_t last = bs->count - 1; + bool last_value = flecs_bitset_get(bs, last); + flecs_bitset_set(bs, elem, last_value); + bs->count --; +error: + return; } -void ecs_map_memory( - ecs_map_t *map, - int32_t *allocd, - int32_t *used) +void flecs_bitset_swap( + ecs_bitset_t *bs, + int32_t elem_a, + int32_t elem_b) { - ecs_assert(map != NULL, ECS_INVALID_PARAMETER, NULL); - - if (used) { - *used = map->count * map->elem_size; - } - - if (allocd) { - *allocd += ECS_SIZEOF(ecs_map_t); - - int i, bucket_count = map->bucket_count; - for (i = 0; i < bucket_count; i ++) { - ecs_bucket_t *bucket = &map->buckets[i]; - *allocd += KEY_SIZE * bucket->count; - *allocd += map->elem_size * bucket->count; - } + ecs_check(elem_a < bs->count, ECS_INVALID_PARAMETER, NULL); + ecs_check(elem_b < bs->count, ECS_INVALID_PARAMETER, NULL); - *allocd += ECS_SIZEOF(ecs_bucket_t) * bucket_count; - } + bool a = flecs_bitset_get(bs, elem_a); + bool b = flecs_bitset_get(bs, elem_b); + flecs_bitset_set(bs, elem_a, b); + flecs_bitset_set(bs, elem_b, a); +error: + return; } - typedef struct ecs_hm_bucket_t { ecs_vector_t *keys; ecs_vector_t *values; @@ -12456,16806 +11430,17868 @@ void* _flecs_hashmap_next( return ecs_vector_get_t(bucket->values, value_size, 8, index); } +/* Roles */ +const ecs_id_t ECS_CASE = (ECS_ROLE | (0x7Cull << 56)); +const ecs_id_t ECS_SWITCH = (ECS_ROLE | (0x7Bull << 56)); +const ecs_id_t ECS_PAIR = (ECS_ROLE | (0x7Aull << 56)); +const ecs_id_t ECS_OVERRIDE = (ECS_ROLE | (0x75ull << 56)); +const ecs_id_t ECS_DISABLED = (ECS_ROLE | (0x74ull << 56)); -#ifdef FLECS_LOG +/** Builtin component ids */ +const ecs_entity_t ecs_id(EcsComponent) = 1; +const ecs_entity_t ecs_id(EcsComponentLifecycle) = 2; +const ecs_entity_t ecs_id(EcsType) = 3; +const ecs_entity_t ecs_id(EcsIdentifier) = 4; +const ecs_entity_t ecs_id(EcsTrigger) = 5; +const ecs_entity_t ecs_id(EcsQuery) = 6; +const ecs_entity_t ecs_id(EcsObserver) = 7; +const ecs_entity_t ecs_id(EcsIterable) = 8; -static -char *ecs_vasprintf( - const char *fmt, - va_list args) -{ - ecs_size_t size = 0; - char *result = NULL; - va_list tmpa; - - va_copy(tmpa, args); - - size = vsnprintf(result, 0, fmt, tmpa); - - va_end(tmpa); - - if ((int32_t)size < 0) { - return NULL; - } - - result = (char *) ecs_os_malloc(size + 1); - - if (!result) { - return NULL; - } - - ecs_os_vsprintf(result, fmt, args); +/* System module component ids */ +const ecs_entity_t ecs_id(EcsSystem) = 10; +const ecs_entity_t ecs_id(EcsTickSource) = 11; - return result; -} +/** Pipeline module component ids */ +const ecs_entity_t ecs_id(EcsPipelineQuery) = 12; -static -void ecs_colorize_buf( - char *msg, - bool enable_colors, - ecs_strbuf_t *buf) -{ - char *ptr, ch, prev = '\0'; - bool isNum = false; - char isStr = '\0'; - bool isVar = false; - bool overrideColor = false; - bool autoColor = true; - bool dontAppend = false; +/** Timer module component ids */ +const ecs_entity_t ecs_id(EcsTimer) = 13; +const ecs_entity_t ecs_id(EcsRateFilter) = 14; - for (ptr = msg; (ch = *ptr); ptr++) { - dontAppend = false; +/** Meta module component ids */ +const ecs_entity_t ecs_id(EcsMetaType) = 15; +const ecs_entity_t ecs_id(EcsMetaTypeSerialized) = 16; +const ecs_entity_t ecs_id(EcsPrimitive) = 17; +const ecs_entity_t ecs_id(EcsEnum) = 18; +const ecs_entity_t ecs_id(EcsBitmask) = 19; +const ecs_entity_t ecs_id(EcsMember) = 20; +const ecs_entity_t ecs_id(EcsStruct) = 21; +const ecs_entity_t ecs_id(EcsArray) = 22; +const ecs_entity_t ecs_id(EcsVector) = 23; - if (!overrideColor) { - if (isNum && !isdigit(ch) && !isalpha(ch) && (ch != '.') && (ch != '%')) { - if (enable_colors) ecs_strbuf_appendstr(buf, ECS_NORMAL); - isNum = false; - } - if (isStr && (isStr == ch) && prev != '\\') { - isStr = '\0'; - } else if (((ch == '\'') || (ch == '"')) && !isStr && - !isalpha(prev) && (prev != '\\')) - { - if (enable_colors) ecs_strbuf_appendstr(buf, ECS_CYAN); - isStr = ch; - } +/* Core scopes & entities */ +const ecs_entity_t EcsWorld = ECS_HI_COMPONENT_ID + 0; +const ecs_entity_t EcsFlecs = ECS_HI_COMPONENT_ID + 1; +const ecs_entity_t EcsFlecsCore = ECS_HI_COMPONENT_ID + 2; +const ecs_entity_t EcsModule = ECS_HI_COMPONENT_ID + 3; +const ecs_entity_t EcsPrefab = ECS_HI_COMPONENT_ID + 4; +const ecs_entity_t EcsDisabled = ECS_HI_COMPONENT_ID + 5; - if ((isdigit(ch) || (ch == '%' && isdigit(prev)) || - (ch == '-' && isdigit(ptr[1]))) && !isNum && !isStr && !isVar && - !isalpha(prev) && !isdigit(prev) && (prev != '_') && - (prev != '.')) - { - if (enable_colors) ecs_strbuf_appendstr(buf, ECS_GREEN); - isNum = true; - } +/* Relation properties */ +const ecs_entity_t EcsWildcard = ECS_HI_COMPONENT_ID + 10; +const ecs_entity_t EcsThis = ECS_HI_COMPONENT_ID + 11; +const ecs_entity_t EcsTransitive = ECS_HI_COMPONENT_ID + 12; +const ecs_entity_t EcsTransitiveSelf = ECS_HI_COMPONENT_ID + 13; +const ecs_entity_t EcsFinal = ECS_HI_COMPONENT_ID + 14; +const ecs_entity_t EcsTag = ECS_HI_COMPONENT_ID + 15; +const ecs_entity_t EcsExclusive = ECS_HI_COMPONENT_ID + 16; +const ecs_entity_t EcsAcyclic = ECS_HI_COMPONENT_ID + 17; - if (isVar && !isalpha(ch) && !isdigit(ch) && ch != '_') { - if (enable_colors) ecs_strbuf_appendstr(buf, ECS_NORMAL); - isVar = false; - } +/* Builtin relations */ +const ecs_entity_t EcsChildOf = ECS_HI_COMPONENT_ID + 25; +const ecs_entity_t EcsIsA = ECS_HI_COMPONENT_ID + 26; - if (!isStr && !isVar && ch == '$' && isalpha(ptr[1])) { - if (enable_colors) ecs_strbuf_appendstr(buf, ECS_CYAN); - isVar = true; - } - } +/* Identifier tags */ +const ecs_entity_t EcsName = ECS_HI_COMPONENT_ID + 27; +const ecs_entity_t EcsSymbol = ECS_HI_COMPONENT_ID + 28; - if (!isVar && !isStr && !isNum && ch == '#' && ptr[1] == '[') { - bool isColor = true; - overrideColor = true; +/* Events */ +const ecs_entity_t EcsOnAdd = ECS_HI_COMPONENT_ID + 30; +const ecs_entity_t EcsOnRemove = ECS_HI_COMPONENT_ID + 31; +const ecs_entity_t EcsOnSet = ECS_HI_COMPONENT_ID + 32; +const ecs_entity_t EcsUnSet = ECS_HI_COMPONENT_ID + 33; +const ecs_entity_t EcsOnDelete = ECS_HI_COMPONENT_ID + 34; +const ecs_entity_t EcsOnCreateTable = ECS_HI_COMPONENT_ID + 35; +const ecs_entity_t EcsOnDeleteTable = ECS_HI_COMPONENT_ID + 36; +const ecs_entity_t EcsOnTableEmpty = ECS_HI_COMPONENT_ID + 37; +const ecs_entity_t EcsOnTableFilled = ECS_HI_COMPONENT_ID + 38; +const ecs_entity_t EcsOnCreateTrigger = ECS_HI_COMPONENT_ID + 39; +const ecs_entity_t EcsOnDeleteTrigger = ECS_HI_COMPONENT_ID + 40; +const ecs_entity_t EcsOnDeleteObservable = ECS_HI_COMPONENT_ID + 41; +const ecs_entity_t EcsOnComponentLifecycle = ECS_HI_COMPONENT_ID + 42; +const ecs_entity_t EcsOnDeleteObject = ECS_HI_COMPONENT_ID + 43; - /* Custom colors */ - if (!ecs_os_strncmp(&ptr[2], "]", ecs_os_strlen("]"))) { - autoColor = false; - } else if (!ecs_os_strncmp(&ptr[2], "green]", ecs_os_strlen("green]"))) { - if (enable_colors) ecs_strbuf_appendstr(buf, ECS_GREEN); - } else if (!ecs_os_strncmp(&ptr[2], "red]", ecs_os_strlen("red]"))) { - if (enable_colors) ecs_strbuf_appendstr(buf, ECS_RED); - } else if (!ecs_os_strncmp(&ptr[2], "blue]", ecs_os_strlen("red]"))) { - if (enable_colors) ecs_strbuf_appendstr(buf, ECS_BLUE); - } else if (!ecs_os_strncmp(&ptr[2], "magenta]", ecs_os_strlen("magenta]"))) { - if (enable_colors) ecs_strbuf_appendstr(buf, ECS_MAGENTA); - } else if (!ecs_os_strncmp(&ptr[2], "cyan]", ecs_os_strlen("cyan]"))) { - if (enable_colors) ecs_strbuf_appendstr(buf, ECS_CYAN); - } else if (!ecs_os_strncmp(&ptr[2], "yellow]", ecs_os_strlen("yellow]"))) { - if (enable_colors) ecs_strbuf_appendstr(buf, ECS_YELLOW); - } else if (!ecs_os_strncmp(&ptr[2], "grey]", ecs_os_strlen("grey]"))) { - if (enable_colors) ecs_strbuf_appendstr(buf, ECS_GREY); - } else if (!ecs_os_strncmp(&ptr[2], "white]", ecs_os_strlen("white]"))) { - if (enable_colors) ecs_strbuf_appendstr(buf, ECS_NORMAL); - } else if (!ecs_os_strncmp(&ptr[2], "bold]", ecs_os_strlen("bold]"))) { - if (enable_colors) ecs_strbuf_appendstr(buf, ECS_BOLD); - } else if (!ecs_os_strncmp(&ptr[2], "normal]", ecs_os_strlen("normal]"))) { - if (enable_colors) ecs_strbuf_appendstr(buf, ECS_NORMAL); - } else if (!ecs_os_strncmp(&ptr[2], "reset]", ecs_os_strlen("reset]"))) { - overrideColor = false; - if (enable_colors) ecs_strbuf_appendstr(buf, ECS_NORMAL); - } else { - isColor = false; - overrideColor = false; - } +/* Actions */ +const ecs_entity_t EcsRemove = ECS_HI_COMPONENT_ID + 50; +const ecs_entity_t EcsDelete = ECS_HI_COMPONENT_ID + 51; +const ecs_entity_t EcsThrow = ECS_HI_COMPONENT_ID + 52; - if (isColor) { - ptr += 2; - while ((ch = *ptr) != ']') ptr ++; - dontAppend = true; - } - if (!autoColor) { - overrideColor = true; - } - } +/* Misc */ +const ecs_entity_t EcsDefaultChildComponent = ECS_HI_COMPONENT_ID + 55; - if (ch == '\n') { - if (isNum || isStr || isVar || overrideColor) { - if (enable_colors) ecs_strbuf_appendstr(buf, ECS_NORMAL); - overrideColor = false; - isNum = false; - isStr = false; - isVar = false; - } - } +/* Systems */ +const ecs_entity_t EcsMonitor = ECS_HI_COMPONENT_ID + 61; +const ecs_entity_t EcsInactive = ECS_HI_COMPONENT_ID + 63; +const ecs_entity_t EcsPipeline = ECS_HI_COMPONENT_ID + 64; +const ecs_entity_t EcsPreFrame = ECS_HI_COMPONENT_ID + 65; +const ecs_entity_t EcsOnLoad = ECS_HI_COMPONENT_ID + 66; +const ecs_entity_t EcsPostLoad = ECS_HI_COMPONENT_ID + 67; +const ecs_entity_t EcsPreUpdate = ECS_HI_COMPONENT_ID + 68; +const ecs_entity_t EcsOnUpdate = ECS_HI_COMPONENT_ID + 69; +const ecs_entity_t EcsOnValidate = ECS_HI_COMPONENT_ID + 70; +const ecs_entity_t EcsPostUpdate = ECS_HI_COMPONENT_ID + 71; +const ecs_entity_t EcsPreStore = ECS_HI_COMPONENT_ID + 72; +const ecs_entity_t EcsOnStore = ECS_HI_COMPONENT_ID + 73; +const ecs_entity_t EcsPostFrame = ECS_HI_COMPONENT_ID + 74; - if (!dontAppend) { - ecs_strbuf_appendstrn(buf, ptr, 1); - } +/* Meta primitive components (don't use low ids to save id space) */ +const ecs_entity_t EcsConstant = ECS_HI_COMPONENT_ID + 80; +const ecs_entity_t ecs_id(ecs_bool_t) = ECS_HI_COMPONENT_ID + 81; +const ecs_entity_t ecs_id(ecs_char_t) = ECS_HI_COMPONENT_ID + 82; +const ecs_entity_t ecs_id(ecs_byte_t) = ECS_HI_COMPONENT_ID + 83; +const ecs_entity_t ecs_id(ecs_u8_t) = ECS_HI_COMPONENT_ID + 84; +const ecs_entity_t ecs_id(ecs_u16_t) = ECS_HI_COMPONENT_ID + 85; +const ecs_entity_t ecs_id(ecs_u32_t) = ECS_HI_COMPONENT_ID + 86; +const ecs_entity_t ecs_id(ecs_u64_t) = ECS_HI_COMPONENT_ID + 87; +const ecs_entity_t ecs_id(ecs_uptr_t) = ECS_HI_COMPONENT_ID + 88; +const ecs_entity_t ecs_id(ecs_i8_t) = ECS_HI_COMPONENT_ID + 89; +const ecs_entity_t ecs_id(ecs_i16_t) = ECS_HI_COMPONENT_ID + 90; +const ecs_entity_t ecs_id(ecs_i32_t) = ECS_HI_COMPONENT_ID + 91; +const ecs_entity_t ecs_id(ecs_i64_t) = ECS_HI_COMPONENT_ID + 92; +const ecs_entity_t ecs_id(ecs_iptr_t) = ECS_HI_COMPONENT_ID + 93; +const ecs_entity_t ecs_id(ecs_f32_t) = ECS_HI_COMPONENT_ID + 94; +const ecs_entity_t ecs_id(ecs_f64_t) = ECS_HI_COMPONENT_ID + 95; +const ecs_entity_t ecs_id(ecs_string_t) = ECS_HI_COMPONENT_ID + 96; +const ecs_entity_t ecs_id(ecs_entity_t) = ECS_HI_COMPONENT_ID + 97; - if (!overrideColor) { - if (((ch == '\'') || (ch == '"')) && !isStr) { - if (enable_colors) ecs_strbuf_appendstr(buf, ECS_NORMAL); - } - } +/* Doc module components */ +const ecs_entity_t ecs_id(EcsDocDescription) =ECS_HI_COMPONENT_ID + 100; +const ecs_entity_t EcsDocBrief = ECS_HI_COMPONENT_ID + 101; +const ecs_entity_t EcsDocDetail = ECS_HI_COMPONENT_ID + 102; +const ecs_entity_t EcsDocLink = ECS_HI_COMPONENT_ID + 103; - prev = ch; - } +/* REST module components */ +const ecs_entity_t ecs_id(EcsRest) = ECS_HI_COMPONENT_ID + 105; - if (isNum || isStr || isVar || overrideColor) { - if (enable_colors) ecs_strbuf_appendstr(buf, ECS_NORMAL); - } -} +/* -- Private functions -- */ -void _ecs_logv( - int level, - const char *file, - int32_t line, - const char *fmt, - va_list args) +const ecs_stage_t* flecs_stage_from_readonly_world( + const ecs_world_t *world) { - (void)level; - (void)line; + ecs_assert(ecs_poly_is(world, ecs_world_t) || + ecs_poly_is(world, ecs_stage_t), + ECS_INTERNAL_ERROR, + NULL); - ecs_strbuf_t msg_buf = ECS_STRBUF_INIT; + if (ecs_poly_is(world, ecs_world_t)) { + return &world->stage; - if (level > ecs_os_api.log_level_) { - return; + } else if (ecs_poly_is(world, ecs_stage_t)) { + return (ecs_stage_t*)world; } - - /* Apply color. Even if we don't want color, we still need to call the - * colorize function to get rid of the color tags (e.g. #[green]) */ - char *msg_nocolor = ecs_vasprintf(fmt, args); - ecs_colorize_buf(msg_nocolor, ecs_os_api.log_with_color_, &msg_buf); - ecs_os_free(msg_nocolor); - char *msg = ecs_strbuf_get(&msg_buf); - ecs_os_api.log_(level, file, line, msg); - ecs_os_free(msg); + return NULL; } -void _ecs_log( - int level, - const char *file, - int32_t line, - const char *fmt, - ...) +ecs_stage_t *flecs_stage_from_world( + ecs_world_t **world_ptr) { - va_list args; - va_start(args, fmt); - _ecs_logv(level, file, line, fmt, args); - va_end(args); -} + ecs_world_t *world = *world_ptr; -void ecs_log_push(void) { - ecs_os_api.log_indent_ ++; -} + ecs_assert(ecs_poly_is(world, ecs_world_t) || + ecs_poly_is(world, ecs_stage_t), + ECS_INTERNAL_ERROR, + NULL); -void ecs_log_pop(void) { - ecs_os_api.log_indent_ --; + if (ecs_poly_is(world, ecs_world_t)) { + ecs_assert(!world->is_readonly, ECS_INVALID_OPERATION, NULL); + return &world->stage; + + } else if (ecs_poly_is(world, ecs_stage_t)) { + ecs_stage_t *stage = (ecs_stage_t*)world; + *world_ptr = stage->world; + return stage; + } + + return NULL; } -void _ecs_parser_errorv( - const char *name, - const char *expr, - int64_t column_arg, - const char *fmt, - va_list args) +/* Evaluate component monitor. If a monitored entity changed it will have set a + * flag in one of the world's component monitors. Queries can register + * themselves with component monitors to determine whether they need to rematch + * with tables. */ +static +void eval_component_monitor( + ecs_world_t *world) { - int32_t column = flecs_itoi32(column_arg); - - if (ecs_os_api.log_level_ >= -2) { - ecs_strbuf_t msg_buf = ECS_STRBUF_INIT; + ecs_relation_monitor_t *rm = &world->monitors; - ecs_strbuf_vappend(&msg_buf, fmt, args); + if (!rm->is_dirty) { + return; + } - if (expr) { - ecs_strbuf_appendstr(&msg_buf, "\n"); + ecs_map_iter_t it = ecs_map_iter(rm->monitor_sets); + ecs_monitor_set_t *ms; - /* Find start of line by taking column and looking for the - * last occurring newline */ - if (column != -1) { - const char *ptr = &expr[column]; - while (ptr[0] != '\n' && ptr > expr) { - ptr --; - } + while ((ms = ecs_map_next(&it, ecs_monitor_set_t, NULL))) { + if (!ms->is_dirty) { + continue; + } - if (ptr == expr) { - /* ptr is already at start of line */ - } else { - column -= (int32_t)(ptr - expr + 1); - expr = ptr + 1; + if (ms->monitors) { + ecs_map_iter_t mit = ecs_map_iter(ms->monitors); + ecs_monitor_t *m; + while ((m = ecs_map_next(&mit, ecs_monitor_t, NULL))) { + if (!m->is_dirty) { + continue; } - } - /* Strip newlines from current statement, if any */ - char *newline_ptr = strchr(expr, '\n'); - if (newline_ptr) { - /* Strip newline from expr */ - ecs_strbuf_appendstrn(&msg_buf, expr, - (int32_t)(newline_ptr - expr)); - } else { - ecs_strbuf_appendstr(&msg_buf, expr); - } - - ecs_strbuf_appendstr(&msg_buf, "\n"); + ecs_vector_each(m->queries, ecs_query_t*, q_ptr, { + flecs_query_notify(world, *q_ptr, &(ecs_query_event_t) { + .kind = EcsQueryTableRematch + }); + }); - if (column != -1) { - ecs_strbuf_append(&msg_buf, "%*s^", column, ""); + m->is_dirty = false; } } - char *msg = ecs_strbuf_get(&msg_buf); - ecs_os_err(name, 0, msg); - ecs_os_free(msg); + ms->is_dirty = false; } + + rm->is_dirty = false; } -void _ecs_parser_error( - const char *name, - const char *expr, - int64_t column, - const char *fmt, - ...) +void flecs_monitor_mark_dirty( + ecs_world_t *world, + ecs_entity_t relation, + ecs_entity_t id) { - if (ecs_os_api.log_level_ >= -2) { - va_list args; - va_start(args, fmt); - _ecs_parser_errorv(name, expr, column, fmt, args); - va_end(args); + ecs_assert(world->monitors.monitor_sets != NULL, ECS_INTERNAL_ERROR, NULL); + + /* Only flag if there are actually monitors registered, so that we + * don't waste cycles evaluating monitors if there's no interest */ + ecs_monitor_set_t *ms = ecs_map_get(world->monitors.monitor_sets, + ecs_monitor_set_t, relation); + if (ms && ms->monitors) { + ecs_monitor_t *m = ecs_map_get(ms->monitors, + ecs_monitor_t, id); + if (m) { + m->is_dirty = true; + ms->is_dirty = true; + world->monitors.is_dirty = true; + } } } -void _ecs_abort( - int32_t err, - const char *file, - int32_t line, - const char *fmt, - ...) +void flecs_monitor_register( + ecs_world_t *world, + ecs_entity_t relation, + ecs_entity_t id, + ecs_query_t *query) { - if (fmt) { - va_list args; - va_start(args, fmt); - char *msg = ecs_vasprintf(fmt, args); - va_end(args); - _ecs_fatal(file, line, "%s (%s)", msg, ecs_strerror(err)); - ecs_os_free(msg); - } else { - _ecs_fatal(file, line, "%s", ecs_strerror(err)); + ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(id != 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(query != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(world->monitors.monitor_sets != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_monitor_set_t *ms = ecs_map_ensure( + world->monitors.monitor_sets, ecs_monitor_set_t, relation); + ecs_assert(ms != NULL, ECS_INTERNAL_ERROR, NULL); + + if (!ms->monitors) { + ms->monitors = ecs_map_new(ecs_monitor_t, 1); } - ecs_os_api.log_last_error_ = err; + + ecs_monitor_t *m = ecs_map_ensure(ms->monitors, ecs_monitor_t, id); + ecs_assert(m != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_query_t **q = ecs_vector_add(&m->queries, ecs_query_t*); + *q = query; } -bool _ecs_assert( - bool condition, - int32_t err, - const char *cond_str, - const char *file, - int32_t line, - const char *fmt, - ...) +static +void monitors_init( + ecs_relation_monitor_t *rm) { - if (!condition) { - if (fmt) { - va_list args; - va_start(args, fmt); - char *msg = ecs_vasprintf(fmt, args); - va_end(args); - _ecs_fatal(file, line, "assert: %s %s (%s)", - cond_str, msg, ecs_strerror(err)); - ecs_os_free(msg); - } else { - _ecs_fatal(file, line, "assert: %s %s", - cond_str, ecs_strerror(err)); + rm->monitor_sets = ecs_map_new(ecs_monitor_t, 0); + rm->is_dirty = false; +} + +static +void monitors_fini( + ecs_relation_monitor_t *rm) +{ + ecs_map_iter_t it = ecs_map_iter(rm->monitor_sets); + ecs_monitor_set_t *ms; + + while ((ms = ecs_map_next(&it, ecs_monitor_set_t, NULL))) { + if (ms->monitors) { + ecs_map_iter_t mit = ecs_map_iter(ms->monitors); + ecs_monitor_t *m; + while ((m = ecs_map_next(&mit, ecs_monitor_t, NULL))) { + ecs_vector_free(m->queries); + } + + ecs_map_free(ms->monitors); } - ecs_os_api.log_last_error_ = err; } - return condition; + ecs_map_free(rm->monitor_sets); } -void _ecs_deprecated( - const char *file, - int32_t line, - const char *msg) +static +void init_store( + ecs_world_t *world) { - _ecs_err(file, line, "%s", msg); -} + ecs_os_memset(&world->store, 0, ECS_SIZEOF(ecs_store_t)); + + /* Initialize entity index */ + world->store.entity_index = flecs_sparse_new(ecs_record_t); + flecs_sparse_set_id_source(world->store.entity_index, &world->stats.last_id); -#define ECS_ERR_STR(code) case code: return &(#code[4]) + /* Initialize root table */ + world->store.tables = flecs_sparse_new(ecs_table_t); -const char* ecs_strerror( - int32_t error_code) -{ - switch (error_code) { - ECS_ERR_STR(ECS_INVALID_PARAMETER); - ECS_ERR_STR(ECS_NOT_A_COMPONENT); - ECS_ERR_STR(ECS_INTERNAL_ERROR); - ECS_ERR_STR(ECS_ALREADY_DEFINED); - ECS_ERR_STR(ECS_INVALID_COMPONENT_SIZE); - ECS_ERR_STR(ECS_INVALID_COMPONENT_ALIGNMENT); - ECS_ERR_STR(ECS_OUT_OF_MEMORY); - ECS_ERR_STR(ECS_OPERATION_FAILED); - ECS_ERR_STR(ECS_INVALID_CONVERSION); - ECS_ERR_STR(ECS_MODULE_UNDEFINED); - ECS_ERR_STR(ECS_COLUMN_INDEX_OUT_OF_RANGE); - ECS_ERR_STR(ECS_COLUMN_IS_NOT_SHARED); - ECS_ERR_STR(ECS_COLUMN_IS_SHARED); - ECS_ERR_STR(ECS_COLUMN_TYPE_MISMATCH); - ECS_ERR_STR(ECS_INVALID_WHILE_ITERATING); - ECS_ERR_STR(ECS_INVALID_FROM_WORKER); - ECS_ERR_STR(ECS_OUT_OF_RANGE); - ECS_ERR_STR(ECS_MISSING_OS_API); - ECS_ERR_STR(ECS_UNSUPPORTED); - ECS_ERR_STR(ECS_COLUMN_ACCESS_VIOLATION); - ECS_ERR_STR(ECS_COMPONENT_NOT_REGISTERED); - ECS_ERR_STR(ECS_INCONSISTENT_COMPONENT_ID); - ECS_ERR_STR(ECS_TYPE_INVALID_CASE); - ECS_ERR_STR(ECS_INCONSISTENT_NAME); - ECS_ERR_STR(ECS_INCONSISTENT_COMPONENT_ACTION); - ECS_ERR_STR(ECS_INVALID_OPERATION); - ECS_ERR_STR(ECS_CONSTRAINT_VIOLATED); - ECS_ERR_STR(ECS_LOCKED_STORAGE); - } + /* Initialize table map */ + world->store.table_map = flecs_table_hashmap_new(); - return "unknown error code"; + /* Initialize one root table per stage */ + flecs_init_root_table(world); } -#else +static +void clean_tables( + ecs_world_t *world) +{ + int32_t i, count = flecs_sparse_count(world->store.tables); -/* Empty bodies for when logging is disabled */ + for (i = 0; i < count; i ++) { + ecs_table_t *t = flecs_sparse_get_dense(world->store.tables, ecs_table_t, i); + flecs_table_free(world, t); + } -FLECS_API -void _ecs_log( - int32_t level, - const char *file, - int32_t line, - const char *fmt, - ...) -{ - (void)level; - (void)file; - (void)line; - (void)fmt; -} + /* Free table types separately so that if application destructors rely on + * a type it's still valid. */ + for (i = 0; i < count; i ++) { + ecs_table_t *t = flecs_sparse_get_dense(world->store.tables, ecs_table_t, i); + flecs_table_free_type(t); + } -FLECS_API -void _ecs_parser_error( - const char *name, - const char *expr, - int64_t column, - const char *fmt, - ...) -{ - (void)name; - (void)expr; - (void)column; - (void)fmt; + /* Clear the root table */ + if (count) { + flecs_table_reset(world, &world->store.root); + } } -FLECS_API -void _ecs_parser_errorv( - const char *name, - const char *expr, - int64_t column, - const char *fmt, - va_list args) -{ - (void)name; - (void)expr; - (void)column; - (void)fmt; - (void)args; +static +void fini_store(ecs_world_t *world) { + clean_tables(world); + flecs_sparse_free(world->store.tables); + flecs_table_free(world, &world->store.root); + flecs_sparse_clear(world->store.entity_index); + flecs_hashmap_free(world->store.table_map); } -FLECS_API -void _ecs_abort( - int32_t error_code, - const char *file, - int32_t line, - const char *fmt, - ...) +/* Implementation for iterable mixin */ +static +bool world_iter_next( + ecs_iter_t *it) { - (void)error_code; - (void)file; - (void)line; - (void)fmt; -} + if (it->is_valid) { + return it->is_valid = false; + } -FLECS_API -bool _ecs_assert( - bool condition, - int32_t error_code, - const char *condition_str, - const char *file, - int32_t line, - const char *fmt, - ...) -{ - (void)condition; - (void)error_code; - (void)condition_str; - (void)file; - (void)line; - (void)fmt; - return true; + ecs_world_t *world = it->real_world; + ecs_sparse_t *entity_index = world->store.entity_index; + it->entities = (ecs_entity_t*)flecs_sparse_ids(entity_index); + it->count = flecs_sparse_count(entity_index); + return it->is_valid = true; } -#endif - -int ecs_log_set_level( - int level) +static +void world_iter_init( + const ecs_world_t *world, + const ecs_poly_t *poly, + ecs_iter_t *iter, + ecs_term_t *filter) { - int prev = level; - ecs_os_api.log_level_ = level; - return prev; -} + ecs_poly_assert(poly, ecs_world_t); + (void)poly; -bool ecs_log_enable_colors( - bool enabled) -{ - bool prev = ecs_os_api.log_with_color_; - ecs_os_api.log_with_color_ = enabled; - return prev; + if (filter) { + iter[0] = ecs_term_iter(world, filter); + } else { + iter[0] = (ecs_iter_t){ + .world = (ecs_world_t*)world, + .real_world = (ecs_world_t*)ecs_get_world(world), + .next = world_iter_next + }; + } } -int ecs_log_last_error(void) -{ - int result = ecs_os_api.log_last_error_; - ecs_os_api.log_last_error_ = 0; - return result; +static +void log_addons(void) { + ecs_trace("addons included in build:"); + ecs_log_push(); + #ifdef FLECS_CPP + ecs_trace("FLECS_CPP"); + #endif + #ifdef FLECS_MODULE + ecs_trace("FLECS_MODULE"); + #endif + #ifdef FLECS_PARSER + ecs_trace("FLECS_PARSER"); + #endif + #ifdef FLECS_PLECS + ecs_trace("FLECS_PLECS"); + #endif + #ifdef FLECS_RULES + ecs_trace("FLECS_RULES"); + #endif + #ifdef FLECS_SNAPSHOT + ecs_trace("FLECS_SNAPSHOT"); + #endif + #ifdef FLECS_STATS + ecs_trace("FLECS_STATS"); + #endif + #ifdef FLECS_SYSTEM + ecs_trace("FLECS_SYSTEM"); + #endif + #ifdef FLECS_PIPELINE + ecs_trace("FLECS_PIPELINE"); + #endif + #ifdef FLECS_TIMER + ecs_trace("FLECS_TIMER"); + #endif + #ifdef FLECS_META + ecs_trace("FLECS_META"); + #endif + #ifdef FLECS_META_C + ecs_trace("FLECS_META_C"); + #endif + #ifdef FLECS_EXPR + ecs_trace("FLECS_EXPR"); + #endif + #ifdef FLECS_JSON + ecs_trace("FLECS_JSON"); + #endif + #ifdef FLECS_DOC + ecs_trace("FLECS_DOC"); + #endif + #ifdef FLECS_COREDOC + ecs_trace("FLECS_COREDOC"); + #endif + #ifdef FLECS_LOG + ecs_trace("FLECS_LOG"); + #endif + #ifdef FLECS_APP + ecs_trace("FLECS_APP"); + #endif + #ifdef FLECS_OS_API_IMPL + ecs_trace("FLECS_OS_API_IMPL"); + #endif + #ifdef FLECS_HTTP + ecs_trace("FLECS_HTTP"); + #endif + #ifdef FLECS_REST + ecs_trace("FLECS_REST"); + #endif + ecs_log_pop(); } -#ifndef FLECS_SYSTEM_PRIVATE_H -#define FLECS_SYSTEM_PRIVATE_H - -#ifdef FLECS_SYSTEM - - -typedef struct EcsSystem { - ecs_iter_action_t action; /* Callback to be invoked for matching it */ - - ecs_entity_t entity; /* Entity id of system, used for ordering */ - ecs_query_t *query; /* System query */ - ecs_system_status_action_t status_action; /* Status action */ - ecs_entity_t tick_source; /* Tick source associated with system */ - - /* Schedule parameters */ - bool multi_threaded; - bool no_staging; +/* -- Public functions -- */ - int32_t invoke_count; /* Number of times system is invoked */ - float time_spent; /* Time spent on running system */ - FLECS_FLOAT time_passed; /* Time passed since last invocation */ - int32_t last_frame; /* Last frame for which the system was considered */ +ecs_world_t *ecs_mini(void) { +#ifdef FLECS_OS_API_IMPL + ecs_set_os_api_impl(); +#endif + ecs_os_init(); - ecs_entity_t self; /* Entity associated with system */ + ecs_trace("#[bold]bootstrapping world"); + ecs_log_push(); - void *ctx; /* Userdata for system */ - void *status_ctx; /* User data for status action */ - void *binding_ctx; /* Optional language binding context */ + ecs_trace("tracing enabled, call ecs_log_set_level(-1) to disable"); - ecs_ctx_free_t ctx_free; - ecs_ctx_free_t status_ctx_free; - ecs_ctx_free_t binding_ctx_free; -} EcsSystem; + if (!ecs_os_has_heap()) { + ecs_abort(ECS_MISSING_OS_API, NULL); + } -/* Invoked when system becomes active / inactive */ -void ecs_system_activate( - ecs_world_t *world, - ecs_entity_t system, - bool activate, - const EcsSystem *system_data); + if (!ecs_os_has_threading()) { + ecs_trace("threading unavailable, to use threads set OS API first (see examples)"); + } -/* Internal function to run a system */ -ecs_entity_t ecs_run_intern( - ecs_world_t *world, - ecs_stage_t *stage, - ecs_entity_t system, - EcsSystem *system_data, - int32_t stage_current, - int32_t stage_count, - FLECS_FLOAT delta_time, - int32_t offset, - int32_t limit, - void *param); + if (!ecs_os_has_time()) { + ecs_trace("time management not available"); + } -#endif + log_addons(); +#ifndef NDEBUG + ecs_trace("debug build, rebuild with NDEBUG for improved performance"); +#else + ecs_trace("#[green]release#[reset] build"); #endif + ecs_world_t *world = ecs_os_calloc(sizeof(ecs_world_t)); + ecs_assert(world != NULL, ECS_OUT_OF_MEMORY, NULL); + ecs_poly_init(world, ecs_world_t); -#ifdef FLECS_PIPELINE -#ifndef FLECS_PIPELINE_PRIVATE_H -#define FLECS_PIPELINE_PRIVATE_H - - -/** Instruction data for pipeline. - * This type is the element type in the "ops" vector of a pipeline and contains - * information about the set of systems that need to be ran before a merge. */ -typedef struct ecs_pipeline_op_t { - int32_t count; /* Number of systems to run before merge */ - bool multi_threaded; /* Whether systems can be ran multi threaded */ - bool no_staging; /* Whether systems are staged or not */ -} ecs_pipeline_op_t; + world->self = world; + world->type_info = flecs_sparse_new(ecs_type_info_t); + world->id_index = ecs_map_new(ecs_id_record_t, 8); + flecs_observable_init(&world->observable); + world->iterable.init = world_iter_init; -typedef struct EcsPipelineQuery { - ecs_query_t *query; - ecs_query_t *build_query; - ecs_vector_t *ops; - int32_t match_count; - int32_t rebuild_count; - ecs_entity_t last_system; -} EcsPipelineQuery; + world->queries = flecs_sparse_new(ecs_query_t); + world->triggers = flecs_sparse_new(ecs_trigger_t); + world->observers = flecs_sparse_new(ecs_observer_t); + world->fini_tasks = ecs_vector_new(ecs_entity_t, 0); + world->aliases = flecs_string_hashmap_new(ecs_entity_t); + world->symbols = flecs_string_hashmap_new(ecs_entity_t); + world->type_handles = ecs_map_new(ecs_entity_t, 0); -//////////////////////////////////////////////////////////////////////////////// -//// Pipeline API -//////////////////////////////////////////////////////////////////////////////// + world->stats.time_scale = 1.0; + + monitors_init(&world->monitors); -/** Update a pipeline (internal function). - * Before running a pipeline, it must be updated. During this update phase - * all systems in the pipeline are collected, ordered and sync points are - * inserted where necessary. This operation may only be called when staging is - * disabled. - * - * Because multiple threads may run a pipeline, preparing the pipeline must - * happen synchronously, which is why this function is separate from - * ecs_run_pipeline. Not running the prepare step may cause systems to not get - * ran, or ran in the wrong order. - * - * If 0 is provided for the pipeline id, the default pipeline will be ran (this - * is either the builtin pipeline or the pipeline set with set_pipeline()). - * - * @param world The world. - * @param pipeline The pipeline to run. - * @return The number of elements in the pipeline. - */ -bool ecs_pipeline_update( - ecs_world_t *world, - ecs_entity_t pipeline, - bool start_of_frame); + if (ecs_os_has_time()) { + ecs_os_get_time(&world->world_start_time); + } -int32_t ecs_pipeline_reset_iter( - ecs_world_t *world, - const EcsPipelineQuery *pq, - ecs_iter_t *iter_out, - ecs_pipeline_op_t **op_out, - ecs_pipeline_op_t **last_op_out); + flecs_stage_init(world, &world->stage); + ecs_set_stages(world, 1); -//////////////////////////////////////////////////////////////////////////////// -//// Worker API -//////////////////////////////////////////////////////////////////////////////// + init_store(world); + ecs_trace("table store initialized"); -void ecs_worker_begin( - ecs_world_t *world); + flecs_bootstrap(world); -int32_t ecs_worker_sync( - ecs_world_t *world, - const EcsPipelineQuery *pq, - ecs_iter_t *it, - int32_t i, - ecs_pipeline_op_t **op_out, - ecs_pipeline_op_t **last_op_out); + ecs_trace("world ready!"); + ecs_log_pop(); -void ecs_worker_end( - ecs_world_t *world); + return world; +} -void ecs_workers_progress( - ecs_world_t *world, - ecs_entity_t pipeline, - FLECS_FLOAT delta_time); +ecs_world_t *ecs_init(void) { + ecs_world_t *world = ecs_mini(); +#ifdef FLECS_MODULE_H + ecs_trace("#[bold]import addons"); + ecs_log_push(); + ecs_trace("use ecs_mini to create world without importing addons"); +#ifdef FLECS_SYSTEM + ECS_IMPORT(world, FlecsSystem); #endif +#ifdef FLECS_PIPELINE + ECS_IMPORT(world, FlecsPipeline); +#endif +#ifdef FLECS_TIMER + ECS_IMPORT(world, FlecsTimer); +#endif +#ifdef FLECS_META + ECS_IMPORT(world, FlecsMeta); +#endif +#ifdef FLECS_DOC + ECS_IMPORT(world, FlecsDoc); +#endif +#ifdef FLECS_COREDOC + ECS_IMPORT(world, FlecsCoreDoc); +#endif +#ifdef FLECS_REST + ECS_IMPORT(world, FlecsRest); +#endif + ecs_trace("addons imported!"); + ecs_log_pop(); +#endif + return world; +} - -/* Worker thread */ -static -void* worker(void *arg) { - ecs_stage_t *stage = arg; - ecs_world_t *world = stage->world; - - /* Start worker thread, increase counter so main thread knows how many - * workers are ready */ - ecs_os_mutex_lock(world->sync_mutex); - world->workers_running ++; - - if (!world->quit_workers) { - ecs_os_cond_wait(world->worker_cond, world->sync_mutex); +#define ARG(short, long, action)\ + if (i < argc) {\ + if (argv[i][0] == '-') {\ + if (argv[i][1] == '-') {\ + if (long && !strcmp(&argv[i][2], long ? long : "")) {\ + action;\ + parsed = true;\ + }\ + } else {\ + if (short && argv[i][1] == short) {\ + action;\ + parsed = true;\ + }\ + }\ + }\ } - ecs_os_mutex_unlock(world->sync_mutex); - - while (!world->quit_workers) { - ecs_entity_t old_scope = ecs_set_scope((ecs_world_t*)stage, 0); +ecs_world_t* ecs_init_w_args( + int argc, + char *argv[]) +{ + ecs_world_t *world = ecs_init(); - ecs_run_pipeline( - (ecs_world_t*)stage, - world->pipeline, - world->stats.delta_time); + (void)argc; + (void) argv; - ecs_set_scope((ecs_world_t*)stage, old_scope); +#ifdef FLECS_DOC + if (argc) { + char *app = argv[0]; + char *last_elem = strrchr(app, '/'); + if (!last_elem) { + last_elem = strrchr(app, '\\'); + } + if (last_elem) { + app = last_elem + 1; + } + ecs_set_pair(world, EcsWorld, EcsDocDescription, EcsName, {app}); } +#endif - ecs_os_mutex_lock(world->sync_mutex); - world->workers_running --; - ecs_os_mutex_unlock(world->sync_mutex); - - return NULL; + return world; } -/* Start threads */ -static -void start_workers( +void ecs_quit( + ecs_world_t *world) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + flecs_stage_from_world(&world); + world->should_quit = true; +error: + return; +} + +bool ecs_should_quit( + const ecs_world_t *world) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + world = ecs_get_world(world); + return world->should_quit; +error: + return true; +} + +void flecs_notify_tables( ecs_world_t *world, - int32_t threads) + ecs_id_t id, + ecs_table_event_t *event) { - ecs_set_stages(world, threads); + ecs_poly_assert(world, ecs_world_t); - ecs_assert(ecs_get_stage_count(world) == threads, ECS_INTERNAL_ERROR, NULL); + /* If no id is specified, broadcast to all tables */ + if (!id) { + ecs_sparse_t *tables = world->store.tables; + int32_t i, count = flecs_sparse_count(tables); + for (i = 0; i < count; i ++) { + ecs_table_t *table = flecs_sparse_get_dense(tables, ecs_table_t, i); + flecs_table_notify(world, table, event); + } - int32_t i; - for (i = 0; i < threads; i ++) { - ecs_stage_t *stage = (ecs_stage_t*)ecs_get_stage(world, i); - ecs_assert(stage != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_poly_assert(stage, ecs_stage_t); + /* If id is specified, only broadcast to tables with id */ + } else { + ecs_id_record_t *idr = flecs_get_id_record(world, id); + if (!idr) { + return; + } - ecs_vector_get(world->worker_stages, ecs_stage_t, i); - stage->thread = ecs_os_thread_new(worker, stage); - ecs_assert(stage->thread != 0, ECS_OPERATION_FAILED, NULL); + const ecs_table_record_t *tables = flecs_id_record_tables(idr); + int32_t i, count = flecs_id_record_count(idr); + for (i = 0; i < count; i ++) { + flecs_table_notify(world, tables[i].table, event); + } + + tables = flecs_id_record_empty_tables(idr); + count = flecs_id_record_empty_count(idr); + for (i = 0; i < count; i ++) { + flecs_table_notify(world, tables[i].table, event); + } } } -/* Wait until all workers are running */ -static -void wait_for_workers( - ecs_world_t *world) +void ecs_default_ctor( + ecs_world_t *world, ecs_entity_t component, const ecs_entity_t *entity_ptr, + void *ptr, size_t size, int32_t count, void *ctx) { - int32_t stage_count = ecs_get_stage_count(world); - bool wait = true; - - do { - ecs_os_mutex_lock(world->sync_mutex); - if (world->workers_running == stage_count) { - wait = false; - } - ecs_os_mutex_unlock(world->sync_mutex); - } while (wait); + (void)world; (void)component; (void)entity_ptr; (void)ctx; + ecs_os_memset(ptr, 0, flecs_uto(ecs_size_t, size) * count); } -/* Synchronize worker threads */ static -void sync_worker( - ecs_world_t *world) +void default_copy_ctor( + ecs_world_t *world, ecs_entity_t component, + const EcsComponentLifecycle *callbacks, const ecs_entity_t *dst_entity, + const ecs_entity_t *src_entity, void *dst_ptr, const void *src_ptr, + size_t size, int32_t count, void *ctx) { - int32_t stage_count = ecs_get_stage_count(world); - - /* Signal that thread is waiting */ - ecs_os_mutex_lock(world->sync_mutex); - if (++ world->workers_waiting == stage_count) { - /* Only signal main thread when all threads are waiting */ - ecs_os_cond_signal(world->sync_cond); - } - - /* Wait until main thread signals that thread can continue */ - ecs_os_cond_wait(world->worker_cond, world->sync_mutex); - ecs_os_mutex_unlock(world->sync_mutex); + callbacks->ctor(world, component, dst_entity, dst_ptr, size, count, ctx); + callbacks->copy(world, component, dst_entity, src_entity, dst_ptr, src_ptr, + size, count, ctx); } -/* Wait until all threads are waiting on sync point */ static -void wait_for_sync( - ecs_world_t *world) +void default_move_ctor( + ecs_world_t *world, ecs_entity_t component, + const EcsComponentLifecycle *callbacks, const ecs_entity_t *dst_entity, + const ecs_entity_t *src_entity, void *dst_ptr, void *src_ptr, size_t size, + int32_t count, void *ctx) { - int32_t stage_count = ecs_get_stage_count(world); - - ecs_os_mutex_lock(world->sync_mutex); - if (world->workers_waiting != stage_count) { - ecs_os_cond_wait(world->sync_cond, world->sync_mutex); - } - - /* We should have been signalled unless all workers are waiting on sync */ - ecs_assert(world->workers_waiting == stage_count, - ECS_INTERNAL_ERROR, NULL); - - ecs_os_mutex_unlock(world->sync_mutex); + callbacks->ctor(world, component, dst_entity, dst_ptr, size, count, ctx); + callbacks->move(world, component, dst_entity, src_entity, dst_ptr, src_ptr, + size, count, ctx); } -/* Signal workers that they can start/resume work */ static -void signal_workers( - ecs_world_t *world) +void default_ctor_w_move_w_dtor( + ecs_world_t *world, ecs_entity_t component, + const EcsComponentLifecycle *callbacks, const ecs_entity_t *dst_entity, + const ecs_entity_t *src_entity, void *dst_ptr, void *src_ptr, size_t size, + int32_t count, void *ctx) { - ecs_os_mutex_lock(world->sync_mutex); - ecs_os_cond_broadcast(world->worker_cond); - ecs_os_mutex_unlock(world->sync_mutex); + callbacks->ctor(world, component, dst_entity, dst_ptr, size, count, ctx); + callbacks->move(world, component, dst_entity, src_entity, dst_ptr, src_ptr, + size, count, ctx); + callbacks->dtor(world, component, src_entity, src_ptr, size, count, ctx); } -/** Stop worker threads */ static -bool ecs_stop_threads( - ecs_world_t *world) +void default_move_ctor_w_dtor( + ecs_world_t *world, ecs_entity_t component, + const EcsComponentLifecycle *callbacks, const ecs_entity_t *dst_entity, + const ecs_entity_t *src_entity, void *dst_ptr, void *src_ptr, size_t size, + int32_t count, void *ctx) { - bool threads_active = false; - - /* Test if threads are created. Cannot use workers_running, since this is - * a potential race if threads haven't spun up yet. */ - ecs_vector_each(world->worker_stages, ecs_stage_t, stage, { - if (stage->thread) { - threads_active = true; - break; - } - stage->thread = 0; - }); - - /* If no threads are active, just return */ - if (!threads_active) { - return false; - } - - /* Make sure all threads are running, to ensure they catch the signal */ - wait_for_workers(world); - - /* Signal threads should quit */ - world->quit_workers = true; - signal_workers(world); - - /* Join all threads with main */ - ecs_vector_each(world->worker_stages, ecs_stage_t, stage, { - ecs_os_thread_join(stage->thread); - stage->thread = 0; - }); + callbacks->move_ctor(world, component, callbacks, dst_entity, src_entity, + dst_ptr, src_ptr, size, count, ctx); + callbacks->dtor(world, component, src_entity, src_ptr, size, count, ctx); +} - world->quit_workers = false; - ecs_assert(world->workers_running == 0, ECS_INTERNAL_ERROR, NULL); +static +void default_move( + ecs_world_t *world, ecs_entity_t component, + const EcsComponentLifecycle *callbacks, const ecs_entity_t *dst_entity, + const ecs_entity_t *src_entity, void *dst_ptr, void *src_ptr, size_t size, + int32_t count, void *ctx) +{ + callbacks->move(world, component, dst_entity, src_entity, + dst_ptr, src_ptr, size, count, ctx); +} - /* Deinitialize stages */ - ecs_set_stages(world, 0); +static +void default_dtor( + ecs_world_t *world, ecs_entity_t component, + const EcsComponentLifecycle *callbacks, const ecs_entity_t *dst_entity, + const ecs_entity_t *src_entity, void *dst_ptr, void *src_ptr, size_t size, + int32_t count, void *ctx) +{ + (void)callbacks; + (void)src_entity; - return true; + /* When there is no move, destruct the destination component & memcpy the + * component to dst. The src component does not have to be destructed when + * a component has a trivial move. */ + callbacks->dtor(world, component, dst_entity, dst_ptr, size, count, ctx); + ecs_os_memcpy(dst_ptr, src_ptr, flecs_uto(ecs_size_t, size) * count); } -/* -- Private functions -- */ - -void ecs_worker_begin( - ecs_world_t *world) +static +void default_move_w_dtor( + ecs_world_t *world, ecs_entity_t component, + const EcsComponentLifecycle *callbacks, const ecs_entity_t *dst_entity, + const ecs_entity_t *src_entity, void *dst_ptr, void *src_ptr, size_t size, + int32_t count, void *ctx) { - flecs_stage_from_world(&world); - int32_t stage_count = ecs_get_stage_count(world); - ecs_assert(stage_count != 0, ECS_INTERNAL_ERROR, NULL); - - if (stage_count == 1) { - ecs_entity_t pipeline = world->pipeline; - const EcsPipelineQuery *pq = ecs_get(world, pipeline, EcsPipelineQuery); - ecs_assert(pq != NULL, ECS_INTERNAL_ERROR, NULL); - - ecs_pipeline_op_t *op = ecs_vector_first(pq->ops, ecs_pipeline_op_t); - if (!op || !op->no_staging) { - ecs_staging_begin(world); - } - } + /* If a component has a move, the move will take care of memcpying the data + * and destroying any data in dst. Because this is not a trivial move, the + * src component must also be destructed. */ + callbacks->move(world, component, dst_entity, src_entity, + dst_ptr, src_ptr, size, count, ctx); + callbacks->dtor(world, component, src_entity, src_ptr, size, count, ctx); } -int32_t ecs_worker_sync( +void ecs_set_component_actions_w_id( ecs_world_t *world, - const EcsPipelineQuery *pq, - ecs_iter_t *it, - int32_t i, - ecs_pipeline_op_t **op_out, - ecs_pipeline_op_t **last_op_out) + ecs_entity_t component, + EcsComponentLifecycle *lifecycle) { - int32_t stage_count = ecs_get_stage_count(world); - ecs_assert(stage_count != 0, ECS_INTERNAL_ERROR, NULL); - int32_t build_count = world->stats.pipeline_build_count_total; + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + flecs_stage_from_world(&world); - /* If there are no threads, merge in place */ - if (stage_count == 1) { - if (!op_out[0]->no_staging) { - ecs_staging_end(world); - } + const EcsComponent *component_ptr = ecs_get(world, component, EcsComponent); - ecs_pipeline_update(world, world->pipeline, false); + /* Cannot register lifecycle actions for things that aren't a component */ + ecs_check(component_ptr != NULL, ECS_INVALID_PARAMETER, NULL); - /* Synchronize all workers. The last worker to reach the sync point will - * signal the main thread, which will perform the merge. */ - } else { - sync_worker(world); - } + /* Cannot register lifecycle actions for components with size 0 */ + ecs_check(component_ptr->size != 0, ECS_INVALID_PARAMETER, NULL); - if (build_count != world->stats.pipeline_build_count_total) { - i = ecs_pipeline_reset_iter(world, pq, it, op_out, last_op_out); + ecs_type_info_t *c_info = flecs_get_or_create_c_info(world, component); + ecs_assert(c_info != NULL, ECS_INTERNAL_ERROR, NULL); + + if (c_info->lifecycle_set) { + ecs_assert(c_info->component == component, ECS_INTERNAL_ERROR, NULL); + ecs_check(c_info->lifecycle.ctor == lifecycle->ctor, + ECS_INCONSISTENT_COMPONENT_ACTION, NULL); + ecs_check(c_info->lifecycle.dtor == lifecycle->dtor, + ECS_INCONSISTENT_COMPONENT_ACTION, NULL); + ecs_check(c_info->lifecycle.copy == lifecycle->copy, + ECS_INCONSISTENT_COMPONENT_ACTION, NULL); + ecs_check(c_info->lifecycle.move == lifecycle->move, + ECS_INCONSISTENT_COMPONENT_ACTION, NULL); } else { - op_out[0] ++; - } + c_info->component = component; + c_info->lifecycle = *lifecycle; + c_info->lifecycle_set = true; + c_info->size = component_ptr->size; + c_info->alignment = component_ptr->alignment; - if (stage_count == 1) { - if (!op_out[0]->no_staging) { - ecs_staging_begin(world); + /* If no constructor is set, invoking any of the other lifecycle actions + * is not safe as they will potentially access uninitialized memory. For + * ease of use, if no constructor is specified, set a default one that + * initializes the component to 0. */ + if (!lifecycle->ctor && + (lifecycle->dtor || lifecycle->copy || lifecycle->move)) + { + c_info->lifecycle.ctor = ecs_default_ctor; } - } - return i; -} + /* Set default copy ctor, move ctor and merge */ + if (lifecycle->copy && !lifecycle->copy_ctor) { + c_info->lifecycle.copy_ctor = default_copy_ctor; + } -void ecs_worker_end( - ecs_world_t *world) -{ - flecs_stage_from_world(&world); + if (lifecycle->move && !lifecycle->move_ctor) { + c_info->lifecycle.move_ctor = default_move_ctor; + } - int32_t stage_count = ecs_get_stage_count(world); - ecs_assert(stage_count != 0, ECS_INTERNAL_ERROR, NULL); + if (!lifecycle->ctor_move_dtor) { + if (lifecycle->move) { + if (lifecycle->dtor) { + if (lifecycle->move_ctor) { + /* If an explicit move ctor has been set, use callback + * that uses the move ctor vs. using a ctor+move */ + c_info->lifecycle.ctor_move_dtor = + default_move_ctor_w_dtor; + } else { + /* If no explicit move_ctor has been set, use + * combination of ctor + move + dtor */ + c_info->lifecycle.ctor_move_dtor = + default_ctor_w_move_w_dtor; + } + } else { + /* If no dtor has been set, this is just a move ctor */ + c_info->lifecycle.ctor_move_dtor = + c_info->lifecycle.move_ctor; + } + } + } - /* If there are no threads, merge in place */ - if (stage_count == 1) { - if (ecs_stage_is_readonly(world)) { - ecs_staging_end(world); + if (!lifecycle->move_dtor) { + if (lifecycle->move) { + if (lifecycle->dtor) { + c_info->lifecycle.move_dtor = default_move_w_dtor; + } else { + c_info->lifecycle.move_dtor = default_move; + } + } else { + if (lifecycle->dtor) { + c_info->lifecycle.move_dtor = default_dtor; + } + } } - /* Synchronize all workers. The last worker to reach the sync point will - * signal the main thread, which will perform the merge. */ - } else { - sync_worker(world); + /* Broadcast to all tables since we need to register a ctor for every + * table that uses the component as itself, as predicate or as object. + * The latter is what makes selecting the right set of tables complex, + * as it depends on the predicate of a pair whether the object is used + * as the component type or not. + * A more selective approach requires a more expressive notification + * framework. */ + flecs_notify_tables(world, 0, &(ecs_table_event_t) { + .kind = EcsTableComponentInfo, + .component = component + }); } +error: + return; } -void ecs_workers_progress( +bool ecs_component_has_actions( + const ecs_world_t *world, + ecs_entity_t component) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(component != 0, ECS_INVALID_PARAMETER, NULL); + + world = ecs_get_world(world); + const ecs_type_info_t *c_info = flecs_get_c_info(world, component); + return (c_info != NULL) && c_info->lifecycle_set; +error: + return false; +} + +void ecs_atfini( ecs_world_t *world, - ecs_entity_t pipeline, - FLECS_FLOAT delta_time) + ecs_fini_action_t action, + void *ctx) { ecs_poly_assert(world, ecs_world_t); - int32_t stage_count = ecs_get_stage_count(world); + ecs_check(action != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_time_t start = {0}; - if (world->measure_frame_time) { - ecs_time_measure(&start); - } + ecs_action_elem_t *elem = ecs_vector_add(&world->fini_actions, + ecs_action_elem_t); + ecs_assert(elem != NULL, ECS_INTERNAL_ERROR, NULL); - if (stage_count == 1) { - ecs_pipeline_update(world, pipeline, true); - ecs_entity_t old_scope = ecs_set_scope(world, 0); - ecs_world_t *stage = ecs_get_stage(world, 0); - ecs_run_pipeline(stage, pipeline, delta_time); - ecs_set_scope(world, old_scope); - } else { - ecs_pipeline_update(world, pipeline, true); + elem->action = action; + elem->ctx = ctx; +error: + return; +} - const EcsPipelineQuery *pq = ecs_get(world, pipeline, EcsPipelineQuery); - ecs_vector_t *ops = pq->ops; - ecs_pipeline_op_t *op = ecs_vector_first(ops, ecs_pipeline_op_t); - ecs_pipeline_op_t *op_last = ecs_vector_last(ops, ecs_pipeline_op_t); +void ecs_run_post_frame( + ecs_world_t *world, + ecs_fini_action_t action, + void *ctx) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(action != NULL, ECS_INVALID_PARAMETER, NULL); + + ecs_stage_t *stage = flecs_stage_from_world(&world); + ecs_action_elem_t *elem = ecs_vector_add(&stage->post_frame_actions, + ecs_action_elem_t); + ecs_assert(elem != NULL, ECS_INTERNAL_ERROR, NULL); - /* Make sure workers are running and ready */ - wait_for_workers(world); + elem->action = action; + elem->ctx = ctx; +error: + return; +} - /* Synchronize n times for each op in the pipeline */ - for (; op <= op_last; op ++) { - if (!op->no_staging) { - ecs_staging_begin(world); - } +/* Unset data in tables */ +static +void fini_unset_tables( + ecs_world_t *world) +{ + ecs_sparse_t *tables = world->store.tables; + int32_t i, count = flecs_sparse_count(tables); - /* Signal workers that they should start running systems */ - world->workers_waiting = 0; - signal_workers(world); + for (i = 0; i < count; i ++) { + ecs_table_t *table = flecs_sparse_get_dense(tables, ecs_table_t, i); + flecs_table_remove_actions(world, table); + } +} - /* Wait until all workers are waiting on sync point */ - wait_for_sync(world); +/* Invoke fini actions */ +static +void fini_actions( + ecs_world_t *world) +{ + ecs_vector_each(world->fini_actions, ecs_action_elem_t, elem, { + elem->action(world, elem->ctx); + }); - /* Merge */ - if (!op->no_staging) { - ecs_staging_end(world); - } + ecs_vector_free(world->fini_actions); +} - if (ecs_pipeline_update(world, pipeline, false)) { - /* Refetch, in case pipeline itself has moved */ - pq = ecs_get(world, pipeline, EcsPipelineQuery); +/* Cleanup component lifecycle callbacks & systems */ +static +void fini_component_lifecycle( + ecs_world_t *world) +{ + flecs_sparse_free(world->type_info); +} - /* Pipeline has changed, reset position in pipeline */ - ecs_iter_t it; - ecs_pipeline_reset_iter(world, pq, &it, &op, &op_last); - op --; - } - } +/* Cleanup queries */ +static +void fini_queries( + ecs_world_t *world) +{ + int32_t i, count = flecs_sparse_count(world->queries); + for (i = 0; i < count; i ++) { + ecs_query_t *query = flecs_sparse_get_dense(world->queries, ecs_query_t, 0); + ecs_query_fini(query); } - - if (world->measure_frame_time) { - world->stats.system_time_total += (float)ecs_time_measure(&start); - } + flecs_sparse_free(world->queries); } -/* -- Public functions -- */ - -void ecs_set_threads( - ecs_world_t *world, - int32_t threads) +static +void fini_observers( + ecs_world_t *world) { - ecs_assert(threads <= 1 || ecs_os_has_threading(), ECS_MISSING_OS_API, NULL); - - int32_t stage_count = ecs_get_stage_count(world); - - if (stage_count != threads) { - /* Stop existing threads */ - if (stage_count > 1) { - if (ecs_stop_threads(world)) { - ecs_os_cond_free(world->worker_cond); - ecs_os_cond_free(world->sync_cond); - ecs_os_mutex_free(world->sync_mutex); - } - } - - /* Start threads if number of threads > 1 */ - if (threads > 1) { - world->worker_cond = ecs_os_cond_new(); - world->sync_cond = ecs_os_cond_new(); - world->sync_mutex = ecs_os_mutex_new(); - start_workers(world, threads); - } - } + flecs_sparse_free(world->observers); } -#endif - +/* Cleanup stages */ +static +void fini_stages( + ecs_world_t *world) +{ + flecs_stage_deinit(world, &world->stage); + ecs_set_stages(world, 0); +} -#ifdef FLECS_PIPELINE +/* Cleanup id index */ +static +void fini_id_index( + ecs_world_t *world) +{ + ecs_map_iter_t it = ecs_map_iter(world->id_index); + ecs_id_record_t *idr; + while ((idr = ecs_map_next(&it, ecs_id_record_t, NULL))) { + ecs_table_cache_fini(&idr->cache); + ecs_map_free(idr->add_refs); + ecs_map_free(idr->remove_refs); + } -static ECS_DTOR(EcsPipelineQuery, ptr, { - ecs_vector_free(ptr->ops); -}) + ecs_map_free(world->id_index); +} +/* Cleanup aliases & symbols */ static -int compare_entity( - ecs_entity_t e1, - const void *ptr1, - ecs_entity_t e2, - const void *ptr2) +void fini_aliases( + ecs_hashmap_t *map) { - (void)ptr1; - (void)ptr2; - return (e1 > e2) - (e1 < e2); + flecs_hashmap_iter_t it = flecs_hashmap_iter(*map); + ecs_hashed_string_t *key; + while (flecs_hashmap_next_w_key(&it, ecs_hashed_string_t, &key, ecs_entity_t)) { + ecs_os_free(key->value); + } + + flecs_hashmap_free(*map); } +/* Cleanup misc structures */ static -uint64_t group_by_phase( - ecs_world_t *world, - ecs_type_t type, - ecs_entity_t pipeline, - void *ctx) +void fini_misc( + ecs_world_t *world) { - (void)ctx; - - const EcsType *pipeline_type = ecs_get(world, pipeline, EcsType); - ecs_assert(pipeline_type != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_map_free(world->type_handles); + ecs_vector_free(world->fini_tasks); + monitors_fini(&world->monitors); +} - /* Find tag in system that belongs to pipeline */ - ecs_entity_t *sys_comps = ecs_vector_first(type, ecs_entity_t); - int32_t c, t, count = ecs_vector_count(type); +/* The destroyer of worlds */ +int ecs_fini( + ecs_world_t *world) +{ + ecs_poly_assert(world, ecs_world_t); + ecs_assert(!world->is_readonly, ECS_INVALID_OPERATION, NULL); + ecs_assert(!world->is_fini, ECS_INVALID_OPERATION, NULL); - ecs_entity_t *tags = ecs_vector_first(pipeline_type->normalized, ecs_entity_t); - int32_t tag_count = ecs_vector_count(pipeline_type->normalized); + ecs_trace("#[bold]shutting down world"); + ecs_log_push(); - ecs_entity_t result = 0; + world->is_fini = true; - for (c = 0; c < count; c ++) { - ecs_entity_t comp = sys_comps[c]; - for (t = 0; t < tag_count; t ++) { - if (comp == tags[t]) { - result = comp; - break; - } - } - if (result) { - break; - } + /* Operations invoked during UnSet/OnRemove/destructors are deferred and + * will be discarded after world cleanup */ + ecs_defer_begin(world); + + /* Run UnSet/OnRemove actions for components while the store is still + * unmodified by cleanup. */ + fini_unset_tables(world); + + /* Run fini actions (simple callbacks ran when world is deleted) before + * destroying the storage */ + fini_actions(world); + + /* This will destroy all entities and components. After this point no more + * user code is executed. */ + fini_store(world); + + /* Purge deferred operations from the queue. This discards operations but + * makes sure that any resources in the queue are freed */ + flecs_defer_purge(world, &world->stage); + + /* Entity index is kept alive until this point so that user code can do + * validity checks on entity ids, even though after store cleanup the index + * will be empty, so all entity ids are invalid. */ + flecs_sparse_free(world->store.entity_index); + + if (world->locking_enabled) { + ecs_os_mutex_free(world->mutex); } - ecs_assert(result != 0, ECS_INTERNAL_ERROR, NULL); - ecs_assert(result < INT_MAX, ECS_INTERNAL_ERROR, NULL); + ecs_trace("table store deinitialized"); - return result; + fini_stages(world); + + fini_component_lifecycle(world); + + fini_queries(world); + + fini_observers(world); + + fini_id_index(world); + + flecs_observable_fini(&world->observable); + + flecs_sparse_free(world->triggers); + + fini_aliases(&world->aliases); + + fini_aliases(&world->symbols); + + fini_misc(world); + + flecs_increase_timer_resolution(0); + + /* End of the world */ + ecs_poly_free(world, ecs_world_t); + + ecs_os_fini(); + + ecs_trace("world destroyed, bye!"); + ecs_log_pop(); + + return 0; } -typedef enum ComponentWriteState { - NotWritten = 0, - WriteToMain, - WriteToStage -} ComponentWriteState; +void ecs_dim( + ecs_world_t *world, + int32_t entity_count) +{ + ecs_poly_assert(world, ecs_world_t); + ecs_eis_set_size(world, entity_count + ECS_HI_COMPONENT_ID); +} -typedef struct write_state_t { - ecs_map_t *components; - bool wildcard; -} write_state_t; +void flecs_eval_component_monitors( + ecs_world_t *world) +{ + ecs_poly_assert(world, ecs_world_t); + eval_component_monitor(world); +} -static -int32_t get_write_state( - ecs_map_t *write_state, - ecs_entity_t component) +void ecs_measure_frame_time( + ecs_world_t *world, + bool enable) { - int32_t *ptr = ecs_map_get(write_state, int32_t, component); - if (ptr) { - return *ptr; - } else { - return 0; + ecs_poly_assert(world, ecs_world_t); + ecs_check(ecs_os_has_time(), ECS_MISSING_OS_API, NULL); + + if (world->stats.target_fps == 0.0f || enable) { + world->measure_frame_time = enable; } +error: + return; } -static -void set_write_state( - write_state_t *write_state, - ecs_entity_t component, - int32_t value) +void ecs_measure_system_time( + ecs_world_t *world, + bool enable) { - if (component == EcsWildcard) { - ecs_assert(value == WriteToStage, ECS_INTERNAL_ERROR, NULL); - write_state->wildcard = true; - } else { - ecs_map_set(write_state->components, component, &value); - } + ecs_poly_assert(world, ecs_world_t); + ecs_check(ecs_os_has_time(), ECS_MISSING_OS_API, NULL); + world->measure_system_time = enable; +error: + return; } -static -void reset_write_state( - write_state_t *write_state) +/* Increase timer resolution based on target fps */ +static +void set_timer_resolution( + FLECS_FLOAT fps) { - ecs_map_clear(write_state->components); - write_state->wildcard = false; + if(fps >= 60.0f) flecs_increase_timer_resolution(1); + else flecs_increase_timer_resolution(0); } -static -int32_t get_any_write_state( - write_state_t *write_state) +void ecs_set_target_fps( + ecs_world_t *world, + FLECS_FLOAT fps) { - if (write_state->wildcard) { - return WriteToStage; - } + ecs_poly_assert(world, ecs_world_t); + ecs_check(ecs_os_has_time(), ECS_MISSING_OS_API, NULL); - ecs_map_iter_t it = ecs_map_iter(write_state->components); - int32_t *elem; - while ((elem = ecs_map_next(&it, int32_t, NULL))) { - if (*elem == WriteToStage) { - return WriteToStage; - } - } + ecs_measure_frame_time(world, true); + world->stats.target_fps = fps; + set_timer_resolution(fps); +error: + return; +} - return 0; +void* ecs_get_context( + const ecs_world_t *world) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + world = ecs_get_world(world); + return world->context; +error: + return NULL; } -static -bool check_term_component( - ecs_term_t *term, - bool is_active, - ecs_entity_t component, - write_state_t *write_state) +void ecs_set_context( + ecs_world_t *world, + void *context) { - int32_t state = get_write_state(write_state->components, component); + ecs_poly_assert(world, ecs_world_t); + world->context = context; +} - ecs_term_id_t *subj = &term->subj; +void ecs_set_entity_range( + ecs_world_t *world, + ecs_entity_t id_start, + ecs_entity_t id_end) +{ + ecs_poly_assert(world, ecs_world_t); + ecs_check(!id_end || id_end > id_start, ECS_INVALID_PARAMETER, NULL); + ecs_check(!id_end || id_end > world->stats.last_id, + ECS_INVALID_PARAMETER, NULL); - if ((subj->set.mask & EcsSelf) && subj->entity == EcsThis && term->oper != EcsNot) { - switch(term->inout) { - case EcsInOutFilter: - /* Ignore terms that aren't read/written */ - break; - case EcsInOutDefault: - case EcsInOut: - case EcsIn: - if (state == WriteToStage || write_state->wildcard) { - return true; - } - // fall through - case EcsOut: - if (is_active && term->inout != EcsIn) { - set_write_state(write_state, component, WriteToMain); - } - }; + if (world->stats.last_id < id_start) { + world->stats.last_id = id_start - 1; + } - } else if (!subj->entity || term->oper == EcsNot) { - bool needs_merge = false; + world->stats.min_id = id_start; + world->stats.max_id = id_end; +error: + return; +} - switch(term->inout) { - case EcsInOutDefault: - case EcsIn: - case EcsInOut: - if (state == WriteToStage) { - needs_merge = true; - } - if (component == EcsWildcard) { - if (get_any_write_state(write_state) == WriteToStage) { - needs_merge = true; - } - } - break; - default: - break; - }; +bool ecs_enable_range_check( + ecs_world_t *world, + bool enable) +{ + ecs_poly_assert(world, ecs_world_t); + bool old_value = world->range_check_enabled; + world->range_check_enabled = enable; + return old_value; +} - switch(term->inout) { - case EcsInOutDefault: - if ((!(subj->set.mask & EcsSelf) || (subj->entity != EcsThis)) && (subj->set.mask != EcsNothing)) { - /* Default inout behavior is [inout] for This terms, and [in] - * for terms that match other entities */ - break; - } - // fall through - case EcsInOut: - case EcsOut: - if (is_active) { - set_write_state(write_state, component, WriteToStage); - } - break; - default: - break; - }; +int32_t ecs_get_threads( + ecs_world_t *world) +{ + return ecs_vector_count(world->worker_stages); +} - if (needs_merge) { - return true; +bool ecs_enable_locking( + ecs_world_t *world, + bool enable) +{ + ecs_poly_assert(world, ecs_world_t); + + if (enable) { + if (!world->locking_enabled) { + world->mutex = ecs_os_mutex_new(); + world->thr_sync = ecs_os_mutex_new(); + world->thr_cond = ecs_os_cond_new(); + } + } else { + if (world->locking_enabled) { + ecs_os_mutex_free(world->mutex); + ecs_os_mutex_free(world->thr_sync); + ecs_os_cond_free(world->thr_cond); } } - return false; + bool old = world->locking_enabled; + world->locking_enabled = enable; + return old; } -static -bool check_term( - ecs_term_t *term, - bool is_active, - write_state_t *write_state) +void ecs_lock( + ecs_world_t *world) { - if (term->oper != EcsOr) { - return check_term_component( - term, is_active, term->id, write_state); - } + ecs_poly_assert(world, ecs_world_t); + ecs_assert(world->locking_enabled, ECS_INVALID_PARAMETER, NULL); + ecs_os_mutex_lock(world->mutex); +} - return false; +void ecs_unlock( + ecs_world_t *world) +{ + ecs_poly_assert(world, ecs_world_t); + ecs_assert(world->locking_enabled, ECS_INVALID_PARAMETER, NULL); + ecs_os_mutex_unlock(world->mutex); } -static -bool check_terms( - ecs_filter_t *filter, - bool is_active, - write_state_t *ws) +void ecs_begin_wait( + ecs_world_t *world) { - bool needs_merge = false; - ecs_term_t *terms = filter->terms; - int32_t t, term_count = filter->term_count; + ecs_poly_assert(world, ecs_world_t); + ecs_assert(world->locking_enabled, ECS_INVALID_PARAMETER, NULL); + ecs_os_mutex_lock(world->thr_sync); + ecs_os_cond_wait(world->thr_cond, world->thr_sync); +} - /* Check This terms first. This way if a term indicating writing to a stage - * was added before the term, it won't cause merging. */ - for (t = 0; t < term_count; t ++) { - ecs_term_t *term = &terms[t]; - if (term->subj.entity == EcsThis) { - needs_merge |= check_term(term, is_active, ws); - } - } +void ecs_end_wait( + ecs_world_t *world) +{ + ecs_poly_assert(world, ecs_world_t); + ecs_assert(world->locking_enabled, ECS_INVALID_PARAMETER, NULL); + ecs_os_mutex_unlock(world->thr_sync); +} - /* Now check staged terms */ - for (t = 0; t < term_count; t ++) { - ecs_term_t *term = &terms[t]; - if (term->subj.entity != EcsThis) { - needs_merge |= check_term(term, is_active, ws); - } - } +const ecs_type_info_t* flecs_get_c_info( + const ecs_world_t *world, + ecs_entity_t component) +{ + ecs_poly_assert(world, ecs_world_t); - return needs_merge; + ecs_assert(component != 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(!(component & ECS_ROLE_MASK), ECS_INTERNAL_ERROR, NULL); + + return flecs_sparse_get(world->type_info, ecs_type_info_t, component); } -static -bool build_pipeline( +ecs_type_info_t* flecs_get_or_create_c_info( ecs_world_t *world, - ecs_entity_t pipeline, - EcsPipelineQuery *pq) + ecs_entity_t component) { - (void)pipeline; - - ecs_query_iter(world, pq->query); + ecs_poly_assert(world, ecs_world_t); - if (pq->match_count == pq->query->match_count) { - /* No need to rebuild the pipeline */ - return false; + const ecs_type_info_t *c_info = flecs_get_c_info(world, component); + ecs_type_info_t *c_info_mut = NULL; + if (!c_info) { + c_info_mut = flecs_sparse_ensure( + world->type_info, ecs_type_info_t, component); + ecs_assert(c_info_mut != NULL, ECS_INTERNAL_ERROR, NULL); + } else { + c_info_mut = (ecs_type_info_t*)c_info; } - world->stats.pipeline_build_count_total ++; - pq->rebuild_count ++; + return c_info_mut; +} - write_state_t ws = { - .components = ecs_map_new(int32_t, ECS_HI_COMPONENT_ID), - .wildcard = false - }; +static +FLECS_FLOAT insert_sleep( + ecs_world_t *world, + ecs_time_t *stop) +{ + ecs_poly_assert(world, ecs_world_t); - ecs_pipeline_op_t *op = NULL; - ecs_vector_t *ops = NULL; - ecs_query_t *query = pq->build_query; + ecs_time_t start = *stop; + FLECS_FLOAT delta_time = (FLECS_FLOAT)ecs_time_measure(stop); - if (pq->ops) { - ecs_vector_free(pq->ops); + if (world->stats.target_fps == (FLECS_FLOAT)0.0) { + return delta_time; } - bool multi_threaded = false; - bool no_staging = false; - bool first = true; - - /* Iterate systems in pipeline, add ops for running / merging */ - ecs_iter_t it = ecs_query_iter(world, query); - while (ecs_query_next(&it)) { - EcsSystem *sys = ecs_term(&it, EcsSystem, 1); - - int i; - for (i = 0; i < it.count; i ++) { - ecs_query_t *q = sys[i].query; - if (!q) { - continue; - } - - bool needs_merge = false; - bool is_active = !ecs_has_id( - world, it.entities[i], EcsInactive); - needs_merge = check_terms(&q->filter, is_active, &ws); - - if (is_active) { - if (first) { - multi_threaded = sys[i].multi_threaded; - no_staging = sys[i].no_staging; - first = false; - } - - if (sys[i].multi_threaded != multi_threaded) { - needs_merge = true; - multi_threaded = sys[i].multi_threaded; - } - if (sys[i].no_staging != no_staging) { - needs_merge = true; - no_staging = sys[i].no_staging; - } - } + FLECS_FLOAT target_delta_time = + ((FLECS_FLOAT)1.0 / (FLECS_FLOAT)world->stats.target_fps); - if (needs_merge) { - /* After merge all components will be merged, so reset state */ - reset_write_state(&ws); - op = NULL; + /* Calculate the time we need to sleep by taking the measured delta from the + * previous frame, and subtracting it from target_delta_time. */ + FLECS_FLOAT sleep = target_delta_time - delta_time; - /* Re-evaluate columns to set write flags if system is active. - * If system is inactive, it can't write anything and so it - * should not insert unnecessary merges. */ - needs_merge = false; - if (is_active) { - needs_merge = check_terms(&q->filter, true, &ws); - } + /* Pick a sleep interval that is 4 times smaller than the time one frame + * should take. */ + FLECS_FLOAT sleep_time = sleep / (FLECS_FLOAT)4.0; - /* The component states were just reset, so if we conclude that - * another merge is needed something is wrong. */ - ecs_assert(needs_merge == false, ECS_INTERNAL_ERROR, NULL); - } + do { + /* Only call sleep when sleep_time is not 0. On some platforms, even + * a sleep with a timeout of 0 can cause stutter. */ + if (sleep_time != 0) { + ecs_sleepf((double)sleep_time); + } - if (!op) { - op = ecs_vector_add(&ops, ecs_pipeline_op_t); - op->count = 0; - op->multi_threaded = false; - op->no_staging = false; - } + ecs_time_t now = start; + delta_time = (FLECS_FLOAT)ecs_time_measure(&now); + } while ((target_delta_time - delta_time) > + (sleep_time / (FLECS_FLOAT)2.0)); - /* Don't increase count for inactive systems, as they are ignored by - * the query used to run the pipeline. */ - if (is_active) { - if (!op->count) { - op->multi_threaded = multi_threaded; - op->no_staging = no_staging; - } - op->count ++; - } - } - } + return delta_time; +} - ecs_map_free(ws.components); +static +FLECS_FLOAT start_measure_frame( + ecs_world_t *world, + FLECS_FLOAT user_delta_time) +{ + ecs_poly_assert(world, ecs_world_t); - /* Find the system ran last this frame (helps workers reset iter) */ - ecs_entity_t last_system = 0; - op = ecs_vector_first(ops, ecs_pipeline_op_t); - int32_t i, ran_since_merge = 0, op_index = 0; + FLECS_FLOAT delta_time = 0; - /* Add schedule to debug tracing */ - ecs_dbg("#[green]pipeline#[reset] rebuild:"); - ecs_log_push(); + if (world->measure_frame_time || (user_delta_time == 0)) { + ecs_time_t t = world->frame_start_time; + do { + if (world->frame_start_time.sec) { + delta_time = insert_sleep(world, &t); - ecs_dbg("#[green]schedule#[reset]: threading: %d, staging: %d:", - op->multi_threaded, !op->no_staging); - ecs_log_push(); - - it = ecs_query_iter(world, pq->query); - while (ecs_query_next(&it)) { - EcsSystem *sys = ecs_term(&it, EcsSystem, 1); - for (i = 0; i < it.count; i ++) { -#ifndef NDEBUG - char *path = ecs_get_fullpath(world, it.entities[i]); - ecs_dbg("#[green]system#[reset] %s", path); - ecs_os_free(path); -#endif - ran_since_merge ++; - if (ran_since_merge == op[op_index].count) { - ecs_dbg("#[magenta]merge#[reset]"); - ecs_log_pop(); - ran_since_merge = 0; - op_index ++; - if (op_index < ecs_vector_count(ops)) { - ecs_dbg("#[green]schedule#[reset]: threading: %d, staging: %d:", - op[op_index].multi_threaded, !op[op_index].no_staging); + ecs_time_measure(&t); + } else { + ecs_time_measure(&t); + if (world->stats.target_fps != 0) { + delta_time = (FLECS_FLOAT)1.0 / world->stats.target_fps; + } else { + /* Best guess */ + delta_time = (FLECS_FLOAT)1.0 / (FLECS_FLOAT)60.0; } - ecs_log_push(); } + + /* Keep trying while delta_time is zero */ + } while (delta_time == 0); - if (sys[i].last_frame == (world->stats.frame_count_total + 1)) { - last_system = it.entities[i]; + world->frame_start_time = t; - /* Can't break from loop yet. It's possible that previously - * inactive systems that ran before the last ran system are now - * active. */ - } - } + /* Keep track of total time passed in world */ + world->stats.world_time_total_raw += (FLECS_FLOAT)delta_time; } - ecs_log_pop(); - ecs_log_pop(); + return (FLECS_FLOAT)delta_time; +} - /* Force sort of query as this could increase the match_count */ - pq->match_count = pq->query->match_count; - pq->ops = ops; - pq->last_system = last_system; +static +void stop_measure_frame( + ecs_world_t* world) +{ + ecs_poly_assert(world, ecs_world_t); - return true; + if (world->measure_frame_time) { + ecs_time_t t = world->frame_start_time; + world->stats.frame_time_total += (FLECS_FLOAT)ecs_time_measure(&t); + } } -int32_t ecs_pipeline_reset_iter( +FLECS_FLOAT ecs_frame_begin( ecs_world_t *world, - const EcsPipelineQuery *pq, - ecs_iter_t *iter_out, - ecs_pipeline_op_t **op_out, - ecs_pipeline_op_t **last_op_out) + FLECS_FLOAT user_delta_time) { - ecs_pipeline_op_t *op = ecs_vector_first(pq->ops, ecs_pipeline_op_t); - int32_t i, ran_since_merge = 0, op_index = 0; + ecs_poly_assert(world, ecs_world_t); + ecs_check(world->is_readonly == false, ECS_INVALID_OPERATION, NULL); + ecs_check(user_delta_time != 0 || ecs_os_has_time(), + ECS_MISSING_OS_API, "get_time"); - if (!pq->last_system) { - /* It's possible that all systems that were ran were removed entirely - * from the pipeline (they could have been deleted or disabled). In that - * case (which should be very rare) the pipeline can't make assumptions - * about where to continue, so end frame. */ - return -1; + if (world->locking_enabled) { + ecs_lock(world); } - /* Move iterator to last ran system */ - *iter_out = ecs_query_iter(world, pq->query); - while (ecs_query_next(iter_out)) { - for (i = 0; i < iter_out->count; i ++) { - ran_since_merge ++; - if (ran_since_merge == op[op_index].count) { - ran_since_merge = 0; - op_index ++; - } + /* Start measuring total frame time */ + FLECS_FLOAT delta_time = start_measure_frame(world, user_delta_time); + if (user_delta_time == 0) { + user_delta_time = delta_time; + } - if (iter_out->entities[i] == pq->last_system) { - *op_out = &op[op_index]; - *last_op_out = ecs_vector_last(pq->ops, ecs_pipeline_op_t); - return i; - } - } - } + world->stats.delta_time_raw = user_delta_time; + world->stats.delta_time = user_delta_time * world->stats.time_scale; - ecs_abort(ECS_INTERNAL_ERROR, NULL); + /* Keep track of total scaled time passed in world */ + world->stats.world_time_total += world->stats.delta_time; - return -1; + flecs_eval_component_monitors(world); + + return world->stats.delta_time; +error: + return (FLECS_FLOAT)0; } -bool ecs_pipeline_update( - ecs_world_t *world, - ecs_entity_t pipeline, - bool start_of_frame) +void ecs_frame_end( + ecs_world_t *world) { ecs_poly_assert(world, ecs_world_t); - ecs_assert(!world->is_readonly, ECS_INVALID_OPERATION, NULL); - ecs_assert(pipeline != 0, ECS_INTERNAL_ERROR, NULL); + ecs_check(world->is_readonly == false, ECS_INVALID_OPERATION, NULL); - /* If any entity mutations happened that could have affected query matching - * notify appropriate queries so caches are up to date. This includes the - * pipeline query. */ - if (start_of_frame) { - flecs_eval_component_monitors(world); + world->stats.frame_count_total ++; + + ecs_vector_each(world->worker_stages, ecs_stage_t, stage, { + flecs_stage_merge_post_frame(world, stage); + }); + + if (world->locking_enabled) { + ecs_unlock(world); + + ecs_os_mutex_lock(world->thr_sync); + ecs_os_cond_broadcast(world->thr_cond); + ecs_os_mutex_unlock(world->thr_sync); } - bool added = false; - EcsPipelineQuery *pq = ecs_get_mut(world, pipeline, EcsPipelineQuery, &added); - ecs_assert(added == false, ECS_INTERNAL_ERROR, NULL); - ecs_assert(pq != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(pq->query != NULL, ECS_INTERNAL_ERROR, NULL); + stop_measure_frame(world); +error: + return; +} - return build_pipeline(world, pipeline, pq); +const ecs_world_info_t* ecs_get_world_info( + const ecs_world_t *world) +{ + world = ecs_get_world(world); + return &world->stats; } -void ecs_run_pipeline( +void flecs_notify_queries( ecs_world_t *world, - ecs_entity_t pipeline, - FLECS_FLOAT delta_time) + ecs_query_event_t *event) { - ecs_assert(world != NULL, ECS_INVALID_OPERATION, NULL); + ecs_poly_assert(world, ecs_world_t); - if (!pipeline) { - pipeline = world->pipeline; + int32_t i, count = flecs_sparse_count(world->queries); + for (i = 0; i < count; i ++) { + ecs_query_t *query = flecs_sparse_get_dense( + world->queries, ecs_query_t, i); + if (query->flags & EcsQueryIsSubquery) { + continue; + } + + flecs_query_notify(world, query, event); } +} - /* If the world is passed to ecs_run_pipeline, the function will take care - * of staging, so the world should not be in staged mode when called. */ - if (ecs_poly_is(world, ecs_world_t)) { - ecs_assert(!world->is_readonly, ECS_INVALID_OPERATION, NULL); +void flecs_delete_table( + ecs_world_t *world, + ecs_table_t *table) +{ + ecs_poly_assert(world, ecs_world_t); - /* Forward to worker_progress. This function handles staging, threading - * and synchronization across workers. */ - ecs_workers_progress(world, pipeline, delta_time); - return; + /* Notify queries that table is to be removed */ + flecs_notify_queries( + world, &(ecs_query_event_t){ + .kind = EcsQueryTableUnmatch, + .table = table + }); - /* If a stage is passed, the function could be ran from a worker thread. In - * that case the main thread should manage staging, and staging should be - * enabled. */ + uint64_t id = table->id; + + /* Free resources associated with table */ + flecs_table_free(world, table); + flecs_table_free_type(table); + + /* Remove table from sparse set */ + ecs_assert(id != 0, ECS_INTERNAL_ERROR, NULL); + flecs_sparse_remove(world->store.tables, id); +} + +static +void register_table( + ecs_world_t *world, + ecs_table_t *table, + ecs_id_t id, + int32_t column) +{ + ecs_id_record_t *idr = flecs_ensure_id_record(world, id); + ecs_assert(idr != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_table_record_t *tr = ecs_table_cache_get( + &idr->cache, ecs_table_record_t, table); + if (tr) { + /* Tables can be registered multiple times for wildcard caches */ + tr->count ++; } else { - ecs_poly_assert(world, ecs_stage_t); + tr = ecs_table_cache_insert(&idr->cache, ecs_table_record_t, table); + tr->table = table; + tr->column = column; + tr->count = 1; } - ecs_stage_t *stage = flecs_stage_from_world(&world); - - const EcsPipelineQuery *pq = ecs_get(world, pipeline, EcsPipelineQuery); - ecs_assert(pq != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(pq->query != NULL, ECS_INTERNAL_ERROR, NULL); + /* Set flags if triggers are registered for table */ + if (flecs_triggers_for_id(world, id, EcsOnAdd)) { + table->flags |= EcsTableHasOnAdd; + } + if (flecs_triggers_for_id(world, id, EcsOnRemove)) { + table->flags |= EcsTableHasOnRemove; + } + if (flecs_triggers_for_id(world, id, EcsOnSet)) { + table->flags |= EcsTableHasOnSet; + } + if (flecs_triggers_for_id(world, id, EcsUnSet)) { + table->flags |= EcsTableHasUnSet; + } +} - ecs_vector_t *ops = pq->ops; - ecs_pipeline_op_t *op = ecs_vector_first(ops, ecs_pipeline_op_t); - ecs_pipeline_op_t *op_last = ecs_vector_last(ops, ecs_pipeline_op_t); - int32_t ran_since_merge = 0; +static +void unregister_table( + ecs_world_t *world, + ecs_table_t *table, + ecs_id_t id, + int32_t column) +{ + (void)column; - int32_t stage_index = ecs_get_stage_id(stage->thread_ctx); - int32_t stage_count = ecs_get_stage_count(world); + ecs_id_record_t *idr = flecs_get_id_record(world, id); + if (!idr) { + return; + } - ecs_worker_begin(stage->thread_ctx); + ecs_table_cache_remove(&idr->cache, ecs_table_record_t, table); - ecs_iter_t it = ecs_query_iter(world, pq->query); - while (ecs_query_next(&it)) { - EcsSystem *sys = ecs_term(&it, EcsSystem, 1); + if (ecs_table_cache_is_empty(&idr->cache)) { + flecs_clear_id_record(world, id); + } +} - int32_t i; - for(i = 0; i < it.count; i ++) { - ecs_entity_t e = it.entities[i]; +static +void set_empty( + ecs_world_t *world, + ecs_table_t *table, + ecs_id_t id, + int32_t column) +{ + (void)column; - if (!stage_index) { - ecs_dbg_3("pipeline: run system %s", ecs_get_name(world, e)); - } + ecs_id_record_t *idr = flecs_get_id_record(world, id); + if (idr) { + ecs_table_cache_set_empty(&idr->cache, table, + ecs_table_count(table) == 0); + } +} - if (!stage_index || op->multi_threaded) { - ecs_stage_t *s = NULL; - if (!op->no_staging) { - s = stage; - } +static +void for_each_id( + ecs_world_t *world, + ecs_table_t *table, + void(*action)(ecs_world_t*, ecs_table_t*, ecs_id_t, int32_t), + bool set_watch) +{ + int32_t i, count = ecs_vector_count(table->type); + ecs_id_t *ids = ecs_vector_first(table->type, ecs_id_t); + bool has_childof = false; + + for (i = 0; i < count; i ++) { + ecs_entity_t id = ids[i]; - ecs_run_intern(world, s, e, &sys[i], stage_index, - stage_count, delta_time, 0, 0, NULL); - } + /* This check ensures that legacy CHILDOF works */ + if (ECS_HAS_RELATION(id, EcsChildOf)) { + has_childof = true; + } - sys[i].last_frame = world->stats.frame_count_total + 1; + action(world, table, ecs_strip_generation(id), i); + action(world, table, EcsWildcard, i); - ran_since_merge ++; - world->stats.systems_ran_frame ++; + if (ECS_HAS_ROLE(id, PAIR)) { + ecs_entity_t pred_w_wildcard = ecs_pair( + ECS_PAIR_RELATION(id), EcsWildcard); + action(world, table, pred_w_wildcard, i); - if (op != op_last && ran_since_merge == op->count) { - ran_since_merge = 0; + ecs_entity_t obj_w_wildcard = ecs_pair( + EcsWildcard, ECS_PAIR_OBJECT(id)); + action(world, table, obj_w_wildcard, i); - if (!stage_index) { - ecs_dbg_3("merge"); - } + ecs_entity_t all_wildcard = ecs_pair(EcsWildcard, EcsWildcard); + action(world, table, all_wildcard, i); - /* If the set of matched systems changed as a result of the - * merge, we have to reset the iterator and move it to our - * current position (system). If there are a lot of systems - * in the pipeline this can be an expensive operation, but - * should happen infrequently. */ - i = ecs_worker_sync(world, pq, &it, i, &op, &op_last); - sys = ecs_term(&it, EcsSystem, 1); + if (set_watch) { + ecs_entity_t rel = ecs_pair_relation(world, id); + ecs_entity_t obj = ecs_pair_object(world, id); + flecs_add_flag(world, rel, ECS_FLAG_OBSERVED_ID); + flecs_add_flag(world, obj, ECS_FLAG_OBSERVED_OBJECT); + if (ecs_has_id(world, rel, EcsAcyclic)) { + flecs_add_flag(world, obj, ECS_FLAG_OBSERVED_ACYCLIC); + } + } + } else { + if (id & ECS_ROLE_MASK) { + id &= ECS_COMPONENT_MASK; + action(world, table, ecs_pair(id, EcsWildcard), i); } + + if (set_watch) { + flecs_add_flag(world, id, ECS_FLAG_OBSERVED_ID); + } } } - ecs_worker_end(stage->thread_ctx); + if (!has_childof) { + action(world, table, ecs_pair(EcsChildOf, 0), 0); + } } -static -void add_pipeline_tags_to_sig( +void flecs_register_table( ecs_world_t *world, - ecs_term_t *terms, - ecs_type_t type) + ecs_table_t *table) { - (void)world; - - int32_t i, count = ecs_vector_count(type); - ecs_entity_t *entities = ecs_vector_first(type, ecs_entity_t); - - for (i = 0; i < count; i ++) { - terms[i] = (ecs_term_t){ - .inout = EcsIn, - .oper = EcsOr, - .pred.entity = entities[i], - .subj = { - .entity = EcsThis, - .set.mask = EcsSelf | EcsSuperSet - } - }; - } + for_each_id(world, table, register_table, true); } -static -ecs_query_t* build_pipeline_query( +void flecs_unregister_table( ecs_world_t *world, - ecs_entity_t pipeline, - const char *name, - bool not_inactive) + ecs_table_t *table) { - const EcsType *type_ptr = ecs_get(world, pipeline, EcsType); - ecs_assert(type_ptr != NULL, ECS_INTERNAL_ERROR, NULL); - - int32_t type_count = ecs_vector_count(type_ptr->normalized); - int32_t term_count = 1; + for_each_id(world, table, unregister_table, false); - if (not_inactive) { - term_count ++; - } - - ecs_term_t *terms = ecs_os_malloc( - (type_count + term_count) * ECS_SIZEOF(ecs_term_t)); - - terms[0] = (ecs_term_t){ - .inout = EcsIn, - .oper = EcsAnd, - .pred.entity = ecs_id(EcsSystem), - .subj = { - .entity = EcsThis, - .set.mask = EcsSelf | EcsSuperSet - } + ecs_ids_t key = { + .array = ecs_vector_first(table->type, ecs_id_t), + .count = ecs_vector_count(table->type) }; - if (not_inactive) { - terms[1] = (ecs_term_t){ - .inout = EcsIn, - .oper = EcsNot, - .pred.entity = EcsInactive, - .subj = { - .entity = EcsThis, - .set.mask = EcsSelf | EcsSuperSet - } - }; - } - - add_pipeline_tags_to_sig(world, &terms[term_count], type_ptr->normalized); - - ecs_query_t *result = ecs_query_init(world, &(ecs_query_desc_t){ - .filter = { - .name = name, - .terms_buffer = terms, - .terms_buffer_count = term_count + type_count - }, - .order_by = compare_entity, - .group_by = group_by_phase, - .group_by_id = pipeline - }); - - ecs_assert(result != NULL, ECS_INTERNAL_ERROR, NULL); - - ecs_os_free(terms); - - return result; + flecs_hashmap_remove(world->store.table_map, &key, ecs_table_t*); } -static -void OnUpdatePipeline( - ecs_iter_t *it) +void flecs_table_set_empty( + ecs_world_t *world, + ecs_table_t *table) { - ecs_world_t *world = it->world; - ecs_entity_t *entities = it->entities; - - int32_t i; - for (i = it->count - 1; i >= 0; i --) { - ecs_entity_t pipeline = entities[i]; - - ecs_trace("#[green]pipeline#[reset] %s created", - ecs_get_name(world, pipeline)); - ecs_log_push(); - - /* Build signature for pipeline query that matches EcsSystems, has the - * pipeline phases as OR columns, and ignores systems with EcsInactive. - * Note that EcsDisabled is automatically ignored - * by the regular query matching */ - ecs_query_t *query = build_pipeline_query( - world, pipeline, "BuiltinPipelineQuery", true); - ecs_assert(query != NULL, ECS_INTERNAL_ERROR, NULL); - - /* Build signature for pipeline build query. The build query includes - * systems that are inactive, as an inactive system may become active as - * a result of another system, and as a result the correct merge - * operations need to be put in place. */ - ecs_query_t *build_query = build_pipeline_query( - world, pipeline, "BuiltinPipelineBuildQuery", false); - ecs_assert(build_query != NULL, ECS_INTERNAL_ERROR, NULL); - - bool added = false; - EcsPipelineQuery *pq = ecs_get_mut( - world, pipeline, EcsPipelineQuery, &added); - ecs_assert(pq != NULL, ECS_INTERNAL_ERROR, NULL); - - if (added) { - /* Should not modify pipeline after it has been used */ - ecs_assert(pq->ops == NULL, ECS_INVALID_OPERATION, NULL); - - if (pq->query) { - ecs_query_fini(pq->query); - } - if (pq->build_query) { - ecs_query_fini(pq->build_query); - } - } - - pq->query = query; - pq->build_query = build_query; - pq->match_count = -1; - pq->ops = NULL; - pq->last_system = 0; - - ecs_log_pop(); - } + for_each_id(world, table, set_empty, false); } -/* -- Public API -- */ - -bool ecs_progress( +ecs_id_record_t* flecs_ensure_id_record( ecs_world_t *world, - FLECS_FLOAT user_delta_time) + ecs_id_t id) { - float delta_time = ecs_frame_begin(world, user_delta_time); + ecs_id_record_t *idr = ecs_map_ensure(world->id_index, ecs_id_record_t, + ecs_strip_generation(id)); - ecs_dbg_3("#[normal]begin progress(dt = %.2f)", (double)delta_time); + if (!ecs_table_cache_is_initialized(&idr->cache)) { + ecs_table_cache_init(&idr->cache, ecs_table_record_t, world, NULL); + } - ecs_run_pipeline(world, 0, delta_time); + return idr; +} - ecs_dbg_3("#[normal]end progress"); +ecs_id_record_t* flecs_get_id_record( + const ecs_world_t *world, + ecs_id_t id) +{ + return ecs_map_get(world->id_index, ecs_id_record_t, + ecs_strip_generation(id)); +} - ecs_frame_end(world); +ecs_table_record_t* flecs_get_table_record( + const ecs_world_t *world, + const ecs_table_t *table, + ecs_id_t id) +{ + ecs_id_record_t* idr = flecs_get_id_record(world, id); + if (!idr) { + return NULL; + } - return !world->should_quit; + return ecs_table_cache_get(&idr->cache, ecs_table_record_t, table); } -void ecs_set_time_scale( +void flecs_register_add_ref( ecs_world_t *world, - FLECS_FLOAT scale) + const ecs_table_t *table, + ecs_id_t id) { - world->stats.time_scale = scale; + ecs_id_record_t *idr = flecs_ensure_id_record(world, id); + if (!idr->add_refs) { + idr->add_refs = ecs_map_new(ecs_table_t*, 1); + } + + ecs_table_t **ptr = ecs_map_ensure( + idr->add_refs, ecs_table_t*, table->id); + ptr[0] = (ecs_table_t*)table; } -void ecs_reset_clock( - ecs_world_t *world) +void flecs_register_remove_ref( + ecs_world_t *world, + const ecs_table_t *table, + ecs_id_t id) { - world->stats.world_time_total = 0; - world->stats.world_time_total_raw = 0; + ecs_id_record_t *idr = flecs_ensure_id_record(world, id); + if (!idr->remove_refs) { + idr->remove_refs = ecs_map_new(ecs_table_t*, 1); + } + + ecs_table_t **ptr = ecs_map_ensure( + idr->remove_refs, ecs_table_t*, table->id); + ptr[0] = (ecs_table_t*)table; } -void ecs_deactivate_systems( - ecs_world_t *world) +void flecs_clear_id_record( + ecs_world_t *world, + ecs_id_t id) { - ecs_assert(!world->is_readonly, ECS_INVALID_WHILE_ITERATING, NULL); - - ecs_entity_t pipeline = world->pipeline; - const EcsPipelineQuery *pq = ecs_get( world, pipeline, EcsPipelineQuery); - ecs_assert(pq != NULL, ECS_INTERNAL_ERROR, NULL); + if (world->is_fini) { + return; + } + + ecs_id_record_t *idr_ptr = flecs_get_id_record(world, id); + if (!idr_ptr) { + return; + } - /* Iterate over all systems, add EcsInvalid tag if queries aren't matched - * with any tables */ - ecs_iter_t it = ecs_query_iter(world, pq->build_query); + ecs_id_record_t idr = *idr_ptr; - /* Make sure that we defer adding the inactive tags until after iterating - * the query */ - flecs_defer_none(world, &world->stage); + ecs_map_remove(world->id_index, ecs_strip_generation(id)); - while( ecs_query_next(&it)) { - EcsSystem *sys = ecs_term(&it, EcsSystem, 1); + ecs_table_cache_fini_delete_all(world, &idr.cache, ecs_table_record_t); - int32_t i; - for (i = 0; i < it.count; i ++) { - ecs_query_t *query = sys[i].query; - if (query) { - if (!ecs_query_table_count(query)) { - ecs_add_id(world, it.entities[i], EcsInactive); - } - } - } + /* Remove add & remove references to id from tables */ + ecs_table_t *table; + ecs_map_iter_t it = ecs_map_iter(idr.add_refs); + while ((table = ecs_map_next_ptr(&it, ecs_table_t*, NULL))) { + flecs_table_clear_add_edge(table, id); } - flecs_defer_flush(world, &world->stage); + it = ecs_map_iter(idr.remove_refs); + while ((table = ecs_map_next_ptr(&it, ecs_table_t*, NULL))) { + flecs_table_clear_remove_edge(table, id); + } + + ecs_map_free(idr.add_refs); + ecs_map_free(idr.remove_refs); } -void ecs_set_pipeline( - ecs_world_t *world, - ecs_entity_t pipeline) +const ecs_table_record_t* flecs_id_record_table( + ecs_id_record_t *idr, + ecs_table_t *table) { - ecs_poly_assert(world, ecs_world_t); - ecs_check( ecs_get(world, pipeline, EcsPipelineQuery) != NULL, - ECS_INVALID_PARAMETER, NULL); - - world->pipeline = pipeline; -error: - return; + if (!idr) { + return NULL; + } + return ecs_table_cache_get(&idr->cache, ecs_table_record_t, table); } -ecs_entity_t ecs_get_pipeline( - const ecs_world_t *world) +const ecs_table_record_t* flecs_id_record_tables( + const ecs_id_record_t *idr) { - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - world = ecs_get_world(world); - return world->pipeline; -error: - return 0; + if (!idr) { + return NULL; + } + return ecs_table_cache_tables(&idr->cache, ecs_table_record_t); } -/* -- Module implementation -- */ - -static -void FlecsPipelineFini( - ecs_world_t *world, - void *ctx) +const ecs_table_record_t* flecs_id_record_empty_tables( + const ecs_id_record_t *idr) { - (void)ctx; - if (ecs_get_stage_count(world)) { - ecs_set_threads(world, 0); + if (!idr) { + return NULL; } + return ecs_table_cache_empty_tables(&idr->cache, ecs_table_record_t); } -void FlecsPipelineImport( - ecs_world_t *world) +int32_t flecs_id_record_count( + const ecs_id_record_t *idr) { - ECS_MODULE(world, FlecsPipeline); - - ECS_IMPORT(world, FlecsSystem); - - ecs_set_name_prefix(world, "Ecs"); - - flecs_bootstrap_tag(world, EcsPipeline); - flecs_bootstrap_component(world, EcsPipelineQuery); - - /* Phases of the builtin pipeline are regular entities. Names are set so - * they can be resolved by type expressions. */ - flecs_bootstrap_tag(world, EcsPreFrame); - flecs_bootstrap_tag(world, EcsOnLoad); - flecs_bootstrap_tag(world, EcsPostLoad); - flecs_bootstrap_tag(world, EcsPreUpdate); - flecs_bootstrap_tag(world, EcsOnUpdate); - flecs_bootstrap_tag(world, EcsOnValidate); - flecs_bootstrap_tag(world, EcsPostUpdate); - flecs_bootstrap_tag(world, EcsPreStore); - flecs_bootstrap_tag(world, EcsOnStore); - flecs_bootstrap_tag(world, EcsPostFrame); - - /* Set ctor and dtor for PipelineQuery */ - ecs_set(world, ecs_id(EcsPipelineQuery), EcsComponentLifecycle, { - .ctor = ecs_default_ctor, - .dtor = ecs_dtor(EcsPipelineQuery) - }); - - /* When the Pipeline tag is added a pipeline will be created */ - ecs_observer_init(world, &(ecs_observer_desc_t) { - .entity.name = "OnUpdatePipeline", - .filter.terms = { - { .id = EcsPipeline }, - { .id = ecs_id(EcsType) } - }, - .events = { EcsOnSet }, - .callback = OnUpdatePipeline - }); - - /* Create the builtin pipeline */ - world->pipeline = ecs_type_init(world, &(ecs_type_desc_t){ - .entity = { - .name = "BuiltinPipeline", - .add = {EcsPipeline} - }, - .ids = { - EcsPreFrame, EcsOnLoad, EcsPostLoad, EcsPreUpdate, EcsOnUpdate, - EcsOnValidate, EcsPostUpdate, EcsPreStore, EcsOnStore, EcsPostFrame - } - }); - - /* Cleanup thread administration when world is destroyed */ - ecs_atfini(world, FlecsPipelineFini, NULL); + if (!idr) { + return 0; + } + return ecs_table_cache_count(&idr->cache); } -#endif - - -#ifdef FLECS_TIMER - - -static -void AddTickSource(ecs_iter_t *it) { - int32_t i; - for (i = 0; i < it->count; i ++) { - ecs_set(it->world, it->entities[i], EcsTickSource, {0}); +int32_t flecs_id_record_empty_count( + const ecs_id_record_t *idr) +{ + if (!idr) { + return 0; } + return ecs_table_cache_empty_count(&idr->cache); } static -void ProgressTimers(ecs_iter_t *it) { - EcsTimer *timer = ecs_term(it, EcsTimer, 1); - EcsTickSource *tick_source = ecs_term(it, EcsTickSource, 2); +void observer_callback(ecs_iter_t *it) { + ecs_observer_t *o = it->ctx; + ecs_world_t *world = it->world; - ecs_assert(timer != NULL, ECS_INTERNAL_ERROR, NULL); + if (o->last_event_id == world->event_id) { + /* Already handled this event */ + return; + } - int i; - for (i = 0; i < it->count; i ++) { - tick_source[i].tick = false; + ecs_iter_t user_it = *it; + user_it.term_count = o->filter.term_count_actual; + user_it.terms = o->filter.terms; + user_it.is_filter = o->filter.filter; + user_it.ids = NULL; + user_it.columns = NULL; + user_it.subjects = NULL; + user_it.sizes = NULL; + user_it.ptrs = NULL; - if (!timer[i].active) { - continue; - } + flecs_iter_init(&user_it); - const ecs_world_info_t *info = ecs_get_world_info(it->world); - FLECS_FLOAT time_elapsed = timer[i].time + info->delta_time_raw; - FLECS_FLOAT timeout = timer[i].timeout; - - if (time_elapsed >= timeout) { - FLECS_FLOAT t = time_elapsed - timeout; - if (t > timeout) { - t = 0; - } + ecs_table_t *table = it->table; + ecs_table_t *prev_table = it->other_table; + int32_t pivot_term = it->term_index; + ecs_term_t *term = &o->filter.terms[pivot_term]; - timer[i].time = t; /* Initialize with remainder */ - tick_source[i].tick = true; - tick_source[i].time_elapsed = time_elapsed; + if (term->oper == EcsNot) { + table = it->other_table; + prev_table = it->table; + } - if (timer[i].single_shot) { - timer[i].active = false; - } - } else { - timer[i].time = time_elapsed; - } + if (!table) { + table = &world->store.root; + } + if (!prev_table) { + prev_table = &world->store.root; } -} -static -void ProgressRateFilters(ecs_iter_t *it) { - EcsRateFilter *filter = ecs_term(it, EcsRateFilter, 1); - EcsTickSource *tick_dst = ecs_term(it, EcsTickSource, 2); + /* Populate the column for the term that triggered. This will allow the + * matching algorithm to pick the right column in case the term is a + * wildcard matching multiple columns. */ + user_it.columns[0] = 0; + user_it.columns[pivot_term] = it->columns[0]; - int i; - for (i = 0; i < it->count; i ++) { - ecs_entity_t src = filter[i].src; - bool inc = false; + if (flecs_filter_match_table(world, &o->filter, table, + user_it.ids, user_it.columns, user_it.subjects, NULL, NULL, false, -1)) + { + /* Monitor observers only trigger when the filter matches for the first + * time with an entity */ + if (o->is_monitor) { + if (world->is_fini) { + goto done; + } - filter[i].time_elapsed += it->delta_time; + if (flecs_filter_match_table(world, &o->filter, prev_table, + NULL, NULL, NULL, NULL, NULL, true, -1)) + { + goto done; + } - if (src) { - const EcsTickSource *tick_src = ecs_get(it->world, src, EcsTickSource); - if (tick_src) { - inc = tick_src->tick; - } else { - inc = true; + if (term->oper == EcsNot) { + /* Flip event if this is a Not, so OnAdd and OnRemove can be + * reliably used to check if we're entering or leaving the + * monitor */ + if (it->event == EcsOnAdd) { + user_it.event = EcsOnRemove; + } else if (it->event == EcsOnRemove) { + user_it.event = EcsOnAdd; + } } - } else { - inc = true; } - if (inc) { - filter[i].tick_count ++; - bool triggered = !(filter[i].tick_count % filter[i].rate); - tick_dst[i].tick = triggered; - tick_dst[i].time_elapsed = filter[i].time_elapsed; - - if (triggered) { - filter[i].time_elapsed = 0; - } - } else { - tick_dst[i].tick = false; - } - } -} + flecs_iter_populate_data(world, &user_it, + it->table, it->offset, it->count, user_it.ptrs, user_it.sizes); -static -void ProgressTickSource(ecs_iter_t *it) { - EcsTickSource *tick_src = ecs_term(it, EcsTickSource, 1); + user_it.ids[it->term_index] = it->event_id; + user_it.system = o->entity; + user_it.term_index = it->term_index; + user_it.self = o->self; + user_it.ctx = o->ctx; + user_it.term_count = o->filter.term_count_actual; - /* If tick source has no filters, tick unconditionally */ - int i; - for (i = 0; i < it->count; i ++) { - tick_src[i].tick = true; - tick_src[i].time_elapsed = it->delta_time; + o->action(&user_it); + o->last_event_id = world->event_id; } + +done: + flecs_iter_fini(&user_it); } -ecs_entity_t ecs_set_timeout( +ecs_entity_t ecs_observer_init( ecs_world_t *world, - ecs_entity_t timer, - FLECS_FLOAT timeout) + const ecs_observer_desc_t *desc) { + ecs_entity_t entity = 0; ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(!world->is_fini, ECS_INVALID_OPERATION, NULL); + ecs_check(desc->callback != NULL, ECS_INVALID_OPERATION, NULL); - timer = ecs_set(world, timer, EcsTimer, { - .timeout = timeout, - .single_shot = true, - .active = true - }); + /* If entity is provided, create it */ + ecs_entity_t existing = desc->entity.entity; + entity = ecs_entity_init(world, &desc->entity); - EcsSystem *system_data = ecs_get_mut(world, timer, EcsSystem, NULL); - if (system_data) { - system_data->tick_source = timer; - } + bool added = false; + EcsObserver *comp = ecs_get_mut(world, entity, EcsObserver, &added); + if (added) { + ecs_observer_t *observer = flecs_sparse_add( + world->observers, ecs_observer_t); + ecs_assert(observer != NULL, ECS_INTERNAL_ERROR, NULL); + observer->id = flecs_sparse_last_id(world->observers); -error: - return timer; -} + /* Make writeable copy of filter desc so that we can set name. This will + * make debugging easier, as any error messages related to creating the + * filter will have the name of the observer. */ + ecs_filter_desc_t filter_desc = desc->filter; + filter_desc.name = desc->entity.name; -FLECS_FLOAT ecs_get_timeout( - const ecs_world_t *world, - ecs_entity_t timer) -{ - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(timer != 0, ECS_INVALID_PARAMETER, NULL); + /* Parse filter */ + if (ecs_filter_init(world, &observer->filter, &filter_desc)) { + flecs_observer_fini(world, observer); + return 0; + } - const EcsTimer *value = ecs_get(world, timer, EcsTimer); - if (value) { - return value->timeout; + ecs_filter_t *filter = &observer->filter; + + int i; + for (i = 0; i < ECS_TRIGGER_DESC_EVENT_COUNT_MAX; i ++) { + ecs_entity_t event = desc->events[i]; + if (!event) { + break; + } + + if (event == EcsMonitor) { + /* Monitor event must be first and last event */ + ecs_check(i == 0, ECS_INVALID_PARAMETER, NULL); + + observer->events[0] = EcsOnAdd; + observer->events[1] = EcsOnRemove; + observer->event_count ++; + observer->is_monitor = true; + } else { + observer->events[i] = event; + } + + observer->event_count ++; + } + + /* Observer must have at least one event */ + ecs_check(observer->event_count != 0, ECS_INVALID_PARAMETER, NULL); + + /* Create a trigger for each term in the filter */ + observer->triggers = ecs_os_malloc_n(ecs_entity_t, + observer->filter.term_count); + + for (i = 0; i < filter->term_count; i ++) { + const ecs_term_t *terms = filter->terms; + const ecs_term_t *t = &terms[i]; + + if (terms[i].subj.entity != EcsThis) { + observer->triggers[i] = 0; + continue; + } + + ecs_trigger_desc_t trigger_desc = { + .term = *t, + .callback = observer_callback, + .ctx = observer, + .binding_ctx = desc->binding_ctx, + .match_prefab = observer->filter.match_prefab, + .match_disabled = observer->filter.match_disabled + }; + + if (observer->filter.filter) { + trigger_desc.term.inout = EcsInOutFilter; + } + + ecs_os_memcpy_n(trigger_desc.events, observer->events, ecs_entity_t, + observer->event_count); + + observer->triggers[i] = ecs_trigger_init(world, &trigger_desc); + } + + observer->action = desc->callback; + observer->self = desc->self; + observer->ctx = desc->ctx; + observer->binding_ctx = desc->binding_ctx; + observer->ctx_free = desc->ctx_free; + observer->binding_ctx_free = desc->binding_ctx_free; + observer->entity = entity; + + comp->observer = observer; + + if (desc->entity.name) { + ecs_trace("#[green]observer#[reset] %s created", + ecs_get_name(world, entity)); + } + } else { + ecs_assert(comp->observer != NULL, ECS_INTERNAL_ERROR, NULL); + + /* If existing entity handle was provided, override existing params */ + if (existing) { + if (desc->callback) { + ((ecs_observer_t*)comp->observer)->action = desc->callback; + } + if (desc->ctx) { + ((ecs_observer_t*)comp->observer)->ctx = desc->ctx; + } + if (desc->binding_ctx) { + ((ecs_observer_t*)comp->observer)->binding_ctx = + desc->binding_ctx; + } + } } + + return entity; error: + if (entity) { + ecs_delete(world, entity); + } return 0; } -ecs_entity_t ecs_set_interval( +void flecs_observer_fini( ecs_world_t *world, - ecs_entity_t timer, - FLECS_FLOAT interval) + ecs_observer_t *observer) { - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + int i, count = observer->filter.term_count; + for (i = 0; i < count; i ++) { + ecs_entity_t trigger = observer->triggers[i]; + if (trigger) { + ecs_delete(world, trigger); + } + } + ecs_os_free(observer->triggers); - timer = ecs_set(world, timer, EcsTimer, { - .timeout = interval, - .active = true - }); + ecs_filter_fini(&observer->filter); - EcsSystem *system_data = ecs_get_mut(world, timer, EcsSystem, NULL); - if (system_data) { - system_data->tick_source = timer; + if (observer->ctx_free) { + observer->ctx_free(observer->ctx); } -error: - return timer; + + if (observer->binding_ctx_free) { + observer->binding_ctx_free(observer->binding_ctx); + } + + flecs_sparse_remove(world->observers, observer->id); } -FLECS_FLOAT ecs_get_interval( +void* ecs_get_observer_ctx( const ecs_world_t *world, - ecs_entity_t timer) + ecs_entity_t observer) { - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - - if (!timer) { - return 0; - } + const EcsObserver *o = ecs_get(world, observer, EcsObserver); + if (o) { + return o->observer->ctx; + } else { + return NULL; + } +} - const EcsTimer *value = ecs_get(world, timer, EcsTimer); - if (value) { - return value->timeout; - } -error: - return 0; +void* ecs_get_observer_binding_ctx( + const ecs_world_t *world, + ecs_entity_t observer) +{ + const EcsObserver *o = ecs_get(world, observer, EcsObserver); + if (o) { + return o->observer->binding_ctx; + } else { + return NULL; + } } -void ecs_start_timer( - ecs_world_t *world, - ecs_entity_t timer) +void flecs_observable_init( + ecs_observable_t *observable) { - EcsTimer *ptr = ecs_get_mut(world, timer, EcsTimer, NULL); - ecs_check(ptr != NULL, ECS_INVALID_PARAMETER, NULL); - ptr->active = true; - ptr->time = 0; -error: - return; + observable->events = ecs_sparse_new(ecs_event_record_t); } -void ecs_stop_timer( - ecs_world_t *world, - ecs_entity_t timer) +void flecs_observable_fini( + ecs_observable_t *observable) { - EcsTimer *ptr = ecs_get_mut(world, timer, EcsTimer, NULL); - ecs_check(ptr != NULL, ECS_INVALID_PARAMETER, NULL); - ptr->active = false; -error: - return; + ecs_sparse_t *triggers = observable->events; + int32_t i, count = flecs_sparse_count(triggers); + + for (i = 0; i < count; i ++) { + ecs_event_record_t *et = + ecs_sparse_get_dense(triggers, ecs_event_record_t, i); + ecs_assert(et != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_map_iter_t it = ecs_map_iter(et->event_ids); + ecs_event_id_record_t *idt; + while ((idt = ecs_map_next(&it, ecs_event_id_record_t, NULL))) { + ecs_map_free(idt->triggers); + ecs_map_free(idt->set_triggers); + } + ecs_map_free(et->event_ids); + } + + flecs_sparse_free(observable->events); } -ecs_entity_t ecs_set_rate( +static +void notify_subset( ecs_world_t *world, - ecs_entity_t filter, - int32_t rate, - ecs_entity_t source) + ecs_iter_t *it, + ecs_observable_t *observable, + ecs_entity_t entity, + ecs_entity_t event, + ecs_ids_t *ids) { - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_id_t pair = ecs_pair(EcsWildcard, entity); + ecs_id_record_t *idr = flecs_get_id_record(world, pair); + if (!idr) { + return; + } - filter = ecs_set(world, filter, EcsRateFilter, { - .rate = rate, - .src = source - }); + ecs_table_record_t *trs = ecs_table_cache_tables( + &idr->cache, ecs_table_record_t); + int32_t i, count = ecs_table_cache_count(&idr->cache); - EcsSystem *system_data = ecs_get_mut(world, filter, EcsSystem, NULL); - if (system_data) { - system_data->tick_source = filter; - } + for (i = 0; i < count; i ++) { + ecs_table_record_t *tr = &trs[i]; + ecs_table_t *table = tr->table; + ecs_id_t id = ecs_vector_get(table->type, ecs_id_t, tr->column)[0]; + ecs_entity_t rel = ECS_PAIR_RELATION(id); -error: - return filter; + if (ecs_is_valid(world, rel) && !ecs_has_id(world, rel, EcsAcyclic)) { + /* Only notify for acyclic relations */ + continue; + } + + int32_t e, entity_count = ecs_table_count(table); + it->table = table; + it->type = table->type; + it->other_table = NULL; + it->offset = 0; + it->count = entity_count; + + flecs_set_triggers_notify(it, observable, ids, event, + ecs_pair(rel, EcsWildcard)); + + ecs_entity_t *entities = ecs_vector_first( + table->storage.entities, ecs_entity_t); + ecs_record_t **records = ecs_vector_first( + table->storage.record_ptrs, ecs_record_t*); + + for (e = 0; e < entity_count; e ++) { + uint32_t flags = ECS_RECORD_TO_ROW_FLAGS(records[e]->row); + if (flags & ECS_FLAG_OBSERVED_ACYCLIC) { + /* Only notify for entities that are used in pairs with + * acyclic relations */ + notify_subset(world, it, observable, entities[e], event, ids); + } + } + } } -void ecs_set_tick_source( +void ecs_emit( ecs_world_t *world, - ecs_entity_t system, - ecs_entity_t tick_source) + ecs_event_desc_t *desc) { - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(system != 0, ECS_INVALID_PARAMETER, NULL); - ecs_check(tick_source != 0, ECS_INVALID_PARAMETER, NULL); + 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); - EcsSystem *system_data = ecs_get_mut(world, system, EcsSystem, NULL); - ecs_check(system_data != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_ids_t *ids = desc->ids; + ecs_entity_t event = desc->event; + ecs_table_t *table = desc->table; + int32_t row = desc->offset; + int32_t i, count = desc->count; - system_data->tick_source = tick_source; + if (!count) { + count = ecs_table_count(table) - row; + } + + ecs_iter_t it = { + .world = world, + .table = table, + .type = table->type, + .term_count = 1, + .other_table = desc->other_table, + .offset = row, + .count = count, + .param = desc->param + }; + + world->event_id ++; + + ecs_observable_t *observable = ecs_get_observable(desc->observable); + ecs_check(observable != NULL, ECS_INVALID_PARAMETER, NULL); + + flecs_triggers_notify(&it, observable, ids, event); + + ecs_record_t **recs = ecs_vector_get( + table->storage.record_ptrs, ecs_record_t*, row); + + for (i = 0; i < count; i ++) { + uint32_t flags = ECS_RECORD_TO_ROW_FLAGS(recs[i]->row); + if (flags & ECS_FLAG_OBSERVED_ACYCLIC) { + notify_subset(world, &it, observable, ecs_vector_first( + table->storage.entities, ecs_entity_t)[row + i], + event, ids); + } + } + error: return; } -void FlecsTimerImport( - ecs_world_t *world) -{ - ECS_MODULE(world, FlecsTimer); - - ECS_IMPORT(world, FlecsPipeline); +#ifdef FLECS_SYSTEM +#ifndef FLECS_SYSTEM_PRIVATE_H +#define FLECS_SYSTEM_PRIVATE_H - ecs_set_name_prefix(world, "Ecs"); +#ifdef FLECS_SYSTEM - flecs_bootstrap_component(world, EcsTimer); - flecs_bootstrap_component(world, EcsRateFilter); - /* Add EcsTickSource to timers and rate filters */ - ecs_system_init(world, &(ecs_system_desc_t) { - .entity = { .name = "AddTickSource", .add = { EcsPreFrame } }, - .query.filter.terms = { - { .id = ecs_id(EcsTimer), .oper = EcsOr, .inout = EcsIn }, - { .id = ecs_id(EcsRateFilter), .oper = EcsOr, .inout = EcsIn }, - { .id = ecs_id(EcsTickSource), .oper = EcsNot, .inout = EcsOut} - }, - .callback = AddTickSource - }); +typedef struct EcsSystem { + ecs_iter_action_t action; /* Callback to be invoked for matching it */ - /* Timer handling */ - ecs_system_init(world, &(ecs_system_desc_t) { - .entity = { .name = "ProgressTimers", .add = { EcsPreFrame } }, - .query.filter.terms = { - { .id = ecs_id(EcsTimer) }, - { .id = ecs_id(EcsTickSource) } - }, - .callback = ProgressTimers - }); + ecs_entity_t entity; /* Entity id of system, used for ordering */ + ecs_query_t *query; /* System query */ + ecs_system_status_action_t status_action; /* Status action */ + ecs_entity_t tick_source; /* Tick source associated with system */ + + /* Schedule parameters */ + bool multi_threaded; + bool no_staging; - /* Rate filter handling */ - ecs_system_init(world, &(ecs_system_desc_t) { - .entity = { .name = "ProgressRateFilters", .add = { EcsPreFrame } }, - .query.filter.terms = { - { .id = ecs_id(EcsRateFilter), .inout = EcsIn }, - { .id = ecs_id(EcsTickSource), .inout = EcsOut } - }, - .callback = ProgressRateFilters - }); + int32_t invoke_count; /* Number of times system is invoked */ + float time_spent; /* Time spent on running system */ + FLECS_FLOAT time_passed; /* Time passed since last invocation */ + int32_t last_frame; /* Last frame for which the system was considered */ - /* TickSource without a timer or rate filter just increases each frame */ - ecs_system_init(world, &(ecs_system_desc_t) { - .entity = { .name = "ProgressTickSource", .add = { EcsPreFrame } }, - .query.filter.terms = { - { .id = ecs_id(EcsTickSource), .inout = EcsOut }, - { .id = ecs_id(EcsRateFilter), .oper = EcsNot }, - { .id = ecs_id(EcsTimer), .oper = EcsNot } - }, - .callback = ProgressTickSource - }); -} + ecs_entity_t self; /* Entity associated with system */ -#endif + void *ctx; /* Userdata for system */ + void *status_ctx; /* User data for status action */ + void *binding_ctx; /* Optional language binding context */ + ecs_ctx_free_t ctx_free; + ecs_ctx_free_t status_ctx_free; + ecs_ctx_free_t binding_ctx_free; +} EcsSystem; -#ifdef FLECS_DEPRECATED +/* Invoked when system becomes active / inactive */ +void ecs_system_activate( + ecs_world_t *world, + ecs_entity_t system, + bool activate, + const EcsSystem *system_data); +/* Internal function to run a system */ +ecs_entity_t ecs_run_intern( + ecs_world_t *world, + ecs_stage_t *stage, + ecs_entity_t system, + EcsSystem *system_data, + int32_t stage_current, + int32_t stage_count, + FLECS_FLOAT delta_time, + int32_t offset, + int32_t limit, + void *param); #endif - -#ifdef FLECS_OS_API_IMPL -#ifdef _MSC_VER -#include +#endif +#endif static -ecs_os_thread_t win_thread_new( - ecs_os_thread_callback_t callback, - void *arg) +void compute_group_id( + ecs_query_t *query, + ecs_query_table_match_t *match) { - HANDLE *thread = ecs_os_malloc_t(HANDLE); - *thread = CreateThread( - NULL, 0, (LPTHREAD_START_ROUTINE)callback, arg, 0, NULL); - return (ecs_os_thread_t)(uintptr_t)thread; -} + ecs_assert(match != NULL, ECS_INTERNAL_ERROR, NULL); -static -void* win_thread_join( - ecs_os_thread_t thr) -{ - HANDLE *thread = (HANDLE*)(uintptr_t)thr; - WaitForSingleObject(thread, INFINITE); - ecs_os_free(thread); - return NULL; -} + if (query->group_by) { + ecs_table_t *table = match->table; + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); -static -int32_t win_ainc( - int32_t *count) -{ - return InterlockedIncrement(count); + match->group_id = query->group_by(query->world, table->type, + query->group_by_id, query->group_by_ctx); + } else { + match->group_id = 0; + } } static -int32_t win_adec( - int32_t *count) +ecs_query_table_list_t* get_group( + ecs_query_t *query, + uint64_t group_id) { - return InterlockedDecrement(count); -} - -static -ecs_os_mutex_t win_mutex_new(void) { - CRITICAL_SECTION *mutex = ecs_os_malloc_t(CRITICAL_SECTION); - InitializeCriticalSection(mutex); - return (ecs_os_mutex_t)(uintptr_t)mutex; + return ecs_map_get(query->groups, ecs_query_table_list_t, group_id); } static -void win_mutex_free( - ecs_os_mutex_t m) +ecs_query_table_list_t* ensure_group( + ecs_query_t *query, + uint64_t group_id) { - CRITICAL_SECTION *mutex = (CRITICAL_SECTION*)(intptr_t)m; - DeleteCriticalSection(mutex); - ecs_os_free(mutex); + return ecs_map_ensure(query->groups, ecs_query_table_list_t, group_id); } +/* Find the last node of the group after which this group should be inserted */ static -void win_mutex_lock( - ecs_os_mutex_t m) +ecs_query_table_node_t* find_group_insertion_node( + ecs_query_t *query, + uint64_t group_id) { - CRITICAL_SECTION *mutex = (CRITICAL_SECTION*)(intptr_t)m; - EnterCriticalSection(mutex); -} + /* Grouping must be enabled */ + ecs_assert(query->group_by != NULL, ECS_INTERNAL_ERROR, NULL); -static -void win_mutex_unlock( - ecs_os_mutex_t m) -{ - CRITICAL_SECTION *mutex = (CRITICAL_SECTION*)(intptr_t)m; - LeaveCriticalSection(mutex); -} - -static -ecs_os_cond_t win_cond_new(void) { - CONDITION_VARIABLE *cond = ecs_os_malloc_t(CONDITION_VARIABLE); - InitializeConditionVariable(cond); - return (ecs_os_cond_t)(uintptr_t)cond; -} - -static -void win_cond_free( - ecs_os_cond_t c) -{ - (void)c; -} - -static -void win_cond_signal( - ecs_os_cond_t c) -{ - CONDITION_VARIABLE *cond = (CONDITION_VARIABLE*)(intptr_t)c; - WakeConditionVariable(cond); -} - -static -void win_cond_broadcast( - ecs_os_cond_t c) -{ - CONDITION_VARIABLE *cond = (CONDITION_VARIABLE*)(intptr_t)c; - WakeAllConditionVariable(cond); -} - -static -void win_cond_wait( - ecs_os_cond_t c, - ecs_os_mutex_t m) -{ - CRITICAL_SECTION *mutex = (CRITICAL_SECTION*)(intptr_t)m; - CONDITION_VARIABLE *cond = (CONDITION_VARIABLE*)(intptr_t)c; - SleepConditionVariableCS(cond, mutex, INFINITE); -} - -void ecs_set_os_api_impl(void) { - ecs_os_set_api_defaults(); - - ecs_os_api_t api = ecs_os_api; - - api.thread_new_ = win_thread_new; - api.thread_join_ = win_thread_join; - api.ainc_ = win_ainc; - api.adec_ = win_adec; - api.mutex_new_ = win_mutex_new; - api.mutex_free_ = win_mutex_free; - api.mutex_lock_ = win_mutex_lock; - api.mutex_unlock_ = win_mutex_unlock; - api.cond_new_ = win_cond_new; - api.cond_free_ = win_cond_free; - api.cond_signal_ = win_cond_signal; - api.cond_broadcast_ = win_cond_broadcast; - api.cond_wait_ = win_cond_wait; - - ecs_os_set_api(&api); -} - -#else + ecs_map_iter_t it = ecs_map_iter(query->groups); + ecs_query_table_list_t *list, *closest_list = NULL; + uint64_t id, closest_id = 0; -#include "pthread.h" + /* Find closest smaller group id */ + while ((list = ecs_map_next(&it, ecs_query_table_list_t, &id))) { + if (id >= group_id) { + continue; + } -static -ecs_os_thread_t posix_thread_new( - ecs_os_thread_callback_t callback, - void *arg) -{ - pthread_t *thread = ecs_os_malloc(sizeof(pthread_t)); + if (!list->last) { + ecs_assert(list->first == NULL, ECS_INTERNAL_ERROR, NULL); + continue; + } - if (pthread_create (thread, NULL, callback, arg) != 0) { - ecs_os_abort(); + if (!closest_list || ((group_id - id) < (group_id - closest_id))) { + closest_id = id; + closest_list = list; + } } - return (ecs_os_thread_t)(uintptr_t)thread; + if (closest_list) { + return closest_list->last; + } else { + return NULL; /* Group should be first in query */ + } } +/* Initialize group with first node */ static -void* posix_thread_join( - ecs_os_thread_t thread) +void create_group( + ecs_query_t *query, + ecs_query_table_node_t *node) { - void *arg; - pthread_t *thr = (pthread_t*)(uintptr_t)thread; - pthread_join(*thr, &arg); - ecs_os_free(thr); - return arg; -} + ecs_query_table_match_t *match = node->match; + uint64_t group_id = match->group_id; -static -int32_t posix_ainc( - int32_t *count) -{ - int value; -#ifdef __GNUC__ - value = __sync_add_and_fetch (count, 1); - return value; -#else - /* Unsupported */ - abort(); -#endif -} + /* If query has grouping enabled & this is a new/empty group, find + * the insertion point for the group */ + ecs_query_table_node_t *insert_after = find_group_insertion_node( + query, group_id); -static -int32_t posix_adec( - int32_t *count) -{ - int value; -#ifdef __GNUC__ - value = __sync_sub_and_fetch (count, 1); - return value; -#else - /* Unsupported */ - abort(); -#endif -} + if (!insert_after) { + /* This group should appear first in the query list */ + ecs_query_table_node_t *query_first = query->list.first; + if (query_first) { + /* If this is not the first match for the query, insert before it */ + node->next = query_first; + query_first->prev = node; + query->list.first = node; + } else { + /* If this is the first match of the query, initialize its list */ + ecs_assert(query->list.last == NULL, ECS_INTERNAL_ERROR, NULL); + query->list.first = node; + query->list.last = node; + } + } else { + ecs_assert(query->list.first != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(query->list.last != NULL, ECS_INTERNAL_ERROR, NULL); -static -ecs_os_mutex_t posix_mutex_new(void) { - pthread_mutex_t *mutex = ecs_os_malloc(sizeof(pthread_mutex_t)); - if (pthread_mutex_init(mutex, NULL)) { - abort(); + /* This group should appear after another group */ + ecs_query_table_node_t *insert_before = insert_after->next; + node->prev = insert_after; + insert_after->next = node; + node->next = insert_before; + if (insert_before) { + insert_before->prev = node; + } else { + ecs_assert(query->list.last == insert_after, + ECS_INTERNAL_ERROR, NULL); + + /* This group should appear last in the query list */ + query->list.last = node; + } } - return (ecs_os_mutex_t)(uintptr_t)mutex; } static -void posix_mutex_free( - ecs_os_mutex_t m) +void remove_group( + ecs_query_t *query, + uint64_t group_id) { - pthread_mutex_t *mutex = (pthread_mutex_t*)(intptr_t)m; - pthread_mutex_destroy(mutex); - ecs_os_free(mutex); + ecs_map_remove(query->groups, group_id); } +/* Find the list the node should be part of */ static -void posix_mutex_lock( - ecs_os_mutex_t m) +ecs_query_table_list_t* get_node_list( + ecs_query_t *query, + ecs_query_table_node_t *node) { - pthread_mutex_t *mutex = (pthread_mutex_t*)(intptr_t)m; - if (pthread_mutex_lock(mutex)) { - abort(); + ecs_query_table_match_t *match = node->match; + if (query->group_by) { + return get_group(query, match->group_id); + } else { + return &query->list; } } +/* Find or create the list the node should be part of */ static -void posix_mutex_unlock( - ecs_os_mutex_t m) +ecs_query_table_list_t* ensure_node_list( + ecs_query_t *query, + ecs_query_table_node_t *node) { - pthread_mutex_t *mutex = (pthread_mutex_t*)(intptr_t)m; - if (pthread_mutex_unlock(mutex)) { - abort(); + ecs_query_table_match_t *match = node->match; + if (query->group_by) { + return ensure_group(query, match->group_id); + } else { + return &query->list; } } +/* Remove node from list */ static -ecs_os_cond_t posix_cond_new(void) { - pthread_cond_t *cond = ecs_os_malloc(sizeof(pthread_cond_t)); - if (pthread_cond_init(cond, NULL)) { - abort(); - } - return (ecs_os_cond_t)(uintptr_t)cond; -} - -static -void posix_cond_free( - ecs_os_cond_t c) +void remove_table_node( + ecs_query_t *query, + ecs_query_table_node_t *node) { - pthread_cond_t *cond = (pthread_cond_t*)(intptr_t)c; - if (pthread_cond_destroy(cond)) { - abort(); - } - ecs_os_free(cond); -} + ecs_query_table_node_t *prev = node->prev; + ecs_query_table_node_t *next = node->next; -static -void posix_cond_signal( - ecs_os_cond_t c) -{ - pthread_cond_t *cond = (pthread_cond_t*)(intptr_t)c; - if (pthread_cond_signal(cond)) { - abort(); - } -} + ecs_assert(prev != node, ECS_INTERNAL_ERROR, NULL); + ecs_assert(next != node, ECS_INTERNAL_ERROR, NULL); + ecs_assert(!prev || prev != next, ECS_INTERNAL_ERROR, NULL); -static -void posix_cond_broadcast( - ecs_os_cond_t c) -{ - pthread_cond_t *cond = (pthread_cond_t*)(intptr_t)c; - if (pthread_cond_broadcast(cond)) { - abort(); + ecs_query_table_list_t *list = get_node_list(query, node); + + if (!list || !list->first) { + /* If list contains no nodes, the node must be empty */ + ecs_assert(!list || list->last == NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(prev == NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(next == NULL, ECS_INTERNAL_ERROR, NULL); + return; } -} -static -void posix_cond_wait( - ecs_os_cond_t c, - ecs_os_mutex_t m) -{ - pthread_cond_t *cond = (pthread_cond_t*)(intptr_t)c; - pthread_mutex_t *mutex = (pthread_mutex_t*)(intptr_t)m; - if (pthread_cond_wait(cond, mutex)) { - abort(); + ecs_assert(prev != NULL || query->list.first == node, + ECS_INTERNAL_ERROR, NULL); + ecs_assert(next != NULL || query->list.last == node, + ECS_INTERNAL_ERROR, NULL); + + if (prev) { + prev->next = next; + } + if (next) { + next->prev = prev; } -} -void ecs_set_os_api_impl(void) { - ecs_os_set_api_defaults(); + ecs_assert(list->count > 0, ECS_INTERNAL_ERROR, NULL); + list->count --; - ecs_os_api_t api = ecs_os_api; + if (query->group_by) { + ecs_query_table_match_t *match = node->match; + uint64_t group_id = match->group_id; - api.thread_new_ = posix_thread_new; - api.thread_join_ = posix_thread_join; - api.ainc_ = posix_ainc; - api.adec_ = posix_adec; - api.mutex_new_ = posix_mutex_new; - api.mutex_free_ = posix_mutex_free; - api.mutex_lock_ = posix_mutex_lock; - api.mutex_unlock_ = posix_mutex_unlock; - api.cond_new_ = posix_cond_new; - api.cond_free_ = posix_cond_free; - api.cond_signal_ = posix_cond_signal; - api.cond_broadcast_ = posix_cond_broadcast; - api.cond_wait_ = posix_cond_wait; + /* Make sure query.list is updated if this is the first or last group */ + if (query->list.first == node) { + ecs_assert(prev == NULL, ECS_INTERNAL_ERROR, NULL); + query->list.first = next; + prev = next; + } + if (query->list.last == node) { + ecs_assert(next == NULL, ECS_INTERNAL_ERROR, NULL); + query->list.last = prev; + next = prev; + } - ecs_os_set_api(&api); -} + ecs_assert(query->list.count > 0, ECS_INTERNAL_ERROR, NULL); + query->list.count --; -#endif -#endif + /* Make sure group list only contains nodes that belong to the group */ + if (prev && prev->match->group_id != group_id) { + /* The previous node belonged to another group */ + prev = next; + } + if (next && next->match->group_id != group_id) { + /* The next node belonged to another group */ + next = prev; + } + /* Do check again, in case both prev & next belonged to another group */ + if (prev && prev->match->group_id != group_id) { + /* There are no more matches left in this group */ + remove_group(query, group_id); + list = NULL; + } + } -#ifdef FLECS_PLECS + if (list) { + if (list->first == node) { + list->first = next; + } + if (list->last == node) { + list->last = prev; + } + } + node->prev = NULL; + node->next = NULL; -#define TOK_NEWLINE '\n' -#define TOK_WITH "with" -#define TOK_USING "using" +#ifdef FLECS_SYSTEM + if (query->list.first == NULL && query->system && !query->world->is_fini) { + ecs_system_activate(query->world, query->system, false, NULL); + } +#endif -#define STACK_MAX_SIZE (64) + query->match_count ++; +} -typedef struct { - const char *name; - const char *code; +/* Add node to list */ +static +void insert_table_node( + ecs_query_t *query, + ecs_query_table_node_t *node) +{ + /* Node should not be part of an existing list */ + ecs_assert(node->prev == NULL && node->next == NULL, + ECS_INTERNAL_ERROR, NULL); - ecs_entity_t last_predicate; - ecs_entity_t last_subject; - ecs_entity_t last_object; + /* If this is the first match, activate system */ +#ifdef FLECS_SYSTEM + if (!query->list.first && query->system) { + ecs_system_activate(query->world, query->system, true, NULL); + } +#endif - ecs_id_t last_assign_id; - ecs_entity_t assign_to; + compute_group_id(query, node->match); - ecs_entity_t scope[STACK_MAX_SIZE]; - ecs_entity_t default_scope_type[STACK_MAX_SIZE]; - ecs_entity_t with[STACK_MAX_SIZE]; - ecs_entity_t using[STACK_MAX_SIZE]; - int32_t with_frames[STACK_MAX_SIZE]; - int32_t using_frames[STACK_MAX_SIZE]; - int32_t sp; - int32_t with_frame; - int32_t using_frame; + ecs_query_table_list_t *list = ensure_node_list(query, node); + if (list->last) { + ecs_assert(query->list.first != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(query->list.last != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(list->first != NULL, ECS_INTERNAL_ERROR, NULL); - char *comment; + ecs_query_table_node_t *last = list->last; + ecs_query_table_node_t *last_next = last->next; - bool with_stmt; - bool scope_assign_stmt; - bool using_stmt; - bool assign_stmt; - bool isa_stmt; + node->prev = last; + node->next = last_next; + last->next = node; - int32_t errors; -} plecs_state_t; + if (last_next) { + last_next->prev = node; + } -static -ecs_entity_t plecs_lookup( - const ecs_world_t *world, - const char *path, - plecs_state_t *state, - bool is_subject) -{ - ecs_entity_t e = 0; + list->last = node; - if (!is_subject) { - int using_scope = state->using_frame - 1; - for (; using_scope >= 0; using_scope--) { - e = ecs_lookup_path_w_sep( - world, state->using[using_scope], path, NULL, NULL, false); - if (e) { - break; + if (query->group_by) { + /* Make sure to update query list if this is the last group */ + if (query->list.last == last) { + query->list.last = node; } } + } else { + ecs_assert(list->first == NULL, ECS_INTERNAL_ERROR, NULL); + + list->first = node; + list->last = node; + + if (query->group_by) { + /* Initialize group with its first node */ + create_group(query, node); + } } - if (!e) { - e = ecs_lookup_path_w_sep(world, 0, path, NULL, NULL, !is_subject); + if (query->group_by) { + query->list.count ++; } - return e; -} + list->count ++; + query->match_count ++; -/* Lookup action used for deserializing entity refs in component values */ -#ifdef FLECS_EXPR -static -ecs_entity_t plecs_lookup_action( - const ecs_world_t *world, - const char *path, - void *ctx) -{ - return plecs_lookup(world, path, ctx, false); + ecs_assert(node->prev != node, ECS_INTERNAL_ERROR, NULL); + ecs_assert(node->next != node, ECS_INTERNAL_ERROR, NULL); + + ecs_assert(list->first != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(list->last != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(list->last == node, ECS_INTERNAL_ERROR, NULL); + ecs_assert(query->list.first != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(query->list.last != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(query->list.first->prev == NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(query->list.last->next == NULL, ECS_INTERNAL_ERROR, NULL); } -#endif static -void clear_comment( - const char *expr, - const char *ptr, - plecs_state_t *state) +ecs_query_table_match_t* cache_add( + ecs_query_table_t *elem) { - if (state->comment) { - ecs_parser_error(state->name, expr, ptr - expr, "unused doc comment"); - ecs_os_free(state->comment); - state->comment = NULL; + ecs_query_table_match_t *result = ecs_os_calloc_t(ecs_query_table_match_t); + ecs_query_table_node_t *node = &result->node; - state->errors ++; /* Non-fatal error */ + node->match = result; + if (!elem->first) { + elem->first = result; + elem->last = result; + } else { + ecs_assert(elem->last != NULL, ECS_INTERNAL_ERROR, NULL); + elem->last->next_match = result; + elem->last = result; } + + return result; } +/* Builtin group_by callback for Cascade terms. + * This function traces the hierarchy depth of an entity type by following a + * relation upwards (to its 'parents') for as long as those parents have the + * specified component id. + * The result of the function is the number of parents with the provided + * component for a given relation. */ static -const char* parse_fluff( - const char *expr, - const char *ptr, - plecs_state_t *state) -{ - char *comment; - const char *next = ecs_parse_fluff(ptr, &comment); +uint64_t group_by_cascade( + ecs_world_t *world, + ecs_type_t type, + ecs_entity_t component, + void *ctx) +{ + uint64_t result = 0; + int32_t i, count = ecs_vector_count(type); + ecs_entity_t *array = ecs_vector_first(type, ecs_entity_t); + ecs_term_t *term = ctx; + ecs_entity_t relation = term->subj.set.relation; - if (comment && comment[0] == '/') { - comment = (char*)ecs_parse_fluff(comment + 1, NULL); - int32_t len = (ecs_size_t)(next - comment); - int32_t newline_count = 0; + /* Cascade needs a relation to calculate depth from */ + ecs_check(relation != 0, ECS_INVALID_PARAMETER, NULL); - /* Trim trailing whitespaces */ - while (len >= 0 && (isspace(comment[len - 1]))) { - if (comment[len - 1] == '\n') { - newline_count ++; - if (newline_count > 1) { - /* If newline separates comment from statement, discard */ - len = -1; + /* Should only be used with cascade terms */ + ecs_check(term->subj.set.mask & EcsCascade, ECS_INVALID_PARAMETER, NULL); + + /* Iterate back to front as relations are more likely to occur near the + * end of a type. */ + for (i = count - 1; i >= 0; i --) { + /* Find relation & relation object in entity type */ + if (ECS_HAS_RELATION(array[i], relation)) { + ecs_type_t obj_type = ecs_get_type(world, + ecs_pair_object(world, array[i])); + int32_t j, c_count = ecs_vector_count(obj_type); + ecs_entity_t *c_array = ecs_vector_first(obj_type, ecs_entity_t); + + /* Iterate object type, check if it has the specified component */ + for (j = 0; j < c_count; j ++) { + /* If it has the component, it is part of the tree matched by + * the query, increase depth */ + if (c_array[j] == component) { + result ++; + + /* Recurse to test if the object has matching parents */ + result += group_by_cascade(world, obj_type, component, ctx); break; } } - len --; - } - if (len > 0) { - clear_comment(expr, ptr, state); - state->comment = ecs_os_calloc_n(char, len + 1); - ecs_os_strncpy(state->comment, comment, len); - } else { - ecs_parser_error(state->name, expr, ptr - expr, - "unused doc comment"); - state->errors ++; - } - } else { - if (ptr != next && state->comment) { - clear_comment(expr, ptr, state); + if (j != c_count) { + break; + } + + /* If the id doesn't have a role set, we'll find no more relations */ + } else if (!(array[i] & ECS_ROLE_MASK)) { + break; } } - return next; + return result; +error: + return 0; } static -ecs_entity_t ensure_entity( +int get_comp_and_src( ecs_world_t *world, - plecs_state_t *state, - const char *path, - bool is_subject) + ecs_query_t *query, + int32_t t, + ecs_table_t *table_arg, + ecs_entity_t *component_out, + ecs_entity_t *entity_out, + bool *match_out) { - if (!path) { - return 0; + ecs_entity_t component = 0, entity = 0; + + ecs_term_t *terms = query->filter.terms; + int32_t term_count = query->filter.term_count; + ecs_term_t *term = &terms[t]; + ecs_term_id_t *subj = &term->subj; + ecs_oper_kind_t op = term->oper; + + *match_out = true; + + if (op == EcsNot) { + entity = subj->entity; } - ecs_entity_t e = plecs_lookup(world, path, state, is_subject); - if (!e) { - if (!is_subject) { - /* If this is not a subject create an existing empty id, which - * ensures that scope & with are not applied */ - e = ecs_new_id(world); + if (!subj->entity) { + component = term->id; + } else { + ecs_table_t *table = table_arg; + if (subj->entity != EcsThis) { + table = ecs_get_table(world, subj->entity); } - e = ecs_add_path(world, e, 0, path); - ecs_assert(e != 0, ECS_INTERNAL_ERROR, NULL); - } else { - /* If entity exists, make sure it gets the right scope and with */ - if (is_subject) { - ecs_entity_t scope = ecs_get_scope(world); - if (scope) { - ecs_add_pair(world, e, EcsChildOf, scope); + ecs_type_t type = NULL; + if (table) { + type = table->type; + } + + if (op == EcsOr) { + for (; t < term_count; t ++) { + term = &terms[t]; + + /* Keep iterating until the next non-OR expression */ + if (term->oper != EcsOr) { + t --; + break; + } + + if (!component) { + ecs_entity_t source = 0; + int32_t result = ecs_type_match(world, table, type, + 0, term->id, subj->set.relation, subj->set.min_depth, + subj->set.max_depth, &source, NULL, NULL); + + if (result != -1) { + component = term->id; + } + + if (source) { + entity = source; + } + } } + } else { + component = term->id; - ecs_entity_t with = ecs_get_with(world); - if (with) { - ecs_add_id(world, e, with); + ecs_entity_t source = 0; + bool result = ecs_type_match(world, table, type, 0, component, + subj->set.relation, subj->set.min_depth, subj->set.max_depth, + &source, NULL, NULL) != -1; + + *match_out = result; + + if (op == EcsNot) { + result = !result; } - } - } - return e; -} + /* Optional terms may not have the component. *From terms contain + * the id of a type of which the contents must match, but the type + * itself does not need to match. */ + if (op == EcsOptional || op == EcsAndFrom || op == EcsOrFrom || + op == EcsNotFrom) + { + result = true; + } -static -bool pred_is_subj( - ecs_term_t *term, - plecs_state_t *state) -{ - if (term->subj.name != NULL) { - return false; - } - if (term->obj.name != NULL) { - return false; - } - if (term->subj.set.mask == EcsNothing) { - return false; - } - if (state->with_stmt) { - return false; - } - if (state->assign_stmt) { - return false; - } - if (state->isa_stmt) { - return false; + /* Table has already been matched, so unless column is optional + * any components matched from the table must be available. */ + if (table == table_arg) { + ecs_assert(result == true, ECS_INTERNAL_ERROR, NULL); + } + + if (source) { + entity = source; + } + } + + if (subj->entity != EcsThis) { + entity = subj->entity; + } } - if (state->using_stmt) { - return false; + + if (entity == EcsThis) { + entity = 0; } - return true; + *component_out = component; + *entity_out = entity; + + return t; } +typedef struct pair_offset_t { + int32_t index; + int32_t count; +} pair_offset_t; + +/* Get index for specified pair. Take into account that a pair can be matched + * multiple times per table, by keeping an offset of the last found index */ static -int create_term( - ecs_world_t *world, - ecs_term_t *term, - const char *name, - const char *expr, - int64_t column, - plecs_state_t *state) +int32_t get_pair_index( + ecs_type_t table_type, + ecs_id_t pair, + int32_t column_index, + pair_offset_t *pair_offsets, + int32_t count) { - state->last_subject = 0; - state->last_predicate = 0; - state->last_object = 0; - state->last_assign_id = 0; - - if (!ecs_term_id_is_set(&term->pred)) { - ecs_parser_error(name, expr, column, "missing predicate in expression"); - return -1; - } + int32_t result; - if (state->assign_stmt && term->subj.entity != EcsThis) { - ecs_parser_error(name, expr, column, - "invalid statement in assign statement"); - return -1; + /* The count variable keeps track of the number of times a pair has been + * matched with the current table. Compare the count to check if the index + * was already resolved for this iteration */ + if (pair_offsets[column_index].count == count) { + /* If it was resolved, return the last stored index. Subtract one as the + * index is offset by one, to ensure we're not getting stuck on the same + * index. */ + result = pair_offsets[column_index].index - 1; + } else { + /* First time for this iteration that the pair index is resolved, look + * it up in the type. */ + result = ecs_type_index_of(table_type, + pair_offsets[column_index].index, pair); + pair_offsets[column_index].index = result + 1; + pair_offsets[column_index].count = count; } - bool pred_as_subj = pred_is_subj(term, state); - - ecs_entity_t pred = ensure_entity(world, state, term->pred.name, pred_as_subj); - ecs_entity_t subj = ensure_entity(world, state, term->subj.name, true); - ecs_entity_t obj = 0; + return result; +} - if (ecs_term_id_is_set(&term->obj)) { - obj = ensure_entity(world, state, term->obj.name, - state->assign_stmt == false); - } +static +int32_t get_component_index( + ecs_world_t *world, + ecs_type_t table_type, + ecs_entity_t *component_out, + int32_t column_index, + ecs_oper_kind_t op, + pair_offset_t *pair_offsets, + int32_t count) +{ + int32_t result = 0; + ecs_entity_t component = *component_out; - if (state->assign_stmt || state->isa_stmt) { - subj = state->assign_to; - } + ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); - if (state->isa_stmt && obj) { - ecs_parser_error(name, expr, column, - "invalid object in inheritance statement"); - return -1; - } + if (component) { + /* If requested component is a case, find the corresponding switch to + * lookup in the table */ + if (ECS_HAS_ROLE(component, CASE)) { + ecs_entity_t sw = ECS_PAIR_RELATION(component); + result = ecs_type_index_of(table_type, 0, ECS_SWITCH | sw); + ecs_assert(result != -1, ECS_INTERNAL_ERROR, NULL); + } else + if (ECS_HAS_ROLE(component, PAIR)) { + ecs_entity_t rel = ECS_PAIR_RELATION(component); + ecs_entity_t obj = ECS_PAIR_OBJECT(component); - if (state->using_stmt && (obj || subj)) { - ecs_parser_error(name, expr, column, - "invalid predicate/object in using statement"); - return -1; - } + /* Both the relationship and the object of the pair must be set */ + ecs_assert(rel != 0, ECS_INVALID_PARAMETER, NULL); + ecs_assert(obj != 0, ECS_INVALID_PARAMETER, NULL); - if (state->isa_stmt) { - pred = ecs_pair(EcsIsA, pred); - } + if (rel == EcsWildcard || obj == EcsWildcard) { + ecs_assert(pair_offsets != NULL, ECS_INTERNAL_ERROR, NULL); - if (subj) { - if (!obj) { - ecs_add_id(world, subj, pred); - state->last_assign_id = pred; - } else { - ecs_add_pair(world, subj, pred, obj); - state->last_object = obj; - state->last_assign_id = ecs_pair(pred, obj); - } - state->last_predicate = pred; - state->last_subject = subj; + /* Get index of pair. Start looking from the last pair index + * as this may not be the first instance of the pair. */ + result = get_pair_index( + table_type, component, column_index, pair_offsets, count); + + if (result != -1) { + /* If component of current column is a pair, get the actual + * pair type for the table, so the system can see which + * component the pair was applied to */ + ecs_entity_t *pair = ecs_vector_get( + table_type, ecs_entity_t, result); + *component_out = *pair; - pred_as_subj = false; - } else { - if (!obj) { - /* If no subject or object were provided, use predicate as subj - * unless the expression explictly excluded the subject */ - if (pred_as_subj) { - state->last_subject = pred; - subj = pred; + /* Check if the pair is a tag or whether it has data */ + if (ecs_get(world, rel, EcsComponent) == NULL) { + /* If pair has no data associated with it, use the + * component to which the pair has been added */ + component = ECS_PAIR_OBJECT(*pair); + } else { + component = rel; + } + } } else { - state->last_predicate = pred; - pred_as_subj = false; + + /* If the low part is a regular entity (component), then + * this query exactly matches a single pair instance. In + * this case we can simply do a lookup of the pair + * identifier in the table type. */ + result = ecs_type_index_of(table_type, 0, component); } } else { - state->last_predicate = pred; - state->last_object = obj; - pred_as_subj = false; + /* Get column index for component */ + result = ecs_type_index_of(table_type, 0, component); } - } - /* If this is a with clause (the list of entities between 'with' and scope - * open), add subject to the array of with frames */ - if (state->with_stmt) { - ecs_assert(pred != 0, ECS_INTERNAL_ERROR, NULL); - ecs_id_t id; + /* If column is found, add one to the index, as column zero in + * a table is reserved for entity id's */ + if (result != -1) { + result ++; + } - if (obj) { - id = ecs_pair(pred, obj); - } else { - id = pred; + /* ecs_table_column_offset may return -1 if the component comes + * from a prefab. If so, the component will be resolved as a + * reference (see below) */ + } + + if (op == EcsAndFrom || op == EcsOrFrom || op == EcsNotFrom) { + result = 0; + } else if (op == EcsOptional) { + /* If table doesn't have the field, mark it as no data */ + if (!ecs_type_has_id(world, table_type, component, false)) { + result = 0; } + } - state->with[state->with_frame ++] = id; - - } else if (state->using_stmt) { - ecs_assert(pred != 0, ECS_INTERNAL_ERROR, NULL); - ecs_assert(obj == 0, ECS_INTERNAL_ERROR, NULL); + return result; +} - state->using[state->using_frame ++] = pred; - state->using_frames[state->sp] = state->using_frame; +static +ecs_vector_t* add_ref( + ecs_world_t *world, + ecs_query_t *query, + ecs_vector_t *references, + ecs_term_t *term, + ecs_entity_t component, + ecs_entity_t entity) +{ + ecs_ref_t *ref = ecs_vector_add(&references, ecs_ref_t); + ecs_term_id_t *subj = &term->subj; - /* If this is not a with/using clause, add with frames to subject */ - } else { - if (subj) { - int32_t i, frame_count = state->with_frames[state->sp]; - for (i = 0; i < frame_count; i ++) { - ecs_add_id(world, subj, state->with[i]); - } - } + if (!(subj->set.mask & EcsCascade)) { + ecs_assert(entity != 0, ECS_INTERNAL_ERROR, NULL); } + + *ref = (ecs_ref_t){0}; + ref->entity = entity; + ref->component = component; - /* If an id was provided by itself, add default scope type to it */ - ecs_entity_t default_scope_type = state->default_scope_type[state->sp]; - if (pred_as_subj && default_scope_type) { - ecs_add_id(world, subj, default_scope_type); - } + const EcsComponent *c_info = flecs_component_from_id(world, component); + if (c_info) { + if (c_info->size && subj->entity != 0) { + if (entity) { + ecs_get_ref_w_id(world, ref, entity, component); + } - /* If a comment preceded the statement, add it as a brief description */ -#ifdef FLECS_DOC - if (subj && state->comment) { - ecs_doc_set_brief(world, subj, state->comment); - ecs_os_free(state->comment); - state->comment = NULL; + query->flags |= EcsQueryHasRefs; + } } -#endif - return 0; + return references; } static -const char* parse_inherit_stmt( - const char *name, - const char *expr, - const char *ptr, - plecs_state_t *state) +int32_t get_pair_count( + ecs_type_t type, + ecs_entity_t pair) { - if (state->isa_stmt) { - ecs_parser_error(name, expr, ptr - expr, - "cannot nest inheritance"); - return NULL; - } - - if (!state->last_subject) { - ecs_parser_error(name, expr, ptr - expr, - "missing entity to assign inheritance to"); - return NULL; + int32_t i = -1, result = 0; + while (-1 != (i = ecs_type_index_of(type, i + 1, pair))) { + result ++; } - - state->isa_stmt = true; - state->assign_to = state->last_subject; - return ptr; + return result; } +/* For each pair that the query subscribes for, count the occurrences in the + * table. Cardinality of subscribed for pairs must be the same as in the table + * or else the table won't match. */ static -const char* parse_assign_expr( - ecs_world_t *world, - const char *name, - const char *expr, - const char *ptr, - plecs_state_t *state) +int32_t count_pairs( + const ecs_query_t *query, + ecs_type_t type) { - (void)world; - - if (!state->assign_stmt) { - ecs_parser_error(name, expr, ptr - expr, - "unexpected value outside of assignment statement"); - return NULL; - } + ecs_term_t *terms = query->filter.terms; + int32_t i, count = query->filter.term_count; + int32_t first_count = 0, pair_count = 0; - ecs_id_t assign_id = state->last_assign_id; - if (!assign_id) { - ecs_parser_error(name, expr, ptr - expr, - "missing type for assignment statement"); - return NULL; - } + for (i = 0; i < count; i ++) { + ecs_term_t *term = &terms[i]; -#ifndef FLECS_EXPR - ecs_parser_error(name, expr, ptr - expr, - "cannot parse value, missing FLECS_EXPR addon"); - return NULL; -#else - ecs_entity_t assign_to = state->assign_to; - if (!assign_to) { - assign_to = state->last_subject; - } + if (!ECS_HAS_ROLE(term->id, PAIR)) { + continue; + } - if (!assign_to) { - ecs_parser_error(name, expr, ptr - expr, - "missing entity to assign to"); - return NULL; - } + if (term->subj.entity != EcsThis) { + continue; + } - ecs_entity_t type = ecs_get_typeid(world, assign_id); - if (!type) { - char *id_str = ecs_id_str(world, assign_id); - ecs_parser_error(name, expr, ptr - expr, - "invalid assignment, '%s' is not a type", id_str); - ecs_os_free(id_str); - return NULL; + if (ecs_id_is_wildcard(term->id)) { + pair_count = get_pair_count(type, term->id); + if (!first_count) { + first_count = pair_count; + } else { + if (first_count != pair_count) { + /* The pairs that this query subscribed for occur in the + * table but don't have the same cardinality. Ignore the + * table. This could typically happen for empty tables along + * a path in the table graph. */ + return -1; + } + } + } } - void *value_ptr = ecs_get_mut_id( - world, assign_to, assign_id, NULL); - - ptr = ecs_parse_expr(world, ptr, type, value_ptr, - &(ecs_parse_expr_desc_t) { - .name = name, - .expr = expr, - .lookup_action = plecs_lookup_action, - .lookup_ctx = state - }); - if (!ptr) { - return NULL; - } + return first_count; +} - ecs_modified_id(world, assign_to, assign_id); -#endif +static +ecs_type_t get_term_type( + ecs_world_t *world, + ecs_term_t *term, + ecs_entity_t component) +{ + ecs_oper_kind_t oper = term->oper; + ecs_assert(oper == EcsAndFrom || oper == EcsOrFrom || oper == EcsNotFrom, + ECS_INTERNAL_ERROR, NULL); + (void)oper; - return ptr; + const EcsType *type = ecs_get(world, component, EcsType); + if (type) { + return type->normalized; + } else { + return ecs_get_type(world, component); + } } +/** Add table to system, compute offsets for system components in table it */ static -const char* parse_assign_stmt( +void add_table( ecs_world_t *world, - const char *name, - const char *expr, - const char *ptr, - plecs_state_t *state) + ecs_query_t *query, + ecs_table_t *table) { - (void)world; + ecs_type_t table_type = NULL; + ecs_term_t *terms = query->filter.terms; + int32_t t, c, term_count = query->filter.term_count; - state->isa_stmt = false; + if (table) { + table_type = table->type; + } - /* Component scope (add components to entity) */ - if (!state->last_subject) { - ecs_parser_error(name, expr, ptr - expr, - "missing entity to assign to"); - return NULL; + int32_t pair_cur = 0, pair_count = count_pairs(query, table_type); + + /* If the query has pairs, we need to account for the fact that a table may + * have multiple components to which the pair is applied, which means the + * table has to be registered with the query multiple times, with different + * table columns. If so, allocate a small array for each pair in which the + * last added table index of the pair is stored, so that in the next + * iteration we can start the search from the correct offset type. */ + pair_offset_t *pair_offsets = NULL; + if (pair_count) { + pair_offsets = ecs_os_calloc( + ECS_SIZEOF(pair_offset_t) * term_count); } - if (state->assign_stmt) { - ecs_parser_error(name, expr, ptr - expr, - "invalid assign statement in assign statement"); - return NULL; + /* From here we recurse */ + ecs_query_table_match_t *table_data; + ecs_vector_t *references = NULL; + + ecs_query_table_t *qt = ecs_table_cache_insert( + &query->cache, ecs_query_table_t, table); + +add_pair: + table_data = cache_add(qt); + table_data->table = table; + if (table) { + table_type = table->type; } - if (!state->scope_assign_stmt) { - state->assign_to = state->last_subject; + if (term_count) { + /* Array that contains the system column to table column mapping */ + table_data->columns = ecs_os_calloc_n(int32_t, query->filter.term_count_actual); + ecs_assert(table_data->columns != NULL, ECS_OUT_OF_MEMORY, NULL); + + /* Store the components of the matched table. In the case of OR expressions, + * components may differ per matched table. */ + table_data->ids = ecs_os_calloc_n(ecs_entity_t, query->filter.term_count_actual); + ecs_assert(table_data->ids != NULL, ECS_OUT_OF_MEMORY, NULL); + + /* Cache subject (source) entity ids for components */ + table_data->subjects = ecs_os_calloc_n(ecs_entity_t, query->filter.term_count_actual); + ecs_assert(table_data->subjects != NULL, ECS_OUT_OF_MEMORY, NULL); + + /* Cache subject (source) entity ids for components */ + table_data->sizes = ecs_os_calloc_n(ecs_size_t, query->filter.term_count_actual); + ecs_assert(table_data->sizes != NULL, ECS_OUT_OF_MEMORY, NULL); } - state->assign_stmt = true; - - /* Assignment without a preceding component */ - if (ptr[0] == '{') { - ecs_entity_t type = 0; + /* Walk columns parsed from the system signature */ + c = 0; + for (t = 0; t < term_count; t ++) { + ecs_term_t *term = &terms[t]; + ecs_term_id_t subj = term->subj; + ecs_entity_t entity = 0, component = 0; + ecs_oper_kind_t op = term->oper; - if (state->scope_assign_stmt) { - ecs_assert(state->assign_to == ecs_get_scope(world), - ECS_INTERNAL_ERROR, NULL); + if (op == EcsNot) { + subj.entity = 0; } - /* If we're in a scope & last_subject is a type, assign to scope */ - if (ecs_get_scope(world) != 0) { - type = ecs_get_typeid(world, state->last_subject); - if (type != 0) { - type = state->last_subject; + /* Get actual component and component source for current column */ + bool match; + t = get_comp_and_src(world, query, t, table, &component, &entity, &match); + + /* This column does not retrieve data from a static entity */ + if (!entity && subj.entity) { + int32_t index = get_component_index(world, table_type, + &component, c, op, pair_offsets, pair_cur + 1); + + if (index == -1) { + if (op == EcsOptional && subj.set.mask == EcsSelf) { + index = 0; + } + } else { + if (op == EcsOptional && !(subj.set.mask & EcsSelf)) { + index = 0; + } + } + + table_data->columns[c] = index; + + /* If the column is a case, we should only iterate the entities in + * the column for this specific case. Add a sparse column with the + * case id so we can find the correct entities when iterating */ + if (ECS_HAS_ROLE(component, CASE)) { + flecs_sparse_column_t *sc = ecs_vector_add( + &table_data->sparse_columns, flecs_sparse_column_t); + sc->signature_column_index = t; + sc->sw_case = ECS_PAIR_OBJECT(component); + sc->sw_column = NULL; + } + + /* If table has a disabled bitmask for components, check if there is + * a disabled column for the queried for component. If so, cache it + * in a vector as the iterator will need to skip the entity when the + * component is disabled. */ + if (index && (table && table->flags & EcsTableHasDisabled)) { + ecs_entity_t bs_id = + (component & ECS_COMPONENT_MASK) | ECS_DISABLED; + int32_t bs_index = ecs_type_index_of(table->type, 0, bs_id); + if (bs_index != -1) { + flecs_bitset_column_t *elem = ecs_vector_add( + &table_data->bitset_columns, flecs_bitset_column_t); + elem->column_index = bs_index; + elem->bs_column = NULL; + } } } - /* If type hasn't been set yet, check if scope has default type */ - if (!type && !state->scope_assign_stmt) { - type = state->default_scope_type[state->sp]; + ecs_entity_t type_id = ecs_get_typeid(world, component); + if (!type_id && !(ECS_ROLE_MASK & component)) { + type_id = component; } - /* If no type has been found still, check if last with id is a type */ - if (!type && !state->scope_assign_stmt) { - int32_t with_frame_count = state->with_frames[state->sp]; - if (with_frame_count) { - type = state->with[with_frame_count - 1]; + if (entity || table_data->columns[c] == -1 || subj.set.mask & EcsCascade) { + if (type_id) { + references = add_ref(world, query, references, term, + component, entity); + table_data->columns[c] = -ecs_vector_count(references); + } + + table_data->subjects[c] = entity; + flecs_add_flag(world, entity, ECS_FLAG_OBSERVED); + + if (!match) { + ecs_ref_t *ref = ecs_vector_last(references, ecs_ref_t); + ref->entity = 0; } } - if (!type) { - ecs_parser_error(name, expr, ptr - expr, - "missing type for assignment"); - return NULL; + if (type_id) { + const EcsComponent *cptr = ecs_get(world, type_id, EcsComponent); + if (!cptr || !cptr->size) { + int32_t column = table_data->columns[c]; + if (column < 0) { + ecs_ref_t *r = ecs_vector_get( + references, ecs_ref_t, -column - 1); + r->component = 0; + } + } + + if (cptr) { + table_data->sizes[c] = cptr->size; + } else { + table_data->sizes[c] = 0; + } + } else { + table_data->sizes[c] = 0; } - state->last_assign_id = type; + + if (ECS_HAS_ROLE(component, SWITCH)) { + table_data->sizes[c] = ECS_SIZEOF(ecs_entity_t); + } else if (ECS_HAS_ROLE(component, CASE)) { + table_data->sizes[c] = ECS_SIZEOF(ecs_entity_t); + } + + table_data->ids[c] = component; + + c ++; } - return ptr; -} + if (references) { + ecs_size_t ref_size = ECS_SIZEOF(ecs_ref_t) * ecs_vector_count(references); + table_data->references = ecs_os_malloc(ref_size); + ecs_os_memcpy(table_data->references, + ecs_vector_first(references, ecs_ref_t), ref_size); + ecs_vector_free(references); + references = NULL; + } -static -const char* parse_using_stmt( - const char *name, - const char *expr, - const char *ptr, - plecs_state_t *state) -{ - if (state->isa_stmt || state->assign_stmt) { - ecs_parser_error(name, expr, ptr - expr, - "invalid usage of using keyword"); - return NULL; + /* Insert match to iteration list if table is not empty */ + if (!table || ecs_table_count(table) != 0) { + ecs_assert(table == qt->hdr.table, ECS_INTERNAL_ERROR, NULL); + insert_table_node(query, &table_data->node); } - /* Add following expressions to using list */ - state->using_stmt = true; + /* Use tail recursion when adding table for multiple pairs */ + pair_cur ++; + if (pair_cur < pair_count) { + goto add_pair; + } - return ptr + 5; + if (table && !(query->flags & EcsQueryIsSubquery)) { + flecs_table_notify(world, table, &(ecs_table_event_t){ + .kind = EcsTableQueryMatch, + .query = query + }); + } + + if (pair_offsets) { + ecs_os_free(pair_offsets); + } } static -const char* parse_with_stmt( - const char *name, - const char *expr, - const char *ptr, - plecs_state_t *state) +bool match_term( + const ecs_world_t *world, + const ecs_table_t *table, + ecs_term_t *term) { - if (state->isa_stmt) { - ecs_parser_error(name, expr, ptr - expr, - "invalid with after inheritance"); - return NULL; + ecs_term_id_t *subj = &term->subj; + + /* If term has no subject, there's nothing to match */ + if (!subj->entity) { + return true; } - if (state->assign_stmt) { - ecs_parser_error(name, expr, ptr - expr, - "invalid with in assign_stmt"); - return NULL; + if (term->subj.entity != EcsThis) { + table = ecs_get_table(world, subj->entity); } - /* Add following expressions to with list */ - state->with_stmt = true; - return ptr + 5; + return ecs_type_match( + world, table, table->type, 0, term->id, subj->set.relation, + subj->set.min_depth, subj->set.max_depth, NULL, NULL, NULL) != -1; } -static -const char* parse_scope_open( - ecs_world_t *world, - const char *name, - const char *expr, - const char *ptr, - plecs_state_t *state) +/* Match table with query */ +bool flecs_query_match( + const ecs_world_t *world, + const ecs_table_t *table, + const ecs_query_t *query) { - state->isa_stmt = false; + if (!(query->flags & EcsQueryNeedsTables)) { + return false; + } - if (state->assign_stmt) { - ecs_parser_error(name, expr, ptr - expr, - "invalid scope in assign_stmt"); - return NULL; + ecs_type_t table_type = table->type; + + /* Don't match disabled entities */ + if (!(query->flags & EcsQueryMatchDisabled) && ecs_type_has_id( + world, table_type, EcsDisabled, true)) + { + return false; } - state->sp ++; + /* Don't match prefab entities */ + if (!(query->flags & EcsQueryMatchPrefab) && ecs_type_has_id( + world, table_type, EcsPrefab, true)) + { + return false; + } - ecs_entity_t scope = 0; - ecs_entity_t default_scope_type = 0; + /* Check if pair cardinality matches pairs in query, if any */ + if (count_pairs(query, table->type) == -1) { + return false; + } - if (!state->with_stmt) { - if (state->last_subject) { - scope = state->last_subject; - ecs_set_scope(world, state->last_subject); + ecs_term_t *terms = query->filter.terms; + int32_t i, term_count = query->filter.term_count; - /* Check if scope has a default child component */ - ecs_entity_t def_type_src = ecs_get_object_for_id(world, scope, - 0, ecs_pair(EcsDefaultChildComponent, EcsWildcard)); + for (i = 0; i < term_count; i ++) { + ecs_term_t *term = &terms[i]; + ecs_oper_kind_t oper = term->oper; - if (def_type_src) { - default_scope_type = ecs_get_object( - world, def_type_src, EcsDefaultChildComponent, 0); + if (oper == EcsAnd) { + if (!match_term(world, table, term)) { + return false; } - } else { - if (state->last_object) { - scope = ecs_pair( - state->last_predicate, state->last_object); - ecs_set_with(world, scope); - } else { - if (state->last_predicate) { - scope = ecs_pair(EcsChildOf, state->last_predicate); + + } else if (oper == EcsNot) { + if (match_term(world, table, term)) { + return false; + } + + } else if (oper == EcsOr) { + bool match = false; + + for (; i < term_count; i ++) { + term = &terms[i]; + if (term->oper != EcsOr) { + i --; + break; + } + + if (!match && match_term( world, table, term)) { + match = true; } - ecs_set_scope(world, state->last_predicate); } - } - state->scope[state->sp] = scope; - state->default_scope_type[state->sp] = default_scope_type; - } else { - state->scope[state->sp] = state->scope[state->sp - 1]; - state->default_scope_type[state->sp] = - state->default_scope_type[state->sp - 1]; - } + if (!match) { + return false; + } + + } else if (oper == EcsAndFrom || oper == EcsOrFrom || oper == EcsNotFrom) { + ecs_type_t type = get_term_type((ecs_world_t*)world, term, term->id); + int32_t match_count = 0, j, count = ecs_vector_count(type); + ecs_entity_t *ids = ecs_vector_first(type, ecs_entity_t); - state->using_frames[state->sp] = state->using_frame; - state->with_frames[state->sp] = state->with_frame; - state->with_stmt = false; + for (j = 0; j < count; j ++) { + ecs_term_t tmp_term = *term; + tmp_term.oper = EcsAnd; + tmp_term.id = ids[j]; + tmp_term.pred.entity = ids[j]; - return ptr; + if (match_term(world, table, &tmp_term)) { + match_count ++; + } + } + + if (oper == EcsAndFrom && match_count != count) { + return false; + } + if (oper == EcsOrFrom && match_count == 0) { + return false; + } + if (oper == EcsNotFrom && match_count != 0) { + return false; + } + } + } + + return true; } +/** Match existing tables against system (table is created before system) */ static -const char* parse_scope_close( +void match_tables( ecs_world_t *world, - const char *name, - const char *expr, - const char *ptr, - plecs_state_t *state) + ecs_query_t *query) { - if (state->isa_stmt) { - ecs_parser_error(name, expr, ptr - expr, - "invalid '}' after inheritance statement"); - return NULL; - } - - if (state->assign_stmt) { - ecs_parser_error(name, expr, ptr - expr, - "unfinished assignment before }"); - return NULL; - } + int32_t i, count = flecs_sparse_count(world->store.tables); - state->scope[state->sp] = 0; - state->default_scope_type[state->sp] = 0; - state->sp --; + for (i = 0; i < count; i ++) { + ecs_table_t *table = flecs_sparse_get_dense( + world->store.tables, ecs_table_t, i); - if (state->sp < 0) { - ecs_parser_error(name, expr, ptr - expr, "invalid } without a {"); - return NULL; + if (flecs_query_match(world, table, query)) { + add_table(world, query, table); + } } +} - ecs_id_t id = state->scope[state->sp]; - - if (!id || ECS_HAS_ROLE(id, PAIR)) { - ecs_set_with(world, id); - } - - if (!id || !ECS_HAS_ROLE(id, PAIR)) { - ecs_set_scope(world, id); - } - - state->with_frame = state->with_frames[state->sp]; - state->using_frame = state->using_frames[state->sp]; - state->last_subject = 0; - state->assign_stmt = false; - - return ptr; -} +#define ELEM(ptr, size, index) ECS_OFFSET(ptr, size * index) static -const char *parse_plecs_term( +int32_t qsort_partition( ecs_world_t *world, - const char *name, - const char *expr, - const char *ptr, - plecs_state_t *state) + ecs_table_t *table, + ecs_data_t *data, + ecs_entity_t *entities, + void *ptr, + int32_t elem_size, + int32_t lo, + int32_t hi, + ecs_order_by_action_t compare) { - ecs_term_t term = {0}; - ecs_entity_t scope = ecs_get_scope(world); + int32_t p = (hi + lo) / 2; + void *pivot = ELEM(ptr, elem_size, p); + ecs_entity_t pivot_e = entities[p]; + int32_t i = lo - 1, j = hi + 1; + void *el; - /* If first character is a (, this should be interpreted as an id assigned - * to the current scope if: - * - this is not already an assignment: "Foo = (Hello, World)" - * - this is in a scope - */ - bool scope_assignment = (ptr[0] == '(') && !state->assign_stmt && scope != 0; +repeat: + { + do { + i ++; + el = ELEM(ptr, elem_size, i); + } while ( compare(entities[i], el, pivot_e, pivot) < 0); - ptr = ecs_parse_term(world, name, expr, ptr, &term); - if (!ptr) { - return NULL; - } + do { + j --; + el = ELEM(ptr, elem_size, j); + } while ( compare(entities[j], el, pivot_e, pivot) > 0); - if (!ecs_term_is_initialized(&term)) { - ecs_parser_error(name, expr, ptr - expr, "expected identifier"); - return NULL; /* No term found */ - } + if (i >= j) { + return j; + } - /* Lookahead to check if this is an implicit scope assignment (no parens) */ - if (ptr[0] == '=') { - const char *tptr = ecs_parse_fluff(ptr + 1, NULL); - if (tptr[0] == '{') { - ecs_entity_t pred = plecs_lookup( - world, term.pred.name, state, false); - ecs_entity_t obj = plecs_lookup( - world, term.obj.name, state, false); - ecs_id_t id = 0; - if (pred && obj) { - id = ecs_pair(pred, obj); - } else if (pred) { - id = pred; - } + flecs_table_swap(world, table, data, i, j); - if (id && (ecs_get_typeid(world, id) != 0)) { - scope_assignment = true; - } - } - } + if (p == i) { + pivot = ELEM(ptr, elem_size, j); + pivot_e = entities[j]; + } else if (p == j) { + pivot = ELEM(ptr, elem_size, i); + pivot_e = entities[i]; + } - bool prev = state->assign_stmt; - if (scope_assignment) { - state->assign_stmt = true; - state->assign_to = scope; - } - if (create_term(world, &term, name, expr, (ptr - expr), state)) { - ecs_term_fini(&term); - return NULL; /* Failed to create term */ + goto repeat; } - if (scope_assignment) { - state->last_subject = state->last_assign_id; - state->scope_assign_stmt = true; +} + +static +void qsort_array( + ecs_world_t *world, + ecs_table_t *table, + ecs_data_t *data, + ecs_entity_t *entities, + void *ptr, + int32_t size, + int32_t lo, + int32_t hi, + ecs_order_by_action_t compare) +{ + if ((hi - lo) < 1) { + return; } - state->assign_stmt = prev; - ecs_term_fini(&term); + int32_t p = qsort_partition( + world, table, data, entities, ptr, size, lo, hi, compare); - return ptr; + qsort_array(world, table, data, entities, ptr, size, lo, p, compare); + + qsort_array(world, table, data, entities, ptr, size, p + 1, hi, compare); } static -const char* parse_stmt( +void sort_table( ecs_world_t *world, - const char *name, - const char *expr, - const char *ptr, - plecs_state_t *state) + ecs_table_t *table, + int32_t column_index, + ecs_order_by_action_t compare) { - state->assign_stmt = false; - state->scope_assign_stmt = false; - state->isa_stmt = false; - state->with_stmt = false; - state->using_stmt = false; - state->last_subject = 0; - state->last_predicate = 0; - state->last_object = 0; - - ptr = parse_fluff(expr, ptr, state); - - char ch = ptr[0]; - - if (!ch) { - goto done; - } else if (ch == '{') { - ptr = parse_fluff(expr, ptr + 1, state); - goto scope_open; - } else if (ch == '}') { - ptr = parse_fluff(expr, ptr + 1, state); - goto scope_close; - } else if (ch == '(') { - goto term_expr; - } else if (!ecs_os_strncmp(ptr, TOK_USING " ", 5)) { - ptr = parse_using_stmt(name, expr, ptr, state); - if (!ptr) goto error; - goto term_expr; - } else if (!ecs_os_strncmp(ptr, TOK_WITH " ", 5)) { - ptr = parse_with_stmt(name, expr, ptr, state); - if (!ptr) goto error; - goto term_expr; - } else { - goto term_expr; + ecs_data_t *data = &table->storage; + if (!data->entities) { + /* Nothing to sort */ + return; } -term_expr: - if (!ptr[0]) { - goto done; + int32_t count = flecs_table_data_count(data); + if (count < 2) { + return; } - if (!(ptr = parse_plecs_term(world, name, ptr, ptr, state))) { - goto error; + ecs_entity_t *entities = ecs_vector_first(data->entities, ecs_entity_t); + + void *ptr = NULL; + int32_t size = 0; + if (column_index != -1) { + ecs_column_t *column = &data->columns[column_index]; + size = column->size; + ptr = ecs_vector_first_t(column->data, size, column->alignment); } - ptr = parse_fluff(expr, ptr, state); + qsort_array(world, table, data, entities, ptr, size, 0, count - 1, compare); +} - if (ptr[0] == '{' && !isspace(ptr[-1])) { - /* A '{' directly after an identifier (no whitespace) is a literal */ - goto assign_expr; - } +/* Helper struct for building sorted table ranges */ +typedef struct sort_helper_t { + ecs_query_table_match_t *match; + ecs_entity_t *entities; + const void *ptr; + int32_t row; + int32_t elem_size; + int32_t count; + bool shared; +} sort_helper_t; - if (!state->using_stmt) { - if (ptr[0] == ':') { - ptr = parse_fluff(expr, ptr + 1, state); - goto inherit_stmt; - } else if (ptr[0] == '=') { - ptr = parse_fluff(expr, ptr + 1, state); - goto assign_stmt; - } else if (ptr[0] == ',') { - ptr = parse_fluff(expr, ptr + 1, state); - goto term_expr; - } else if (ptr[0] == '{') { - state->assign_stmt = false; - ptr = parse_fluff(expr, ptr + 1, state); - goto scope_open; - } +static +const void* ptr_from_helper( + sort_helper_t *helper) +{ + ecs_assert(helper->row < helper->count, ECS_INTERNAL_ERROR, NULL); + ecs_assert(helper->elem_size >= 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(helper->row >= 0, ECS_INTERNAL_ERROR, NULL); + if (helper->shared) { + return helper->ptr; + } else { + return ELEM(helper->ptr, helper->elem_size, helper->row); } +} - state->assign_stmt = false; - goto done; - -inherit_stmt: - ptr = parse_inherit_stmt(name, expr, ptr, state); - if (!ptr) goto error; +static +ecs_entity_t e_from_helper( + sort_helper_t *helper) +{ + if (helper->row < helper->count) { + return helper->entities[helper->row]; + } else { + return 0; + } +} - /* Expect base identifier */ - goto term_expr; +static +void build_sorted_table_range( + ecs_query_t *query, + ecs_query_table_list_t *list) +{ + ecs_world_t *world = query->world; + ecs_entity_t component = query->order_by_component; + ecs_order_by_action_t compare = query->order_by; + + if (!list->count) { + return; + } -assign_stmt: - ptr = parse_assign_stmt(world, name, expr, ptr, state); - if (!ptr) goto error; + int to_sort = 0; + sort_helper_t *helper = ecs_os_malloc_n(sort_helper_t, list->count); + ecs_query_table_node_t *cur, *end = list->last->next; + for (cur = list->first; cur != end; cur = cur->next) { + ecs_query_table_match_t *match = cur->match; + ecs_table_t *table = match->table; + ecs_data_t *data = &table->storage; + ecs_vector_t *entities; + if (!(entities = data->entities) || !ecs_table_count(table)) { + continue; + } - ptr = parse_fluff(expr, ptr, state); + int32_t index = ecs_type_index_of(table->storage_type, 0, component); + if (index != -1) { + ecs_column_t *column = &data->columns[index]; + int16_t size = column->size; + int16_t align = column->alignment; + helper[to_sort].ptr = ecs_vector_first_t(column->data, size, align); + helper[to_sort].elem_size = size; + helper[to_sort].shared = false; + } else if (component) { + /* Find component in prefab */ + ecs_entity_t base; + ecs_type_match(world, table, table->type, 0, component, + EcsIsA, 1, 0, &base, NULL, NULL); - /* Assignment without a preceding component */ - if (ptr[0] == '{') { - goto assign_expr; - } + /* If a base was not found, the query should not have allowed using + * the component for sorting */ + ecs_assert(base != 0, ECS_INTERNAL_ERROR, NULL); - /* Expect component identifiers */ - goto term_expr; + const EcsComponent *cptr = ecs_get(world, component, EcsComponent); + ecs_assert(cptr != NULL, ECS_INTERNAL_ERROR, NULL); -assign_expr: - ptr = parse_assign_expr(world, name, expr, ptr, state); - if (!ptr) goto error; + helper[to_sort].ptr = ecs_get_id(world, base, component); + helper[to_sort].elem_size = cptr->size; + helper[to_sort].shared = true; + } else { + helper[to_sort].ptr = NULL; + helper[to_sort].elem_size = 0; + helper[to_sort].shared = false; + } - ptr = parse_fluff(expr, ptr, state); - if (ptr[0] == ',') { - ptr ++; - goto term_expr; - } else if (ptr[0] == '{') { - state->assign_stmt = false; - ptr ++; - goto scope_open; - } else { - state->assign_stmt = false; - goto done; + helper[to_sort].match = match; + helper[to_sort].entities = ecs_vector_first(entities, ecs_entity_t); + helper[to_sort].row = 0; + helper[to_sort].count = ecs_table_count(table); + to_sort ++; } -scope_open: - ptr = parse_scope_open(world, name, expr, ptr, state); - if (!ptr) goto error; - goto done; - -scope_close: - ptr = parse_scope_close(world, name, expr, ptr, state); - if (!ptr) goto error; - goto done; + bool proceed; + do { + int32_t j, min = 0; + proceed = true; -done: - return ptr; -error: - return NULL; -} + ecs_entity_t e1; + while (!(e1 = e_from_helper(&helper[min]))) { + min ++; + if (min == to_sort) { + proceed = false; + break; + } + } -int ecs_plecs_from_str( - ecs_world_t *world, - const char *name, - const char *expr) -{ - const char *ptr = expr; - ecs_term_t term = {0}; - plecs_state_t state = {0}; + if (!proceed) { + break; + } - if (!expr) { - return 0; - } + for (j = min + 1; j < to_sort; j++) { + ecs_entity_t e2 = e_from_helper(&helper[j]); + if (!e2) { + continue; + } - state.scope[0] = ecs_get_scope(world); - ecs_entity_t prev_with = ecs_set_with(world, 0); + const void *ptr1 = ptr_from_helper(&helper[min]); + const void *ptr2 = ptr_from_helper(&helper[j]); - do { - expr = ptr = parse_stmt(world, name, expr, ptr, &state); - if (!ptr) { - goto error; + if (compare(e1, ptr1, e2, ptr2) > 0) { + min = j; + e1 = e_from_helper(&helper[min]); + } } - if (!ptr[0]) { - break; /* End of expression */ + sort_helper_t *cur_helper = &helper[min]; + if (!cur || cur->match != cur_helper->match) { + cur = ecs_vector_add(&query->table_slices, ecs_query_table_node_t); + ecs_assert(cur != NULL, ECS_INTERNAL_ERROR, NULL); + cur->match = cur_helper->match; + cur->offset = cur_helper->row; + cur->count = 1; + } else { + cur->count ++; } - } while (true); - - ecs_set_scope(world, state.scope[0]); - ecs_set_with(world, prev_with); - - clear_comment(expr, ptr, &state); - if (state.sp != 0) { - ecs_parser_error(name, expr, 0, "missing end of scope"); - goto error; - } + cur_helper->row ++; + } while (proceed); - if (state.assign_stmt) { - ecs_parser_error(name, expr, 0, "unfinished assignment"); - goto error; + /* Iterate through the vector of slices to set the prev/next ptrs. This + * can't be done while building the vector, as reallocs may occur */ + int32_t i, count = ecs_vector_count(query->table_slices); + ecs_query_table_node_t *nodes = ecs_vector_first( + query->table_slices, ecs_query_table_node_t); + for (i = 0; i < count; i ++) { + nodes[i].prev = &nodes[i - 1]; + nodes[i].next = &nodes[i + 1]; } - if (state.errors) { - goto error; - } + nodes[0].prev = NULL; + nodes[i - 1].next = NULL; - return 0; -error: - ecs_set_scope(world, state.scope[0]); - ecs_set_with(world, prev_with); - ecs_term_fini(&term); - return -1; + ecs_os_free(helper); } -int ecs_plecs_from_file( - ecs_world_t *world, - const char *filename) +static +void build_sorted_tables( + ecs_query_t *query) { - FILE* file; - char* content = NULL; - int32_t bytes; - size_t size; - - /* Open file for reading */ - ecs_os_fopen(&file, filename, "r"); - if (!file) { - ecs_err("%s (%s)", ecs_os_strerror(errno), filename); - goto error; - } + ecs_vector_clear(query->table_slices); - /* Determine file size */ - fseek(file, 0 , SEEK_END); - bytes = (int32_t)ftell(file); - if (bytes == -1) { - goto error; - } - rewind(file); + if (query->group_by) { + /* Populate sorted node list in grouping order */ + ecs_query_table_node_t *cur = query->list.first; + if (cur) { + do { + /* Find list for current group */ + ecs_query_table_match_t *match = cur->match; + ecs_assert(match != NULL, ECS_INTERNAL_ERROR, NULL); + uint64_t group_id = match->group_id; + ecs_query_table_list_t *list = ecs_map_get(query->groups, + ecs_query_table_list_t, group_id); + ecs_assert(list != NULL, ECS_INTERNAL_ERROR, NULL); - /* Load contents in memory */ - content = ecs_os_malloc(bytes + 1); - size = (size_t)bytes; - if (!(size = fread(content, 1, size, file)) && bytes) { - ecs_err("%s: read zero bytes instead of %d", filename, size); - ecs_os_free(content); - content = NULL; - goto error; + /* Sort tables in current group */ + build_sorted_table_range(query, list); + + /* Find next group to sort */ + cur = list->last->next; + } while (cur); + } } else { - content[size] = '\0'; + build_sorted_table_range(query, &query->list); } +} - fclose(file); +static +bool tables_dirty( + const ecs_query_t *query) +{ + bool is_dirty = false; - int result = ecs_plecs_from_str(world, filename, content); - ecs_os_free(content); - return result; -error: - ecs_os_free(content); - return -1; -} + ecs_vector_t *vec = query->cache.tables; + ecs_query_table_t *tables = ecs_vector_first(vec, ecs_query_table_t); + int32_t i, count = ecs_vector_count(vec); -#endif + for (i = 0; i < count; i ++) { + ecs_query_table_t *qt = &tables[i]; + ecs_table_t *table = qt->hdr.table; + if (!qt->monitor) { + qt->monitor = flecs_table_get_monitor(table); + is_dirty = true; + } -#ifdef FLECS_RULES + int32_t *dirty_state = flecs_table_get_dirty_state(table); + int32_t t, storage_count = ecs_table_storage_count(table); + for (t = 0; t < storage_count + 1; t ++) { + is_dirty = is_dirty || (dirty_state[t] != qt->monitor[t]); + } + } -/** Implementation of the rule query engine. - * - * A rule (terminology borrowed from prolog) is a list of constraints that - * specify which conditions must be met for an entity to match the rule. While - * this description matches any kind of ECS query, the rule engine has features - * that go beyond regular (flecs) ECS queries: - * - * - query for all components of an entity (vs. all entities for a component) - * - query for all relationship pairs of an entity - * - support for query variables that are resolved at evaluation time - * - automatic traversal of transitive relationships - * - * Query terms can have the following forms: - * - * - Component(Subject) - * - Relation(Subject, Object) - * - * Additionally the query parser supports the following shorthand notations: - * - * - Component // short for Component(This) - * - (Relation, Object) // short for Relation(This, Object) - * - * The subject, or first arugment of a term represents the entity on which the - * component or relation is matched. By default the subject is set to a builtin - * This variable, which causes the behavior to match a regular ECS query: - * - * - Position, Velocity - * - * Is equivalent to - * - * - Position(This), Velocity(This) - * - * The function of the variable is to ensure that all components are matched on - * the same entity. Conceptually the query first populates the This variable - * with all entities that have Position. When the query evaluates the Velocity - * term, the variable is populated and the entity it contains will be checked - * for whether it has Velocity. - * - * The actual implementation is more efficient and does not check per-entity. - * - * Custom variables can be used to join parts of different terms. For example, - * the following query can be used to find entities with a parent that has a - * Position component (note that variable names start with a _): - * - * - ChildOf(This, _Parent), Component(_Parent) - * - * The rule engine uses a backtracking algorithm to find the set of entities - * and variables that match all terms. As soon as the engine finds a term that - * does not match with the currently evaluated entity, the entity is discarded. - * When an entity is found for which all terms match, the entity is yielded to - * the iterator. - * - * While a rule is being evaluated, a variable can either contain a single - * entity or a table. The engine will attempt to work with tables as much as - * possible so entities can be eliminated/yielded in bulk. A rule may store - * both the table and entity version of a variable and only switch from table to - * entity when necessary. - * - * The rule engine has an algorithm for computing which variables should be - * resolved first. This algorithm works by finding a "root" variable, which is - * the subject variable that occurs in the term with the least dependencies. The - * remaining variables are then resolved based on their "distance" from the root - * with the closest variables being resolved first. - * - * This generally results in an ordering that resolves the variables with the - * least dependencies first and the most dependencies last, which is beneficial - * for two reasons: - * - * - it improves the average performance of all queries - * - it makes performance less dependent on how an application orders the terms - * - * A possible improvement would be for the query engine to also consider - * the number of tables that need to be evaluated for each term, as starting - * with the smallest term reduces the amount of work. Other than static variable - * analysis however, this can only be determined when the query is executed. - * - * Rules are "compiled" into a set of instructions that encode the operations - * the query needs to perform in order to find the right set of entities. - * Operations can either yield data, which progresses the program, or signal - * that there is no (more) matching data, which discards the current variables. - * - * An operation can yield multiple times, if there are multiple matches for its - * inputs. Operations are called with a redo flag, which can be either true or - * false. When redo is true the operation will yield the next result. When redo - * is false, the operation will reset its state and start from the first result. - * - * Operations can have an input, output and a filter. Most commonly an operation - * either matches the filter against an input and yields if it matches, or uses - * the filter to find all matching results and store the result in the output. - * - * Variables are resolved by matching a filter against the output of an - * operation. When a term contains variables, they are encoded as register ids - * in the filter. When the filter is evaluated, the most recent values of the - * register are used to match/lookup the output. - * - * For example, a filter could be (ChildOf, _Parent). When the program starts, - * the _Parent register is initialized with *, so that when this filter is first - * evaluated, the operation will find all tables with (ChildOf, *). The _Parent - * register is then populated by taking the actual value of the table. If the - * table has type [(ChildOf, Sun)], _Parent will be initialized with Sun. - * - * It is possible that a filter matches multiple times. Consider the filter - * (Likes, _Food), and a table [(Likes, Apples), (Likes, Pears)]. In this case - * an operation will yield the table twice, once with _Food=Apples, and once - * with _Food=Pears. - * - * If a rule contains a term with a transitive relation, it will automatically - * substitute the parts of the term to find a fact that matches. The following - * examples illustrate how transitivity is resolved: - * - * Query: - * LocatedIn(Bob, SanFrancisco) - * - * Expands to: - * LocatedIn(Bob, SanFrancisco:self|subset) - * - * Explanation: - * "Is Bob located in San Francisco" - This term is true if Bob is either - * located in San Francisco, or is located in anything that is itself located - * in (a subset of) San Francisco. - * - * - * Query: - * LocatedIn(Bob, X) - * - * Expands to: - * LocatedIn(Bob, X:self|superset) - * - * Explanation: - * "Where is Bob located?" - This term recursively returns all places that - * Bob is located in, which includes his location and the supersets of his - * location. When Bob is located in San Francisco, he is also located in - * the United States, North America etc. - * - * - * Query: - * LocatedIn(X, NorthAmerica) - * - * Expands to: - * LocatedIn(X, NorthAmerica:self|subset) - * - * Explanation: - * "What is located in North America?" - This term returns everything located - * in North America and its subsets, as something located in San Francisco is - * located in UnitedStates, which is located in NorthAmerica. - * - * - * Query: - * LocatedIn(X, Y) - * - * Expands to: - * LocatedIn(X, Y) - * - * Explanation: - * "Where is everything located" - This term returns everything that is - * located somewhere. No substitution is performed as this would explode the - * results while not yielding new information. - * - * - * In the above terms, the variable indicates the part of the term that is - * unknown at evaluation time. In an actual rule the picked strategy depends on - * whether the variable is known when the term is evaluated. For example, if - * variable X has been resolved by the time Located(X, Y) is evaluated, the - * strategy from the LocatedIn(Bob, X) example will be used. - */ + is_dirty = is_dirty || (query->match_count != query->prev_match_count); -#define ECS_RULE_MAX_VARIABLE_COUNT (256) + return is_dirty; +} -#define RULE_PAIR_PREDICATE (1) -#define RULE_PAIR_OBJECT (2) +static +void tables_reset_dirty( + ecs_query_t *query) +{ + query->prev_match_count = query->match_count; -/* A rule pair contains a predicate and object that can be stored in a register. */ -typedef struct ecs_rule_pair_t { - union { - int32_t reg; - ecs_entity_t ent; - } pred; - union { - int32_t reg; - ecs_entity_t ent; - } obj; - int32_t reg_mask; /* bit 1 = predicate, bit 2 = object, bit 4 = wildcard */ - bool transitive; /* Is predicate transitive */ - bool final; /* Is predicate final */ - bool inclusive; /* Is predicate inclusive */ - bool obj_0; -} ecs_rule_pair_t; + ecs_vector_t *vec = query->cache.tables; + ecs_query_table_t *tables = ecs_vector_first(vec, ecs_query_table_t); + int32_t i, count = ecs_vector_count(vec); -/* Filter for evaluating & reifing types and variables. Filters are created ad- - * hoc from pairs, and take into account all variables that had been resolved - * up to that point. */ -typedef struct ecs_rule_filter_t { - ecs_id_t mask; /* Mask with wildcard in place of variables */ + for (i = 0; i < count; i ++) { + ecs_query_table_t *qt = &tables[i]; + ecs_table_t *table = qt->hdr.table; - bool wildcard; /* Does the filter contain wildcards */ - bool pred_wildcard; /* Is predicate a wildcard */ - bool obj_wildcard; /* Is object a wildcard */ - bool same_var; /* True if pred & obj are both the same variable */ + if (!qt->monitor) { + /* If one table doesn't have a monitor, none of the tables will have + * a monitor, so early out. */ + return; + } - int32_t hi_var; /* If hi part should be stored in var, this is the var id */ - int32_t lo_var; /* If lo part should be stored in var, this is the var id */ -} ecs_rule_filter_t; + int32_t *dirty_state = flecs_table_get_dirty_state(table); + int32_t t, storage_count = ecs_table_storage_count(table); + for (t = 0; t < storage_count + 1; t ++) { + qt->monitor[t] = dirty_state[t]; + } + } +} -/* A rule register stores temporary values for rule variables */ -typedef enum ecs_rule_var_kind_t { - EcsRuleVarKindTable, /* Used for sorting, must be smallest */ - EcsRuleVarKindEntity, - EcsRuleVarKindUnknown -} ecs_rule_var_kind_t; +static +void sort_tables( + ecs_world_t *world, + ecs_query_t *query) +{ + ecs_order_by_action_t compare = query->order_by; + if (!compare) { + return; + } + + ecs_entity_t order_by_component = query->order_by_component; -typedef struct ecs_rule_reg_t { - /* Used for table variable */ - ecs_table_t *table; - int32_t offset; - int32_t count; + /* Iterate over non-empty tables. Don't bother with empty tables as they + * have nothing to sort */ - /* Used for entity variable. May also be set for table variable if it needs - * to store an empty entity. */ - ecs_entity_t entity; -} ecs_rule_reg_t; - -/* Operations describe how the rule should be evaluated */ -typedef enum ecs_rule_op_kind_t { - EcsRuleInput, /* Input placeholder, first instruction in every rule */ - EcsRuleSelect, /* Selects all ables for a given predicate */ - EcsRuleWith, /* Applies a filter to a table or entity */ - EcsRuleSubSet, /* Finds all subsets for transitive relationship */ - EcsRuleSuperSet, /* Finds all supersets for a transitive relationship */ - EcsRuleStore, /* Store entity in table or entity variable */ - EcsRuleEach, /* Forwards each entity in a table */ - EcsRuleSetJmp, /* Set label for jump operation to one of two values */ - EcsRuleJump, /* Jump to an operation label */ - EcsRuleNot, /* Invert result of an operation */ - EcsRuleYield /* Yield result */ -} ecs_rule_op_kind_t; + bool tables_sorted = false; + ecs_vector_t *vec = query->cache.tables; + ecs_query_table_t *tables = ecs_vector_first(vec, ecs_query_table_t); + int32_t i, count = ecs_vector_count(vec); -/* Single operation */ -typedef struct ecs_rule_op_t { - ecs_rule_op_kind_t kind; /* What kind of operation is it */ - ecs_rule_pair_t filter; /* Parameter that contains optional filter */ - ecs_entity_t subject; /* If set, operation has a constant subject */ + for (i = 0; i < count; i ++) { + ecs_query_table_t *qt = &tables[i]; + ecs_table_t *table = qt->hdr.table; - int32_t on_pass; /* Jump location when match succeeds */ - int32_t on_fail; /* Jump location when match fails */ - int32_t frame; /* Register frame */ + /* If no monitor had been created for the table yet, create it now */ + bool is_dirty = false; + if (!qt->monitor) { + qt->monitor = flecs_table_get_monitor(table); - int32_t term; /* Corresponding term index in signature */ - int32_t r_in; /* Optional In/Out registers */ - int32_t r_out; + /* A new table is always dirty */ + is_dirty = true; + } - bool has_in, has_out; /* Keep track of whether operation uses input - * and/or output registers. This helps with - * debugging rule programs. */ -} ecs_rule_op_t; + int32_t *dirty_state = flecs_table_get_dirty_state(table); + is_dirty = is_dirty || (dirty_state[0] != qt->monitor[0]); -/* With context. Shared with select. */ -typedef struct ecs_rule_with_ctx_t { - ecs_id_record_t *idr; /* Currently evaluated table set */ - int32_t table_index; -} ecs_rule_with_ctx_t; + int32_t index = -1; + if (order_by_component) { + /* Get index of sorted component. We only care if the component we're + * sorting on has changed or if entities have been added / re(moved) */ + index = ecs_type_index_of(table->storage_type, + 0, order_by_component); -/* Subset context */ -typedef struct ecs_rule_subset_frame_t { - ecs_rule_with_ctx_t with_ctx; - ecs_table_t *table; - int32_t row; - int32_t column; -} ecs_rule_subset_frame_t; + if (index != -1) { + ecs_assert(index < ecs_vector_count(table->type), + ECS_INTERNAL_ERROR, NULL); -typedef struct ecs_rule_subset_ctx_t { - ecs_rule_subset_frame_t storage[16]; /* Alloc-free array for small trees */ - ecs_rule_subset_frame_t *stack; - int32_t sp; -} ecs_rule_subset_ctx_t; + is_dirty = is_dirty || (dirty_state[index + 1] != qt->monitor[index + 1]); + } else { + /* Table does not contain component which means the sorted + * component is shared. Table does not need to be sorted */ + continue; + } + } + + /* Check both if entities have moved (element 0) or if the component + * we're sorting on has changed (index + 1) */ + if (is_dirty) { + /* Sort the table */ + sort_table(world, table, index, compare); + tables_sorted = true; + } + } -/* Superset context */ -typedef struct ecs_rule_superset_frame_t { - ecs_table_t *table; - int32_t column; -} ecs_rule_superset_frame_t; + if (tables_sorted || query->match_count != query->prev_match_count) { + build_sorted_tables(query); + query->match_count ++; /* Increase version if tables changed */ + } +} -typedef struct ecs_rule_superset_ctx_t { - ecs_rule_superset_frame_t storage[16]; /* Alloc-free array for small trees */ - ecs_rule_superset_frame_t *stack; - ecs_id_record_t *idr; - int32_t sp; -} ecs_rule_superset_ctx_t; +static +bool has_refs( + ecs_query_t *query) +{ + ecs_term_t *terms = query->filter.terms; + int32_t i, count = query->filter.term_count; -/* Each context */ -typedef struct ecs_rule_each_ctx_t { - int32_t row; /* Currently evaluated row in evaluated table */ -} ecs_rule_each_ctx_t; + for (i = 0; i < count; i ++) { + ecs_term_t *term = &terms[i]; + ecs_term_id_t *subj = &term->subj; -/* Jump context */ -typedef struct ecs_rule_setjmp_ctx_t { - int32_t label; /* Operation label to jump to */ -} ecs_rule_setjmp_ctx_t; + if (term->oper == EcsNot && !subj->entity) { + /* Special case: if oper kind is Not and the query contained a + * shared expression, the expression is translated to FromEmpty to + * prevent resolving the ref */ + return true; + } else if (subj->entity && (subj->entity != EcsThis || subj->set.mask != EcsSelf)) { + /* If entity is not this, or if it can be substituted by other + * entities, the query can have references. */ + return true; + } + } -/* Operation context. This is a per-operation, per-iterator structure that - * stores information for stateful operations. */ -typedef struct ecs_rule_op_ctx_t { - union { - ecs_rule_subset_ctx_t subset; - ecs_rule_superset_ctx_t superset; - ecs_rule_with_ctx_t with; - ecs_rule_each_ctx_t each; - ecs_rule_setjmp_ctx_t setjmp; - } is; -} ecs_rule_op_ctx_t; + return false; +} -/* Rule variables allow for the rule to be parameterized */ -typedef struct ecs_rule_var_t { - ecs_rule_var_kind_t kind; - char *name; /* Variable name */ - int32_t id; /* Unique variable id */ - int32_t occurs; /* Number of occurrences (used for operation ordering) */ - int32_t depth; /* Depth in dependency tree (used for operation ordering) */ - bool marked; /* Used for cycle detection */ -} ecs_rule_var_t; +static +bool has_pairs( + ecs_query_t *query) +{ + ecs_term_t *terms = query->filter.terms; + int32_t i, count = query->filter.term_count; -/* Top-level rule datastructure */ -struct ecs_rule_t { - ecs_header_t hdr; - - ecs_world_t *world; /* Ref to world so rule can be used by itself */ - ecs_rule_op_t *operations; /* Operations array */ - ecs_rule_var_t *variables; /* Variable array */ - ecs_filter_t filter; /* Filter of rule */ + for (i = 0; i < count; i ++) { + if (ecs_id_is_wildcard(terms[i].id)) { + return true; + } + } - char **variable_names; /* Array with var names, used by iterators */ - int32_t *subject_variables; /* Variable id for term subject (if any) */ + return false; +} - int32_t variable_count; /* Number of variables in signature */ - int32_t subject_variable_count; - int32_t frame_count; /* Number of register frames */ - int32_t operation_count; /* Number of operations in rule */ +static +void register_monitors( + ecs_world_t *world, + ecs_query_t *query) +{ + ecs_term_t *terms = query->filter.terms; + int32_t i, count = query->filter.term_count; - ecs_iterable_t iterable; /* Iterable mixin */ -}; + for (i = 0; i < count; i++) { + ecs_term_t *term = &terms[i]; + ecs_term_id_t *subj = &term->subj; -/* ecs_rule_t mixins */ -ecs_mixins_t ecs_rule_t_mixins = { - .type_name = "ecs_rule_t", - .elems = { - [EcsMixinWorld] = offsetof(ecs_rule_t, world), - [EcsMixinIterable] = offsetof(ecs_rule_t, iterable) - } -}; + /* If component is requested with EcsCascade register component as a + * parent monitor. Parent monitors keep track of whether an entity moved + * in the hierarchy, which potentially requires the query to reorder its + * tables. + * Also register a regular component monitor for EcsCascade columns. + * This ensures that when the component used in the EcsCascade column + * is added or removed tables are updated accordingly*/ + if (subj->set.mask & EcsSuperSet && subj->set.mask & EcsCascade && + subj->set.relation != EcsIsA) + { + if (term->oper != EcsOr) { + if (term->subj.set.relation != EcsIsA) { + flecs_monitor_register( + world, term->subj.set.relation, term->id, query); + } + flecs_monitor_register(world, 0, term->id, query); + } -static -void rule_error( - const ecs_rule_t *rule, - const char *fmt, - ...) -{ - va_list valist; - va_start(valist, fmt); - ecs_parser_errorv(rule->filter.name, rule->filter.expr, -1, fmt, valist); - va_end(valist); + /* FromAny also requires registering a monitor, as FromAny columns can + * be matched with prefabs. The only term kinds that do not require + * registering a monitor are FromOwned and FromEmpty. */ + } else if ((subj->set.mask & EcsSuperSet) || (subj->entity != EcsThis)){ + if (term->oper != EcsOr) { + flecs_monitor_register(world, 0, term->id, query); + } + } + }; } static -bool subj_is_set( - ecs_term_t *term) +void process_signature( + ecs_world_t *world, + ecs_query_t *query) { - return ecs_term_id_is_set(&term->subj); -} + ecs_term_t *terms = query->filter.terms; + int32_t i, count = query->filter.term_count; -static -bool obj_is_set( - ecs_term_t *term) -{ - return ecs_term_id_is_set(&term->obj) || term->role == ECS_PAIR; -} + for (i = 0; i < count; i ++) { + ecs_term_t *term = &terms[i]; + ecs_term_id_t *pred = &term->pred; + ecs_term_id_t *subj = &term->subj; + ecs_term_id_t *obj = &term->obj; + ecs_oper_kind_t op = term->oper; + ecs_inout_kind_t inout = term->inout; -static -ecs_rule_op_t* create_operation( - ecs_rule_t *rule) -{ - int32_t cur = rule->operation_count ++; - rule->operations = ecs_os_realloc( - rule->operations, (cur + 1) * ECS_SIZEOF(ecs_rule_op_t)); + (void)pred; + (void)obj; - ecs_rule_op_t *result = &rule->operations[cur]; - ecs_os_memset_t(result, 0, ecs_rule_op_t); + /* Queries do not support variables */ + ecs_check(pred->var != EcsVarIsVariable, + ECS_UNSUPPORTED, NULL); + ecs_check(subj->entity == EcsThis || subj->var != EcsVarIsVariable, + ECS_UNSUPPORTED, NULL); + ecs_check(obj->var != EcsVarIsVariable, + ECS_UNSUPPORTED, NULL); - return result; -} + /* If self is not included in set, always start from depth 1 */ + if (!subj->set.min_depth && !(subj->set.mask & EcsSelf)) { + subj->set.min_depth = 1; + } -static -const char* get_var_name(const char *name) { - if (name && !ecs_os_strcmp(name, "This")) { - /* Make sure that both This and . resolve to the same variable */ - name = "."; - } + if (inout != EcsIn) { + query->flags |= EcsQueryHasOutColumns; + } - return name; -} + if (op == EcsOptional) { + query->flags |= EcsQueryHasOptional; + } -static -ecs_rule_var_t* create_variable( - ecs_rule_t *rule, - ecs_rule_var_kind_t kind, - const char *name) -{ - int32_t cur = ++ rule->variable_count; - rule->variables = ecs_os_realloc( - rule->variables, cur * ECS_SIZEOF(ecs_rule_var_t)); + if (!(query->flags & EcsQueryMatchDisabled)) { + if (op == EcsAnd || op == EcsOr || op == EcsOptional) { + if (term->id == EcsDisabled) { + query->flags |= EcsQueryMatchDisabled; + } + } + } - name = get_var_name(name); + if (!(query->flags & EcsQueryMatchPrefab)) { + if (op == EcsAnd || op == EcsOr || op == EcsOptional) { + if (term->id == EcsPrefab) { + query->flags |= EcsQueryMatchPrefab; + } + } + } - ecs_rule_var_t *var = &rule->variables[cur - 1]; - if (name) { - var->name = ecs_os_strdup(name); - } else { - /* Anonymous register */ - char name_buff[32]; - ecs_os_sprintf(name_buff, "_%u", cur - 1); - var->name = ecs_os_strdup(name_buff); - } + if (subj->entity == EcsThis) { + query->flags |= EcsQueryNeedsTables; + } - var->kind = kind; + if (subj->set.mask & EcsCascade && term->oper == EcsOptional) { + /* Query can only have one cascade column */ + ecs_assert(query->cascade_by == 0, ECS_INVALID_PARAMETER, NULL); + query->cascade_by = i + 1; + } - /* The variable id is the location in the variable array and also points to - * the register element that corresponds with the variable. */ - var->id = cur - 1; + if (subj->entity && subj->entity != EcsThis && + subj->set.mask == EcsSelf) + { + flecs_add_flag(world, term->subj.entity, ECS_FLAG_OBSERVED); + } + } - /* Depth is used to calculate how far the variable is from the root, where - * the root is the variable with 0 dependencies. */ - var->depth = UINT8_MAX; - var->marked = false; - var->occurs = 0; + query->flags |= (ecs_flags32_t)(has_refs(query) * EcsQueryHasRefs); + query->flags |= (ecs_flags32_t)(has_pairs(query) * EcsQueryHasTraits); - return var; + if (!(query->flags & EcsQueryIsSubquery)) { + register_monitors(world, query); + } +error: + return; } static -ecs_rule_var_t* create_anonymous_variable( - ecs_rule_t *rule, - ecs_rule_var_kind_t kind) +bool match_table( + ecs_world_t *world, + ecs_query_t *query, + ecs_table_t *table) { - return create_variable(rule, kind, NULL); + if (flecs_query_match(world, table, query)) { + add_table(world, query, table); + return true; + } + return false; } -/* Find variable with specified name and type. If Unknown is provided as type, - * the function will return any variable with the provided name. The root - * variable can occur both as a table and entity variable, as some rules - * require that each entity in a table is iterated. In this case, there are two - * variables, one for the table and one for the entities in the table, that both - * have the same name. */ +/** When a table becomes empty remove it from the query list, or vice versa. */ static -ecs_rule_var_t* find_variable( - const ecs_rule_t *rule, - ecs_rule_var_kind_t kind, - const char *name) +void update_table( + ecs_query_t *query, + ecs_table_t *table, + bool empty) { - ecs_assert(rule != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(name != NULL, ECS_INTERNAL_ERROR, NULL); + int32_t prev_count = ecs_query_table_count(query); + ecs_table_cache_set_empty(&query->cache, table, empty); + int32_t cur_count = ecs_query_table_count(query); - name = get_var_name(name); + if (prev_count != cur_count) { + ecs_query_table_t *qt = ecs_table_cache_get( + &query->cache, ecs_query_table_t, table); + ecs_assert(qt != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_query_table_match_t *cur, *next; - ecs_rule_var_t *variables = rule->variables; - int32_t i, count = rule->variable_count; - - for (i = 0; i < count; i ++) { - ecs_rule_var_t *variable = &variables[i]; - if (!ecs_os_strcmp(name, variable->name)) { - if (kind == EcsRuleVarKindUnknown || kind == variable->kind) { - return variable; + for (cur = qt->first; cur != NULL; cur = next) { + next = cur->next_match; + + if (empty) { + ecs_assert(ecs_table_count(table) == 0, + ECS_INTERNAL_ERROR, NULL); + + remove_table_node(query, &cur->node); + } else { + ecs_assert(ecs_table_count(table) != 0, + ECS_INTERNAL_ERROR, NULL); + insert_table_node(query, &cur->node); } } } - return NULL; + ecs_assert(cur_count || query->list.first == NULL, + ECS_INTERNAL_ERROR, NULL); } -/* Ensure variable with specified name and type exists. If an existing variable - * is found with an unknown type, its type will be overwritten with the - * specified type. During the variable ordering phase it is not yet clear which - * variable is the root. Which variable is the root determines its type, which - * is why during this phase variables are still untyped. */ static -ecs_rule_var_t* ensure_variable( - ecs_rule_t *rule, - ecs_rule_var_kind_t kind, - const char *name) +void add_subquery( + ecs_world_t *world, + ecs_query_t *parent, + ecs_query_t *subquery) { - ecs_rule_var_t *var = find_variable(rule, kind, name); - if (!var) { - var = create_variable(rule, kind, name); - } else { - if (var->kind == EcsRuleVarKindUnknown) { - var->kind = kind; - } + ecs_query_t **elem = ecs_vector_add(&parent->subqueries, ecs_query_t*); + *elem = subquery; + + ecs_table_cache_t *cache = &parent->cache; + ecs_vector_t *tables = cache->tables, *empty = cache->empty_tables; + + ecs_query_table_t *elems = ecs_vector_first(tables, ecs_query_table_t); + int32_t i, count = ecs_vector_count(tables); + for (i = 0; i < count; i ++) { + match_table(world, subquery, elems[i].hdr.table); } - return var; + elems = ecs_vector_first(empty, ecs_query_table_t); + count = ecs_vector_count(empty); + for (i = 0; i < count; i ++) { + match_table(world, subquery, elems[i].hdr.table); + } } static -bool term_id_is_variable( - ecs_term_id_t *term_id) +void notify_subqueries( + ecs_world_t *world, + ecs_query_t *query, + ecs_query_event_t *event) { - return term_id->var == EcsVarIsVariable; -} + if (query->subqueries) { + ecs_query_t **queries = ecs_vector_first(query->subqueries, ecs_query_t*); + int32_t i, count = ecs_vector_count(query->subqueries); -static -const char *term_id_var_name( - ecs_term_id_t *term_id) -{ - if (term_id->var == EcsVarIsVariable) { - if (term_id->entity == EcsThis) { - return "."; - } else { - ecs_check(term_id->name != NULL, ECS_INVALID_PARAMETER, NULL); - return term_id->name; + ecs_query_event_t sub_event = *event; + sub_event.parent_query = query; + + for (i = 0; i < count; i ++) { + ecs_query_t *sub = queries[i]; + flecs_query_notify(world, sub, &sub_event); } } - -error: - return NULL; } -/* Get variable from a term identifier */ static -ecs_rule_var_t* term_id_to_var( - ecs_rule_t *rule, - ecs_term_id_t *id) +void resolve_cascade_subject_for_table( + ecs_world_t *world, + ecs_query_t *query, + const ecs_table_t *table, + ecs_type_t table_type, + ecs_query_table_match_t *table_data) { - if (id->var == EcsVarIsVariable) {; - return find_variable(rule, EcsRuleVarKindUnknown, term_id_var_name(id)); - } - return NULL; -} + int32_t term_index = query->cascade_by - 1; + ecs_term_t *term = &query->filter.terms[term_index]; -/* Get variable from a term predicate */ -static -ecs_rule_var_t* term_pred( - ecs_rule_t *rule, - ecs_term_t *term) -{ - return term_id_to_var(rule, &term->pred); -} + ecs_assert(table_data->references != 0, ECS_INTERNAL_ERROR, NULL); -/* Get variable from a term subject */ -static -ecs_rule_var_t* term_subj( - ecs_rule_t *rule, - ecs_term_t *term) -{ - return term_id_to_var(rule, &term->subj); -} + /* Obtain reference index */ + int32_t *column_indices = table_data->columns; + int32_t ref_index = -column_indices[term_index] - 1; -/* Get variable from a term object */ -static -ecs_rule_var_t* term_obj( - ecs_rule_t *rule, - ecs_term_t *term) -{ - if (obj_is_set(term)) { - return term_id_to_var(rule, &term->obj); + /* Obtain pointer to the reference data */ + ecs_ref_t *references = table_data->references; + + /* Find source for component */ + ecs_entity_t subject; + ecs_type_match(world, table, table_type, 0, term->id, + term->subj.set.relation, 1, 0, &subject, NULL, NULL); + + /* If container was found, update the reference */ + if (subject) { + ecs_ref_t *ref = &references[ref_index]; + ecs_assert(ref->component == term->id, ECS_INTERNAL_ERROR, NULL); + + references[ref_index].entity = ecs_get_alive(world, subject); + table_data->subjects[term_index] = subject; + ecs_get_ref_w_id(world, ref, subject, term->id); } else { - return NULL; + references[ref_index].entity = 0; + table_data->subjects[term_index] = 0; + } + + if (ecs_table_count(table)) { + /* The subject (or depth of the subject) may have changed, so reinsert + * the node to make sure it's in the right group */ + remove_table_node(query, &table_data->node); + insert_table_node(query, &table_data->node); } } -/* Return predicate variable from pair */ static -ecs_rule_var_t* pair_pred( - ecs_rule_t *rule, - const ecs_rule_pair_t *pair) +void resolve_cascade_subject( + ecs_world_t *world, + ecs_query_t *query, + ecs_query_table_t *elem, + const ecs_table_t *table, + ecs_type_t table_type) { - if (pair->reg_mask & RULE_PAIR_PREDICATE) { - return &rule->variables[pair->pred.reg]; - } else { - return NULL; + ecs_query_table_match_t *cur; + for (cur = elem->first; cur != NULL; cur = cur->next_match) { + resolve_cascade_subject_for_table( + world, query, table, table_type, cur); } } -/* Return object variable from pair */ +/* Remove table */ static -ecs_rule_var_t* pair_obj( - ecs_rule_t *rule, - const ecs_rule_pair_t *pair) +void remove_table( + ecs_poly_t *poly, + void *ptr) { - if (pair->reg_mask & RULE_PAIR_OBJECT) { - return &rule->variables[pair->obj.reg]; - } else { - return NULL; + ecs_poly_assert(poly, ecs_query_t); + + ecs_query_t *query = poly; + ecs_query_table_t *elem = ptr; + ecs_query_table_match_t *cur, *next; + + for (cur = elem->first; cur != NULL; cur = next) { + ecs_os_free(cur->columns); + ecs_os_free(cur->ids); + ecs_os_free(cur->subjects); + ecs_os_free(cur->sizes); + ecs_os_free(cur->references); + ecs_os_free(cur->sparse_columns); + ecs_os_free(cur->bitset_columns); + + if (!elem->hdr.empty) { + remove_table_node(query, &cur->node); + } + + next = cur->next_match; + + ecs_os_free(cur); } + + ecs_os_free(elem->monitor); + + elem->first = NULL; } -/* Create new frame for storing register values. Each operation that yields data - * gets its own register frame, which contains all variables reified up to that - * point. The preceding frame always contains the reified variables from the - * previous operation. Operations that do not yield data (such as control flow) - * do not have their own frames. */ static -int32_t push_frame( - ecs_rule_t *rule) +void unmatch_table( + ecs_query_t *query, + ecs_table_t *table) { - return rule->frame_count ++; + ecs_table_cache_remove(&query->cache, ecs_query_table_t, table); } -/* Get register array for current stack frame. The stack frame is determined by - * the current operation that is evaluated. The register array contains the - * values for the reified variables. If a variable hasn't been reified yet, its - * register will store a wildcard. */ static -ecs_rule_reg_t* get_register_frame( - ecs_rule_iter_t *it, - int32_t frame) +void rematch_table( + ecs_world_t *world, + ecs_query_t *query, + ecs_table_t *table) { - if (it->registers) { - return &it->registers[frame * it->rule->variable_count]; + ecs_query_table_t *match = ecs_table_cache_get( + &query->cache, ecs_query_table_t, table); + + if (flecs_query_match(world, table, query)) { + /* If the table matches, and it is not currently matched, add */ + if (match == NULL) { + add_table(world, query, table); + + /* If table still matches and has cascade column, reevaluate the + * sources of references. This may have changed in case + * components were added/removed to container entities */ + } else if (query->cascade_by) { + resolve_cascade_subject(world, query, match, table, table->type); + + /* If query has optional columns, it is possible that a column that + * previously had data no longer has data, or vice versa. Do a full + * rematch to make sure data is consistent. */ + } else if (query->flags & EcsQueryHasOptional) { + unmatch_table(query, table); + if (!(query->flags & EcsQueryIsSubquery)) { + flecs_table_notify(world, table, &(ecs_table_event_t){ + .kind = EcsTableQueryUnmatch, + .query = query + }); + } + add_table(world, query, table); + } } else { - return NULL; + /* Table no longer matches, remove */ + if (match != NULL) { + unmatch_table(query, table); + if (!(query->flags & EcsQueryIsSubquery)) { + flecs_table_notify(world, table, &(ecs_table_event_t){ + .kind = EcsTableQueryUnmatch, + .query = query + }); + } + notify_subqueries(world, query, &(ecs_query_event_t){ + .kind = EcsQueryTableUnmatch, + .table = table + }); + } } } -/* Get register array for current stack frame. The stack frame is determined by - * the current operation that is evaluated. The register array contains the - * values for the reified variables. If a variable hasn't been reified yet, its - * register will store a wildcard. */ static -ecs_rule_reg_t* get_registers( - ecs_rule_iter_t *it, - ecs_rule_op_t *op) +bool satisfy_constraints( + ecs_world_t *world, + const ecs_filter_t *filter) { - return get_register_frame(it, op->frame); -} + ecs_term_t *terms = filter->terms; + int32_t i, count = filter->term_count; -/* Get columns array. Columns store, for each matched column in a table, the - * index at which it occurs. This reduces the amount of searching that - * operations need to do in a type, since select/with already provide it. */ -static -int32_t* rule_get_columns_frame( - ecs_rule_iter_t *it, - int32_t frame) -{ - return &it->columns[frame * it->rule->filter.term_count]; -} + for (i = 0; i < count; i ++) { + ecs_term_t *term = &terms[i]; + ecs_term_id_t *subj = &term->subj; + ecs_oper_kind_t oper = term->oper; + + if (oper == EcsOptional) { + continue; + } + + if (subj->entity != EcsThis && subj->set.mask & EcsSelf) { + ecs_type_t type = ecs_get_type(world, subj->entity); + + if (ecs_type_has_id(world, type, term->id, false)) { + if (oper == EcsNot) { + return false; + } + } else { + if (oper != EcsNot) { + return false; + } + } + } + } -static -int32_t* rule_get_columns( - ecs_rule_iter_t *it, - ecs_rule_op_t *op) -{ - return rule_get_columns_frame(it, op->frame); + return true; } +/* Rematch system with tables after a change happened to a watched entity */ static -ecs_table_t* table_from_entity( +void rematch_tables( ecs_world_t *world, - ecs_entity_t e) + ecs_query_t *query, + ecs_query_t *parent_query) { - ecs_record_t *record = ecs_eis_get(world, e); - if (record) { - return record->table; + if (parent_query) { + ecs_query_table_t *tables = ecs_vector_first( + parent_query->cache.tables, ecs_query_table_t); + int32_t i, count = ecs_vector_count(parent_query->cache.tables); + for (i = 0; i < count; i ++) { + rematch_table(world, query, tables[i].hdr.table); + } + + tables = ecs_vector_first( + parent_query->cache.empty_tables, ecs_query_table_t); + count = ecs_vector_count(parent_query->cache.empty_tables); + for (i = 0; i < count; i ++) { + rematch_table(world, query, tables[i].hdr.table); + } } else { - return NULL; + ecs_sparse_t *tables = world->store.tables; + int32_t i, count = flecs_sparse_count(tables); + + for (i = 0; i < count; i ++) { + /* Is the system currently matched with the table? */ + ecs_table_t *table = flecs_sparse_get_dense(tables, ecs_table_t, i); + rematch_table(world, query, table); + } } -} -static -void entity_reg_set( - const ecs_rule_t *rule, - ecs_rule_reg_t *regs, - int32_t r, - ecs_entity_t entity) -{ - (void)rule; - ecs_assert(rule->variables[r].kind == EcsRuleVarKindEntity, - ECS_INTERNAL_ERROR, NULL); - ecs_check(ecs_is_valid(rule->world, entity), ECS_INVALID_PARAMETER, NULL); - regs[r].entity = entity; -error: - return; + /* Enable/disable system if constraints are (not) met. If the system is + * already dis/enabled this operation has no side effects. */ + query->constraints_satisfied = satisfy_constraints(world, &query->filter); } static -ecs_entity_t entity_reg_get( - const ecs_rule_t *rule, - ecs_rule_reg_t *regs, - int32_t r) +void remove_subquery( + ecs_query_t *parent, + ecs_query_t *sub) { - (void)rule; - ecs_entity_t e = regs[r].entity; - if (!e) { - return EcsWildcard; - } - - ecs_check(ecs_is_valid(rule->world, e), ECS_INVALID_PARAMETER, NULL); - return e; -error: - return 0; -} + ecs_assert(parent != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(sub != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(parent->subqueries != NULL, ECS_INTERNAL_ERROR, NULL); -static -void table_reg_set( - const ecs_rule_t *rule, - ecs_rule_reg_t *regs, - int32_t r, - ecs_table_t *table) -{ - (void)rule; - ecs_assert(rule->variables[r].kind == EcsRuleVarKindTable, - ECS_INTERNAL_ERROR, NULL); + int32_t i, count = ecs_vector_count(parent->subqueries); + ecs_query_t **sq = ecs_vector_first(parent->subqueries, ecs_query_t*); - regs[r].table = table; - regs[r].offset = 0; - regs[r].count = 0; - regs[r].entity = 0; + for (i = 0; i < count; i ++) { + if (sq[i] == sub) { + break; + } + } + + ecs_vector_remove(parent->subqueries, ecs_query_t*, i); } -static -ecs_table_t* table_reg_get( - const ecs_rule_t *rule, - ecs_rule_reg_t *regs, - int32_t r) +/* -- Private API -- */ + +void flecs_query_notify( + ecs_world_t *world, + ecs_query_t *query, + ecs_query_event_t *event) { - (void)rule; - ecs_assert(rule->variables[r].kind == EcsRuleVarKindTable, - ECS_INTERNAL_ERROR, NULL); + bool notify = true; - return regs[r].table; + switch(event->kind) { + case EcsQueryTableMatch: + /* Creation of new table */ + if (match_table(world, query, event->table)) { + if (query->subqueries) { + notify_subqueries(world, query, event); + } + } + notify = false; + break; + case EcsQueryTableUnmatch: + /* Deletion of table */ + unmatch_table(query, event->table); + break; + case EcsQueryTableRematch: + /* Rematch tables of query */ + rematch_tables(world, query, event->parent_query); + break; + case EcsQueryTableEmpty: + /* Table is empty, deactivate */ + update_table(query, event->table, true); + break; + case EcsQueryTableNonEmpty: + /* Table is non-empty, activate */ + update_table(query, event->table, false); + break; + case EcsQueryOrphan: + ecs_assert(query->flags & EcsQueryIsSubquery, ECS_INTERNAL_ERROR, NULL); + query->flags |= EcsQueryIsOrphaned; + query->parent = NULL; + break; + } + + if (notify) { + notify_subqueries(world, query, event); + } } static -ecs_entity_t reg_get_entity( - const ecs_rule_t *rule, - ecs_rule_op_t *op, - ecs_rule_reg_t *regs, - int32_t r) +void query_order_by( + ecs_world_t *world, + ecs_query_t *query, + ecs_entity_t order_by_component, + ecs_order_by_action_t order_by) { - if (r == UINT8_MAX) { - ecs_assert(op->subject != 0, ECS_INTERNAL_ERROR, NULL); - - /* The subject is referenced from the query string by string identifier. - * If subject entity is not valid, it could have been deletd by the - * application after the rule was created */ - ecs_check(ecs_is_valid(rule->world, op->subject), - ECS_INVALID_PARAMETER, NULL); + ecs_check(query != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(!(query->flags & EcsQueryIsOrphaned), ECS_INVALID_PARAMETER, NULL); + ecs_check(query->flags & EcsQueryNeedsTables, ECS_INVALID_PARAMETER, NULL); - return op->subject; - } - if (rule->variables[r].kind == EcsRuleVarKindTable) { - int32_t offset = regs[r].offset; + query->order_by_component = order_by_component; + query->order_by = order_by; - ecs_assert(regs[r].count == 1, ECS_INTERNAL_ERROR, NULL); - ecs_data_t *data = &table_reg_get(rule, regs, r)->storage; - ecs_assert(data != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_entity_t *entities = ecs_vector_first(data->entities, ecs_entity_t); - ecs_assert(entities != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(offset < ecs_vector_count(data->entities), - ECS_INTERNAL_ERROR, NULL); - ecs_check(ecs_is_valid(rule->world, entities[offset]), - ECS_INVALID_PARAMETER, NULL); - - return entities[offset]; - } - if (rule->variables[r].kind == EcsRuleVarKindEntity) { - return entity_reg_get(rule, regs, r); - } + ecs_vector_free(query->table_slices); + query->table_slices = NULL; - /* Must return an entity */ - ecs_assert(false, ECS_INTERNAL_ERROR, NULL); + sort_tables(world, query); + if (!query->table_slices) { + build_sorted_tables(query); + } error: - return 0; + return; } static -ecs_table_t* reg_get_table( - const ecs_rule_t *rule, - ecs_rule_op_t *op, - ecs_rule_reg_t *regs, - int32_t r) -{ - if (r == UINT8_MAX) { - ecs_assert(op->subject != 0, ECS_INTERNAL_ERROR, NULL); - ecs_check(ecs_is_valid(rule->world, op->subject), - ECS_INVALID_PARAMETER, NULL); +void query_group_by( + ecs_query_t *query, + ecs_entity_t sort_component, + ecs_group_by_action_t group_by) +{ + /* Cannot change grouping once a query has been created */ + ecs_check(query->group_by_id == 0, ECS_INVALID_OPERATION, NULL); + ecs_check(query->group_by == 0, ECS_INVALID_OPERATION, NULL); - return table_from_entity(rule->world, op->subject); - } - if (rule->variables[r].kind == EcsRuleVarKindTable) { - return table_reg_get(rule, regs, r); - } - if (rule->variables[r].kind == EcsRuleVarKindEntity) { - return table_from_entity(rule->world, entity_reg_get(rule, regs, r)); - } + query->group_by_id = sort_component; + query->group_by = group_by; + query->groups = ecs_map_new(ecs_query_table_list_t, 16); error: - return NULL; + return; } +/* Implementation for iterable mixin */ static -void reg_set_entity( - const ecs_rule_t *rule, - ecs_rule_reg_t *regs, - int32_t r, - ecs_entity_t entity) +void query_iter_init( + const ecs_world_t *world, + const ecs_poly_t *poly, + ecs_iter_t *iter, + ecs_term_t *filter) { - if (rule->variables[r].kind == EcsRuleVarKindTable) { - ecs_world_t *world = rule->world; - ecs_check(ecs_is_valid(world, entity), ECS_INVALID_PARAMETER, NULL); + ecs_poly_assert(poly, ecs_query_t); - ecs_record_t *record = ecs_eis_get(world, entity); - if (!record || !record->table) { - regs[r].table = NULL; - regs[r].offset = 0; - regs[r].count = 0; - regs[r].entity = entity; - } else { - regs[r].table = record->table; - regs[r].offset = ECS_RECORD_TO_ROW(record->row); - regs[r].count = 1; - regs[r].entity = 0; - } + if (filter) { + iter[1] = ecs_query_iter(world, (ecs_query_t*)poly); + iter[0] = ecs_term_chain_iter(&iter[1], filter); } else { - entity_reg_set(rule, regs, r, entity); + iter[0] = ecs_query_iter(world, (ecs_query_t*)poly); } -error: - return; } -/* This encodes a column expression into a pair. A pair stores information about - * the variable(s) associated with the column. Pairs are used by operations to - * apply filters, and when there is a match, to reify variables. */ -static -ecs_rule_pair_t term_to_pair( - ecs_rule_t *rule, - ecs_term_t *term) +/* -- Public API -- */ + +ecs_query_t* ecs_query_init( + ecs_world_t *world, + const ecs_query_desc_t *desc) { - ecs_rule_pair_t result = {0}; + ecs_query_t *result = NULL; + ecs_check(world != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_check(!world->is_fini, ECS_INVALID_OPERATION, NULL); - /* Terms must always have at least one argument (the subject) */ - ecs_assert(subj_is_set(term), ECS_INTERNAL_ERROR, NULL); + result = flecs_sparse_add(world->queries, ecs_query_t); + ecs_poly_init(result, ecs_query_t); - /* If the predicate id is a variable, find the variable and encode its id - * in the pair so the operation can find it later. */ - if (term->pred.var == EcsVarIsVariable) { - /* Always lookup the as an entity, as pairs never refer to tables */ - const ecs_rule_var_t *var = find_variable( - rule, EcsRuleVarKindEntity, term_id_var_name(&term->pred)); + result->id = flecs_sparse_last_id(world->queries); - /* Variables should have been declared */ - ecs_assert(var != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(var->kind == EcsRuleVarKindEntity, ECS_INTERNAL_ERROR, NULL); - result.pred.reg = var->id; + if (ecs_filter_init(world, &result->filter, &desc->filter)) { + flecs_sparse_remove(world->queries, result->id); + return NULL; + } - /* Set flag so the operation can see that the predicate is a variable */ - result.reg_mask |= RULE_PAIR_PREDICATE; - result.final = true; - } else { - /* If the predicate is not a variable, simply store its id. */ - ecs_entity_t pred_id = term->pred.entity; - result.pred.ent = pred_id; + result->world = world; + result->iterable.init = query_iter_init; + result->system = desc->system; + result->prev_match_count = -1; - /* Test if predicate is transitive. When evaluating the predicate, this - * will also take into account transitive relationships */ - if (ecs_has_id(rule->world, pred_id, EcsTransitive)) { - /* Transitive queries must have an object */ - if (obj_is_set(term)) { - result.transitive = true; - } - } + ecs_table_cache_init( + &result->cache, ecs_query_table_t, result, remove_table); - if (ecs_has_id(rule->world, pred_id, EcsFinal)) { - result.final = true; - } + process_signature(world, result); - if (ecs_has_id(rule->world, pred_id, EcsTransitiveSelf)) { - result.inclusive = true; - } + /* Group before matching so we won't have to move tables around later */ + int32_t cascade_by = result->cascade_by; + if (cascade_by) { + query_group_by(result, result->filter.terms[cascade_by - 1].id, + group_by_cascade); + result->group_by_ctx = &result->filter.terms[cascade_by - 1]; } - /* The pair doesn't do anything with the subject (subjects are the things that - * are matched against pairs) so if the column does not have a object, - * there is nothing left to do. */ - if (!obj_is_set(term)) { - return result; + if (desc->group_by) { + /* Can't have a cascade term and group by at the same time, as cascade + * uses the group_by mechanism */ + ecs_check(!result->cascade_by, ECS_INVALID_PARAMETER, NULL); + query_group_by(result, desc->group_by_id, desc->group_by); + result->group_by_ctx = desc->group_by_ctx; + result->group_by_ctx_free = desc->group_by_ctx_free; } - /* If arguments is higher than 2 this is not a pair but a nested rule */ - ecs_assert(obj_is_set(term), ECS_INTERNAL_ERROR, NULL); + if (desc->parent != NULL) { + result->flags |= EcsQueryIsSubquery; + } - /* Same as above, if the object is a variable, store it and flag it */ - if (term->obj.var == EcsVarIsVariable) { - const ecs_rule_var_t *var = find_variable( - rule, EcsRuleVarKindEntity, term_id_var_name(&term->obj)); + /* If a system is specified, ensure that if there are any subjects in the + * filter that refer to the system, the component is added */ + if (desc->system) { + int32_t t, term_count = result->filter.term_count; + ecs_term_t *terms = result->filter.terms; - /* Variables should have been declared */ - ecs_assert(var != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(var->kind == EcsRuleVarKindEntity, ECS_INTERNAL_ERROR, NULL); + for (t = 0; t < term_count; t ++) { + ecs_term_t *term = &terms[t]; + if (term->subj.entity == desc->system) { + ecs_add_id(world, desc->system, term->id); + } + } + } - result.obj.reg = var->id; - result.reg_mask |= RULE_PAIR_OBJECT; - } else { - /* If the object is not a variable, simply store its id */ - result.obj.ent = term->obj.entity; - if (!result.obj.ent) { - result.obj_0 = true; +#ifndef NDEBUG + char *filter_expr = ecs_filter_str(world, &result->filter); + ecs_dbg_1("#[green]query#[normal] [%s] created", filter_expr); + ecs_os_free(filter_expr); +#endif + + ecs_log_push(); + + if (!desc->parent) { + if (result->flags & EcsQueryNeedsTables) { + match_tables(world, result); + } else { + /* Add stub table that resolves references (if any) so everything is + * preprocessed when the query is evaluated. */ + add_table(world, result, NULL); } + } else { + add_subquery(world, desc->parent, result); + result->parent = desc->parent; + } + + if (desc->order_by) { + query_order_by( + world, result, desc->order_by_component, desc->order_by); } + result->constraints_satisfied = satisfy_constraints(world, &result->filter); + + ecs_log_pop(); + return result; +error: + if (result) { + flecs_sparse_remove(world->queries, result->id); + } + return NULL; } -/* When an operation has a pair, it is used to filter its input. This function - * translates a pair back into an entity id, and in the process substitutes the - * variables that have already been filled out. It's one of the most important - * functions, as a lot of the filtering logic depends on having an entity that - * has all of the reified variables correctly filled out. */ -static -ecs_rule_filter_t pair_to_filter( - ecs_rule_iter_t *it, - ecs_rule_op_t *op, - ecs_rule_pair_t pair) +void ecs_query_fini( + ecs_query_t *query) { - ecs_entity_t pred = pair.pred.ent; - ecs_entity_t obj = pair.obj.ent; - ecs_rule_filter_t result = { - .lo_var = -1, - .hi_var = -1 - }; - - /* Get registers in case we need to resolve ids from registers. Get them - * from the previous, not the current stack frame as the current operation - * hasn't reified its variables yet. */ - ecs_rule_reg_t *regs = get_register_frame(it, op->frame - 1); - - if (pair.reg_mask & RULE_PAIR_OBJECT) { - obj = entity_reg_get(it->rule, regs, pair.obj.reg); - obj = ecs_entity_t_lo(obj); /* Filters don't have generations */ + ecs_poly_assert(query, ecs_query_t); + ecs_world_t *world = query->world; + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - if (obj == EcsWildcard) { - result.wildcard = true; - result.obj_wildcard = true; - result.lo_var = pair.obj.reg; + if (query->group_by_ctx_free) { + if (query->group_by_ctx) { + query->group_by_ctx_free(query->group_by_ctx); } } - if (pair.reg_mask & RULE_PAIR_PREDICATE) { - pred = entity_reg_get(it->rule, regs, pair.pred.reg); - pred = ecs_entity_t_lo(pred); /* Filters don't have generations */ + if ((query->flags & EcsQueryIsSubquery) && + !(query->flags & EcsQueryIsOrphaned)) + { + remove_subquery(query->parent, query); + } - if (pred == EcsWildcard) { - if (result.wildcard) { - result.same_var = pair.pred.reg == pair.obj.reg; - } + notify_subqueries(world, query, &(ecs_query_event_t){ + .kind = EcsQueryOrphan + }); - result.wildcard = true; - result.pred_wildcard = true; + ecs_vector_each(query->cache.empty_tables, ecs_query_table_t, table, { + if (!(query->flags & EcsQueryIsSubquery)) { + flecs_table_notify(world, table->hdr.table, &(ecs_table_event_t){ + .kind = EcsTableQueryUnmatch, + .query = query + }); + } + }); - if (obj) { - result.hi_var = pair.pred.reg; - } else { - result.lo_var = pair.pred.reg; - } + ecs_vector_each(query->cache.tables, ecs_query_table_t, table, { + if (!(query->flags & EcsQueryIsSubquery)) { + flecs_table_notify(world, table->hdr.table, &(ecs_table_event_t){ + .kind = EcsTableQueryUnmatch, + .query = query + }); } - } + }); - if (!obj && !pair.obj_0) { - result.mask = pred; - } else { - result.mask = ecs_pair(pred, obj); - } + ecs_table_cache_fini(&query->cache); + ecs_map_free(query->groups); - return result; + ecs_vector_free(query->subqueries); + ecs_vector_free(query->table_slices); + ecs_filter_fini(&query->filter); + + ecs_poly_fini(query, ecs_query_t); + + /* Remove query from storage */ + flecs_sparse_remove(world->queries, query->id); +error: + return; +} + +const ecs_filter_t* ecs_query_get_filter( + ecs_query_t *query) +{ + ecs_poly_assert(query, ecs_query_t); + return &query->filter; } -/* This function is responsible for reifying the variables (filling them out - * with their actual values as soon as they are known). It uses the pair - * expression returned by pair_get_most_specific_var, and attempts to fill out each of the - * wildcards in the pair. If a variable isn't reified yet, the pair expression - * will still contain one or more wildcards, which is harmless as the respective - * registers will also point to a wildcard. */ -static -void reify_variables( - ecs_rule_iter_t *it, - ecs_rule_op_t *op, - ecs_rule_filter_t *filter, - ecs_type_t type, - int32_t column) +/* Create query iterator */ +ecs_iter_t ecs_query_iter_page( + const ecs_world_t *stage, + ecs_query_t *query, + int32_t offset, + int32_t limit) { - const ecs_rule_t *rule = it->rule; - const ecs_rule_var_t *vars = rule->variables; - (void)vars; - - ecs_rule_reg_t *regs = get_registers(it, op); - ecs_entity_t *elem = ecs_vector_get(type, ecs_entity_t, column); - ecs_assert(elem != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_poly_assert(query, ecs_query_t); + ecs_check(!(query->flags & EcsQueryIsOrphaned), + ECS_INVALID_PARAMETER, NULL); - int32_t obj_var = filter->lo_var; - int32_t pred_var = filter->hi_var; + ecs_world_t *world = (ecs_world_t*)ecs_get_world(stage); - if (obj_var != -1) { - ecs_assert(vars[obj_var].kind == EcsRuleVarKindEntity, - ECS_INTERNAL_ERROR, NULL); + sort_tables(world, query); - entity_reg_set(rule, regs, obj_var, - ecs_get_alive(rule->world, ECS_PAIR_OBJECT(*elem))); + if (!world->is_readonly && query->flags & EcsQueryHasRefs) { + flecs_eval_component_monitors(world); } - if (pred_var != -1) { - ecs_assert(vars[pred_var].kind == EcsRuleVarKindEntity, - ECS_INTERNAL_ERROR, NULL); + tables_reset_dirty(query); - entity_reg_set(rule, regs, pred_var, - ecs_get_alive(rule->world, - ECS_PAIR_RELATION(*elem))); + int32_t table_count; + if (query->table_slices) { + table_count = ecs_vector_count(query->table_slices); + } else { + table_count = ecs_vector_count(query->cache.tables); } -} -/* Returns whether variable is a subject */ -static -bool is_subject( - ecs_rule_t *rule, - ecs_rule_var_t *var) -{ - ecs_assert(rule != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_query_iter_t it = { + .query = query, + .node = query->list.first, + .page_iter = { + .offset = offset, + .limit = limit, + .remaining = limit + } + }; - if (!var) { - return false; + if (query->order_by && query->list.count) { + it.node = ecs_vector_first(query->table_slices, ecs_query_table_node_t); } - if (var->id < rule->subject_variable_count) { - return true; - } + return (ecs_iter_t){ + .real_world = world, + .world = (ecs_world_t*)stage, + .terms = query->filter.terms, + .term_count = query->filter.term_count_actual, + .table_count = table_count, + .is_filter = query->filter.filter, + .is_instanced = query->filter.instanced, + .iter.query = it, + .next = ecs_query_next, + }; +error: + return (ecs_iter_t){ 0 }; +} - return false; +ecs_iter_t ecs_query_iter( + const ecs_world_t *world, + ecs_query_t *query) +{ + ecs_poly_assert(query, ecs_query_t); + return ecs_query_iter_page(world, query, 0, 0); } static -bool skip_term(ecs_term_t *term) { - if (term->subj.set.mask & EcsNothing) { - return true; +int ecs_page_iter_next( + ecs_page_iter_t *it, + ecs_page_cursor_t *cur) +{ + int32_t offset = it->offset; + int32_t limit = it->limit; + if (!(offset || limit)) { + return cur->count == 0; } - if (term->oper == EcsNot) { - return true; + + int32_t count = cur->count; + int32_t remaining = it->remaining; + + if (offset) { + if (offset > count) { + /* No entities to iterate in current table */ + it->offset -= count; + return 1; + } else { + cur->first += offset; + count = cur->count -= offset; + it->offset = 0; + } } - return false; -} + if (remaining) { + if (remaining > count) { + it->remaining -= count; + } else { + count = cur->count = remaining; + it->remaining = 0; + } + } else if (limit) { + /* Limit hit: no more entities left to iterate */ + return -1; + } -static -int32_t get_variable_depth( - ecs_rule_t *rule, - ecs_rule_var_t *var, - ecs_rule_var_t *root, - int recur); + return count == 0; +} static -int32_t crawl_variable( - ecs_rule_t *rule, - ecs_rule_var_t *var, - ecs_rule_var_t *root, - int recur) +int find_smallest_column( + ecs_table_t *table, + ecs_query_table_match_t *table_data, + ecs_vector_t *sparse_columns) { - ecs_term_t *terms = rule->filter.terms; - int32_t i, count = rule->filter.term_count; + flecs_sparse_column_t *sparse_column_array = + ecs_vector_first(sparse_columns, flecs_sparse_column_t); + int32_t i, count = ecs_vector_count(sparse_columns); + int32_t min = INT_MAX, index = 0; for (i = 0; i < count; i ++) { - ecs_term_t *term = &terms[i]; - if (skip_term(term)) { - continue; - } - - ecs_rule_var_t - *pred = term_pred(rule, term), - *subj = term_subj(rule, term), - *obj = term_obj(rule, term); + /* The array with sparse queries for the matched table */ + flecs_sparse_column_t *sparse_column = &sparse_column_array[i]; - /* Variable must at least appear once in term */ - if (var != pred && var != subj && var != obj) { - continue; - } + /* Pointer to the switch column struct of the table */ + ecs_sw_column_t *sc = sparse_column->sw_column; - if (pred && pred != var && !pred->marked) { - get_variable_depth(rule, pred, root, recur + 1); - } + /* If the sparse column pointer hadn't been retrieved yet, do it now */ + if (!sc) { + /* Get the table column index from the signature column index */ + int32_t table_column_index = table_data->columns[ + sparse_column->signature_column_index]; - if (subj && subj != var && !subj->marked) { - get_variable_depth(rule, subj, root, recur + 1); + /* Translate the table column index to switch column index */ + table_column_index -= table->sw_column_offset; + ecs_assert(table_column_index >= 1, ECS_INTERNAL_ERROR, NULL); + + /* Get the sparse column */ + ecs_data_t *data = &table->storage; + sc = sparse_column->sw_column = + &data->sw_columns[table_column_index - 1]; } - if (obj && obj != var && !obj->marked) { - get_variable_depth(rule, obj, root, recur + 1); + /* Find the smallest column */ + ecs_switch_t *sw = sc->data; + int32_t case_count = flecs_switch_case_count(sw, sparse_column->sw_case); + if (case_count < min) { + min = case_count; + index = i + 1; } } - return 0; + return index; } static -int32_t get_depth_from_var( - ecs_rule_t *rule, - ecs_rule_var_t *var, - ecs_rule_var_t *root, - int recur) +int sparse_column_next( + ecs_table_t *table, + ecs_query_table_match_t *matched_table, + ecs_vector_t *sparse_columns, + ecs_query_iter_t *iter, + ecs_page_cursor_t *cur, + bool filter) { - /* If variable is the root or if depth has been set, return depth + 1 */ - if (var == root || var->depth != UINT8_MAX) { - return var->depth + 1; - } + bool first_iteration = false; + int32_t sparse_smallest; - /* Variable is already being evaluated, so this indicates a cycle. Stop */ - if (var->marked) { - return 0; - } - - /* Variable is not yet being evaluated and depth has not yet been set. - * Calculate depth. */ - int32_t depth = get_variable_depth(rule, var, root, recur + 1); - if (depth == UINT8_MAX) { - return depth; - } else { - return depth + 1; + if (!(sparse_smallest = iter->sparse_smallest)) { + sparse_smallest = iter->sparse_smallest = find_smallest_column( + table, matched_table, sparse_columns); + first_iteration = true; } -} -static -int32_t get_depth_from_term( - ecs_rule_t *rule, - ecs_rule_var_t *cur, - ecs_rule_var_t *pred, - ecs_rule_var_t *obj, - ecs_rule_var_t *root, - int recur) -{ - int32_t result = UINT8_MAX; + sparse_smallest -= 1; - /* If neither of the other parts of the terms are variables, this - * variable is guaranteed to have no dependencies. */ - if (!pred && !obj) { - result = 0; - } else { - /* If this is a variable that is not the same as the current, - * we can use it to determine dependency depth. */ - if (pred && cur != pred) { - int32_t depth = get_depth_from_var(rule, pred, root, recur); - if (depth == UINT8_MAX) { - return UINT8_MAX; - } + flecs_sparse_column_t *columns = ecs_vector_first( + sparse_columns, flecs_sparse_column_t); + flecs_sparse_column_t *column = &columns[sparse_smallest]; + ecs_switch_t *sw, *sw_smallest = column->sw_column->data; + ecs_entity_t case_smallest = column->sw_case; - /* If the found depth is lower than the depth found, overwrite it */ - if (depth < result) { - result = depth; + /* Find next entity to iterate in sparse column */ + int32_t first, sparse_first = iter->sparse_first; + + if (!filter) { + if (first_iteration) { + first = flecs_switch_first(sw_smallest, case_smallest); + } else { + first = flecs_switch_next(sw_smallest, sparse_first); + } + } else { + int32_t cur_first = cur->first, cur_count = cur->count; + first = cur_first; + while (flecs_switch_get(sw_smallest, first) != case_smallest) { + first ++; + if (first >= (cur_first + cur_count)) { + first = -1; + break; } } + } - /* Same for obj */ - if (obj && cur != obj) { - int32_t depth = get_depth_from_var(rule, obj, root, recur); - if (depth == UINT8_MAX) { - return UINT8_MAX; + if (first == -1) { + goto done; + } + + /* Check if entity matches with other sparse columns, if any */ + int32_t i, count = ecs_vector_count(sparse_columns); + do { + for (i = 0; i < count; i ++) { + if (i == sparse_smallest) { + /* Already validated this one */ + continue; } - if (depth < result) { - result = depth; + column = &columns[i]; + sw = column->sw_column->data; + + if (flecs_switch_get(sw, first) != column->sw_case) { + first = flecs_switch_next(sw_smallest, first); + if (first == -1) { + goto done; + } } } - } + } while (i != count); - return result; + cur->first = iter->sparse_first = first; + cur->count = 1; + + return 0; +done: + /* Iterated all elements in the sparse list, we should move to the + * next matched table. */ + iter->sparse_smallest = 0; + iter->sparse_first = 0; + + return -1; } -/* Find the depth of the dependency tree from the variable to the root */ +#define BS_MAX ((uint64_t)0xFFFFFFFFFFFFFFFF) + static -int32_t get_variable_depth( - ecs_rule_t *rule, - ecs_rule_var_t *var, - ecs_rule_var_t *root, - int recur) +int bitset_column_next( + ecs_table_t *table, + ecs_vector_t *bitset_columns, + ecs_query_iter_t *iter, + ecs_page_cursor_t *cur) { - var->marked = true; + /* Precomputed single-bit test */ + static const uint64_t bitmask[64] = { + (uint64_t)1 << 0, (uint64_t)1 << 1, (uint64_t)1 << 2, (uint64_t)1 << 3, + (uint64_t)1 << 4, (uint64_t)1 << 5, (uint64_t)1 << 6, (uint64_t)1 << 7, + (uint64_t)1 << 8, (uint64_t)1 << 9, (uint64_t)1 << 10, (uint64_t)1 << 11, + (uint64_t)1 << 12, (uint64_t)1 << 13, (uint64_t)1 << 14, (uint64_t)1 << 15, + (uint64_t)1 << 16, (uint64_t)1 << 17, (uint64_t)1 << 18, (uint64_t)1 << 19, + (uint64_t)1 << 20, (uint64_t)1 << 21, (uint64_t)1 << 22, (uint64_t)1 << 23, + (uint64_t)1 << 24, (uint64_t)1 << 25, (uint64_t)1 << 26, (uint64_t)1 << 27, + (uint64_t)1 << 28, (uint64_t)1 << 29, (uint64_t)1 << 30, (uint64_t)1 << 31, + (uint64_t)1 << 32, (uint64_t)1 << 33, (uint64_t)1 << 34, (uint64_t)1 << 35, + (uint64_t)1 << 36, (uint64_t)1 << 37, (uint64_t)1 << 38, (uint64_t)1 << 39, + (uint64_t)1 << 40, (uint64_t)1 << 41, (uint64_t)1 << 42, (uint64_t)1 << 43, + (uint64_t)1 << 44, (uint64_t)1 << 45, (uint64_t)1 << 46, (uint64_t)1 << 47, + (uint64_t)1 << 48, (uint64_t)1 << 49, (uint64_t)1 << 50, (uint64_t)1 << 51, + (uint64_t)1 << 52, (uint64_t)1 << 53, (uint64_t)1 << 54, (uint64_t)1 << 55, + (uint64_t)1 << 56, (uint64_t)1 << 57, (uint64_t)1 << 58, (uint64_t)1 << 59, + (uint64_t)1 << 60, (uint64_t)1 << 61, (uint64_t)1 << 62, (uint64_t)1 << 63 + }; - /* Iterate columns, find all instances where 'var' is not used as subject. - * If the subject of that column is either the root or a variable for which - * the depth is known, the depth for this variable can be determined. */ - ecs_term_t *terms = rule->filter.terms; + /* Precomputed test to verify if remainder of block is set (or not) */ + static const uint64_t bitmask_remain[64] = { + BS_MAX, BS_MAX - (BS_MAX >> 63), BS_MAX - (BS_MAX >> 62), + BS_MAX - (BS_MAX >> 61), BS_MAX - (BS_MAX >> 60), BS_MAX - (BS_MAX >> 59), + BS_MAX - (BS_MAX >> 58), BS_MAX - (BS_MAX >> 57), BS_MAX - (BS_MAX >> 56), + BS_MAX - (BS_MAX >> 55), BS_MAX - (BS_MAX >> 54), BS_MAX - (BS_MAX >> 53), + BS_MAX - (BS_MAX >> 52), BS_MAX - (BS_MAX >> 51), BS_MAX - (BS_MAX >> 50), + BS_MAX - (BS_MAX >> 49), BS_MAX - (BS_MAX >> 48), BS_MAX - (BS_MAX >> 47), + BS_MAX - (BS_MAX >> 46), BS_MAX - (BS_MAX >> 45), BS_MAX - (BS_MAX >> 44), + BS_MAX - (BS_MAX >> 43), BS_MAX - (BS_MAX >> 42), BS_MAX - (BS_MAX >> 41), + BS_MAX - (BS_MAX >> 40), BS_MAX - (BS_MAX >> 39), BS_MAX - (BS_MAX >> 38), + BS_MAX - (BS_MAX >> 37), BS_MAX - (BS_MAX >> 36), BS_MAX - (BS_MAX >> 35), + BS_MAX - (BS_MAX >> 34), BS_MAX - (BS_MAX >> 33), BS_MAX - (BS_MAX >> 32), + BS_MAX - (BS_MAX >> 31), BS_MAX - (BS_MAX >> 30), BS_MAX - (BS_MAX >> 29), + BS_MAX - (BS_MAX >> 28), BS_MAX - (BS_MAX >> 27), BS_MAX - (BS_MAX >> 26), + BS_MAX - (BS_MAX >> 25), BS_MAX - (BS_MAX >> 24), BS_MAX - (BS_MAX >> 23), + BS_MAX - (BS_MAX >> 22), BS_MAX - (BS_MAX >> 21), BS_MAX - (BS_MAX >> 20), + BS_MAX - (BS_MAX >> 19), BS_MAX - (BS_MAX >> 18), BS_MAX - (BS_MAX >> 17), + BS_MAX - (BS_MAX >> 16), BS_MAX - (BS_MAX >> 15), BS_MAX - (BS_MAX >> 14), + BS_MAX - (BS_MAX >> 13), BS_MAX - (BS_MAX >> 12), BS_MAX - (BS_MAX >> 11), + BS_MAX - (BS_MAX >> 10), BS_MAX - (BS_MAX >> 9), BS_MAX - (BS_MAX >> 8), + BS_MAX - (BS_MAX >> 7), BS_MAX - (BS_MAX >> 6), BS_MAX - (BS_MAX >> 5), + BS_MAX - (BS_MAX >> 4), BS_MAX - (BS_MAX >> 3), BS_MAX - (BS_MAX >> 2), + BS_MAX - (BS_MAX >> 1) + }; - int32_t i, count = rule->filter.term_count; - int32_t result = UINT8_MAX; + int32_t i, count = ecs_vector_count(bitset_columns); + flecs_bitset_column_t *columns = ecs_vector_first( + bitset_columns, flecs_bitset_column_t); + int32_t bs_offset = table->bs_column_offset; + + int32_t first = iter->bitset_first; + int32_t last = 0; for (i = 0; i < count; i ++) { - ecs_term_t *term = &terms[i]; - if (skip_term(term)) { - continue; + flecs_bitset_column_t *column = &columns[i]; + ecs_bs_column_t *bs_column = columns[i].bs_column; + + if (!bs_column) { + int32_t index = column->column_index; + ecs_assert((index - bs_offset >= 0), ECS_INTERNAL_ERROR, NULL); + bs_column = &table->storage.bs_columns[index - bs_offset]; + columns[i].bs_column = bs_column; + } + + ecs_bitset_t *bs = &bs_column->data; + int32_t bs_elem_count = bs->count; + int32_t bs_block = first >> 6; + int32_t bs_block_count = ((bs_elem_count - 1) >> 6) + 1; + + if (bs_block >= bs_block_count) { + goto done; } - ecs_rule_var_t - *pred = term_pred(rule, term), - *subj = term_subj(rule, term), - *obj = term_obj(rule, term); + uint64_t *data = bs->data; + int32_t bs_start = first & 0x3F; - if (subj != var) { - continue; + /* Step 1: find the first non-empty block */ + uint64_t v = data[bs_block]; + uint64_t remain = bitmask_remain[bs_start]; + while (!(v & remain)) { + /* If no elements are remaining, move to next block */ + if ((++bs_block) >= bs_block_count) { + /* No non-empty blocks left */ + goto done; + } + + bs_start = 0; + remain = BS_MAX; /* Test the full block */ + v = data[bs_block]; } - if (!is_subject(rule, pred)) { - pred = NULL; + /* Step 2: find the first non-empty element in the block */ + while (!(v & bitmask[bs_start])) { + bs_start ++; + + /* Block was not empty, so bs_start must be smaller than 64 */ + ecs_assert(bs_start < 64, ECS_INTERNAL_ERROR, NULL); } + + /* Step 3: Find number of contiguous enabled elements after start */ + int32_t bs_end = bs_start, bs_block_end = bs_block; + + remain = bitmask_remain[bs_end]; + while ((v & remain) == remain) { + bs_end = 0; + bs_block_end ++; - if (!is_subject(rule, obj)) { - obj = NULL; + if (bs_block_end == bs_block_count) { + break; + } + + v = data[bs_block_end]; + remain = BS_MAX; /* Test the full block */ } - int32_t depth = get_depth_from_term(rule, var, pred, obj, root, recur); - if (depth < result) { - result = depth; + /* Step 4: find remainder of enabled elements in current block */ + if (bs_block_end != bs_block_count) { + while ((v & bitmask[bs_end])) { + bs_end ++; + } + } + + /* Block was not 100% occupied, so bs_start must be smaller than 64 */ + ecs_assert(bs_end < 64, ECS_INTERNAL_ERROR, NULL); + + /* Step 5: translate to element start/end and make sure that each column + * range is a subset of the previous one. */ + first = bs_block * 64 + bs_start; + int32_t cur_last = bs_block_end * 64 + bs_end; + + /* No enabled elements found in table */ + if (first == cur_last) { + goto done; + } + + /* If multiple bitsets are evaluated, make sure each subsequent range + * is equal or a subset of the previous range */ + if (i) { + /* If the first element of a subsequent bitset is larger than the + * previous last value, start over. */ + if (first >= last) { + i = -1; + continue; + } + + /* Make sure the last element of the range doesn't exceed the last + * element of the previous range. */ + if (cur_last > last) { + cur_last = last; + } + } + + last = cur_last; + int32_t elem_count = last - first; + + /* Make sure last element doesn't exceed total number of elements in + * the table */ + if (elem_count > (bs_elem_count - first)) { + elem_count = (bs_elem_count - first); + if (!elem_count) { + iter->bitset_first = 0; + goto done; + } } + + cur->first = first; + cur->count = elem_count; + iter->bitset_first = first; } + + /* Keep track of last processed element for iteration */ + iter->bitset_first = last; - if (result == UINT8_MAX) { - result = 0; - } + return 0; +done: + iter->sparse_smallest = 0; + iter->sparse_first = 0; + return -1; +} - var->depth = result; +static +void mark_columns_dirty( + ecs_query_t *query, + ecs_query_table_match_t *table_data) +{ + ecs_table_t *table = table_data->table; - /* Dependencies are calculated from subject to (pred, obj). If there were - * subjects that are only related by object (like (X, Y), (Z, Y)) it is - * possible that those have not yet been found yet. To make sure those - * variables are found, loop again & follow predicate & object links */ - for (i = 0; i < count; i ++) { - ecs_term_t *term = &terms[i]; - if (skip_term(term)) { - continue; - } + if (table && table->dirty_state) { + ecs_term_t *terms = query->filter.terms; + int32_t i, count = query->filter.term_count_actual; + for (i = 0; i < count; i ++) { + ecs_term_t *term = &terms[i]; + int32_t ti = term->index; - ecs_rule_var_t - *subj = term_subj(rule, term), - *pred = term_pred(rule, term), - *obj = term_obj(rule, term); + if (term->inout == EcsIn) { + /* Don't mark readonly terms dirty */ + continue; + } - /* Only evaluate pred & obj for current subject. This ensures that we - * won't evaluate variables that are unreachable from the root. This - * must be detected as unconstrained variables are not allowed. */ - if (subj != var) { - continue; - } + if (table_data->subjects[ti] != 0) { + /* Don't mark table dirty if term is not from the table */ + continue; + } - crawl_variable(rule, subj, root, recur); + int32_t index = table_data->columns[ti]; + if (index <= 0) { + /* If term is not set, there's nothing to mark dirty */ + continue; + } - if (pred && pred != var) { - crawl_variable(rule, pred, root, recur); + /* Potential candidate for marking table dirty, if a component */ + int32_t storage_index = ecs_table_type_to_storage_index( + table, index - 1); + if (storage_index >= 0) { + table->dirty_state[storage_index + 1] ++; + } } + } +} - if (obj && obj != var) { - crawl_variable(rule, obj, root, recur); - } +bool ecs_query_next( + ecs_iter_t *it) +{ + ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(it->next == ecs_query_next, ECS_INVALID_PARAMETER, NULL); + + if (flecs_iter_next_row(it)) { + return true; } - return var->depth; + return flecs_iter_next_instanced(it, ecs_query_next_instanced(it)); +error: + return false; } -/* Compare function used for qsort. It ensures that variables are first ordered - * by depth, followed by how often they occur. */ -static -int compare_variable( - const void* ptr1, - const void *ptr2) +bool ecs_query_next_instanced( + ecs_iter_t *it) { - const ecs_rule_var_t *v1 = ptr1; - const ecs_rule_var_t *v2 = ptr2; + ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(it->next == ecs_query_next, ECS_INVALID_PARAMETER, NULL); - if (v1->kind < v2->kind) { - return -1; - } else if (v1->kind > v2->kind) { - return 1; - } + ecs_query_iter_t *iter = &it->iter.query; + ecs_page_iter_t *piter = &iter->page_iter; + ecs_query_t *query = iter->query; + ecs_world_t *world = query->world; + (void)world; - if (v1->depth < v2->depth) { - return -1; - } else if (v1->depth > v2->depth) { - return 1; - } + it->is_valid = true; - if (v1->occurs < v2->occurs) { - return 1; - } else { - return -1; - } + ecs_poly_assert(world, ecs_world_t); - return (v1->id < v2->id) - (v1->id > v2->id); -} + if (!query->constraints_satisfied) { + goto done; + } + + ecs_page_cursor_t cur; + int32_t prev_count = it->total_count; -/* After all subject variables have been found, inserted and sorted, the - * remaining variables (predicate & object) still need to be inserted. This - * function serves two purposes. The first purpose is to ensure that all - * variables are known before operations are emitted. This ensures that the - * variables array won't be reallocated while emitting, which simplifies code. - * The second purpose of the function is to ensure that if the root variable - * (which, if it exists has now been created with a table type) is also inserted - * with an entity type if required. This is used later to decide whether the - * rule needs to insert an each instruction. */ -static -void ensure_all_variables( - ecs_rule_t *rule) -{ - ecs_term_t *terms = rule->filter.terms; - int32_t i, count = rule->filter.term_count; + ecs_query_table_node_t *node, *next; + for (node = iter->node; node != NULL; node = next) { + ecs_query_table_match_t *match = node->match; + ecs_table_t *table = match->table; - for (i = 0; i < count; i ++) { - ecs_term_t *term = &terms[i]; - if (skip_term(term)) { - continue; - } + next = node->next; - /* If predicate is a variable, make sure it has been registered */ - if (term->pred.var == EcsVarIsVariable) { - ensure_variable(rule, EcsRuleVarKindEntity, term_id_var_name(&term->pred)); - } + if (table) { + cur.first = node->offset; + cur.count = node->count; + if (!cur.count) { + cur.count = ecs_table_count(table); - /* If subject is a variable and it is not This, make sure it is - * registered as an entity variable. This ensures that the program will - * correctly return all permutations */ - if (term->subj.var == EcsVarIsVariable) { - if (term->subj.entity != EcsThis) { - ensure_variable(rule, EcsRuleVarKindEntity, term_id_var_name(&term->subj)); + /* List should never contain empty tables */ + ecs_assert(cur.count != 0, ECS_INTERNAL_ERROR, NULL); } - } - - /* If object is a variable, make sure it has been registered */ - if (obj_is_set(term) && (term->obj.var == EcsVarIsVariable)) { - ensure_variable(rule, EcsRuleVarKindEntity, term_id_var_name(&term->obj)); - } - } -} -/* Scan for variables, put them in optimal dependency order. */ -static -int scan_variables( - ecs_rule_t *rule) -{ - /* Objects found in rule. One will be elected root */ - int32_t subject_count = 0; + ecs_vector_t *bitset_columns = match->bitset_columns; + ecs_vector_t *sparse_columns = match->sparse_columns; - /* If this (.) is found, it always takes precedence in root election */ - int32_t this_var = UINT8_MAX; + if (bitset_columns || sparse_columns) { + bool found = false; - /* Keep track of the subject variable that occurs the most. In the absence of - * this (.) the variable with the most occurrences will be elected root. */ - int32_t max_occur = 0; - int32_t max_occur_var = UINT8_MAX; + do { + found = false; - /* Step 1: find all possible roots */ - ecs_term_t *terms = rule->filter.terms; - int32_t i, term_count = rule->filter.term_count; + if (bitset_columns) { + if (bitset_column_next(table, bitset_columns, iter, + &cur) == -1) + { + /* No more enabled components for table */ + break; + } else { + found = true; + next = node; + } + } - for (i = 0; i < term_count; i ++) { - ecs_term_t *term = &terms[i]; + if (sparse_columns) { + if (sparse_column_next(table, match, + sparse_columns, iter, &cur, found) == -1) + { + /* No more elements in sparse column */ + if (found) { + /* Try again */ + next = node->next; + found = false; + } else { + /* Nothing found */ + break; + } + } else { + found = true; + next = node; + iter->bitset_first = cur.first + cur.count; + } + } + } while (!found); - /* Evaluate the subject. The predicate and object are not evaluated, - * since they never can be elected as root. */ - if (term_id_is_variable(&term->subj)) { - const char *subj_name = term_id_var_name(&term->subj); - - ecs_rule_var_t *subj = find_variable( - rule, EcsRuleVarKindTable, subj_name); - if (!subj) { - subj = create_variable(rule, EcsRuleVarKindTable, subj_name); - if (subject_count >= ECS_RULE_MAX_VARIABLE_COUNT) { - rule_error(rule, "too many variables in rule"); - goto error; + if (!found) { + continue; } } - if (++ subj->occurs > max_occur) { - max_occur = subj->occurs; - max_occur_var = subj->id; + int ret = ecs_page_iter_next(piter, &cur); + if (ret < 0) { + goto done; + } else if (ret > 0) { + continue; } - } - } - - rule->subject_variable_count = rule->variable_count; - ensure_all_variables(rule); + it->total_count = cur.count; + } else { + cur.count = 0; + cur.first = 0; + } - /* Variables in a term with a literal subject have depth 0 */ - for (i = 0; i < term_count; i ++) { - ecs_term_t *term = &terms[i]; + it->ids = match->ids; + it->columns = match->columns; + it->subjects = match->subjects; + it->sizes = match->sizes; + it->references = match->references; + it->frame_offset += prev_count; + it->instance_count = 0; - if (term->subj.var == EcsVarIsEntity) { - ecs_rule_var_t - *pred = term_pred(rule, term), - *obj = term_obj(rule, term); + flecs_iter_init(it); + flecs_iter_populate_data(world, it, match->table, cur.first, cur.count, it->ptrs, NULL); - if (pred) { - pred->depth = 0; - } - if (obj) { - obj->depth = 0; + if (query->flags & EcsQueryHasOutColumns) { + if (table) { + mark_columns_dirty(query, match); } } - } - /* Elect a root. This is either this (.) or the variable with the most - * occurrences. */ - int32_t root_var = this_var; - if (root_var == UINT8_MAX) { - root_var = max_occur_var; - if (root_var == UINT8_MAX) { - /* If no subject variables have been found, the rule expression only - * operates on a fixed set of entities, in which case no root - * election is required. */ - goto done; - } + iter->node = next; + + goto yield; } - ecs_rule_var_t *root = &rule->variables[root_var]; - root->depth = get_variable_depth(rule, root, root, 0); +done: +error: + flecs_iter_fini(it); + return false; + +yield: + return true; +} - /* Verify that there are no unconstrained variables. Unconstrained variables - * are variables that are unreachable from the root. */ - for (i = 0; i < rule->subject_variable_count; i ++) { - if (rule->variables[i].depth == UINT8_MAX) { - rule_error(rule, "unconstrained variable '%s'", - rule->variables[i].name); - goto error; - } - } +bool ecs_query_next_worker( + ecs_iter_t *it, + int32_t current, + int32_t total) +{ + int32_t per_worker, instances_per_worker, first, prev_offset = it->offset; + ecs_world_t *world = it->world; - /* For each Not term, verify that variables are known */ - for (i = 0; i < term_count; i ++) { - ecs_term_t *term = &terms[i]; - if (term->oper != EcsNot) { - continue; + do { + if (!ecs_query_next(it)) { + return false; } - ecs_rule_var_t - *pred = term_pred(rule, term), - *obj = term_obj(rule, term); + int32_t count = it->count; + int32_t instance_count = it->instance_count; + per_worker = count / total; + instances_per_worker = instance_count / total; + first = per_worker * current; + count -= per_worker * total; - if (!pred && term_id_is_variable(&term->pred)) { - rule_error(rule, "missing predicate variable '%s'", - term_id_var_name(&term->pred)); - goto error; + if (count) { + if (current < count) { + per_worker ++; + first += current; + } else { + first += count; + } } - if (!obj && term_id_is_variable(&term->obj)) { - rule_error(rule, "missing object variable '%s'", - term_id_var_name(&term->obj)); - goto error; + + if (!per_worker && !(it->iter.query.query->flags & EcsQueryNeedsTables)) { + if (current == 0) { + flecs_iter_populate_data(world, it, it->table, it->offset, it->count, it->ptrs, NULL); + return true; + } else { + return false; + } } - } + } while (!per_worker); - /* Order variables by depth, followed by occurrence. The variable - * array will later be used to lead the iteration over the terms, and - * determine which operations get inserted first. */ - size_t var_count = flecs_itosize(rule->variable_count); - qsort(rule->variables, var_count, sizeof(ecs_rule_var_t), compare_variable); + it->instance_count = instances_per_worker; + it->frame_offset -= prev_offset; + it->frame_offset += first; - /* Iterate variables to correct ids after sort */ - for (i = 0; i < rule->variable_count; i ++) { - rule->variables[i].id = i; - } - -done: - return 0; -error: - return -1; + flecs_iter_populate_data( + world, it, it->table, it->offset + first, per_worker, it->ptrs, NULL); + + return true; } -/* Get entity variable from table variable */ -static -ecs_rule_var_t* to_entity( - ecs_rule_t *rule, - ecs_rule_var_t *var) +bool ecs_query_changed( + const ecs_query_t *query) { - if (!var) { - return NULL; - } - - ecs_rule_var_t *evar = NULL; - if (var->kind == EcsRuleVarKindTable) { - evar = find_variable(rule, EcsRuleVarKindEntity, var->name); - } else { - evar = var; - } - - return evar; + ecs_poly_assert(query, ecs_query_t); + ecs_check(!(query->flags & EcsQueryIsOrphaned), + ECS_INVALID_PARAMETER, NULL); + return tables_dirty(query); +error: + return false; } -/* Ensure that if a table variable has been written, the corresponding entity - * variable is populated. The function will return the most specific, populated - * variable. */ -static -ecs_rule_var_t* most_specific_var( - ecs_rule_t *rule, - ecs_rule_var_t *var, - bool *written, - bool create) +bool ecs_query_orphaned( + ecs_query_t *query) { - if (!var) { - return NULL; - } - - ecs_rule_var_t *tvar, *evar = to_entity(rule, var); - if (!evar) { - return var; - } + ecs_poly_assert(query, ecs_query_t); + return query->flags & EcsQueryIsOrphaned; +} - if (var->kind == EcsRuleVarKindTable) { - tvar = var; - } else { - tvar = find_variable(rule, EcsRuleVarKindTable, var->name); +#define INIT_CACHE(it, f, term_count)\ + if (!it->f && term_count) {\ + if (term_count < ECS_TERM_CACHE_SIZE) {\ + it->f = it->cache.f;\ + it->cache.f##_alloc = false;\ + } else {\ + it->f = ecs_os_calloc(ECS_SIZEOF(*(it->f)) * term_count);\ + it->cache.f##_alloc = true;\ + }\ } - /* If variable is used as predicate or object, it should have been - * registered as an entity. */ - ecs_assert(evar != NULL, ECS_INTERNAL_ERROR, NULL); - - /* Usually table variables are resolved before they are used as a predicate - * or object, but in the case of cyclic dependencies this is not guaranteed. - * Only insert an each instruction of the table variable has been written */ - if (tvar && written[tvar->id]) { - /* If the variable has been written as a table but not yet - * as an entity, insert an each operation that yields each - * entity in the table. */ - if (evar) { - if (written[evar->id]) { - return evar; - } else if (create) { - ecs_rule_op_t *op = create_operation(rule); - op->kind = EcsRuleEach; - op->on_pass = rule->operation_count; - op->on_fail = rule->operation_count - 2; - op->frame = rule->frame_count; - op->has_in = true; - op->has_out = true; - op->r_in = tvar->id; - op->r_out = evar->id; - - /* Entity will either be written or has been written */ - written[evar->id] = true; - - push_frame(rule); +#define FINI_CACHE(it, f)\ + if (it->f) {\ + if (it->cache.f##_alloc) {\ + ecs_os_free((void*)it->f);\ + }\ + } - return evar; - } else { - return tvar; - } - } - } else if (evar && written[evar->id]) { - return evar; +void flecs_iter_init( + ecs_iter_t *it) +{ + INIT_CACHE(it, ids, it->term_count); + INIT_CACHE(it, subjects, it->term_count); + INIT_CACHE(it, match_indices, it->term_count); + INIT_CACHE(it, columns, it->term_count); + + if (!it->is_filter) { + INIT_CACHE(it, sizes, it->term_count); + INIT_CACHE(it, ptrs, it->term_count); + } else { + it->sizes = NULL; + it->ptrs = NULL; } - return var; + it->is_valid = true; } -/* Get most specific known variable */ -static -ecs_rule_var_t *get_most_specific_var( - ecs_rule_t *rule, - ecs_rule_var_t *var, - bool *written) +void flecs_iter_fini( + ecs_iter_t *it) { - return most_specific_var(rule, var, written, false); + ecs_check(it->is_valid == true, ECS_INVALID_PARAMETER, NULL); + it->is_valid = false; + + FINI_CACHE(it, ids); + FINI_CACHE(it, columns); + FINI_CACHE(it, subjects); + FINI_CACHE(it, sizes); + FINI_CACHE(it, ptrs); + FINI_CACHE(it, match_indices); +error: + return; } -/* Get or create most specific known variable. This will populate an entity - * variable if a table variable is known but the entity variable isn't. */ static -ecs_rule_var_t *ensure_most_specific_var( - ecs_rule_t *rule, - ecs_rule_var_t *var, - bool *written) +bool flecs_iter_populate_term_data( + ecs_world_t *world, + ecs_iter_t *it, + int32_t t, + int32_t column, + void **ptr_out, + ecs_size_t *size_out) { - return most_specific_var(rule, var, written, true); -} + bool is_shared = false; + + if (!column) { + /* Term has no data. This includes terms that have Not operators. */ + goto no_data; + } + ecs_assert(it->terms != NULL, ECS_INTERNAL_ERROR, NULL); -/* Ensure that an entity variable is written before using it */ -static -ecs_rule_var_t* ensure_entity_written( - ecs_rule_t *rule, - ecs_rule_var_t *var, - bool *written) -{ - if (!var) { - return NULL; + /* Filter terms may match with data but don't return it */ + if (it->terms[t].inout == EcsInOutFilter) { + goto no_data; } - /* Ensure we're working with the most specific version of subj we can get */ - ecs_rule_var_t *evar = ensure_most_specific_var(rule, var, written); + ecs_table_t *table; + ecs_vector_t *vec; + ecs_size_t size; + ecs_size_t align; + int32_t row; - /* The post condition of this function is that there is an entity variable, - * and that it is written. Make sure that the result is an entity */ - ecs_assert(evar != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(evar->kind == EcsRuleVarKindEntity, ECS_INTERNAL_ERROR, NULL); + if (column < 0) { + is_shared = true; - /* Make sure the variable has been written */ - ecs_assert(written[evar->id] == true, ECS_INTERNAL_ERROR, NULL); - - return evar; -} + /* Data is not from This */ + if (it->references) { + /* Iterator provides cached references for non-This terms */ + ecs_ref_t *ref = &it->references[-column - 1]; + if (ptr_out) ptr_out[0] = (void*)ecs_get_ref_w_id( + world, ref, ref->entity, ref->component); -static -ecs_rule_op_t* insert_operation( - ecs_rule_t *rule, - int32_t term_index, - bool *written) -{ - ecs_rule_pair_t pair = {0}; + /* If cached references were provided, the code that populated + * the iterator also had a chance to cache sizes, so size array + * should already have been assigned. This saves us from having + * to do additional lookups to find the component size. */ + ecs_assert(size_out == NULL, ECS_INTERNAL_ERROR, NULL); + return true; + } else { + ecs_entity_t subj = it->subjects[t]; + ecs_assert(subj != 0, ECS_INTERNAL_ERROR, NULL); - /* Parse the term's type into a pair. A pair extracts the ids from - * the term, and replaces variables with wildcards which can then - * be matched against actual relationships. A pair retains the - * information about the variables, so that when a match happens, - * the pair can be used to reify the variable. */ - if (term_index != -1) { - ecs_term_t *term = &rule->filter.terms[term_index]; + /* Don't use ecs_get_id directly. Instead, go directly to the + * storage so that we can get both the pointer and size */ + ecs_record_t *r = ecs_eis_get(world, subj); + ecs_assert(r != NULL && r->table != NULL, ECS_INTERNAL_ERROR, NULL); - pair = term_to_pair(rule, term); + row = ECS_RECORD_TO_ROW(r->row); + table = r->table; - /* If the pair contains entity variables that have not yet been written, - * insert each instructions in case their tables are known. Variables in - * a pair that are truly unknown will be populated by the operation, - * but an operation should never overwrite an entity variable if the - * corresponding table variable has already been resolved. */ - if (pair.reg_mask & RULE_PAIR_PREDICATE) { - ecs_rule_var_t *pred = &rule->variables[pair.pred.reg]; - pred = get_most_specific_var(rule, pred, written); - pair.pred.reg = pred->id; - } + ecs_id_t id = it->ids[t]; + ecs_table_t *s_table = table->storage_table; + ecs_table_record_t *tr; - if (pair.reg_mask & RULE_PAIR_OBJECT) { - ecs_rule_var_t *obj = &rule->variables[pair.obj.reg]; - obj = get_most_specific_var(rule, obj, written); - pair.obj.reg = obj->id; + if (!s_table || !(tr = flecs_get_table_record(world, s_table, id))){ + /* The entity has no components or the id is not a component */ + + ecs_id_t term_id = it->terms[t].id; + if (ECS_HAS_ROLE(term_id, SWITCH) || ECS_HAS_ROLE(term_id, CASE)) { + /* Edge case: if this is a switch. Find switch column in + * actual table, as its not in the storage table */ + tr = flecs_get_table_record(world, table, id); + ecs_assert(tr != NULL, ECS_INTERNAL_ERROR, NULL); + column = tr->column; + goto has_switch; + } else { + goto no_data; + } + } + + /* We now have row and column, so we can get the storage for the id + * which gives us the pointer and size */ + column = tr->column; + ecs_column_t *s = &table->storage.columns[column]; + size = s->size; + align = s->alignment; + vec = s->data; + /* Fallthrough to has_data */ } } else { - /* Not all operations have a filter (like Each) */ + /* Data is from This, use table from iterator */ + table = it->table; + if (!table) { + goto no_data; + } + + row = it->offset; + + int32_t storage_column = ecs_table_type_to_storage_index( + table, column - 1); + if (storage_column == -1) { + ecs_id_t id = it->terms[t].id; + if (ECS_HAS_ROLE(id, SWITCH) || ECS_HAS_ROLE(id, CASE)) { + goto has_switch; + } + goto no_data; + } + + ecs_column_t *s = &table->storage.columns[storage_column]; + size = s->size; + align = s->alignment; + vec = s->data; + /* Fallthrough to has_data */ } - ecs_rule_op_t *op = create_operation(rule); - op->on_pass = rule->operation_count; - op->on_fail = rule->operation_count - 2; - op->frame = rule->frame_count; - op->filter = pair; +has_data: + if (ptr_out) ptr_out[0] = ecs_vector_get_t(vec, size, align, row); + if (size_out) size_out[0] = size; + return is_shared; - /* Store corresponding signature term so we can correlate and - * store the table columns with signature columns. */ - op->term = term_index; +has_switch: { + /* Edge case: if column is a switch we should return the vector with case + * identifiers. Will be replaced in the future with pluggable storage */ + ecs_switch_t *sw = table->storage.sw_columns[ + (column - 1) - table->sw_column_offset].data; + vec = flecs_switch_values(sw); + size = ECS_SIZEOF(ecs_entity_t); + align = ECS_ALIGNOF(ecs_entity_t); + goto has_data; + } - return op; +no_data: + if (ptr_out) ptr_out[0] = NULL; + if (size_out) size_out[0] = 0; + return false; } -/* Insert first operation, which is always Input. This creates an entry in - * the register stack for the initial state. */ -static -void insert_input( - ecs_rule_t *rule) +void flecs_iter_populate_data( + ecs_world_t *world, + ecs_iter_t *it, + ecs_table_t *table, + int32_t offset, + int32_t count, + void **ptrs, + ecs_size_t *sizes) { - ecs_rule_op_t *op = create_operation(rule); - op->kind = EcsRuleInput; + it->table = table; + it->offset = offset; + it->count = count; - /* The first time Input is evaluated it goes to the next/first operation */ - op->on_pass = 1; + if (table) { + it->type = it->table->type; + if (!count) { + count = it->count = ecs_table_count(table); + } + if (count) { + it->entities = ecs_vector_get( + table->storage.entities, ecs_entity_t, offset); + } else { + it->entities = NULL; + } + } - /* When Input is evaluated with redo = true it will return false, which will - * finish the program as op becomes -1. */ - op->on_fail = -1; + if (it->is_filter) { + it->has_shared = false; + return; + } - push_frame(rule); + int t, term_count = it->term_count; + bool has_shared = false; + + if (ptrs && sizes) { + for (t = 0; t < term_count; t ++) { + int32_t column = it->columns[t]; + has_shared |= flecs_iter_populate_term_data(world, it, t, column, + &ptrs[t], + &sizes[t]); + } + } else { + for (t = 0; t < term_count; t ++) { + int32_t column = it->columns[t]; + void **ptr = NULL; + if (ptrs) { + ptr = &ptrs[t]; + } + ecs_size_t *size = NULL; + if (sizes) { + size = &sizes[t]; + } + has_shared |= flecs_iter_populate_term_data(world, it, t, column, + ptr, size); + } + } + + it->has_shared = has_shared; } -/* Insert last operation, which is always Yield. When the program hits Yield, - * data is returned to the application. */ -static -void insert_yield( - ecs_rule_t *rule) +bool flecs_iter_next_row( + ecs_iter_t *it) { - ecs_rule_op_t *op = create_operation(rule); - op->kind = EcsRuleYield; - op->has_in = true; - op->on_fail = rule->operation_count - 2; - /* Yield can only "fail" since it is the end of the program */ + ecs_assert(it != NULL, ECS_INTERNAL_ERROR, NULL); - /* Find variable associated with this. It is possible that the variable - * exists both as a table and as an entity. This can happen when a rule - * first selects a table for this, but then subsequently needs to evaluate - * each entity in that table. In that case the yield instruction should - * return the entity, so look for that first. */ - ecs_rule_var_t *var = find_variable(rule, EcsRuleVarKindEntity, "."); - if (!var) { - var = find_variable(rule, EcsRuleVarKindTable, "."); - } + bool is_instanced = it->is_instanced; + if (!is_instanced) { + int32_t instance_count = it->instance_count; + int32_t count = it->count; + int32_t offset = it->offset; - /* If there is no this, there is nothing to yield. In that case the rule - * simply returns true or false. */ - if (!var) { - op->r_in = UINT8_MAX; - } else { - op->r_in = var->id; + if (instance_count > count && offset < (instance_count - 1)) { + ecs_assert(count == 1, ECS_INTERNAL_ERROR, NULL); + int t, term_count = it->term_count; + for (t = 0; t < term_count; t ++) { + int32_t column = it->columns[t]; + if (column >= 0) { + void *ptr = it->ptrs[t]; + if (ptr) { + it->ptrs[t] = ECS_OFFSET(ptr, it->sizes[t]); + } + } + } + + if (it->entities) { + it->entities ++; + } + it->offset ++; + + return true; + } } - op->frame = push_frame(rule); + return false; } -/* Return superset/subset including the root */ -static -void insert_inclusive_set( - ecs_rule_t *rule, - ecs_rule_op_kind_t op_kind, - ecs_rule_var_t *out, - const ecs_rule_pair_t pair, - int32_t c, - bool *written, - bool inclusive) +bool flecs_iter_next_instanced( + ecs_iter_t *it, + bool result) { - ecs_assert(out != NULL, ECS_INTERNAL_ERROR, NULL); - - /* If the operation to be inserted is a superset, the output variable needs - * to be an entity as a superset is always resolved one at a time */ - ecs_assert((op_kind != EcsRuleSuperSet) || - out->kind == EcsRuleVarKindEntity, ECS_INTERNAL_ERROR, NULL); + it->instance_count = it->count; + if (result && !it->is_instanced && it->count && it->has_shared) { + it->count = 1; + } + return result; +} - int32_t setjmp_lbl = rule->operation_count; - int32_t store_lbl = setjmp_lbl + 1; - int32_t set_lbl = setjmp_lbl + 2; - int32_t next_op = setjmp_lbl + 4; - int32_t prev_op = setjmp_lbl - 1; +/* --- Public API --- */ - /* Insert 4 operations at once, so we don't have to worry about how - * the instruction array reallocs. If operation is not inclusive, we only - * need to insert the set operation. */ - if (inclusive) { - insert_operation(rule, -1, written); - insert_operation(rule, -1, written); - insert_operation(rule, -1, written); - } +void* ecs_term_w_size( + const ecs_iter_t *it, + size_t size, + int32_t term) +{ + ecs_check(it->is_valid, ECS_INVALID_PARAMETER, NULL); + ecs_check(!size || ecs_term_size(it, term) == size, + ECS_INVALID_PARAMETER, NULL); - ecs_rule_op_t *op = insert_operation(rule, -1, written); - ecs_rule_op_t *setjmp = op - 3; - ecs_rule_op_t *store = op - 2; - ecs_rule_op_t *set = op - 1; - ecs_rule_op_t *jump = op; + (void)size; - if (!inclusive) { - set_lbl = setjmp_lbl; - set = op; - setjmp = NULL; - store = NULL; - jump = NULL; - next_op = set_lbl + 1; - prev_op = set_lbl - 1; + if (!term) { + return it->entities; } - /* The SetJmp operation stores a conditional jump label that either - * points to the Store or *Set operation */ - if (inclusive) { - setjmp->kind = EcsRuleSetJmp; - setjmp->on_pass = store_lbl; - setjmp->on_fail = set_lbl; + if (!it->ptrs) { + return NULL; } - ecs_rule_var_t *pred = pair_pred(rule, &pair); - ecs_rule_var_t *obj = pair_obj(rule, &pair); + return it->ptrs[term - 1]; +error: + return NULL; +} - /* The Store operation yields the root of the subtree. After yielding, - * this operation will fail and return to SetJmp, which will cause it - * to switch to the *Set operation. */ - if (inclusive) { - store->kind = EcsRuleStore; - store->on_pass = next_op; - store->on_fail = setjmp_lbl; - store->has_in = true; - store->has_out = true; - store->r_out = out->id; - store->term = c; +bool ecs_term_is_readonly( + const ecs_iter_t *it, + int32_t term_index) +{ + ecs_check(it->is_valid, ECS_INVALID_PARAMETER, NULL); + ecs_check(term_index > 0, ECS_INVALID_PARAMETER, NULL); - if (!pred) { - store->filter.pred = pair.pred; - } else { - store->filter.pred.reg = pred->id; - store->filter.reg_mask |= RULE_PAIR_PREDICATE; + ecs_term_t *term = &it->terms[term_index - 1]; + ecs_check(term != NULL, ECS_INVALID_PARAMETER, NULL); + + if (term->inout == EcsIn) { + return true; + } else { + ecs_term_id_t *subj = &term->subj; + + if (term->inout == EcsInOutDefault) { + if (subj->entity != EcsThis) { + return true; + } + + if (!(subj->set.mask & EcsSelf)) { + return true; + } } + } - /* If the object of the filter is not a variable, store literal */ - if (!obj) { - store->r_in = UINT8_MAX; - store->subject = ecs_get_alive(rule->world, pair.obj.ent); - store->filter.obj = pair.obj; +error: + return false; +} + +int32_t ecs_iter_find_column( + const ecs_iter_t *it, + ecs_entity_t component) +{ + ecs_check(it->is_valid, ECS_INVALID_PARAMETER, NULL); + ecs_check(it->table != NULL, ECS_INVALID_PARAMETER, NULL); + return ecs_type_index_of(it->table->type, 0, component); +error: + return -1; +} + +bool ecs_term_is_set( + const ecs_iter_t *it, + int32_t index) +{ + ecs_check(it->is_valid, ECS_INVALID_PARAMETER, NULL); + + int32_t column = it->columns[index - 1]; + if (!column) { + return false; + } else if (column < 0) { + if (it->references) { + column = -column - 1; + ecs_ref_t *ref = &it->references[column]; + return ref->entity != 0; } else { - store->r_in = obj->id; - store->filter.obj.reg = obj->id; - store->filter.reg_mask |= RULE_PAIR_OBJECT; + return true; } } - /* This is either a SubSet or SuperSet operation */ - set->kind = op_kind; - set->on_pass = next_op; - set->on_fail = prev_op; - set->has_out = true; - set->r_out = out->id; - set->term = c; + return true; +error: + return false; +} - /* Predicate can be a variable if it's non-final */ - if (!pred) { - set->filter.pred = pair.pred; - } else { - set->filter.pred.reg = pred->id; - set->filter.reg_mask |= RULE_PAIR_PREDICATE; +void* ecs_iter_column_w_size( + const ecs_iter_t *it, + size_t size, + int32_t index) +{ + ecs_check(it->is_valid, ECS_INVALID_PARAMETER, NULL); + ecs_check(it->table != NULL, ECS_INVALID_PARAMETER, NULL); + (void)size; + + ecs_table_t *table = it->table; + int32_t storage_index = ecs_table_type_to_storage_index(table, index); + if (storage_index == -1) { + return NULL; } - if (!obj) { - set->filter.obj = pair.obj; - } else { - set->filter.obj.reg = obj->id; - set->filter.reg_mask |= RULE_PAIR_OBJECT; - } + ecs_column_t *columns = table->storage.columns; + ecs_column_t *column = &columns[storage_index]; + ecs_check(!size || (ecs_size_t)size == column->size, + ECS_INVALID_PARAMETER, NULL); - if (inclusive) { - /* The jump operation jumps to either the store or subset operation, - * depending on whether the store operation already yielded. The - * operation is inserted last, so that the on_fail label of the next - * operation will point to it */ - jump->kind = EcsRuleJump; - - /* The pass/fail labels of the Jump operation are not used, since it - * jumps to a variable location. Instead, the pass label is (ab)used to - * store the label of the SetJmp operation, so that the jump can access - * the label it needs to jump to from the setjmp op_ctx. */ - jump->on_pass = setjmp_lbl; - jump->on_fail = -1; + void *ptr = ecs_vector_first_t( + column->data, column->size, column->alignment); + + return ECS_OFFSET(ptr, column->size * it->offset); +error: + return NULL; +} + +size_t ecs_iter_column_size( + const ecs_iter_t *it, + int32_t index) +{ + ecs_check(it->is_valid, ECS_INVALID_PARAMETER, NULL); + ecs_check(it->table != NULL, ECS_INVALID_PARAMETER, NULL); + + ecs_table_t *table = it->table; + int32_t storage_index = ecs_table_type_to_storage_index(table, index); + if (storage_index == -1) { + return 0; } - written[out->id] = true; + ecs_column_t *columns = table->storage.columns; + ecs_column_t *column = &columns[storage_index]; + + return flecs_ito(size_t, column->size); +error: + return 0; } -static -ecs_rule_var_t* store_inclusive_set( - ecs_rule_t *rule, - ecs_rule_op_kind_t op_kind, - ecs_rule_pair_t *pair, - bool *written, - bool inclusive) +char* ecs_iter_str( + const ecs_iter_t *it) { - /* The subset operation returns tables */ - ecs_rule_var_kind_t var_kind = EcsRuleVarKindTable; + ecs_world_t *world = it->world; + ecs_strbuf_t buf = ECS_STRBUF_INIT; + int i; - /* The superset operation returns entities */ - if (op_kind == EcsRuleSuperSet) { - var_kind = EcsRuleVarKindEntity; + if (it->term_count) { + ecs_strbuf_list_push(&buf, "term: ", ","); + for (i = 0; i < it->term_count; i ++) { + ecs_id_t id = ecs_term_id(it, i + 1); + char *str = ecs_id_str(world, id); + ecs_strbuf_list_appendstr(&buf, str); + ecs_os_free(str); + } + ecs_strbuf_list_pop(&buf, "\n"); + + ecs_strbuf_list_push(&buf, "subj: ", ","); + for (i = 0; i < it->term_count; i ++) { + ecs_entity_t subj = ecs_term_source(it, i + 1); + char *str = ecs_get_fullpath(world, subj); + ecs_strbuf_list_appendstr(&buf, str); + ecs_os_free(str); + } + ecs_strbuf_list_pop(&buf, "\n"); } - /* Create anonymous variable for storing the set */ - ecs_rule_var_t *av = create_anonymous_variable(rule, var_kind); - ecs_rule_var_t *ave = NULL; + if (it->variable_count) { + int32_t actual_count = 0; + for (i = 0; i < it->variable_count; i ++) { + const char *var_name = it->variable_names[i]; + if (!var_name || var_name[0] == '_' || var_name[0] == '.') { + /* Skip anonymous variables */ + continue; + } - /* If the variable kind is a table, also create an entity variable as the - * result of the set operation should be returned as an entity */ - if (var_kind == EcsRuleVarKindTable) { - ave = create_variable(rule, EcsRuleVarKindEntity, av->name); - av = &rule->variables[ave->id - 1]; + ecs_entity_t var = it->variables[i]; + if (!var) { + /* Skip table variables */ + continue; + } + + if (!actual_count) { + ecs_strbuf_list_push(&buf, "vars: ", ","); + } + + char *str = ecs_get_fullpath(world, var); + ecs_strbuf_list_append(&buf, "%s=%s", var_name, str); + ecs_os_free(str); + + actual_count ++; + } + if (actual_count) { + ecs_strbuf_list_pop(&buf, "\n"); + } } - /* Ensure we're using the most specific version of obj */ - ecs_rule_var_t *obj = pair_obj(rule, pair); - if (obj) { - pair->obj.reg = obj->id; + if (it->count) { + ecs_strbuf_appendstr(&buf, "this:\n"); + for (i = 0; i < it->count; i ++) { + ecs_entity_t e = it->entities[i]; + char *str = ecs_get_fullpath(world, e); + ecs_strbuf_appendstr(&buf, " - "); + ecs_strbuf_appendstr(&buf, str); + ecs_strbuf_appendstr(&buf, "\n"); + ecs_os_free(str); + } } - /* Generate the operations */ - insert_inclusive_set(rule, op_kind, av, *pair, -1, written, inclusive); + return ecs_strbuf_get(&buf); +} - /* Make sure to return entity variable, and that it is populated */ - return ensure_entity_written(rule, av, written); +void ecs_iter_poly( + const ecs_world_t *world, + const ecs_poly_t *poly, + ecs_iter_t *iter_out, + ecs_term_t *filter) +{ + ecs_iterable_t *iterable = ecs_get_iterable(poly); + iterable->init(world, poly, iter_out, filter); } -static -bool is_known( - ecs_rule_var_t *var, - bool *written) +bool ecs_iter_next( + ecs_iter_t *iter) { - if (!var) { - return true; - } else { - return written[var->id]; - } + ecs_check(iter != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(iter->next != NULL, ECS_INVALID_PARAMETER, NULL); + return iter->next(iter); +error: + return false; } -static -bool is_pair_known( - ecs_rule_t *rule, - ecs_rule_pair_t *pair, - bool *written) +bool ecs_iter_count( + ecs_iter_t *it) { - ecs_rule_var_t *pred_var = pair_pred(rule, pair); - if (!is_known(pred_var, written)) { - return false; + ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); + int32_t count = 0; + while (ecs_iter_next(it)) { + count += it->count; } + return count; +error: + return 0; +} - ecs_rule_var_t *obj_var = pair_obj(rule, pair); - if (!is_known(obj_var, written)) { - return false; - } +/* -- Component lifecycle -- */ - return true; -} +/* Component lifecycle actions for EcsIdentifier */ +static ECS_CTOR(EcsIdentifier, ptr, { + ptr->value = NULL; + ptr->hash = 0; + ptr->length = 0; +}) -static -void set_input_to_subj( - ecs_rule_t *rule, - ecs_rule_op_t *op, - ecs_term_t *term, - ecs_rule_var_t *var) -{ - (void)rule; - - op->has_in = true; - if (!var) { - op->r_in = UINT8_MAX; - op->subject = term->subj.entity; +static ECS_DTOR(EcsIdentifier, ptr, { + ecs_os_strset(&ptr->value, NULL); +}) - /* Invalid entities should have been caught during parsing */ - ecs_assert(ecs_is_valid(rule->world, op->subject), - ECS_INTERNAL_ERROR, NULL); - } else { - op->r_in = var->id; - } -} +static ECS_COPY(EcsIdentifier, dst, src, { + ecs_os_strset(&dst->value, src->value); + dst->hash = src->hash; + dst->length = src->length; +}) -static -void set_output_to_subj( - ecs_rule_t *rule, - ecs_rule_op_t *op, - ecs_term_t *term, - ecs_rule_var_t *var) -{ - (void)rule; +static ECS_MOVE(EcsIdentifier, dst, src, { + ecs_os_strset(&dst->value, NULL); + dst->value = src->value; + dst->hash = src->hash; + dst->length = src->length; - op->has_out = true; - if (!var) { - op->r_out = UINT8_MAX; - op->subject = term->subj.entity; + src->value = NULL; + src->hash = 0; + src->length = 0; - /* Invalid entities should have been caught during parsing */ - ecs_assert(ecs_is_valid(rule->world, op->subject), - ECS_INTERNAL_ERROR, NULL); +}) + +static ECS_ON_SET(EcsIdentifier, ptr, { + if (ptr->value) { + ptr->length = ecs_os_strlen(ptr->value); + ptr->hash = flecs_hash(ptr->value, ptr->length); } else { - op->r_out = var->id; + ptr->length = 0; + ptr->hash = 0; } -} +}) -static -void insert_select_or_with( - ecs_rule_t *rule, - int32_t c, - ecs_term_t *term, - ecs_rule_var_t *subj, - ecs_rule_pair_t *pair, - bool *written) -{ - ecs_rule_op_t *op; - bool eval_subject_supersets = false; - bool wildcard_subj = term->subj.entity == EcsWildcard; +/* Component lifecycle actions for EcsTrigger */ +static ECS_CTOR(EcsTrigger, ptr, { + ptr->trigger = NULL; +}) - /* Find any entity and/or table variables for subject */ - ecs_rule_var_t *tvar = NULL, *evar = to_entity(rule, subj), *var = evar; - if (subj && subj->kind == EcsRuleVarKindTable) { - tvar = subj; - if (!evar) { - var = tvar; - } +static ECS_DTOR(EcsTrigger, ptr, { + if (ptr->trigger) { + flecs_trigger_fini(world, (ecs_trigger_t*)ptr->trigger); } +}) - int32_t lbl_start = rule->operation_count; - ecs_rule_pair_t filter; - if (pair) { - filter = *pair; - } else { - filter = term_to_pair(rule, term); - } +static ECS_COPY(EcsTrigger, dst, src, { + ecs_abort(ECS_INVALID_OPERATION, "Trigger component cannot be copied"); +}) - if (!var && !wildcard_subj) { - /* Only insert implicit IsA if filter isn't already an IsA */ - if (!filter.transitive || filter.pred.ent != EcsIsA) { - ecs_rule_pair_t isa_pair = { - .pred.ent = EcsIsA, - .obj.ent = term->subj.entity - }; - evar = subj = store_inclusive_set( - rule, EcsRuleSuperSet, &isa_pair, written, true); - tvar = NULL; - eval_subject_supersets = true; - } +static ECS_MOVE(EcsTrigger, dst, src, { + if (dst->trigger) { + flecs_trigger_fini(world, (ecs_trigger_t*)dst->trigger); } + dst->trigger = src->trigger; + src->trigger = NULL; +}) - /* If no pair is provided, create operation from specified term */ - if (!pair) { - op = insert_operation(rule, c, written); +/* Component lifecycle actions for EcsObserver */ +static ECS_CTOR(EcsObserver, ptr, { + ptr->observer = NULL; +}) - /* If an explicit pair is provided, override the default one from the - * term. This allows for using a predicate or object variable different - * from what is in the term. One application of this is to substitute a - * predicate with its subsets, if it is non final */ - } else { - op = insert_operation(rule, -1, written); - op->filter = *pair; +static ECS_DTOR(EcsObserver, ptr, { + if (ptr->observer) { + flecs_observer_fini(world, (ecs_observer_t*)ptr->observer); + } +}) - /* Assign the term id, so that the operation will still be correctly - * associated with the correct expression term. */ - op->term = c; +static ECS_COPY(EcsObserver, dst, src, { + ecs_abort(ECS_INVALID_OPERATION, "Observer component cannot be copied"); +}) + +static ECS_MOVE(EcsObserver, dst, src, { + if (dst->observer) { + flecs_observer_fini(world, (ecs_observer_t*)dst->observer); } + dst->observer = src->observer; + src->observer = NULL; +}) - /* If entity variable is known and resolved, create with for it */ - if (evar && is_known(evar, written)) { - op->kind = EcsRuleWith; - op->r_in = evar->id; - set_input_to_subj(rule, op, term, subj); - /* If table variable is known and resolved, create with for it */ - } else if (tvar && is_known(tvar, written)) { - op->kind = EcsRuleWith; - op->r_in = tvar->id; - set_input_to_subj(rule, op, term, subj); +/* -- Builtin triggers -- */ - /* If subject is neither table nor entitiy, with operates on literal */ - } else if (!tvar && !evar && !wildcard_subj) { - op->kind = EcsRuleWith; - set_input_to_subj(rule, op, term, subj); +static +void register_on_delete(ecs_iter_t *it) { + ecs_world_t *world = it->world; + ecs_id_t id = ecs_term_id(it, 1); + + int i, count = it->count; + for (i = 0; i < count; i ++) { + ecs_entity_t e = it->entities[i]; + ecs_id_record_t *r = flecs_ensure_id_record(world, e); + ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); + r->on_delete = ECS_PAIR_OBJECT(id); - /* If subject is table or entity but not known, use select */ - } else { - ecs_assert(wildcard_subj || subj != NULL, ECS_INTERNAL_ERROR, NULL); - op->kind = EcsRuleSelect; - if (!wildcard_subj) { - set_output_to_subj(rule, op, term, subj); - written[subj->id] = true; - } + r = flecs_ensure_id_record(world, ecs_pair(e, EcsWildcard)); + ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); + r->on_delete = ECS_PAIR_OBJECT(id); + + flecs_add_flag(world, e, ECS_FLAG_OBSERVED_ID); } +} - /* If supersets of subject are being evaluated, and we're looking for a - * specific filter, stop as soon as the filter has been matched. */ - if (eval_subject_supersets && is_pair_known(rule, &op->filter, written)) { - op = insert_operation(rule, -1, written); +static +void register_on_delete_object(ecs_iter_t *it) { + ecs_world_t *world = it->world; + ecs_id_t id = ecs_term_id(it, 1); - /* When the next operation returns, it will first hit SetJmp with a redo - * which will switch the jump label to the previous operation */ - op->kind = EcsRuleSetJmp; - op->on_pass = rule->operation_count; - op->on_fail = lbl_start - 1; - } + int i, count = it->count; + for (i = 0; i < count; i ++) { + ecs_entity_t e = it->entities[i]; + ecs_id_record_t *r = flecs_ensure_id_record(world, e); + ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); + r->on_delete_object = ECS_PAIR_OBJECT(id); - if (op->filter.reg_mask & RULE_PAIR_PREDICATE) { - written[op->filter.pred.reg] = true; - } + flecs_add_flag(world, e, ECS_FLAG_OBSERVED_ID); + } +} - if (op->filter.reg_mask & RULE_PAIR_OBJECT) { - written[op->filter.obj.reg] = true; +static +void on_set_component_lifecycle(ecs_iter_t *it) { + ecs_world_t *world = it->world; + EcsComponentLifecycle *cl = ecs_term(it, EcsComponentLifecycle, 1); + + int i, count = it->count; + for (i = 0; i < count; i ++) { + ecs_entity_t e = it->entities[i]; + ecs_set_component_actions_w_id(world, e, &cl[i]); } } static -void prepare_predicate( - ecs_rule_t *rule, - ecs_rule_pair_t *pair, - bool *written) -{ - /* If pair is not final, resolve term for all IsA relationships of the - * predicate. Note that if the pair has final set to true, it is guaranteed - * that the predicate can be used in an IsA query */ - if (!pair->final) { - ecs_rule_pair_t isa_pair = { - .pred.ent = EcsIsA, - .obj.ent = pair->pred.ent - }; - - ecs_rule_var_t *pred = store_inclusive_set( - rule, EcsRuleSubSet, &isa_pair, written, true); +void ensure_module_tag(ecs_iter_t *it) { + ecs_world_t *world = it->world; - pair->pred.reg = pred->id; - pair->reg_mask |= RULE_PAIR_PREDICATE; + int i, count = it->count; + for (i = 0; i < count; i ++) { + ecs_entity_t e = it->entities[i]; + ecs_entity_t parent = ecs_get_object(world, e, EcsChildOf, 0); + if (parent) { + ecs_add_id(world, parent, EcsModule); + } } } + +/* -- Iterable mixins -- */ + static -void insert_term_2( - ecs_rule_t *rule, - ecs_term_t *term, - ecs_rule_pair_t *filter, - int32_t c, - bool *written) +void on_event_iterable_init( + const ecs_world_t *world, + const ecs_poly_t *poly, /* Observable */ + ecs_iter_t *it, + ecs_term_t *filter) { - int32_t subj_id = -1, obj_id = -1; - ecs_rule_var_t *subj = term_subj(rule, term); - if ((subj = get_most_specific_var(rule, subj, written))) { - subj_id = subj->id; - } + ecs_iter_poly(world, poly, it, filter); + it->event = EcsOnAdd; + it->event_id = filter->id; +} - ecs_rule_var_t *obj = term_obj(rule, term); - if ((obj = get_most_specific_var(rule, obj, written))) { - obj_id = obj->id; - } - if (!filter->transitive) { - insert_select_or_with(rule, c, term, subj, filter, written); +/* -- Bootstrapping -- */ - } else if (filter->transitive) { - if (is_known(subj, written)) { - if (is_known(obj, written)) { - ecs_rule_var_t *obj_subsets = store_inclusive_set( - rule, EcsRuleSubSet, filter, written, true); +#define bootstrap_component(world, table, name)\ + _bootstrap_component(world, table, ecs_id(name), #name, sizeof(name),\ + ECS_ALIGNOF(name)) - if (subj) { - subj = &rule->variables[subj_id]; - } +static +void _bootstrap_component( + ecs_world_t *world, + ecs_table_t *table, + ecs_entity_t entity, + const char *symbol, + ecs_size_t size, + ecs_size_t alignment) +{ + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_rule_pair_t pair = *filter; - pair.obj.reg = obj_subsets->id; - pair.reg_mask |= RULE_PAIR_OBJECT; + ecs_column_t *columns = table->storage.columns; + ecs_assert(columns != NULL, ECS_INTERNAL_ERROR, NULL); - insert_select_or_with(rule, c, term, subj, &pair, written); - } else { - ecs_assert(obj != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_record_t *record = ecs_eis_ensure(world, entity); + record->table = table; + + int32_t index = flecs_table_append(world, table, &table->storage, + entity, record, false); + record->row = ECS_ROW_TO_RECORD(index, 0); - /* If subject is literal, find supersets for subject */ - if (subj == NULL || subj->kind == EcsRuleVarKindEntity) { - obj = to_entity(rule, obj); + EcsComponent *component = ecs_vector_first(columns[0].data, EcsComponent); + component[index].size = size; + component[index].alignment = alignment; - ecs_rule_pair_t set_pair = *filter; - set_pair.reg_mask &= RULE_PAIR_PREDICATE; + const char *name = &symbol[3]; /* Strip 'Ecs' */ + ecs_size_t symbol_length = ecs_os_strlen(symbol); + ecs_size_t name_length = symbol_length - 3; - if (subj) { - set_pair.obj.reg = subj->id; - set_pair.reg_mask |= RULE_PAIR_OBJECT; - } else { - set_pair.obj.ent = term->subj.entity; - } + EcsIdentifier *name_col = ecs_vector_first(columns[1].data, EcsIdentifier); + name_col[index].value = ecs_os_strdup(name); + name_col[index].length = name_length; + name_col[index].hash = flecs_hash(name, name_length); - insert_inclusive_set(rule, EcsRuleSuperSet, obj, set_pair, - c, written, filter->inclusive); + EcsIdentifier *symbol_col = ecs_vector_first(columns[2].data, EcsIdentifier); + symbol_col[index].value = ecs_os_strdup(symbol); + symbol_col[index].length = symbol_length; + symbol_col[index].hash = flecs_hash(symbol, symbol_length); +} - /* If subject is variable, first find matching pair for the - * evaluated entity(s) and return supersets */ - } else { - ecs_rule_var_t *av = create_anonymous_variable( - rule, EcsRuleVarKindEntity); +/** Create type for component */ +ecs_type_t flecs_bootstrap_type( + ecs_world_t *world, + ecs_entity_t entity) +{ + ecs_table_t *table = flecs_table_find_or_create(world, &(ecs_ids_t){ + .array = (ecs_entity_t[]){entity}, + .count = 1 + }); - subj = &rule->variables[subj_id]; - obj = &rule->variables[obj_id]; - obj = to_entity(rule, obj); + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(table->type != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_rule_pair_t set_pair = *filter; - set_pair.obj.reg = av->id; - set_pair.reg_mask |= RULE_PAIR_OBJECT; + return table->type; +} - /* Insert with to find initial object for relation */ - insert_select_or_with( - rule, c, term, subj, &set_pair, written); +/** Initialize component table. This table is manually constructed to bootstrap + * flecs. After this function has been called, the builtin components can be + * created. + * The reason this table is constructed manually is because it requires the size + * and alignment of the EcsComponent and EcsIdentifier components, which haven't + * been created yet */ +static +ecs_table_t* bootstrap_component_table( + ecs_world_t *world) +{ + ecs_entity_t entities[] = { + ecs_id(EcsComponent), + ecs_pair(ecs_id(EcsIdentifier), EcsName), + ecs_pair(ecs_id(EcsIdentifier), EcsSymbol), + ecs_pair(EcsChildOf, EcsFlecsCore) + }; + + ecs_ids_t array = { + .array = entities, + .count = 4 + }; - push_frame(rule); + ecs_table_t *result = flecs_table_find_or_create(world, &array); + ecs_data_t *data = &result->storage; - /* Find supersets for returned initial object. Make sure - * this is always inclusive since it needs to return the - * object from the pair that the entity has itself. */ - insert_inclusive_set(rule, EcsRuleSuperSet, obj, set_pair, - c, written, true); - } - } + /* Preallocate enough memory for initial components */ + data->entities = ecs_vector_new(ecs_entity_t, EcsFirstUserComponentId); + data->record_ptrs = ecs_vector_new(ecs_record_t*, EcsFirstUserComponentId); - /* subj is not known */ - } else { - ecs_assert(subj != NULL, ECS_INTERNAL_ERROR, NULL); + data->columns[0].data = ecs_vector_new(EcsComponent, EcsFirstUserComponentId); + data->columns[1].data = ecs_vector_new(EcsIdentifier, EcsFirstUserComponentId); + data->columns[2].data = ecs_vector_new(EcsIdentifier, EcsFirstUserComponentId); + + return result; +} - if (is_known(obj, written)) { - ecs_rule_pair_t set_pair = *filter; - set_pair.reg_mask &= RULE_PAIR_PREDICATE; /* clear object mask */ +static +void bootstrap_entity( + ecs_world_t *world, + ecs_entity_t id, + const char *name, + ecs_entity_t parent) +{ + char symbol[256]; + ecs_os_strcpy(symbol, "flecs.core."); + ecs_os_strcat(symbol, name); - if (obj) { - set_pair.obj.reg = obj->id; - set_pair.reg_mask |= RULE_PAIR_OBJECT; - } else { - set_pair.obj.ent = term->obj.entity; - } + ecs_set_name(world, id, name); + ecs_set_symbol(world, id, symbol); - insert_inclusive_set(rule, EcsRuleSubSet, subj, set_pair, c, - written, filter->inclusive); - } else if (subj == obj) { - insert_select_or_with(rule, c, term, subj, filter, written); - } else { - ecs_assert(obj != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(ecs_get_name(world, id) != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_add_pair(world, id, EcsChildOf, parent); - ecs_rule_var_t *av = create_anonymous_variable( - rule, EcsRuleVarKindEntity); + if (!parent || parent == EcsFlecsCore) { + ecs_assert(ecs_lookup_fullpath(world, name) == id, + ECS_INTERNAL_ERROR, NULL); + } +} - subj = &rule->variables[subj_id]; - obj = &rule->variables[obj_id]; - obj = to_entity(rule, obj); +void flecs_bootstrap( + ecs_world_t *world) +{ + ecs_log_push(); - /* TODO: this instruction currently does not return inclusive - * results. For example, it will return IsA(XWing, Machine) and - * IsA(XWing, Thing), but not IsA(XWing, XWing). To enable - * inclusive behavior, we need to be able to find all subjects - * that have IsA relationships, without expanding to all - * IsA relationships. For this a new mode needs to be supported - * where an operation never does a redo. - * - * This select can then be used to find all subjects, and those - * same subjects can then be used to find all (inclusive) - * supersets for those subjects. */ + ecs_set_name_prefix(world, "Ecs"); - /* Insert instruction to find all subjects and objects */ - ecs_rule_op_t *op = insert_operation(rule, -1, written); - op->kind = EcsRuleSelect; - set_output_to_subj(rule, op, term, subj); + /* Create table for initial components */ + ecs_table_t *table = bootstrap_component_table(world); + assert(table != NULL); - /* Set object to anonymous variable */ - op->filter.pred = filter->pred; - op->filter.obj.reg = av->id; - op->filter.reg_mask = filter->reg_mask | RULE_PAIR_OBJECT; + bootstrap_component(world, table, EcsIdentifier); + bootstrap_component(world, table, EcsComponent); + bootstrap_component(world, table, EcsComponentLifecycle); - written[subj->id] = true; - written[av->id] = true; + bootstrap_component(world, table, EcsType); + bootstrap_component(world, table, EcsQuery); + bootstrap_component(world, table, EcsTrigger); + bootstrap_component(world, table, EcsObserver); + bootstrap_component(world, table, EcsIterable); - /* Create new frame for operations that create inclusive set */ - push_frame(rule); + ecs_set_component_actions(world, EcsComponent, { .ctor = ecs_default_ctor }); - /* Insert superset instruction to find all supersets */ - insert_inclusive_set(rule, EcsRuleSuperSet, obj, op->filter, c, - written, true); - } - } - } -} + ecs_set_component_actions(world, EcsIdentifier, { + .ctor = ecs_ctor(EcsIdentifier), + .dtor = ecs_dtor(EcsIdentifier), + .copy = ecs_copy(EcsIdentifier), + .move = ecs_move(EcsIdentifier), + .on_set = ecs_on_set(EcsIdentifier) + }); -static -void insert_term_1( - ecs_rule_t *rule, - ecs_term_t *term, - ecs_rule_pair_t *filter, - int32_t c, - bool *written) -{ - ecs_rule_var_t *subj = term_subj(rule, term); - subj = get_most_specific_var(rule, subj, written); - insert_select_or_with(rule, c, term, subj, filter, written); -} + ecs_set_component_actions(world, EcsTrigger, { + .ctor = ecs_ctor(EcsTrigger), + .dtor = ecs_dtor(EcsTrigger), + .copy = ecs_copy(EcsTrigger), + .move = ecs_move(EcsTrigger) + }); -static -void insert_term( - ecs_rule_t *rule, - ecs_term_t *term, - int32_t c, - bool *written) -{ - bool obj_set = obj_is_set(term); + ecs_set_component_actions(world, EcsObserver, { + .ctor = ecs_ctor(EcsObserver), + .dtor = ecs_dtor(EcsObserver), + .copy = ecs_copy(EcsObserver), + .move = ecs_move(EcsObserver) + }); - ensure_most_specific_var(rule, term_pred(rule, term), written); - if (obj_set) { - ensure_most_specific_var(rule, term_obj(rule, term), written); - } + world->stats.last_component_id = EcsFirstUserComponentId; + world->stats.last_id = EcsFirstUserEntityId; + world->stats.min_id = 0; + world->stats.max_id = 0; - /* If term has Not operator, prepend Not which turns a fail into a pass */ - int32_t prev = rule->operation_count; - ecs_rule_op_t *not_pre; - if (term->oper == EcsNot) { - not_pre = insert_operation(rule, -1, written); - not_pre->kind = EcsRuleNot; - not_pre->has_in = false; - not_pre->has_out = false; - } + /* Populate core module */ + ecs_set_scope(world, EcsFlecsCore); - ecs_rule_pair_t filter = term_to_pair(rule, term); - prepare_predicate(rule, &filter, written); + flecs_bootstrap_tag(world, EcsName); + flecs_bootstrap_tag(world, EcsSymbol); - if (subj_is_set(term) && !obj_set) { - insert_term_1(rule, term, &filter, c, written); - } else if (obj_set) { - insert_term_2(rule, term, &filter, c, written); - } + flecs_bootstrap_tag(world, EcsModule); + flecs_bootstrap_tag(world, EcsPrefab); + flecs_bootstrap_tag(world, EcsDisabled); - /* If term has Not operator, append Not which turns a pass into a fail */ - if (term->oper == EcsNot) { - ecs_rule_op_t *not_post = insert_operation(rule, -1, written); - not_post->kind = EcsRuleNot; - not_post->has_in = false; - not_post->has_out = false; + /* Initialize builtin modules */ + ecs_set_name(world, EcsFlecs, "flecs"); + ecs_add_id(world, EcsFlecs, EcsModule); + ecs_set_name(world, EcsFlecsCore, "core"); + ecs_add_id(world, EcsFlecsCore, EcsModule); + ecs_add_pair(world, EcsFlecsCore, EcsChildOf, EcsFlecs); - not_post->on_pass = prev - 1; - not_post->on_fail = prev - 1; - not_pre = &rule->operations[prev]; - not_pre->on_fail = rule->operation_count; - } + /* Initialize builtin entities */ + bootstrap_entity(world, EcsWorld, "World", EcsFlecsCore); + bootstrap_entity(world, EcsThis, "This", EcsFlecsCore); + bootstrap_entity(world, EcsWildcard, "*", EcsFlecsCore); - if (term->oper == EcsOptional) { - /* Insert jump instruction that ensures that the optional term is only - * executed once */ - ecs_rule_op_t *jump = insert_operation(rule, -1, written); - jump->kind = EcsRuleNot; - jump->has_in = false; - jump->has_out = false; - jump->on_pass = rule->operation_count; - jump->on_fail = prev - 1; + /* Component/relationship properties */ + flecs_bootstrap_tag(world, EcsTransitive); + flecs_bootstrap_tag(world, EcsTransitiveSelf); + flecs_bootstrap_tag(world, EcsFinal); + flecs_bootstrap_tag(world, EcsTag); + flecs_bootstrap_tag(world, EcsExclusive); + flecs_bootstrap_tag(world, EcsAcyclic); - /* Find exit instruction for optional term, and make the fail label - * point to the Not operation, so that even when the operation fails, - * it won't discard the result */ - int i, min_fail = -1, exit_op = -1; - for (i = prev; i < rule->operation_count; i ++) { - ecs_rule_op_t *op = &rule->operations[i]; - if (min_fail == -1 || (op->on_fail >= 0 && op->on_fail < min_fail)){ - min_fail = op->on_fail; - exit_op = i; - } - } + flecs_bootstrap_tag(world, EcsOnDelete); + flecs_bootstrap_tag(world, EcsOnDeleteObject); + flecs_bootstrap_tag(world, EcsRemove); + flecs_bootstrap_tag(world, EcsDelete); + flecs_bootstrap_tag(world, EcsThrow); - ecs_assert(exit_op != -1, ECS_INTERNAL_ERROR, NULL); - ecs_rule_op_t *op = &rule->operations[exit_op]; - op->on_fail = rule->operation_count - 1; - } + flecs_bootstrap_tag(world, EcsDefaultChildComponent); - push_frame(rule); -} + /* Builtin relations */ + flecs_bootstrap_tag(world, EcsIsA); + flecs_bootstrap_tag(world, EcsChildOf); -/* Create program from operations that will execute the query */ -static -void compile_program( - ecs_rule_t *rule) -{ - /* Trace which variables have been written while inserting instructions. - * This determines which instruction needs to be inserted */ - bool written[ECS_RULE_MAX_VARIABLE_COUNT] = { false }; + /* Builtin events */ + bootstrap_entity(world, EcsOnAdd, "OnAdd", EcsFlecsCore); + bootstrap_entity(world, EcsOnRemove, "OnRemove", EcsFlecsCore); + bootstrap_entity(world, EcsOnSet, "OnSet", EcsFlecsCore); + bootstrap_entity(world, EcsUnSet, "UnSet", EcsFlecsCore); + bootstrap_entity(world, EcsOnTableEmpty, "OnTableEmpty", EcsFlecsCore); + bootstrap_entity(world, EcsOnTableFilled, "OnTableFilled", EcsFlecsCore); + // bootstrap_entity(world, EcsOnCreateTable, "OnCreateTable", EcsFlecsCore); + // bootstrap_entity(world, EcsOnDeleteTable, "OnDeleteTable", EcsFlecsCore); + // bootstrap_entity(world, EcsOnCreateTrigger, "OnCreateTrigger", EcsFlecsCore); + // bootstrap_entity(world, EcsOnDeleteTrigger, "OnDeleteTrigger", EcsFlecsCore); + // bootstrap_entity(world, EcsOnDeleteObservable, "OnDeleteObservable", EcsFlecsCore); + // bootstrap_entity(world, EcsOnComponentLifecycle, "OnComponentLifecycle", EcsFlecsCore); - ecs_term_t *terms = rule->filter.terms; - int32_t v, c, term_count = rule->filter.term_count; - ecs_rule_op_t *op; + /* Transitive relations */ + ecs_add_id(world, EcsIsA, EcsTransitive); + ecs_add_id(world, EcsIsA, EcsTransitiveSelf); - /* Insert input, which is always the first instruction */ - insert_input(rule); + /* Tag relations (relations that should never have data) */ + ecs_add_id(world, EcsIsA, EcsTag); + ecs_add_id(world, EcsChildOf, EcsTag); + ecs_add_id(world, EcsDefaultChildComponent, EcsTag); - /* First insert all instructions that do not have a variable subject. Such - * instructions iterate the type of an entity literal and are usually good - * candidates for quickly narrowing down the set of potential results. */ - for (c = 0; c < term_count; c ++) { - ecs_term_t *term = &terms[c]; - if (skip_term(term)) { - continue; - } + /* Final components/relations */ + ecs_add_id(world, ecs_id(EcsComponent), EcsFinal); + ecs_add_id(world, ecs_id(EcsComponentLifecycle), EcsFinal); + ecs_add_id(world, ecs_id(EcsIdentifier), EcsFinal); + ecs_add_id(world, EcsModule, EcsFinal); + ecs_add_id(world, EcsDisabled, EcsFinal); + ecs_add_id(world, EcsPrefab, EcsFinal); + ecs_add_id(world, EcsTransitive, EcsFinal); + ecs_add_id(world, EcsFinal, EcsFinal); + ecs_add_id(world, EcsTag, EcsFinal); + ecs_add_id(world, EcsExclusive, EcsFinal); + ecs_add_id(world, EcsAcyclic, EcsFinal); + ecs_add_id(world, EcsIsA, EcsFinal); + ecs_add_id(world, EcsChildOf, EcsFinal); + ecs_add_id(world, EcsOnDelete, EcsFinal); + ecs_add_id(world, EcsOnDeleteObject, EcsFinal); + ecs_add_id(world, EcsDefaultChildComponent, EcsFinal); - if (term->oper == EcsOptional) { - continue; - } + /* Acyclic relations */ + ecs_add_id(world, EcsIsA, EcsAcyclic); + ecs_add_id(world, EcsChildOf, EcsAcyclic); - ecs_rule_var_t* subj = term_subj(rule, term); - if (subj) { - continue; - } + /* Exclusive properties */ + ecs_add_id(world, EcsChildOf, EcsExclusive); + ecs_add_id(world, EcsOnDelete, EcsExclusive); + ecs_add_id(world, EcsOnDeleteObject, EcsExclusive); + ecs_add_id(world, EcsDefaultChildComponent, EcsExclusive); - if (term->subj.entity == EcsWildcard) { - continue; - } + /* Make EcsOnAdd, EcsOnSet events iterable to enable .yield_existing */ + ecs_set(world, EcsOnAdd, EcsIterable, { .init = on_event_iterable_init }); + ecs_set(world, EcsOnSet, EcsIterable, { .init = on_event_iterable_init }); - insert_term(rule, term, c, written); - } + /* Define triggers for when relationship cleanup rules are assigned */ + ecs_trigger_init(world, &(ecs_trigger_desc_t){ + .term = {.id = ecs_pair(EcsOnDelete, EcsWildcard)}, + .callback = register_on_delete, + .events = {EcsOnAdd} + }); - /* Insert variables based on dependency order */ - for (v = 0; v < rule->subject_variable_count; v ++) { - ecs_rule_var_t *var = &rule->variables[v]; + ecs_trigger_init(world, &(ecs_trigger_desc_t){ + .term = {.id = ecs_pair(EcsOnDeleteObject, EcsWildcard)}, + .callback = register_on_delete_object, + .events = {EcsOnAdd} + }); - ecs_assert(var->kind == EcsRuleVarKindTable, ECS_INTERNAL_ERROR, NULL); + /* Define trigger to make sure that adding a module to a child entity also + * adds it to the parent. */ + ecs_trigger_init(world, &(ecs_trigger_desc_t){ + .term = {.id = EcsModule}, + .callback = ensure_module_tag, + .events = {EcsOnAdd} + }); - for (c = 0; c < term_count; c ++) { - ecs_term_t *term = &terms[c]; - if (skip_term(term)) { - continue; - } + /* Define trigger for when component lifecycle is set for component */ + ecs_trigger_init(world, &(ecs_trigger_desc_t){ + .term = {.id = ecs_id(EcsComponentLifecycle)}, + .callback = on_set_component_lifecycle, + .events = {EcsOnSet} + }); - if (term->oper == EcsOptional) { - continue; - } + /* Removal of ChildOf objects (parents) deletes the subject (child) */ + ecs_add_pair(world, EcsChildOf, EcsOnDeleteObject, EcsDelete); - /* Only process columns for which variable is subject */ - ecs_rule_var_t* subj = term_subj(rule, term); - if (subj != var) { - continue; - } + /* Run bootstrap functions for other parts of the code */ + flecs_bootstrap_hierarchy(world); - insert_term(rule, term, c, written); + ecs_set_scope(world, 0); - var = &rule->variables[v]; - } - } + ecs_log_pop(); +} - /* Insert terms with wildcard subject */ - for (c = 0; c < term_count; c ++) { - ecs_term_t *term = &terms[c]; +/* Move table from empty to non-empty list, or vice versa */ +static +int32_t move_table( + ecs_table_cache_t *cache, + const ecs_table_t *table, + int32_t index, + ecs_vector_t **dst_array, + ecs_vector_t *src_array, + bool empty) +{ + (void)table; - if (term->subj.entity != EcsWildcard) { - continue; - } + int32_t new_index = 0, old_index = 0; + ecs_size_t size = cache->size; + int32_t last_src_index = ecs_vector_count(src_array) - 1; + ecs_assert(last_src_index >= 0, ECS_INTERNAL_ERROR, NULL); - insert_term(rule, term, c, written); - } + ecs_table_cache_hdr_t *elem = ecs_vector_last_t(src_array, size, 8); + + /* The last table of the source array will be moved to the location of the + * table to move, do some bookkeeping to keep things consistent. */ + if (last_src_index) { + int32_t *old_index_ptr = ecs_map_get(cache->index, + int32_t, elem->table->id); + ecs_assert(old_index_ptr != NULL, ECS_INTERNAL_ERROR, NULL); - /* Insert terms with Not operators */ - for (c = 0; c < term_count; c ++) { - ecs_term_t *term = &terms[c]; - if (term->oper != EcsNot) { - continue; + old_index = old_index_ptr[0]; + if (!empty) { + if (old_index >= 0) { + /* old_index should be negative if not empty, since + * we're moving from the empty list to the non-empty list. + * However, if the last table in the source array is also + * the table being moved, this can happen. */ + ecs_assert(table == elem->table, ECS_INTERNAL_ERROR, NULL); + } else { + /* If not empty, src = the empty list, and index should + * be negative. */ + old_index = old_index * -1 - 1; /* Normalize */ + } } - insert_term(rule, term, c, written); - } - - /* Insert terms with Optional operators last, as optional terms cannot - * eliminate results, and would just add overhead to evaluation of - * non-matching entities. */ - for (c = 0; c < term_count; c ++) { - ecs_term_t *term = &terms[c]; - if (term->oper != EcsOptional) { - continue; + if (old_index == last_src_index) { + old_index_ptr[0] = index; } - - insert_term(rule, term, c, written); + } else { + /* If last_src_index is 0, the table to move was the only table in the + * src array, so no other administration needs to be updated. */ } - /* Verify all subject variables have been written. Subject variables are of - * the table type, and a select/subset should have been inserted for each */ - for (v = 0; v < rule->subject_variable_count; v ++) { - if (!written[v]) { - /* If the table variable hasn't been written, this can only happen - * if an instruction wrote the variable before a select/subset could - * have been inserted for it. Make sure that this is the case by - * testing if an entity variable exists and whether it has been - * written. */ - ecs_rule_var_t *var = find_variable( - rule, EcsRuleVarKindEntity, rule->variables[v].name); - ecs_assert(written[var->id], ECS_INTERNAL_ERROR, var->name); - (void)var; - } + if (!empty) { + old_index = index * -1 - 1; + } else { + old_index = index; } - /* Make sure that all entity variables are written. With the exception of - * the this variable, which can be returned as a table, other variables need - * to be available as entities. This ensures that all permutations for all - * variables are correctly returned by the iterator. When an entity variable - * hasn't been written yet at this point, it is because it only constrained - * through a common predicate or object. */ - for (; v < rule->variable_count; v ++) { - if (!written[v]) { - ecs_rule_var_t *var = &rule->variables[v]; - ecs_assert(var->kind == EcsRuleVarKindEntity, - ECS_INTERNAL_ERROR, NULL); + /* Actually move the table. Only move from src to dst if we have a + * dst_array, otherwise just remove it from src. */ + if (dst_array) { + new_index = ecs_vector_count(*dst_array); + ecs_vector_move_index_t(dst_array, src_array, size, 8, old_index); - ecs_rule_var_t *table_var = find_variable( - rule, EcsRuleVarKindTable, var->name); - - /* A table variable must exist if the variable hasn't been resolved - * yet. If there doesn't exist one, this could indicate an - * unconstrained variable which should have been caught earlier */ - ecs_assert(table_var != NULL, ECS_INTERNAL_ERROR, var->name); + /* Make sure table is where we expect it */ + elem = ecs_vector_last_t(*dst_array, size, 8); + ecs_assert(elem->table == table, ECS_INTERNAL_ERROR, NULL); + ecs_assert(ecs_vector_count(*dst_array) == (new_index + 1), + ECS_INTERNAL_ERROR, NULL); + elem->empty = empty; + } else { + ecs_vector_remove_t(src_array, size, 8, old_index); + } - /* Insert each operation that takes the table variable as input, and - * yields each entity in the table */ - op = insert_operation(rule, -1, written); - op->kind = EcsRuleEach; - op->r_in = table_var->id; - op->r_out = var->id; - op->frame = rule->frame_count; - op->has_in = true; - op->has_out = true; - written[var->id] = true; - - push_frame(rule); - } - } + /* Ensure that src array has now one element less */ + ecs_assert(ecs_vector_count(src_array) == last_src_index, + ECS_INTERNAL_ERROR, NULL); - /* Insert yield, which is always the last operation */ - insert_yield(rule); + if (empty) { + /* Table is now empty, index is negative */ + new_index = new_index * -1 - 1; + } + + return new_index; } static -void create_variable_name_array( - ecs_rule_t *rule) +void ensure_index( + ecs_table_cache_t *cache) { - if (rule->variable_count) { - rule->variable_names = ecs_os_malloc_n(char*, rule->variable_count); - int i; - for (i = 0; i < rule->variable_count; i ++) { - ecs_rule_var_t *var = &rule->variables[i]; - - if (var->kind != EcsRuleVarKindEntity) { - /* Table variables are hidden for applications. */ - rule->variable_names[var->id] = NULL; - } else { - rule->variable_names[var->id] = var->name; - } - } + if (!cache->index) { + cache->index = ecs_map_new(int32_t, 0); } } -/* Implementation for iterable mixin */ +void _ecs_table_cache_init( + ecs_table_cache_t *cache, + ecs_size_t size, + ecs_poly_t *parent, + void(*free_payload)(ecs_poly_t*, void*)) +{ + ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(size >= ECS_SIZEOF(ecs_table_cache_hdr_t), + ECS_INTERNAL_ERROR, NULL); + cache->index = NULL; + cache->empty_tables = NULL; + cache->tables = NULL; + cache->size = size; + cache->parent = parent; + cache->free_payload = free_payload; +} + static -void rule_iter_init( - const ecs_world_t *world, - const ecs_poly_t *poly, - ecs_iter_t *iter, - ecs_term_t *filter) +void free_payload( + ecs_table_cache_t *cache, + ecs_vector_t *tables) { - ecs_poly_assert(poly, ecs_rule_t); + void(*free_payload_func)(ecs_poly_t*, void*) = cache->free_payload; + if (free_payload_func) { + ecs_poly_t *parent = cache->parent; + ecs_size_t size = cache->size; + int32_t i, count = ecs_vector_count(tables); - if (filter) { - iter[1] = ecs_rule_iter(world, (ecs_rule_t*)poly); - iter[0] = ecs_term_chain_iter(&iter[1], filter); - } else { - iter[0] = ecs_rule_iter(world, (ecs_rule_t*)poly); + for (i = 0; i < count; i ++) { + void *ptr = ecs_vector_get_t(tables, size, 8, i); + free_payload_func(parent, ptr); + } } + + ecs_vector_free(tables); } -ecs_rule_t* ecs_rule_init( - ecs_world_t *world, - const ecs_filter_desc_t *desc) +void ecs_table_cache_fini( + ecs_table_cache_t *cache) { - ecs_rule_t *result = ecs_poly_new(ecs_rule_t); + ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_map_free(cache->index); + free_payload(cache, cache->tables); + free_payload(cache, cache->empty_tables); +} - /* Parse the signature expression. This initializes the columns array which - * contains the information about which components/pairs are requested. */ - if (ecs_filter_init(world, &result->filter, desc)) { - goto error; +bool ecs_table_cache_is_initialized( + ecs_table_cache_t *cache) +{ + return cache->size != 0; +} + +void* _ecs_table_cache_insert( + ecs_table_cache_t *cache, + ecs_size_t size, + const ecs_table_t *table) +{ + ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(size == cache->size, ECS_INTERNAL_ERROR, NULL); + + ecs_assert(!table || (_ecs_table_cache_get(cache, size, table) == NULL), + ECS_INTERNAL_ERROR, NULL); + + int32_t index; + ecs_table_cache_hdr_t *result; + bool empty; + + if (!table) { + empty = false; + } else { + empty = ecs_table_count(table) == 0; } - result->world = world; + if (empty) { + result = ecs_vector_add_t(&cache->empty_tables, size, 8); + index = -ecs_vector_count(cache->empty_tables); + } else { + index = ecs_vector_count(cache->tables); + result = ecs_vector_add_t(&cache->tables, size, 8); + } - /* Rule has no terms */ - if (!result->filter.term_count) { - rule_error(result, "rule has no terms"); - goto error; + if (table) { + ensure_index(cache); + ecs_map_set(cache->index, table->id, &index); } + + ecs_os_memset(result, 0, size); + result->table = (ecs_table_t*)table; + result->empty = empty; - ecs_term_t *terms = result->filter.terms; - int32_t i, term_count = result->filter.term_count; + return result; +} - /* Make sure rule doesn't just have Not terms */ - for (i = 0; i < term_count; i++) { - ecs_term_t *term = &terms[i]; - if (term->oper != EcsNot) { - break; - } +void _ecs_table_cache_remove( + ecs_table_cache_t *cache, + ecs_size_t size, + const ecs_table_t *table) +{ + ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(size == cache->size, ECS_INTERNAL_ERROR, NULL); + (void)size; + + int32_t *index = ecs_map_get(cache->index, int32_t, table->id); + if (!index) { + return; } - if (i == term_count) { - rule_error(result, "rule cannot only have terms with Not operator"); - goto error; + + if (cache->free_payload) { + ecs_table_cache_hdr_t *elem = _ecs_table_cache_get( + cache, cache->size, table); + ecs_assert(elem != NULL, ECS_INTERNAL_ERROR, NULL); + cache->free_payload(cache->parent, elem); } - /* Find all variables & resolve dependencies */ - if (scan_variables(result) != 0) { - goto error; + if (index[0] < 0) { + move_table(cache, table, index[0], NULL, cache->empty_tables, false); + } else { + move_table(cache, table, index[0], NULL, cache->tables, true); } - /* Generate the opcode array */ - compile_program(result); + ecs_map_remove(cache->index, table->id); - /* Create array with variable names so this can be easily accessed by - * iterators without requiring access to the ecs_rule_t */ - create_variable_name_array(result); + if (!ecs_map_count(cache->index)) { + ecs_assert(ecs_vector_count(cache->tables) == 0, + ECS_INTERNAL_ERROR, NULL); + ecs_assert(ecs_vector_count(cache->empty_tables) == 0, + ECS_INTERNAL_ERROR, NULL); + ecs_table_cache_fini(cache); - /* Create lookup array for subject variables */ - result->subject_variables = ecs_os_malloc_n(int32_t, term_count); + cache->index = NULL; + cache->tables = NULL; + cache->empty_tables = NULL; + } +} - for (i = 0; i < term_count; i ++) { - ecs_term_t *term = &terms[i]; - if (term_id_is_variable(&term->subj)) { - const char *subj_name = term_id_var_name(&term->subj); - ecs_rule_var_t *subj = find_variable( - result, EcsRuleVarKindEntity, subj_name); - if (subj) { - result->subject_variables[i] = subj->id; - continue; - } - } +void* _ecs_table_cache_get( + const ecs_table_cache_t *cache, + ecs_size_t size, + const ecs_table_t *table) +{ + ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(size == cache->size, ECS_INTERNAL_ERROR, NULL); - result->subject_variables[i] = -1; + int32_t *index = ecs_map_get(cache->index, int32_t, table->id); + if (!index) { + return NULL; } - result->iterable.init = rule_iter_init; + ecs_table_cache_hdr_t *result; + if (index[0] >= 0) { + result = ecs_vector_get_t(cache->tables, size, 8, index[0]); + } else { + result = ecs_vector_get_t( + cache->empty_tables, size, 8, index[0] * -1 - 1); + } + + ecs_assert(!result || result->table == table, ECS_INTERNAL_ERROR, NULL); return result; -error: - ecs_rule_fini(result); - return NULL; } -void ecs_rule_fini( - ecs_rule_t *rule) +void ecs_table_cache_set_empty( + ecs_table_cache_t *cache, + const ecs_table_t *table, + bool empty) { - int32_t i; - for (i = 0; i < rule->variable_count; i ++) { - ecs_os_free(rule->variables[i].name); - } + ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_os_free(rule->variables); - ecs_os_free(rule->operations); - ecs_os_free(rule->variable_names); - ecs_os_free(rule->subject_variables); + int32_t *index = ecs_map_get(cache->index, int32_t, table->id); + if (!index) { + return; + } - ecs_filter_fini(&rule->filter); + /* If table is already in the correct array nothing needs to be done */ + if (empty && index[0] < 0) { + return; + } else if (!empty && index[0] >= 0) { + return; + } - ecs_os_free(rule); + if (index[0] < 0) { + index[0] = move_table( + cache, table, index[0], &cache->tables, cache->empty_tables, empty); + } else { + index[0] = move_table( + cache, table, index[0], &cache->empty_tables, cache->tables, empty); + } } -const ecs_filter_t* ecs_rule_filter( - const ecs_rule_t *rule) +void* _ecs_table_cache_tables( + const ecs_table_cache_t *cache, + ecs_size_t size) { - return &rule->filter; + if (!cache) { + return NULL; + } + return ecs_vector_first_t(cache->tables, size, 8); } -/* Quick convenience function to get a variable from an id */ -static -ecs_rule_var_t* get_variable( - const ecs_rule_t *rule, - int32_t var_id) +void* _ecs_table_cache_empty_tables( + const ecs_table_cache_t *cache, + ecs_size_t size) { - if (var_id == UINT8_MAX) { + if (!cache) { return NULL; } + return ecs_vector_first_t(cache->empty_tables, size, 8); +} - return &rule->variables[var_id]; +int32_t ecs_table_cache_count( + const ecs_table_cache_t *cache) +{ + if (!cache) { + return 0; + } + return ecs_vector_count(cache->tables); } -/* Convert the program to a string. This can be useful to analyze how a rule is - * being evaluated. */ -char* ecs_rule_str( - ecs_rule_t *rule) +int32_t ecs_table_cache_empty_count( + const ecs_table_cache_t *cache) { - ecs_check(rule != NULL, ECS_INVALID_PARAMETER, NULL); - - ecs_world_t *world = rule->world; - ecs_strbuf_t buf = ECS_STRBUF_INIT; - char filter_expr[256]; + if (!cache) { + return 0; + } + return ecs_vector_count(cache->empty_tables); +} - int32_t i, count = rule->operation_count; - for (i = 1; i < count; i ++) { - ecs_rule_op_t *op = &rule->operations[i]; - ecs_rule_pair_t pair = op->filter; - ecs_entity_t pred = pair.pred.ent; - ecs_entity_t obj = pair.obj.ent; - const char *pred_name = NULL, *obj_name = NULL; - char *pred_name_alloc = NULL, *obj_name_alloc = NULL; +bool ecs_table_cache_is_empty( + const ecs_table_cache_t *cache) +{ + if (!cache) { + return true; + } + return ecs_map_count(cache->index) == 0; +} - if (pair.reg_mask & RULE_PAIR_PREDICATE) { - ecs_assert(rule->variables != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_rule_var_t *type_var = &rule->variables[pair.pred.reg]; - pred_name = type_var->name; - } else if (pred) { - pred_name_alloc = ecs_get_fullpath(world, ecs_get_alive(world, pred)); - pred_name = pred_name_alloc; - } +void _ecs_table_cache_fini_delete_all( + ecs_world_t *world, + ecs_table_cache_t *cache, + ecs_size_t size) +{ + if (!cache || !cache->index) { + return; + } - if (pair.reg_mask & RULE_PAIR_OBJECT) { - ecs_assert(rule->variables != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_rule_var_t *obj_var = &rule->variables[pair.obj.reg]; - obj_name = obj_var->name; - } else if (obj) { - obj_name_alloc = ecs_get_fullpath(world, ecs_get_alive(world, obj)); - obj_name = obj_name_alloc; - } else if (pair.obj_0) { - obj_name = "0"; - } + /* Temporarily set index to NULL, so that when the table tries to remove + * itself from the cache it won't be able to. This keeps the arrays we're + * iterating over consistent */ + ecs_map_t *index = cache->index; + cache->index = NULL; - ecs_strbuf_append(&buf, "%2d: [S:%2d, P:%2d, F:%2d] ", i, - op->frame, op->on_pass, op->on_fail); + int32_t i, count = ecs_vector_count(cache->tables); + for (i = 0; i < count; i ++) { + ecs_table_cache_hdr_t *ptr = ecs_vector_get_t( + cache->tables, size, 8, i); + flecs_delete_table(world, ptr->table); + } - bool has_filter = false; + count = ecs_vector_count(cache->empty_tables); + for (i = 0; i < count; i ++) { + ecs_table_cache_hdr_t *ptr = ecs_vector_get_t( + cache->empty_tables, size, 8, i); + flecs_delete_table(world, ptr->table); + } - switch(op->kind) { - case EcsRuleSelect: - ecs_strbuf_append(&buf, "select "); - has_filter = true; - break; - case EcsRuleWith: - ecs_strbuf_append(&buf, "with "); - has_filter = true; - break; - case EcsRuleStore: - ecs_strbuf_append(&buf, "store "); - break; - case EcsRuleSuperSet: - ecs_strbuf_append(&buf, "superset "); - has_filter = true; - break; - case EcsRuleSubSet: - ecs_strbuf_append(&buf, "subset "); - has_filter = true; - break; - case EcsRuleEach: - ecs_strbuf_append(&buf, "each "); - break; - case EcsRuleSetJmp: - ecs_strbuf_append(&buf, "setjmp "); - break; - case EcsRuleJump: - ecs_strbuf_append(&buf, "jump "); - break; - case EcsRuleNot: - ecs_strbuf_append(&buf, "not "); - break; - case EcsRuleYield: - ecs_strbuf_append(&buf, "yield "); - break; - default: - continue; - } + cache->index = index; - if (op->has_out) { - ecs_rule_var_t *r_out = get_variable(rule, op->r_out); - if (r_out) { - ecs_strbuf_append(&buf, "O:%s%s ", - r_out->kind == EcsRuleVarKindTable ? "t" : "", - r_out->name); - } else if (op->subject) { - char *subj_path = ecs_get_fullpath(world, op->subject); - ecs_strbuf_append(&buf, "O:%s ", subj_path); - ecs_os_free(subj_path); - } - } + ecs_table_cache_fini(cache); +} - if (op->has_in) { - ecs_rule_var_t *r_in = get_variable(rule, op->r_in); - if (r_in) { - ecs_strbuf_append(&buf, "I:%s%s ", - r_in->kind == EcsRuleVarKindTable ? "t" : "", - r_in->name); - } else if (op->subject) { - char *subj_path = ecs_get_fullpath(world, op->subject); - ecs_strbuf_append(&buf, "I:%s ", subj_path); - ecs_os_free(subj_path); +/* Count number of switch columns */ +static +int32_t switch_column_count( + ecs_table_t *table) +{ + int32_t i, sw_count = 0, count = ecs_vector_count(table->type); + ecs_id_t *ids = ecs_vector_first(table->type, ecs_id_t); + + for (i = 0; i < count; i ++) { + ecs_id_t id = ids[i]; + if (ECS_HAS_ROLE(id, SWITCH)) { + if (!sw_count) { + table->sw_column_offset = i; } + sw_count ++; } + } - if (has_filter) { - if (!obj && !pair.obj_0) { - ecs_os_sprintf(filter_expr, "(%s)", pred_name); - } else { - ecs_os_sprintf(filter_expr, "(%s, %s)", pred_name, obj_name); + return sw_count; +} + +/* Count number of bitset columns */ +static +int32_t bitset_column_count( + ecs_table_t *table) +{ + int32_t count = 0; + ecs_vector_each(table->type, ecs_entity_t, c_ptr, { + ecs_entity_t component = *c_ptr; + + if (ECS_HAS_ROLE(component, DISABLED)) { + if (!count) { + table->bs_column_offset = c_ptr_i; } - ecs_strbuf_append(&buf, "F:%s", filter_expr); + count ++; } + }); - ecs_strbuf_appendstr(&buf, "\n"); + return count; +} - ecs_os_free(pred_name_alloc); - ecs_os_free(obj_name_alloc); +static +void init_storage_map( + ecs_table_t *table) +{ + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + if (!table->storage_table) { + return; } - return ecs_strbuf_get(&buf); -error: - return NULL; -} + ecs_id_t *ids = ecs_vector_first(table->type, ecs_id_t); + int32_t t, ids_count = ecs_vector_count(table->type); + ecs_id_t *storage_ids = ecs_vector_first(table->storage_type, ecs_id_t); + int32_t s, storage_ids_count = ecs_vector_count(table->storage_type); -/* Public function that returns number of variables. This enables an application - * to iterate the variables and obtain their values. */ -int32_t ecs_rule_variable_count( - const ecs_rule_t *rule) -{ - ecs_assert(rule != NULL, ECS_INTERNAL_ERROR, NULL); - return rule->variable_count; -} + if (!ids_count) { + table->storage_map = NULL; + return; + } -/* Public function to find a variable by name */ -int32_t ecs_rule_find_variable( - const ecs_rule_t *rule, - const char *name) -{ - ecs_rule_var_t *v = find_variable(rule, EcsRuleVarKindEntity, name); - if (v) { - return v->id; - } else { - return -1; + table->storage_map = ecs_os_malloc_n( + int32_t, ids_count + storage_ids_count); + + int32_t *t2s = table->storage_map; + int32_t *s2t = &table->storage_map[ids_count]; + + for (s = 0, t = 0; (t < ids_count) && (s < storage_ids_count); ) { + ecs_id_t id = ids[t]; + ecs_id_t storage_id = storage_ids[s]; + + if (id == storage_id) { + t2s[t] = s; + s2t[s] = t; + } else { + t2s[t] = -1; + } + + /* Ids can never get ahead of storage id, as ids are a superset of the + * storage ids */ + ecs_assert(id <= storage_id, ECS_INTERNAL_ERROR, NULL); + + t += (id <= storage_id); + s += (id == storage_id); } -} -/* Public function to get the name of a variable. */ -const char* ecs_rule_variable_name( - const ecs_rule_t *rule, - int32_t var_id) -{ - return rule->variables[var_id].name; -} + /* Storage ids is always a subset of ids, so all should be iterated */ + ecs_assert(s == storage_ids_count, ECS_INTERNAL_ERROR, NULL); -/* Public function to get the type of a variable. */ -bool ecs_rule_variable_is_entity( - const ecs_rule_t *rule, - int32_t var_id) -{ - return rule->variables[var_id].kind == EcsRuleVarKindEntity; + /* Initialize remainder of type -> storage_type map */ + for (; (t < ids_count); t ++) { + t2s[t] = -1; + } } -/* Public function to get the value of a variable. */ -ecs_entity_t ecs_rule_variable( - ecs_iter_t *iter, - int32_t var_id) +static +void init_storage_table( + ecs_world_t *world, + ecs_table_t *table) { - ecs_rule_iter_t *it = &iter->iter.rule; - const ecs_rule_t *rule = it->rule; + int32_t i, count = ecs_vector_count(table->type); + ecs_id_t *ids = ecs_vector_first(table->type, ecs_id_t); + ecs_ids_t storage_ids = { + .array = ecs_os_alloca_n(ecs_id_t, count) + }; - /* We can only return entity variables */ - if (rule->variables[var_id].kind == EcsRuleVarKindEntity) { - ecs_rule_reg_t *regs = get_register_frame(it, rule->frame_count - 1); - return entity_reg_get(rule, regs, var_id); - } else { - return 0; + for (i = 0; i < count; i ++) { + ecs_id_t id = ids[i]; + + if ((id == ecs_id(EcsComponent)) || + (ECS_PAIR_RELATION(id) == ecs_id(EcsIdentifier))) + { + storage_ids.array[storage_ids.count ++] = id; + continue; + } + + const EcsComponent *comp = flecs_component_from_id(world, id); + if (!comp || !comp->size) { + continue; + } + + storage_ids.array[storage_ids.count ++] = id; + } + + if (storage_ids.count && storage_ids.count != count) { + table->storage_table = flecs_table_find_or_create(world, &storage_ids); + table->storage_type = table->storage_table->type; + ecs_assert(table->storage_table != NULL, ECS_INTERNAL_ERROR, NULL); + } else if (storage_ids.count) { + table->storage_table = table; + table->storage_type = table->storage_table->type; + ecs_assert(table->storage_table != NULL, ECS_INTERNAL_ERROR, NULL); + } + + if (!table->storage_map) { + init_storage_map(table); } } -/* Create rule iterator */ -ecs_iter_t ecs_rule_iter( - const ecs_world_t *world, - const ecs_rule_t *rule) +void flecs_table_init_data( + ecs_world_t *world, + ecs_table_t *table) { - ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_assert(rule != NULL, ECS_INVALID_PARAMETER, NULL); + init_storage_table(world, table); - ecs_iter_t result = {0}; - int i; + int32_t sw_count = table->sw_column_count = switch_column_count(table); + int32_t bs_count = table->bs_column_count = bitset_column_count(table); - result.world = (ecs_world_t*)world; - result.real_world = (ecs_world_t*)ecs_get_world(rule->world); + ecs_data_t *storage = &table->storage; + ecs_type_t type = table->storage_type; - ecs_rule_iter_t *it = &result.iter.rule; - it->rule = rule; + int32_t i, count = ecs_vector_count(type); - if (rule->operation_count) { - if (rule->variable_count) { - it->registers = ecs_os_malloc_n(ecs_rule_reg_t, - rule->operation_count * rule->variable_count); + /* Root tables don't have columns */ + if (!count && !sw_count && !bs_count) { + storage->columns = NULL; + } - it->variables = ecs_os_malloc_n(ecs_entity_t, rule->variable_count); - } - - it->op_ctx = ecs_os_calloc_n(ecs_rule_op_ctx_t, rule->operation_count); + if (count) { + ecs_entity_t *ids = ecs_vector_first(type, ecs_entity_t); + storage->columns = ecs_os_calloc_n(ecs_column_t, count); - if (rule->filter.term_count) { - it->columns = ecs_os_malloc_n(int32_t, - rule->operation_count * rule->filter.term_count); - } + for (i = 0; i < count; i ++) { + ecs_entity_t id = ids[i]; - for (i = 0; i < rule->filter.term_count; i ++) { - it->columns[i] = -1; + /* Bootstrap components */ + if (id == ecs_id(EcsComponent)) { + storage->columns[i].size = ECS_SIZEOF(EcsComponent); + storage->columns[i].alignment = ECS_ALIGNOF(EcsComponent); + continue; + } else if (ECS_PAIR_RELATION(id) == ecs_id(EcsIdentifier)) { + storage->columns[i].size = ECS_SIZEOF(EcsIdentifier); + storage->columns[i].alignment = ECS_ALIGNOF(EcsIdentifier); + continue; + } + + const EcsComponent *component = flecs_component_from_id(world, id); + ecs_assert(component != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(component->size != 0, ECS_INTERNAL_ERROR, NULL); + + storage->columns[i].size = flecs_itoi16(component->size); + storage->columns[i].alignment = flecs_itoi16(component->alignment); } } - it->op = 0; + if (sw_count) { + ecs_entity_t *ids = ecs_vector_first(table->type, ecs_entity_t); + int32_t sw_offset = table->sw_column_offset; + storage->sw_columns = ecs_os_calloc_n(ecs_sw_column_t, sw_count); - for (i = 0; i < rule->variable_count; i ++) { - if (rule->variables[i].kind == EcsRuleVarKindEntity) { - entity_reg_set(rule, it->registers, i, EcsWildcard); - } else { - table_reg_set(rule, it->registers, i, NULL); + for (i = 0; i < sw_count; i ++) { + ecs_entity_t e = ids[i + sw_offset]; + ecs_assert(ECS_HAS_ROLE(e, SWITCH), ECS_INTERNAL_ERROR, NULL); + e = e & ECS_COMPONENT_MASK; + const EcsType *type_ptr = ecs_get(world, e, EcsType); + ecs_assert(type_ptr != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_type_t sw_type = type_ptr->normalized; + + ecs_entity_t *sw_array = ecs_vector_first(sw_type, ecs_entity_t); + int32_t sw_array_count = ecs_vector_count(sw_type); + + ecs_switch_t *sw = flecs_switch_new( + sw_array[0], + sw_array[sw_array_count - 1], + 0); + + storage->sw_columns[i].data = sw; + storage->sw_columns[i].type = sw_type; } } - result.variable_names = rule->variable_names; - result.variable_count = rule->variable_count; - result.term_count = rule->filter.term_count; - result.terms = rule->filter.terms; - result.next = ecs_rule_next; - result.is_filter = rule->filter.filter; - - return result; + if (bs_count) { + storage->bs_columns = ecs_os_calloc_n(ecs_bs_column_t, bs_count); + for (i = 0; i < bs_count; i ++) { + flecs_bitset_init(&storage->bs_columns[i].data); + } + } } -void ecs_rule_iter_free( - ecs_iter_t *iter) +static +ecs_flags32_t get_component_action_flags( + const ecs_type_info_t *c_info) { - ecs_rule_iter_t *it = &iter->iter.rule; - ecs_os_free(it->registers); - ecs_os_free(it->columns); - ecs_os_free(it->op_ctx); - ecs_os_free(it->variables); - it->registers = NULL; - it->columns = NULL; - it->op_ctx = NULL; - flecs_iter_fini(iter); + ecs_flags32_t flags = 0; + + if (c_info->lifecycle.ctor) { + flags |= EcsTableHasCtors; + } + if (c_info->lifecycle.dtor) { + flags |= EcsTableHasDtors; + } + if (c_info->lifecycle.copy) { + flags |= EcsTableHasCopy; + } + if (c_info->lifecycle.move) { + flags |= EcsTableHasMove; + } + + return flags; } -/* Edge case: if the filter has the same variable for both predicate and - * object, they are both resolved at the same time but at the time of - * evaluating the filter they're still wildcards which would match columns - * that have different predicates/objects. Do an additional scan to make - * sure the column we're returning actually matches. */ +/* Check if table has instance of component, including pairs */ static -int32_t find_next_same_var( +bool has_component( + ecs_world_t *world, ecs_type_t type, - int32_t column, - ecs_id_t pattern) + ecs_entity_t component) { - /* If same_var is true, this has to be a wildcard pair. We cannot have - * the same variable in a pair, and one part of a pair resolved with - * another part unresolved. */ - ecs_assert(pattern == ecs_pair(EcsWildcard, EcsWildcard), - ECS_INTERNAL_ERROR, NULL); - (void)pattern; - - /* Keep scanning for an id where rel and obj are the same */ - ecs_id_t *ids = ecs_vector_first(type, ecs_id_t); + ecs_entity_t *entities = ecs_vector_first(type, ecs_entity_t); int32_t i, count = ecs_vector_count(type); - for (i = column + 1; i < count; i ++) { - ecs_id_t id = ids[i]; - if (!ECS_HAS_ROLE(id, PAIR)) { - /* If id is not a pair, this will definitely not match, and we - * will find no further matches. */ - return -1; - } - if (ECS_PAIR_RELATION(id) == ECS_PAIR_OBJECT(id)) { - /* Found a match! */ - return i; + for (i = 0; i < count; i ++) { + if (component == ecs_get_typeid(world, entities[i])) { + return true; } } - - /* No pairs found with same rel/obj */ - return -1; + + return false; } static -int32_t find_next_column( - const ecs_world_t *world, - const ecs_table_t *table, - int32_t column, - ecs_rule_filter_t *filter) +void notify_component_info( + ecs_world_t *world, + ecs_table_t *table, + ecs_entity_t component) { - ecs_entity_t pattern = filter->mask; - ecs_type_t type = table->type; + ecs_type_t table_type = table->storage_type; + if (!component || has_component(world, table_type, component)){ + int32_t column_count = ecs_vector_count(table_type); + ecs_assert(!component || column_count != 0, ECS_INTERNAL_ERROR, NULL); - if (column == -1) { - ecs_table_record_t *tr = flecs_get_table_record(world, table, pattern); - if (!tr) { - return -1; + if (!column_count) { + return; } - column = tr->column; - } else { - column ++; - - if (ecs_vector_count(table->type) <= column) { - return -1; + + if (!table->c_info) { + table->c_info = ecs_os_calloc( + ECS_SIZEOF(ecs_type_info_t*) * column_count); } - ecs_id_t *ids = ecs_vector_first(table->type, ecs_id_t); - if (!ecs_id_match(ids[column], pattern)) { - return -1; - } + /* Reset lifecycle flags before recomputing */ + table->flags &= ~EcsTableHasLifecycle; + + /* Recompute lifecycle flags */ + ecs_entity_t *array = ecs_vector_first(table_type, ecs_entity_t); + int32_t i; + for (i = 0; i < column_count; i ++) { + ecs_id_t id = array[i]; + ecs_entity_t c; + + /* Hardcode components used in bootstrap */ + if (id == ecs_id(EcsComponent)) { + c = id; + } else if (ECS_PAIR_RELATION(id) == ecs_id(EcsIdentifier)) { + c = ecs_id(EcsIdentifier); + } else { + c = ecs_get_typeid(world, array[i]); + } + ecs_assert(c != 0, ECS_INTERNAL_ERROR, NULL); + + const ecs_type_info_t *c_info = flecs_get_c_info(world, c); + if (c_info) { + ecs_flags32_t flags = get_component_action_flags(c_info); + table->flags |= flags; + } + + /* Store pointer to c_info for fast access */ + table->c_info[i] = (ecs_type_info_t*)c_info; + } } +} - if (filter->same_var) { - column = find_next_same_var(type, column, filter->mask); +static +void notify_trigger( + ecs_world_t *world, + ecs_table_t *table, + ecs_entity_t event) +{ + (void)world; + + if (event == EcsOnAdd) { + table->flags |= EcsTableHasOnAdd; + } else if (event == EcsOnRemove) { + table->flags |= EcsTableHasOnRemove; + } else if (event == EcsOnSet) { + table->flags |= EcsTableHasOnSet; + } else if (event == EcsUnSet) { + table->flags |= EcsTableHasUnSet; } +} - return column; +static +void run_on_remove( + ecs_world_t *world, + ecs_table_t *table, + ecs_data_t *data) +{ + int32_t count = ecs_vector_count(data->entities); + if (count) { + ecs_ids_t removed = { + .array = ecs_vector_first(table->type, ecs_id_t), + .count = ecs_vector_count(table->type) + }; + + ecs_table_diff_t diff = { + .removed = removed, + .un_set = removed + }; + + flecs_notify_on_remove(world, table, NULL, 0, count, &diff); + } } -/* This function finds the next table in a table set, and is used by the select - * operation. The function automatically skips empty tables, so that subsequent - * operations don't waste a lot of processing for nothing. */ +/* -- Private functions -- */ + +/* If table goes from 0 to >0 entities or from >0 entities to 0 entities notify + * queries. This allows systems associated with queries to move inactive tables + * out of the main loop. */ static -ecs_table_record_t find_next_table( - ecs_rule_filter_t *filter, - ecs_rule_with_ctx_t *op_ctx) +void table_activate( + ecs_world_t *world, + ecs_table_t *table, + bool activate) { - ecs_id_record_t *idr = op_ctx->idr; - const ecs_table_record_t *tables = flecs_id_record_tables(idr); - int32_t i = op_ctx->table_index, count = flecs_id_record_count(idr); - ecs_table_t *table = NULL; - int32_t column = -1; + ecs_vector_t *queries = table->queries; + ecs_query_t **buffer = ecs_vector_first(queries, ecs_query_t*); + int32_t i, count = ecs_vector_count(queries); - for (; i < count && (column == -1); i ++) { - const ecs_table_record_t *tr = &tables[i]; - table = tr->table; + for (i = 0; i < count; i ++) { + flecs_query_notify(world, buffer[i], &(ecs_query_event_t) { + .kind = activate ? EcsQueryTableNonEmpty : EcsQueryTableEmpty, + .table = table + }); + } - /* Should only iterate non-empty tables */ - ecs_assert(ecs_table_count(table) != 0, ECS_INTERNAL_ERROR, NULL); + flecs_table_set_empty(world, table); +} + +/* This function is called when a query is matched with a table. A table keeps + * a list of tables that match so that they can be notified when the table + * becomes empty / non-empty. */ +static +void register_query( + ecs_world_t *world, + ecs_table_t *table, + ecs_query_t *query) +{ + (void)world; +#ifndef NDEBUG + /* Sanity check if query has already been added */ + int32_t i, count = ecs_vector_count(table->queries); + for (i = 0; i < count; i ++) { + ecs_query_t **q = ecs_vector_get(table->queries, ecs_query_t*, i); + ecs_assert(*q != query, ECS_INTERNAL_ERROR, NULL); + } +#endif + + ecs_query_t **q = ecs_vector_add(&table->queries, ecs_query_t*); + if (q) *q = query; +} + +/* This function is called when a query is unmatched with a table. This can + * happen for queries that have shared components expressions in their signature + * and those shared components changed (for example, a base removed a comp). */ +static +void unregister_query( + ecs_world_t *world, + ecs_table_t *table, + ecs_query_t *query) +{ + (void)world; - column = tr->column; - if (filter->same_var) { - column = find_next_same_var(table->type, column - 1, filter->mask); + int32_t i, count = ecs_vector_count(table->queries); + for (i = 0; i < count; i ++) { + ecs_query_t **q = ecs_vector_get(table->queries, ecs_query_t*, i); + if (*q == query) { + break; } } - if (column == -1) { - table = NULL; - } - - op_ctx->table_index = i; + /* Query must have been registered with table */ + ecs_assert(i != count, ECS_INTERNAL_ERROR, NULL); - return (ecs_table_record_t){.table = table, .column = column}; + /* Remove query */ + ecs_vector_remove(table->queries, ecs_query_t*, i); } static -ecs_id_record_t* find_tables( +void ctor_component( ecs_world_t *world, - ecs_id_t id) + ecs_type_info_t *cdata, + ecs_column_t *column, + ecs_entity_t *entities, + int32_t row, + int32_t count) { - ecs_id_record_t *idr = flecs_get_id_record(world, id); - if (!flecs_id_record_count(idr)) { - /* Skip ids that don't have (non-empty) tables */ - return NULL; - } + /* A new component is constructed */ + ecs_xtor_t ctor; + if (cdata && (ctor = cdata->lifecycle.ctor)) { + void *ctx = cdata->lifecycle.ctx; + int16_t size = column->size; + int16_t alignment = column->alignment; - return idr; -} + void *ptr = ecs_vector_get_t(column->data, size, alignment, row); -static -ecs_id_t rule_get_column( - ecs_type_t type, - int32_t column) -{ - ecs_id_t *comp = ecs_vector_get(type, ecs_id_t, column); - ecs_assert(comp != NULL, ECS_INTERNAL_ERROR, NULL); - return *comp; + ctor(world, cdata->component, entities, ptr, + flecs_itosize(size), count, ctx); + } } static -void set_column( - ecs_iter_t *it, - ecs_rule_op_t *op, - ecs_type_t type, - int32_t column) +void dtor_component( + ecs_world_t *world, + ecs_type_info_t *cdata, + ecs_column_t *column, + ecs_entity_t *entities, + int32_t row, + int32_t count) { - if (op->term == -1) { - /* If operation is not associated with a term, don't set anything */ + if (!count) { return; } + + /* An old component is destructed */ + ecs_xtor_t dtor; + if (cdata && (dtor = cdata->lifecycle.dtor)) { + void *ctx = cdata->lifecycle.ctx; + int16_t size = column->size; + int16_t alignment = column->alignment; - ecs_assert(op->term >= 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(column->data != NULL, ECS_INTERNAL_ERROR, NULL); + void *ptr = ecs_vector_get_t(column->data, size, alignment, row); + ecs_assert(ptr != NULL, ECS_INTERNAL_ERROR, NULL); - if (type) { - it->ids[op->term] = rule_get_column(type, column); - } else { - it->ids[op->term] = 0; + dtor(world, cdata->component, &entities[row], ptr, + flecs_itosize(size), count, ctx); } } static -void set_source( - ecs_iter_t *it, - ecs_rule_op_t *op, - ecs_rule_reg_t *regs, - int32_t r) +void dtor_all_components( + ecs_world_t *world, + ecs_table_t *table, + ecs_data_t *data, + int32_t row, + int32_t count, + bool update_entity_index, + bool is_delete) { - if (op->term == -1) { - /* If operation is not associated with a term, don't set anything */ - return; - } + /* Can't delete and not update the entity index */ + ecs_assert(!is_delete || update_entity_index, ECS_INTERNAL_ERROR, NULL); - ecs_assert(op->term >= 0, ECS_INTERNAL_ERROR, NULL); + ecs_record_t **records = ecs_vector_first(data->record_ptrs, ecs_record_t*); + ecs_entity_t *entities = ecs_vector_first(data->entities, ecs_entity_t); + int32_t i, c, end = row + count; + int32_t column_count = ecs_vector_count(table->storage_type); - const ecs_rule_t *rule = it->iter.rule.rule; - if ((r != UINT8_MAX) && rule->variables[r].kind == EcsRuleVarKindEntity) { - it->subjects[op->term] = reg_get_entity(rule, op, regs, r); - } else { - it->subjects[op->term] = 0; + (void)records; + + /* If table has components with destructors, iterate component columns */ + if (table->flags & EcsTableHasDtors) { + /* Prevent the storage from getting modified while deleting */ + ecs_defer_begin(world); + + /* Throw up a lock just to be sure */ + table->lock = true; + + /* Iterate entities first, then components. This ensures that only one + * entity is invalidated at a time, which ensures that destructors can + * safely access other entities. */ + for (i = row; i < end; i ++) { + for (c = 0; c < column_count; c++) { + ecs_column_t *column = &data->columns[c]; + dtor_component(world, table->c_info[c], column, entities, i, 1); + } + + /* Update entity index after invoking destructors so that entity can + * be safely used in destructor callbacks. */ + if (update_entity_index) { + ecs_entity_t e = entities[i]; + ecs_assert(!e || ecs_is_valid(world, e), + ECS_INTERNAL_ERROR, NULL); + ecs_assert(!e || records[i] == ecs_eis_get(world, e), + ECS_INTERNAL_ERROR, NULL); + ecs_assert(!e || records[i]->table == table, + ECS_INTERNAL_ERROR, NULL); + + if (is_delete) { + ecs_eis_delete(world, e); + ecs_assert(ecs_is_valid(world, e) == false, + ECS_INTERNAL_ERROR, NULL); + } else { + // If this is not a delete, clear the entity index record + ecs_record_t r = {NULL, 0}; + ecs_eis_set(world, e, &r); + } + } else { + /* This should only happen in rare cases, such as when the data + * cleaned up is not part of the world (like with snapshots) */ + } + } + + table->lock = false; + + ecs_defer_end(world); + + /* If table does not have destructors, just update entity index */ + } else if (update_entity_index) { + if (is_delete) { + for (i = row; i < end; i ++) { + ecs_entity_t e = entities[i]; + ecs_assert(!e || ecs_is_valid(world, e), ECS_INTERNAL_ERROR, NULL); + ecs_assert(!e || records[i] == ecs_eis_get(world, e), + ECS_INTERNAL_ERROR, NULL); + ecs_assert(!e || records[i]->table == table, + ECS_INTERNAL_ERROR, NULL); + + ecs_eis_delete(world, e); + ecs_assert(!ecs_is_valid(world, e), ECS_INTERNAL_ERROR, NULL); + } + } else { + for (i = row; i < end; i ++) { + ecs_entity_t e = entities[i]; + ecs_assert(!e || ecs_is_valid(world, e), ECS_INTERNAL_ERROR, NULL); + ecs_assert(!e || records[i] == ecs_eis_get(world, e), + ECS_INTERNAL_ERROR, NULL); + ecs_assert(!e || records[i]->table == table, + ECS_INTERNAL_ERROR, NULL); + ecs_record_t r = {NULL, 0}; + ecs_eis_set(world, e, &r); + } + } } } -/* Input operation. The input operation acts as a placeholder for the start of - * the program, and creates an entry in the register array that can serve to - * store variables passed to an iterator. */ static -bool eval_input( - ecs_iter_t *it, - ecs_rule_op_t *op, - int32_t op_index, - bool redo) +void fini_data( + ecs_world_t *world, + ecs_table_t *table, + ecs_data_t *data, + bool do_on_remove, + bool update_entity_index, + bool is_delete, + bool deactivate) { - (void)it; - (void)op; - (void)op_index; + ecs_assert(!table->lock, ECS_LOCKED_STORAGE, NULL); - if (!redo) { - /* First operation executed by the iterator. Always return true. */ - return true; - } else { - /* When Input is asked to redo, it means that all other operations have - * exhausted their results. Input itself does not yield anything, so - * return false. This will terminate rule execution. */ - return false; + if (!data) { + return; } -} -static -bool eval_superset( - ecs_iter_t *it, - ecs_rule_op_t *op, - int32_t op_index, - bool redo) -{ - ecs_rule_iter_t *iter = &it->iter.rule; - const ecs_rule_t *rule = iter->rule; - ecs_world_t *world = rule->world; - ecs_rule_superset_ctx_t *op_ctx = &iter->op_ctx[op_index].is.superset; - ecs_rule_superset_frame_t *frame = NULL; - ecs_rule_reg_t *regs = get_registers(iter, op); + if (do_on_remove) { + run_on_remove(world, table, data); + } - /* Get register indices for output */ - int32_t sp; - int32_t r = op->r_out; + int32_t count = flecs_table_data_count(data); + if (count) { + dtor_all_components(world, table, data, 0, count, + update_entity_index, is_delete); + } - /* Register cannot be a literal, since we need to store things in it */ - ecs_assert(r != UINT8_MAX, ECS_INTERNAL_ERROR, NULL); + /* Sanity check */ + ecs_assert(ecs_vector_count(data->record_ptrs) == + ecs_vector_count(data->entities), ECS_INTERNAL_ERROR, NULL); - /* Superset results are always stored in an entity variable */ - ecs_assert(rule->variables[r].kind == EcsRuleVarKindEntity, - ECS_INTERNAL_ERROR, NULL); + ecs_column_t *columns = data->columns; + if (columns) { + int32_t c, column_count = ecs_vector_count(table->storage_type); + for (c = 0; c < column_count; c ++) { + /* Sanity check */ + ecs_assert(!columns[c].data || (ecs_vector_count(columns[c].data) == + ecs_vector_count(data->entities)), ECS_INTERNAL_ERROR, NULL); - /* Get queried for id, fill out potential variables */ - ecs_rule_pair_t pair = op->filter; + ecs_vector_free(columns[c].data); + } + ecs_os_free(columns); + data->columns = NULL; + } - ecs_rule_filter_t filter = pair_to_filter(iter, op, pair); - ecs_rule_filter_t super_filter = { - .mask = ecs_pair(ECS_PAIR_RELATION(filter.mask), EcsWildcard) - }; - ecs_table_t *table = NULL; + ecs_sw_column_t *sw_columns = data->sw_columns; + if (sw_columns) { + int32_t c, column_count = table->sw_column_count; + for (c = 0; c < column_count; c ++) { + flecs_switch_free(sw_columns[c].data); + } + ecs_os_free(sw_columns); + data->sw_columns = NULL; + } - if (!redo) { - op_ctx->stack = op_ctx->storage; - sp = op_ctx->sp = 0; - frame = &op_ctx->stack[sp]; + ecs_bs_column_t *bs_columns = data->bs_columns; + if (bs_columns) { + int32_t c, column_count = table->bs_column_count; + for (c = 0; c < column_count; c ++) { + flecs_bitset_deinit(&bs_columns[c].data); + } + ecs_os_free(bs_columns); + data->bs_columns = NULL; + } - /* Get table of object for which to get supersets */ - ecs_entity_t obj = ECS_PAIR_OBJECT(filter.mask); + ecs_vector_free(data->entities); + ecs_vector_free(data->record_ptrs); - /* If obj is wildcard, there's nothing to determine a superset for */ - ecs_assert(obj != EcsWildcard, ECS_INTERNAL_ERROR, NULL); + data->entities = NULL; + data->record_ptrs = NULL; - /* Find first matching column in table */ - table = table_from_entity(world, obj); - int32_t column = find_next_column(world, table, -1, &super_filter); + if (deactivate && count) { + table_activate(world, table, false); + } +} - /* If no matching column was found, there are no supersets */ - if (column == -1) { - return false; - } +/* Cleanup, no OnRemove, don't update entity index, don't deactivate table */ +void flecs_table_clear_data( + ecs_world_t *world, + ecs_table_t *table, + ecs_data_t *data) +{ + fini_data(world, table, data, false, false, false, false); +} - ecs_entity_t col_entity = rule_get_column(table->type, column); - ecs_entity_t col_obj = ecs_entity_t_lo(col_entity); +/* Cleanup, no OnRemove, clear entity index, deactivate table */ +void flecs_table_clear_entities_silent( + ecs_world_t *world, + ecs_table_t *table) +{ + fini_data(world, table, &table->storage, false, true, false, true); +} - entity_reg_set(rule, regs, r, col_obj); - set_column(it, op, table->type, column); +/* Cleanup, run OnRemove, clear entity index, deactivate table */ +void flecs_table_clear_entities( + ecs_world_t *world, + ecs_table_t *table) +{ + fini_data(world, table, &table->storage, true, true, false, true); +} - frame->table = table; - frame->column = column; +/* Cleanup, run OnRemove, delete from entity index, deactivate table */ +void flecs_table_delete_entities( + ecs_world_t *world, + ecs_table_t *table) +{ + fini_data(world, table, &table->storage, true, true, true, true); +} - return true; - } +/* Unset all components in table. This function is called before a table is + * deleted, and invokes all UnSet handlers, if any */ +void flecs_table_remove_actions( + ecs_world_t *world, + ecs_table_t *table) +{ + (void)world; + run_on_remove(world, table, &table->storage); +} - sp = op_ctx->sp; - frame = &op_ctx->stack[sp]; - table = frame->table; - int32_t column = frame->column; +/* Free table resources. */ +void flecs_table_free( + ecs_world_t *world, + ecs_table_t *table) +{ + ecs_assert(!table->lock, ECS_LOCKED_STORAGE, NULL); + (void)world; - ecs_entity_t col_entity = rule_get_column(table->type, column); - ecs_entity_t col_obj = ecs_entity_t_lo(col_entity); - ecs_table_t *next_table = table_from_entity(world, col_obj); +#ifndef NDEBUG + char *expr = ecs_type_str(world, table->type); + ecs_dbg_2("#[green]table#[reset] [%s] deleted", expr); + ecs_os_free(expr); +#endif - if (next_table) { - sp ++; - frame = &op_ctx->stack[sp]; - frame->table = next_table; - frame->column = -1; + /* Cleanup data, no OnRemove, delete from entity index, don't deactivate */ + fini_data(world, table, &table->storage, false, true, true, false); + + flecs_table_clear_edges(world, table); + + flecs_unregister_table(world, table); + + ecs_vector_free(table->queries); + ecs_os_free(table->dirty_state); + ecs_os_free(table->storage_map); + + if (table->c_info) { + ecs_os_free(table->c_info); } - do { - frame = &op_ctx->stack[sp]; - table = frame->table; - column = frame->column; + table->id = 0; +} - column = find_next_column(world, table, column, &super_filter); - if (column != -1) { - op_ctx->sp = sp; - frame->column = column; - col_entity = rule_get_column(table->type, column); - col_obj = ecs_entity_t_lo(col_entity); +/* Free table type. Do this separately from freeing the table as types can be + * in use by application destructors. */ +void flecs_table_free_type( + ecs_table_t *table) +{ + ecs_vector_free((ecs_vector_t*)table->type); +} - entity_reg_set(rule, regs, r, col_obj); - set_column(it, op, table->type, column); +/* Reset a table to its initial state. */ +void flecs_table_reset( + ecs_world_t *world, + ecs_table_t *table) +{ + ecs_assert(!table->lock, ECS_LOCKED_STORAGE, NULL); + flecs_table_clear_edges(world, table); +} - return true; - } +static +void mark_table_dirty( + ecs_table_t *table, + int32_t index) +{ + if (table->dirty_state) { + table->dirty_state[index] ++; + } +} - sp --; - } while (sp >= 0); +void flecs_table_mark_dirty( + ecs_world_t *world, + ecs_table_t *table, + ecs_entity_t component) +{ + ecs_assert(!table->lock, ECS_LOCKED_STORAGE, NULL); + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); - return false; + if (table->dirty_state) { + int32_t index = ecs_type_match(world, table->storage_table, + table->storage_type, 0, component, 0, 0, 0, NULL, NULL, NULL); + ecs_assert(index != -1, ECS_INTERNAL_ERROR, NULL); + table->dirty_state[index + 1] ++; + } } static -bool eval_subset( - ecs_iter_t *it, - ecs_rule_op_t *op, - int32_t op_index, - bool redo) +void move_switch_columns( + ecs_table_t *new_table, + ecs_data_t *new_data, + int32_t new_index, + ecs_table_t *old_table, + ecs_data_t *old_data, + int32_t old_index, + int32_t count) { - ecs_rule_iter_t *iter = &it->iter.rule; - const ecs_rule_t *rule = iter->rule; - ecs_world_t *world = rule->world; - ecs_rule_subset_ctx_t *op_ctx = &iter->op_ctx[op_index].is.subset; - ecs_rule_subset_frame_t *frame = NULL; - ecs_table_record_t table_record; - ecs_rule_reg_t *regs = get_registers(iter, op); + int32_t i_old = 0, old_column_count = old_table->sw_column_count; + int32_t i_new = 0, new_column_count = new_table->sw_column_count; - /* Get register indices for output */ - int32_t sp, row; - int32_t r = op->r_out; - ecs_assert(r != UINT8_MAX, ECS_INTERNAL_ERROR, NULL); + if (!old_column_count || !new_column_count) { + return; + } - /* Get queried for id, fill out potential variables */ - ecs_rule_pair_t pair = op->filter; - ecs_rule_filter_t filter = pair_to_filter(iter, op, pair); - ecs_id_record_t *idr; - ecs_table_t *table = NULL; + ecs_sw_column_t *old_columns = old_data->sw_columns; + ecs_sw_column_t *new_columns = new_data->sw_columns; - if (!redo) { - op_ctx->stack = op_ctx->storage; - sp = op_ctx->sp = 0; - frame = &op_ctx->stack[sp]; - idr = frame->with_ctx.idr = find_tables(world, filter.mask); - if (!idr) { - return false; - } + ecs_type_t new_type = new_table->type; + ecs_type_t old_type = old_table->type; - frame->with_ctx.table_index = 0; - table_record = find_next_table(&filter, &frame->with_ctx); - - /* If first table set has no non-empty table, yield nothing */ - if (!table_record.table) { - return false; - } + int32_t offset_new = new_table->sw_column_offset; + int32_t offset_old = old_table->sw_column_offset; - frame->row = 0; - frame->column = table_record.column; - table_reg_set(rule, regs, r, (frame->table = table_record.table)); - set_column(it, op, table_record.table->type, table_record.column); - return true; - } + ecs_id_t *new_ids = ecs_vector_first(new_type, ecs_id_t); + ecs_id_t *old_ids = ecs_vector_first(old_type, ecs_id_t); - do { - sp = op_ctx->sp; - frame = &op_ctx->stack[sp]; - table = frame->table; - row = frame->row; + for (; (i_new < new_column_count) && (i_old < old_column_count);) { + ecs_entity_t new_id = new_ids[i_new + offset_new]; + ecs_entity_t old_id = old_ids[i_old + offset_old]; - /* If row exceeds number of elements in table, find next table in frame that - * still has entities */ - while ((sp >= 0) && (row >= ecs_table_count(table))) { - table_record = find_next_table(&filter, &frame->with_ctx); + if (new_id == old_id) { + ecs_switch_t *old_switch = old_columns[i_old].data; + ecs_switch_t *new_switch = new_columns[i_new].data; - if (table_record.table) { - table = frame->table = table_record.table; - ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); - frame->row = 0; - frame->column = table_record.column; - set_column(it, op, table_record.table->type, table_record.column); - table_reg_set(rule, regs, r, table); - return true; - } else { - sp = -- op_ctx->sp; - if (sp < 0) { - /* If none of the frames yielded anything, no more data */ - return false; - } - frame = &op_ctx->stack[sp]; - table = frame->table; - idr = frame->with_ctx.idr; - row = ++ frame->row; + flecs_switch_ensure(new_switch, new_index + count); - ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(idr != NULL, ECS_INTERNAL_ERROR, NULL); + int i; + for (i = 0; i < count; i ++) { + uint64_t value = flecs_switch_get(old_switch, old_index + i); + flecs_switch_set(new_switch, new_index + i, value); } } - int32_t row_count = ecs_table_count(table); + i_new += new_id <= old_id; + i_old += new_id >= old_id; + } +} - /* Table must have at least row elements */ - ecs_assert(row_count > row, ECS_INTERNAL_ERROR, NULL); +static +void move_bitset_columns( + ecs_table_t *new_table, + ecs_data_t *new_data, + int32_t new_index, + ecs_table_t *old_table, + ecs_data_t *old_data, + int32_t old_index, + int32_t count) +{ + int32_t i_old = 0, old_column_count = old_table->bs_column_count; + int32_t i_new = 0, new_column_count = new_table->bs_column_count; - ecs_entity_t *entities = ecs_vector_first( - table->storage.entities, ecs_entity_t); - ecs_assert(entities != NULL, ECS_INTERNAL_ERROR, NULL); + if (!old_column_count || !new_column_count) { + return; + } - /* The entity used to find the next table set */ - do { - ecs_entity_t e = entities[row]; + ecs_bs_column_t *old_columns = old_data->bs_columns; + ecs_bs_column_t *new_columns = new_data->bs_columns; - /* Create look_for expression with the resolved entity as object */ - pair.reg_mask &= ~RULE_PAIR_OBJECT; /* turn of bit because it's not a reg */ - pair.obj.ent = e; - filter = pair_to_filter(iter, op, pair); + ecs_type_t new_type = new_table->type; + ecs_type_t old_type = old_table->type; - /* Find table set for expression */ - table = NULL; - idr = find_tables(world, filter.mask); + int32_t offset_new = new_table->bs_column_offset; + int32_t offset_old = old_table->bs_column_offset; - /* If table set is found, find first non-empty table */ - if (idr) { - ecs_rule_subset_frame_t *new_frame = &op_ctx->stack[sp + 1]; - new_frame->with_ctx.idr = idr; - new_frame->with_ctx.table_index = 0; - table_record = find_next_table(&filter, &new_frame->with_ctx); + ecs_entity_t *new_components = ecs_vector_first(new_type, ecs_entity_t); + ecs_entity_t *old_components = ecs_vector_first(old_type, ecs_entity_t); - /* If set contains non-empty table, push it to stack */ - if (table_record.table) { - table = table_record.table; - op_ctx->sp ++; - new_frame->table = table; - new_frame->row = 0; - new_frame->column = table_record.column; - frame = new_frame; - } - } + for (; (i_new < new_column_count) && (i_old < old_column_count);) { + ecs_entity_t new_component = new_components[i_new + offset_new]; + ecs_entity_t old_component = old_components[i_old + offset_old]; - /* If no table was found for the current entity, advance row */ - if (!table) { - row = ++ frame->row; - } - } while (!table && row < row_count); - } while (!table); + if (new_component == old_component) { + ecs_bitset_t *old_bs = &old_columns[i_old].data; + ecs_bitset_t *new_bs = &new_columns[i_new].data; - table_reg_set(rule, regs, r, table); - set_column(it, op, table->type, frame->column); + flecs_bitset_ensure(new_bs, new_index + count); - return true; + int i; + for (i = 0; i < count; i ++) { + uint64_t value = flecs_bitset_get(old_bs, old_index + i); + flecs_bitset_set(new_bs, new_index + i, value); + } + } + + i_new += new_component <= old_component; + i_old += new_component >= old_component; + } } -/* Select operation. The select operation finds and iterates a table set that - * corresponds to its pair expression. */ static -bool eval_select( - ecs_iter_t *it, - ecs_rule_op_t *op, - int32_t op_index, - bool redo) +void grow_column( + ecs_world_t *world, + ecs_entity_t *entities, + ecs_column_t *column, + ecs_type_info_t *c_info, + int32_t to_add, + int32_t new_size, + bool construct) { - ecs_rule_iter_t *iter = &it->iter.rule; - const ecs_rule_t *rule = iter->rule; - ecs_world_t *world = rule->world; - ecs_rule_with_ctx_t *op_ctx = &iter->op_ctx[op_index].is.with; - ecs_table_record_t table_record; - ecs_rule_reg_t *regs = get_registers(iter, op); + ecs_vector_t *vec = column->data; + int16_t alignment = column->alignment; - /* Get register indices for output */ - int32_t r = op->r_out; - ecs_assert(r != UINT8_MAX, ECS_INTERNAL_ERROR, NULL); + int32_t size = column->size; + int32_t count = ecs_vector_count(vec); + int32_t old_size = ecs_vector_size(vec); + int32_t new_count = count + to_add; + bool can_realloc = new_size != old_size; - /* Get queried for id, fill out potential variables */ - ecs_rule_pair_t pair = op->filter; - ecs_rule_filter_t filter = pair_to_filter(iter, op, pair); - ecs_entity_t pattern = filter.mask; - int32_t *columns = rule_get_columns(iter, op); + ecs_assert(new_size >= new_count, ECS_INTERNAL_ERROR, NULL); - int32_t column = -1; - ecs_table_t *table = NULL; - ecs_id_record_t *idr; + /* If the array could possibly realloc and the component has a move action + * defined, move old elements manually */ + ecs_move_t move; + if (c_info && count && can_realloc && (move = c_info->lifecycle.move)) { + ecs_xtor_t ctor = c_info->lifecycle.ctor; + ecs_assert(ctor != NULL, ECS_INTERNAL_ERROR, NULL); - if (!redo && op->term != -1) { - it->ids[op->term] = pattern; - columns[op->term] = -1; - } + /* Create new vector */ + ecs_vector_t *new_vec = ecs_vector_new_t(size, alignment, new_size); + ecs_vector_set_count_t(&new_vec, size, alignment, new_count); - /* If this is a redo, we already looked up the table set */ - if (redo) { - idr = op_ctx->idr; - - /* If this is not a redo lookup the table set. Even though this may not be - * the first time the operation is evaluated, variables may have changed - * since last time, which could change the table set to lookup. */ + void *old_buffer = ecs_vector_first_t( + vec, size, alignment); + + void *new_buffer = ecs_vector_first_t( + new_vec, size, alignment); + + /* First construct elements (old and new) in new buffer */ + ctor(world, c_info->component, entities, new_buffer, + flecs_itosize(size), construct ? new_count : count, + c_info->lifecycle.ctx); + + /* Move old elements */ + move(world, c_info->component, entities, entities, + new_buffer, old_buffer, flecs_itosize(size), count, + c_info->lifecycle.ctx); + + /* Free old vector */ + ecs_vector_free(vec); + column->data = new_vec; } else { - /* A table set is a set of tables that all contain at least the - * requested look_for expression. What is returned is a table record, - * which in addition to the table also stores the first occurrance at - * which the requested expression occurs in the table. This reduces (and - * in most cases eliminates) any searching that needs to occur in a - * table type. Tables are also registered under wildcards, which is why - * this operation can simply use the look_for variable directly */ + /* If array won't realloc or has no move, simply add new elements */ + if (can_realloc) { + ecs_vector_set_size_t(&vec, size, alignment, new_size); + } - idr = op_ctx->idr = find_tables(world, pattern); - } + void *elem = ecs_vector_addn_t(&vec, size, alignment, to_add); - /* If no table set was found for queried for entity, there are no results */ - if (!idr) { - return false; + ecs_xtor_t ctor; + if (construct && c_info && (ctor = c_info->lifecycle.ctor)) { + /* If new elements need to be constructed and component has a + * constructor, construct */ + ctor(world, c_info->component, &entities[count], elem, + flecs_itosize(size), to_add, c_info->lifecycle.ctx); + } + + column->data = vec; } - /* If this is not a redo, start at the beginning */ - if (!redo) { - op_ctx->table_index = 0; + ecs_assert(ecs_vector_size(column->data) == new_size, + ECS_INTERNAL_ERROR, NULL); +} - /* Return the first table_record in the table set. */ - table_record = find_next_table(&filter, op_ctx); - - /* If no table record was found, there are no results. */ - if (!table_record.table) { - return false; - } +static +int32_t grow_data( + ecs_world_t *world, + ecs_table_t *table, + ecs_data_t *data, + int32_t to_add, + int32_t size, + const ecs_entity_t *ids) +{ + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(data != NULL, ECS_INTERNAL_ERROR, NULL); - table = table_record.table; + int32_t cur_count = flecs_table_data_count(data); + int32_t column_count = ecs_vector_count(table->storage_type); + int32_t sw_column_count = table->sw_column_count; + int32_t bs_column_count = table->bs_column_count; + ecs_column_t *columns = data->columns; + ecs_sw_column_t *sw_columns = data->sw_columns; + ecs_bs_column_t *bs_columns = data->bs_columns; - /* Set current column to first occurrence of queried for entity */ - column = columns[op->term] = table_record.column; + /* Add record to record ptr array */ + ecs_vector_set_size(&data->record_ptrs, ecs_record_t*, size); + ecs_record_t **r = ecs_vector_addn(&data->record_ptrs, ecs_record_t*, to_add); + ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); + if (ecs_vector_size(data->record_ptrs) > size) { + size = ecs_vector_size(data->record_ptrs); + } - /* Store table in register */ - table_reg_set(rule, regs, r, table); - - /* If this is a redo, progress to the next match */ - } else { - /* First test if there are any more matches for the current table, in - * case we're looking for a wildcard. */ - if (filter.wildcard) { - table = table_reg_get(rule, regs, r); - ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + /* Add entity to column with entity ids */ + ecs_vector_set_size(&data->entities, ecs_entity_t, size); + ecs_entity_t *e = ecs_vector_addn(&data->entities, ecs_entity_t, to_add); + ecs_assert(e != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(ecs_vector_size(data->entities) == size, ECS_INTERNAL_ERROR, NULL); - column = columns[op->term]; - column = find_next_column(world, table, column, &filter); - columns[op->term] = column; + /* Initialize entity ids and record ptrs */ + int32_t i; + if (ids) { + for (i = 0; i < to_add; i ++) { + e[i] = ids[i]; } + } else { + ecs_os_memset(e, 0, ECS_SIZEOF(ecs_entity_t) * to_add); + } + ecs_os_memset(r, 0, ECS_SIZEOF(ecs_record_t*) * to_add); - /* If no next match was found for this table, move to next table */ - if (column == -1) { - table_record = find_next_table(&filter, op_ctx); - if (!table_record.table) { - return false; - } - - /* Assign new table to table register */ - table_reg_set(rule, regs, r, (table = table_record.table)); + /* Add elements to each column array */ + ecs_type_info_t **c_info_array = table->c_info; + ecs_entity_t *entities = ecs_vector_first(data->entities, ecs_entity_t); + for (i = 0; i < column_count; i ++) { + ecs_column_t *column = &columns[i]; + ecs_assert(column->size != 0, ECS_INTERNAL_ERROR, NULL); - /* Assign first matching column */ - column = columns[op->term] = table_record.column; + ecs_type_info_t *c_info = NULL; + if (c_info_array) { + c_info = c_info_array[i]; } + + grow_column(world, entities, column, c_info, to_add, size, true); + ecs_assert(ecs_vector_size(columns[i].data) == size, + ECS_INTERNAL_ERROR, NULL); } - /* If we got here, we found a match. Table and column must be set */ - ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(column != -1, ECS_INTERNAL_ERROR, NULL); + /* Add elements to each switch column */ + for (i = 0; i < sw_column_count; i ++) { + ecs_switch_t *sw = sw_columns[i].data; + flecs_switch_addn(sw, to_add); + } - /* If this is a wildcard query, fill out the variable registers */ - if (filter.wildcard) { - reify_variables(iter, op, &filter, table->type, column); + /* Add elements to each bitset column */ + for (i = 0; i < bs_column_count; i ++) { + ecs_bitset_t *bs = &bs_columns[i].data; + flecs_bitset_addn(bs, to_add); } - - if (!pair.obj_0) { - set_column(it, op, table->type, column); + + /* If the table is monitored indicate that there has been a change */ + mark_table_dirty(table, 0); + + if (!world->is_readonly && !cur_count) { + table_activate(world, table, true); } - return true; + table->alloc_count ++; + + /* Return index of first added entity */ + return cur_count; } -/* With operation. The With operation always comes after either the Select or - * another With operation, and applies additional filters to the table. */ static -bool eval_with( - ecs_iter_t *it, - ecs_rule_op_t *op, - int32_t op_index, - bool redo) +void fast_append( + ecs_column_t *columns, + int32_t column_count) { - ecs_rule_iter_t *iter = &it->iter.rule; - const ecs_rule_t *rule = iter->rule; - ecs_world_t *world = rule->world; - ecs_rule_with_ctx_t *op_ctx = &iter->op_ctx[op_index].is.with; - ecs_rule_reg_t *regs = get_registers(iter, op); + /* Add elements to each column array */ + int32_t i; + for (i = 0; i < column_count; i ++) { + ecs_column_t *column = &columns[i]; + int16_t size = column->size; + if (size) { + int16_t alignment = column->alignment; + ecs_vector_add_t(&column->data, size, alignment); + } + } +} - /* Get register indices for input */ - int32_t r = op->r_in; +int32_t flecs_table_append( + ecs_world_t *world, + ecs_table_t *table, + ecs_data_t *data, + ecs_entity_t entity, + ecs_record_t *record, + bool construct) +{ + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(data != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(!table->lock, ECS_LOCKED_STORAGE, NULL); - /* Get queried for id, fill out potential variables */ - ecs_rule_pair_t pair = op->filter; - ecs_rule_filter_t filter = pair_to_filter(iter, op, pair); - int32_t *columns = rule_get_columns(iter, op); + /* Get count & size before growing entities array. This tells us whether the + * arrays will realloc */ + int32_t count = ecs_vector_count(data->entities); + int32_t size = ecs_vector_size(data->entities); + int32_t column_count = ecs_vector_count(table->storage_type); + ecs_column_t *columns = table->storage.columns; + + /* Grow buffer with entity ids, set new element to new entity */ + ecs_entity_t *e = ecs_vector_add(&data->entities, ecs_entity_t); + ecs_assert(e != NULL, ECS_INTERNAL_ERROR, NULL); + *e = entity; - /* If looked for entity is not a wildcard (meaning there are no unknown/ - * unconstrained variables) and this is a redo, nothing more to yield. */ - if (redo && !filter.wildcard) { - return false; - } + /* Keep track of alloc count. This allows references to check if cached + * pointers need to be updated. */ + table->alloc_count += (count == size); - int32_t column = -1; - ecs_table_t *table = NULL; - ecs_id_record_t *idr; + /* Add record ptr to array with record ptrs */ + ecs_record_t **r = ecs_vector_add(&data->record_ptrs, ecs_record_t*); + ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); + *r = record; + + /* If the table is monitored indicate that there has been a change */ + mark_table_dirty(table, 0); - if (!redo && op->term != -1) { - columns[op->term] = -1; - } + /* If this is the first entity in this table, signal queries so that the + * table moves from an inactive table to an active table. */ + if (!world->is_readonly && !count) { + table_activate(world, table, true); + } - /* If this is a redo, we already looked up the table set */ - if (redo) { - idr = op_ctx->idr; - - /* If this is not a redo lookup the table set. Even though this may not be - * the first time the operation is evaluated, variables may have changed - * since last time, which could change the table set to lookup. */ - } else { - /* Transitive queries are inclusive, which means that if we have a - * transitive predicate which is provided with the same subject and - * object, it should return true. By default with will not return true - * as the subject likely does not have itself as a relationship, which - * is why this is a special case. - * - * TODO: might want to move this code to a separate with_inclusive - * instruction to limit branches for non-transitive queries (and to keep - * code more readable). - */ - if (pair.transitive && pair.inclusive) { - ecs_entity_t subj = 0, obj = 0; - - if (r == UINT8_MAX) { - subj = op->subject; - } else { - ecs_rule_var_t *v_subj = &rule->variables[r]; - if (v_subj->kind == EcsRuleVarKindEntity) { - subj = entity_reg_get(rule, regs, r); + ecs_assert(count >= 0, ECS_INTERNAL_ERROR, NULL); - /* This is the input for the op, so should always be set */ - ecs_assert(subj != 0, ECS_INTERNAL_ERROR, NULL); - } - } + /* Fast path: no switch columns, no lifecycle actions */ + if (!(table->flags & EcsTableIsComplex)) { + fast_append(columns, column_count); + return count; + } - /* If subj is set, it means that it is an entity. Try to also - * resolve the object. */ - if (subj) { - /* If the object is not a wildcard, it has been reified. Get the - * value from either the register or as a literal */ - if (!filter.obj_wildcard) { - obj = ecs_entity_t_lo(filter.mask); - if (subj == obj) { - it->ids[op->term] = filter.mask; - return true; - } - } - } - } + int32_t sw_column_count = table->sw_column_count; + int32_t bs_column_count = table->bs_column_count; + ecs_sw_column_t *sw_columns = table->storage.sw_columns; + ecs_bs_column_t *bs_columns = table->storage.bs_columns; - /* The With operation finds the table set that belongs to its pair - * filter. The table set is a sparse set that provides an O(1) operation - * to check whether the current table has the required expression. */ - idr = op_ctx->idr = find_tables(world, filter.mask); - } + ecs_type_info_t **c_info_array = table->c_info; + ecs_entity_t *entities = ecs_vector_first( + data->entities, ecs_entity_t); - /* If no table set was found for queried for entity, there are no results. - * If this result is a transitive query, the table we're evaluating may not - * be in the returned table set. Regardless, if the filter that contains a - * transitive predicate does not have any tables associated with it, there - * can be no transitive matches for the filter. */ - if (!idr) { - return false; - } + /* Reobtain size to ensure that the columns have the same size as the + * entities and record vectors. This keeps reasoning about when allocations + * occur easier. */ + size = ecs_vector_size(data->entities); - table = reg_get_table(rule, op, regs, r); - if (!table) { - return false; - } + /* Grow component arrays with 1 element */ + int32_t i; + for (i = 0; i < column_count; i ++) { + ecs_column_t *column = &columns[i]; + ecs_assert(column->size != 0, ECS_INTERNAL_ERROR, NULL); - /* If this is not a redo, start at the beginning */ - if (!redo) { - column = find_next_column(world, table, -1, &filter); - - /* If this is a redo, progress to the next match */ - } else { - if (!filter.wildcard) { - return false; + ecs_type_info_t *c_info = NULL; + if (c_info_array) { + c_info = c_info_array[i]; } + + grow_column(world, entities, column, c_info, 1, size, construct); - /* Find the next match for the expression in the column. The columns - * array keeps track of the state for each With operation, so that - * even after redoing a With, the search doesn't have to start from - * the beginning. */ - column = find_next_column(world, table, columns[op->term], &filter); + ecs_assert( + ecs_vector_size(columns[i].data) == ecs_vector_size(data->entities), + ECS_INTERNAL_ERROR, NULL); + + ecs_assert( + ecs_vector_count(columns[i].data) == ecs_vector_count(data->entities), + ECS_INTERNAL_ERROR, NULL); } - /* If no next match was found for this table, no more data */ - if (column == -1) { - return false; + /* Add element to each switch column */ + for (i = 0; i < sw_column_count; i ++) { + ecs_assert(sw_columns != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_switch_t *sw = sw_columns[i].data; + flecs_switch_add(sw); } - columns[op->term] = column; - - /* If we got here, we found a match. Table and column must be set */ - ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(column != -1, ECS_INTERNAL_ERROR, NULL); + /* Add element to each bitset column */ + for (i = 0; i < bs_column_count; i ++) { + ecs_assert(bs_columns != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_bitset_t *bs = &bs_columns[i].data; + flecs_bitset_addn(bs, 1); + } - /* If this is a wildcard query, fill out the variable registers */ - if (filter.wildcard) { - reify_variables(iter, op, &filter, table->type, column); - } + return count; +} - if (!pair.obj_0) { - set_column(it, op, table->type, column); +static +void fast_delete_last( + ecs_column_t *columns, + int32_t column_count) +{ + int i; + for (i = 0; i < column_count; i ++) { + ecs_column_t *column = &columns[i]; + ecs_vector_remove_last(column->data); } +} - set_source(it, op, regs, r); +static +void fast_delete( + ecs_column_t *columns, + int32_t column_count, + int32_t index) +{ + int i; + for (i = 0; i < column_count; i ++) { + ecs_column_t *column = &columns[i]; + int16_t size = column->size; + ecs_assert(size != 0, ECS_INTERNAL_ERROR, NULL); - return true; + int16_t alignment = column->alignment; + ecs_vector_remove_t(column->data, size, alignment, index); + } } -/* Each operation. The each operation is a simple operation that takes a table - * as input, and outputs each of the entities in a table. This operation is - * useful for rules that match a table, and where the entities of the table are - * used as predicate or object. If a rule contains an each operation, an - * iterator is guaranteed to yield an entity instead of a table. The input for - * an each operation can only be the root variable. */ -static -bool eval_each( - ecs_iter_t *it, - ecs_rule_op_t *op, - int32_t op_index, - bool redo) +void flecs_table_delete( + ecs_world_t *world, + ecs_table_t *table, + ecs_data_t *data, + int32_t index, + bool destruct) { - ecs_rule_iter_t *iter = &it->iter.rule; - ecs_rule_each_ctx_t *op_ctx = &iter->op_ctx[op_index].is.each; - ecs_rule_reg_t *regs = get_registers(iter, op); - int32_t r_in = op->r_in; - int32_t r_out = op->r_out; - ecs_entity_t e; + ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(data != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(!table->lock, ECS_LOCKED_STORAGE, NULL); - /* Make sure in/out registers are of the correct kind */ - ecs_assert(iter->rule->variables[r_in].kind == EcsRuleVarKindTable, - ECS_INTERNAL_ERROR, NULL); - ecs_assert(iter->rule->variables[r_out].kind == EcsRuleVarKindEntity, - ECS_INTERNAL_ERROR, NULL); + ecs_vector_t *v_entities = data->entities; + int32_t count = ecs_vector_count(v_entities); - /* Get table, make sure that it contains data. The select operation should - * ensure that empty tables are never forwarded. */ - ecs_table_t *table = table_reg_get(iter->rule, regs, r_in); - if (table) { - int32_t row, count = regs[r_in].count; - int32_t offset = regs[r_in].offset; + ecs_assert(count > 0, ECS_INTERNAL_ERROR, NULL); + count --; + ecs_assert(index <= count, ECS_INTERNAL_ERROR, NULL); - if (!count) { - count = ecs_table_count(table); - ecs_assert(count != 0, ECS_INTERNAL_ERROR, NULL); - } else { - count += offset; - } + /* Move last entity id to index */ + ecs_entity_t *entities = ecs_vector_first(v_entities, ecs_entity_t); + ecs_entity_t entity_to_move = entities[count]; + ecs_entity_t entity_to_delete = entities[index]; + entities[index] = entity_to_move; + ecs_vector_remove_last(v_entities); - ecs_entity_t *entities = ecs_vector_first( - table->storage.entities, ecs_entity_t); - ecs_assert(entities != NULL, ECS_INTERNAL_ERROR, NULL); + /* Move last record ptr to index */ + ecs_vector_t *v_records = data->record_ptrs; + ecs_assert(count < ecs_vector_count(v_records), ECS_INTERNAL_ERROR, NULL); - /* If this is is not a redo, start from row 0, otherwise go to the - * next entity. */ - if (!redo) { - row = op_ctx->row = offset; - } else { - row = ++ op_ctx->row; - } + ecs_record_t **records = ecs_vector_first(v_records, ecs_record_t*); + ecs_record_t *record_to_move = records[count]; + records[index] = record_to_move; + ecs_vector_remove_last(v_records); - /* If row exceeds number of entities in table, return false */ - if (row >= count) { - return false; + /* Update record of moved entity in entity index */ + if (index != count) { + if (record_to_move) { + uint32_t row_flags = record_to_move->row & ECS_ROW_FLAGS_MASK; + record_to_move->row = ECS_ROW_TO_RECORD(index, row_flags); + ecs_assert(record_to_move->table != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(record_to_move->table == table, ECS_INTERNAL_ERROR, NULL); } + } - /* Skip builtin entities that could confuse operations */ - e = entities[row]; - while (e == EcsWildcard || e == EcsThis) { - row ++; - if (row == count) { - return false; - } - e = entities[row]; - } - } else { - if (!redo) { - e = entity_reg_get(iter->rule, regs, r_in); - } else { - return false; - } - } + /* If the table is monitored indicate that there has been a change */ + mark_table_dirty(table, 0); - /* Assign entity */ - entity_reg_set(iter->rule, regs, r_out, e); + /* If table is empty, deactivate it */ + if (!count) { + table_activate(world, table, false); + } - return true; -} + /* Destruct component data */ + ecs_type_info_t **c_info_array = table->c_info; + ecs_column_t *columns = data->columns; + int32_t column_count = ecs_vector_count(table->storage_type); + int32_t i; -/* Store operation. Stores entity in register. This can either be an entity - * literal or an entity variable that will be stored in a table register. The - * latter facilitates scenarios where an iterator only need to return a single - * entity but where the Yield returns tables. */ -static -bool eval_store( - ecs_iter_t *it, - ecs_rule_op_t *op, - int32_t op_index, - bool redo) -{ - (void)op_index; + /* If this is a table without lifecycle callbacks or special columns, take + * fast path that just remove an element from the array(s) */ + if (!(table->flags & EcsTableIsComplex)) { + if (index == count) { + fast_delete_last(columns, column_count); + } else { + fast_delete(columns, column_count, index); + } - if (redo) { - /* Only ever return result once */ - return false; + return; } - ecs_rule_iter_t *iter = &it->iter.rule; - const ecs_rule_t *rule = iter->rule; - ecs_rule_reg_t *regs = get_registers(iter, op); - int32_t r_in = op->r_in; - int32_t r_out = op->r_out; + /* Last element, destruct & remove */ + if (index == count) { + /* If table has component destructors, invoke */ + if (destruct && (table->flags & EcsTableHasDtors)) { + ecs_assert(c_info_array != NULL, ECS_INTERNAL_ERROR, NULL); + + for (i = 0; i < column_count; i ++) { + ecs_type_info_t *c_info = c_info_array[i]; + ecs_xtor_t dtor; + if (c_info && (dtor = c_info->lifecycle.dtor)) { + ecs_size_t size = c_info->size; + ecs_size_t alignment = c_info->alignment; + dtor(world, c_info->component, &entity_to_delete, + ecs_vector_last_t(columns[i].data, size, alignment), + flecs_itosize(size), 1, c_info->lifecycle.ctx); + } + } + } - ecs_entity_t e = reg_get_entity(rule, op, regs, r_in); - reg_set_entity(rule, regs, r_out, e); + fast_delete_last(columns, column_count); - if (op->term >= 0) { - ecs_rule_filter_t filter = pair_to_filter(iter, op, op->filter); - it->ids[op->term] = filter.mask; - } + /* Not last element, move last element to deleted element & destruct */ + } else { + /* If table has component destructors, invoke */ + if (destruct && (table->flags & (EcsTableHasDtors | EcsTableHasMove))) { + ecs_assert(c_info_array != NULL, ECS_INTERNAL_ERROR, NULL); - return true; -} + for (i = 0; i < column_count; i ++) { + ecs_column_t *column = &columns[i]; + ecs_size_t size = column->size; + ecs_size_t align = column->alignment; + ecs_vector_t *vec = column->data; + void *dst = ecs_vector_get_t(vec, size, align, index); + void *src = ecs_vector_last_t(vec, size, align); + + ecs_type_info_t *c_info = c_info_array[i]; + ecs_move_ctor_t move_dtor; + if (c_info && (move_dtor = c_info->lifecycle.move_dtor)) { + move_dtor(world, c_info->component, &c_info->lifecycle, + &entity_to_move, &entity_to_delete, dst, src, + flecs_itosize(size), 1, c_info->lifecycle.ctx); + } else { + ecs_os_memcpy(dst, src, size); + } -/* A setjmp operation sets the jump label for a subsequent jump label. When the - * operation is first evaluated (redo=false) it sets the label to the on_pass - * label, and returns true. When the operation is evaluated again (redo=true) - * the label is set to on_fail and the operation returns false. */ -static -bool eval_setjmp( - ecs_iter_t *it, - ecs_rule_op_t *op, - int32_t op_index, - bool redo) -{ - ecs_rule_iter_t *iter = &it->iter.rule; - ecs_rule_setjmp_ctx_t *ctx = &iter->op_ctx[op_index].is.setjmp; + ecs_vector_remove_last(vec); + } - if (!redo) { - ctx->label = op->on_pass; - return true; - } else { - ctx->label = op->on_fail; - return false; + } else { + fast_delete(columns, column_count, index); + } } -} -/* The jump operation jumps to an operation label. The operation always returns - * true. Since the operation modifies the control flow of the program directly, - * the dispatcher does not look at the on_pass or on_fail labels of the jump - * instruction. Instead, the on_pass label is used to store the label of the - * operation that contains the label to jump to. */ -static -bool eval_jump( - ecs_iter_t *it, - ecs_rule_op_t *op, - int32_t op_index, - bool redo) -{ - (void)it; - (void)op; - (void)op_index; + /* Remove elements from switch columns */ + ecs_sw_column_t *sw_columns = data->sw_columns; + int32_t sw_column_count = table->sw_column_count; + for (i = 0; i < sw_column_count; i ++) { + flecs_switch_remove(sw_columns[i].data, index); + } - /* Passthrough, result is not used for control flow */ - return !redo; + /* Remove elements from bitset columns */ + ecs_bs_column_t *bs_columns = data->bs_columns; + int32_t bs_column_count = table->bs_column_count; + for (i = 0; i < bs_column_count; i ++) { + flecs_bitset_remove(&bs_columns[i].data, index); + } } -/* The not operation reverts the result of the operation it embeds */ static -bool eval_not( - ecs_iter_t *it, - ecs_rule_op_t *op, - int32_t op_index, - bool redo) +void fast_move( + ecs_table_t *new_table, + ecs_data_t *new_data, + int32_t new_index, + ecs_table_t *old_table, + ecs_data_t *old_data, + int32_t old_index) { - (void)it; - (void)op; - (void)op_index; + ecs_type_t new_type = new_table->storage_type; + ecs_type_t old_type = old_table->storage_type; - return !redo; -} + int32_t i_new = 0, new_column_count = ecs_vector_count(new_table->storage_type); + int32_t i_old = 0, old_column_count = ecs_vector_count(old_table->storage_type); + ecs_entity_t *new_components = ecs_vector_first(new_type, ecs_entity_t); + ecs_entity_t *old_components = ecs_vector_first(old_type, ecs_entity_t); -/* Yield operation. This is the simplest operation, as all it does is return - * false. This will move the solver back to the previous instruction which - * forces redo's on previous operations, for as long as there are matching - * results. */ -static -bool eval_yield( - ecs_iter_t *it, - ecs_rule_op_t *op, - int32_t op_index, - bool redo) -{ - (void)it; - (void)op; - (void)op_index; - (void)redo; + ecs_column_t *old_columns = old_data->columns; + ecs_column_t *new_columns = new_data->columns; - /* Yield always returns false, because there are never any operations after - * a yield. */ - return false; -} -/* Dispatcher for operations */ -static -bool eval_op( - ecs_iter_t *it, - ecs_rule_op_t *op, - int32_t op_index, - bool redo) -{ - switch(op->kind) { - case EcsRuleInput: - return eval_input(it, op, op_index, redo); - case EcsRuleSelect: - return eval_select(it, op, op_index, redo); - case EcsRuleWith: - return eval_with(it, op, op_index, redo); - case EcsRuleSubSet: - return eval_subset(it, op, op_index, redo); - case EcsRuleSuperSet: - return eval_superset(it, op, op_index, redo); - case EcsRuleEach: - return eval_each(it, op, op_index, redo); - case EcsRuleStore: - return eval_store(it, op, op_index, redo); - case EcsRuleSetJmp: - return eval_setjmp(it, op, op_index, redo); - case EcsRuleJump: - return eval_jump(it, op, op_index, redo); - case EcsRuleNot: - return eval_not(it, op, op_index, redo); - case EcsRuleYield: - return eval_yield(it, op, op_index, redo); - default: - return false; - } -} + for (; (i_new < new_column_count) && (i_old < old_column_count);) { + ecs_entity_t new_component = new_components[i_new]; + ecs_entity_t old_component = old_components[i_old]; -/* Utility to copy all registers to the next frame. Keeping track of register - * values for each operation is necessary, because if an operation is asked to - * redo matching, it must to be able to pick up from where it left of */ -static -void push_registers( - ecs_rule_iter_t *it, - int32_t cur, - int32_t next) -{ - if (!it->rule->variable_count) { - return; - } + if (new_component == old_component) { + ecs_column_t *new_column = &new_columns[i_new]; + ecs_column_t *old_column = &old_columns[i_old]; + int16_t size = new_column->size; + ecs_assert(size != 0, ECS_INTERNAL_ERROR, NULL); - ecs_rule_reg_t *src_regs = get_register_frame(it, cur); - ecs_rule_reg_t *dst_regs = get_register_frame(it, next); + int16_t alignment = new_column->alignment; + void *dst = ecs_vector_get_t( + new_column->data, size, alignment, new_index); + void *src = ecs_vector_get_t( + old_column->data, size, alignment, old_index); - ecs_os_memcpy_n(dst_regs, src_regs, - ecs_rule_reg_t, it->rule->variable_count); -} + ecs_assert(dst != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(src != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_os_memcpy(dst, src, size); + } -/* Utility to copy all columns to the next frame. Columns keep track of which - * columns are currently being evaluated for a table, and are populated by the - * Select and With operations. The columns array is important, as it is used - * to tell the application where to find component data. */ -static -void push_columns( - ecs_rule_iter_t *it, - int32_t cur, - int32_t next) -{ - if (!it->rule->filter.term_count) { - return; + i_new += new_component <= old_component; + i_old += new_component >= old_component; } - - int32_t *src_cols = rule_get_columns_frame(it, cur); - int32_t *dst_cols = rule_get_columns_frame(it, next); - - ecs_os_memcpy_n(dst_cols, src_cols, int32_t, it->rule->filter.term_count); } -/* Populate iterator with data before yielding to application */ -static -void populate_iterator( - const ecs_rule_t *rule, - ecs_iter_t *iter, - ecs_rule_iter_t *it, - ecs_rule_op_t *op) +void flecs_table_move( + ecs_world_t *world, + ecs_entity_t dst_entity, + ecs_entity_t src_entity, + ecs_table_t *new_table, + ecs_data_t *new_data, + int32_t new_index, + ecs_table_t *old_table, + ecs_data_t *old_data, + int32_t old_index, + bool construct) { - ecs_world_t *world = rule->world; - int32_t r = op->r_in; - ecs_rule_reg_t *regs = get_register_frame(it, op->frame); - ecs_table_t *table = NULL; - int32_t count = 0; - int32_t offset = 0; - - /* If the input register for the yield does not point to a variable, - * the rule doesn't contain a this (.) variable. In that case, the - * iterator doesn't contain any data, and this function will simply - * return true or false. An application will still be able to obtain - * the variables that were resolved. */ - if (r != UINT8_MAX) { - ecs_rule_var_t *var = &rule->variables[r]; - ecs_rule_reg_t *reg = ®s[r]; - - if (var->kind == EcsRuleVarKindTable) { - table = table_reg_get(rule, regs, r); - count = regs[r].count; - offset = regs[r].offset; - } else { - /* If a single entity is returned, simply return the - * iterator with count 1 and a pointer to the entity id */ - ecs_assert(var->kind == EcsRuleVarKindEntity, - ECS_INTERNAL_ERROR, NULL); + ecs_assert(new_table != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(old_table != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(!new_table->lock, ECS_LOCKED_STORAGE, NULL); + ecs_assert(!old_table->lock, ECS_LOCKED_STORAGE, NULL); - ecs_entity_t e = reg->entity; - ecs_record_t *record = ecs_eis_get(world, e); - offset = ECS_RECORD_TO_ROW(record->row); + ecs_assert(old_index >= 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(new_index >= 0, ECS_INTERNAL_ERROR, NULL); - /* If an entity is not stored in a table, it could not have - * been matched by anything */ - ecs_assert(record != NULL, ECS_INTERNAL_ERROR, NULL); - table = record->table; - count = 1; - } + ecs_assert(old_data != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(new_data != NULL, ECS_INTERNAL_ERROR, NULL); + + if (!((new_table->flags | old_table->flags) & EcsTableIsComplex)) { + fast_move(new_table, new_data, new_index, old_table, old_data, old_index); + return; } - int32_t i, variable_count = rule->variable_count; - int32_t term_count = rule->filter.term_count; - iter->variables = it->variables; + move_switch_columns( + new_table, new_data, new_index, old_table, old_data, old_index, 1); - for (i = 0; i < variable_count; i ++) { - if (rule->variables[i].kind == EcsRuleVarKindEntity) { - it->variables[i] = regs[i].entity; - } else { - it->variables[i] = 0; - } - } + move_bitset_columns( + new_table, new_data, new_index, old_table, old_data, old_index, 1); - for (i = 0; i < term_count; i ++) { - int32_t v = rule->subject_variables[i]; - if (v != -1) { - ecs_rule_var_t *var = &rule->variables[v]; - if (var->kind == EcsRuleVarKindEntity) { - iter->subjects[i] = regs[var->id].entity; - } - } - } + bool same_entity = dst_entity == src_entity; - /* Iterator expects column indices to start at 1 */ - iter->columns = rule_get_columns_frame(it, op->frame); - for (i = 0; i < term_count; i ++) { - ecs_entity_t subj = iter->subjects[i]; - int32_t c = ++ iter->columns[i]; + ecs_type_t new_type = new_table->storage_type; + ecs_type_t old_type = old_table->storage_type; - if (!subj) { - if (iter->terms[i].subj.entity != EcsThis) { - iter->columns[i] = 0; - } - } else if (c) { - iter->columns[i] = -1; - } - } + int32_t i_new = 0, new_column_count = ecs_vector_count(new_table->storage_type); + int32_t i_old = 0, old_column_count = ecs_vector_count(old_table->storage_type); + ecs_entity_t *new_components = ecs_vector_first(new_type, ecs_entity_t); + ecs_entity_t *old_components = ecs_vector_first(old_type, ecs_entity_t); - flecs_iter_populate_data(world, iter, table, offset, count, - iter->ptrs, iter->sizes); -} + ecs_column_t *old_columns = old_data->columns; + ecs_column_t *new_columns = new_data->columns; -static -bool is_control_flow( - ecs_rule_op_t *op) -{ - switch(op->kind) { - case EcsRuleSetJmp: - case EcsRuleJump: - return true; - default: - return false; - } -} + for (; (i_new < new_column_count) && (i_old < old_column_count);) { + ecs_entity_t new_component = new_components[i_new]; + ecs_entity_t old_component = old_components[i_old]; -/* Iterator next function. This evaluates the program until it reaches a Yield - * operation, and returns the intermediate result(s) to the application. An - * iterator can, depending on the program, either return a table, entity, or - * just true/false, in case a rule doesn't contain the this variable. */ -bool ecs_rule_next( - ecs_iter_t *it) -{ - ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(it->next == ecs_rule_next, ECS_INVALID_PARAMETER, NULL); + if (new_component == old_component) { + ecs_column_t *new_column = &new_columns[i_new]; + ecs_column_t *old_column = &old_columns[i_old]; + int16_t size = new_column->size; + int16_t alignment = new_column->alignment; - ecs_rule_iter_t *iter = &it->iter.rule; - const ecs_rule_t *rule = iter->rule; - bool redo = iter->redo; - int32_t last_frame = -1; - bool init_subjects = it->subjects == NULL; + ecs_assert(size != 0, ECS_INTERNAL_ERROR, NULL); - /* Can't iterate an iterator that's already depleted */ - ecs_check(iter->op != -1, ECS_INVALID_PARAMETER, NULL); + void *dst = ecs_vector_get_t( + new_column->data, size, alignment, new_index); + void *src = ecs_vector_get_t( + old_column->data, size, alignment, old_index); - flecs_iter_init(it); + ecs_assert(dst != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(src != NULL, ECS_INTERNAL_ERROR, NULL); - /* Make sure that if there are any terms with literal subjects, they're - * initialized in the subjects array */ - if (init_subjects) { - int32_t i; - for (i = 0; i < rule->filter.term_count; i ++) { - ecs_term_t *t = &rule->filter.terms[i]; - ecs_term_id_t *subj = &t->subj; - if (subj->var == EcsVarIsEntity && subj->entity != EcsThis) { - it->subjects[i] = subj->entity; + ecs_type_info_t *cdata = new_table->c_info[i_new]; + if (same_entity) { + ecs_move_ctor_t callback; + if (cdata && (callback = cdata->lifecycle.ctor_move_dtor)) { + void *ctx = cdata->lifecycle.ctx; + /* ctor + move + dtor */ + callback(world, new_component, &cdata->lifecycle, + &dst_entity, &src_entity, + dst, src, flecs_itosize(size), 1, ctx); + } else { + ecs_os_memcpy(dst, src, size); + } + } else { + ecs_copy_ctor_t copy; + if (cdata && (copy = cdata->lifecycle.copy_ctor)) { + void *ctx = cdata->lifecycle.ctx; + copy(world, new_component, &cdata->lifecycle, + &dst_entity, &src_entity, + dst, src, flecs_itosize(size), 1, ctx); + } else { + ecs_os_memcpy(dst, src, size); + } } - } - - for (i = 0; i < rule->filter.term_count; i ++) { - ecs_term_t *term = &rule->filter.terms[i]; - if (term->subj.set.mask & EcsNothing || - term->oper == EcsNot || - term->oper == EcsOptional || - term->id == ecs_pair(EcsChildOf, 0)) { - it->ids[i] = term->id; + } else { + if (new_component < old_component) { + if (construct) { + ctor_component(world, new_table->c_info[i_new], + &new_columns[i_new], &dst_entity, new_index, 1); + } + } else { + dtor_component(world, old_table->c_info[i_old], + &old_columns[i_old], &src_entity, old_index, 1); } } - } - do { - /* Evaluate an operation. The result of an operation determines the - * flow of the program. If an operation returns true, the program - * continues to the operation pointed to by 'on_pass'. If the operation - * returns false, the program continues to the operation pointed to by - * 'on_fail'. - * - * In most scenarios, on_pass points to the next operation, and on_fail - * points to the previous operation. - * - * When an operation fails, the previous operation will be invoked with - * redo=true. This will cause the operation to continue its search from - * where it left off. When the operation succeeds, the next operation - * will be invoked with redo=false. This causes the operation to start - * from the beginning, which is necessary since it just received a new - * input. */ - int32_t op_index = iter->op; - ecs_rule_op_t *op = &rule->operations[op_index]; - int32_t cur = op->frame; + i_new += new_component <= old_component; + i_old += new_component >= old_component; + } - /* If this is not the first operation and is also not a control flow - * operation, push a new frame on the stack for the next operation */ - if (!redo && !is_control_flow(op) && cur && cur != last_frame) { - int32_t prev = cur - 1; - push_registers(iter, prev, cur); - push_columns(iter, prev, cur); + if (construct) { + for (; (i_new < new_column_count); i_new ++) { + ctor_component(world, new_table->c_info[i_new], + &new_columns[i_new], &dst_entity, new_index, 1); } + } - /* Dispatch the operation */ - bool result = eval_op(it, op, op_index, redo); - iter->op = result ? op->on_pass : op->on_fail; - - /* If the current operation is yield, return results */ - if (op->kind == EcsRuleYield) { - populate_iterator(rule, it, iter, op); - iter->redo = true; - return true; - } + for (; (i_old < old_column_count); i_old ++) { + dtor_component(world, old_table->c_info[i_old], + &old_columns[i_old], &src_entity, old_index, 1); + } +} - /* If the current operation is a jump, goto stored label */ - if (op->kind == EcsRuleJump) { - /* Label is stored in setjmp context */ - iter->op = iter->op_ctx[op->on_pass].is.setjmp.label; - } +int32_t flecs_table_appendn( + ecs_world_t *world, + ecs_table_t *table, + ecs_data_t *data, + int32_t to_add, + const ecs_entity_t *ids) +{ + ecs_assert(!table->lock, ECS_LOCKED_STORAGE, NULL); - /* If jumping backwards, it's a redo */ - redo = iter->op <= op_index; + int32_t cur_count = flecs_table_data_count(data); + return grow_data(world, table, data, to_add, cur_count + to_add, ids); +} - if (!is_control_flow(op)) { - last_frame = op->frame; - } - } while (iter->op != -1); +void flecs_table_set_size( + ecs_world_t *world, + ecs_table_t *table, + ecs_data_t *data, + int32_t size) +{ + ecs_assert(!table->lock, ECS_LOCKED_STORAGE, NULL); - ecs_rule_iter_free(it); + int32_t cur_count = flecs_table_data_count(data); -error: - return false; + if (cur_count < size) { + grow_data(world, table, data, 0, size, NULL); + } } -#endif +int32_t flecs_table_data_count( + const ecs_data_t *data) +{ + return data ? ecs_vector_count(data->entities) : 0; +} +static +void swap_switch_columns( + ecs_table_t *table, + ecs_data_t *data, + int32_t row_1, + int32_t row_2) +{ + int32_t i = 0, column_count = table->sw_column_count; + if (!column_count) { + return; + } -#ifdef FLECS_MODULE + ecs_sw_column_t *columns = data->sw_columns; + for (i = 0; i < column_count; i ++) { + ecs_switch_t *sw = columns[i].data; + flecs_switch_swap(sw, row_1, row_2); + } +} -char* ecs_module_path_from_c( - const char *c_name) +static +void swap_bitset_columns( + ecs_table_t *table, + ecs_data_t *data, + int32_t row_1, + int32_t row_2) { - ecs_strbuf_t str = ECS_STRBUF_INIT; - const char *ptr; - char ch; + int32_t i = 0, column_count = table->bs_column_count; + if (!column_count) { + return; + } - for (ptr = c_name; (ch = *ptr); ptr++) { - if (isupper(ch)) { - ch = flecs_ito(char, tolower(ch)); - if (ptr != c_name) { - ecs_strbuf_appendstrn(&str, ".", 1); - } - } + ecs_bs_column_t *columns = data->bs_columns; - ecs_strbuf_appendstrn(&str, &ch, 1); + for (i = 0; i < column_count; i ++) { + ecs_bitset_t *bs = &columns[i].data; + flecs_bitset_swap(bs, row_1, row_2); } - - return ecs_strbuf_get(&str); } -ecs_entity_t ecs_import( +void flecs_table_swap( ecs_world_t *world, - ecs_module_action_t init_action, - const char *module_name) -{ - ecs_check(!world->is_readonly, ECS_INVALID_WHILE_ITERATING, NULL); - - ecs_entity_t old_scope = ecs_set_scope(world, 0); - const char *old_name_prefix = world->name_prefix; + ecs_table_t *table, + ecs_data_t *data, + int32_t row_1, + int32_t row_2) +{ + (void)world; - char *path = ecs_module_path_from_c(module_name); - ecs_entity_t e = ecs_lookup_fullpath(world, path); - ecs_os_free(path); + ecs_assert(!table->lock, ECS_LOCKED_STORAGE, NULL); + ecs_assert(data != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(row_1 >= 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(row_2 >= 0, ECS_INTERNAL_ERROR, NULL); - if (!e) { - ecs_trace("#[magenta]import#[reset] %s", module_name); - ecs_log_push(); + if (row_1 == row_2) { + return; + } - /* Load module */ - init_action(world); + /* If the table is monitored indicate that there has been a change */ + mark_table_dirty(table, 0); - /* Lookup module entity (must be registered by module) */ - e = ecs_lookup_fullpath(world, module_name); - ecs_check(e != 0, ECS_MODULE_UNDEFINED, module_name); + ecs_entity_t *entities = ecs_vector_first(data->entities, ecs_entity_t); + ecs_entity_t e1 = entities[row_1]; + ecs_entity_t e2 = entities[row_2]; - ecs_log_pop(); - } + ecs_record_t **record_ptrs = ecs_vector_first(data->record_ptrs, ecs_record_t*); + ecs_record_t *record_ptr_1 = record_ptrs[row_1]; + ecs_record_t *record_ptr_2 = record_ptrs[row_2]; - /* Restore to previous state */ - ecs_set_scope(world, old_scope); - world->name_prefix = old_name_prefix; + ecs_assert(record_ptr_1 != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(record_ptr_2 != NULL, ECS_INTERNAL_ERROR, NULL); - return e; -error: - return 0; -} + /* Keep track of whether entity is watched */ + uint32_t flags_1 = ECS_RECORD_TO_ROW_FLAGS(record_ptr_1->row); + uint32_t flags_2 = ECS_RECORD_TO_ROW_FLAGS(record_ptr_2->row); -ecs_entity_t ecs_import_from_library( - ecs_world_t *world, - const char *library_name, - const char *module_name) -{ - ecs_check(library_name != NULL, ECS_INVALID_PARAMETER, NULL); + /* Swap entities & records */ + entities[row_1] = e2; + entities[row_2] = e1; + record_ptr_1->row = ECS_ROW_TO_RECORD(row_2, flags_1); + record_ptr_2->row = ECS_ROW_TO_RECORD(row_1, flags_2); + record_ptrs[row_1] = record_ptr_2; + record_ptrs[row_2] = record_ptr_1; - char *import_func = (char*)module_name; /* safe */ - char *module = (char*)module_name; + swap_switch_columns(table, data, row_1, row_2); + swap_bitset_columns(table, data, row_1, row_2); - if (!ecs_os_has_modules() || !ecs_os_has_dl()) { - ecs_err( - "library loading not supported, set module_to_dl, dlopen, dlclose " - "and dlproc os API callbacks first"); - return 0; + ecs_column_t *columns = data->columns; + if (!columns) { + return; } - /* If no module name is specified, try default naming convention for loading - * the main module from the library */ - if (!import_func) { - import_func = ecs_os_malloc(ecs_os_strlen(library_name) + ECS_SIZEOF("Import")); - ecs_assert(import_func != NULL, ECS_OUT_OF_MEMORY, NULL); - - const char *ptr; - char ch, *bptr = import_func; - bool capitalize = true; - for (ptr = library_name; (ch = *ptr); ptr ++) { - if (ch == '.') { - capitalize = true; - } else { - if (capitalize) { - *bptr = flecs_ito(char, toupper(ch)); - bptr ++; - capitalize = false; - } else { - *bptr = flecs_ito(char, tolower(ch)); - bptr ++; - } - } - } + /* Swap columns */ + int32_t i, column_count = ecs_vector_count(table->storage_type); + + for (i = 0; i < column_count; i ++) { + int16_t size = columns[i].size; + int16_t alignment = columns[i].alignment; - *bptr = '\0'; + ecs_assert(size != 0, ECS_INTERNAL_ERROR, NULL); - module = ecs_os_strdup(import_func); - ecs_assert(module != NULL, ECS_OUT_OF_MEMORY, NULL); + void *ptr = ecs_vector_first_t(columns[i].data, size, alignment); + void *tmp = ecs_os_alloca(size); - ecs_os_strcat(bptr, "Import"); - } + void *el_1 = ECS_OFFSET(ptr, size * row_1); + void *el_2 = ECS_OFFSET(ptr, size * row_2); - char *library_filename = ecs_os_module_to_dl(library_name); - if (!library_filename) { - ecs_err("failed to find library file for '%s'", library_name); - if (module != module_name) { - ecs_os_free(module); + ecs_os_memcpy(tmp, el_1, size); + ecs_os_memcpy(el_1, el_2, size); + ecs_os_memcpy(el_2, tmp, size); + } +} + +static +void merge_vector( + ecs_vector_t **dst_out, + ecs_vector_t *src, + int16_t size, + int16_t alignment) +{ + ecs_vector_t *dst = *dst_out; + int32_t dst_count = ecs_vector_count(dst); + + if (!dst_count) { + if (dst) { + ecs_vector_free(dst); } - return 0; - } else { - ecs_trace("found file '%s' for library '%s'", - library_filename, library_name); - } - ecs_os_dl_t dl = ecs_os_dlopen(library_filename); - if (!dl) { - ecs_err("failed to load library '%s' ('%s')", - library_name, library_filename); + *dst_out = src; + + /* If the new table is not empty, copy the contents from the + * src into the dst. */ + } else { + int32_t src_count = ecs_vector_count(src); + ecs_vector_set_count_t(&dst, size, alignment, dst_count + src_count); - ecs_os_free(library_filename); + void *dst_ptr = ecs_vector_first_t(dst, size, alignment); + void *src_ptr = ecs_vector_first_t(src, size, alignment); - if (module != module_name) { - ecs_os_free(module); - } + dst_ptr = ECS_OFFSET(dst_ptr, size * dst_count); + + ecs_os_memcpy(dst_ptr, src_ptr, size * src_count); - return 0; - } else { - ecs_trace("library '%s' ('%s') loaded", - library_name, library_filename); + ecs_vector_free(src); + *dst_out = dst; } +} - ecs_module_action_t action = (ecs_module_action_t) - ecs_os_dlproc(dl, import_func); - if (!action) { - ecs_err("failed to load import function %s from library %s", - import_func, library_name); - ecs_os_free(library_filename); - ecs_os_dlclose(dl); - return 0; +static +void merge_column( + ecs_world_t *world, + ecs_table_t *table, + ecs_data_t *data, + int32_t column_id, + ecs_vector_t *src) +{ + ecs_entity_t *entities = ecs_vector_first(data->entities, ecs_entity_t); + ecs_type_info_t *c_info = table->c_info[column_id]; + ecs_column_t *column = &data->columns[column_id]; + ecs_vector_t *dst = column->data; + int16_t size = column->size; + int16_t alignment = column->alignment; + int32_t dst_count = ecs_vector_count(dst); + + if (!dst_count) { + if (dst) { + ecs_vector_free(dst); + } + + column->data = src; + + /* If the new table is not empty, copy the contents from the + * src into the dst. */ } else { - ecs_trace("found import function '%s' in library '%s' for module '%s'", - import_func, library_name, module); - } + int32_t src_count = ecs_vector_count(src); + ecs_vector_set_count_t(&dst, size, alignment, dst_count + src_count); + column->data = dst; - /* Do not free id, as it will be stored as the component identifier */ - ecs_entity_t result = ecs_import(world, action, module); + /* Construct new values */ + if (c_info) { + ctor_component( + world, c_info, column, entities, dst_count, src_count); + } + + void *dst_ptr = ecs_vector_first_t(dst, size, alignment); + void *src_ptr = ecs_vector_first_t(src, size, alignment); - if (import_func != module_name) { - ecs_os_free(import_func); - } + dst_ptr = ECS_OFFSET(dst_ptr, size * dst_count); + + /* Move values into column */ + ecs_move_t move; + if (c_info && (move = c_info->lifecycle.move)) { + move(world, c_info->component, entities, entities, + dst_ptr, src_ptr, flecs_itosize(size), src_count, + c_info->lifecycle.ctx); + } else { + ecs_os_memcpy(dst_ptr, src_ptr, size * src_count); + } - if (module != module_name) { - ecs_os_free(module); + ecs_vector_free(src); } - - ecs_os_free(library_filename); - - return result; -error: - return 0; } -ecs_entity_t ecs_module_init( +static +void merge_table_data( ecs_world_t *world, - const ecs_component_desc_t *desc) + ecs_table_t *new_table, + ecs_table_t *old_table, + int32_t old_count, + int32_t new_count, + ecs_data_t *old_data, + ecs_data_t *new_data) { - ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_poly_assert(world, ecs_world_t); - - const char *name = desc->entity.name; - - char *module_path = ecs_module_path_from_c(name); - ecs_entity_t e = ecs_new_from_fullpath(world, module_path); - ecs_set_symbol(world, e, module_path); - ecs_os_free(module_path); + ecs_type_t new_type = new_table->storage_type; + ecs_type_t old_type = old_table->storage_type; + int32_t i_new = 0, new_column_count = ecs_vector_count(new_type); + int32_t i_old = 0, old_column_count = ecs_vector_count(old_type); + ecs_entity_t *new_components = ecs_vector_first(new_type, ecs_entity_t); + ecs_entity_t *old_components = ecs_vector_first(old_type, ecs_entity_t); - ecs_component_desc_t private_desc = *desc; - private_desc.entity.entity = e; - private_desc.entity.name = NULL; + ecs_column_t *old_columns = old_data->columns; + ecs_column_t *new_columns = new_data->columns; - if (desc->size) { - ecs_entity_t result = ecs_component_init(world, &private_desc); - ecs_assert(result != 0, ECS_INTERNAL_ERROR, NULL); - ecs_assert(result == e, ECS_INTERNAL_ERROR, NULL); - (void)result; - } else { - ecs_entity_t result = ecs_entity_init(world, &private_desc.entity); - ecs_assert(result != 0, ECS_INTERNAL_ERROR, NULL); - ecs_assert(result == e, ECS_INTERNAL_ERROR, NULL); - (void)result; + if (!new_columns && !new_data->entities) { + new_columns = new_data->columns; } + + ecs_assert(!new_column_count || new_columns, ECS_INTERNAL_ERROR, NULL); - return e; -error: - return 0; -} - -#endif + if (!old_count) { + return; + } -#ifndef FLECS_META_PRIVATE_H -#define FLECS_META_PRIVATE_H + /* Merge entities */ + merge_vector(&new_data->entities, old_data->entities, ECS_SIZEOF(ecs_entity_t), + ECS_ALIGNOF(ecs_entity_t)); + old_data->entities = NULL; + ecs_entity_t *entities = ecs_vector_first(new_data->entities, ecs_entity_t); + ecs_assert(ecs_vector_count(new_data->entities) == old_count + new_count, + ECS_INTERNAL_ERROR, NULL); -#ifdef FLECS_META + /* Merge entity index record pointers */ + merge_vector(&new_data->record_ptrs, old_data->record_ptrs, + ECS_SIZEOF(ecs_record_t*), ECS_ALIGNOF(ecs_record_t*)); + old_data->record_ptrs = NULL; -void ecs_meta_type_serialized_init( - ecs_iter_t *it); + for (; (i_new < new_column_count) && (i_old < old_column_count); ) { + ecs_entity_t new_component = new_components[i_new]; + ecs_entity_t old_component = old_components[i_old]; + int16_t size = new_columns[i_new].size; + int16_t alignment = new_columns[i_new].alignment; + ecs_assert(size != 0, ECS_INTERNAL_ERROR, NULL); -void ecs_meta_dtor_serialized( - EcsMetaTypeSerialized *ptr); + if (new_component == old_component) { + merge_column(world, new_table, new_data, i_new, + old_columns[i_old].data); + old_columns[i_old].data = NULL; -#endif - -#endif + /* Mark component column as dirty */ + mark_table_dirty(new_table, i_new + 1); + + i_new ++; + i_old ++; + } else if (new_component < old_component) { + /* New column does not occur in old table, make sure vector is large + * enough. */ + ecs_column_t *column = &new_columns[i_new]; + ecs_vector_set_count_t(&column->data, size, alignment, + old_count + new_count); + /* Construct new values */ + ecs_type_info_t *c_info = new_table->c_info[i_new]; + if (c_info) { + ctor_component(world, c_info, column, + entities, 0, old_count + new_count); + } + + i_new ++; + } else if (new_component > old_component) { + ecs_column_t *column = &old_columns[i_old]; + + /* Destruct old values */ + ecs_type_info_t *c_info = old_table->c_info[i_old]; + if (c_info) { + dtor_component(world, c_info, column, + entities, 0, old_count); + } -#ifdef FLECS_META + /* Old column does not occur in new table, remove */ + ecs_vector_free(column->data); + column->data = NULL; -ecs_entity_t ecs_enum_init( - ecs_world_t *world, - const ecs_enum_desc_t *desc) -{ - ecs_entity_t t = ecs_entity_init(world, &desc->entity); - if (!t) { - return 0; + i_old ++; + } } - ecs_add(world, t, EcsEnum); + move_switch_columns( + new_table, new_data, new_count, old_table, old_data, 0, old_count); - ecs_entity_t old_scope = ecs_set_scope(world, t); + /* Initialize remaining columns */ + for (; i_new < new_column_count; i_new ++) { + ecs_column_t *column = &new_columns[i_new]; + int16_t size = column->size; + int16_t alignment = column->alignment; + ecs_assert(size != 0, ECS_INTERNAL_ERROR, NULL); - int i; - for (i = 0; i < ECS_MEMBER_DESC_CACHE_SIZE; i ++) { - const ecs_enum_constant_t *m_desc = &desc->constants[i]; - if (!m_desc->name) { - break; + ecs_vector_set_count_t(&column->data, size, alignment, + old_count + new_count); + + /* Construct new values */ + ecs_type_info_t *c_info = new_table->c_info[i_new]; + if (c_info) { + ctor_component(world, c_info, column, + entities, 0, old_count + new_count); } + } - ecs_entity_t c = ecs_entity_init(world, &(ecs_entity_desc_t) { - .name = m_desc->name - }); + /* Destroy remaining columns */ + for (; i_old < old_column_count; i_old ++) { + ecs_column_t *column = &old_columns[i_old]; - if (!m_desc->value) { - ecs_add_id(world, c, EcsConstant); - } else { - ecs_set_pair_object(world, c, EcsConstant, ecs_i32_t, - {m_desc->value}); + /* Destruct old values */ + ecs_type_info_t *c_info = old_table->c_info[i_old]; + if (c_info) { + dtor_component(world, c_info, column, entities, + 0, old_count); } - } - ecs_set_scope(world, old_scope); + /* Old column does not occur in new table, remove */ + ecs_vector_free(column->data); + column->data = NULL; + } - if (i == 0) { - ecs_err("enum '%s' has no constants", ecs_get_name(world, t)); - ecs_delete(world, t); - return 0; - } + /* Mark entity column as dirty */ + mark_table_dirty(new_table, 0); +} - return t; +int32_t ecs_table_count( + const ecs_table_t *table) +{ + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + return flecs_table_data_count(&table->storage); } -ecs_entity_t ecs_bitmask_init( +void flecs_table_merge( ecs_world_t *world, - const ecs_bitmask_desc_t *desc) + ecs_table_t *new_table, + ecs_table_t *old_table, + ecs_data_t *new_data, + ecs_data_t *old_data) { - ecs_entity_t t = ecs_entity_init(world, &desc->entity); - if (!t) { - return 0; + ecs_assert(old_table != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(!old_table->lock, ECS_LOCKED_STORAGE, NULL); + + bool move_data = false; + + /* If there is nothing to merge to, just clear the old table */ + if (!new_table) { + flecs_table_clear_data(world, old_table, old_data); + return; + } else { + ecs_assert(!new_table->lock, ECS_LOCKED_STORAGE, NULL); } - ecs_add(world, t, EcsBitmask); - - ecs_entity_t old_scope = ecs_set_scope(world, t); + /* If there is no data to merge, drop out */ + if (!old_data) { + return; + } - int i; - for (i = 0; i < ECS_MEMBER_DESC_CACHE_SIZE; i ++) { - const ecs_bitmask_constant_t *m_desc = &desc->constants[i]; - if (!m_desc->name) { - break; + if (!new_data) { + new_data = &new_table->storage; + if (new_table == old_table) { + move_data = true; } + } - ecs_entity_t c = ecs_entity_init(world, &(ecs_entity_desc_t) { - .name = m_desc->name - }); + ecs_entity_t *old_entities = ecs_vector_first(old_data->entities, ecs_entity_t); + int32_t old_count = ecs_vector_count(old_data->entities); + int32_t new_count = ecs_vector_count(new_data->entities); - if (!m_desc->value) { - ecs_add_id(world, c, EcsConstant); + ecs_record_t **old_records = ecs_vector_first( + old_data->record_ptrs, ecs_record_t*); + + /* First, update entity index so old entities point to new type */ + int32_t i; + for(i = 0; i < old_count; i ++) { + ecs_record_t *record; + if (new_table != old_table) { + record = old_records[i]; + ecs_assert(record != NULL, ECS_INTERNAL_ERROR, NULL); } else { - ecs_set_pair_object(world, c, EcsConstant, ecs_u32_t, - {m_desc->value}); + record = ecs_eis_ensure(world, old_entities[i]); } - } - ecs_set_scope(world, old_scope); - - if (i == 0) { - ecs_err("bitmask '%s' has no constants", ecs_get_name(world, t)); - ecs_delete(world, t); - return 0; + uint32_t flags = ECS_RECORD_TO_ROW_FLAGS(record->row); + record->row = ECS_ROW_TO_RECORD(new_count + i, flags); + record->table = new_table; } - return t; -} - -ecs_entity_t ecs_array_init( - ecs_world_t *world, - const ecs_array_desc_t *desc) -{ - ecs_entity_t t = ecs_entity_init(world, &desc->entity); - if (!t) { - return 0; + /* Merge table columns */ + if (move_data) { + *new_data = *old_data; + } else { + merge_table_data(world, new_table, old_table, old_count, new_count, + old_data, new_data); } - ecs_set(world, t, EcsArray, { - .type = desc->type, - .count = desc->count - }); - - return t; -} + new_table->alloc_count ++; -ecs_entity_t ecs_vector_init( - ecs_world_t *world, - const ecs_vector_desc_t *desc) -{ - ecs_entity_t t = ecs_entity_init(world, &desc->entity); - if (!t) { - return 0; + if (!new_count && old_count) { + table_activate(world, new_table, true); } - - ecs_set(world, t, EcsVector, { - .type = desc->type - }); - - return t; } -ecs_entity_t ecs_struct_init( +void flecs_table_replace_data( ecs_world_t *world, - const ecs_struct_desc_t *desc) + ecs_table_t *table, + ecs_data_t *data) { - ecs_entity_t t = ecs_entity_init(world, &desc->entity); - if (!t) { - return 0; - } - - ecs_entity_t old_scope = ecs_set_scope(world, t); - - int i; - for (i = 0; i < ECS_MEMBER_DESC_CACHE_SIZE; i ++) { - const ecs_member_t *m_desc = &desc->members[i]; - if (!m_desc->type) { - break; - } - - if (!m_desc->name) { - ecs_err("member %d of struct '%s' does not have a name", i, - ecs_get_name(world, t)); - ecs_delete(world, t); - return 0; - } + int32_t prev_count = 0; + ecs_data_t *table_data = &table->storage; + ecs_assert(!data || data != table_data, ECS_INTERNAL_ERROR, NULL); + ecs_assert(!table->lock, ECS_LOCKED_STORAGE, NULL); - ecs_entity_t m = ecs_entity_init(world, &(ecs_entity_desc_t) { - .name = m_desc->name - }); + prev_count = ecs_vector_count(table_data->entities); + run_on_remove(world, table, table_data); + flecs_table_clear_data(world, table, table_data); - ecs_set(world, m, EcsMember, { - .type = m_desc->type, - .count = m_desc->count - }); + if (data) { + table->storage = *data; + } else { + flecs_table_init_data(world, table); } - ecs_set_scope(world, old_scope); + int32_t count = ecs_table_count(table); - if (i == 0) { - ecs_err("struct '%s' has no members", ecs_get_name(world, t)); - ecs_delete(world, t); - return 0; + if (!prev_count && count) { + table_activate(world, table, true); + } else if (prev_count && !count) { + table_activate(world, table, false); } - return t; + table->alloc_count ++; } -#endif - - -#ifdef FLECS_META +int32_t* flecs_table_get_dirty_state( + ecs_table_t *table) +{ + ecs_assert(!table->lock, ECS_LOCKED_STORAGE, NULL); + + if (!table->dirty_state) { + int32_t column_count = ecs_vector_count(table->storage_type); + table->dirty_state = ecs_os_calloc_n( int32_t, column_count + 1); + ecs_assert(table->dirty_state != NULL, ECS_INTERNAL_ERROR, NULL); + } + return table->dirty_state; +} -static -ecs_vector_t* serialize_type( - ecs_world_t *world, - ecs_entity_t type, - ecs_size_t offset, - ecs_vector_t *ops); +int32_t* flecs_table_get_monitor( + ecs_table_t *table) +{ + int32_t *dirty_state = flecs_table_get_dirty_state(table); + ecs_assert(dirty_state != NULL, ECS_INTERNAL_ERROR, NULL); -static -ecs_meta_type_op_kind_t primitive_to_op_kind(ecs_primitive_kind_t kind) { - return EcsOpPrimitive + kind; + int32_t column_count = ecs_vector_count(table->storage_type); + return ecs_os_memdup(dirty_state, (column_count + 1) * ECS_SIZEOF(int32_t)); } -static -ecs_size_t type_size(ecs_world_t *world, ecs_entity_t type) { - const EcsComponent *comp = ecs_get(world, type, EcsComponent); - ecs_assert(comp != NULL, ECS_INTERNAL_ERROR, NULL); - return comp->size; -} +void flecs_table_notify( + ecs_world_t *world, + ecs_table_t *table, + ecs_table_event_t *event) +{ + if (world->is_fini) { + return; + } -static -ecs_meta_type_op_t* ops_add(ecs_vector_t **ops, ecs_meta_type_op_kind_t kind) { - ecs_meta_type_op_t *op = ecs_vector_add(ops, ecs_meta_type_op_t); - op->kind = kind; - op->offset = 0; - op->count = 1; - op->op_count = 1; - op->size = 0; - op->name = NULL; - op->members = NULL; - op->type = 0; - return op; + switch(event->kind) { + case EcsTableQueryMatch: + register_query(world, table, event->query); + break; + case EcsTableQueryUnmatch: + unregister_query(world, table, event->query); + break; + case EcsTableComponentInfo: + notify_component_info(world, table, event->component); + break; + case EcsTableTriggersForId: + notify_trigger(world, table, event->event); + break; + case EcsTableNoTriggersForId: + break; + } } -static -ecs_meta_type_op_t* ops_get(ecs_vector_t *ops, int32_t index) { - ecs_meta_type_op_t* op = ecs_vector_get(ops, ecs_meta_type_op_t, index); - ecs_assert(op != NULL, ECS_INTERNAL_ERROR, NULL); - return op; +void ecs_table_lock( + ecs_world_t *world, + ecs_table_t *table) +{ + if (ecs_poly_is(world, ecs_world_t) && !world->is_readonly) { + table->lock ++; + } } -static -ecs_vector_t* serialize_primitive( +void ecs_table_unlock( ecs_world_t *world, - ecs_entity_t type, - ecs_size_t offset, - ecs_vector_t *ops) + ecs_table_t *table) { - const EcsPrimitive *ptr = ecs_get(world, type, EcsPrimitive); - ecs_assert(ptr != NULL, ECS_INTERNAL_ERROR, NULL); - - ecs_meta_type_op_t *op = ops_add(&ops, primitive_to_op_kind(ptr->kind)); - op->offset = offset, - op->type = type; - op->size = type_size(world, type); + if (ecs_poly_is(world, ecs_world_t) && !world->is_readonly) { + table->lock --; + ecs_assert(table->lock >= 0, ECS_INVALID_OPERATION, NULL); + } +} - return ops; +bool ecs_table_has_module( + ecs_table_t *table) +{ + return table->flags & EcsTableHasModule; } -static -ecs_vector_t* serialize_enum( - ecs_world_t *world, - ecs_entity_t type, - ecs_size_t offset, - ecs_vector_t *ops) +ecs_column_t *ecs_table_column_for_id( + const ecs_world_t *world, + const ecs_table_t *table, + ecs_id_t id) { - (void)world; - - ecs_meta_type_op_t *op = ops_add(&ops, EcsOpEnum); - op->offset = offset, - op->type = type; - op->size = ECS_SIZEOF(ecs_i32_t); + ecs_table_t *storage_table = table->storage_table; + if (!storage_table) { + return NULL; + } - return ops; + ecs_table_record_t *tr = flecs_get_table_record(world, storage_table, id); + if (tr) { + return &table->storage.columns[tr->column]; + } + + return NULL; } -static -ecs_vector_t* serialize_bitmask( - ecs_world_t *world, - ecs_entity_t type, - ecs_size_t offset, - ecs_vector_t *ops) +ecs_type_t ecs_table_get_type( + const ecs_table_t *table) { - (void)world; - - ecs_meta_type_op_t *op = ops_add(&ops, EcsOpBitmask); - op->offset = offset, - op->type = type; - op->size = ECS_SIZEOF(ecs_u32_t); + return table->type; +} - return ops; +ecs_type_t ecs_table_get_storage_type( + const ecs_table_t *table) +{ + return table->storage_type; } -static -ecs_vector_t* serialize_array( - ecs_world_t *world, - ecs_entity_t type, - ecs_size_t offset, - ecs_vector_t *ops) +int32_t ecs_table_storage_count( + const ecs_table_t *table) { - (void)world; + return ecs_vector_count(table->storage_type); +} - ecs_meta_type_op_t *op = ops_add(&ops, EcsOpArray); - op->offset = offset; - op->type = type; - op->size = type_size(world, type); +int32_t ecs_table_type_to_storage_index( + const ecs_table_t *table, + int32_t index) +{ + ecs_check(index < ecs_vector_count(table->type), + ECS_INVALID_PARAMETER, NULL); + int32_t *storage_map = table->storage_map; + if (storage_map) { + return storage_map[index]; + } +error: + return -1; +} - return ops; +int32_t ecs_table_storage_to_type_index( + const ecs_table_t *table, + int32_t index) +{ + ecs_check(index < ecs_vector_count(table->storage_type), + ECS_INVALID_PARAMETER, NULL); + ecs_check(table->storage_map != NULL, ECS_INVALID_PARAMETER, NULL); + int32_t offset = ecs_vector_count(table->type); + return table->storage_map[offset + index]; +error: + return -1; } -static -ecs_vector_t* serialize_array_component( +ecs_record_t* ecs_record_find( ecs_world_t *world, - ecs_entity_t type) + ecs_entity_t entity) { - const EcsArray *ptr = ecs_get(world, type, EcsArray); - if (!ptr) { - return NULL; /* Should never happen, will trigger internal error */ + ecs_poly_assert(world, ecs_world_t); + ecs_check(entity != 0, ECS_INVALID_PARAMETER, NULL); + ecs_record_t *r = ecs_eis_get(world, entity); + if (r) { + return r; } - - ecs_vector_t *ops = serialize_type(world, ptr->type, 0, NULL); - ecs_assert(ops != NULL, ECS_INTERNAL_ERROR, NULL); - - ecs_meta_type_op_t *first = ecs_vector_first(ops, ecs_meta_type_op_t); - first->count = ptr->count; - - return ops; +error: + return NULL; } -static -ecs_vector_t* serialize_vector( - ecs_world_t *world, - ecs_entity_t type, - ecs_size_t offset, - ecs_vector_t *ops) +void* ecs_record_get_column( + ecs_record_t *r, + int32_t column, + size_t c_size) { - (void)world; + (void)c_size; + ecs_table_t *table = r->table; - ecs_meta_type_op_t *op = ops_add(&ops, EcsOpVector); - op->offset = offset; - op->type = type; - op->size = type_size(world, type); + ecs_check(column < ecs_vector_count(table->storage_type), + ECS_INVALID_PARAMETER, NULL); - return ops; -} + ecs_column_t *c = &table->storage.columns[column]; + ecs_assert(c != NULL, ECS_INTERNAL_ERROR, NULL); -static -ecs_vector_t* serialize_struct( - ecs_world_t *world, - ecs_entity_t type, - ecs_size_t offset, - ecs_vector_t *ops) -{ - const EcsStruct *ptr = ecs_get(world, type, EcsStruct); - ecs_assert(ptr != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_check(!flecs_utosize(c_size) || + flecs_utosize(c_size) == c->size, + ECS_INVALID_PARAMETER, NULL); - int32_t cur, first = ecs_vector_count(ops); - ecs_meta_type_op_t *op = ops_add(&ops, EcsOpPush); - op->offset = offset; - op->type = type; - op->size = type_size(world, type); + return ecs_vector_get_t(c->data, c->size, c->alignment, + ECS_RECORD_TO_ROW(r->row)); +error: + return NULL; +} - ecs_member_t *members = ecs_vector_first(ptr->members, ecs_member_t); - int32_t i, count = ecs_vector_count(ptr->members); +static const char* mixin_kind_str[] = { + [EcsMixinBase] = "base (should never be requested by application)", + [EcsMixinWorld] = "world", + [EcsMixinObservable] = "observable", + [EcsMixinIterable] = "iterable", + [EcsMixinMax] = "max (should never be requested by application)" +}; - ecs_hashmap_t *member_index = NULL; - if (count) { - member_index = ecs_os_malloc_t(ecs_hashmap_t); - *member_index = flecs_string_hashmap_new(int32_t); - op->members = member_index; +ecs_mixins_t ecs_world_t_mixins = { + .type_name = "ecs_world_t", + .elems = { + [EcsMixinWorld] = offsetof(ecs_world_t, self), + [EcsMixinObservable] = offsetof(ecs_world_t, observable), + [EcsMixinIterable] = offsetof(ecs_world_t, iterable) } +}; - for (i = 0; i < count; i ++) { - ecs_member_t *member = &members[i]; +ecs_mixins_t ecs_stage_t_mixins = { + .type_name = "ecs_stage_t", + .elems = { + [EcsMixinBase] = offsetof(ecs_stage_t, world), + [EcsMixinWorld] = offsetof(ecs_stage_t, world) + } +}; - cur = ecs_vector_count(ops); - ops = serialize_type(world, member->type, offset + member->offset, ops); +ecs_mixins_t ecs_query_t_mixins = { + .type_name = "ecs_query_t", + .elems = { + [EcsMixinWorld] = offsetof(ecs_query_t, world), + [EcsMixinIterable] = offsetof(ecs_query_t, iterable) + } +}; - op = ops_get(ops, cur); - if (!op->type) { - op->type = member->type; - } +ecs_mixins_t ecs_filter_t_mixins = { + .type_name = "ecs_filter_t", + .elems = { + [EcsMixinIterable] = offsetof(ecs_filter_t, iterable) + } +}; - if (op->count <= 1) { - op->count = member->count; - } - - const char *member_name = member->name; - op->name = member_name; - op->op_count = ecs_vector_count(ops) - cur; +static +void* get_mixin( + const ecs_poly_t *poly, + ecs_mixin_kind_t kind) +{ + ecs_assert(poly != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(kind < EcsMixinMax, ECS_INVALID_PARAMETER, NULL); + + const ecs_header_t *hdr = poly; + ecs_assert(hdr != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(hdr->magic == ECS_OBJECT_MAGIC, ECS_INVALID_PARAMETER, NULL); - ecs_size_t len = ecs_os_strlen(member_name); + const ecs_mixins_t *mixins = hdr->mixins; + if (!mixins) { + /* Object has no mixins */ + goto not_found; + } - ecs_hashed_string_t key = ecs_get_hashed_string(member_name, len, 0); - flecs_hashmap_result_t hmr = flecs_hashmap_ensure( - *member_index, &key, int32_t); - *((int32_t*)hmr.value) = cur - first - 1; + ecs_size_t offset = mixins->elems[kind]; + if (offset == 0) { + /* Object has mixins but not the requested one. Try to find the mixin + * in the poly's base */ + goto find_in_base; } - ops_add(&ops, EcsOpPop); - ops_get(ops, first)->op_count = ecs_vector_count(ops) - first; + /* Object has mixin, return its address */ + return ECS_OFFSET(hdr, offset); - return ops; +find_in_base: + if (offset) { + /* If the poly has a base, try to find the mixin in the base */ + ecs_poly_t *base = *(ecs_poly_t**)ECS_OFFSET(hdr, offset); + if (base) { + return get_mixin(base, kind); + } + } + +not_found: + /* Mixin wasn't found for poly */ + return NULL; } static -ecs_vector_t* serialize_type( - ecs_world_t *world, - ecs_entity_t type, - ecs_size_t offset, - ecs_vector_t *ops) +void* assert_mixin( + const ecs_poly_t *poly, + ecs_mixin_kind_t kind) { - const EcsMetaType *ptr = ecs_get(world, type, EcsMetaType); + void *ptr = get_mixin(poly, kind); if (!ptr) { - char *path = ecs_get_fullpath(world, type); - ecs_err("missing EcsMetaType for type %s'", path); - ecs_os_free(path); - return NULL; + const ecs_header_t *header = poly; + const ecs_mixins_t *mixins = header->mixins; + ecs_err("%s not available for type %s", + mixin_kind_str[kind], + mixins ? mixins->type_name : "unknown"); + ecs_os_abort(); } - switch(ptr->kind) { - case EcsPrimitiveType: - ops = serialize_primitive(world, type, offset, ops); - break; + return ptr; +} - case EcsEnumType: - ops = serialize_enum(world, type, offset, ops); - break; +void* _ecs_poly_init( + ecs_poly_t *poly, + int32_t type, + ecs_size_t size, + ecs_mixins_t *mixins) +{ + ecs_assert(poly != NULL, ECS_INVALID_PARAMETER, NULL); - case EcsBitmaskType: - ops = serialize_bitmask(world, type, offset, ops); - break; + ecs_header_t *hdr = poly; + ecs_os_memset(poly, 0, size); - case EcsStructType: - ops = serialize_struct(world, type, offset, ops); - break; + hdr->magic = ECS_OBJECT_MAGIC; + hdr->type = type; + hdr->mixins = mixins; - case EcsArrayType: - ops = serialize_array(world, type, offset, ops); - break; + return poly; +} - case EcsVectorType: - ops = serialize_vector(world, type, offset, ops); - break; - } +void _ecs_poly_fini( + ecs_poly_t *poly, + int32_t type) +{ + ecs_assert(poly != NULL, ECS_INVALID_PARAMETER, NULL); + (void)type; - return ops; + ecs_header_t *hdr = poly; + + /* Don't deinit poly that wasn't initialized */ + ecs_assert(hdr->magic == ECS_OBJECT_MAGIC, ECS_INVALID_PARAMETER, NULL); + ecs_assert(hdr->type == type, ECS_INVALID_PARAMETER, NULL); + hdr->magic = 0; } -static -ecs_vector_t* serialize_component( - ecs_world_t *world, - ecs_entity_t type) +#define assert_object(cond, file, line)\ + _ecs_assert((cond), ECS_INVALID_PARAMETER, #cond, file, line, NULL);\ + assert(cond) + +#ifndef NDEBUG +void _ecs_poly_assert( + const ecs_poly_t *poly, + int32_t type, + const char *file, + int32_t line) { - const EcsMetaType *ptr = ecs_get(world, type, EcsMetaType); - if (!ptr) { - char *path = ecs_get_fullpath(world, type); - ecs_err("missing EcsMetaType for type %s'", path); - ecs_os_free(path); - return NULL; - } + assert_object(poly != NULL, file, line); + + const ecs_header_t *hdr = poly; + assert_object(hdr->magic == ECS_OBJECT_MAGIC, file, line); + assert_object(hdr->type == type, file, line); +} +#endif - ecs_vector_t *ops = NULL; +bool _ecs_poly_is( + const ecs_poly_t *poly, + int32_t type) +{ + ecs_assert(poly != NULL, ECS_INVALID_PARAMETER, NULL); - switch(ptr->kind) { - case EcsArrayType: - ops = serialize_array_component(world, type); - break; - default: - ops = serialize_type(world, type, 0, NULL); - break; - } + const ecs_header_t *hdr = poly; + ecs_assert(hdr->magic == ECS_OBJECT_MAGIC, ECS_INVALID_PARAMETER, NULL); + return hdr->type == type; +} - return ops; +ecs_iterable_t* ecs_get_iterable( + const ecs_poly_t *poly) +{ + return (ecs_iterable_t*)assert_mixin(poly, EcsMixinIterable); } -void ecs_meta_type_serialized_init( - ecs_iter_t *it) +ecs_observable_t* ecs_get_observable( + const ecs_poly_t *poly) { - ecs_world_t *world = it->world; + return (ecs_observable_t*)assert_mixin(poly, EcsMixinObservable); +} - int i, count = it->count; - for (i = 0; i < count; i ++) { - ecs_entity_t e = it->entities[i]; - ecs_vector_t *ops = serialize_component(world, e); - ecs_assert(ops != NULL, ECS_INTERNAL_ERROR, NULL); +const ecs_world_t* ecs_get_world( + const ecs_poly_t *poly) +{ + return *(ecs_world_t**)assert_mixin(poly, EcsMixinWorld); +} - EcsMetaTypeSerialized *ptr = ecs_get_mut( - world, e, EcsMetaTypeSerialized, NULL); - if (ptr->ops) { - ecs_meta_dtor_serialized(ptr); - } +#ifndef NDEBUG +static int64_t s_min[] = { + [1] = INT8_MIN, [2] = INT16_MIN, [4] = INT32_MIN, [8] = INT64_MIN }; +static int64_t s_max[] = { + [1] = INT8_MAX, [2] = INT16_MAX, [4] = INT32_MAX, [8] = INT64_MAX }; +static uint64_t u_max[] = { + [1] = UINT8_MAX, [2] = UINT16_MAX, [4] = UINT32_MAX, [8] = UINT64_MAX }; - ptr->ops = ops; +uint64_t _flecs_ito( + size_t size, + bool is_signed, + bool lt_zero, + uint64_t u, + const char *err) +{ + union { + uint64_t u; + int64_t s; + } v; + + v.u = u; + + if (is_signed) { + ecs_assert(v.s >= s_min[size], ECS_INVALID_CONVERSION, err); + ecs_assert(v.s <= s_max[size], ECS_INVALID_CONVERSION, err); + } else { + ecs_assert(lt_zero == false, ECS_INVALID_CONVERSION, err); + ecs_assert(u <= u_max[size], ECS_INVALID_CONVERSION, err); } -} + return u; +} #endif +int32_t flecs_next_pow_of_2( + int32_t n) +{ + n --; + n |= n >> 1; + n |= n >> 2; + n |= n >> 4; + n |= n >> 8; + n |= n >> 16; + n ++; -#ifdef FLECS_META + return n; +} -/* EcsMetaTypeSerialized lifecycle */ +/** Convert time to double */ +double ecs_time_to_double( + ecs_time_t t) +{ + double result; + result = t.sec; + return result + (double)t.nanosec / (double)1000000000; +} -void ecs_meta_dtor_serialized( - EcsMetaTypeSerialized *ptr) +ecs_time_t ecs_time_sub( + ecs_time_t t1, + ecs_time_t t2) { - int32_t i, count = ecs_vector_count(ptr->ops); - ecs_meta_type_op_t *ops = ecs_vector_first(ptr->ops, ecs_meta_type_op_t); - - for (i = 0; i < count; i ++) { - ecs_meta_type_op_t *op = &ops[i]; - if (op->members) { - flecs_hashmap_free(*op->members); - ecs_os_free(op->members); - } + ecs_time_t result; + + if (t1.nanosec >= t2.nanosec) { + result.nanosec = t1.nanosec - t2.nanosec; + result.sec = t1.sec - t2.sec; + } else { + result.nanosec = t1.nanosec - t2.nanosec + 1000000000; + result.sec = t1.sec - t2.sec - 1; } - ecs_vector_free(ptr->ops); + return result; } -static ECS_COPY(EcsMetaTypeSerialized, dst, src, { - ecs_meta_dtor_serialized(dst); - - dst->ops = ecs_vector_copy(src->ops, ecs_meta_type_op_t); - - int32_t o, count = ecs_vector_count(src->ops); - ecs_meta_type_op_t *ops = ecs_vector_first(src->ops, ecs_meta_type_op_t); - - for (o = 0; o < count; o ++) { - ecs_meta_type_op_t *op = &ops[o]; - if (op->members) { - op->members = ecs_os_memdup_t(op->members, ecs_hashmap_t); - *op->members = flecs_hashmap_copy(*op->members); - } +void ecs_sleepf( + double t) +{ + if (t > 0) { + int sec = (int)t; + int nsec = (int)((t - sec) * 1000000000); + ecs_os_sleep(sec, nsec); } -}) - -static ECS_MOVE(EcsMetaTypeSerialized, dst, src, { - ecs_meta_dtor_serialized(dst); - dst->ops = src->ops; - src->ops = NULL; -}) +} -static ECS_DTOR(EcsMetaTypeSerialized, ptr, { - ecs_meta_dtor_serialized(ptr); -}) +double ecs_time_measure( + ecs_time_t *start) +{ + ecs_time_t stop, temp; + ecs_os_get_time(&stop); + temp = stop; + stop = ecs_time_sub(stop, *start); + *start = temp; + return ecs_time_to_double(stop); +} +void* ecs_os_memdup( + const void *src, + ecs_size_t size) +{ + if (!src) { + return NULL; + } + + void *dst = ecs_os_malloc(size); + ecs_assert(dst != NULL, ECS_OUT_OF_MEMORY, NULL); + ecs_os_memcpy(dst, src, size); + return dst; +} -/* EcsStruct lifecycle */ +int flecs_entity_compare( + ecs_entity_t e1, + const void *ptr1, + ecs_entity_t e2, + const void *ptr2) +{ + (void)ptr1; + (void)ptr2; + return (e1 > e2) - (e1 < e2); +} -static void dtor_struct( - EcsStruct *ptr) +int flecs_entity_compare_qsort( + const void *e1, + const void *e2) { - ecs_member_t *members = ecs_vector_first(ptr->members, ecs_member_t); - int32_t i, count = ecs_vector_count(ptr->members); - for (i = 0; i < count; i ++) { - ecs_os_free((char*)members[i].name); - } - ecs_vector_free(ptr->members); + ecs_entity_t v1 = *(ecs_entity_t*)e1; + ecs_entity_t v2 = *(ecs_entity_t*)e2; + return flecs_entity_compare(v1, NULL, v2, NULL); } -static ECS_COPY(EcsStruct, dst, src, { - dtor_struct(dst); - - dst->members = ecs_vector_copy(src->members, ecs_member_t); - - ecs_member_t *members = ecs_vector_first(dst->members, ecs_member_t); - int32_t m, count = ecs_vector_count(dst->members); +uint64_t flecs_string_hash( + const void *ptr) +{ + const ecs_hashed_string_t *str = ptr; + ecs_assert(str->hash != 0, ECS_INTERNAL_ERROR, NULL); + return str->hash; +} - for (m = 0; m < count; m ++) { - members[m].name = ecs_os_strdup(members[m].name); - } -}) +/* + This code was taken from sokol_time.h + + zlib/libpng license + Copyright (c) 2018 Andre Weissflog + This software is provided 'as-is', without any express or implied warranty. + In no event will the authors be held liable for any damages arising from the + use of this software. + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software in a + product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not + be misrepresented as being the original software. + 3. This notice may not be removed or altered from any source + distribution. +*/ -static ECS_MOVE(EcsStruct, dst, src, { - dtor_struct(dst); - dst->members = src->members; - src->members = NULL; -}) -static ECS_DTOR(EcsStruct, ptr, { dtor_struct(ptr); }) +static int ecs_os_time_initialized; +#if defined(_WIN32) +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#include +static double _ecs_os_time_win_freq; +static LARGE_INTEGER _ecs_os_time_win_start; +#elif defined(__APPLE__) && defined(__MACH__) +#include +static mach_timebase_info_data_t _ecs_os_time_osx_timebase; +static uint64_t _ecs_os_time_osx_start; +#else /* anything else, this will need more care for non-Linux platforms */ +#include +static uint64_t _ecs_os_time_posix_start; +#endif -/* EcsEnum lifecycle */ +/* prevent 64-bit overflow when computing relative timestamp + see https://gist.github.com/jspohr/3dc4f00033d79ec5bdaf67bc46c813e3 +*/ +#if defined(_WIN32) || (defined(__APPLE__) && defined(__MACH__)) +int64_t int64_muldiv(int64_t value, int64_t numer, int64_t denom) { + int64_t q = value / denom; + int64_t r = value % denom; + return q * numer + r * numer / denom; +} +#endif -static void dtor_enum( - EcsEnum *ptr) -{ - ecs_map_iter_t it = ecs_map_iter(ptr->constants); - ecs_enum_constant_t *c; - while ((c = ecs_map_next(&it, ecs_enum_constant_t, NULL))) { - ecs_os_free((char*)c->name); +void flecs_os_time_setup(void) { + if ( ecs_os_time_initialized) { + return; } - ecs_map_free(ptr->constants); + + ecs_os_time_initialized = 1; + #if defined(_WIN32) + LARGE_INTEGER freq; + QueryPerformanceFrequency(&freq); + QueryPerformanceCounter(&_ecs_os_time_win_start); + _ecs_os_time_win_freq = (double)freq.QuadPart / 1000000000.0; + #elif defined(__APPLE__) && defined(__MACH__) + mach_timebase_info(&_ecs_os_time_osx_timebase); + _ecs_os_time_osx_start = mach_absolute_time(); + #else + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + _ecs_os_time_posix_start = (uint64_t)ts.tv_sec*1000000000 + (uint64_t)ts.tv_nsec; + #endif } -static ECS_COPY(EcsEnum, dst, src, { - dtor_enum(dst); +uint64_t flecs_os_time_now(void) { + ecs_assert(ecs_os_time_initialized != 0, ECS_INTERNAL_ERROR, NULL); - dst->constants = ecs_map_copy(src->constants); - ecs_assert(ecs_map_count(dst->constants) == ecs_map_count(src->constants), - ECS_INTERNAL_ERROR, NULL); + uint64_t now; - ecs_map_iter_t it = ecs_map_iter(dst->constants); - ecs_enum_constant_t *c; - while ((c = ecs_map_next(&it, ecs_enum_constant_t, NULL))) { - c->name = ecs_os_strdup(c->name); - } -}) + #if defined(_WIN32) + LARGE_INTEGER qpc_t; + QueryPerformanceCounter(&qpc_t); + now = (uint64_t)(qpc_t.QuadPart / _ecs_os_time_win_freq); + #elif defined(__APPLE__) && defined(__MACH__) + now = (uint64_t) int64_muldiv((int64_t)mach_absolute_time(), (int64_t)_ecs_os_time_osx_timebase.numer, (int64_t)_ecs_os_time_osx_timebase.denom); + #else + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + now = ((uint64_t)ts.tv_sec * 1000000000 + (uint64_t)ts.tv_nsec); + #endif -static ECS_MOVE(EcsEnum, dst, src, { - dtor_enum(dst); - dst->constants = src->constants; - src->constants = NULL; -}) + return now; +} -static ECS_DTOR(EcsEnum, ptr, { dtor_enum(ptr); }) +void flecs_os_time_sleep( + int32_t sec, + int32_t nanosec) +{ +#ifndef _WIN32 + struct timespec sleepTime; + ecs_assert(sec >= 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(nanosec >= 0, ECS_INTERNAL_ERROR, NULL); + sleepTime.tv_sec = sec; + sleepTime.tv_nsec = nanosec; + if (nanosleep(&sleepTime, NULL)) { + ecs_err("nanosleep failed"); + } +#else + HANDLE timer; + LARGE_INTEGER ft; -/* EcsBitmask lifecycle */ + ft.QuadPart = -((int64_t)sec * 10000000 + (int64_t)nanosec / 100); -static void dtor_bitmask( - EcsBitmask *ptr) -{ - ecs_map_iter_t it = ecs_map_iter(ptr->constants); - ecs_bitmask_constant_t *c; - while ((c = ecs_map_next(&it, ecs_bitmask_constant_t, NULL))) { - ecs_os_free((char*)c->name); - } - ecs_map_free(ptr->constants); + timer = CreateWaitableTimer(NULL, TRUE, NULL); + SetWaitableTimer(timer, &ft, 0, NULL, NULL, 0); + WaitForSingleObject(timer, INFINITE); + CloseHandle(timer); +#endif } -static ECS_COPY(EcsBitmask, dst, src, { - dtor_bitmask(dst); +#if defined(_WIN32) - dst->constants = ecs_map_copy(src->constants); - ecs_assert(ecs_map_count(dst->constants) == ecs_map_count(src->constants), - ECS_INTERNAL_ERROR, NULL); +static ULONG win32_current_resolution; - ecs_map_iter_t it = ecs_map_iter(dst->constants); - ecs_bitmask_constant_t *c; - while ((c = ecs_map_next(&it, ecs_bitmask_constant_t, NULL))) { - c->name = ecs_os_strdup(c->name); +void flecs_increase_timer_resolution(bool enable) +{ + HMODULE hntdll = GetModuleHandle((LPCTSTR)"ntdll.dll"); + if (!hntdll) { + return; } -}) - -static ECS_MOVE(EcsBitmask, dst, src, { - dtor_bitmask(dst); - dst->constants = src->constants; - src->constants = NULL; -}) -static ECS_DTOR(EcsBitmask, ptr, { dtor_bitmask(ptr); }) - - -/* Type initialization */ + LONG (__stdcall *pNtSetTimerResolution)( + ULONG desired, BOOLEAN set, ULONG * current); -static -int init_type( - ecs_world_t *world, - ecs_entity_t type, - ecs_type_kind_t kind) -{ - ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(type != 0, ECS_INTERNAL_ERROR, NULL); + pNtSetTimerResolution = (LONG(__stdcall*)(ULONG, BOOLEAN, ULONG*)) + GetProcAddress(hntdll, "NtSetTimerResolution"); - EcsMetaType *meta_type = ecs_get_mut(world, type, EcsMetaType, NULL); - if (meta_type->kind && meta_type->kind != kind) { - ecs_err("type '%s' reregistered with different kind", - ecs_get_name(world, type)); - return -1; + if(!pNtSetTimerResolution) { + return; } - meta_type->kind = kind; - ecs_modified(world, type, EcsMetaType); + ULONG current, resolution = 10000; /* 1 ms */ - return 0; -} + if (!enable && win32_current_resolution) { + pNtSetTimerResolution(win32_current_resolution, 0, ¤t); + win32_current_resolution = 0; + return; + } else if (!enable) { + return; + } -static -int init_component( - ecs_world_t *world, - ecs_entity_t type, - ecs_size_t size, - ecs_size_t alignment) -{ - ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(type != 0, ECS_INTERNAL_ERROR, NULL); - ecs_assert(size != 0, ECS_INTERNAL_ERROR, NULL); - ecs_assert(alignment != 0, ECS_INTERNAL_ERROR, NULL); + if (resolution == win32_current_resolution) { + return; + } - EcsComponent *component = ecs_get_mut(world, type, EcsComponent, NULL); - if (component->size && component->size != size) { - ecs_err("type '%s' reregistered with different size", - ecs_get_name(world, type)); - return -1; + if (win32_current_resolution) { + pNtSetTimerResolution(win32_current_resolution, 0, ¤t); } - if (component->alignment && component->alignment != alignment) { - ecs_err("type '%s' reregistered with different alignment", - ecs_get_name(world, type)); - return -1; + if (pNtSetTimerResolution(resolution, 1, ¤t)) { + /* Try setting a lower resolution */ + resolution *= 2; + if(pNtSetTimerResolution(resolution, 1, ¤t)) return; } - component->size = size; - component->alignment = alignment; - ecs_modified(world, type, EcsComponent); + win32_current_resolution = resolution; +} - return 0; +#else +void flecs_increase_timer_resolution(bool enable) +{ + (void)enable; + return; } +#endif + static -void set_struct_member( - ecs_member_t *member, - ecs_entity_t entity, +void term_error( + const ecs_world_t *world, + const ecs_term_t *term, const char *name, - ecs_entity_t type, - int32_t count) + const char *fmt, + ...) { - member->member = entity; - member->type = type; - member->count = count; + va_list args; + va_start(args, fmt); - if (!count) { - member->count = 1; - } + char *expr = ecs_term_str(world, term); + ecs_parser_errorv(name, expr, 0, fmt, args); + ecs_os_free(expr); - ecs_os_strset((char**)&member->name, name); + va_end(args); } static -int add_member_to_struct( - ecs_world_t *world, - ecs_entity_t type, - ecs_entity_t member, - EcsMember *m) +int finalize_term_set( + const ecs_world_t *world, + ecs_term_t *term, + ecs_term_id_t *identifier, + const char *name) { - ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(type != 0, ECS_INTERNAL_ERROR, NULL); - ecs_assert(member != 0, ECS_INTERNAL_ERROR, NULL); - ecs_assert(m != NULL, ECS_INTERNAL_ERROR, NULL); - - const char *name = ecs_get_name(world, member); - if (!name) { - char *path = ecs_get_fullpath(world, type); - ecs_err("member for struct '%s' does not have a name", path); - ecs_os_free(path); - return -1; - } - - if (!m->type) { - char *path = ecs_get_fullpath(world, member); - ecs_err("member '%s' does not have a type", path); - ecs_os_free(path); - return -1; - } - - if (ecs_get_typeid(world, m->type) == 0) { - char *path = ecs_get_fullpath(world, member); - char *ent_path = ecs_get_fullpath(world, m->type); - ecs_err("member '%s.type' is '%s' which is not a type", path, ent_path); - ecs_os_free(path); - ecs_os_free(ent_path); - return -1; + if (identifier->set.mask & EcsParent) { + identifier->set.mask |= EcsSuperSet; + identifier->set.relation = EcsChildOf; } - EcsStruct *s = ecs_get_mut(world, type, EcsStruct, NULL); - ecs_assert(s != NULL, ECS_INTERNAL_ERROR, NULL); - - /* First check if member is already added to struct */ - ecs_member_t *members = ecs_vector_first(s->members, ecs_member_t); - int32_t i, count = ecs_vector_count(s->members); - for (i = 0; i < count; i ++) { - if (members[i].member == member) { - set_struct_member(&members[i], member, name, m->type, m->count); - break; + /* Default relation for superset/subset is EcsIsA */ + if (identifier->set.mask & (EcsSuperSet|EcsSubSet)) { + if (!identifier->set.relation) { + identifier->set.relation = EcsIsA; } - } - - /* If member wasn't added yet, add a new element to vector */ - if (i == count) { - ecs_member_t *elem = ecs_vector_add(&s->members, ecs_member_t); - elem->name = NULL; - set_struct_member(elem, member, name, m->type, m->count); - - /* Reobtain members array in case it was reallocated */ - members = ecs_vector_first(s->members, ecs_member_t); - count ++; - } - - /* Compute member offsets and size & alignment of struct */ - ecs_size_t size = 0; - ecs_size_t alignment = 0; - - for (i = 0; i < count; i ++) { - ecs_member_t *elem = &members[i]; - - ecs_assert(elem->name != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(elem->type != 0, ECS_INTERNAL_ERROR, NULL); - /* Get component of member type to get its size & alignment */ - const EcsComponent *mbr_comp = ecs_get(world, elem->type, EcsComponent); - if (!mbr_comp) { - char *path = ecs_get_fullpath(world, member); - ecs_err("member '%s' is not a type", path); - ecs_os_free(path); - return -1; + if (!(identifier->set.mask & EcsSelf)) { + if (!identifier->set.min_depth) { + identifier->set.min_depth = 1; + } } - - ecs_size_t member_size = mbr_comp->size; - ecs_size_t member_alignment = mbr_comp->alignment; - - if (!member_size || !member_alignment) { - char *path = ecs_get_fullpath(world, member); - ecs_err("member '%s' has 0 size/alignment"); - ecs_os_free(path); + } else { + if (identifier->set.min_depth > 0) { + term_error(world, term, name, + "min depth cannnot be non-zero for Self term"); return -1; } - - member_size *= elem->count; - size = ECS_ALIGN(size, member_alignment); - elem->size = member_size; - elem->offset = size; - - size += member_size; - - if (member_alignment > alignment) { - alignment = member_alignment; + if (identifier->set.max_depth > 1) { + term_error(world, term, name, + "max depth cannnot be larger than 1 for Self term"); + return -1; } - } - - if (size == 0) { - ecs_err("struct '%s' has 0 size", ecs_get_name(world, type)); - return -1; - } - - if (alignment == 0) { - ecs_err("struct '%s' has 0 alignment", ecs_get_name(world, type)); - return -1; - } - /* Align struct size to struct alignment */ - size = ECS_ALIGN(size, alignment); - - ecs_modified(world, type, EcsStruct); - - /* Overwrite component size & alignment */ - if (type != ecs_id(EcsComponent)) { - EcsComponent *comp = ecs_get_mut(world, type, EcsComponent, NULL); - comp->size = size; - comp->alignment = alignment; - ecs_modified(world, type, EcsComponent); + identifier->set.max_depth = 1; } - /* Do this last as it triggers the update of EcsMetaTypeSerialized */ - if (init_type(world, type, EcsStructType)) { + if ((identifier->set.mask != EcsNothing) && + (identifier->set.mask & EcsNothing)) + { + term_error(world, term, name, "invalid Nothing in set mask"); return -1; } - /* If current struct is also a member, assign to itself */ - if (ecs_has(world, type, EcsMember)) { - EcsMember *type_mbr = ecs_get_mut(world, type, EcsMember, NULL); - ecs_assert(type_mbr != NULL, ECS_INTERNAL_ERROR, NULL); - - type_mbr->type = type; - type_mbr->count = 1; - - ecs_modified(world, type, EcsMember); - } - return 0; } static -int add_constant_to_enum( - ecs_world_t *world, - ecs_entity_t type, - ecs_entity_t e, - ecs_id_t constant_id) +int finalize_term_var( + const ecs_world_t *world, + ecs_term_t *term, + ecs_term_id_t *identifier, + const char *name) { - EcsEnum *ptr = ecs_get_mut(world, type, EcsEnum, NULL); - - /* Remove constant from map if it was already added */ - ecs_map_iter_t it = ecs_map_iter(ptr->constants); - ecs_enum_constant_t *c; - ecs_map_key_t key; - while ((c = ecs_map_next(&it, ecs_enum_constant_t, &key))) { - if (c->constant == e) { - ecs_os_free((char*)c->name); - ecs_map_remove(ptr->constants, key); + if (identifier->var == EcsVarDefault) { + const char *var = ecs_identifier_is_var(identifier->name); + if (ecs_identifier_is_var(identifier->name)) { + char *var_id = ecs_os_strdup(var); + ecs_os_free(identifier->name); + identifier->name = var_id; + identifier->var = EcsVarIsVariable; } } - /* Check if constant sets explicit value */ - int32_t value = 0; - bool value_set = false; - if (ecs_id_is_pair(constant_id)) { - if (ecs_pair_object(world, constant_id) != ecs_id(ecs_i32_t)) { - char *path = ecs_get_fullpath(world, e); - ecs_err("expected i32 type for enum constant '%s'", path); - ecs_os_free(path); - return -1; - } - - const int32_t *value_ptr = ecs_get_pair_object( - world, e, EcsConstant, ecs_i32_t); - ecs_assert(value_ptr != NULL, ECS_INTERNAL_ERROR, NULL); - value = *value_ptr; - value_set = true; + if (identifier->var == EcsVarDefault && identifier->set.mask != EcsNothing){ + identifier->var = EcsVarIsEntity; } - /* Make sure constant value doesn't conflict if set / find the next value */ - it = ecs_map_iter(ptr->constants); - while ((c = ecs_map_next(&it, ecs_enum_constant_t, &key))) { - if (value_set) { - if (c->value == value) { - char *path = ecs_get_fullpath(world, e); - ecs_err("conflicting constant value for '%s' (other is '%s')", - path, c->name); - ecs_os_free(path); - return -1; - } + if (!identifier->name) { + return 0; + } + + if (identifier->var != EcsVarIsVariable) { + if (ecs_identifier_is_0(identifier->name)) { + identifier->entity = 0; } else { - if (c->value >= value) { - value = c->value + 1; + ecs_entity_t e = ecs_lookup_symbol(world, identifier->name, true); + if (!e) { + term_error(world, term, name, + "unresolved identifier '%s'", identifier->name); + return -1; } + + identifier->entity = e; } } - if (!ptr->constants) { - ptr->constants = ecs_map_new(ecs_enum_constant_t, 1); + if ((identifier->set.mask == EcsNothing) && + (identifier->var != EcsVarDefault)) + { + term_error(world, term, name, "Invalid Nothing with entity"); + return -1; } - c = ecs_map_ensure(ptr->constants, ecs_enum_constant_t, value); - c->name = ecs_os_strdup(ecs_get_name(world, e)); - c->value = value; - c->constant = e; - - ecs_i32_t *cptr = ecs_get_mut_pair_object( - world, e, EcsConstant, ecs_i32_t, NULL); - ecs_assert(cptr != NULL, ECS_INTERNAL_ERROR, NULL); - cptr[0] = value; + return 0; +} +static +int finalize_term_identifier( + const ecs_world_t *world, + ecs_term_t *term, + ecs_term_id_t *identifier, + const char *name) +{ + if (finalize_term_set(world, term, identifier, name)) { + return -1; + } + if (finalize_term_var(world, term, identifier, name)) { + return -1; + } return 0; } static -int add_constant_to_bitmask( - ecs_world_t *world, - ecs_entity_t type, - ecs_entity_t e, - ecs_id_t constant_id) +bool term_can_inherit( + ecs_term_t *term) { - EcsBitmask *ptr = ecs_get_mut(world, type, EcsBitmask, NULL); - - /* Remove constant from map if it was already added */ - ecs_map_iter_t it = ecs_map_iter(ptr->constants); - ecs_bitmask_constant_t *c; - ecs_map_key_t key; - while ((c = ecs_map_next(&it, ecs_bitmask_constant_t, &key))) { - if (c->constant == e) { - ecs_os_free((char*)c->name); - ecs_map_remove(ptr->constants, key); - } + /* Hardcoded components that can't be inherited. TODO: replace with + * relationship property. */ + if (term->pred.entity == EcsChildOf || + (term->id == ecs_pair(ecs_id(EcsIdentifier), EcsName)) || + (term->id == EcsPrefab) || + (term->id == EcsDisabled)) + { + return false; } + return true; +} - /* Check if constant sets explicit value */ - uint32_t value = 1; - if (ecs_id_is_pair(constant_id)) { - if (ecs_pair_object(world, constant_id) != ecs_id(ecs_u32_t)) { - char *path = ecs_get_fullpath(world, e); - ecs_err("expected u32 type for bitmask constant '%s'", path); - ecs_os_free(path); - return -1; +static +ecs_entity_t term_id_entity( + const ecs_world_t *world, + ecs_term_id_t *term_id) +{ + if (term_id->entity && term_id->entity != EcsThis && + term_id->entity != EcsWildcard) + { + if (!(term_id->entity & ECS_ROLE_MASK)) { + return term_id->entity; + } else { + return 0; + } + } else if (term_id->name) { + if (term_id->var == EcsVarIsEntity || + (term_id->var == EcsVarDefault && + !ecs_identifier_is_var(term_id->name))) + { + ecs_entity_t e = ecs_lookup_fullpath(world, term_id->name); + if (e != EcsWildcard && e != EcsThis) { + return e; + } + return 0; + } else { + return 0; } - - const uint32_t *value_ptr = ecs_get_pair_object( - world, e, EcsConstant, ecs_u32_t); - ecs_assert(value_ptr != NULL, ECS_INTERNAL_ERROR, NULL); - value = *value_ptr; } else { - value = 1u << (ecs_u32_t)ecs_map_count(ptr->constants); + return 0; } +} - /* Make sure constant value doesn't conflict */ - it = ecs_map_iter(ptr->constants); - while ((c = ecs_map_next(&it, ecs_bitmask_constant_t, &key))) { - if (c->value == value) { - char *path = ecs_get_fullpath(world, e); - ecs_err("conflicting constant value for '%s' (other is '%s')", - path, c->name); - ecs_os_free(path); - return -1; - } +static +int finalize_term_vars( + const ecs_world_t *world, + ecs_term_t *term, + const char *name) +{ + if (finalize_term_var(world, term, &term->pred, name)) { + return -1; } - - if (!ptr->constants) { - ptr->constants = ecs_map_new(ecs_bitmask_constant_t, 1); + if (finalize_term_var(world, term, &term->subj, name)) { + return -1; + } + if (finalize_term_var(world, term, &term->obj, name)) { + return -1; } - - c = ecs_map_ensure(ptr->constants, ecs_bitmask_constant_t, value); - c->name = ecs_os_strdup(ecs_get_name(world, e)); - c->value = value; - c->constant = e; - - ecs_u32_t *cptr = ecs_get_mut_pair_object( - world, e, EcsConstant, ecs_u32_t, NULL); - ecs_assert(cptr != NULL, ECS_INTERNAL_ERROR, NULL); - cptr[0] = value; - return 0; } static -void set_primitive(ecs_iter_t *it) { - ecs_world_t *world = it->world; - EcsPrimitive *type = ecs_term(it, EcsPrimitive, 1); +int finalize_term_identifiers( + const ecs_world_t *world, + ecs_term_t *term, + const char *name) +{ + /* By default select subsets for predicates. For example, when the term + * matches "Tree", also include "Oak", "Pine", "Elm". */ + if (term->pred.set.mask == EcsDefaultSet) { + ecs_entity_t e = term_id_entity(world, &term->pred); - int i, count = it->count; - for (i = 0; i < count; i ++) { - ecs_entity_t e = it->entities[i]; - switch(type->kind) { - case EcsBool: - init_component(world, e, - ECS_SIZEOF(bool), ECS_ALIGNOF(bool)); - init_type(world, e, EcsPrimitiveType); - break; - case EcsChar: - init_component(world, e, - ECS_SIZEOF(char), ECS_ALIGNOF(char)); - init_type(world, e, EcsPrimitiveType); - break; - case EcsByte: - init_component(world, e, - ECS_SIZEOF(bool), ECS_ALIGNOF(bool)); - init_type(world, e, EcsPrimitiveType); - break; - case EcsU8: - init_component(world, e, - ECS_SIZEOF(uint8_t), ECS_ALIGNOF(uint8_t)); - init_type(world, e, EcsPrimitiveType); - break; - case EcsU16: - init_component(world, e, - ECS_SIZEOF(uint16_t), ECS_ALIGNOF(uint16_t)); - init_type(world, e, EcsPrimitiveType); - break; - case EcsU32: - init_component(world, e, - ECS_SIZEOF(uint32_t), ECS_ALIGNOF(uint32_t)); - init_type(world, e, EcsPrimitiveType); - break; - case EcsU64: - init_component(world, e, - ECS_SIZEOF(uint64_t), ECS_ALIGNOF(uint64_t)); - init_type(world, e, EcsPrimitiveType); - break; - case EcsI8: - init_component(world, e, - ECS_SIZEOF(int8_t), ECS_ALIGNOF(int8_t)); - init_type(world, e, EcsPrimitiveType); - break; - case EcsI16: - init_component(world, e, - ECS_SIZEOF(int16_t), ECS_ALIGNOF(int16_t)); - init_type(world, e, EcsPrimitiveType); - break; - case EcsI32: - init_component(world, e, - ECS_SIZEOF(int32_t), ECS_ALIGNOF(int32_t)); - init_type(world, e, EcsPrimitiveType); - break; - case EcsI64: - init_component(world, e, - ECS_SIZEOF(int64_t), ECS_ALIGNOF(int64_t)); - init_type(world, e, EcsPrimitiveType); - break; - case EcsF32: - init_component(world, e, - ECS_SIZEOF(float), ECS_ALIGNOF(float)); - init_type(world, e, EcsPrimitiveType); - break; - case EcsF64: - init_component(world, e, - ECS_SIZEOF(double), ECS_ALIGNOF(double)); - init_type(world, e, EcsPrimitiveType); - break; - case EcsUPtr: - init_component(world, e, - ECS_SIZEOF(uintptr_t), ECS_ALIGNOF(uintptr_t)); - init_type(world, e, EcsPrimitiveType); - break; - case EcsIPtr: - init_component(world, e, - ECS_SIZEOF(intptr_t), ECS_ALIGNOF(intptr_t)); - init_type(world, e, EcsPrimitiveType); - break; - case EcsString: - init_component(world, e, - ECS_SIZEOF(char*), ECS_ALIGNOF(char*)); - init_type(world, e, EcsPrimitiveType); - break; - case EcsEntity: - init_component(world, e, - ECS_SIZEOF(ecs_entity_t), ECS_ALIGNOF(ecs_entity_t)); - init_type(world, e, EcsPrimitiveType); - break; + if (e && !ecs_has_id(world, e, EcsFinal)) { + term->pred.set.mask = EcsSelf|EcsSubSet; + } else { + /* If predicate is final, don't search subsets */ + term->pred.set.mask = EcsSelf; } } -} -static -void set_member(ecs_iter_t *it) { - ecs_world_t *world = it->world; - EcsMember *member = ecs_term(it, EcsMember, 1); + /* By default select supersets for subjects. For example, when an entity has + * (IsA, SpaceShip), also search the components of SpaceShip. */ + if (term->subj.set.mask == EcsDefaultSet) { + term->subj.set.mask = EcsSelf|EcsSuperSet; + } - int i, count = it->count; - for (i = 0; i < count; i ++) { - ecs_entity_t e = it->entities[i]; - ecs_entity_t parent = ecs_get_object(world, e, EcsChildOf, 0); - if (!parent) { - ecs_err("missing parent for member '%s'", ecs_get_name(world, e)); - continue; - } + /* By default select self for objects. */ + if (term->obj.set.mask == EcsDefaultSet) { + term->obj.set.mask = EcsSelf; + } - add_member_to_struct(world, parent, e, member); + if (finalize_term_set(world, term, &term->pred, name)) { + return -1; + } + if (finalize_term_set(world, term, &term->subj, name)) { + return -1; + } + if (finalize_term_set(world, term, &term->obj, name)) { + return -1; } -} -static -void add_enum(ecs_iter_t *it) { - ecs_world_t *world = it->world; + if (term->pred.set.mask & EcsNothing) { + term_error(world, term, name, + "invalid Nothing value for predicate set mask"); + return -1; + } - int i, count = it->count; - for (i = 0; i < count; i ++) { - ecs_entity_t e = it->entities[i]; - - if (init_component( - world, e, ECS_SIZEOF(ecs_i32_t), ECS_ALIGNOF(ecs_i32_t))) - { - continue; - } + if (term->obj.set.mask & EcsNothing) { + term_error(world, term, name, + "invalid Nothing value for object set mask"); + return -1; + } - if (init_type(world, e, EcsEnumType)) { - continue; - } + if (!(term->subj.set.mask & EcsNothing) && + !term->subj.entity && + term->subj.var == EcsVarIsEntity) + { + term->subj.entity = EcsThis; + } + + if (term->pred.entity == EcsThis) { + term->pred.var = EcsVarIsVariable; + } + if (term->subj.entity == EcsThis) { + term->subj.var = EcsVarIsVariable; + } + if (term->obj.entity == EcsThis) { + term->obj.var = EcsVarIsVariable; } + + return 0; } static -void add_bitmask(ecs_iter_t *it) { - ecs_world_t *world = it->world; - - int i, count = it->count; - for (i = 0; i < count; i ++) { - ecs_entity_t e = it->entities[i]; - - if (init_component( - world, e, ECS_SIZEOF(ecs_u32_t), ECS_ALIGNOF(ecs_u32_t))) - { - continue; - } - - if (init_type(world, e, EcsBitmaskType)) { - continue; - } +ecs_entity_t entity_from_identifier( + const ecs_term_id_t *identifier) +{ + if (identifier->var == EcsVarDefault) { + return 0; + } else if (identifier->var == EcsVarIsEntity) { + return identifier->entity; + } else if (identifier->var == EcsVarIsVariable) { + return EcsWildcard; + } else { + /* This should've been caught earlier */ + ecs_abort(ECS_INTERNAL_ERROR, NULL); } } static -void add_constant(ecs_iter_t *it) { - ecs_world_t *world = it->world; - - int i, count = it->count; - for (i = 0; i < count; i ++) { - ecs_entity_t e = it->entities[i]; - ecs_entity_t parent = ecs_get_object(world, e, EcsChildOf, 0); - if (!parent) { - ecs_err("missing parent for constant '%s'", ecs_get_name(world, e)); - continue; - } +int finalize_term_id( + const ecs_world_t *world, + ecs_term_t *term, + const char *name) +{ + ecs_entity_t pred = entity_from_identifier(&term->pred); + ecs_entity_t obj = entity_from_identifier(&term->obj); + ecs_id_t role = term->role; - if (ecs_has(world, parent, EcsEnum)) { - add_constant_to_enum(world, parent, e, it->event_id); - } else if (ecs_has(world, parent, EcsBitmask)) { - add_constant_to_bitmask(world, parent, e, it->event_id); + if (ECS_HAS_ROLE(pred, PAIR)) { + if (obj) { + term_error(world, term, name, + "cannot set term.pred to a pair and term.obj at the same time"); + return -1; } - } -} -static -void set_array(ecs_iter_t *it) { - ecs_world_t *world = it->world; - EcsArray *array = ecs_term(it, EcsArray, 1); + obj = ECS_PAIR_OBJECT(pred); + pred = ECS_PAIR_RELATION(pred); - int i, count = it->count; - for (i = 0; i < count; i ++) { - ecs_entity_t e = it->entities[i]; - ecs_entity_t elem_type = array[i].type; - int32_t elem_count = array[i].count; + term->pred.entity = pred; + term->obj.entity = obj; - if (!elem_type) { - ecs_err("array '%s' has no element type", ecs_get_name(world, e)); - continue; + if (finalize_term_identifier(world, term, &term->obj, name)) { + return -1; } + } - if (!elem_count) { - ecs_err("array '%s' has size 0", ecs_get_name(world, e)); - continue; - } + if (!obj && role != ECS_PAIR) { + term->id = pred | role; + } else { + if (role) { + if (role && role != ECS_PAIR && role != ECS_CASE) { + term_error(world, term, name, "invalid role for pair"); + return -1; + } - const EcsComponent *elem_ptr = ecs_get(world, elem_type, EcsComponent); - if (init_component( - world, e, elem_ptr->size * elem_count, elem_ptr->alignment)) - { - continue; + term->role = role; + } else { + term->role = ECS_PAIR; } - if (init_type(world, e, EcsArrayType)) { - continue; - } + term->id = term->role | ecs_entity_t_comb(obj, pred); } + + return 0; } static -void set_vector(ecs_iter_t *it) { - ecs_world_t *world = it->world; - EcsVector *array = ecs_term(it, EcsVector, 1); +int populate_from_term_id( + const ecs_world_t *world, + ecs_term_t *term, + const char *name) +{ + ecs_entity_t pred = 0; + ecs_entity_t obj = 0; + ecs_id_t role = term->id & ECS_ROLE_MASK; - int i, count = it->count; - for (i = 0; i < count; i ++) { - ecs_entity_t e = it->entities[i]; - ecs_entity_t elem_type = array[i].type; + if (!role && term->role) { + role = term->role; + term->id |= role; + } - if (!elem_type) { - ecs_err("vector '%s' has no element type", ecs_get_name(world, e)); - continue; - } + if (term->role && term->role != role) { + term_error(world, term, name, "mismatch between term.id & term.role"); + return -1; + } - if (init_component(world, e, - ECS_SIZEOF(ecs_vector_t*), ECS_ALIGNOF(ecs_vector_t*))) - { - continue; - } + term->role = role; - if (init_type(world, e, EcsVectorType)) { - continue; + if (ECS_HAS_ROLE(term->id, PAIR)) { + pred = ECS_PAIR_RELATION(term->id); + obj = ECS_PAIR_OBJECT(term->id); + + if (!pred) { + term_error(world, term, name, "missing predicate in term.id pair"); + return -1; + } + if (!obj) { + if (pred != EcsChildOf) { + term_error(world, term, name, "missing object in term.id pair"); + return -1; + } + } + } else { + pred = term->id & ECS_COMPONENT_MASK; + if (!pred) { + term_error(world, term, name, "missing predicate in term.id"); + return -1; } } -} - -static -void ecs_meta_type_init_default_ctor(ecs_iter_t *it) { - ecs_world_t *world = it->world; - - int i; - for (i = 0; i < it->count; i ++) { - ecs_entity_t type = it->entities[i]; - /* If component has no component actions (which is typical if a type is - * created with reflection data) make sure its values are always - * initialized with zero. This prevents the injection of invalid data - * through generic APIs after adding a component without setting it. */ - if (!ecs_component_has_actions(world, type)) { - ecs_set_component_actions_w_id(world, type, - &(EcsComponentLifecycle){ - .ctor = ecs_default_ctor - }); + ecs_entity_t term_pred = entity_from_identifier(&term->pred); + if (term_pred) { + if (term_pred != pred) { + term_error(world, term, name, + "mismatch between term.id and term.pred"); + return -1; + } + } else { + term->pred.entity = pred; + if (finalize_term_identifier(world, term, &term->pred, name)) { + return -1; + } + } + + ecs_entity_t term_obj = entity_from_identifier(&term->obj); + if (term_obj) { + if (ecs_entity_t_lo(term_obj) != obj) { + term_error(world, term, name, + "mismatch between term.id and term.obj"); + return -1; + } + } else { + term->obj.entity = obj; + if (finalize_term_identifier(world, term, &term->obj, name)) { + return -1; } } + + return 0; } static -void member_on_set( - ecs_world_t *world, - ecs_entity_t component, - const ecs_entity_t *entity_ptr, - void *ptr, - size_t size, - int32_t count, - void *ctx) +int verify_term_consistency( + const ecs_world_t *world, + const ecs_term_t *term, + const char *name) { - (void)world; - (void)component; - (void)entity_ptr; - (void)size; - (void)count; - (void)ctx; + ecs_entity_t pred = entity_from_identifier(&term->pred); + ecs_entity_t obj = entity_from_identifier(&term->obj); + ecs_id_t role = term->role; + ecs_id_t id = term->id; + bool wildcard = pred == EcsWildcard || obj == EcsWildcard; - EcsMember *mbr = ptr; - if (!mbr->count) { - mbr->count = 1; + if (obj && (!role || (role != ECS_PAIR && role != ECS_CASE))) { + term_error(world, term, name, + "invalid role for term with pair (expected ECS_PAIR)"); + return -1; } -} - -void FlecsMetaImport( - ecs_world_t *world) -{ - ECS_MODULE(world, FlecsMeta); - ecs_set_name_prefix(world, "Ecs"); + if (role == ECS_CASE && !obj) { + term_error(world, term, name, + "missing object for term with ECS_CASE role"); + return -1; + } - flecs_bootstrap_component(world, EcsMetaType); - flecs_bootstrap_component(world, EcsMetaTypeSerialized); - flecs_bootstrap_component(world, EcsPrimitive); - flecs_bootstrap_component(world, EcsEnum); - flecs_bootstrap_component(world, EcsBitmask); - flecs_bootstrap_component(world, EcsMember); - flecs_bootstrap_component(world, EcsStruct); - flecs_bootstrap_component(world, EcsArray); - flecs_bootstrap_component(world, EcsVector); + if (!pred) { + term_error(world, term, name, "missing predicate for term"); + return -1; + } - flecs_bootstrap_tag(world, EcsConstant); + if (role != (id & ECS_ROLE_MASK)) { + term_error(world, term, name, "mismatch between term.role & term.id"); + return -1; + } - ecs_set_component_actions(world, EcsMetaType, { .ctor = ecs_default_ctor }); + if (obj && !ECS_HAS_ROLE(id, PAIR) && !ECS_HAS_ROLE(id, CASE)) { + term_error(world, term, name, "term has object but id is not a pair"); + return -1; + } - ecs_set_component_actions(world, EcsMetaTypeSerialized, { - .ctor = ecs_default_ctor, - .move = ecs_move(EcsMetaTypeSerialized), - .copy = ecs_copy(EcsMetaTypeSerialized), - .dtor = ecs_dtor(EcsMetaTypeSerialized) - }); + if (ECS_HAS_ROLE(id, PAIR) || ECS_HAS_ROLE(id, CASE)) { + if (!wildcard) { + role = ECS_ROLE_MASK & id; + if (id != (role | ecs_entity_t_comb( + term->obj.entity, term->pred.entity))) + { + char *id_str = ecs_id_str(world, ecs_pair(pred, obj)); + term_error(world, term, name, + "term id does not match pred/obj (%s)", id_str); + ecs_os_free(id_str); + return -1; + } + } + } else if (term->pred.entity != (id & ECS_COMPONENT_MASK)) { + if (!wildcard) { + char *pred_str = ecs_get_fullpath(world, term->pred.entity); + term_error(world, term, name, "term id does not match pred '%s'", + pred_str); + ecs_os_free(pred_str); + return -1; + } + } - ecs_set_component_actions(world, EcsStruct, { - .ctor = ecs_default_ctor, - .move = ecs_move(EcsStruct), - .copy = ecs_copy(EcsStruct), - .dtor = ecs_dtor(EcsStruct) - }); + return 0; +} - ecs_set_component_actions(world, EcsMember, { - .ctor = ecs_default_ctor, - .on_set = member_on_set - }); +bool ecs_identifier_is_0( + const char *id) +{ + return id[0] == '0' && !id[1]; +} - ecs_set_component_actions(world, EcsEnum, { - .ctor = ecs_default_ctor, - .move = ecs_move(EcsEnum), - .copy = ecs_copy(EcsEnum), - .dtor = ecs_dtor(EcsEnum) - }); +const char* ecs_identifier_is_var( + const char *id) +{ + if (!id) { + return NULL; + } - ecs_set_component_actions(world, EcsBitmask, { - .ctor = ecs_default_ctor, - .move = ecs_move(EcsBitmask), - .copy = ecs_copy(EcsBitmask), - .dtor = ecs_dtor(EcsBitmask) - }); + /* Variable identifiers cannot start with a number */ + if (isdigit(id[0])) { + return NULL; + } - /* Register triggers to finalize type information from component data */ - ecs_trigger_init(world, &(ecs_trigger_desc_t) { - .term.id = ecs_id(EcsPrimitive), - .term.subj.set.mask = EcsSelf, - .events = {EcsOnSet}, - .callback = set_primitive - }); + /* Identifiers that start with _ are variables */ + if (id[0] == '_') { + return &id[1]; + } - ecs_trigger_init(world, &(ecs_trigger_desc_t) { - .term.id = ecs_id(EcsMember), - .term.subj.set.mask = EcsSelf, - .events = {EcsOnSet}, - .callback = set_member - }); + /* Identifiers that have a single uppercase character are variables */ + if (ecs_os_strlen(id) == 1 && isupper(id[0])) { + return id; + } - ecs_trigger_init(world, &(ecs_trigger_desc_t) { - .term.id = ecs_id(EcsEnum), - .term.subj.set.mask = EcsSelf, - .events = {EcsOnAdd}, - .callback = add_enum - }); + return NULL; +} - ecs_trigger_init(world, &(ecs_trigger_desc_t) { - .term.id = ecs_id(EcsBitmask), - .term.subj.set.mask = EcsSelf, - .events = {EcsOnAdd}, - .callback = add_bitmask - }); +bool ecs_id_match( + ecs_id_t id, + ecs_id_t pattern) +{ + if (id == pattern) { + return true; + } - ecs_trigger_init(world, &(ecs_trigger_desc_t) { - .term.id = EcsConstant, - .term.subj.set.mask = EcsSelf, - .events = {EcsOnAdd}, - .callback = add_constant - }); + if (ECS_HAS_ROLE(pattern, PAIR)) { + if (!ECS_HAS_ROLE(id, PAIR)) { + return false; + } - ecs_trigger_init(world, &(ecs_trigger_desc_t) { - .term.id = ecs_pair(EcsConstant, EcsWildcard), - .term.subj.set.mask = EcsSelf, - .events = {EcsOnSet}, - .callback = add_constant - }); + ecs_entity_t id_rel = ECS_PAIR_RELATION(id); + ecs_entity_t id_obj = ECS_PAIR_OBJECT(id); + ecs_entity_t pattern_rel = ECS_PAIR_RELATION(pattern); + ecs_entity_t pattern_obj = ECS_PAIR_OBJECT(pattern); - ecs_trigger_init(world, &(ecs_trigger_desc_t) { - .term.id = ecs_id(EcsArray), - .term.subj.set.mask = EcsSelf, - .events = {EcsOnSet}, - .callback = set_array - }); + ecs_check(id_rel != 0, ECS_INVALID_PARAMETER, NULL); + ecs_check(id_obj != 0, ECS_INVALID_PARAMETER, NULL); - ecs_trigger_init(world, &(ecs_trigger_desc_t) { - .term.id = ecs_id(EcsVector), - .term.subj.set.mask = EcsSelf, - .events = {EcsOnSet}, - .callback = set_vector - }); + ecs_check(pattern_rel != 0, ECS_INVALID_PARAMETER, NULL); + ecs_check(pattern_obj != 0, ECS_INVALID_PARAMETER, NULL); + + if (pattern_rel == EcsWildcard) { + if (pattern_obj == EcsWildcard || pattern_obj == id_obj) { + return true; + } + } else if (pattern_obj == EcsWildcard) { + if (pattern_rel == id_rel) { + return true; + } + } + } else { + if ((id & ECS_ROLE_MASK) != (pattern & ECS_ROLE_MASK)) { + return false; + } - ecs_trigger_init(world, &(ecs_trigger_desc_t) { - .term.id = ecs_id(EcsMetaType), - .term.subj.set.mask = EcsSelf, - .events = {EcsOnSet}, - .callback = ecs_meta_type_serialized_init - }); + if ((ECS_COMPONENT_MASK & pattern) == EcsWildcard) { + return true; + } + } - ecs_trigger_init(world, &(ecs_trigger_desc_t) { - .term.id = ecs_id(EcsMetaType), - .term.subj.set.mask = EcsSelf, - .events = {EcsOnSet}, - .callback = ecs_meta_type_init_default_ctor - }); +error: + return false; +} - /* Initialize primitive types */ - #define ECS_PRIMITIVE(world, type, primitive_kind)\ - ecs_entity_init(world, &(ecs_entity_desc_t) {\ - .entity = ecs_id(ecs_##type##_t),\ - .name = #type });\ - ecs_set(world, ecs_id(ecs_##type##_t), EcsPrimitive, {\ - .kind = primitive_kind\ - }); +bool ecs_id_is_pair( + ecs_id_t id) +{ + return ECS_HAS_ROLE(id, PAIR); +} - ECS_PRIMITIVE(world, bool, EcsBool); - ECS_PRIMITIVE(world, char, EcsChar); - ECS_PRIMITIVE(world, byte, EcsByte); - ECS_PRIMITIVE(world, u8, EcsU8); - ECS_PRIMITIVE(world, u16, EcsU16); - ECS_PRIMITIVE(world, u32, EcsU32); - ECS_PRIMITIVE(world, u64, EcsU64); - ECS_PRIMITIVE(world, uptr, EcsUPtr); - ECS_PRIMITIVE(world, i8, EcsI8); - ECS_PRIMITIVE(world, i16, EcsI16); - ECS_PRIMITIVE(world, i32, EcsI32); - ECS_PRIMITIVE(world, i64, EcsI64); - ECS_PRIMITIVE(world, iptr, EcsIPtr); - ECS_PRIMITIVE(world, f32, EcsF32); - ECS_PRIMITIVE(world, f64, EcsF64); - ECS_PRIMITIVE(world, string, EcsString); - ECS_PRIMITIVE(world, entity, EcsEntity); +bool ecs_id_is_wildcard( + ecs_id_t id) +{ + if (id == EcsWildcard) { + return true; + } else if (ECS_HAS_ROLE(id, PAIR)) { + return ECS_PAIR_RELATION(id) == EcsWildcard || + ECS_PAIR_OBJECT(id) == EcsWildcard; + } - #undef ECS_PRIMITIVE + return false; +} - /* Set default child components */ - ecs_add_pair(world, ecs_id(EcsStruct), - EcsDefaultChildComponent, ecs_id(EcsMember)); +bool ecs_term_id_is_set( + const ecs_term_id_t *id) +{ + return id->entity != 0 || id->name != NULL; +} - ecs_add_pair(world, ecs_id(EcsMember), - EcsDefaultChildComponent, ecs_id(EcsMember)); +bool ecs_term_is_initialized( + const ecs_term_t *term) +{ + return term->id != 0 || ecs_term_id_is_set(&term->pred); +} - ecs_add_pair(world, ecs_id(EcsEnum), - EcsDefaultChildComponent, EcsConstant); +bool ecs_term_is_trivial( + const ecs_term_t *term) +{ + if (term->inout != EcsInOutDefault) { + return false; + } - ecs_add_pair(world, ecs_id(EcsBitmask), - EcsDefaultChildComponent, EcsConstant); + if (term->subj.entity != EcsThis) { + return false; + } - /* Initialize reflection data for meta components */ - ecs_entity_t type_kind = ecs_enum_init(world, &(ecs_enum_desc_t) { - .entity.name = "TypeKind", - .constants = { - {.name = "PrimitiveType"}, - {.name = "BitmaskType"}, - {.name = "EnumType"}, - {.name = "StructType"}, - {.name = "ArrayType"}, - {.name = "VectorType"} - } - }); + if (term->subj.set.mask && (term->subj.set.mask != EcsSelf)) { + return false; + } - ecs_struct_init(world, &(ecs_struct_desc_t) { - .entity.entity = ecs_id(EcsMetaType), - .members = { - {.name = (char*)"kind", .type = type_kind} - } - }); + if (term->oper != EcsAnd && term->oper != EcsAndFrom) { + return false; + } - ecs_entity_t primitive_kind = ecs_enum_init(world, &(ecs_enum_desc_t) { - .entity.name = "PrimitiveKind", - .constants = { - {.name = "Bool", 1}, - {.name = "Char"}, - {.name = "Byte"}, - {.name = "U8"}, - {.name = "U16"}, - {.name = "U32"}, - {.name = "U64"}, - {.name = "I8"}, - {.name = "I16"}, - {.name = "I32"}, - {.name = "I64"}, - {.name = "F32"}, - {.name = "F64"}, - {.name = "UPtr"}, - {.name = "IPtr"}, - {.name = "String"}, - {.name = "Entity"} - } - }); + if (term->name != NULL) { + return false; + } - ecs_struct_init(world, &(ecs_struct_desc_t) { - .entity.entity = ecs_id(EcsPrimitive), - .members = { - {.name = (char*)"kind", .type = primitive_kind} - } - }); + return true; +} - ecs_struct_init(world, &(ecs_struct_desc_t) { - .entity.entity = ecs_id(EcsMember), - .members = { - {.name = (char*)"type", .type = ecs_id(ecs_entity_t)}, - {.name = (char*)"count", .type = ecs_id(ecs_i32_t)} - } - }); +int ecs_term_finalize( + const ecs_world_t *world, + const char *name, + ecs_term_t *term) +{ + if (finalize_term_vars(world, term, name)) { + return -1; + } - ecs_struct_init(world, &(ecs_struct_desc_t) { - .entity.entity = ecs_id(EcsArray), - .members = { - {.name = (char*)"type", .type = ecs_id(ecs_entity_t)}, - {.name = (char*)"count", .type = ecs_id(ecs_i32_t)}, + if (!term->id) { + if (finalize_term_id(world, term, name)) { + return -1; } - }); - - ecs_struct_init(world, &(ecs_struct_desc_t) { - .entity.entity = ecs_id(EcsVector), - .members = { - {.name = (char*)"type", .type = ecs_id(ecs_entity_t)} + } else { + if (populate_from_term_id(world, term, name)) { + return -1; } - }); -} - -#endif - - -#ifdef FLECS_META + } -static -const char* op_kind_str( - ecs_meta_type_op_kind_t kind) -{ - switch(kind) { + if (finalize_term_identifiers(world, term, name)) { + return -1; + } - case EcsOpEnum: return "Enum"; - case EcsOpBitmask: return "Bitmask"; - case EcsOpArray: return "Array"; - case EcsOpVector: return "Vector"; - case EcsOpPush: return "Push"; - case EcsOpPop: return "Pop"; - case EcsOpPrimitive: return "Primitive"; - case EcsOpBool: return "Bool"; - case EcsOpChar: return "Char"; - case EcsOpByte: return "Byte"; - case EcsOpU8: return "U8"; - case EcsOpU16: return "U16"; - case EcsOpU32: return "U32"; - case EcsOpU64: return "U64"; - case EcsOpI8: return "I8"; - case EcsOpI16: return "I16"; - case EcsOpI32: return "I32"; - case EcsOpI64: return "I64"; - case EcsOpF32: return "F32"; - case EcsOpF64: return "F64"; - case EcsOpUPtr: return "UPtr"; - case EcsOpIPtr: return "IPtr"; - case EcsOpString: return "String"; - case EcsOpEntity: return "Entity"; - default: return "<< invalid kind >>"; + if (!term_can_inherit(term)) { + if (term->subj.set.relation == EcsIsA) { + term->subj.set.relation = 0; + term->subj.set.mask = EcsSelf; + } } -} -/* Get current scope */ -static -ecs_meta_scope_t* get_scope( - ecs_meta_cursor_t *cursor) -{ - ecs_check(cursor != NULL, ECS_INVALID_PARAMETER, NULL); - return &cursor->scope[cursor->depth]; -error: - return NULL; -} + if (verify_term_consistency(world, term, name)) { + return -1; + } -/* Get previous scope */ -static -ecs_meta_scope_t* get_prev_scope( - ecs_meta_cursor_t *cursor) -{ - ecs_check(cursor != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(cursor->depth > 0, ECS_INVALID_PARAMETER, NULL); - return &cursor->scope[cursor->depth - 1]; -error: - return NULL; + return 0; } -/* Get current operation for scope */ -static -ecs_meta_type_op_t* get_op( - ecs_meta_scope_t *scope) +ecs_term_t ecs_term_copy( + const ecs_term_t *src) { - return &scope->ops[scope->op_cur]; + ecs_term_t dst = *src; + dst.name = ecs_os_strdup(src->name); + dst.pred.name = ecs_os_strdup(src->pred.name); + dst.subj.name = ecs_os_strdup(src->subj.name); + dst.obj.name = ecs_os_strdup(src->obj.name); + return dst; } -/* Get component for type in current scope */ -static -const EcsComponent* get_component_ptr( - const ecs_world_t *world, - ecs_meta_scope_t *scope) +ecs_term_t ecs_term_move( + ecs_term_t *src) { - const EcsComponent *comp = scope->comp; - if (!comp) { - comp = scope->comp = ecs_get(world, scope->type, EcsComponent); - ecs_assert(comp != NULL, ECS_INTERNAL_ERROR, NULL); + if (src->move) { + ecs_term_t dst = *src; + src->name = NULL; + src->pred.name = NULL; + src->subj.name = NULL; + src->obj.name = NULL; + return dst; + } else { + return ecs_term_copy(src); } - return comp; } -/* Get size for type in current scope */ -static -ecs_size_t get_size( - const ecs_world_t *world, - ecs_meta_scope_t *scope) +void ecs_term_fini( + ecs_term_t *term) { - return get_component_ptr(world, scope)->size; + ecs_os_free(term->pred.name); + ecs_os_free(term->subj.name); + ecs_os_free(term->obj.name); + ecs_os_free(term->name); + + term->pred.name = NULL; + term->subj.name = NULL; + term->obj.name = NULL; + term->name = NULL; } -/* Get alignment for type in current scope */ -static -ecs_size_t get_alignment( +int ecs_filter_finalize( const ecs_world_t *world, - ecs_meta_scope_t *scope) + ecs_filter_t *f) { - return get_component_ptr(world, scope)->alignment; -} + int32_t i, term_count = f->term_count, actual_count = 0; + ecs_term_t *terms = f->terms; + bool is_or = false, prev_or = false; + int32_t filter_terms = 0; -static -int32_t get_elem_count( - ecs_meta_scope_t *scope) -{ - if (scope->vector) { - return ecs_vector_count(*(scope->vector)); + for (i = 0; i < term_count; i ++) { + ecs_term_t *term = &terms[i]; + + if (ecs_term_finalize(world, f->name, term)) { + return -1; + } + + is_or = term->oper == EcsOr; + actual_count += !(is_or && prev_or); + term->index = actual_count - 1; + prev_or = is_or; + + if (term->subj.entity == EcsThis) { + f->match_this = true; + if (term->subj.set.mask != EcsSelf) { + f->match_only_this = false; + } + } else { + f->match_only_this = false; + } + + if (term->id == EcsPrefab) { + f->match_prefab = true; + } + if (term->id == EcsDisabled) { + f->match_disabled = true; + } + + if (f->filter) { + term->inout = EcsInOutFilter; + } + + if (term->inout == EcsInOutFilter) { + filter_terms ++; + } + + if (term->oper != EcsNot || term->subj.entity != EcsThis) { + f->match_anything = false; + } } - ecs_meta_type_op_t *op = get_op(scope); - return op->count; + f->term_count_actual = actual_count; + + if (filter_terms == term_count) { + f->filter = true; + } + + return 0; } -/* Get pointer to current field/element */ +/* Implementation for iterable mixin */ static -ecs_meta_type_op_t* get_ptr( +void filter_iter_init( const ecs_world_t *world, - ecs_meta_scope_t *scope) + const ecs_poly_t *poly, + ecs_iter_t *iter, + ecs_term_t *filter) { - ecs_meta_type_op_t *op = get_op(scope); - ecs_size_t size = get_size(world, scope); + ecs_poly_assert(poly, ecs_filter_t); - if (scope->vector) { - ecs_size_t align = get_alignment(world, scope); - ecs_vector_set_min_count_t( - scope->vector, size, align, scope->elem_cur + 1); - scope->ptr = ecs_vector_first_t(*(scope->vector), size, align); + if (filter) { + iter[1] = ecs_filter_iter(world, (ecs_filter_t*)poly); + iter[0] = ecs_term_chain_iter(&iter[1], filter); + } else { + iter[0] = ecs_filter_iter(world, (ecs_filter_t*)poly); } - - return ECS_OFFSET(scope->ptr, size * scope->elem_cur + op->offset); } -static -int push_type( - const ecs_world_t *world, - ecs_meta_scope_t *scope, - ecs_entity_t type, - void *ptr) +int ecs_filter_init( + const ecs_world_t *stage, + ecs_filter_t *filter_out, + const ecs_filter_desc_t *desc) { - const EcsMetaTypeSerialized *ser = ecs_get( - world, type, EcsMetaTypeSerialized); - if (ser == NULL) { - char *str = ecs_id_str(world, type); - ecs_err("cannot open scope for entity '%s' which is not a type", str); - ecs_os_free(str); - return -1; - } + ecs_filter_t f; + ecs_poly_init(&f, ecs_filter_t); - scope[0] = (ecs_meta_scope_t) { - .type = type, - .ops = ecs_vector_first(ser->ops, ecs_meta_type_op_t), - .op_count = ecs_vector_count(ser->ops), - .ptr = ptr - }; + ecs_check(stage != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(filter_out != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL); - return 0; -} + const ecs_world_t *world = ecs_get_world(stage); -ecs_meta_cursor_t ecs_meta_cursor( - const ecs_world_t *world, - ecs_entity_t type, - void *ptr) -{ - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(type != 0, ECS_INVALID_PARAMETER, NULL); - ecs_check(ptr != NULL, ECS_INVALID_PARAMETER, NULL); + int i, term_count = 0; + ecs_term_t *terms = desc->terms_buffer; + const char *name = desc->name; + const char *expr = desc->expr; - ecs_meta_cursor_t result = { - .world = world, - .valid = true - }; + /* Temporarily set the fields to the values provided in desc, until the + * filter has been validated. */ + f.name = (char*)name; + f.expr = (char*)expr; + f.filter = desc->filter; + f.instanced = desc->instanced; + f.match_anything = true; - if (push_type(world, result.scope, type, ptr) != 0) { - result.valid = false; + if (terms) { + terms = desc->terms_buffer; + term_count = desc->terms_buffer_count; + } else { + terms = (ecs_term_t*)desc->terms; + for (i = 0; i < ECS_TERM_DESC_CACHE_SIZE; i ++) { + if (!ecs_term_is_initialized(&terms[i])) { + break; + } + + term_count ++; + } } - return result; -error: - return (ecs_meta_cursor_t){ 0 }; -} + /* Temporarily set array from desc to filter, until the filter has been + * validated. */ + f.terms = terms; + f.term_count = term_count; -void* ecs_meta_get_ptr( - ecs_meta_cursor_t *cursor) -{ - return get_ptr(cursor->world, get_scope(cursor)); -} + if (expr) { +#ifdef FLECS_PARSER + int32_t buffer_count = 0; -int ecs_meta_next( - ecs_meta_cursor_t *cursor) -{ - ecs_meta_scope_t *scope = get_scope(cursor); - ecs_meta_type_op_t *op = get_op(scope); + /* If terms have already been set, copy buffer to allocated one */ + if (terms && term_count) { + terms = ecs_os_memdup(terms, term_count * ECS_SIZEOF(ecs_term_t)); + buffer_count = term_count; + } else { + terms = NULL; + } - if (scope->is_collection) { - scope->elem_cur ++; - scope->op_cur = 0; - if (scope->elem_cur >= get_elem_count(scope)) { - ecs_err("out of collection bounds (%d)", scope->elem_cur); - return -1; + /* Parse expression into array of terms */ + const char *ptr = desc->expr; + ecs_term_t term = {0}; + while (ptr[0] && (ptr = ecs_parse_term(world, name, expr, ptr, &term))){ + if (!ecs_term_is_initialized(&term)) { + break; + } + + if (term_count == buffer_count) { + buffer_count = buffer_count ? buffer_count * 2 : 8; + terms = ecs_os_realloc(terms, + buffer_count * ECS_SIZEOF(ecs_term_t)); + } + + terms[term_count] = term; + term_count ++; + + if (ptr[0] == '\n') { + break; + } } - - return 0; + + f.terms = terms; + f.term_count = term_count; + + if (!ptr) { + goto error; + } +#else + ecs_abort(ECS_UNSUPPORTED, "parser addon is not available"); +#endif } - scope->op_cur += op->op_count; - if (scope->op_cur >= scope->op_count) { - ecs_err("out of bounds"); - return -1; + /* Ensure all fields are consistent and properly filled out */ + if (ecs_filter_finalize(world, &f)) { + goto error; } - return 0; -} + *filter_out = f; -int ecs_meta_member( - ecs_meta_cursor_t *cursor, - const char *name) -{ - if (cursor->depth == 0) { - ecs_err("cannot move to member in root scope"); - return -1; + /* Copy term resources. */ + if (term_count) { + if (!filter_out->expr) { + if (term_count < ECS_TERM_CACHE_SIZE) { + filter_out->terms = filter_out->term_cache; + filter_out->term_cache_used = true; + } else { + filter_out->terms = ecs_os_malloc_n(ecs_term_t, term_count); + } + } + + for (i = 0; i < term_count; i ++) { + filter_out->terms[i] = ecs_term_move(&terms[i]); + } + } else { + filter_out->terms = NULL; } - ecs_meta_scope_t *prev_scope = get_prev_scope(cursor); - ecs_meta_scope_t *scope = get_scope(cursor); - ecs_meta_type_op_t *push_op = get_op(prev_scope); - const ecs_world_t *world = cursor->world; + filter_out->name = ecs_os_strdup(desc->name); + filter_out->expr = ecs_os_strdup(desc->expr); - ecs_assert(push_op->kind == EcsOpPush, ECS_INTERNAL_ERROR, NULL); - - if (!push_op->members) { - ecs_err("cannot move to member '%s' for non-struct type", name); - return -1; - } + ecs_assert(!filter_out->term_cache_used || + filter_out->terms == filter_out->term_cache, + ECS_INTERNAL_ERROR, NULL); - ecs_hashed_string_t key = ecs_get_hashed_string( - name, ecs_os_strlen(name), 0); - int32_t *cur = flecs_hashmap_get(*push_op->members, &key, int32_t); - if (!cur) { - char *path = ecs_get_fullpath(world, scope->type); - ecs_err("unknown member '%s' for type '%s'", name, path); - ecs_os_free(path); - return -1; + filter_out->iterable.init = filter_iter_init; + + return 0; +error: + /* NULL members that point to non-owned resources */ + if (!f.expr) { + f.terms = NULL; } - scope->op_cur = *cur; + f.name = NULL; + f.expr = NULL; - return 0; + ecs_filter_fini(&f); + + return -1; } -int ecs_meta_push( - ecs_meta_cursor_t *cursor) +void ecs_filter_copy( + ecs_filter_t *dst, + const ecs_filter_t *src) { - ecs_meta_scope_t *scope = get_scope(cursor); - ecs_meta_type_op_t *op = get_op(scope); - const ecs_world_t *world = cursor->world; + if (src) { + *dst = *src; - if (cursor->depth == 0) { - if (!cursor->is_primitive_scope) { - if (op->kind > EcsOpScope) { - cursor->is_primitive_scope = true; - return 0; - } + int32_t term_count = src->term_count; + + if (src->term_cache_used) { + dst->terms = dst->term_cache; + } else { + dst->terms = ecs_os_memdup_n(src->terms, ecs_term_t, term_count); + } + + int i; + for (i = 0; i < term_count; i ++) { + dst->terms[i] = ecs_term_copy(&src->terms[i]); } + } else { + ecs_os_memset_t(dst, 0, ecs_filter_t); } +} - void *ptr = get_ptr(world, scope); - cursor->depth ++; - ecs_check(cursor->depth < ECS_META_MAX_SCOPE_DEPTH, - ECS_INVALID_PARAMETER, NULL); +void ecs_filter_move( + ecs_filter_t *dst, + ecs_filter_t *src) +{ + if (src) { + *dst = *src; - ecs_meta_scope_t *next_scope = get_scope(cursor); + if (src->term_cache_used) { + dst->terms = dst->term_cache; + } - /* If we're not already in an inline array and this operation is an inline - * array, push a frame for the array. - * Doing this first ensures that inline arrays take precedence over other - * kinds of push operations, such as for a struct element type. */ - if (!scope->is_inline_array && op->count > 1 && !scope->is_collection) { - /* Push a frame just for the element type, with inline_array = true */ - next_scope[0] = (ecs_meta_scope_t){ - .ops = op, - .op_count = op->op_count, - .ptr = scope->ptr, - .type = op->type, - .is_collection = true, - .is_inline_array = true - }; + src->terms = NULL; + src->term_count = 0; + } else { + ecs_os_memset_t(dst, 0, ecs_filter_t); + } +} - /* With 'is_inline_array' set to true we ensure that we can never push - * the same inline array twice */ +void ecs_filter_fini( + ecs_filter_t *filter) +{ + if (filter->terms) { + int i, count = filter->term_count; + for (i = 0; i < count; i ++) { + ecs_term_fini(&filter->terms[i]); + } - return 0; + if (!filter->term_cache_used) { + ecs_os_free(filter->terms); + } } - switch(op->kind) { - case EcsOpPush: - next_scope[0] = (ecs_meta_scope_t) { - .ops = &op[1], /* op after push */ - .op_count = op->op_count - 1, /* don't include pop */ - .ptr = scope->ptr, - .type = op->type - }; - break; + ecs_os_free(filter->name); + ecs_os_free(filter->expr); +} - case EcsOpArray: { - if (push_type(world, next_scope, op->type, ptr) != 0) { - goto error; +static +void filter_str_add_id( + const ecs_world_t *world, + ecs_strbuf_t *buf, + const ecs_term_id_t *id, + bool is_subject, + uint8_t default_set_mask) +{ + if (id->name) { + ecs_strbuf_appendstr(buf, id->name); + } else if (id->entity) { + bool id_added = false; + if (!is_subject || id->entity != EcsThis) { + char *path = ecs_get_fullpath(world, id->entity); + ecs_strbuf_appendstr(buf, path); + ecs_os_free(path); + id_added = true; } - const EcsArray *type_ptr = ecs_get(world, op->type, EcsArray); - next_scope->type = type_ptr->type; - next_scope->is_collection = true; - break; - } + if (id->set.mask != default_set_mask) { + if (id_added) { + ecs_strbuf_list_push(buf, ":", "|"); + } else { + ecs_strbuf_list_push(buf, "", "|"); + } + if (id->set.mask & EcsSelf) { + ecs_strbuf_list_appendstr(buf, "self"); + } + if (id->set.mask & EcsSuperSet) { + ecs_strbuf_list_appendstr(buf, "superset"); + } + if (id->set.mask & EcsSubSet) { + ecs_strbuf_list_appendstr(buf, "subset"); + } - case EcsOpVector: - next_scope->vector = ptr; - if (push_type(world, next_scope, op->type, NULL) != 0) { - goto error; - } + if (id->set.relation != EcsIsA) { + ecs_strbuf_list_push(buf, "(", ""); - const EcsVector *type_ptr = ecs_get(world, op->type, EcsVector); - next_scope->type = type_ptr->type; - next_scope->is_collection = true; - break; + char *rel_path = ecs_get_fullpath(world, id->set.relation); + ecs_strbuf_appendstr(buf, rel_path); + ecs_os_free(rel_path); - default: { - char *path = ecs_get_fullpath(world, scope->type); - ecs_err("invalid push for type '%s'", path); - ecs_os_free(path); - goto error; - } - } + ecs_strbuf_list_pop(buf, ")"); + } - if (scope->is_collection) { - next_scope[0].ptr = ECS_OFFSET(next_scope[0].ptr, - scope->elem_cur * get_size(world, scope)); + ecs_strbuf_list_pop(buf, ""); + } + } else { + ecs_strbuf_appendstr(buf, "0"); } - - return 0; -error: - return -1; } -int ecs_meta_pop( - ecs_meta_cursor_t *cursor) +static +void term_str_w_strbuf( + const ecs_world_t *world, + const ecs_term_t *term, + ecs_strbuf_t *buf) { - if (cursor->is_primitive_scope) { - cursor->is_primitive_scope = false; - return 0; + const ecs_term_id_t *subj = &term->subj; + const ecs_term_id_t *obj = &term->obj; + + const uint8_t def_pred_mask = EcsSelf|EcsSubSet; + const uint8_t def_subj_mask = EcsSelf|EcsSuperSet; + const uint8_t def_obj_mask = EcsSelf; + + bool pred_set = ecs_term_id_is_set(&term->pred); + bool subj_set = ecs_term_id_is_set(subj); + bool obj_set = ecs_term_id_is_set(obj); + + if (term->role && term->role != ECS_PAIR) { + ecs_strbuf_appendstr(buf, ecs_role_str(term->role)); + ecs_strbuf_appendstr(buf, " "); } - ecs_meta_scope_t *scope = get_scope(cursor); - cursor->depth --; - if (cursor->depth < 0) { - ecs_err("unexpected end of scope"); - return -1; + if (term->oper == EcsNot) { + ecs_strbuf_appendstr(buf, "!"); + } else if (term->oper == EcsOptional) { + ecs_strbuf_appendstr(buf, "?"); } - ecs_meta_scope_t *next_scope = get_scope(cursor); - ecs_meta_type_op_t *op = get_op(next_scope); - - if (!scope->is_inline_array) { - if (op->kind == EcsOpPush) { - next_scope->op_cur += op->op_count - 1; - - /* push + op_count should point to the operation after pop */ - op = get_op(next_scope); - ecs_assert(op->kind == EcsOpPop, ECS_INTERNAL_ERROR, NULL); - } else if (op->kind == EcsOpArray || op->kind == EcsOpVector) { - /* Collection type, nothing else to do */ - } else { - /* should not have been able to push if the previous scope was not - * a complex or collection type */ - ecs_assert(false, ECS_INTERNAL_ERROR, NULL); + if (!subj_set) { + filter_str_add_id(world, buf, &term->pred, false, def_pred_mask); + ecs_strbuf_appendstr(buf, "()"); + } else if (subj_set && subj->entity == EcsThis && subj->set.mask == def_subj_mask) + { + if (term->id) { + char *str = ecs_id_str(world, term->id); + ecs_strbuf_appendstr(buf, str); + ecs_os_free(str); + } else if (pred_set) { + filter_str_add_id(world, buf, &term->pred, false, def_pred_mask); } } else { - /* Make sure that this was an inline array */ - ecs_assert(next_scope->op_count > 1, ECS_INTERNAL_ERROR, NULL); + filter_str_add_id(world, buf, &term->pred, false, def_pred_mask); + ecs_strbuf_appendstr(buf, "("); + filter_str_add_id(world, buf, &term->subj, true, def_subj_mask); + if (obj_set) { + ecs_strbuf_appendstr(buf, ","); + filter_str_add_id(world, buf, &term->obj, false, def_obj_mask); + } + ecs_strbuf_appendstr(buf, ")"); } - - return 0; } -int ecs_meta_is_collection( - ecs_meta_cursor_t *cursor) +char* ecs_term_str( + const ecs_world_t *world, + const ecs_term_t *term) { - ecs_meta_scope_t *scope = get_scope(cursor); - return scope->is_collection; + ecs_strbuf_t buf = ECS_STRBUF_INIT; + term_str_w_strbuf(world, term, &buf); + return ecs_strbuf_get(&buf); } -ecs_entity_t ecs_meta_get_type( - ecs_meta_cursor_t *cursor) +char* ecs_filter_str( + const ecs_world_t *world, + const ecs_filter_t *filter) { - ecs_meta_scope_t *scope = get_scope(cursor); - ecs_meta_type_op_t *op = get_op(scope); - return op->type; -} - -/* Utility macro's to let the compiler do the conversion work for us */ -#define set_T(T, ptr, value)\ - ((T*)ptr)[0] = ((T)value) - -#define case_T(kind, T, dst, src)\ -case kind:\ - set_T(T, dst, src);\ - break + ecs_strbuf_t buf = ECS_STRBUF_INIT; -#define cases_T_float(dst, src)\ - case_T(EcsOpF32, ecs_f32_t, dst, src);\ - case_T(EcsOpF64, ecs_f64_t, dst, src) + ecs_check(!filter->term_cache_used || filter->terms == filter->term_cache, + ECS_INVALID_PARAMETER, NULL); -#define cases_T_signed(dst, src)\ - case_T(EcsOpChar, ecs_char_t, dst, src);\ - case_T(EcsOpI8, ecs_i8_t, dst, src);\ - case_T(EcsOpI16, ecs_i16_t, dst, src);\ - case_T(EcsOpI32, ecs_i32_t, dst, src);\ - case_T(EcsOpI64, ecs_i64_t, dst, src);\ - case_T(EcsOpIPtr, ecs_iptr_t, dst, src) + ecs_term_t *terms = filter->terms; + int32_t i, count = filter->term_count; + int32_t or_count = 0; -#define cases_T_unsigned(dst, src)\ - case_T(EcsOpByte, ecs_byte_t, dst, src);\ - case_T(EcsOpU8, ecs_u8_t, dst, src);\ - case_T(EcsOpU16, ecs_u16_t, dst, src);\ - case_T(EcsOpU32, ecs_u32_t, dst, src);\ - case_T(EcsOpU64, ecs_u64_t, dst, src);\ - case_T(EcsOpUPtr, ecs_uptr_t, dst, src);\ + for (i = 0; i < count; i ++) { + ecs_term_t *term = &terms[i]; -#define cases_T_bool(dst, src)\ -case EcsOpBool:\ - set_T(ecs_bool_t, dst, value != 0);\ - break + if (i) { + if (terms[i - 1].oper == EcsOr && term->oper == EcsOr) { + ecs_strbuf_appendstr(&buf, " || "); + } else { + ecs_strbuf_appendstr(&buf, ", "); + } + } -static -void conversion_error( - ecs_meta_cursor_t *cursor, - ecs_meta_type_op_t *op, - const char *from) -{ - char *path = ecs_get_fullpath(cursor->world, op->type); - ecs_err("unsupported conversion from %s to '%s'", from, path); - ecs_os_free(path); -} + if (or_count < 1) { + if (term->inout == EcsIn) { + ecs_strbuf_appendstr(&buf, "[in] "); + } else if (term->inout == EcsInOut) { + ecs_strbuf_appendstr(&buf, "[inout] "); + } else if (term->inout == EcsOut) { + ecs_strbuf_appendstr(&buf, "[out] "); + } + } -int ecs_meta_set_bool( - ecs_meta_cursor_t *cursor, - bool value) -{ - ecs_meta_scope_t *scope = get_scope(cursor); - ecs_meta_type_op_t *op = get_op(scope); - void *ptr = get_ptr(cursor->world, scope); + if (term->oper == EcsOr) { + or_count ++; + } else { + or_count = 0; + } - switch(op->kind) { - cases_T_bool(ptr, value); - cases_T_unsigned(ptr, value); - default: - conversion_error(cursor, op, "bool"); - return -1; + term_str_w_strbuf(world, term, &buf); } - return 0; + return ecs_strbuf_get(&buf); +error: + return NULL; } -int ecs_meta_set_char( - ecs_meta_cursor_t *cursor, - char value) +bool flecs_term_match_table( + ecs_world_t *world, + const ecs_term_t *term, + const ecs_table_t *table, + ecs_type_t type, + ecs_id_t *id_out, + int32_t *column_out, + ecs_entity_t *subject_out, + int32_t *match_index_out, + bool first) { - ecs_meta_scope_t *scope = get_scope(cursor); - ecs_meta_type_op_t *op = get_op(scope); - void *ptr = get_ptr(cursor->world, scope); + const ecs_term_id_t *subj = &term->subj; + ecs_oper_kind_t oper = term->oper; + const ecs_table_t *match_table = table; + ecs_type_t match_type = type; - switch(op->kind) { - cases_T_bool(ptr, value); - cases_T_signed(ptr, value); - default: - conversion_error(cursor, op, "char"); - return -1; + ecs_entity_t subj_entity = subj->entity; + if (!subj_entity) { + id_out[0] = term->id; /* no source corresponds with Nothing set mask */ + return true; } - return 0; -} - -int ecs_meta_set_int( - ecs_meta_cursor_t *cursor, - int64_t value) -{ - ecs_meta_scope_t *scope = get_scope(cursor); - ecs_meta_type_op_t *op = get_op(scope); - void *ptr = get_ptr(cursor->world, scope); - - switch(op->kind) { - cases_T_bool(ptr, value); - cases_T_signed(ptr, value); - cases_T_float(ptr, value); - default: { - conversion_error(cursor, op, "int"); - return -1; - } + /* If source is not This, search in table of source */ + if (subj_entity != EcsThis) { + match_table = ecs_get_table(world, subj_entity); + if (match_table) { + match_type = match_table->type; + } else { + match_type = NULL; + } + } else { + /* If filter contains This terms, a table must be provided */ + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); } - return 0; -} + ecs_entity_t source; -int ecs_meta_set_uint( - ecs_meta_cursor_t *cursor, - uint64_t value) -{ - ecs_meta_scope_t *scope = get_scope(cursor); - ecs_meta_type_op_t *op = get_op(scope); - void *ptr = get_ptr(cursor->world, scope); + /* If first = false, we're searching from an offset. This supports returning + * multiple results when using wildcard filters. */ + int32_t column = 0; + if (!first && column_out && column_out[0] != 0) { + column = column_out[0]; + if (column < 0) { + /* In case column is not from This, flip sign */ + column = -column; + } - switch(op->kind) { - cases_T_bool(ptr, value); - cases_T_unsigned(ptr, value); - cases_T_float(ptr, value); - case EcsOpEntity: - set_T(ecs_entity_t, ptr, value); - break; - default: - conversion_error(cursor, op, "uint"); - return -1; + /* Remove base 1 offset */ + column --; } - return 0; -} + /* Find location, source and id of match in table type */ + column = ecs_type_match(world, match_table, match_type, + column, term->id, subj->set.relation, subj->set.min_depth, + subj->set.max_depth, &source, id_out, match_index_out); -int ecs_meta_set_float( - ecs_meta_cursor_t *cursor, - double value) -{ - ecs_meta_scope_t *scope = get_scope(cursor); - ecs_meta_type_op_t *op = get_op(scope); - void *ptr = get_ptr(cursor->world, scope); + bool result = column != -1; - switch(op->kind) { - cases_T_bool(ptr, value); - cases_T_signed(ptr, value); - cases_T_unsigned(ptr, value); - cases_T_float(ptr, value); - default: - conversion_error(cursor, op, "float"); - return -1; + if (oper == EcsNot) { + result = !result; } - return 0; -} + if (oper == EcsOptional) { + result = true; + } -static -int add_bitmask_constant( - ecs_meta_cursor_t *cursor, - ecs_meta_type_op_t *op, - void *out, - const char *value) -{ - ecs_assert(op->type != 0, ECS_INTERNAL_ERROR, NULL); + if (!result) { + return false; + } - if (!ecs_os_strcmp(value, "0")) { - return 0; + if (subj_entity != EcsThis) { + if (!source) { + source = subj_entity; + } } - ecs_entity_t c = ecs_lookup_child(cursor->world, op->type, value); - if (!c) { - char *path = ecs_get_fullpath(cursor->world, op->type); - ecs_err("unresolved bitmask constant '%s' for type '%s'", value, path); - ecs_os_free(path); - return -1; + if (id_out && column < 0) { + id_out[0] = term->id; } - const ecs_u32_t *v = ecs_get_pair_object( - cursor->world, c, EcsConstant, ecs_u32_t); - if (v == NULL) { - char *path = ecs_get_fullpath(cursor->world, op->type); - ecs_err("'%s' is not an bitmask constant for type '%s'", value, path); - ecs_os_free(path); - return -1; + if (column_out) { + if (column >= 0) { + column ++; + if (source != 0) { + column *= -1; + } + column_out[0] = column; + } else { + column_out[0] = 0; + } } - *(ecs_u32_t*)out |= v[0]; + if (subject_out) { + subject_out[0] = source; + } - return 0; + return result; } -static -int parse_bitmask( - ecs_meta_cursor_t *cursor, - ecs_meta_type_op_t *op, - void *out, - const char *value) +bool flecs_filter_match_table( + ecs_world_t *world, + const ecs_filter_t *filter, + const ecs_table_t *table, + ecs_id_t *ids, + int32_t *columns, + ecs_entity_t *subjects, + int32_t *match_indices, + int32_t *matches_left, + bool first, + int32_t skip_term) { - char token[ECS_MAX_TOKEN_SIZE]; + ecs_assert(!filter->term_cache_used || filter->terms == filter->term_cache, + ECS_INTERNAL_ERROR, NULL); - const char *prev = value, *ptr = value; + ecs_type_t type = NULL; + if (table) { + type = table->type; + } - *(ecs_u32_t*)out = 0; + ecs_term_t *terms = filter->terms; + int32_t i, count = filter->term_count; - while ((ptr = strchr(ptr, '|'))) { - ecs_os_memcpy(token, prev, ptr - prev); - token[ptr - prev] = '\0'; - if (add_bitmask_constant(cursor, op, out, token) != 0) { - return -1; + bool is_or = false; + bool or_result = false; + int32_t match_count = 0; + + for (i = 0; i < count; i ++) { + if (i == skip_term) { + continue; } - ptr ++; - prev = ptr; - } + ecs_term_t *term = &terms[i]; + ecs_term_id_t *subj = &term->subj; + ecs_oper_kind_t oper = term->oper; + const ecs_table_t *match_table = table; + ecs_type_t match_type = type; + int32_t t_i = term->index; - if (add_bitmask_constant(cursor, op, out, prev) != 0) { - return -1; - } + if (!is_or && oper == EcsOr) { + is_or = true; + or_result = false; + } else if (is_or && oper != EcsOr) { + if (!or_result) { + return false; + } - return 0; -} + is_or = false; + } -int ecs_meta_set_string( - ecs_meta_cursor_t *cursor, - const char *value) -{ - ecs_meta_scope_t *scope = get_scope(cursor); - ecs_meta_type_op_t *op = get_op(scope); - void *ptr = get_ptr(cursor->world, scope); + ecs_entity_t subj_entity = subj->entity; + if (!subj_entity) { + if (ids) { + ids[t_i] = term->id; + } + continue; + } - switch(op->kind) { - case EcsOpBool: - if (!ecs_os_strcmp(value, "true")) { - set_T(ecs_bool_t, ptr, true); - } else if (!ecs_os_strcmp(value, "false")) { - set_T(ecs_bool_t, ptr, false); + if (subj_entity != EcsThis) { + match_table = ecs_get_table(world, subj_entity); + if (match_table) { + match_type = match_table->type; + } else { + match_type = NULL; + } } else { - ecs_err("invalid value for boolean '%s'", value); - return -1; + /* If filter contains This terms, table must be provided */ + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); } - break; - case EcsOpI8: - case EcsOpU8: - case EcsOpChar: - case EcsOpByte: - set_T(ecs_i8_t, ptr, atol(value)); - break; - case EcsOpI16: - case EcsOpU16: - set_T(ecs_i16_t, ptr, atol(value)); - break; - case EcsOpI32: - case EcsOpU32: - set_T(ecs_i32_t, ptr, atol(value)); - break; - case EcsOpI64: - case EcsOpU64: - set_T(ecs_i64_t, ptr, atol(value)); - break; - case EcsOpIPtr: - case EcsOpUPtr: - set_T(ecs_iptr_t, ptr, atol(value)); - break; - case EcsOpF32: - set_T(ecs_f32_t, ptr, atof(value)); - break; - case EcsOpF64: - set_T(ecs_f64_t, ptr, atof(value)); - break; - case EcsOpString: { - ecs_os_free(*(char**)ptr); - char *result = ecs_os_strdup(value); - set_T(ecs_string_t, ptr, result); - break; - } - case EcsOpEnum: { - ecs_assert(op->type != 0, ECS_INTERNAL_ERROR, NULL); - ecs_entity_t c = ecs_lookup_child(cursor->world, op->type, value); - if (!c) { - char *path = ecs_get_fullpath(cursor->world, op->type); - ecs_err("unresolved enum constant '%s' for type '%s'", value, path); - ecs_os_free(path); - return -1; + + bool result = flecs_term_match_table(world, term, match_table, + match_type, + ids ? &ids[t_i] : NULL, + columns ? &columns[t_i] : NULL, + subjects ? &subjects[t_i] : NULL, + match_indices ? &match_indices[t_i] : NULL, + first); + + if (is_or) { + or_result |= result; + } else if (!result) { + return false; } - const ecs_i32_t *v = ecs_get_pair_object( - cursor->world, c, EcsConstant, ecs_i32_t); - if (v == NULL) { - char *path = ecs_get_fullpath(cursor->world, op->type); - ecs_err("'%s' is not an enum constant for type '%s'", value, path); - ecs_os_free(path); - return -1; + /* Match indices is populated with the number of matches for this term. + * This is used to determine whether to keep iterating this table. */ + if (first && match_indices && match_indices[t_i]) { + match_indices[t_i] --; + match_count += match_indices[t_i]; } + } - set_T(ecs_i32_t, ptr, v[0]); - break; + if (matches_left) { + *matches_left = match_count; } - case EcsOpBitmask: - if (parse_bitmask(cursor, op, ptr, value) != 0) { - return -1; - } - break; - case EcsOpEntity: { - ecs_entity_t e = 0; - if (ecs_os_strcmp(value, "0")) { - if (cursor->lookup_action) { - e = cursor->lookup_action( - cursor->world, value, - cursor->lookup_ctx); - } else { - e = ecs_lookup_path(cursor->world, 0, value); - } + return !is_or || or_result; +} + +static +void term_iter_init_no_data( + ecs_term_iter_t *iter) +{ + iter->term = (ecs_term_t){ .index = -1 }; + iter->self_index = NULL; + iter->index = 0; +} + +static +void term_iter_init_wildcard( + const ecs_world_t *world, + ecs_term_iter_t *iter) +{ + iter->term = (ecs_term_t){ .index = -1 }; + iter->self_index = flecs_get_id_record(world, EcsWildcard); + iter->cur = iter->self_index; + iter->index = 0; +} + +static +void term_iter_init( + const ecs_world_t *world, + ecs_term_t *term, + ecs_term_iter_t *iter) +{ + const ecs_term_id_t *subj = &term->subj; - if (!e) { - ecs_err("unresolved entity identifier '%s'", value); - return -1; - } - } + iter->term = *term; - set_T(ecs_entity_t, ptr, e); - break; + if (subj->set.mask == EcsDefaultSet || subj->set.mask & EcsSelf) { + iter->self_index = flecs_get_id_record(world, term->id); } - case EcsOpPop: - ecs_err("excess element '%s' in scope", value); - return -1; - default: - ecs_err("unsupported conversion from string '%s' to '%s'", - value, op_kind_str(op->kind)); - return -1; + + if (subj->set.mask & EcsSuperSet) { + iter->set_index = flecs_get_id_record(world, + ecs_pair(subj->set.relation, EcsWildcard)); } - return 0; + iter->index = 0; + if (iter->self_index) { + iter->cur = iter->self_index; + } else { + iter->cur = iter->set_index; + } } -int ecs_meta_set_string_literal( - ecs_meta_cursor_t *cursor, - const char *value) +ecs_iter_t ecs_term_iter( + const ecs_world_t *stage, + ecs_term_t *term) { - ecs_meta_scope_t *scope = get_scope(cursor); - ecs_meta_type_op_t *op = get_op(scope); - void *ptr = get_ptr(cursor->world, scope); + ecs_check(stage != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(term != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(term->id != 0, ECS_INVALID_PARAMETER, NULL); - ecs_size_t len = ecs_os_strlen(value); - if (value[0] != '\"' || value[len - 1] != '\"') { - ecs_err("invalid string literal '%s'", value); - return -1; + const ecs_world_t *world = ecs_get_world(stage); + + if (ecs_term_finalize(world, NULL, term)) { + ecs_throw(ECS_INVALID_PARAMETER, NULL); } - switch(op->kind) { - case EcsOpChar: - set_T(ecs_char_t, ptr, value[1]); - break; - - default: - case EcsOpEntity: - case EcsOpString: - len -= 2; + ecs_iter_t it = { + .real_world = (ecs_world_t*)world, + .world = (ecs_world_t*)stage, + .term_count = 1, + .next = ecs_term_next + }; - char *result = ecs_os_malloc(len + 1); - ecs_os_memcpy(result, value + 1, len); - result[len] = '\0'; + term_iter_init(world, term, &it.iter.term); - if (ecs_meta_set_string(cursor, result)) { - ecs_os_free(result); - return -1; - } + return it; +error: + return (ecs_iter_t){ 0 }; +} - ecs_os_free(result); +ecs_iter_t ecs_term_chain_iter( + const ecs_iter_t *chain_it, + ecs_term_t *term) +{ + ecs_check(chain_it != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(term != NULL, ECS_INVALID_PARAMETER, NULL); - break; + ecs_world_t *world = chain_it->real_world; + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + + if (ecs_term_finalize(world, NULL, term)) { + ecs_throw(ECS_INVALID_PARAMETER, NULL); } - return 0; + ecs_iter_t it = { + .chain_it = (ecs_iter_t*)chain_it, + .real_world = (ecs_world_t*)world, + .world = chain_it->world, + .terms = term, + .term_count = 1, + .next = ecs_term_next + }; + + term_iter_init(world, term, &it.iter.term); + + return it; +error: + return (ecs_iter_t){ 0 }; } -int ecs_meta_set_entity( - ecs_meta_cursor_t *cursor, - ecs_entity_t value) +static +const ecs_table_record_t *next_table( + ecs_term_iter_t *iter) { - ecs_meta_scope_t *scope = get_scope(cursor); - ecs_meta_type_op_t *op = get_op(scope); - void *ptr = get_ptr(cursor->world, scope); - - switch(op->kind) { - case EcsOpEntity: - set_T(ecs_entity_t, ptr, value); - break; - default: - conversion_error(cursor, op, "entity"); - return -1; + const ecs_table_record_t *tables = flecs_id_record_tables(iter->cur); + int32_t count = flecs_id_record_count(iter->cur); + if (iter->index >= count) { + return NULL; } - return 0; + return &tables[iter->index ++]; } -int ecs_meta_set_null( - ecs_meta_cursor_t *cursor) +static +bool term_iter_next( + ecs_world_t *world, + ecs_term_iter_t *iter, + bool match_prefab, + bool match_disabled) { - ecs_meta_scope_t *scope = get_scope(cursor); - ecs_meta_type_op_t *op = get_op(scope); - void *ptr = get_ptr(cursor->world, scope); - switch (op->kind) { - case EcsOpString: - ecs_os_free(*(char**)ptr); - set_T(ecs_string_t, ptr, NULL); - break; - default: - conversion_error(cursor, op, "null"); - return -1; - } + ecs_table_t *table = iter->table; + ecs_entity_t source = 0; + const ecs_table_record_t *tr; + ecs_term_t *term = &iter->term; - return 0; -} + do { + if (table) { + iter->cur_match ++; + if (iter->cur_match >= iter->match_count) { + table = NULL; + } else { + iter->last_column = ecs_type_index_of( + table->type, iter->last_column + 1, term->id); + iter->column = iter->last_column + 1; + if (iter->last_column >= 0) { + iter->id = ecs_vector_get( + table->type, ecs_id_t, iter->last_column)[0]; + } + } + } -#endif + if (!table) { + if (!(tr = next_table(iter))) { + if (iter->cur != iter->set_index && iter->set_index != NULL) { + iter->cur = iter->set_index; + iter->index = 0; + tr = next_table(iter); + } + if (!tr) { + return false; + } + } + table = tr->table; -#ifdef FLECS_EXPR + if (!match_prefab && (table->flags & EcsTableIsPrefab)) { + continue; + } -static -int expr_ser_type( - const ecs_world_t *world, - ecs_vector_t *ser, - const void *base, - ecs_strbuf_t *str); + if (!match_disabled && (table->flags & EcsTableIsDisabled)) { + continue; + } -static -int expr_ser_type_ops( - const ecs_world_t *world, - ecs_meta_type_op_t *ops, - int32_t op_count, - const void *base, - ecs_strbuf_t *str); + if (!ecs_table_count(table)) { + continue; + } -static -int expr_ser_type_op( - const ecs_world_t *world, - ecs_meta_type_op_t *op, - const void *base, - ecs_strbuf_t *str); + iter->table = table; + iter->match_count = tr->count; + iter->cur_match = 0; + iter->last_column = tr->column; + iter->column = tr->column + 1; + iter->id = ecs_vector_get(table->type, ecs_id_t, tr->column)[0]; + } -static -ecs_primitive_kind_t expr_op_to_primitive_kind(ecs_meta_type_op_kind_t kind) { - return kind - EcsOpPrimitive; -} + if (iter->cur == iter->set_index) { + const ecs_term_id_t *subj = &term->subj; -/* Serialize a primitive value */ -static -int expr_ser_primitive( - const ecs_world_t *world, - ecs_primitive_kind_t kind, - const void *base, - ecs_strbuf_t *str) -{ - const char *bool_str[] = { "false", "true" }; + if (iter->self_index) { + if (flecs_id_record_table(iter->self_index, table) != NULL) { + /* If the table has the id itself and this term matched Self + * we already matched it */ + continue; + } + } - switch(kind) { - case EcsBool: - ecs_strbuf_appendstr(str, bool_str[(int)*(bool*)base]); - break; - case EcsChar: { - char chbuf[3]; - char ch = *(char*)base; - if (ch) { - ecs_chresc(chbuf, *(char*)base, '"'); - ecs_strbuf_appendstrn(str, "\"", 1); - ecs_strbuf_appendstr(str, chbuf); - ecs_strbuf_appendstrn(str, "\"", 1); - } else { - ecs_strbuf_appendstr(str, "0"); - } - break; - } - case EcsByte: - ecs_strbuf_append(str, "%u", *(uint8_t*)base); - break; - case EcsU8: - ecs_strbuf_append(str, "%u", *(uint8_t*)base); - break; - case EcsU16: - ecs_strbuf_append(str, "%u", *(uint16_t*)base); - break; - case EcsU32: - ecs_strbuf_append(str, "%u", *(uint32_t*)base); - break; - case EcsU64: - ecs_strbuf_append(str, "%llu", *(uint64_t*)base); - break; - case EcsI8: - ecs_strbuf_append(str, "%d", *(int8_t*)base); - break; - case EcsI16: - ecs_strbuf_append(str, "%d", *(int16_t*)base); - break; - case EcsI32: - ecs_strbuf_append(str, "%d", *(int32_t*)base); - break; - case EcsI64: - ecs_strbuf_append(str, "%lld", *(int64_t*)base); - break; - case EcsF32: - ecs_strbuf_appendflt(str, (double)*(float*)base); - break; - case EcsF64: - ecs_strbuf_appendflt(str, *(double*)base); - break; - case EcsIPtr: - ecs_strbuf_append(str, "%i", *(intptr_t*)base); - break; - case EcsUPtr: - ecs_strbuf_append(str, "%u", *(uintptr_t*)base); - break; - case EcsString: { - char *value = *(char**)base; - if (value) { - ecs_size_t length = ecs_stresc(NULL, 0, '"', value); - if (length == ecs_os_strlen(value)) { - ecs_strbuf_appendstrn(str, "\"", 1); - ecs_strbuf_appendstr(str, value); - ecs_strbuf_appendstrn(str, "\"", 1); - } else { - char *out = ecs_os_malloc(length + 3); - ecs_stresc(out + 1, length, '"', value); - out[0] = '"'; - out[length + 1] = '"'; - out[length + 2] = '\0'; - ecs_strbuf_appendstr_zerocpy(str, out); + /* Test if following the relation finds the id */ + int32_t index = ecs_type_match(world, table, table->type, 0, + term->id, subj->set.relation, subj->set.min_depth, + subj->set.max_depth, &source, &iter->id, NULL); + + if (index == -1) { + source = 0; + continue; } - } else { - ecs_strbuf_appendstr(str, "null"); + + ecs_assert(source != 0, ECS_INTERNAL_ERROR, NULL); + + iter->column = (index + 1) * -1; } + break; + } while (true); + + iter->subject = source; + + return true; +} + +bool ecs_term_next( + ecs_iter_t *it) +{ + ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(it->next == ecs_term_next, ECS_INVALID_PARAMETER, NULL); + + ecs_term_iter_t *iter = &it->iter.term; + ecs_term_t *term = &iter->term; + ecs_world_t *world = it->real_world; + ecs_table_t *table; + + it->ids = &iter->id; + it->subjects = &iter->subject; + it->columns = &iter->column; + it->terms = &iter->term; + + if (term->inout != EcsInOutFilter) { + it->sizes = &iter->size; + it->ptrs = &iter->ptr; + } else { + it->sizes = NULL; + it->ptrs = NULL; } - case EcsEntity: { - ecs_entity_t e = *(ecs_entity_t*)base; - if (!e) { - ecs_strbuf_appendstr(str, "0"); - } else { - char *path = ecs_get_fullpath(world, e); - ecs_assert(path != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_strbuf_appendstr(str, path); - ecs_os_free(path); + + ecs_iter_t *chain_it = it->chain_it; + if (chain_it) { + ecs_iter_next_action_t next = chain_it->next; + bool match; + + do { + if (!next(chain_it)) { + goto done; + } + + table = chain_it->table; + match = flecs_term_match_table(world, term, table, table->type, + it->ids, it->columns, it->subjects, it->match_indices, true); + } while (!match); + goto yield; + + } else { + if (!term_iter_next(world, iter, false, false)) { + goto done; } - break; - } - default: - ecs_err("invalid primitive kind"); - return -1; + + table = iter->table; + + /* Source must either be 0 (EcsThis) or nonzero in case of substitution */ + ecs_assert(iter->subject || iter->cur != iter->set_index, + ECS_INTERNAL_ERROR, NULL); + ecs_assert(iter->table != NULL, ECS_INTERNAL_ERROR, NULL); } - return 0; +yield: + flecs_iter_populate_data(world, it, table, 0, 0, it->ptrs, it->sizes); + it->is_valid = true; + return true; +done: +error: + return false; } -/* Serialize enumeration */ static -int expr_ser_enum( +const ecs_filter_t* init_filter_iter( const ecs_world_t *world, - ecs_meta_type_op_t *op, - const void *base, - ecs_strbuf_t *str) + ecs_iter_t *it, + const ecs_filter_t *filter) { - const EcsEnum *enum_type = ecs_get(world, op->type, EcsEnum); - ecs_check(enum_type != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_filter_iter_t *iter = &it->iter.filter; - int32_t value = *(int32_t*)base; - - /* Enumeration constants are stored in a map that is keyed on the - * enumeration value. */ - ecs_enum_constant_t *constant = ecs_map_get( - enum_type->constants, ecs_enum_constant_t, value); - if (!constant) { - char *path = ecs_get_fullpath(world, op->type); - ecs_err("value %d is not valid for enum type '%s'", value, path); - ecs_os_free(path); - goto error; + if (filter) { + iter->filter = *filter; + + if (filter->term_cache_used) { + iter->filter.terms = iter->filter.term_cache; + } + + ecs_filter_finalize(world, &iter->filter); + + ecs_assert(!filter->term_cache_used || + filter->terms == filter->term_cache, ECS_INTERNAL_ERROR, NULL); + } else { + ecs_filter_init(world, &iter->filter, &(ecs_filter_desc_t) { + .terms = {{ .id = EcsWildcard }} + }); + + filter = &iter->filter; } - ecs_strbuf_appendstr(str, ecs_get_name(world, constant->constant)); + it->term_count = filter->term_count_actual; - return 0; -error: - return -1; + return filter; } -/* Serialize bitmask */ -static -int expr_ser_bitmask( - const ecs_world_t *world, - ecs_meta_type_op_t *op, - const void *ptr, - ecs_strbuf_t *str) +ecs_iter_t ecs_filter_iter( + const ecs_world_t *stage, + const ecs_filter_t *filter) { - const EcsBitmask *bitmask_type = ecs_get(world, op->type, EcsBitmask); - ecs_check(bitmask_type != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(stage != NULL, ECS_INVALID_PARAMETER, NULL); + + const ecs_world_t *world = ecs_get_world(stage); + + ecs_iter_t it = { + .real_world = (ecs_world_t*)world, + .world = (ecs_world_t*)stage, + .terms = filter ? filter->terms : NULL, + .next = ecs_filter_next, + .is_instanced = filter ? filter->instanced : false + }; + + ecs_filter_iter_t *iter = &it.iter.filter; + + filter = init_filter_iter(world, &it, filter); + + int32_t i, term_count = filter->term_count; + ecs_term_t *terms = filter->terms; + int32_t min_count = -1; + int32_t pivot_term = -1; + + /* Find term that represents smallest superset */ + if (filter->match_this) { + iter->kind = EcsIterEvalIndex; + + for (i = 0; i < term_count; i ++) { + ecs_term_t *term = &terms[i]; + + ecs_check(term != NULL, ECS_INVALID_PARAMETER, NULL); - uint32_t value = *(uint32_t*)ptr; - ecs_map_key_t key; - ecs_bitmask_constant_t *constant; - int count = 0; + if (term->oper != EcsAnd) { + continue; + } - ecs_strbuf_list_push(str, "", "|"); + if (term->subj.entity != EcsThis) { + continue; + } - /* Multiple flags can be set at a given time. Iterate through all the flags - * and append the ones that are set. */ - ecs_map_iter_t it = ecs_map_iter(bitmask_type->constants); - while ((constant = ecs_map_next(&it, ecs_bitmask_constant_t, &key))) { - if ((value & key) == key) { - ecs_strbuf_list_appendstr(str, - ecs_get_name(world, constant->constant)); - count ++; - value -= (uint32_t)key; + ecs_id_record_t *idr = flecs_get_id_record(world, term->id); + if (!idr) { + /* If one of the terms does not match with any data, iterator + * should not return anything */ + term_iter_init_no_data(&iter->term_iter); + return it; + } + + int32_t table_count = flecs_id_record_count(idr); + if (min_count == -1 || table_count < min_count) { + min_count = table_count; + pivot_term = i; + } } - } - if (value != 0) { - /* All bits must have been matched by a constant */ - char *path = ecs_get_fullpath(world, op->type); - ecs_err( - "value for bitmask %s contains bits (%u) that cannot be mapped to constant", - path, value); - ecs_os_free(path); - goto error; + if (pivot_term == -1) { + term_iter_init_wildcard(world, &iter->term_iter); + } else { + term_iter_init(world, &terms[pivot_term], &iter->term_iter); + } + } else { + if (!filter->match_anything) { + iter->kind = EcsIterEvalCondition; + term_iter_init_no_data(&iter->term_iter); + } else { + iter->kind = EcsIterEvalNone; + } } - if (!count) { - ecs_strbuf_list_appendstr(str, "0"); + if (filter->terms == filter->term_cache) { + /* Because we're returning the iterator by value, the address of the + * term cache changes. The ecs_filter_next function will set the correct + * address when it detects that terms is set to NULL */ + iter->filter.terms = NULL; } - ecs_strbuf_list_pop(str, ""); + it.is_filter = filter->filter; - return 0; + return it; error: - return -1; + return (ecs_iter_t){ 0 }; } -/* Serialize elements of a contiguous array */ -static -int expr_ser_elements( - const ecs_world_t *world, - ecs_meta_type_op_t *ops, - int32_t op_count, - const void *base, - int32_t elem_count, - int32_t elem_size, - ecs_strbuf_t *str) +ecs_iter_t ecs_filter_chain_iter( + const ecs_iter_t *chain_it, + const ecs_filter_t *filter) { - ecs_strbuf_list_push(str, "[", ", "); + ecs_iter_t it = { + .terms = filter->terms, + .term_count = filter->term_count, + .chain_it = (ecs_iter_t*)chain_it, + .next = ecs_filter_next, + .world = chain_it->world, + .real_world = chain_it->real_world + }; - const void *ptr = base; + ecs_filter_iter_t *iter = &it.iter.filter; + init_filter_iter(it.world, &it, filter); - int i; - for (i = 0; i < elem_count; i ++) { - ecs_strbuf_list_next(str); - if (expr_ser_type_ops(world, ops, op_count, ptr, str)) { - return -1; - } - ptr = ECS_OFFSET(ptr, elem_size); - } + iter->kind = EcsIterEvalChain; - ecs_strbuf_list_pop(str, "]"); + if (filter->terms == filter->term_cache) { + /* See ecs_filter_iter */ + iter->filter.terms = NULL; + } - return 0; + return it; } -static -int expr_ser_type_elements( - const ecs_world_t *world, - ecs_entity_t type, - const void *base, - int32_t elem_count, - ecs_strbuf_t *str) +bool ecs_filter_next( + ecs_iter_t *it) { - const EcsMetaTypeSerialized *ser = ecs_get( - world, type, EcsMetaTypeSerialized); - ecs_assert(ser != NULL, ECS_INTERNAL_ERROR, NULL); - - const EcsComponent *comp = ecs_get(world, type, EcsComponent); - ecs_assert(comp != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(it->next == ecs_filter_next, ECS_INVALID_PARAMETER, NULL); - ecs_meta_type_op_t *ops = ecs_vector_first(ser->ops, ecs_meta_type_op_t); - int32_t op_count = ecs_vector_count(ser->ops); + if (flecs_iter_next_row(it)) { + return true; + } - return expr_ser_elements( - world, ops, op_count, base, elem_count, comp->size, str); + return flecs_iter_next_instanced(it, ecs_filter_next_instanced(it)); +error: + return false; } -/* Serialize array */ -static -int expr_ser_array( - const ecs_world_t *world, - ecs_meta_type_op_t *op, - const void *ptr, - ecs_strbuf_t *str) +bool ecs_filter_next_instanced( + ecs_iter_t *it) { - const EcsArray *a = ecs_get(world, op->type, EcsArray); - ecs_assert(a != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(it->next == ecs_filter_next, ECS_INVALID_PARAMETER, NULL); + ecs_check(it->chain_it != it, ECS_INVALID_PARAMETER, NULL); - return expr_ser_type_elements( - world, a->type, ptr, a->count, str); -} + ecs_filter_iter_t *iter = &it->iter.filter; + ecs_filter_t *filter = &iter->filter; + ecs_world_t *world = it->real_world; + ecs_table_t *table = NULL; + bool match; + int i; -/* Serialize vector */ -static -int expr_ser_vector( - const ecs_world_t *world, - ecs_meta_type_op_t *op, - const void *base, - ecs_strbuf_t *str) -{ - ecs_vector_t *value = *(ecs_vector_t**)base; - if (!value) { - ecs_strbuf_appendstr(str, "null"); - return 0; + if (!filter->terms) { + filter->terms = filter->term_cache; } - const EcsVector *v = ecs_get(world, op->type, EcsVector); - ecs_assert(v != NULL, ECS_INTERNAL_ERROR, NULL); + flecs_iter_init(it); - const EcsComponent *comp = ecs_get(world, v->type, EcsComponent); - ecs_assert(comp != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_iter_t *chain_it = it->chain_it; + ecs_iter_kind_t kind = iter->kind; - int32_t count = ecs_vector_count(value); - void *array = ecs_vector_first_t(value, comp->size, comp->alignment); + if (chain_it) { + ecs_assert(kind == EcsIterEvalChain, ECS_INVALID_PARAMETER, NULL); + + ecs_iter_next_action_t next = chain_it->next; + do { + if (!next(chain_it)) { + goto done; + } - /* Serialize contiguous buffer of vector */ - return expr_ser_type_elements(world, v->type, array, count, str); -} + table = chain_it->table; + match = flecs_filter_match_table(world, filter, table, + it->ids, it->columns, it->subjects, it->match_indices, NULL, + true, -1); + } while (!match); -/* Forward serialization to the different type kinds */ -static -int expr_ser_type_op( - const ecs_world_t *world, - ecs_meta_type_op_t *op, - const void *ptr, - ecs_strbuf_t *str) -{ - switch(op->kind) { - case EcsOpPush: - case EcsOpPop: - /* Should not be parsed as single op */ - ecs_throw(ECS_INVALID_PARAMETER, NULL); - break; - case EcsOpEnum: - if (expr_ser_enum(world, op, ECS_OFFSET(ptr, op->offset), str)) { - goto error; - } - break; - case EcsOpBitmask: - if (expr_ser_bitmask(world, op, ECS_OFFSET(ptr, op->offset), str)) { - goto error; - } - break; - case EcsOpArray: - if (expr_ser_array(world, op, ECS_OFFSET(ptr, op->offset), str)) { - goto error; - } - break; - case EcsOpVector: - if (expr_ser_vector(world, op, ECS_OFFSET(ptr, op->offset), str)) { - goto error; - } - break; - default: - if (expr_ser_primitive(world, expr_op_to_primitive_kind(op->kind), - ECS_OFFSET(ptr, op->offset), str)) - { - /* Unknown operation */ - ecs_err("unknown serializer operation kind (%d)", op->kind); - goto error; - } - break; - } + goto yield; + } else if (kind == EcsIterEvalIndex || kind == EcsIterEvalCondition) { + ecs_term_iter_t *term_iter = &iter->term_iter; + ecs_term_t *term = &term_iter->term; + int32_t pivot_term = term->index; + bool term_is_filter = term->inout == EcsInOutFilter; - return 0; -error: - return -1; -} + do { + int32_t matches_left = iter->matches_left; + if (matches_left < 0) { + goto done; + } -/* Iterate over a slice of the type ops array */ -static -int expr_ser_type_ops( - const ecs_world_t *world, - ecs_meta_type_op_t *ops, - int32_t op_count, - const void *base, - ecs_strbuf_t *str) -{ - for (int i = 0; i < op_count; i ++) { - ecs_meta_type_op_t *op = &ops[i]; + bool first_match = matches_left == 0; + if (first_match) { + if (kind != EcsIterEvalCondition) { + /* Find new match, starting with the leading term */ + if (!term_iter_next(world, term_iter, + filter->match_prefab, filter->match_disabled)) + { + goto done; + } - if (op != ops) { - if (op->name) { - ecs_strbuf_list_next(str); - ecs_strbuf_append(str, "%s: ", op->name); - } + table = it->table = term_iter->table; + if (pivot_term != -1) { + it->ids[pivot_term] = term_iter->id; + it->subjects[pivot_term] = term_iter->subject; + it->columns[pivot_term] = term_iter->column; + } + } + } else { + /* Progress iterator to next match for table, if any */ + table = it->table; + first_match = false; - int32_t elem_count = op->count; - if (elem_count > 1 && op != ops) { - /* Serialize inline array */ - if (expr_ser_elements(world, op, op->op_count, base, - elem_count, op->size, str)) - { - return -1; + for (i = filter->term_count_actual - 1; i >= 0; i --) { + if (it->match_indices[i] > 0) { + it->match_indices[i] --; + if (!term_is_filter) { + it->columns[i] ++; + } + break; + } } + } - i += op->op_count - 1; - continue; + /* Match the remainder of the terms */ + match = flecs_filter_match_table(world, filter, table, + it->ids, it->columns, it->subjects, + it->match_indices, &matches_left, first_match, + pivot_term); + + if (kind == EcsIterEvalCondition && !matches_left) { + matches_left --; } - } - - switch(op->kind) { - case EcsOpPush: - ecs_strbuf_list_push(str, "{", ", "); - break; - case EcsOpPop: - ecs_strbuf_list_pop(str, "}"); - break; - default: - if (expr_ser_type_op(world, op, base, str)) { - goto error; + + /* Check if there are any terms which have more matching columns */ + if (!first_match) { + matches_left = 0; + for (i = 0; i < filter->term_count_actual; i ++) { + if (it->match_indices[i] > 0) { + matches_left += it->match_indices[i]; + } + } } - break; - } + + iter->matches_left = matches_left; + } while (!match); + + goto yield; } - return 0; +done: error: - return -1; + flecs_iter_fini(it); + return false; + +yield: + it->offset = 0; + flecs_iter_populate_data(world, it, table, 0, 0, it->ptrs, it->sizes); + it->is_valid = true; + return true; } -/* Iterate over the type ops of a type */ +#ifdef FLECS_TIMER + + static -int expr_ser_type( - const ecs_world_t *world, - ecs_vector_t *v_ops, - const void *base, - ecs_strbuf_t *str) -{ - ecs_meta_type_op_t *ops = ecs_vector_first(v_ops, ecs_meta_type_op_t); - int32_t count = ecs_vector_count(v_ops); - return expr_ser_type_ops(world, ops, count, base, str); +void AddTickSource(ecs_iter_t *it) { + int32_t i; + for (i = 0; i < it->count; i ++) { + ecs_set(it->world, it->entities[i], EcsTickSource, {0}); + } } -int ecs_ptr_to_expr_buf( - const ecs_world_t *world, - ecs_entity_t type, - const void *ptr, - ecs_strbuf_t *buf_out) -{ - const EcsMetaTypeSerialized *ser = ecs_get( - world, type, EcsMetaTypeSerialized); - if (ser == NULL) { - char *path = ecs_get_fullpath(world, type); - ecs_err("cannot serialize value for type '%s'", path); - ecs_os_free(path); - goto error; - } +static +void ProgressTimers(ecs_iter_t *it) { + EcsTimer *timer = ecs_term(it, EcsTimer, 1); + EcsTickSource *tick_source = ecs_term(it, EcsTickSource, 2); - if (expr_ser_type(world, ser->ops, ptr, buf_out)) { - goto error; - } + ecs_assert(timer != NULL, ECS_INTERNAL_ERROR, NULL); - return 0; -error: - return -1; -} + int i; + for (i = 0; i < it->count; i ++) { + tick_source[i].tick = false; -char* ecs_ptr_to_expr( - const ecs_world_t *world, - ecs_entity_t type, - const void* ptr) -{ - ecs_strbuf_t str = ECS_STRBUF_INIT; + if (!timer[i].active) { + continue; + } - if (ecs_ptr_to_expr_buf(world, type, ptr, &str) != 0) { - ecs_strbuf_reset(&str); - return NULL; - } + const ecs_world_info_t *info = ecs_get_world_info(it->world); + FLECS_FLOAT time_elapsed = timer[i].time + info->delta_time_raw; + FLECS_FLOAT timeout = timer[i].timeout; + + if (time_elapsed >= timeout) { + FLECS_FLOAT t = time_elapsed - timeout; + if (t > timeout) { + t = 0; + } - return ecs_strbuf_get(&str); -} + timer[i].time = t; /* Initialize with remainder */ + tick_source[i].tick = true; + tick_source[i].time_elapsed = time_elapsed; -int ecs_primitive_to_expr_buf( - const ecs_world_t *world, - ecs_primitive_kind_t kind, - const void *base, - ecs_strbuf_t *str) -{ - return expr_ser_primitive(world, kind, base, str); + if (timer[i].single_shot) { + timer[i].active = false; + } + } else { + timer[i].time = time_elapsed; + } + } } -#endif +static +void ProgressRateFilters(ecs_iter_t *it) { + EcsRateFilter *filter = ecs_term(it, EcsRateFilter, 1); + EcsTickSource *tick_dst = ecs_term(it, EcsTickSource, 2); + int i; + for (i = 0; i < it->count; i ++) { + ecs_entity_t src = filter[i].src; + bool inc = false; + filter[i].time_elapsed += it->delta_time; -#ifdef FLECS_EXPR + if (src) { + const EcsTickSource *tick_src = ecs_get(it->world, src, EcsTickSource); + if (tick_src) { + inc = tick_src->tick; + } else { + inc = true; + } + } else { + inc = true; + } -char* ecs_chresc( - char *out, - char in, - char delimiter) -{ - char *bptr = out; - switch(in) { - case '\a': - *bptr++ = '\\'; - *bptr = 'a'; - break; - case '\b': - *bptr++ = '\\'; - *bptr = 'b'; - break; - case '\f': - *bptr++ = '\\'; - *bptr = 'f'; - break; - case '\n': - *bptr++ = '\\'; - *bptr = 'n'; - break; - case '\r': - *bptr++ = '\\'; - *bptr = 'r'; - break; - case '\t': - *bptr++ = '\\'; - *bptr = 't'; - break; - case '\v': - *bptr++ = '\\'; - *bptr = 'v'; - break; - case '\\': - *bptr++ = '\\'; - *bptr = '\\'; - break; - default: - if (in == delimiter) { - *bptr++ = '\\'; - *bptr = delimiter; + if (inc) { + filter[i].tick_count ++; + bool triggered = !(filter[i].tick_count % filter[i].rate); + tick_dst[i].tick = triggered; + tick_dst[i].time_elapsed = filter[i].time_elapsed; + + if (triggered) { + filter[i].time_elapsed = 0; + } } else { - *bptr = in; + tick_dst[i].tick = false; } - break; } +} - *(++bptr) = '\0'; +static +void ProgressTickSource(ecs_iter_t *it) { + EcsTickSource *tick_src = ecs_term(it, EcsTickSource, 1); - return bptr; + /* If tick source has no filters, tick unconditionally */ + int i; + for (i = 0; i < it->count; i ++) { + tick_src[i].tick = true; + tick_src[i].time_elapsed = it->delta_time; + } } -const char* ecs_chrparse( - const char *in, - char *out) +ecs_entity_t ecs_set_timeout( + ecs_world_t *world, + ecs_entity_t timer, + FLECS_FLOAT timeout) { - const char *result = in + 1; - char ch; + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - if (in[0] == '\\') { - result ++; + timer = ecs_set(world, timer, EcsTimer, { + .timeout = timeout, + .single_shot = true, + .active = true + }); - switch(in[1]) { - case 'a': - ch = '\a'; - break; - case 'b': - ch = '\b'; - break; - case 'f': - ch = '\f'; - break; - case 'n': - ch = '\n'; - break; - case 'r': - ch = '\r'; - break; - case 't': - ch = '\t'; - break; - case 'v': - ch = '\v'; - break; - case '\\': - ch = '\\'; - break; - case '"': - ch = '"'; - break; - case '0': - ch = '\0'; - break; - case ' ': - ch = ' '; - break; - case '$': - ch = '$'; - break; - default: - goto error; - } - } else { - ch = in[0]; + EcsSystem *system_data = ecs_get_mut(world, timer, EcsSystem, NULL); + if (system_data) { + system_data->tick_source = timer; } - if (out) { - *out = ch; +error: + return timer; +} + +FLECS_FLOAT ecs_get_timeout( + const ecs_world_t *world, + ecs_entity_t timer) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(timer != 0, ECS_INVALID_PARAMETER, NULL); + + const EcsTimer *value = ecs_get(world, timer, EcsTimer); + if (value) { + return value->timeout; } +error: + return 0; +} - return result; +ecs_entity_t ecs_set_interval( + ecs_world_t *world, + ecs_entity_t timer, + FLECS_FLOAT interval) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + + timer = ecs_set(world, timer, EcsTimer, { + .timeout = interval, + .active = true + }); + + EcsSystem *system_data = ecs_get_mut(world, timer, EcsSystem, NULL); + if (system_data) { + system_data->tick_source = timer; + } error: - return NULL; + return timer; } -ecs_size_t ecs_stresc( - char *out, - ecs_size_t n, - char delimiter, - const char *in) +FLECS_FLOAT ecs_get_interval( + const ecs_world_t *world, + ecs_entity_t timer) { - const char *ptr = in; - char ch, *bptr = out, buff[3]; - ecs_size_t written = 0; - while ((ch = *ptr++)) { - if ((written += (ecs_size_t)(ecs_chresc( - buff, ch, delimiter) - buff)) <= n) - { - /* If size != 0, an out buffer must be provided. */ - ecs_check(out != NULL, ECS_INVALID_PARAMETER, NULL); - *bptr++ = buff[0]; - if ((ch = buff[1])) { - *bptr = ch; - bptr++; - } - } + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + + if (!timer) { + return 0; } - if (bptr) { - while (written < n) { - *bptr = '\0'; - bptr++; - written++; - } + const EcsTimer *value = ecs_get(world, timer, EcsTimer); + if (value) { + return value->timeout; } - return written; error: return 0; } -char* ecs_astresc( - char delimiter, - const char *in) +void ecs_start_timer( + ecs_world_t *world, + ecs_entity_t timer) { - if (!in) { - return NULL; - } + EcsTimer *ptr = ecs_get_mut(world, timer, EcsTimer, NULL); + ecs_check(ptr != NULL, ECS_INVALID_PARAMETER, NULL); + ptr->active = true; + ptr->time = 0; +error: + return; +} - ecs_size_t len = ecs_stresc(NULL, 0, delimiter, in); - char *out = ecs_os_malloc_n(char, len + 1); - ecs_stresc(out, len, delimiter, in); - out[len] = '\0'; - return out; +void ecs_stop_timer( + ecs_world_t *world, + ecs_entity_t timer) +{ + EcsTimer *ptr = ecs_get_mut(world, timer, EcsTimer, NULL); + ecs_check(ptr != NULL, ECS_INVALID_PARAMETER, NULL); + ptr->active = false; +error: + return; } -#endif +ecs_entity_t ecs_set_rate( + ecs_world_t *world, + ecs_entity_t filter, + int32_t rate, + ecs_entity_t source) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + filter = ecs_set(world, filter, EcsRateFilter, { + .rate = rate, + .src = source + }); + EcsSystem *system_data = ecs_get_mut(world, filter, EcsSystem, NULL); + if (system_data) { + system_data->tick_source = filter; + } -#ifdef FLECS_EXPR +error: + return filter; +} -const char *ecs_parse_expr_token( - const char *name, - const char *expr, - const char *ptr, - char *token) +void ecs_set_tick_source( + ecs_world_t *world, + ecs_entity_t system, + ecs_entity_t tick_source) { - const char *start = ptr; - char *token_ptr = token; + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(system != 0, ECS_INVALID_PARAMETER, NULL); + ecs_check(tick_source != 0, ECS_INVALID_PARAMETER, NULL); - while ((ptr = ecs_parse_token(name, expr, ptr, token_ptr))) { - if (ptr[0] == '|') { - token_ptr = &token_ptr[ptr - start]; - token_ptr[0] = '|'; - token_ptr[1] = '\0'; - token_ptr ++; - ptr ++; - start = ptr; - } else { - break; - } - } + EcsSystem *system_data = ecs_get_mut(world, system, EcsSystem, NULL); + ecs_check(system_data != NULL, ECS_INVALID_PARAMETER, NULL); - return ptr; + system_data->tick_source = tick_source; +error: + return; } -const char* ecs_parse_expr( - const ecs_world_t *world, - const char *ptr, - ecs_entity_t type, - void *data_out, - const ecs_parse_expr_desc_t *desc) -{ - ecs_assert(ptr != NULL, ECS_INTERNAL_ERROR, NULL); - char token[ECS_MAX_TOKEN_SIZE]; - int depth = 0; +void FlecsTimerImport( + ecs_world_t *world) +{ + ECS_MODULE(world, FlecsTimer); - const char *name = NULL; - const char *expr = NULL; + ECS_IMPORT(world, FlecsPipeline); - ptr = ecs_parse_fluff(ptr, NULL); + ecs_set_name_prefix(world, "Ecs"); - ecs_meta_cursor_t cur = ecs_meta_cursor(world, type, data_out); - if (cur.valid == false) { - return NULL; - } + flecs_bootstrap_component(world, EcsTimer); + flecs_bootstrap_component(world, EcsRateFilter); - if (desc) { - name = desc->name; - expr = desc->expr; - cur.lookup_action = desc->lookup_action; - cur.lookup_ctx = desc->lookup_ctx; - } + /* Add EcsTickSource to timers and rate filters */ + ecs_system_init(world, &(ecs_system_desc_t) { + .entity = { .name = "AddTickSource", .add = { EcsPreFrame } }, + .query.filter.terms = { + { .id = ecs_id(EcsTimer), .oper = EcsOr, .inout = EcsIn }, + { .id = ecs_id(EcsRateFilter), .oper = EcsOr, .inout = EcsIn }, + { .id = ecs_id(EcsTickSource), .oper = EcsNot, .inout = EcsOut} + }, + .callback = AddTickSource + }); - while ((ptr = ecs_parse_expr_token(name, expr, ptr, token))) { + /* Timer handling */ + ecs_system_init(world, &(ecs_system_desc_t) { + .entity = { .name = "ProgressTimers", .add = { EcsPreFrame } }, + .query.filter.terms = { + { .id = ecs_id(EcsTimer) }, + { .id = ecs_id(EcsTickSource) } + }, + .callback = ProgressTimers + }); - if (!ecs_os_strcmp(token, "{")) { - ecs_entity_t scope_type = ecs_meta_get_type(&cur); - depth ++; - if (ecs_meta_push(&cur) != 0) { - goto error; - } + /* Rate filter handling */ + ecs_system_init(world, &(ecs_system_desc_t) { + .entity = { .name = "ProgressRateFilters", .add = { EcsPreFrame } }, + .query.filter.terms = { + { .id = ecs_id(EcsRateFilter), .inout = EcsIn }, + { .id = ecs_id(EcsTickSource), .inout = EcsOut } + }, + .callback = ProgressRateFilters + }); - if (ecs_meta_is_collection(&cur)) { - char *path = ecs_get_fullpath(world, scope_type); - ecs_parser_error(name, expr, ptr - expr, - "expected '[' for collection type '%s'", path); - ecs_os_free(path); - return NULL; - } - } + /* TickSource without a timer or rate filter just increases each frame */ + ecs_system_init(world, &(ecs_system_desc_t) { + .entity = { .name = "ProgressTickSource", .add = { EcsPreFrame } }, + .query.filter.terms = { + { .id = ecs_id(EcsTickSource), .inout = EcsOut }, + { .id = ecs_id(EcsRateFilter), .oper = EcsNot }, + { .id = ecs_id(EcsTimer), .oper = EcsNot } + }, + .callback = ProgressTickSource + }); +} - else if (!ecs_os_strcmp(token, "}")) { - depth --; +#endif - if (ecs_meta_is_collection(&cur)) { - ecs_parser_error(name, expr, ptr - expr, "expected ']'"); - return NULL; - } - if (ecs_meta_pop(&cur) != 0) { - goto error; - } - } +#ifdef FLECS_EXPR - else if (!ecs_os_strcmp(token, "[")) { - depth ++; - if (ecs_meta_push(&cur) != 0) { - goto error; - } +static +int expr_ser_type( + const ecs_world_t *world, + ecs_vector_t *ser, + const void *base, + ecs_strbuf_t *str); - if (!ecs_meta_is_collection(&cur)) { - ecs_parser_error(name, expr, ptr - expr, "expected '{'"); - return NULL; - } - } +static +int expr_ser_type_ops( + const ecs_world_t *world, + ecs_meta_type_op_t *ops, + int32_t op_count, + const void *base, + ecs_strbuf_t *str); - else if (!ecs_os_strcmp(token, "]")) { - depth --; +static +int expr_ser_type_op( + const ecs_world_t *world, + ecs_meta_type_op_t *op, + const void *base, + ecs_strbuf_t *str); - if (!ecs_meta_is_collection(&cur)) { - ecs_parser_error(name, expr, ptr - expr, "expected '}'"); - return NULL; - } +static +ecs_primitive_kind_t expr_op_to_primitive_kind(ecs_meta_type_op_kind_t kind) { + return kind - EcsOpPrimitive; +} - if (ecs_meta_pop(&cur) != 0) { - goto error; - } - } +/* Serialize a primitive value */ +static +int expr_ser_primitive( + const ecs_world_t *world, + ecs_primitive_kind_t kind, + const void *base, + ecs_strbuf_t *str) +{ + const char *bool_str[] = { "false", "true" }; - else if (!ecs_os_strcmp(token, ",")) { - if (ecs_meta_next(&cur) != 0) { - goto error; + switch(kind) { + case EcsBool: + ecs_strbuf_appendstr(str, bool_str[(int)*(bool*)base]); + break; + case EcsChar: { + char chbuf[3]; + char ch = *(char*)base; + if (ch) { + ecs_chresc(chbuf, *(char*)base, '"'); + ecs_strbuf_appendstrn(str, "\"", 1); + ecs_strbuf_appendstr(str, chbuf); + ecs_strbuf_appendstrn(str, "\"", 1); + } else { + ecs_strbuf_appendstr(str, "0"); + } + break; + } + case EcsByte: + ecs_strbuf_append(str, "%u", *(uint8_t*)base); + break; + case EcsU8: + ecs_strbuf_append(str, "%u", *(uint8_t*)base); + break; + case EcsU16: + ecs_strbuf_append(str, "%u", *(uint16_t*)base); + break; + case EcsU32: + ecs_strbuf_append(str, "%u", *(uint32_t*)base); + break; + case EcsU64: + ecs_strbuf_append(str, "%llu", *(uint64_t*)base); + break; + case EcsI8: + ecs_strbuf_append(str, "%d", *(int8_t*)base); + break; + case EcsI16: + ecs_strbuf_append(str, "%d", *(int16_t*)base); + break; + case EcsI32: + ecs_strbuf_append(str, "%d", *(int32_t*)base); + break; + case EcsI64: + ecs_strbuf_append(str, "%lld", *(int64_t*)base); + break; + case EcsF32: + ecs_strbuf_appendflt(str, (double)*(float*)base); + break; + case EcsF64: + ecs_strbuf_appendflt(str, *(double*)base); + break; + case EcsIPtr: + ecs_strbuf_append(str, "%i", *(intptr_t*)base); + break; + case EcsUPtr: + ecs_strbuf_append(str, "%u", *(uintptr_t*)base); + break; + case EcsString: { + char *value = *(char**)base; + if (value) { + ecs_size_t length = ecs_stresc(NULL, 0, '"', value); + if (length == ecs_os_strlen(value)) { + ecs_strbuf_appendstrn(str, "\"", 1); + ecs_strbuf_appendstr(str, value); + ecs_strbuf_appendstrn(str, "\"", 1); + } else { + char *out = ecs_os_malloc(length + 3); + ecs_stresc(out + 1, length, '"', value); + out[0] = '"'; + out[length + 1] = '"'; + out[length + 2] = '\0'; + ecs_strbuf_appendstr_zerocpy(str, out); } + } else { + ecs_strbuf_appendstr(str, "null"); + } + break; + } + case EcsEntity: { + ecs_entity_t e = *(ecs_entity_t*)base; + if (!e) { + ecs_strbuf_appendstr(str, "0"); + } else { + char *path = ecs_get_fullpath(world, e); + ecs_assert(path != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_strbuf_appendstr(str, path); + ecs_os_free(path); } + break; + } + default: + ecs_err("invalid primitive kind"); + return -1; + } + + return 0; +} + +/* Serialize enumeration */ +static +int expr_ser_enum( + const ecs_world_t *world, + ecs_meta_type_op_t *op, + const void *base, + ecs_strbuf_t *str) +{ + const EcsEnum *enum_type = ecs_get(world, op->type, EcsEnum); + ecs_check(enum_type != NULL, ECS_INVALID_PARAMETER, NULL); + + int32_t value = *(int32_t*)base; + + /* Enumeration constants are stored in a map that is keyed on the + * enumeration value. */ + ecs_enum_constant_t *constant = ecs_map_get( + enum_type->constants, ecs_enum_constant_t, value); + if (!constant) { + char *path = ecs_get_fullpath(world, op->type); + ecs_err("value %d is not valid for enum type '%s'", value, path); + ecs_os_free(path); + goto error; + } + + ecs_strbuf_appendstr(str, ecs_get_name(world, constant->constant)); - else if (!ecs_os_strcmp(token, "null")) { - if (ecs_meta_set_null(&cur) != 0) { - goto error; - } - } + return 0; +error: + return -1; +} - else if (token[0] == '\"') { - if (ecs_meta_set_string_literal(&cur, token) != 0) { - goto error; - } - } +/* Serialize bitmask */ +static +int expr_ser_bitmask( + const ecs_world_t *world, + ecs_meta_type_op_t *op, + const void *ptr, + ecs_strbuf_t *str) +{ + const EcsBitmask *bitmask_type = ecs_get(world, op->type, EcsBitmask); + ecs_check(bitmask_type != NULL, ECS_INVALID_PARAMETER, NULL); - else { - ptr = ecs_parse_fluff(ptr, NULL); + uint32_t value = *(uint32_t*)ptr; + ecs_map_key_t key; + ecs_bitmask_constant_t *constant; + int count = 0; - if (ptr[0] == ':') { - /* Member assignment */ - ptr ++; - if (ecs_meta_member(&cur, token) != 0) { - goto error; - } - } else { - if (ecs_meta_set_string(&cur, token) != 0) { - goto error; - } - } - } + ecs_strbuf_list_push(str, "", "|"); - if (!depth) { - break; + /* Multiple flags can be set at a given time. Iterate through all the flags + * and append the ones that are set. */ + ecs_map_iter_t it = ecs_map_iter(bitmask_type->constants); + while ((constant = ecs_map_next(&it, ecs_bitmask_constant_t, &key))) { + if ((value & key) == key) { + ecs_strbuf_list_appendstr(str, + ecs_get_name(world, constant->constant)); + count ++; + value -= (uint32_t)key; } + } - ptr = ecs_parse_fluff(ptr, NULL); + if (value != 0) { + /* All bits must have been matched by a constant */ + char *path = ecs_get_fullpath(world, op->type); + ecs_err( + "value for bitmask %s contains bits (%u) that cannot be mapped to constant", + path, value); + ecs_os_free(path); + goto error; } - return ptr; -error: - return NULL; -} + if (!count) { + ecs_strbuf_list_appendstr(str, "0"); + } -#endif + ecs_strbuf_list_pop(str, ""); + return 0; +error: + return -1; +} +/* Serialize elements of a contiguous array */ +static +int expr_ser_elements( + const ecs_world_t *world, + ecs_meta_type_op_t *ops, + int32_t op_count, + const void *base, + int32_t elem_count, + int32_t elem_size, + ecs_strbuf_t *str) +{ + ecs_strbuf_list_push(str, "[", ", "); -#ifdef FLECS_SYSTEM -#endif + const void *ptr = base; -#ifdef FLECS_PIPELINE -#endif + int i; + for (i = 0; i < elem_count; i ++) { + ecs_strbuf_list_next(str); + if (expr_ser_type_ops(world, ops, op_count, ptr, str)) { + return -1; + } + ptr = ECS_OFFSET(ptr, elem_size); + } -#ifdef FLECS_STATS + ecs_strbuf_list_pop(str, "]"); -static -int32_t t_next( - int32_t t) -{ - return (t + 1) % ECS_STAT_WINDOW; + return 0; } static -int32_t t_prev( - int32_t t) +int expr_ser_type_elements( + const ecs_world_t *world, + ecs_entity_t type, + const void *base, + int32_t elem_count, + ecs_strbuf_t *str) { - return (t - 1 + ECS_STAT_WINDOW) % ECS_STAT_WINDOW; + const EcsMetaTypeSerialized *ser = ecs_get( + world, type, EcsMetaTypeSerialized); + ecs_assert(ser != NULL, ECS_INTERNAL_ERROR, NULL); + + const EcsComponent *comp = ecs_get(world, type, EcsComponent); + ecs_assert(comp != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_meta_type_op_t *ops = ecs_vector_first(ser->ops, ecs_meta_type_op_t); + int32_t op_count = ecs_vector_count(ser->ops); + + return expr_ser_elements( + world, ops, op_count, base, elem_count, comp->size, str); } +/* Serialize array */ static -void _record_gauge( - ecs_gauge_t *m, - int32_t t, - float value) +int expr_ser_array( + const ecs_world_t *world, + ecs_meta_type_op_t *op, + const void *ptr, + ecs_strbuf_t *str) { - m->avg[t] = value; - m->min[t] = value; - m->max[t] = value; + const EcsArray *a = ecs_get(world, op->type, EcsArray); + ecs_assert(a != NULL, ECS_INTERNAL_ERROR, NULL); + + return expr_ser_type_elements( + world, a->type, ptr, a->count, str); } +/* Serialize vector */ static -float _record_counter( - ecs_counter_t *m, - int32_t t, - float value) +int expr_ser_vector( + const ecs_world_t *world, + ecs_meta_type_op_t *op, + const void *base, + ecs_strbuf_t *str) { - int32_t tp = t_prev(t); - float prev = m->value[tp]; - m->value[t] = value; - _record_gauge((ecs_gauge_t*)m, t, value - prev); - return value - prev; -} + ecs_vector_t *value = *(ecs_vector_t**)base; + if (!value) { + ecs_strbuf_appendstr(str, "null"); + return 0; + } -/* Macro's to silence conversion warnings without adding casts everywhere */ -#define record_gauge(m, t, value)\ - _record_gauge(m, t, (float)value) + const EcsVector *v = ecs_get(world, op->type, EcsVector); + ecs_assert(v != NULL, ECS_INTERNAL_ERROR, NULL); -#define record_counter(m, t, value)\ - _record_counter(m, t, (float)value) + const EcsComponent *comp = ecs_get(world, v->type, EcsComponent); + ecs_assert(comp != NULL, ECS_INTERNAL_ERROR, NULL); -static -void print_value( - const char *name, - float value) -{ - ecs_size_t len = ecs_os_strlen(name); - printf("%s: %*s %.2f\n", name, 32 - len, "", (double)value); + int32_t count = ecs_vector_count(value); + void *array = ecs_vector_first_t(value, comp->size, comp->alignment); + + /* Serialize contiguous buffer of vector */ + return expr_ser_type_elements(world, v->type, array, count, str); } +/* Forward serialization to the different type kinds */ static -void print_gauge( - const char *name, - int32_t t, - const ecs_gauge_t *m) +int expr_ser_type_op( + const ecs_world_t *world, + ecs_meta_type_op_t *op, + const void *ptr, + ecs_strbuf_t *str) { - print_value(name, m->avg[t]); + switch(op->kind) { + case EcsOpPush: + case EcsOpPop: + /* Should not be parsed as single op */ + ecs_throw(ECS_INVALID_PARAMETER, NULL); + break; + case EcsOpEnum: + if (expr_ser_enum(world, op, ECS_OFFSET(ptr, op->offset), str)) { + goto error; + } + break; + case EcsOpBitmask: + if (expr_ser_bitmask(world, op, ECS_OFFSET(ptr, op->offset), str)) { + goto error; + } + break; + case EcsOpArray: + if (expr_ser_array(world, op, ECS_OFFSET(ptr, op->offset), str)) { + goto error; + } + break; + case EcsOpVector: + if (expr_ser_vector(world, op, ECS_OFFSET(ptr, op->offset), str)) { + goto error; + } + break; + default: + if (expr_ser_primitive(world, expr_op_to_primitive_kind(op->kind), + ECS_OFFSET(ptr, op->offset), str)) + { + /* Unknown operation */ + ecs_err("unknown serializer operation kind (%d)", op->kind); + goto error; + } + break; + } + + return 0; +error: + return -1; } +/* Iterate over a slice of the type ops array */ static -void print_counter( - const char *name, - int32_t t, - const ecs_counter_t *m) +int expr_ser_type_ops( + const ecs_world_t *world, + ecs_meta_type_op_t *ops, + int32_t op_count, + const void *base, + ecs_strbuf_t *str) { - print_value(name, m->rate.avg[t]); -} + for (int i = 0; i < op_count; i ++) { + ecs_meta_type_op_t *op = &ops[i]; -void ecs_gauge_reduce( - ecs_gauge_t *dst, - int32_t t_dst, - ecs_gauge_t *src, - int32_t t_src) -{ - ecs_check(dst != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(src != NULL, ECS_INVALID_PARAMETER, NULL); + if (op != ops) { + if (op->name) { + ecs_strbuf_list_next(str); + ecs_strbuf_append(str, "%s: ", op->name); + } - bool min_set = false; - dst->min[t_dst] = 0; - dst->avg[t_dst] = 0; - dst->max[t_dst] = 0; + int32_t elem_count = op->count; + if (elem_count > 1 && op != ops) { + /* Serialize inline array */ + if (expr_ser_elements(world, op, op->op_count, base, + elem_count, op->size, str)) + { + return -1; + } - int32_t i; - for (i = 0; i < ECS_STAT_WINDOW; i ++) { - int32_t t = (t_src + i) % ECS_STAT_WINDOW; - dst->avg[t_dst] += src->avg[t] / (float)ECS_STAT_WINDOW; - if (!min_set || (src->min[t] < dst->min[t_dst])) { - dst->min[t_dst] = src->min[t]; - min_set = true; + i += op->op_count - 1; + continue; + } } - if ((src->max[t] > dst->max[t_dst])) { - dst->max[t_dst] = src->max[t]; + + switch(op->kind) { + case EcsOpPush: + ecs_strbuf_list_push(str, "{", ", "); + break; + case EcsOpPop: + ecs_strbuf_list_pop(str, "}"); + break; + default: + if (expr_ser_type_op(world, op, base, str)) { + goto error; + } + break; } } + + return 0; error: - return; + return -1; } -void ecs_get_world_stats( +/* Iterate over the type ops of a type */ +static +int expr_ser_type( const ecs_world_t *world, - ecs_world_stats_t *s) + ecs_vector_t *v_ops, + const void *base, + ecs_strbuf_t *str) { - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(s != NULL, ECS_INVALID_PARAMETER, NULL); - - world = ecs_get_world(world); - - int32_t t = s->t = t_next(s->t); - - float delta_world_time = record_counter(&s->world_time_total_raw, t, world->stats.world_time_total_raw); - record_counter(&s->world_time_total, t, world->stats.world_time_total); - record_counter(&s->frame_time_total, t, world->stats.frame_time_total); - record_counter(&s->system_time_total, t, world->stats.system_time_total); - record_counter(&s->merge_time_total, t, world->stats.merge_time_total); - - float delta_frame_count = record_counter(&s->frame_count_total, t, world->stats.frame_count_total); - record_counter(&s->merge_count_total, t, world->stats.merge_count_total); - record_counter(&s->pipeline_build_count_total, t, world->stats.pipeline_build_count_total); - record_counter(&s->systems_ran_frame, t, world->stats.systems_ran_frame); + ecs_meta_type_op_t *ops = ecs_vector_first(v_ops, ecs_meta_type_op_t); + int32_t count = ecs_vector_count(v_ops); + return expr_ser_type_ops(world, ops, count, base, str); +} - if (delta_world_time != 0.0f && delta_frame_count != 0.0f) { - record_gauge( - &s->fps, t, 1.0f / (delta_world_time / (float)delta_frame_count)); - } else { - record_gauge(&s->fps, t, 0); +int ecs_ptr_to_expr_buf( + const ecs_world_t *world, + ecs_entity_t type, + const void *ptr, + ecs_strbuf_t *buf_out) +{ + const EcsMetaTypeSerialized *ser = ecs_get( + world, type, EcsMetaTypeSerialized); + if (ser == NULL) { + char *path = ecs_get_fullpath(world, type); + ecs_err("cannot serialize value for type '%s'", path); + ecs_os_free(path); + goto error; } - record_gauge(&s->entity_count, t, flecs_sparse_count(world->store.entity_index)); - record_gauge(&s->component_count, t, ecs_count_id(world, ecs_id(EcsComponent))); - record_gauge(&s->query_count, t, flecs_sparse_count(world->queries)); - record_gauge(&s->system_count, t, ecs_count_id(world, ecs_id(EcsSystem))); - - record_counter(&s->new_count, t, world->new_count); - record_counter(&s->bulk_new_count, t, world->bulk_new_count); - record_counter(&s->delete_count, t, world->delete_count); - record_counter(&s->clear_count, t, world->clear_count); - record_counter(&s->add_count, t, world->add_count); - record_counter(&s->remove_count, t, world->remove_count); - record_counter(&s->set_count, t, world->set_count); - record_counter(&s->discard_count, t, world->discard_count); - - /* Compute table statistics */ - int32_t empty_table_count = 0; - int32_t singleton_table_count = 0; - int32_t matched_table_count = 0, matched_entity_count = 0; - - int32_t i, count = flecs_sparse_count(world->store.tables); - for (i = 0; i < count; i ++) { - ecs_table_t *table = flecs_sparse_get_dense(world->store.tables, ecs_table_t, i); - int32_t entity_count = ecs_table_count(table); - - if (!entity_count) { - empty_table_count ++; - } - - /* Singleton tables are tables that have just one entity that also has - * itself in the table type. */ - if (entity_count == 1) { - ecs_entity_t *entities = ecs_vector_first( - table->storage.entities, ecs_entity_t); - if (ecs_type_has_id(world, table->type, entities[0], false)) { - singleton_table_count ++; - } - } - - /* If this table matches with queries and is not empty, increase the - * matched table & matched entity count. These statistics can be used to - * compute actual fragmentation ratio for queries. */ - int32_t queries_matched = ecs_vector_count(table->queries); - if (queries_matched && entity_count) { - matched_table_count ++; - matched_entity_count += entity_count; - } + if (expr_ser_type(world, ser->ops, ptr, buf_out)) { + goto error; } - record_gauge(&s->matched_table_count, t, matched_table_count); - record_gauge(&s->matched_entity_count, t, matched_entity_count); - - record_gauge(&s->table_count, t, count); - record_gauge(&s->empty_table_count, t, empty_table_count); - record_gauge(&s->singleton_table_count, t, singleton_table_count); - + return 0; error: - return; + return -1; } -void ecs_get_query_stats( - const ecs_world_t *world, - const ecs_query_t *query, - ecs_query_stats_t *s) +char* ecs_ptr_to_expr( + const ecs_world_t *world, + ecs_entity_t type, + const void* ptr) { - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(query != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(s != NULL, ECS_INVALID_PARAMETER, NULL); - (void)world; + ecs_strbuf_t str = ECS_STRBUF_INIT; - int32_t t = s->t = t_next(s->t); + if (ecs_ptr_to_expr_buf(world, type, ptr, &str) != 0) { + ecs_strbuf_reset(&str); + return NULL; + } - ecs_iter_t it = ecs_query_iter(world, (ecs_query_t*)query); - record_gauge(&s->matched_entity_count, t, ecs_iter_count(&it)); - record_gauge(&s->matched_table_count, t, ecs_query_table_count(query)); - record_gauge(&s->matched_empty_table_count, t, - ecs_query_empty_table_count(query)); -error: - return; + return ecs_strbuf_get(&str); } -#ifdef FLECS_SYSTEM -bool ecs_get_system_stats( +int ecs_primitive_to_expr_buf( const ecs_world_t *world, - ecs_entity_t system, - ecs_system_stats_t *s) + ecs_primitive_kind_t kind, + const void *base, + ecs_strbuf_t *str) { - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(s != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(system != 0, ECS_INVALID_PARAMETER, NULL); + return expr_ser_primitive(world, kind, base, str); +} - world = ecs_get_world(world); +#endif - const EcsSystem *ptr = ecs_get(world, system, EcsSystem); - if (!ptr) { - return false; - } - ecs_get_query_stats(world, ptr->query, &s->query_stats); - int32_t t = s->query_stats.t; +#ifdef FLECS_EXPR + +char* ecs_chresc( + char *out, + char in, + char delimiter) +{ + char *bptr = out; + switch(in) { + case '\a': + *bptr++ = '\\'; + *bptr = 'a'; + break; + case '\b': + *bptr++ = '\\'; + *bptr = 'b'; + break; + case '\f': + *bptr++ = '\\'; + *bptr = 'f'; + break; + case '\n': + *bptr++ = '\\'; + *bptr = 'n'; + break; + case '\r': + *bptr++ = '\\'; + *bptr = 'r'; + break; + case '\t': + *bptr++ = '\\'; + *bptr = 't'; + break; + case '\v': + *bptr++ = '\\'; + *bptr = 'v'; + break; + case '\\': + *bptr++ = '\\'; + *bptr = '\\'; + break; + default: + if (in == delimiter) { + *bptr++ = '\\'; + *bptr = delimiter; + } else { + *bptr = in; + } + break; + } - record_counter(&s->time_spent, t, ptr->time_spent); - record_counter(&s->invoke_count, t, ptr->invoke_count); - record_gauge(&s->active, t, !ecs_has_id(world, system, EcsInactive)); - record_gauge(&s->enabled, t, !ecs_has_id(world, system, EcsDisabled)); + *(++bptr) = '\0'; - return true; -error: - return false; + return bptr; } -#endif +const char* ecs_chrparse( + const char *in, + char *out) +{ + const char *result = in + 1; + char ch; -#ifdef FLECS_PIPELINE + if (in[0] == '\\') { + result ++; -static -ecs_system_stats_t* get_system_stats( - ecs_map_t *systems, - ecs_entity_t system) -{ - ecs_check(systems != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(system != 0, ECS_INVALID_PARAMETER, NULL); + switch(in[1]) { + case 'a': + ch = '\a'; + break; + case 'b': + ch = '\b'; + break; + case 'f': + ch = '\f'; + break; + case 'n': + ch = '\n'; + break; + case 'r': + ch = '\r'; + break; + case 't': + ch = '\t'; + break; + case 'v': + ch = '\v'; + break; + case '\\': + ch = '\\'; + break; + case '"': + ch = '"'; + break; + case '0': + ch = '\0'; + break; + case ' ': + ch = ' '; + break; + case '$': + ch = '$'; + break; + default: + goto error; + } + } else { + ch = in[0]; + } - ecs_system_stats_t *s = ecs_map_get(systems, ecs_system_stats_t, system); - if (!s) { - s = ecs_map_ensure(systems, ecs_system_stats_t, system); + if (out) { + *out = ch; } - return s; + return result; error: return NULL; } -bool ecs_get_pipeline_stats( - ecs_world_t *stage, - ecs_entity_t pipeline, - ecs_pipeline_stats_t *s) +ecs_size_t ecs_stresc( + char *out, + ecs_size_t n, + char delimiter, + const char *in) { - ecs_check(stage != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(s != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(pipeline != 0, ECS_INVALID_PARAMETER, NULL); - - const ecs_world_t *world = ecs_get_world(stage); - - const EcsPipelineQuery *pq = ecs_get(world, pipeline, EcsPipelineQuery); - if (!pq) { - return false; + const char *ptr = in; + char ch, *bptr = out, buff[3]; + ecs_size_t written = 0; + while ((ch = *ptr++)) { + if ((written += (ecs_size_t)(ecs_chresc( + buff, ch, delimiter) - buff)) <= n) + { + /* If size != 0, an out buffer must be provided. */ + ecs_check(out != NULL, ECS_INVALID_PARAMETER, NULL); + *bptr++ = buff[0]; + if ((ch = buff[1])) { + *bptr = ch; + bptr++; + } + } } - int32_t sys_count = 0, active_sys_count = 0; - - /* Count number of active systems */ - ecs_iter_t it = ecs_query_iter(stage, pq->query); - while (ecs_query_next(&it)) { - active_sys_count += it.count; + if (bptr) { + while (written < n) { + *bptr = '\0'; + bptr++; + written++; + } } + return written; +error: + return 0; +} - /* Count total number of systems in pipeline */ - it = ecs_query_iter(stage, pq->build_query); - while (ecs_query_next(&it)) { - sys_count += it.count; - } - - /* Also count synchronization points */ - ecs_vector_t *ops = pq->ops; - ecs_pipeline_op_t *op = ecs_vector_first(ops, ecs_pipeline_op_t); - ecs_pipeline_op_t *op_last = ecs_vector_last(ops, ecs_pipeline_op_t); - int32_t pip_count = active_sys_count + ecs_vector_count(ops); - - if (!sys_count) { - return false; +char* ecs_astresc( + char delimiter, + const char *in) +{ + if (!in) { + return NULL; } - if (s->system_stats && !sys_count) { - ecs_map_free(s->system_stats); - } - if (!s->system_stats && sys_count) { - s->system_stats = ecs_map_new(ecs_system_stats_t, sys_count); - } - if (!sys_count) { - s->system_stats = NULL; - } + ecs_size_t len = ecs_stresc(NULL, 0, delimiter, in); + char *out = ecs_os_malloc_n(char, len + 1); + ecs_stresc(out, len, delimiter, in); + out[len] = '\0'; + return out; +} - /* Make sure vector is large enough to store all systems & sync points */ - ecs_entity_t *systems = NULL; - if (pip_count) { - ecs_vector_set_count(&s->systems, ecs_entity_t, pip_count); - systems = ecs_vector_first(s->systems, ecs_entity_t); +#endif - /* Populate systems vector, keep track of sync points */ - it = ecs_query_iter(stage, pq->query); - - int32_t i, i_system = 0, ran_since_merge = 0; - while (ecs_query_next(&it)) { - for (i = 0; i < it.count; i ++) { - systems[i_system ++] = it.entities[i]; - ran_since_merge ++; - if (op != op_last && ran_since_merge == op->count) { - ran_since_merge = 0; - op++; - systems[i_system ++] = 0; /* 0 indicates a merge point */ - } - } - } - systems[i_system ++] = 0; /* Last merge */ - ecs_assert(pip_count == i_system, ECS_INTERNAL_ERROR, NULL); - } else { - ecs_vector_free(s->systems); - s->systems = NULL; - } +#ifdef FLECS_EXPR - /* Separately populate system stats map from build query, which includes - * systems that aren't currently active */ - it = ecs_query_iter(stage, pq->build_query); - while (ecs_query_next(&it)) { - int i; - for (i = 0; i < it.count; i ++) { - ecs_system_stats_t *sys_stats = get_system_stats( - s->system_stats, it.entities[i]); - ecs_get_system_stats(world, it.entities[i], sys_stats); +const char *ecs_parse_expr_token( + const char *name, + const char *expr, + const char *ptr, + char *token) +{ + const char *start = ptr; + char *token_ptr = token; + + while ((ptr = ecs_parse_token(name, expr, ptr, token_ptr))) { + if (ptr[0] == '|') { + token_ptr = &token_ptr[ptr - start]; + token_ptr[0] = '|'; + token_ptr[1] = '\0'; + token_ptr ++; + ptr ++; + start = ptr; + } else { + break; } } - return true; -error: - return false; -} - -void ecs_pipeline_stats_fini( - ecs_pipeline_stats_t *stats) -{ - ecs_map_free(stats->system_stats); - ecs_vector_free(stats->systems); + return ptr; } -#endif - -void ecs_dump_world_stats( +const char* ecs_parse_expr( const ecs_world_t *world, - const ecs_world_stats_t *s) + const char *ptr, + ecs_entity_t type, + void *data_out, + const ecs_parse_expr_desc_t *desc) { - int32_t t = s->t; + ecs_assert(ptr != NULL, ECS_INTERNAL_ERROR, NULL); + char token[ECS_MAX_TOKEN_SIZE]; + int depth = 0; - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(s != NULL, ECS_INVALID_PARAMETER, NULL); + const char *name = NULL; + const char *expr = NULL; - world = ecs_get_world(world); - - print_counter("Frame", t, &s->frame_count_total); - printf("-------------------------------------\n"); - print_counter("pipeline rebuilds", t, &s->pipeline_build_count_total); - print_counter("systems ran last frame", t, &s->systems_ran_frame); - printf("\n"); - print_value("target FPS", world->stats.target_fps); - print_value("time scale", world->stats.time_scale); - printf("\n"); - print_gauge("actual FPS", t, &s->fps); - print_counter("frame time", t, &s->frame_time_total); - print_counter("system time", t, &s->system_time_total); - print_counter("merge time", t, &s->merge_time_total); - print_counter("simulation time elapsed", t, &s->world_time_total); - printf("\n"); - print_gauge("entity count", t, &s->entity_count); - print_gauge("component count", t, &s->component_count); - print_gauge("query count", t, &s->query_count); - print_gauge("system count", t, &s->system_count); - print_gauge("table count", t, &s->table_count); - print_gauge("singleton table count", t, &s->singleton_table_count); - print_gauge("empty table count", t, &s->empty_table_count); - printf("\n"); - print_counter("deferred new operations", t, &s->new_count); - print_counter("deferred bulk_new operations", t, &s->bulk_new_count); - print_counter("deferred delete operations", t, &s->delete_count); - print_counter("deferred clear operations", t, &s->clear_count); - print_counter("deferred add operations", t, &s->add_count); - print_counter("deferred remove operations", t, &s->remove_count); - print_counter("deferred set operations", t, &s->set_count); - print_counter("discarded operations", t, &s->discard_count); - printf("\n"); - -error: - return; -} + ptr = ecs_parse_fluff(ptr, NULL); -#endif + ecs_meta_cursor_t cur = ecs_meta_cursor(world, type, data_out); + if (cur.valid == false) { + return NULL; + } + if (desc) { + name = desc->name; + expr = desc->expr; + cur.lookup_action = desc->lookup_action; + cur.lookup_ctx = desc->lookup_ctx; + } -#ifdef FLECS_SNAPSHOT + while ((ptr = ecs_parse_expr_token(name, expr, ptr, token))) { + if (!ecs_os_strcmp(token, "{")) { + ecs_entity_t scope_type = ecs_meta_get_type(&cur); + depth ++; + if (ecs_meta_push(&cur) != 0) { + goto error; + } -/* World snapshot */ -struct ecs_snapshot_t { - ecs_world_t *world; - ecs_sparse_t *entity_index; - ecs_vector_t *tables; - ecs_entity_t last_id; - ecs_filter_t filter; -}; + if (ecs_meta_is_collection(&cur)) { + char *path = ecs_get_fullpath(world, scope_type); + ecs_parser_error(name, expr, ptr - expr, + "expected '[' for collection type '%s'", path); + ecs_os_free(path); + return NULL; + } + } -/** Small footprint data structure for storing data associated with a table. */ -typedef struct ecs_table_leaf_t { - ecs_table_t *table; - ecs_vector_t *type; - ecs_data_t *data; -} ecs_table_leaf_t; + else if (!ecs_os_strcmp(token, "}")) { + depth --; -static -ecs_data_t* duplicate_data( - const ecs_world_t *world, - ecs_table_t *table, - ecs_data_t *main_data) -{ - if (!ecs_table_count(table)) { - return NULL; - } + if (ecs_meta_is_collection(&cur)) { + ecs_parser_error(name, expr, ptr - expr, "expected ']'"); + return NULL; + } - ecs_data_t *result = ecs_os_calloc(ECS_SIZEOF(ecs_data_t)); + if (ecs_meta_pop(&cur) != 0) { + goto error; + } + } - ecs_type_t storage_type = table->storage_type; - int32_t i, column_count = ecs_vector_count(storage_type); - ecs_entity_t *components = ecs_vector_first(storage_type, ecs_entity_t); + else if (!ecs_os_strcmp(token, "[")) { + depth ++; + if (ecs_meta_push(&cur) != 0) { + goto error; + } - result->columns = ecs_os_memdup( - main_data->columns, ECS_SIZEOF(ecs_column_t) * column_count); + if (!ecs_meta_is_collection(&cur)) { + ecs_parser_error(name, expr, ptr - expr, "expected '{'"); + return NULL; + } + } - /* Copy entities */ - result->entities = ecs_vector_copy(main_data->entities, ecs_entity_t); - ecs_entity_t *entities = ecs_vector_first(result->entities, ecs_entity_t); + else if (!ecs_os_strcmp(token, "]")) { + depth --; - /* Copy record ptrs */ - result->record_ptrs = ecs_vector_copy(main_data->record_ptrs, ecs_record_t*); + if (!ecs_meta_is_collection(&cur)) { + ecs_parser_error(name, expr, ptr - expr, "expected '}'"); + return NULL; + } - /* Copy each column */ - for (i = 0; i < column_count; i ++) { - ecs_entity_t component = components[i]; - ecs_column_t *column = &result->columns[i]; + if (ecs_meta_pop(&cur) != 0) { + goto error; + } + } - component = ecs_get_typeid(world, component); + else if (!ecs_os_strcmp(token, ",")) { + if (ecs_meta_next(&cur) != 0) { + goto error; + } + } - const ecs_type_info_t *cdata = flecs_get_c_info(world, component); - int16_t size = column->size; - int16_t alignment = column->alignment; - ecs_copy_t copy; + else if (!ecs_os_strcmp(token, "null")) { + if (ecs_meta_set_null(&cur) != 0) { + goto error; + } + } - if (cdata && (copy = cdata->lifecycle.copy)) { - int32_t count = ecs_vector_count(column->data); - ecs_vector_t *dst_vec = ecs_vector_new_t(size, alignment, count); - ecs_vector_set_count_t(&dst_vec, size, alignment, count); - void *dst_ptr = ecs_vector_first_t(dst_vec, size, alignment); - void *ctx = cdata->lifecycle.ctx; - - ecs_xtor_t ctor = cdata->lifecycle.ctor; - if (ctor) { - ctor((ecs_world_t*)world, component, entities, dst_ptr, - flecs_itosize(size), count, ctx); + else if (token[0] == '\"') { + if (ecs_meta_set_string_literal(&cur, token) != 0) { + goto error; } + } - void *src_ptr = ecs_vector_first_t(column->data, size, alignment); - copy((ecs_world_t*)world, component, entities, entities, dst_ptr, - src_ptr, flecs_itosize(size), count, ctx); + else { + ptr = ecs_parse_fluff(ptr, NULL); - column->data = dst_vec; - } else { - column->data = ecs_vector_copy_t(column->data, size, alignment); + if (ptr[0] == ':') { + /* Member assignment */ + ptr ++; + if (ecs_meta_member(&cur, token) != 0) { + goto error; + } + } else { + if (ecs_meta_set_string(&cur, token) != 0) { + goto error; + } + } } - } - return result; -} + if (!depth) { + break; + } -static -void snapshot_table( - const ecs_world_t *world, - ecs_snapshot_t *snapshot, - ecs_table_t *table) -{ - if (table->flags & EcsTableHasBuiltins) { - return; + ptr = ecs_parse_fluff(ptr, NULL); } - ecs_table_leaf_t *l = ecs_vector_get( - snapshot->tables, ecs_table_leaf_t, (int32_t)table->id); - ecs_assert(l != NULL, ECS_INTERNAL_ERROR, NULL); - - l->table = table; - l->type = ecs_vector_copy(table->type, ecs_id_t); - l->data = duplicate_data(world, table, &table->storage); + return ptr; +error: + return NULL; } -static -ecs_snapshot_t* snapshot_create( - const ecs_world_t *world, - const ecs_sparse_t *entity_index, - ecs_iter_t *iter, - ecs_iter_next_action_t next) -{ - ecs_snapshot_t *result = ecs_os_calloc_t(ecs_snapshot_t); - ecs_assert(result != NULL, ECS_OUT_OF_MEMORY, NULL); +#endif - result->world = (ecs_world_t*)world; +#ifdef FLECS_REST - /* If no iterator is provided, the snapshot will be taken of the entire - * world, and we can simply copy the entity index as it will be restored - * entirely upon snapshote restore. */ - if (!iter && entity_index) { - result->entity_index = flecs_sparse_copy(entity_index); +typedef struct { + ecs_world_t *world; + ecs_entity_t entity; + ecs_http_server_t *srv; + int32_t rc; +} ecs_rest_ctx_t; + +static ECS_COPY(EcsRest, dst, src, { + ecs_rest_ctx_t *impl = src->impl; + if (impl) { + impl->rc ++; } - /* Create vector with as many elements as tables, so we can store the - * snapshot tables at their element ids. When restoring a snapshot, the code - * will run a diff between the tables in the world and the snapshot, to see - * which of the world tables still exist, no longer exist, or need to be - * deleted. */ - uint64_t t, table_count = flecs_sparse_last_id(world->store.tables) + 1; - result->tables = ecs_vector_new(ecs_table_leaf_t, (int32_t)table_count); - ecs_vector_set_count(&result->tables, ecs_table_leaf_t, (int32_t)table_count); - ecs_table_leaf_t *arr = ecs_vector_first(result->tables, ecs_table_leaf_t); + ecs_os_strset(&dst->ipaddr, src->ipaddr); + dst->port = src->port; + dst->impl = impl; +}) - /* Array may have holes, so initialize with 0 */ - ecs_os_memset_n(arr, 0, ecs_table_leaf_t, table_count); +static ECS_MOVE(EcsRest, dst, src, { + *dst = *src; + src->ipaddr = NULL; + src->impl = NULL; +}) - /* Iterate tables in iterator */ - if (iter) { - while (next(iter)) { - ecs_table_t *table = iter->table; - snapshot_table(world, result, table); - } - } else { - for (t = 0; t < table_count; t ++) { - ecs_table_t *table = flecs_sparse_get( - world->store.tables, ecs_table_t, t); - snapshot_table(world, result, table); +static ECS_DTOR(EcsRest, ptr, { + ecs_rest_ctx_t *impl = ptr->impl; + if (impl) { + impl->rc --; + if (!impl->rc) { + ecs_http_server_fini(impl->srv); + ecs_os_free(impl); } } + ecs_os_free(ptr->ipaddr); +}) - return result; -} +static char *rest_last_err; -/** Create a snapshot */ -ecs_snapshot_t* ecs_snapshot_take( - ecs_world_t *stage) +static +void rest_capture_log( + int32_t level, + const char *file, + int32_t line, + const char *msg) { - const ecs_world_t *world = ecs_get_world(stage); - - ecs_snapshot_t *result = snapshot_create( - world, - world->store.entity_index, - NULL, - NULL); + (void)file; (void)line; - result->last_id = world->stats.last_id; + if (!rest_last_err && level < 0) { + rest_last_err = ecs_os_strdup(msg); + } +} +static +char* rest_get_captured_log(void) { + char *result = rest_last_err; + rest_last_err = NULL; return result; } -/** Create a filtered snapshot */ -ecs_snapshot_t* ecs_snapshot_take_w_iter( - ecs_iter_t *iter) +static +void reply_error( + ecs_http_reply_t *reply, + const char *msg) { - ecs_world_t *world = iter->world; - ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); - - ecs_snapshot_t *result = snapshot_create( - world, - world->store.entity_index, - iter, - iter ? iter->next : NULL); + ecs_strbuf_append(&reply->body, "{\"error\":\"%s\"}", msg); +} - result->last_id = world->stats.last_id; +static +void rest_bool_param( + const ecs_http_request_t *req, + const char *name, + bool *value_out) +{ + const char *value = ecs_http_get_param(req, name); + if (value) { + if (!ecs_os_strcmp(value, "true")) { + value_out[0] = true; + } else { + value_out[0] = false; + } + } +} - return result; +static +void rest_parse_json_ser_entity_params( + ecs_entity_to_json_desc_t *desc, + const ecs_http_request_t *req) +{ + rest_bool_param(req, "path", &desc->serialize_path); + rest_bool_param(req, "base", &desc->serialize_base); + rest_bool_param(req, "values", &desc->serialize_values); + rest_bool_param(req, "type_info", &desc->serialize_type_info); } -/* Restoring an unfiltered snapshot restores the world to the exact state it was - * when the snapshot was taken. */ static -void restore_unfiltered( - ecs_world_t *world, - ecs_snapshot_t *snapshot) +void rest_parse_json_ser_iter_params( + ecs_iter_to_json_desc_t *desc, + const ecs_http_request_t *req) { - flecs_sparse_restore(world->store.entity_index, snapshot->entity_index); - flecs_sparse_free(snapshot->entity_index); - - world->stats.last_id = snapshot->last_id; + rest_bool_param(req, "term_ids", &desc->serialize_term_ids); + rest_bool_param(req, "ids", &desc->serialize_ids); + rest_bool_param(req, "subjects", &desc->serialize_subjects); + rest_bool_param(req, "variables", &desc->serialize_variables); + rest_bool_param(req, "is_set", &desc->serialize_is_set); + rest_bool_param(req, "values", &desc->serialize_values); + rest_bool_param(req, "entities", &desc->serialize_entities); + rest_bool_param(req, "duration", &desc->measure_eval_duration); + rest_bool_param(req, "type_info", &desc->serialize_type_info); +} - ecs_table_leaf_t *leafs = ecs_vector_first( - snapshot->tables, ecs_table_leaf_t); - int32_t i, count = (int32_t)flecs_sparse_last_id(world->store.tables); - int32_t snapshot_count = ecs_vector_count(snapshot->tables); +static +bool rest_reply( + const ecs_http_request_t* req, + ecs_http_reply_t *reply, + void *ctx) +{ + ecs_rest_ctx_t *impl = ctx; + ecs_world_t *world = impl->world; - for (i = 0; i <= count; i ++) { - ecs_table_t *world_table = flecs_sparse_get( - world->store.tables, ecs_table_t, (uint32_t)i); + ecs_strbuf_appendstr(&reply->headers, "Access-Control-Allow-Origin: *\r\n"); - if (world_table && (world_table->flags & EcsTableHasBuiltins)) { - continue; - } + if (req->method == EcsHttpGet) { + /* Entity endpoint */ + if (!ecs_os_strncmp(req->path, "entity/", 7)) { + char *path = &req->path[7]; + ecs_dbg("rest: request entity '%s'", path); - ecs_table_leaf_t *snapshot_table = NULL; - if (i < snapshot_count) { - snapshot_table = &leafs[i]; - if (!snapshot_table->table) { - snapshot_table = NULL; + ecs_entity_t e = ecs_lookup_path_w_sep( + world, 0, path, "/", NULL, false); + if (!e) { + e = ecs_lookup_path_w_sep( + world, EcsFlecsCore, path, "/", NULL, false); + if (!e) { + ecs_dbg("rest: requested entity '%s' does not exist", path); + reply_error(reply, "entity not found"); + return true; + } } - } - - /* If the world table no longer exists but the snapshot table does, - * reinsert it */ - if (!world_table && snapshot_table) { - ecs_ids_t type = { - .array = ecs_vector_first(snapshot_table->type, ecs_id_t), - .count = ecs_vector_count(snapshot_table->type) - }; - ecs_table_t *table = flecs_table_find_or_create(world, &type); - ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_entity_to_json_desc_t desc = ECS_ENTITY_TO_JSON_INIT; + rest_parse_json_ser_entity_params(&desc, req); - if (snapshot_table->data) { - flecs_table_replace_data(world, table, snapshot_table->data); - } + ecs_entity_to_json_buf(world, e, &reply->body, &desc); + return true; - /* If the world table still exists, replace its data */ - } else if (world_table && snapshot_table) { - if (snapshot_table->data) { - flecs_table_replace_data( - world, world_table, snapshot_table->data); - } else { - flecs_table_clear_data( - world, world_table, &world_table->storage); - flecs_table_init_data(world, world_table); + /* Query endpoint */ + } else if (!ecs_os_strcmp(req->path, "query")) { + const char *q = ecs_http_get_param(req, "q"); + if (!q) { + ecs_strbuf_appendstr(&reply->body, "Missing parameter 'q'"); + reply->code = 400; /* bad request */ + return true; } - - /* If the snapshot table doesn't exist, this table was created after the - * snapshot was taken and needs to be deleted */ - } else if (world_table && !snapshot_table) { - /* Deleting a table invokes OnRemove triggers & updates the entity - * index. That is not what we want, since entities may no longer be - * valid (if they don't exist in the snapshot) or may have been - * restored in a different table. Therefore first clear the data - * from the table (which doesn't invoke triggers), and then delete - * the table. */ - flecs_table_clear_data(world, world_table, &world_table->storage); - flecs_delete_table(world, world_table); - - /* If there is no world & snapshot table, nothing needs to be done */ - } else { } - if (snapshot_table) { - ecs_os_free(snapshot_table->data); - ecs_os_free(snapshot_table->type); - } - } + ecs_dbg("rest: request query '%s'", q); + bool prev_color = ecs_log_enable_colors(false); + ecs_os_api_log_t prev_log_ = ecs_os_api.log_; + ecs_os_api.log_ = rest_capture_log; - /* Now that all tables have been restored and world is in a consistent - * state, run OnSet systems */ - int32_t world_count = flecs_sparse_count(world->store.tables); - for (i = 0; i < world_count; i ++) { - ecs_table_t *table = flecs_sparse_get_dense( - world->store.tables, ecs_table_t, i); - if (table->flags & EcsTableHasBuiltins) { - continue; - } + ecs_rule_t *r = ecs_rule_init(world, &(ecs_filter_desc_t) { + .expr = q + }); + if (!r) { + char *err = rest_get_captured_log(); + char *escaped_err = ecs_astresc('"', err); + reply_error(reply, escaped_err); + ecs_os_free(escaped_err); + ecs_os_free(err); + } else { + ecs_iter_to_json_desc_t desc = ECS_ITER_TO_JSON_INIT; + rest_parse_json_ser_iter_params(&desc, req); + + ecs_iter_t it = ecs_rule_iter(world, r); + ecs_iter_to_json_buf(world, &it, &reply->body, &desc); + ecs_rule_fini(r); + } - int32_t tcount = ecs_table_count(table); - if (tcount) { - flecs_notify_on_set(world, table, 0, tcount, NULL, true); + ecs_os_api.log_ = prev_log_; + ecs_log_enable_colors(prev_color); + + return true; } } + + return false; } -/* Restoring a filtered snapshots only restores the entities in the snapshot - * to their previous state. */ static -void restore_filtered( +void on_set_rest( ecs_world_t *world, - ecs_snapshot_t *snapshot) + ecs_entity_t component, + const ecs_entity_t *entities, + void *ptr, + size_t size, + int32_t count, + void *ctx) { - ecs_table_leaf_t *leafs = ecs_vector_first( - snapshot->tables, ecs_table_leaf_t); - int32_t l = 0, snapshot_count = ecs_vector_count(snapshot->tables); + EcsRest *rest = ptr; - for (l = 0; l < snapshot_count; l ++) { - ecs_table_leaf_t *snapshot_table = &leafs[l]; - ecs_table_t *table = snapshot_table->table; + (void)component; + (void)size; + (void)ctx; - if (!table) { - continue; + int i; + for(i = 0; i < count; i ++) { + if (!rest[i].port) { + rest[i].port = ECS_REST_DEFAULT_PORT; } - ecs_data_t *data = snapshot_table->data; - if (!data) { - ecs_vector_free(snapshot_table->type); + ecs_rest_ctx_t *srv_ctx = ecs_os_malloc_t(ecs_rest_ctx_t); + ecs_http_server_t *srv = ecs_http_server_init(&(ecs_http_server_desc_t){ + .ipaddr = rest[i].ipaddr, + .port = rest[i].port, + .callback = rest_reply, + .ctx = srv_ctx + }); + + if (!srv) { + const char *ipaddr = rest[i].ipaddr ? rest[i].ipaddr : "0.0.0.0"; + ecs_err("failed to create REST server on %s:%u", + ipaddr, rest[i].port); + ecs_os_free(srv_ctx); continue; } - /* Delete entity from storage first, so that when we restore it to the - * current table we can be sure that there won't be any duplicates */ - int32_t i, entity_count = ecs_vector_count(data->entities); - ecs_entity_t *entities = ecs_vector_first( - snapshot_table->data->entities, ecs_entity_t); - for (i = 0; i < entity_count; i ++) { - ecs_entity_t e = entities[i]; - ecs_record_t *r = ecs_eis_get(world, e); - if (r && r->table) { - flecs_table_delete(world, r->table, &r->table->storage, - ECS_RECORD_TO_ROW(r->row), true); - } else { - /* Make sure that the entity has the same generation count */ - ecs_eis_set_generation(world, e); - } - } + srv_ctx->world = world; + srv_ctx->entity = entities[i]; + srv_ctx->srv = srv; + srv_ctx->rc = 1; - /* Merge data from snapshot table with world table */ - int32_t old_count = ecs_table_count(snapshot_table->table); - int32_t new_count = flecs_table_data_count(snapshot_table->data); + rest[i].impl = srv_ctx; - flecs_table_merge(world, table, table, &table->storage, snapshot_table->data); + ecs_http_server_start(srv_ctx->srv); + } +} - /* Run OnSet systems for merged entities */ - if (new_count) { - flecs_notify_on_set( - world, table, old_count, new_count, NULL, true); - } +static +void DequeueRest(ecs_iter_t *it) { + EcsRest *rest = ecs_term(it, EcsRest, 1); - ecs_os_free(snapshot_table->data->columns); - ecs_os_free(snapshot_table->data); - ecs_vector_free(snapshot_table->type); + if (it->delta_system_time > (FLECS_FLOAT)1.0) { + ecs_warn( + "detected large progress interval (%.2fs), REST request may timeout", + (double)it->delta_system_time); } + + int32_t i; + for(i = 0; i < it->count; i ++) { + ecs_rest_ctx_t *ctx = rest[i].impl; + if (ctx) { + ecs_http_server_dequeue(ctx->srv, it->delta_time); + } + } } -/** Restore a snapshot */ -void ecs_snapshot_restore( - ecs_world_t *world, - ecs_snapshot_t *snapshot) +void FlecsRestImport( + ecs_world_t *world) { - if (snapshot->entity_index) { - /* Unfiltered snapshots have a copy of the entity index which is - * copied back entirely when the snapshot is restored */ - restore_unfiltered(world, snapshot); - } else { - restore_filtered(world, snapshot); - } + ECS_MODULE(world, FlecsRest); - ecs_vector_free(snapshot->tables); + ecs_set_name_prefix(world, "Ecs"); - ecs_os_free(snapshot); -} + flecs_bootstrap_component(world, EcsRest); -ecs_iter_t ecs_snapshot_iter( - ecs_snapshot_t *snapshot) -{ - ecs_snapshot_iter_t iter = { - .tables = snapshot->tables, - .index = 0 - }; + ecs_set_component_actions(world, EcsRest, { + .ctor = ecs_default_ctor, + .move = ecs_move(EcsRest), + .copy = ecs_copy(EcsRest), + .dtor = ecs_dtor(EcsRest), + .on_set = on_set_rest + }); - return (ecs_iter_t){ - .world = snapshot->world, - .table_count = ecs_vector_count(snapshot->tables), - .iter.snapshot = iter, - .next = ecs_snapshot_next - }; + ECS_SYSTEM(world, DequeueRest, EcsPostFrame, EcsRest); } -bool ecs_snapshot_next( - ecs_iter_t *it) -{ - ecs_snapshot_iter_t *iter = &it->iter.snapshot; - ecs_table_leaf_t *tables = ecs_vector_first(iter->tables, ecs_table_leaf_t); - int32_t count = ecs_vector_count(iter->tables); - int32_t i; +#endif +#ifndef FLECS_META_PRIVATE_H +#define FLECS_META_PRIVATE_H - for (i = iter->index; i < count; i ++) { - ecs_table_t *table = tables[i].table; - if (!table) { - continue; - } - ecs_data_t *data = tables[i].data; +#ifdef FLECS_META - it->table = table; - it->count = ecs_table_count(table); - if (data) { - it->entities = ecs_vector_first(data->entities, ecs_entity_t); - } else { - it->entities = NULL; - } +void ecs_meta_type_serialized_init( + ecs_iter_t *it); - it->is_valid = true; - iter->index = i + 1; - - goto yield; +void ecs_meta_dtor_serialized( + EcsMetaTypeSerialized *ptr); + +#endif + +#endif + +#ifdef FLECS_META + +static +const char* op_kind_str( + ecs_meta_type_op_kind_t kind) +{ + switch(kind) { + + case EcsOpEnum: return "Enum"; + case EcsOpBitmask: return "Bitmask"; + case EcsOpArray: return "Array"; + case EcsOpVector: return "Vector"; + case EcsOpPush: return "Push"; + case EcsOpPop: return "Pop"; + case EcsOpPrimitive: return "Primitive"; + case EcsOpBool: return "Bool"; + case EcsOpChar: return "Char"; + case EcsOpByte: return "Byte"; + case EcsOpU8: return "U8"; + case EcsOpU16: return "U16"; + case EcsOpU32: return "U32"; + case EcsOpU64: return "U64"; + case EcsOpI8: return "I8"; + case EcsOpI16: return "I16"; + case EcsOpI32: return "I32"; + case EcsOpI64: return "I64"; + case EcsOpF32: return "F32"; + case EcsOpF64: return "F64"; + case EcsOpUPtr: return "UPtr"; + case EcsOpIPtr: return "IPtr"; + case EcsOpString: return "String"; + case EcsOpEntity: return "Entity"; + default: return "<< invalid kind >>"; } +} - it->is_valid = false; - return false; +/* Get current scope */ +static +ecs_meta_scope_t* get_scope( + ecs_meta_cursor_t *cursor) +{ + ecs_check(cursor != NULL, ECS_INVALID_PARAMETER, NULL); + return &cursor->scope[cursor->depth]; +error: + return NULL; +} -yield: - it->is_valid = true; - return true; +/* Get previous scope */ +static +ecs_meta_scope_t* get_prev_scope( + ecs_meta_cursor_t *cursor) +{ + ecs_check(cursor != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(cursor->depth > 0, ECS_INVALID_PARAMETER, NULL); + return &cursor->scope[cursor->depth - 1]; +error: + return NULL; } -/** Cleanup snapshot */ -void ecs_snapshot_free( - ecs_snapshot_t *snapshot) +/* Get current operation for scope */ +static +ecs_meta_type_op_t* get_op( + ecs_meta_scope_t *scope) { - flecs_sparse_free(snapshot->entity_index); + return &scope->ops[scope->op_cur]; +} - ecs_table_leaf_t *tables = ecs_vector_first(snapshot->tables, ecs_table_leaf_t); - int32_t i, count = ecs_vector_count(snapshot->tables); - for (i = 0; i < count; i ++) { - ecs_table_leaf_t *snapshot_table = &tables[i]; - ecs_table_t *table = snapshot_table->table; - if (table) { - ecs_data_t *data = snapshot_table->data; - if (data) { - flecs_table_clear_data(snapshot->world, table, data); - ecs_os_free(data); - } - ecs_vector_free(snapshot_table->type); - } - } +/* Get component for type in current scope */ +static +const EcsComponent* get_component_ptr( + const ecs_world_t *world, + ecs_meta_scope_t *scope) +{ + const EcsComponent *comp = scope->comp; + if (!comp) { + comp = scope->comp = ecs_get(world, scope->type, EcsComponent); + ecs_assert(comp != NULL, ECS_INTERNAL_ERROR, NULL); + } + return comp; +} - ecs_vector_free(snapshot->tables); - ecs_os_free(snapshot); +/* Get size for type in current scope */ +static +ecs_size_t get_size( + const ecs_world_t *world, + ecs_meta_scope_t *scope) +{ + return get_component_ptr(world, scope)->size; } -#endif +/* Get alignment for type in current scope */ +static +ecs_size_t get_alignment( + const ecs_world_t *world, + ecs_meta_scope_t *scope) +{ + return get_component_ptr(world, scope)->alignment; +} +static +int32_t get_elem_count( + ecs_meta_scope_t *scope) +{ + if (scope->vector) { + return ecs_vector_count(*(scope->vector)); + } -#ifdef FLECS_SYSTEM + ecs_meta_type_op_t *op = get_op(scope); + return op->count; +} + +/* Get pointer to current field/element */ +static +ecs_meta_type_op_t* get_ptr( + const ecs_world_t *world, + ecs_meta_scope_t *scope) +{ + ecs_meta_type_op_t *op = get_op(scope); + ecs_size_t size = get_size(world, scope); + + if (scope->vector) { + ecs_size_t align = get_alignment(world, scope); + ecs_vector_set_min_count_t( + scope->vector, size, align, scope->elem_cur + 1); + scope->ptr = ecs_vector_first_t(*(scope->vector), size, align); + } + return ECS_OFFSET(scope->ptr, size * scope->elem_cur + op->offset); +} static -void invoke_status_action( - ecs_world_t *world, - ecs_entity_t system, - const EcsSystem *system_data, - ecs_system_status_t status) +int push_type( + const ecs_world_t *world, + ecs_meta_scope_t *scope, + ecs_entity_t type, + void *ptr) { - ecs_system_status_action_t action = system_data->status_action; - if (action) { - action(world, system, status, system_data->status_ctx); + const EcsMetaTypeSerialized *ser = ecs_get( + world, type, EcsMetaTypeSerialized); + if (ser == NULL) { + char *str = ecs_id_str(world, type); + ecs_err("cannot open scope for entity '%s' which is not a type", str); + ecs_os_free(str); + return -1; } + + scope[0] = (ecs_meta_scope_t) { + .type = type, + .ops = ecs_vector_first(ser->ops, ecs_meta_type_op_t), + .op_count = ecs_vector_count(ser->ops), + .ptr = ptr + }; + + return 0; } -/* Invoked when system becomes active or inactive */ -void ecs_system_activate( - ecs_world_t *world, - ecs_entity_t system, - bool activate, - const EcsSystem *system_data) +ecs_meta_cursor_t ecs_meta_cursor( + const ecs_world_t *world, + ecs_entity_t type, + void *ptr) { - ecs_assert(!world->is_readonly, ECS_INTERNAL_ERROR, NULL); - - if (activate) { - /* If activating system, ensure that it doesn't have the Inactive tag. - * Systems are implicitly activated so they are kept out of the main - * loop as long as they aren't used. They are not implicitly deactivated - * to prevent overhead in case of oscillating app behavior. - * After activation, systems that aren't matched with anything can be - * deactivated again by explicitly calling ecs_deactivate_systems. - */ - ecs_remove_id(world, system, EcsInactive); - } + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(type != 0, ECS_INVALID_PARAMETER, NULL); + ecs_check(ptr != NULL, ECS_INVALID_PARAMETER, NULL); - if (!system_data) { - system_data = ecs_get(world, system, EcsSystem); - } - if (!system_data || !system_data->query) { - return; - } + ecs_meta_cursor_t result = { + .world = world, + .valid = true + }; - if (!activate) { - if (ecs_has_id(world, system, EcsDisabled)) { - if (!ecs_query_table_count(system_data->query)) { - /* If deactivating a disabled system that isn't matched with - * any active tables, there is nothing to deactivate. */ - return; - } - } + if (push_type(world, result.scope, type, ptr) != 0) { + result.valid = false; } - /* Invoke system status action */ - invoke_status_action(world, system, system_data, - activate ? EcsSystemActivated : EcsSystemDeactivated); + return result; +error: + return (ecs_meta_cursor_t){ 0 }; +} - ecs_dbg_1("#[green]system#[reset] %s %s", - ecs_get_name(world, system), - activate ? "activated" : "deactivated"); +void* ecs_meta_get_ptr( + ecs_meta_cursor_t *cursor) +{ + return get_ptr(cursor->world, get_scope(cursor)); } -/* Actually enable or disable system */ -static -void ecs_enable_system( - ecs_world_t *world, - ecs_entity_t system, - EcsSystem *system_data, - bool enabled) +int ecs_meta_next( + ecs_meta_cursor_t *cursor) { - ecs_poly_assert(world, ecs_world_t); - ecs_assert(!world->is_readonly, ECS_INTERNAL_ERROR, NULL); + ecs_meta_scope_t *scope = get_scope(cursor); + ecs_meta_type_op_t *op = get_op(scope); - ecs_query_t *query = system_data->query; - if (!query) { - return; + if (scope->is_collection) { + scope->elem_cur ++; + scope->op_cur = 0; + if (scope->elem_cur >= get_elem_count(scope)) { + ecs_err("out of collection bounds (%d)", scope->elem_cur); + return -1; + } + + return 0; } - if (ecs_query_table_count(query)) { - /* Only (de)activate system if it has non-empty tables. */ - ecs_system_activate(world, system, enabled, system_data); - system_data = ecs_get_mut(world, system, EcsSystem, NULL); + scope->op_cur += op->op_count; + if (scope->op_cur >= scope->op_count) { + ecs_err("out of bounds"); + return -1; } - - /* Invoke action for enable/disable status */ - invoke_status_action( - world, system, system_data, - enabled ? EcsSystemEnabled : EcsSystemDisabled); -} -/* -- Public API -- */ + return 0; +} -ecs_entity_t ecs_run_intern( - ecs_world_t *world, - ecs_stage_t *stage, - ecs_entity_t system, - EcsSystem *system_data, - int32_t stage_current, - int32_t stage_count, - FLECS_FLOAT delta_time, - int32_t offset, - int32_t limit, - void *param) +int ecs_meta_member( + ecs_meta_cursor_t *cursor, + const char *name) { - FLECS_FLOAT time_elapsed = delta_time; - ecs_entity_t tick_source = system_data->tick_source; + if (cursor->depth == 0) { + ecs_err("cannot move to member in root scope"); + return -1; + } - /* Support legacy behavior */ - if (!param) { - param = system_data->ctx; + ecs_meta_scope_t *prev_scope = get_prev_scope(cursor); + ecs_meta_scope_t *scope = get_scope(cursor); + ecs_meta_type_op_t *push_op = get_op(prev_scope); + const ecs_world_t *world = cursor->world; + + ecs_assert(push_op->kind == EcsOpPush, ECS_INTERNAL_ERROR, NULL); + + if (!push_op->members) { + ecs_err("cannot move to member '%s' for non-struct type", name); + return -1; } - if (tick_source) { - const EcsTickSource *tick = ecs_get( - world, tick_source, EcsTickSource); + ecs_hashed_string_t key = ecs_get_hashed_string( + name, ecs_os_strlen(name), 0); + int32_t *cur = flecs_hashmap_get(*push_op->members, &key, int32_t); + if (!cur) { + char *path = ecs_get_fullpath(world, scope->type); + ecs_err("unknown member '%s' for type '%s'", name, path); + ecs_os_free(path); + return -1; + } - if (tick) { - time_elapsed = tick->time_elapsed; + scope->op_cur = *cur; - /* If timer hasn't fired we shouldn't run the system */ - if (!tick->tick) { + return 0; +} + +int ecs_meta_push( + ecs_meta_cursor_t *cursor) +{ + ecs_meta_scope_t *scope = get_scope(cursor); + ecs_meta_type_op_t *op = get_op(scope); + const ecs_world_t *world = cursor->world; + + if (cursor->depth == 0) { + if (!cursor->is_primitive_scope) { + if (op->kind > EcsOpScope) { + cursor->is_primitive_scope = true; return 0; } - } else { - /* If a timer has been set but the timer entity does not have the - * EcsTimer component, don't run the system. This can be the result - * of a single-shot timer that has fired already. Not resetting the - * timer field of the system will ensure that the system won't be - * ran after the timer has fired. */ - return 0; } } - ecs_time_t time_start; - bool measure_time = world->measure_system_time; - if (measure_time) { - ecs_os_get_time(&time_start); - } + void *ptr = get_ptr(world, scope); + cursor->depth ++; + ecs_check(cursor->depth < ECS_META_MAX_SCOPE_DEPTH, + ECS_INVALID_PARAMETER, NULL); - ecs_world_t *thread_ctx = world; - if (stage) { - thread_ctx = stage->thread_ctx; - } + ecs_meta_scope_t *next_scope = get_scope(cursor); - ecs_defer_begin(thread_ctx); + /* If we're not already in an inline array and this operation is an inline + * array, push a frame for the array. + * Doing this first ensures that inline arrays take precedence over other + * kinds of push operations, such as for a struct element type. */ + if (!scope->is_inline_array && op->count > 1 && !scope->is_collection) { + /* Push a frame just for the element type, with inline_array = true */ + next_scope[0] = (ecs_meta_scope_t){ + .ops = op, + .op_count = op->op_count, + .ptr = scope->ptr, + .type = op->type, + .is_collection = true, + .is_inline_array = true + }; - /* Prepare the query iterator */ - ecs_iter_t it = ecs_query_iter_page( - thread_ctx, system_data->query, offset, limit); + /* With 'is_inline_array' set to true we ensure that we can never push + * the same inline array twice */ - it.system = system; - it.self = system_data->self; - it.delta_time = delta_time; - it.delta_system_time = time_elapsed; - it.frame_offset = offset; - it.param = param; - it.ctx = system_data->ctx; - it.binding_ctx = system_data->binding_ctx; + return 0; + } - ecs_iter_action_t action = system_data->action; + switch(op->kind) { + case EcsOpPush: + next_scope[0] = (ecs_meta_scope_t) { + .ops = &op[1], /* op after push */ + .op_count = op->op_count - 1, /* don't include pop */ + .ptr = scope->ptr, + .type = op->type + }; + break; - /* If no filter is provided, just iterate tables & invoke action */ - if (stage_count <= 1 || !system_data->multi_threaded) { - while (ecs_query_next(&it)) { - action(&it); - } - } else { - while (ecs_query_next_worker(&it, stage_current, stage_count)) { - action(&it); + case EcsOpArray: { + if (push_type(world, next_scope, op->type, ptr) != 0) { + goto error; } - } - if (measure_time) { - system_data->time_spent += (float)ecs_time_measure(&time_start); + const EcsArray *type_ptr = ecs_get(world, op->type, EcsArray); + next_scope->type = type_ptr->type; + next_scope->is_collection = true; + break; } - system_data->invoke_count ++; - - ecs_defer_end(thread_ctx); - - return it.interrupted_by; -} + case EcsOpVector: + next_scope->vector = ptr; + if (push_type(world, next_scope, op->type, NULL) != 0) { + goto error; + } -/* -- Public API -- */ + const EcsVector *type_ptr = ecs_get(world, op->type, EcsVector); + next_scope->type = type_ptr->type; + next_scope->is_collection = true; + break; -ecs_entity_t ecs_run_w_filter( - ecs_world_t *world, - ecs_entity_t system, - FLECS_FLOAT delta_time, - int32_t offset, - int32_t limit, - void *param) -{ - ecs_stage_t *stage = flecs_stage_from_world(&world); + default: { + char *path = ecs_get_fullpath(world, scope->type); + ecs_err("invalid push for type '%s'", path); + ecs_os_free(path); + goto error; + } + } - EcsSystem *system_data = (EcsSystem*)ecs_get( - world, system, EcsSystem); - assert(system_data != NULL); + if (scope->is_collection) { + next_scope[0].ptr = ECS_OFFSET(next_scope[0].ptr, + scope->elem_cur * get_size(world, scope)); + } - return ecs_run_intern(world, stage, system, system_data, 0, 0, delta_time, - offset, limit, param); + return 0; +error: + return -1; } -ecs_entity_t ecs_run_worker( - ecs_world_t *world, - ecs_entity_t system, - int32_t stage_current, - int32_t stage_count, - FLECS_FLOAT delta_time, - void *param) +int ecs_meta_pop( + ecs_meta_cursor_t *cursor) { - ecs_stage_t *stage = flecs_stage_from_world(&world); + if (cursor->is_primitive_scope) { + cursor->is_primitive_scope = false; + return 0; + } - EcsSystem *system_data = (EcsSystem*)ecs_get( - world, system, EcsSystem); - assert(system_data != NULL); + ecs_meta_scope_t *scope = get_scope(cursor); + cursor->depth --; + if (cursor->depth < 0) { + ecs_err("unexpected end of scope"); + return -1; + } - return ecs_run_intern( - world, stage, system, system_data, stage_current, stage_count, - delta_time, 0, 0, param); -} + ecs_meta_scope_t *next_scope = get_scope(cursor); + ecs_meta_type_op_t *op = get_op(next_scope); -ecs_entity_t ecs_run( - ecs_world_t *world, - ecs_entity_t system, - FLECS_FLOAT delta_time, - void *param) -{ - return ecs_run_w_filter(world, system, delta_time, 0, 0, param); -} + if (!scope->is_inline_array) { + if (op->kind == EcsOpPush) { + next_scope->op_cur += op->op_count - 1; -ecs_query_t* ecs_system_get_query( - const ecs_world_t *world, - ecs_entity_t system) -{ - const EcsQuery *q = ecs_get(world, system, EcsQuery); - if (q) { - return q->query; - } else { - const EcsSystem *s = ecs_get(world, system, EcsSystem); - if (s) { - return s->query; + /* push + op_count should point to the operation after pop */ + op = get_op(next_scope); + ecs_assert(op->kind == EcsOpPop, ECS_INTERNAL_ERROR, NULL); + } else if (op->kind == EcsOpArray || op->kind == EcsOpVector) { + /* Collection type, nothing else to do */ } else { - return NULL; + /* should not have been able to push if the previous scope was not + * a complex or collection type */ + ecs_assert(false, ECS_INTERNAL_ERROR, NULL); } + } else { + /* Make sure that this was an inline array */ + ecs_assert(next_scope->op_count > 1, ECS_INTERNAL_ERROR, NULL); } + + return 0; } -void* ecs_get_system_ctx( - const ecs_world_t *world, - ecs_entity_t system) +int ecs_meta_is_collection( + ecs_meta_cursor_t *cursor) { - const EcsSystem *s = ecs_get(world, system, EcsSystem); - if (s) { - return s->ctx; - } else { - return NULL; - } + ecs_meta_scope_t *scope = get_scope(cursor); + return scope->is_collection; } -void* ecs_get_system_binding_ctx( - const ecs_world_t *world, - ecs_entity_t system) +ecs_entity_t ecs_meta_get_type( + ecs_meta_cursor_t *cursor) { - const EcsSystem *s = ecs_get(world, system, EcsSystem); - if (s) { - return s->binding_ctx; - } else { - return NULL; - } + ecs_meta_scope_t *scope = get_scope(cursor); + ecs_meta_type_op_t *op = get_op(scope); + return op->type; } -/* Generic constructor to initialize a component to 0 */ +/* Utility macro's to let the compiler do the conversion work for us */ +#define set_T(T, ptr, value)\ + ((T*)ptr)[0] = ((T)value) + +#define case_T(kind, T, dst, src)\ +case kind:\ + set_T(T, dst, src);\ + break + +#define cases_T_float(dst, src)\ + case_T(EcsOpF32, ecs_f32_t, dst, src);\ + case_T(EcsOpF64, ecs_f64_t, dst, src) + +#define cases_T_signed(dst, src)\ + case_T(EcsOpChar, ecs_char_t, dst, src);\ + case_T(EcsOpI8, ecs_i8_t, dst, src);\ + case_T(EcsOpI16, ecs_i16_t, dst, src);\ + case_T(EcsOpI32, ecs_i32_t, dst, src);\ + case_T(EcsOpI64, ecs_i64_t, dst, src);\ + case_T(EcsOpIPtr, ecs_iptr_t, dst, src) + +#define cases_T_unsigned(dst, src)\ + case_T(EcsOpByte, ecs_byte_t, dst, src);\ + case_T(EcsOpU8, ecs_u8_t, dst, src);\ + case_T(EcsOpU16, ecs_u16_t, dst, src);\ + case_T(EcsOpU32, ecs_u32_t, dst, src);\ + case_T(EcsOpU64, ecs_u64_t, dst, src);\ + case_T(EcsOpUPtr, ecs_uptr_t, dst, src);\ + +#define cases_T_bool(dst, src)\ +case EcsOpBool:\ + set_T(ecs_bool_t, dst, value != 0);\ + break + static -void sys_ctor_init_zero( - ecs_world_t *world, - ecs_entity_t component, - const ecs_entity_t *entities, - void *ptr, - size_t size, - int32_t count, - void *ctx) +void conversion_error( + ecs_meta_cursor_t *cursor, + ecs_meta_type_op_t *op, + const char *from) { - (void)world; - (void)component; - (void)entities; - (void)ctx; - memset(ptr, 0, size * (size_t)count); + char *path = ecs_get_fullpath(cursor->world, op->type); + ecs_err("unsupported conversion from %s to '%s'", from, path); + ecs_os_free(path); } -/* System destructor */ -static -void ecs_colsystem_dtor( - ecs_world_t *world, - ecs_entity_t component, - const ecs_entity_t *entities, - void *ptr, - size_t size, - int32_t count, - void *ctx) +int ecs_meta_set_bool( + ecs_meta_cursor_t *cursor, + bool value) { - (void)component; - (void)ctx; - (void)size; + ecs_meta_scope_t *scope = get_scope(cursor); + ecs_meta_type_op_t *op = get_op(scope); + void *ptr = get_ptr(cursor->world, scope); - EcsSystem *system_data = ptr; + switch(op->kind) { + cases_T_bool(ptr, value); + cases_T_unsigned(ptr, value); + default: + conversion_error(cursor, op, "bool"); + return -1; + } - int i; - for (i = 0; i < count; i ++) { - EcsSystem *system = &system_data[i]; - ecs_entity_t e = entities[i]; + return 0; +} - if (!ecs_is_alive(world, e)) { - /* This can happen when a set is deferred while a system is being - * cleaned up. The operation will be discarded, but the destructor - * still needs to be invoked for the value */ - continue; - } +int ecs_meta_set_char( + ecs_meta_cursor_t *cursor, + char value) +{ + ecs_meta_scope_t *scope = get_scope(cursor); + ecs_meta_type_op_t *op = get_op(scope); + void *ptr = get_ptr(cursor->world, scope); - /* Invoke Deactivated action for active systems */ - if (system->query && ecs_query_table_count(system->query)) { - invoke_status_action(world, e, ptr, EcsSystemDeactivated); - } + switch(op->kind) { + cases_T_bool(ptr, value); + cases_T_signed(ptr, value); + default: + conversion_error(cursor, op, "char"); + return -1; + } - /* Invoke Disabled action for enabled systems */ - if (!ecs_has_id(world, e, EcsDisabled)) { - invoke_status_action(world, e, ptr, EcsSystemDisabled); - } + return 0; +} - if (system->ctx_free) { - system->ctx_free(system->ctx); - } +int ecs_meta_set_int( + ecs_meta_cursor_t *cursor, + int64_t value) +{ + ecs_meta_scope_t *scope = get_scope(cursor); + ecs_meta_type_op_t *op = get_op(scope); + void *ptr = get_ptr(cursor->world, scope); - if (system->status_ctx_free) { - system->status_ctx_free(system->status_ctx); - } + switch(op->kind) { + cases_T_bool(ptr, value); + cases_T_signed(ptr, value); + cases_T_float(ptr, value); + default: { + conversion_error(cursor, op, "int"); + return -1; + } + } - if (system->binding_ctx_free) { - system->binding_ctx_free(system->binding_ctx); - } + return 0; +} - if (system->query) { - ecs_query_fini(system->query); - } +int ecs_meta_set_uint( + ecs_meta_cursor_t *cursor, + uint64_t value) +{ + ecs_meta_scope_t *scope = get_scope(cursor); + ecs_meta_type_op_t *op = get_op(scope); + void *ptr = get_ptr(cursor->world, scope); + + switch(op->kind) { + cases_T_bool(ptr, value); + cases_T_unsigned(ptr, value); + cases_T_float(ptr, value); + case EcsOpEntity: + set_T(ecs_entity_t, ptr, value); + break; + default: + conversion_error(cursor, op, "uint"); + return -1; } + + return 0; } -static -void EnableMonitor( - ecs_iter_t *it) +int ecs_meta_set_float( + ecs_meta_cursor_t *cursor, + double value) { - EcsSystem *sys = ecs_term(it, EcsSystem, 1); + ecs_meta_scope_t *scope = get_scope(cursor); + ecs_meta_type_op_t *op = get_op(scope); + void *ptr = get_ptr(cursor->world, scope); - int32_t i; - for (i = 0; i < it->count; i ++) { - if (it->event == EcsOnAdd) { - ecs_enable_system(it->world, it->entities[i], &sys[i], true); - } else if (it->event == EcsOnRemove) { - ecs_enable_system(it->world, it->entities[i], &sys[i], false); - } + switch(op->kind) { + cases_T_bool(ptr, value); + cases_T_signed(ptr, value); + cases_T_unsigned(ptr, value); + cases_T_float(ptr, value); + default: + conversion_error(cursor, op, "float"); + return -1; } + + return 0; } -ecs_entity_t ecs_system_init( - ecs_world_t *world, - const ecs_system_desc_t *desc) +static +int add_bitmask_constant( + ecs_meta_cursor_t *cursor, + ecs_meta_type_op_t *op, + void *out, + const char *value) { - ecs_poly_assert(world, ecs_world_t); - ecs_check(!world->is_readonly, ECS_INVALID_WHILE_ITERATING, NULL); + ecs_assert(op->type != 0, ECS_INTERNAL_ERROR, NULL); - ecs_entity_t existing = desc->entity.entity; - ecs_entity_t result = ecs_entity_init(world, &desc->entity); - if (!result) { + if (!ecs_os_strcmp(value, "0")) { return 0; } - bool added = false; - EcsSystem *system = ecs_get_mut(world, result, EcsSystem, &added); - if (added) { - ecs_check(desc->callback != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_entity_t c = ecs_lookup_child(cursor->world, op->type, value); + if (!c) { + char *path = ecs_get_fullpath(cursor->world, op->type); + ecs_err("unresolved bitmask constant '%s' for type '%s'", value, path); + ecs_os_free(path); + return -1; + } - memset(system, 0, sizeof(EcsSystem)); + const ecs_u32_t *v = ecs_get_pair_object( + cursor->world, c, EcsConstant, ecs_u32_t); + if (v == NULL) { + char *path = ecs_get_fullpath(cursor->world, op->type); + ecs_err("'%s' is not an bitmask constant for type '%s'", value, path); + ecs_os_free(path); + return -1; + } - ecs_query_desc_t query_desc = desc->query; - query_desc.filter.name = desc->entity.name; - query_desc.system = result; + *(ecs_u32_t*)out |= v[0]; - ecs_query_t *query = ecs_query_init(world, &query_desc); - if (!query) { - ecs_delete(world, result); - return 0; - } + return 0; +} - /* Re-obtain pointer, as query may have added components */ - system = ecs_get_mut(world, result, EcsSystem, &added); - ecs_assert(added == false, ECS_INTERNAL_ERROR, NULL); +static +int parse_bitmask( + ecs_meta_cursor_t *cursor, + ecs_meta_type_op_t *op, + void *out, + const char *value) +{ + char token[ECS_MAX_TOKEN_SIZE]; - /* Prevent the system from moving while we're initializing */ - ecs_defer_begin(world); + const char *prev = value, *ptr = value; - system->entity = result; - system->query = query; + *(ecs_u32_t*)out = 0; - system->action = desc->callback; - system->status_action = desc->status_callback; + while ((ptr = strchr(ptr, '|'))) { + ecs_os_memcpy(token, prev, ptr - prev); + token[ptr - prev] = '\0'; + if (add_bitmask_constant(cursor, op, out, token) != 0) { + return -1; + } - system->self = desc->self; - system->ctx = desc->ctx; - system->status_ctx = desc->status_ctx; - system->binding_ctx = desc->binding_ctx; + ptr ++; + prev = ptr; + } - system->ctx_free = desc->ctx_free; - system->status_ctx_free = desc->status_ctx_free; - system->binding_ctx_free = desc->binding_ctx_free; + if (add_bitmask_constant(cursor, op, out, prev) != 0) { + return -1; + } - system->tick_source = desc->tick_source; + return 0; +} - system->multi_threaded = desc->multi_threaded; - system->no_staging = desc->no_staging; +int ecs_meta_set_string( + ecs_meta_cursor_t *cursor, + const char *value) +{ + ecs_meta_scope_t *scope = get_scope(cursor); + ecs_meta_type_op_t *op = get_op(scope); + void *ptr = get_ptr(cursor->world, scope); - /* If tables have been matched with this system it is active, and we - * should activate the in terms, if any. This will ensure that any - * OnDemand systems get enabled. */ - if (ecs_query_table_count(query)) { - ecs_system_activate(world, result, true, system); + switch(op->kind) { + case EcsOpBool: + if (!ecs_os_strcmp(value, "true")) { + set_T(ecs_bool_t, ptr, true); + } else if (!ecs_os_strcmp(value, "false")) { + set_T(ecs_bool_t, ptr, false); } else { - /* If system isn't matched with any tables, mark it as inactive. This - * causes it to be ignored by the main loop. When the system matches - * with a table it will be activated. */ - ecs_add_id(world, result, EcsInactive); + ecs_err("invalid value for boolean '%s'", value); + return -1; } - - if (!ecs_has_id(world, result, EcsDisabled)) { - /* If system is already enabled, generate enable status. The API - * should guarantee that it exactly matches enable-disable - * notifications and activate-deactivate notifications. */ - invoke_status_action(world, result, system, EcsSystemEnabled); - - /* If column system has active (non-empty) tables, also generate the - * activate status. */ - if (ecs_query_table_count(system->query)) { - invoke_status_action(world, result, system, EcsSystemActivated); - } + break; + case EcsOpI8: + case EcsOpU8: + case EcsOpChar: + case EcsOpByte: + set_T(ecs_i8_t, ptr, atol(value)); + break; + case EcsOpI16: + case EcsOpU16: + set_T(ecs_i16_t, ptr, atol(value)); + break; + case EcsOpI32: + case EcsOpU32: + set_T(ecs_i32_t, ptr, atol(value)); + break; + case EcsOpI64: + case EcsOpU64: + set_T(ecs_i64_t, ptr, atol(value)); + break; + case EcsOpIPtr: + case EcsOpUPtr: + set_T(ecs_iptr_t, ptr, atol(value)); + break; + case EcsOpF32: + set_T(ecs_f32_t, ptr, atof(value)); + break; + case EcsOpF64: + set_T(ecs_f64_t, ptr, atof(value)); + break; + case EcsOpString: { + ecs_os_free(*(char**)ptr); + char *result = ecs_os_strdup(value); + set_T(ecs_string_t, ptr, result); + break; + } + case EcsOpEnum: { + ecs_assert(op->type != 0, ECS_INTERNAL_ERROR, NULL); + ecs_entity_t c = ecs_lookup_child(cursor->world, op->type, value); + if (!c) { + char *path = ecs_get_fullpath(cursor->world, op->type); + ecs_err("unresolved enum constant '%s' for type '%s'", value, path); + ecs_os_free(path); + return -1; } - if (desc->interval != 0 || desc->rate != 0 || desc->tick_source != 0) { -#ifdef FLECS_TIMER - if (desc->interval != 0) { - ecs_set_interval(world, result, desc->interval); - } - - if (desc->rate) { - ecs_set_rate(world, result, desc->rate, desc->tick_source); - } else if (desc->tick_source) { - ecs_set_tick_source(world, result, desc->tick_source); - } -#else - ecs_abort(ECS_UNSUPPORTED, "timer module not available"); -#endif + const ecs_i32_t *v = ecs_get_pair_object( + cursor->world, c, EcsConstant, ecs_i32_t); + if (v == NULL) { + char *path = ecs_get_fullpath(cursor->world, op->type); + ecs_err("'%s' is not an enum constant for type '%s'", value, path); + ecs_os_free(path); + return -1; } - ecs_modified(world, result, EcsSystem); - - if (desc->entity.name) { - ecs_trace("#[green]system#[reset] %s created", - ecs_get_name(world, result)); + set_T(ecs_i32_t, ptr, v[0]); + break; + } + case EcsOpBitmask: + if (parse_bitmask(cursor, op, ptr, value) != 0) { + return -1; } + break; + case EcsOpEntity: { + ecs_entity_t e = 0; - ecs_defer_end(world); - } else { - const char *expr_desc = desc->query.filter.expr; - const char *expr_sys = system->query->filter.expr; - - /* Only check expression if it's set */ - if (expr_desc) { - if (expr_sys && !strcmp(expr_sys, "0")) expr_sys = NULL; - if (expr_desc && !strcmp(expr_desc, "0")) expr_desc = NULL; - - if (expr_sys && expr_desc) { - if (strcmp(expr_sys, expr_desc)) { - ecs_abort(ECS_ALREADY_DEFINED, desc->entity.name); - } + if (ecs_os_strcmp(value, "0")) { + if (cursor->lookup_action) { + e = cursor->lookup_action( + cursor->world, value, + cursor->lookup_ctx); } else { - if (expr_sys != expr_desc) { - ecs_abort(ECS_ALREADY_DEFINED, desc->entity.name); - } + e = ecs_lookup_path(cursor->world, 0, value); } - /* If expr_desc is not set, and this is an existing system, don't throw - * an error because we could be updating existing parameters of the - * system such as the context or system callback. However, if no - * entity handle was provided, we have to assume that the application is - * trying to redeclare the system. */ - } else if (!existing) { - if (expr_sys) { - ecs_abort(ECS_ALREADY_DEFINED, desc->entity.name); + if (!e) { + ecs_err("unresolved entity identifier '%s'", value); + return -1; } } - if (desc->callback) { - system->action = desc->callback; - } - if (desc->ctx) { - system->ctx = desc->ctx; - } - if (desc->binding_ctx) { - system->binding_ctx = desc->binding_ctx; - } - if (desc->query.filter.instanced) { - system->query->filter.instanced = true; - } - if (desc->multi_threaded) { - system->multi_threaded = desc->multi_threaded; - } - if (desc->no_staging) { - system->no_staging = desc->no_staging; - } + set_T(ecs_entity_t, ptr, e); + break; + } + case EcsOpPop: + ecs_err("excess element '%s' in scope", value); + return -1; + default: + ecs_err("unsupported conversion from string '%s' to '%s'", + value, op_kind_str(op->kind)); + return -1; } - return result; -error: return 0; } -void FlecsSystemImport( - ecs_world_t *world) +int ecs_meta_set_string_literal( + ecs_meta_cursor_t *cursor, + const char *value) { - ECS_MODULE(world, FlecsSystem); + ecs_meta_scope_t *scope = get_scope(cursor); + ecs_meta_type_op_t *op = get_op(scope); + void *ptr = get_ptr(cursor->world, scope); - ecs_set_name_prefix(world, "Ecs"); + ecs_size_t len = ecs_os_strlen(value); + if (value[0] != '\"' || value[len - 1] != '\"') { + ecs_err("invalid string literal '%s'", value); + return -1; + } - flecs_bootstrap_component(world, EcsSystem); - flecs_bootstrap_component(world, EcsTickSource); + switch(op->kind) { + case EcsOpChar: + set_T(ecs_char_t, ptr, value[1]); + break; + + default: + case EcsOpEntity: + case EcsOpString: + len -= 2; - /* Put following tags in flecs.core so they can be looked up - * without using the flecs.systems prefix. */ - ecs_entity_t old_scope = ecs_set_scope(world, EcsFlecsCore); - flecs_bootstrap_tag(world, EcsInactive); - flecs_bootstrap_tag(world, EcsMonitor); - ecs_set_scope(world, old_scope); + char *result = ecs_os_malloc(len + 1); + ecs_os_memcpy(result, value + 1, len); + result[len] = '\0'; - /* Bootstrap ctor and dtor for EcsSystem */ - ecs_set_component_actions_w_id(world, ecs_id(EcsSystem), - &(EcsComponentLifecycle) { - .ctor = sys_ctor_init_zero, - .dtor = ecs_colsystem_dtor - }); + if (ecs_meta_set_string(cursor, result)) { + ecs_os_free(result); + return -1; + } - ecs_observer_init(world, &(ecs_observer_desc_t) { - .entity.name = "EnableMonitor", - .filter.terms = { - { .id = ecs_id(EcsSystem) }, - { .id = EcsDisabled, .oper = EcsNot }, - }, - .events = {EcsMonitor}, - .callback = EnableMonitor - }); + ecs_os_free(result); + + break; + } + + return 0; } -#endif +int ecs_meta_set_entity( + ecs_meta_cursor_t *cursor, + ecs_entity_t value) +{ + ecs_meta_scope_t *scope = get_scope(cursor); + ecs_meta_type_op_t *op = get_op(scope); + void *ptr = get_ptr(cursor->world, scope); + switch(op->kind) { + case EcsOpEntity: + set_T(ecs_entity_t, ptr, value); + break; + default: + conversion_error(cursor, op, "entity"); + return -1; + } + return 0; +} -#ifdef FLECS_JSON +int ecs_meta_set_null( + ecs_meta_cursor_t *cursor) +{ + ecs_meta_scope_t *scope = get_scope(cursor); + ecs_meta_type_op_t *op = get_op(scope); + void *ptr = get_ptr(cursor->world, scope); + switch (op->kind) { + case EcsOpString: + ecs_os_free(*(char**)ptr); + set_T(ecs_string_t, ptr, NULL); + break; + default: + conversion_error(cursor, op, "null"); + return -1; + } + return 0; +} -static -int json_ser_type( - const ecs_world_t *world, - ecs_vector_t *ser, - const void *base, - ecs_strbuf_t *str); +#endif -static -int json_ser_type_ops( - const ecs_world_t *world, - ecs_meta_type_op_t *ops, - int32_t op_count, - const void *base, - ecs_strbuf_t *str); +#ifdef FLECS_META static -int json_ser_type_op( - const ecs_world_t *world, - ecs_meta_type_op_t *op, - const void *base, - ecs_strbuf_t *str); +ecs_vector_t* serialize_type( + ecs_world_t *world, + ecs_entity_t type, + ecs_size_t offset, + ecs_vector_t *ops); static -void json_next( - ecs_strbuf_t *buf) -{ - ecs_strbuf_list_next(buf); +ecs_meta_type_op_kind_t primitive_to_op_kind(ecs_primitive_kind_t kind) { + return EcsOpPrimitive + kind; } static -void json_literal( - ecs_strbuf_t *buf, - const char *value) -{ - ecs_strbuf_appendstr(buf, value); +ecs_size_t type_size(ecs_world_t *world, ecs_entity_t type) { + const EcsComponent *comp = ecs_get(world, type, EcsComponent); + ecs_assert(comp != NULL, ECS_INTERNAL_ERROR, NULL); + return comp->size; } static -void json_number( - ecs_strbuf_t *buf, - double value) -{ - ecs_strbuf_appendflt(buf, value); +ecs_meta_type_op_t* ops_add(ecs_vector_t **ops, ecs_meta_type_op_kind_t kind) { + ecs_meta_type_op_t *op = ecs_vector_add(ops, ecs_meta_type_op_t); + op->kind = kind; + op->offset = 0; + op->count = 1; + op->op_count = 1; + op->size = 0; + op->name = NULL; + op->members = NULL; + op->type = 0; + return op; } static -void json_true( - ecs_strbuf_t *buf) -{ - json_literal(buf, "true"); +ecs_meta_type_op_t* ops_get(ecs_vector_t *ops, int32_t index) { + ecs_meta_type_op_t* op = ecs_vector_get(ops, ecs_meta_type_op_t, index); + ecs_assert(op != NULL, ECS_INTERNAL_ERROR, NULL); + return op; } static -void json_false( - ecs_strbuf_t *buf) +ecs_vector_t* serialize_primitive( + ecs_world_t *world, + ecs_entity_t type, + ecs_size_t offset, + ecs_vector_t *ops) { - json_literal(buf, "false"); -} + const EcsPrimitive *ptr = ecs_get(world, type, EcsPrimitive); + ecs_assert(ptr != NULL, ECS_INTERNAL_ERROR, NULL); -static -void json_array_push( - ecs_strbuf_t *buf) -{ - ecs_strbuf_list_push(buf, "[", ", "); + ecs_meta_type_op_t *op = ops_add(&ops, primitive_to_op_kind(ptr->kind)); + op->offset = offset, + op->type = type; + op->size = type_size(world, type); + + return ops; } static -void json_array_pop( - ecs_strbuf_t *buf) +ecs_vector_t* serialize_enum( + ecs_world_t *world, + ecs_entity_t type, + ecs_size_t offset, + ecs_vector_t *ops) { - ecs_strbuf_list_pop(buf, "]"); + (void)world; + + ecs_meta_type_op_t *op = ops_add(&ops, EcsOpEnum); + op->offset = offset, + op->type = type; + op->size = ECS_SIZEOF(ecs_i32_t); + + return ops; } static -void json_object_push( - ecs_strbuf_t *buf) +ecs_vector_t* serialize_bitmask( + ecs_world_t *world, + ecs_entity_t type, + ecs_size_t offset, + ecs_vector_t *ops) { - ecs_strbuf_list_push(buf, "{", ", "); + (void)world; + + ecs_meta_type_op_t *op = ops_add(&ops, EcsOpBitmask); + op->offset = offset, + op->type = type; + op->size = ECS_SIZEOF(ecs_u32_t); + + return ops; } static -void json_object_pop( - ecs_strbuf_t *buf) +ecs_vector_t* serialize_array( + ecs_world_t *world, + ecs_entity_t type, + ecs_size_t offset, + ecs_vector_t *ops) { - ecs_strbuf_list_pop(buf, "}"); + (void)world; + + ecs_meta_type_op_t *op = ops_add(&ops, EcsOpArray); + op->offset = offset; + op->type = type; + op->size = type_size(world, type); + + return ops; } static -void json_string( - ecs_strbuf_t *buf, - const char *value) +ecs_vector_t* serialize_array_component( + ecs_world_t *world, + ecs_entity_t type) { - ecs_strbuf_appendstr(buf, "\""); - ecs_strbuf_appendstr(buf, value); - ecs_strbuf_appendstr(buf, "\""); + const EcsArray *ptr = ecs_get(world, type, EcsArray); + if (!ptr) { + return NULL; /* Should never happen, will trigger internal error */ + } + + ecs_vector_t *ops = serialize_type(world, ptr->type, 0, NULL); + ecs_assert(ops != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_meta_type_op_t *first = ecs_vector_first(ops, ecs_meta_type_op_t); + first->count = ptr->count; + + return ops; } static -void json_member( - ecs_strbuf_t *buf, - const char *name) +ecs_vector_t* serialize_vector( + ecs_world_t *world, + ecs_entity_t type, + ecs_size_t offset, + ecs_vector_t *ops) { - ecs_strbuf_list_appendstr(buf, "\""); - ecs_strbuf_appendstr(buf, name); - ecs_strbuf_appendstr(buf, "\":"); + (void)world; + + ecs_meta_type_op_t *op = ops_add(&ops, EcsOpVector); + op->offset = offset; + op->type = type; + op->size = type_size(world, type); + + return ops; } static -void json_path( - ecs_strbuf_t *buf, - const ecs_world_t *world, - ecs_entity_t e) +ecs_vector_t* serialize_struct( + ecs_world_t *world, + ecs_entity_t type, + ecs_size_t offset, + ecs_vector_t *ops) { - ecs_strbuf_appendch(buf, '"'); - ecs_get_fullpath_buf(world, e, buf); - ecs_strbuf_appendch(buf, '"'); + const EcsStruct *ptr = ecs_get(world, type, EcsStruct); + ecs_assert(ptr != NULL, ECS_INTERNAL_ERROR, NULL); + + int32_t cur, first = ecs_vector_count(ops); + ecs_meta_type_op_t *op = ops_add(&ops, EcsOpPush); + op->offset = offset; + op->type = type; + op->size = type_size(world, type); + + ecs_member_t *members = ecs_vector_first(ptr->members, ecs_member_t); + int32_t i, count = ecs_vector_count(ptr->members); + + ecs_hashmap_t *member_index = NULL; + if (count) { + member_index = ecs_os_malloc_t(ecs_hashmap_t); + *member_index = flecs_string_hashmap_new(int32_t); + op->members = member_index; + } + + for (i = 0; i < count; i ++) { + ecs_member_t *member = &members[i]; + + cur = ecs_vector_count(ops); + ops = serialize_type(world, member->type, offset + member->offset, ops); + + op = ops_get(ops, cur); + if (!op->type) { + op->type = member->type; + } + + if (op->count <= 1) { + op->count = member->count; + } + + const char *member_name = member->name; + op->name = member_name; + op->op_count = ecs_vector_count(ops) - cur; + + ecs_size_t len = ecs_os_strlen(member_name); + + ecs_hashed_string_t key = ecs_get_hashed_string(member_name, len, 0); + flecs_hashmap_result_t hmr = flecs_hashmap_ensure( + *member_index, &key, int32_t); + *((int32_t*)hmr.value) = cur - first - 1; + } + + ops_add(&ops, EcsOpPop); + ops_get(ops, first)->op_count = ecs_vector_count(ops) - first; + + return ops; } static -void json_id( - ecs_strbuf_t *buf, - const ecs_world_t *world, - ecs_id_t id) +ecs_vector_t* serialize_type( + ecs_world_t *world, + ecs_entity_t type, + ecs_size_t offset, + ecs_vector_t *ops) { - ecs_strbuf_appendch(buf, '"'); - ecs_id_str_buf(world, id, buf); - ecs_strbuf_appendch(buf, '"'); -} + const EcsMetaType *ptr = ecs_get(world, type, EcsMetaType); + if (!ptr) { + char *path = ecs_get_fullpath(world, type); + ecs_err("missing EcsMetaType for type %s'", path); + ecs_os_free(path); + return NULL; + } -static -ecs_primitive_kind_t json_op_to_primitive_kind(ecs_meta_type_op_kind_t kind) { - return kind - EcsOpPrimitive; + switch(ptr->kind) { + case EcsPrimitiveType: + ops = serialize_primitive(world, type, offset, ops); + break; + + case EcsEnumType: + ops = serialize_enum(world, type, offset, ops); + break; + + case EcsBitmaskType: + ops = serialize_bitmask(world, type, offset, ops); + break; + + case EcsStructType: + ops = serialize_struct(world, type, offset, ops); + break; + + case EcsArrayType: + ops = serialize_array(world, type, offset, ops); + break; + + case EcsVectorType: + ops = serialize_vector(world, type, offset, ops); + break; + } + + return ops; } -/* Serialize enumeration */ static -int json_ser_enum( - const ecs_world_t *world, - ecs_meta_type_op_t *op, - const void *base, - ecs_strbuf_t *str) +ecs_vector_t* serialize_component( + ecs_world_t *world, + ecs_entity_t type) { - const EcsEnum *enum_type = ecs_get(world, op->type, EcsEnum); - ecs_check(enum_type != NULL, ECS_INVALID_PARAMETER, NULL); - - int32_t value = *(int32_t*)base; - - /* Enumeration constants are stored in a map that is keyed on the - * enumeration value. */ - ecs_enum_constant_t *constant = ecs_map_get( - enum_type->constants, ecs_enum_constant_t, value); - if (!constant) { - goto error; + const EcsMetaType *ptr = ecs_get(world, type, EcsMetaType); + if (!ptr) { + char *path = ecs_get_fullpath(world, type); + ecs_err("missing EcsMetaType for type %s'", path); + ecs_os_free(path); + return NULL; } - ecs_strbuf_appendch(str, '"'); - ecs_strbuf_appendstr(str, ecs_get_name(world, constant->constant)); - ecs_strbuf_appendch(str, '"'); + ecs_vector_t *ops = NULL; - return 0; -error: - return -1; + switch(ptr->kind) { + case EcsArrayType: + ops = serialize_array_component(world, type); + break; + default: + ops = serialize_type(world, type, 0, NULL); + break; + } + + return ops; } -/* Serialize bitmask */ -static -int json_ser_bitmask( - const ecs_world_t *world, - ecs_meta_type_op_t *op, - const void *ptr, - ecs_strbuf_t *str) +void ecs_meta_type_serialized_init( + ecs_iter_t *it) { - const EcsBitmask *bitmask_type = ecs_get(world, op->type, EcsBitmask); - ecs_check(bitmask_type != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_world_t *world = it->world; - uint32_t value = *(uint32_t*)ptr; - ecs_map_key_t key; - ecs_bitmask_constant_t *constant; + int i, count = it->count; + for (i = 0; i < count; i ++) { + ecs_entity_t e = it->entities[i]; + ecs_vector_t *ops = serialize_component(world, e); + ecs_assert(ops != NULL, ECS_INTERNAL_ERROR, NULL); - if (!value) { - ecs_strbuf_appendch(str, '0'); - return 0; + EcsMetaTypeSerialized *ptr = ecs_get_mut( + world, e, EcsMetaTypeSerialized, NULL); + if (ptr->ops) { + ecs_meta_dtor_serialized(ptr); + } + + ptr->ops = ops; } +} - ecs_strbuf_list_push(str, "\"", "|"); +#endif - /* Multiple flags can be set at a given time. Iterate through all the flags - * and append the ones that are set. */ - ecs_map_iter_t it = ecs_map_iter(bitmask_type->constants); - while ((constant = ecs_map_next(&it, ecs_bitmask_constant_t, &key))) { - if ((value & key) == key) { - ecs_strbuf_list_appendstr(str, - ecs_get_name(world, constant->constant)); - value -= (uint32_t)key; +#ifdef FLECS_META + +/* EcsMetaTypeSerialized lifecycle */ + +void ecs_meta_dtor_serialized( + EcsMetaTypeSerialized *ptr) +{ + int32_t i, count = ecs_vector_count(ptr->ops); + ecs_meta_type_op_t *ops = ecs_vector_first(ptr->ops, ecs_meta_type_op_t); + + for (i = 0; i < count; i ++) { + ecs_meta_type_op_t *op = &ops[i]; + if (op->members) { + flecs_hashmap_free(*op->members); + ecs_os_free(op->members); } } - if (value != 0) { - /* All bits must have been matched by a constant */ - goto error; + ecs_vector_free(ptr->ops); +} + +static ECS_COPY(EcsMetaTypeSerialized, dst, src, { + ecs_meta_dtor_serialized(dst); + + dst->ops = ecs_vector_copy(src->ops, ecs_meta_type_op_t); + + int32_t o, count = ecs_vector_count(src->ops); + ecs_meta_type_op_t *ops = ecs_vector_first(src->ops, ecs_meta_type_op_t); + + for (o = 0; o < count; o ++) { + ecs_meta_type_op_t *op = &ops[o]; + if (op->members) { + op->members = ecs_os_memdup_t(op->members, ecs_hashmap_t); + *op->members = flecs_hashmap_copy(*op->members); + } } +}) - ecs_strbuf_list_pop(str, "\""); +static ECS_MOVE(EcsMetaTypeSerialized, dst, src, { + ecs_meta_dtor_serialized(dst); + dst->ops = src->ops; + src->ops = NULL; +}) - return 0; -error: - return -1; -} +static ECS_DTOR(EcsMetaTypeSerialized, ptr, { + ecs_meta_dtor_serialized(ptr); +}) -/* Serialize elements of a contiguous array */ -static -int json_ser_elements( - const ecs_world_t *world, - ecs_meta_type_op_t *ops, - int32_t op_count, - const void *base, - int32_t elem_count, - int32_t elem_size, - ecs_strbuf_t *str) + +/* EcsStruct lifecycle */ + +static void dtor_struct( + EcsStruct *ptr) { - json_array_push(str); + ecs_member_t *members = ecs_vector_first(ptr->members, ecs_member_t); + int32_t i, count = ecs_vector_count(ptr->members); + for (i = 0; i < count; i ++) { + ecs_os_free((char*)members[i].name); + } + ecs_vector_free(ptr->members); +} - const void *ptr = base; +static ECS_COPY(EcsStruct, dst, src, { + dtor_struct(dst); - int i; - for (i = 0; i < elem_count; i ++) { - ecs_strbuf_list_next(str); - if (json_ser_type_ops(world, ops, op_count, ptr, str)) { - return -1; - } - ptr = ECS_OFFSET(ptr, elem_size); + dst->members = ecs_vector_copy(src->members, ecs_member_t); + + ecs_member_t *members = ecs_vector_first(dst->members, ecs_member_t); + int32_t m, count = ecs_vector_count(dst->members); + + for (m = 0; m < count; m ++) { + members[m].name = ecs_os_strdup(members[m].name); } +}) - json_array_pop(str); +static ECS_MOVE(EcsStruct, dst, src, { + dtor_struct(dst); + dst->members = src->members; + src->members = NULL; +}) - return 0; -} +static ECS_DTOR(EcsStruct, ptr, { dtor_struct(ptr); }) -static -int json_ser_type_elements( - const ecs_world_t *world, - ecs_entity_t type, - const void *base, - int32_t elem_count, - ecs_strbuf_t *str) + +/* EcsEnum lifecycle */ + +static void dtor_enum( + EcsEnum *ptr) { - const EcsMetaTypeSerialized *ser = ecs_get( - world, type, EcsMetaTypeSerialized); - ecs_assert(ser != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_map_iter_t it = ecs_map_iter(ptr->constants); + ecs_enum_constant_t *c; + while ((c = ecs_map_next(&it, ecs_enum_constant_t, NULL))) { + ecs_os_free((char*)c->name); + } + ecs_map_free(ptr->constants); +} - const EcsComponent *comp = ecs_get(world, type, EcsComponent); - ecs_assert(comp != NULL, ECS_INTERNAL_ERROR, NULL); +static ECS_COPY(EcsEnum, dst, src, { + dtor_enum(dst); - ecs_meta_type_op_t *ops = ecs_vector_first(ser->ops, ecs_meta_type_op_t); - int32_t op_count = ecs_vector_count(ser->ops); + dst->constants = ecs_map_copy(src->constants); + ecs_assert(ecs_map_count(dst->constants) == ecs_map_count(src->constants), + ECS_INTERNAL_ERROR, NULL); - return json_ser_elements( - world, ops, op_count, base, elem_count, comp->size, str); -} + ecs_map_iter_t it = ecs_map_iter(dst->constants); + ecs_enum_constant_t *c; + while ((c = ecs_map_next(&it, ecs_enum_constant_t, NULL))) { + c->name = ecs_os_strdup(c->name); + } +}) -/* Serialize array */ -static -int json_ser_array( - const ecs_world_t *world, - ecs_meta_type_op_t *op, - const void *ptr, - ecs_strbuf_t *str) -{ - const EcsArray *a = ecs_get(world, op->type, EcsArray); - ecs_assert(a != NULL, ECS_INTERNAL_ERROR, NULL); +static ECS_MOVE(EcsEnum, dst, src, { + dtor_enum(dst); + dst->constants = src->constants; + src->constants = NULL; +}) - return json_ser_type_elements( - world, a->type, ptr, a->count, str); -} +static ECS_DTOR(EcsEnum, ptr, { dtor_enum(ptr); }) -/* Serialize vector */ -static -int json_ser_vector( - const ecs_world_t *world, - ecs_meta_type_op_t *op, - const void *base, - ecs_strbuf_t *str) + +/* EcsBitmask lifecycle */ + +static void dtor_bitmask( + EcsBitmask *ptr) { - ecs_vector_t *value = *(ecs_vector_t**)base; - if (!value) { - ecs_strbuf_appendstr(str, "null"); - return 0; + ecs_map_iter_t it = ecs_map_iter(ptr->constants); + ecs_bitmask_constant_t *c; + while ((c = ecs_map_next(&it, ecs_bitmask_constant_t, NULL))) { + ecs_os_free((char*)c->name); } + ecs_map_free(ptr->constants); +} + +static ECS_COPY(EcsBitmask, dst, src, { + dtor_bitmask(dst); + + dst->constants = ecs_map_copy(src->constants); + ecs_assert(ecs_map_count(dst->constants) == ecs_map_count(src->constants), + ECS_INTERNAL_ERROR, NULL); + + ecs_map_iter_t it = ecs_map_iter(dst->constants); + ecs_bitmask_constant_t *c; + while ((c = ecs_map_next(&it, ecs_bitmask_constant_t, NULL))) { + c->name = ecs_os_strdup(c->name); + } +}) - const EcsVector *v = ecs_get(world, op->type, EcsVector); - ecs_assert(v != NULL, ECS_INTERNAL_ERROR, NULL); +static ECS_MOVE(EcsBitmask, dst, src, { + dtor_bitmask(dst); + dst->constants = src->constants; + src->constants = NULL; +}) - const EcsComponent *comp = ecs_get(world, v->type, EcsComponent); - ecs_assert(comp != NULL, ECS_INTERNAL_ERROR, NULL); +static ECS_DTOR(EcsBitmask, ptr, { dtor_bitmask(ptr); }) - int32_t count = ecs_vector_count(value); - void *array = ecs_vector_first_t(value, comp->size, comp->alignment); - /* Serialize contiguous buffer of vector */ - return json_ser_type_elements(world, v->type, array, count, str); -} +/* Type initialization */ -/* Forward serialization to the different type kinds */ static -int json_ser_type_op( - const ecs_world_t *world, - ecs_meta_type_op_t *op, - const void *ptr, - ecs_strbuf_t *str) +int init_type( + ecs_world_t *world, + ecs_entity_t type, + ecs_type_kind_t kind) { - switch(op->kind) { - case EcsOpPush: - case EcsOpPop: - /* Should not be parsed as single op */ - ecs_throw(ECS_INVALID_PARAMETER, NULL); - break; - case EcsOpEnum: - if (json_ser_enum(world, op, ECS_OFFSET(ptr, op->offset), str)) { - goto error; - } - break; - case EcsOpBitmask: - if (json_ser_bitmask(world, op, ECS_OFFSET(ptr, op->offset), str)) { - goto error; - } - break; - case EcsOpArray: - if (json_ser_array(world, op, ECS_OFFSET(ptr, op->offset), str)) { - goto error; - } - break; - case EcsOpVector: - if (json_ser_vector(world, op, ECS_OFFSET(ptr, op->offset), str)) { - goto error; - } - break; - case EcsOpEntity: { - ecs_entity_t e = *(ecs_entity_t*)ptr; - if (!e) { - ecs_strbuf_appendch(str, '0'); - } else { - char *path = ecs_get_fullpath(world, e); - ecs_assert(path != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_strbuf_append(str, "\"%s\"", path); - ecs_os_free(path); - } - break; - } + ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(type != 0, ECS_INTERNAL_ERROR, NULL); - default: - if (ecs_primitive_to_expr_buf(world, json_op_to_primitive_kind(op->kind), - ECS_OFFSET(ptr, op->offset), str)) - { - /* Unknown operation */ - ecs_throw(ECS_INTERNAL_ERROR, NULL); - return -1; - } - break; + EcsMetaType *meta_type = ecs_get_mut(world, type, EcsMetaType, NULL); + if (meta_type->kind && meta_type->kind != kind) { + ecs_err("type '%s' reregistered with different kind", + ecs_get_name(world, type)); + return -1; } + meta_type->kind = kind; + ecs_modified(world, type, EcsMetaType); + return 0; -error: - return -1; } -/* Iterate over a slice of the type ops array */ static -int json_ser_type_ops( - const ecs_world_t *world, - ecs_meta_type_op_t *ops, - int32_t op_count, - const void *base, - ecs_strbuf_t *str) +int init_component( + ecs_world_t *world, + ecs_entity_t type, + ecs_size_t size, + ecs_size_t alignment) { - for (int i = 0; i < op_count; i ++) { - ecs_meta_type_op_t *op = &ops[i]; - - if (op != ops) { - if (op->name) { - json_member(str, op->name); - } + ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(type != 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(size != 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(alignment != 0, ECS_INTERNAL_ERROR, NULL); - int32_t elem_count = op->count; - if (elem_count > 1 && op != ops) { - /* Serialize inline array */ - if (json_ser_elements(world, op, op->op_count, base, - elem_count, op->size, str)) - { - return -1; - } + EcsComponent *component = ecs_get_mut(world, type, EcsComponent, NULL); + if (component->size && component->size != size) { + ecs_err("type '%s' reregistered with different size", + ecs_get_name(world, type)); + return -1; + } - i += op->op_count - 1; - continue; - } - } - - switch(op->kind) { - case EcsOpPush: - json_object_push(str); - break; - case EcsOpPop: - json_object_pop(str); - break; - default: - if (json_ser_type_op(world, op, base, str)) { - goto error; - } - break; - } + if (component->alignment && component->alignment != alignment) { + ecs_err("type '%s' reregistered with different alignment", + ecs_get_name(world, type)); + return -1; } - return 0; -error: - return -1; -} + component->size = size; + component->alignment = alignment; + ecs_modified(world, type, EcsComponent); -/* Iterate over the type ops of a type */ -static -int json_ser_type( - const ecs_world_t *world, - ecs_vector_t *v_ops, - const void *base, - ecs_strbuf_t *str) -{ - ecs_meta_type_op_t *ops = ecs_vector_first(v_ops, ecs_meta_type_op_t); - int32_t count = ecs_vector_count(v_ops); - return json_ser_type_ops(world, ops, count, base, str); + return 0; } static -int array_to_json_buf_w_type_data( - const ecs_world_t *world, - const void *ptr, - int32_t count, - ecs_strbuf_t *buf, - const EcsComponent *comp, - const EcsMetaTypeSerialized *ser) +void set_struct_member( + ecs_member_t *member, + ecs_entity_t entity, + const char *name, + ecs_entity_t type, + int32_t count) { - if (count) { - ecs_size_t size = comp->size; - - json_array_push(buf); - - do { - ecs_strbuf_list_next(buf); - if (json_ser_type(world, ser->ops, ptr, buf)) { - return -1; - } - - ptr = ECS_OFFSET(ptr, size); - } while (-- count); + member->member = entity; + member->type = type; + member->count = count; - json_array_pop(buf); - } else { - if (json_ser_type(world, ser->ops, ptr, buf)) { - return -1; - } + if (!count) { + member->count = 1; } - return 0; + ecs_os_strset((char**)&member->name, name); } -int ecs_array_to_json_buf( - const ecs_world_t *world, +static +int add_member_to_struct( + ecs_world_t *world, ecs_entity_t type, - const void *ptr, - int32_t count, - ecs_strbuf_t *buf) + ecs_entity_t member, + EcsMember *m) { - const EcsComponent *comp = ecs_get(world, type, EcsComponent); - if (!comp) { + ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(type != 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(member != 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(m != NULL, ECS_INTERNAL_ERROR, NULL); + + const char *name = ecs_get_name(world, member); + if (!name) { char *path = ecs_get_fullpath(world, type); - ecs_err("cannot serialize to JSON, '%s' is not a component", path); + ecs_err("member for struct '%s' does not have a name", path); ecs_os_free(path); return -1; } - const EcsMetaTypeSerialized *ser = ecs_get( - world, type, EcsMetaTypeSerialized); - if (!ser) { - char *path = ecs_get_fullpath(world, type); - ecs_err("cannot serialize to JSON, '%s' has no reflection data", path); + if (!m->type) { + char *path = ecs_get_fullpath(world, member); + ecs_err("member '%s' does not have a type", path); ecs_os_free(path); return -1; } - return array_to_json_buf_w_type_data(world, ptr, count, buf, comp, ser); -} - -char* ecs_array_to_json( - const ecs_world_t *world, - ecs_entity_t type, - const void* ptr, - int32_t count) -{ - ecs_strbuf_t str = ECS_STRBUF_INIT; - - if (ecs_array_to_json_buf(world, type, ptr, count, &str) != 0) { - ecs_strbuf_reset(&str); - return NULL; + if (ecs_get_typeid(world, m->type) == 0) { + char *path = ecs_get_fullpath(world, member); + char *ent_path = ecs_get_fullpath(world, m->type); + ecs_err("member '%s.type' is '%s' which is not a type", path, ent_path); + ecs_os_free(path); + ecs_os_free(ent_path); + return -1; } - return ecs_strbuf_get(&str); -} + EcsStruct *s = ecs_get_mut(world, type, EcsStruct, NULL); + ecs_assert(s != NULL, ECS_INTERNAL_ERROR, NULL); -int ecs_ptr_to_json_buf( - const ecs_world_t *world, - ecs_entity_t type, - const void *ptr, - ecs_strbuf_t *buf) -{ - return ecs_array_to_json_buf(world, type, ptr, 0, buf); -} + /* First check if member is already added to struct */ + ecs_member_t *members = ecs_vector_first(s->members, ecs_member_t); + int32_t i, count = ecs_vector_count(s->members); + for (i = 0; i < count; i ++) { + if (members[i].member == member) { + set_struct_member(&members[i], member, name, m->type, m->count); + break; + } + } -char* ecs_ptr_to_json( - const ecs_world_t *world, - ecs_entity_t type, - const void* ptr) -{ - return ecs_array_to_json(world, type, ptr, 0); -} + /* If member wasn't added yet, add a new element to vector */ + if (i == count) { + ecs_member_t *elem = ecs_vector_add(&s->members, ecs_member_t); + elem->name = NULL; + set_struct_member(elem, member, name, m->type, m->count); -static -int append_type( - const ecs_world_t *world, - ecs_strbuf_t *buf, - ecs_entity_t ent, - ecs_entity_t inst) -{ - ecs_type_t type = ecs_get_type(world, ent); - ecs_id_t *ids = ecs_vector_first(type, ecs_id_t); - int32_t i, count = ecs_vector_count(type); + /* Reobtain members array in case it was reallocated */ + members = ecs_vector_first(s->members, ecs_member_t); + count ++; + } - json_member(buf, "type"); - json_array_push(buf); + /* Compute member offsets and size & alignment of struct */ + ecs_size_t size = 0; + ecs_size_t alignment = 0; for (i = 0; i < count; i ++) { - ecs_id_t id = ids[i]; - ecs_entity_t pred = 0, obj = 0, role = 0; - - if (ECS_HAS_RELATION(id, EcsIsA)) { - /* Skip IsA as they are already added in "inherits" */ - continue; - } + ecs_member_t *elem = &members[i]; - if (ent != inst) { - /* If not serializing the top level entity, skip components that are - * never inherited from a base entity */ - if (id == ecs_pair(ecs_id(EcsIdentifier), EcsName) || - ECS_PAIR_RELATION(id) == EcsChildOf || - id == EcsPrefab) - { - continue; - } - } + ecs_assert(elem->name != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(elem->type != 0, ECS_INTERNAL_ERROR, NULL); - if (ECS_HAS_ROLE(id, PAIR)) { - pred = ecs_pair_relation(world, id); - obj = ecs_pair_object(world, id); - } else { - pred = id & ECS_COMPONENT_MASK; - if (id & ECS_ROLE_MASK) { - role = id & ECS_ROLE_MASK; - } + /* Get component of member type to get its size & alignment */ + const EcsComponent *mbr_comp = ecs_get(world, elem->type, EcsComponent); + if (!mbr_comp) { + char *path = ecs_get_fullpath(world, member); + ecs_err("member '%s' is not a type", path); + ecs_os_free(path); + return -1; } - ecs_strbuf_list_next(buf); - json_object_push(buf); + ecs_size_t member_size = mbr_comp->size; + ecs_size_t member_alignment = mbr_comp->alignment; - if (pred) { - char *str = ecs_get_fullpath(world, pred); - json_member(buf, "pred"); json_string(buf, str); - ecs_os_free(str); - } - if (obj) { - char *str = ecs_get_fullpath(world, obj); - json_member(buf, "obj"); - json_string(buf, str); - ecs_os_free(str); - } - if (role) { - json_member(buf, "obj"); - json_string(buf, ecs_role_str(role)); + if (!member_size || !member_alignment) { + char *path = ecs_get_fullpath(world, member); + ecs_err("member '%s' has 0 size/alignment"); + ecs_os_free(path); + return -1; } - bool hidden = false; + member_size *= elem->count; + size = ECS_ALIGN(size, member_alignment); + elem->size = member_size; + elem->offset = size; - if (ent != inst) { - if (ecs_get_object_for_id(world, inst, EcsIsA, id) != ent) { - hidden = true; - ecs_strbuf_list_appendstr(buf, "\"hidden\":true"); - } - } + size += member_size; - if (!hidden) { - ecs_entity_t typeid = ecs_get_typeid(world, id); - if (typeid) { - const EcsMetaTypeSerialized *ser = ecs_get( - world, typeid, EcsMetaTypeSerialized); - if (ser) { - const void *ptr = ecs_get_id(world, ent, id); - ecs_assert(ptr != NULL, ECS_INTERNAL_ERROR, NULL); - json_member(buf, "value"); - if (json_ser_type(world, ser->ops, ptr, buf) != 0) { - /* Entity contains invalid value */ - return -1; - } - } - } + if (member_alignment > alignment) { + alignment = member_alignment; } - - json_object_pop(buf); } - json_array_pop(buf); - - return 0; -} - -static -int append_base( - const ecs_world_t *world, - ecs_strbuf_t *buf, - ecs_entity_t ent, - ecs_entity_t inst) -{ - ecs_type_t type = ecs_get_type(world, ent); - ecs_id_t *ids = ecs_vector_first(type, ecs_id_t); - int32_t i, count = ecs_vector_count(type); + if (size == 0) { + ecs_err("struct '%s' has 0 size", ecs_get_name(world, type)); + return -1; + } - for (i = 0; i < count; i ++) { - ecs_id_t id = ids[i]; - if (ECS_HAS_RELATION(id, EcsIsA)) { - if (append_base(world, buf, ecs_pair_object(world, id), inst)) { - return -1; - } - } + if (alignment == 0) { + ecs_err("struct '%s' has 0 alignment", ecs_get_name(world, type)); + return -1; } - char *path = ecs_get_fullpath(world, ent); - json_member(buf, path); - ecs_os_free(path); + /* Align struct size to struct alignment */ + size = ECS_ALIGN(size, alignment); - json_object_push(buf); + ecs_modified(world, type, EcsStruct); + + /* Overwrite component size & alignment */ + if (type != ecs_id(EcsComponent)) { + EcsComponent *comp = ecs_get_mut(world, type, EcsComponent, NULL); + comp->size = size; + comp->alignment = alignment; + ecs_modified(world, type, EcsComponent); + } - if (append_type(world, buf, ent, inst)) { + /* Do this last as it triggers the update of EcsMetaTypeSerialized */ + if (init_type(world, type, EcsStructType)) { return -1; } - json_object_pop(buf); + /* If current struct is also a member, assign to itself */ + if (ecs_has(world, type, EcsMember)) { + EcsMember *type_mbr = ecs_get_mut(world, type, EcsMember, NULL); + ecs_assert(type_mbr != NULL, ECS_INTERNAL_ERROR, NULL); + + type_mbr->type = type; + type_mbr->count = 1; + + ecs_modified(world, type, EcsMember); + } return 0; } -int ecs_entity_to_json_buf( - const ecs_world_t *world, - ecs_entity_t entity, - ecs_strbuf_t *buf) +static +int add_constant_to_enum( + ecs_world_t *world, + ecs_entity_t type, + ecs_entity_t e, + ecs_id_t constant_id) { - if (!entity || !ecs_is_valid(world, entity)) { - return -1; + EcsEnum *ptr = ecs_get_mut(world, type, EcsEnum, NULL); + + /* Remove constant from map if it was already added */ + ecs_map_iter_t it = ecs_map_iter(ptr->constants); + ecs_enum_constant_t *c; + ecs_map_key_t key; + while ((c = ecs_map_next(&it, ecs_enum_constant_t, &key))) { + if (c->constant == e) { + ecs_os_free((char*)c->name); + ecs_map_remove(ptr->constants, key); + } } - json_object_push(buf); - - char *path = ecs_get_fullpath(world, entity); - json_member(buf, "path"); - json_string(buf, path); - ecs_os_free(path); - - ecs_type_t type = ecs_get_type(world, entity); - ecs_id_t *ids = ecs_vector_first(type, ecs_id_t); - int32_t i, count = ecs_vector_count(type); + /* Check if constant sets explicit value */ + int32_t value = 0; + bool value_set = false; + if (ecs_id_is_pair(constant_id)) { + if (ecs_pair_object(world, constant_id) != ecs_id(ecs_i32_t)) { + char *path = ecs_get_fullpath(world, e); + ecs_err("expected i32 type for enum constant '%s'", path); + ecs_os_free(path); + return -1; + } - if (ecs_has_pair(world, entity, EcsIsA, EcsWildcard)) { - json_member(buf, "is_a"); - json_object_push(buf); + const int32_t *value_ptr = ecs_get_pair_object( + world, e, EcsConstant, ecs_i32_t); + ecs_assert(value_ptr != NULL, ECS_INTERNAL_ERROR, NULL); + value = *value_ptr; + value_set = true; + } - for (i = 0; i < count; i ++) { - ecs_id_t id = ids[i]; - if (ECS_HAS_RELATION(id, EcsIsA)) { - if (append_base( - world, buf, ecs_pair_object(world, id), entity)) - { - return -1; - } + /* Make sure constant value doesn't conflict if set / find the next value */ + it = ecs_map_iter(ptr->constants); + while ((c = ecs_map_next(&it, ecs_enum_constant_t, &key))) { + if (value_set) { + if (c->value == value) { + char *path = ecs_get_fullpath(world, e); + ecs_err("conflicting constant value for '%s' (other is '%s')", + path, c->name); + ecs_os_free(path); + return -1; + } + } else { + if (c->value >= value) { + value = c->value + 1; } } - - json_object_pop(buf); } - if (append_type(world, buf, entity, entity)) { - goto error; + if (!ptr->constants) { + ptr->constants = ecs_map_new(ecs_enum_constant_t, 1); } - json_object_pop(buf); + c = ecs_map_ensure(ptr->constants, ecs_enum_constant_t, value); + c->name = ecs_os_strdup(ecs_get_name(world, e)); + c->value = value; + c->constant = e; + + ecs_i32_t *cptr = ecs_get_mut_pair_object( + world, e, EcsConstant, ecs_i32_t, NULL); + ecs_assert(cptr != NULL, ECS_INTERNAL_ERROR, NULL); + cptr[0] = value; return 0; -error: - return -1; } -char* ecs_entity_to_json( - const ecs_world_t *world, - ecs_entity_t entity) +static +int add_constant_to_bitmask( + ecs_world_t *world, + ecs_entity_t type, + ecs_entity_t e, + ecs_id_t constant_id) { - ecs_strbuf_t buf = ECS_STRBUF_INIT; - - if (ecs_entity_to_json_buf(world, entity, &buf) != 0) { - ecs_strbuf_reset(&buf); - return NULL; + EcsBitmask *ptr = ecs_get_mut(world, type, EcsBitmask, NULL); + + /* Remove constant from map if it was already added */ + ecs_map_iter_t it = ecs_map_iter(ptr->constants); + ecs_bitmask_constant_t *c; + ecs_map_key_t key; + while ((c = ecs_map_next(&it, ecs_bitmask_constant_t, &key))) { + if (c->constant == e) { + ecs_os_free((char*)c->name); + ecs_map_remove(ptr->constants, key); + } } - return ecs_strbuf_get(&buf); -} + /* Check if constant sets explicit value */ + uint32_t value = 1; + if (ecs_id_is_pair(constant_id)) { + if (ecs_pair_object(world, constant_id) != ecs_id(ecs_u32_t)) { + char *path = ecs_get_fullpath(world, e); + ecs_err("expected u32 type for bitmask constant '%s'", path); + ecs_os_free(path); + return -1; + } -static -bool skip_variable( - const char *name) -{ - if (!name || name[0] == '_' || name[0] == '.') { - return true; + const uint32_t *value_ptr = ecs_get_pair_object( + world, e, EcsConstant, ecs_u32_t); + ecs_assert(value_ptr != NULL, ECS_INTERNAL_ERROR, NULL); + value = *value_ptr; } else { - return false; + value = 1u << (ecs_u32_t)ecs_map_count(ptr->constants); } -} -static -void serialize_id( - const ecs_world_t *world, - ecs_id_t id, - ecs_strbuf_t *buf) -{ - json_id(buf, world, id); -} + /* Make sure constant value doesn't conflict */ + it = ecs_map_iter(ptr->constants); + while ((c = ecs_map_next(&it, ecs_bitmask_constant_t, &key))) { + if (c->value == value) { + char *path = ecs_get_fullpath(world, e); + ecs_err("conflicting constant value for '%s' (other is '%s')", + path, c->name); + ecs_os_free(path); + return -1; + } + } -static -void serialize_iter_ids( - const ecs_world_t *world, - const ecs_iter_t *it, - ecs_strbuf_t *buf) -{ - int32_t term_count = it->term_count; - if (!term_count) { - return; + if (!ptr->constants) { + ptr->constants = ecs_map_new(ecs_bitmask_constant_t, 1); } - json_member(buf, "ids"); - json_array_push(buf); + c = ecs_map_ensure(ptr->constants, ecs_bitmask_constant_t, value); + c->name = ecs_os_strdup(ecs_get_name(world, e)); + c->value = value; + c->constant = e; - for (int i = 0; i < term_count; i ++) { - json_next(buf); - serialize_id(world, it->terms[i].id, buf); - } + ecs_u32_t *cptr = ecs_get_mut_pair_object( + world, e, EcsConstant, ecs_u32_t, NULL); + ecs_assert(cptr != NULL, ECS_INTERNAL_ERROR, NULL); + cptr[0] = value; - json_array_pop(buf); + return 0; } static -void serialize_iter_variables(ecs_iter_t *it, ecs_strbuf_t *buf) { - char **variable_names = it->variable_names; - int32_t var_count = it->variable_count; - int32_t actual_count = 0; - - for (int i = 0; i < var_count; i ++) { - const char *var_name = variable_names[i]; - if (skip_variable(var_name)) continue; +void set_primitive(ecs_iter_t *it) { + ecs_world_t *world = it->world; + EcsPrimitive *type = ecs_term(it, EcsPrimitive, 1); - if (!actual_count) { - json_member(buf, "vars"); - json_array_push(buf); - actual_count ++; + int i, count = it->count; + for (i = 0; i < count; i ++) { + ecs_entity_t e = it->entities[i]; + switch(type->kind) { + case EcsBool: + init_component(world, e, + ECS_SIZEOF(bool), ECS_ALIGNOF(bool)); + init_type(world, e, EcsPrimitiveType); + break; + case EcsChar: + init_component(world, e, + ECS_SIZEOF(char), ECS_ALIGNOF(char)); + init_type(world, e, EcsPrimitiveType); + break; + case EcsByte: + init_component(world, e, + ECS_SIZEOF(bool), ECS_ALIGNOF(bool)); + init_type(world, e, EcsPrimitiveType); + break; + case EcsU8: + init_component(world, e, + ECS_SIZEOF(uint8_t), ECS_ALIGNOF(uint8_t)); + init_type(world, e, EcsPrimitiveType); + break; + case EcsU16: + init_component(world, e, + ECS_SIZEOF(uint16_t), ECS_ALIGNOF(uint16_t)); + init_type(world, e, EcsPrimitiveType); + break; + case EcsU32: + init_component(world, e, + ECS_SIZEOF(uint32_t), ECS_ALIGNOF(uint32_t)); + init_type(world, e, EcsPrimitiveType); + break; + case EcsU64: + init_component(world, e, + ECS_SIZEOF(uint64_t), ECS_ALIGNOF(uint64_t)); + init_type(world, e, EcsPrimitiveType); + break; + case EcsI8: + init_component(world, e, + ECS_SIZEOF(int8_t), ECS_ALIGNOF(int8_t)); + init_type(world, e, EcsPrimitiveType); + break; + case EcsI16: + init_component(world, e, + ECS_SIZEOF(int16_t), ECS_ALIGNOF(int16_t)); + init_type(world, e, EcsPrimitiveType); + break; + case EcsI32: + init_component(world, e, + ECS_SIZEOF(int32_t), ECS_ALIGNOF(int32_t)); + init_type(world, e, EcsPrimitiveType); + break; + case EcsI64: + init_component(world, e, + ECS_SIZEOF(int64_t), ECS_ALIGNOF(int64_t)); + init_type(world, e, EcsPrimitiveType); + break; + case EcsF32: + init_component(world, e, + ECS_SIZEOF(float), ECS_ALIGNOF(float)); + init_type(world, e, EcsPrimitiveType); + break; + case EcsF64: + init_component(world, e, + ECS_SIZEOF(double), ECS_ALIGNOF(double)); + init_type(world, e, EcsPrimitiveType); + break; + case EcsUPtr: + init_component(world, e, + ECS_SIZEOF(uintptr_t), ECS_ALIGNOF(uintptr_t)); + init_type(world, e, EcsPrimitiveType); + break; + case EcsIPtr: + init_component(world, e, + ECS_SIZEOF(intptr_t), ECS_ALIGNOF(intptr_t)); + init_type(world, e, EcsPrimitiveType); + break; + case EcsString: + init_component(world, e, + ECS_SIZEOF(char*), ECS_ALIGNOF(char*)); + init_type(world, e, EcsPrimitiveType); + break; + case EcsEntity: + init_component(world, e, + ECS_SIZEOF(ecs_entity_t), ECS_ALIGNOF(ecs_entity_t)); + init_type(world, e, EcsPrimitiveType); + break; } - - ecs_strbuf_list_next(buf); - json_string(buf, var_name); - } - - if (actual_count) { - json_array_pop(buf); } } static -void serialize_iter_result_ids( - const ecs_world_t *world, - const ecs_iter_t *it, - ecs_strbuf_t *buf) -{ - json_member(buf, "ids"); - json_array_push(buf); +void set_member(ecs_iter_t *it) { + ecs_world_t *world = it->world; + EcsMember *member = ecs_term(it, EcsMember, 1); - for (int i = 0; i < it->term_count; i ++) { - json_next(buf); - serialize_id(world, ecs_term_id(it, i + 1), buf); - } + int i, count = it->count; + for (i = 0; i < count; i ++) { + ecs_entity_t e = it->entities[i]; + ecs_entity_t parent = ecs_get_object(world, e, EcsChildOf, 0); + if (!parent) { + ecs_err("missing parent for member '%s'", ecs_get_name(world, e)); + continue; + } - json_array_pop(buf); + add_member_to_struct(world, parent, e, member); + } } static -void serialize_iter_result_subjects( - const ecs_world_t *world, - const ecs_iter_t *it, - ecs_strbuf_t *buf) -{ - json_member(buf, "subjects"); - json_array_push(buf); +void add_enum(ecs_iter_t *it) { + ecs_world_t *world = it->world; - for (int i = 0; i < it->term_count; i ++) { - json_next(buf); - ecs_entity_t subj = it->subjects[i]; - if (subj) { - json_path(buf, world, subj); - } else { - json_literal(buf, "0"); + int i, count = it->count; + for (i = 0; i < count; i ++) { + ecs_entity_t e = it->entities[i]; + + if (init_component( + world, e, ECS_SIZEOF(ecs_i32_t), ECS_ALIGNOF(ecs_i32_t))) + { + continue; } - } - - json_array_pop(buf); -} - -static -void serialize_iter_result_is_set( - const ecs_iter_t *it, - ecs_strbuf_t *buf) -{ - json_member(buf, "is_set"); - json_array_push(buf); - for (int i = 0; i < it->term_count; i ++) { - ecs_strbuf_list_next(buf); - if (ecs_term_is_set(it, i + 1)) { - json_true(buf); - } else { - json_false(buf); + if (init_type(world, e, EcsEnumType)) { + continue; } } - - json_array_pop(buf); } static -void serialize_iter_result_variables( - const ecs_world_t *world, - const ecs_iter_t *it, - ecs_strbuf_t *buf) -{ - char **variable_names = it->variable_names; - ecs_entity_t *variables = it->variables; - int32_t var_count = it->variable_count; - int32_t actual_count = 0; - - for (int i = 0; i < var_count; i ++) { - const char *var_name = variable_names[i]; - if (skip_variable(var_name)) continue; +void add_bitmask(ecs_iter_t *it) { + ecs_world_t *world = it->world; - if (!actual_count) { - json_member(buf, "vars"); - json_array_push(buf); - actual_count ++; + int i, count = it->count; + for (i = 0; i < count; i ++) { + ecs_entity_t e = it->entities[i]; + + if (init_component( + world, e, ECS_SIZEOF(ecs_u32_t), ECS_ALIGNOF(ecs_u32_t))) + { + continue; } - ecs_strbuf_list_next(buf); - json_path(buf, world, variables[i]); - } - - if (actual_count) { - json_array_pop(buf); + if (init_type(world, e, EcsBitmaskType)) { + continue; + } } } static -void serialize_iter_result_entities( - const ecs_world_t *world, - const ecs_iter_t *it, - ecs_strbuf_t *buf) -{ - int32_t count = it->count; - if (!it->count) { - return; - } - - json_member(buf, "entities"); - json_array_push(buf); +void add_constant(ecs_iter_t *it) { + ecs_world_t *world = it->world; - ecs_entity_t *entities = it->entities; + int i, count = it->count; + for (i = 0; i < count; i ++) { + ecs_entity_t e = it->entities[i]; + ecs_entity_t parent = ecs_get_object(world, e, EcsChildOf, 0); + if (!parent) { + ecs_err("missing parent for constant '%s'", ecs_get_name(world, e)); + continue; + } - for (int i = 0; i < count; i ++) { - json_next(buf); - json_path(buf, world, entities[i]); + if (ecs_has(world, parent, EcsEnum)) { + add_constant_to_enum(world, parent, e, it->event_id); + } else if (ecs_has(world, parent, EcsBitmask)) { + add_constant_to_bitmask(world, parent, e, it->event_id); + } } - - json_array_pop(buf); } static -void serialize_iter_result_values( - const ecs_world_t *world, - const ecs_iter_t *it, - ecs_strbuf_t *buf) -{ - int32_t count = it->count; - if (!it->count) { - return; - } +void set_array(ecs_iter_t *it) { + ecs_world_t *world = it->world; + EcsArray *array = ecs_term(it, EcsArray, 1); - json_member(buf, "values"); - json_array_push(buf); + int i, count = it->count; + for (i = 0; i < count; i ++) { + ecs_entity_t e = it->entities[i]; + ecs_entity_t elem_type = array[i].type; + int32_t elem_count = array[i].count; - int32_t i, term_count = it->term_count; - for (i = 0; i < term_count; i ++) { - ecs_strbuf_list_next(buf); - - const void *ptr = it->ptrs[i]; - if (!ptr) { - /* No data in column */ - json_literal(buf, "0"); + if (!elem_type) { + ecs_err("array '%s' has no element type", ecs_get_name(world, e)); continue; } - /* Get component id (can be different in case of pairs) */ - ecs_entity_t type = ecs_get_typeid(world, it->ids[i]); - if (!type) { - /* Odd, we have a ptr but no Component? Not the place of the - * serializer to complain about that. */ - json_literal(buf, "0"); + if (!elem_count) { + ecs_err("array '%s' has size 0", ecs_get_name(world, e)); continue; } - const EcsComponent *comp = ecs_get(world, type, EcsComponent); - if (!comp) { - /* Also odd, typeid but not a component? */ - json_literal(buf, "0"); + const EcsComponent *elem_ptr = ecs_get(world, elem_type, EcsComponent); + if (init_component( + world, e, elem_ptr->size * elem_count, elem_ptr->alignment)) + { continue; } - const EcsMetaTypeSerialized *ser = ecs_get( - world, type, EcsMetaTypeSerialized); - if (!ser) { - /* Not odd, component just has no reflection data */ - json_literal(buf, "0"); + if (init_type(world, e, EcsArrayType)) { continue; } - - if (ecs_term_is_owned(it, i + 1)) { - array_to_json_buf_w_type_data(world, ptr, count, buf, comp, ser); - } else { - array_to_json_buf_w_type_data(world, ptr, 0, buf, comp, ser); - } } - - json_array_pop(buf); } static -void serialize_iter_result( - const ecs_world_t *world, - const ecs_iter_t *it, - ecs_strbuf_t *buf, - const ecs_iter_to_json_desc_t *desc) -{ - json_next(buf); - json_object_push(buf); - - /* Each result can be matched with different component ids. Add them to - * the result so clients know with which component an entity was matched */ - if (!desc || !desc->dont_serialize_ids) { - serialize_iter_result_ids(world, it, buf); - } - - /* Include information on which entity the term is matched with */ - if (!desc || !desc->dont_serialize_ids) { - serialize_iter_result_subjects(world, it, buf); - } +void set_vector(ecs_iter_t *it) { + ecs_world_t *world = it->world; + EcsVector *array = ecs_term(it, EcsVector, 1); - /* Write variable values for current result */ - if (!desc || !desc->dont_serialize_variables) { - serialize_iter_result_variables(world, it, buf); - } + int i, count = it->count; + for (i = 0; i < count; i ++) { + ecs_entity_t e = it->entities[i]; + ecs_entity_t elem_type = array[i].type; - /* Include information on which terms are set, to support optional terms */ - if (!desc || !desc->dont_serialize_is_set) { - serialize_iter_result_is_set(it, buf); - } + if (!elem_type) { + ecs_err("vector '%s' has no element type", ecs_get_name(world, e)); + continue; + } - /* Write entity ids for current result (for queries with This terms) */ - if (!desc || !desc->dont_serialize_entities) { - serialize_iter_result_entities(world, it, buf); - } + if (init_component(world, e, + ECS_SIZEOF(ecs_vector_t*), ECS_ALIGNOF(ecs_vector_t*))) + { + continue; + } - /* Serialize component values */ - if (!desc || !desc->dont_serialize_values) { - serialize_iter_result_values(world, it, buf); + if (init_type(world, e, EcsVectorType)) { + continue; + } } - - json_object_pop(buf); } -int ecs_iter_to_json_buf( - const ecs_world_t *world, - ecs_iter_t *it, - ecs_strbuf_t *buf, - const ecs_iter_to_json_desc_t *desc) -{ - ecs_time_t duration = {0}; - if (desc && desc->measure_eval_duration) { - ecs_time_measure(&duration); - } +static +void ecs_meta_type_init_default_ctor(ecs_iter_t *it) { + ecs_world_t *world = it->world; - json_object_push(buf); + int i; + for (i = 0; i < it->count; i ++) { + ecs_entity_t type = it->entities[i]; - /* Serialize component ids of the terms (usually provided by query) */ - if (!desc || !desc->dont_serialize_term_ids) { - serialize_iter_ids(world, it, buf); + /* If component has no component actions (which is typical if a type is + * created with reflection data) make sure its values are always + * initialized with zero. This prevents the injection of invalid data + * through generic APIs after adding a component without setting it. */ + if (!ecs_component_has_actions(world, type)) { + ecs_set_component_actions_w_id(world, type, + &(EcsComponentLifecycle){ + .ctor = ecs_default_ctor + }); + } } +} - /* Serialize variable names, if iterator has any */ - serialize_iter_variables(it, buf); - - /* Serialize results */ - json_member(buf, "results"); - json_array_push(buf); - - /* Use instancing for improved performance */ - it->is_instanced = true; +static +void member_on_set( + ecs_world_t *world, + ecs_entity_t component, + const ecs_entity_t *entity_ptr, + void *ptr, + size_t size, + int32_t count, + void *ctx) +{ + (void)world; + (void)component; + (void)entity_ptr; + (void)size; + (void)count; + (void)ctx; - ecs_iter_next_action_t next = it->next; - while (next(it)) { - serialize_iter_result(world, it, buf, desc); + EcsMember *mbr = ptr; + if (!mbr->count) { + mbr->count = 1; } +} - json_array_pop(buf); +void FlecsMetaImport( + ecs_world_t *world) +{ + ECS_MODULE(world, FlecsMeta); - if (desc && desc->measure_eval_duration) { - double dt = ecs_time_measure(&duration); - json_member(buf, "eval_duration"); - json_number(buf, dt); - } + ecs_set_name_prefix(world, "Ecs"); - json_object_pop(buf); + flecs_bootstrap_component(world, EcsMetaType); + flecs_bootstrap_component(world, EcsMetaTypeSerialized); + flecs_bootstrap_component(world, EcsPrimitive); + flecs_bootstrap_component(world, EcsEnum); + flecs_bootstrap_component(world, EcsBitmask); + flecs_bootstrap_component(world, EcsMember); + flecs_bootstrap_component(world, EcsStruct); + flecs_bootstrap_component(world, EcsArray); + flecs_bootstrap_component(world, EcsVector); - return 0; -} + flecs_bootstrap_tag(world, EcsConstant); -char* ecs_iter_to_json( - const ecs_world_t *world, - ecs_iter_t *it, - const ecs_iter_to_json_desc_t *desc) -{ - ecs_strbuf_t buf = ECS_STRBUF_INIT; + ecs_set_component_actions(world, EcsMetaType, { .ctor = ecs_default_ctor }); - if (ecs_iter_to_json_buf(world, it, &buf, desc)) { - ecs_strbuf_reset(&buf); - return NULL; - } + ecs_set_component_actions(world, EcsMetaTypeSerialized, { + .ctor = ecs_default_ctor, + .move = ecs_move(EcsMetaTypeSerialized), + .copy = ecs_copy(EcsMetaTypeSerialized), + .dtor = ecs_dtor(EcsMetaTypeSerialized) + }); - return ecs_strbuf_get(&buf); -} + ecs_set_component_actions(world, EcsStruct, { + .ctor = ecs_default_ctor, + .move = ecs_move(EcsStruct), + .copy = ecs_copy(EcsStruct), + .dtor = ecs_dtor(EcsStruct) + }); -#endif + ecs_set_component_actions(world, EcsMember, { + .ctor = ecs_default_ctor, + .on_set = member_on_set + }); + ecs_set_component_actions(world, EcsEnum, { + .ctor = ecs_default_ctor, + .move = ecs_move(EcsEnum), + .copy = ecs_copy(EcsEnum), + .dtor = ecs_dtor(EcsEnum) + }); + ecs_set_component_actions(world, EcsBitmask, { + .ctor = ecs_default_ctor, + .move = ecs_move(EcsBitmask), + .copy = ecs_copy(EcsBitmask), + .dtor = ecs_dtor(EcsBitmask) + }); -#ifdef FLECS_JSON + /* Register triggers to finalize type information from component data */ + ecs_trigger_init(world, &(ecs_trigger_desc_t) { + .term.id = ecs_id(EcsPrimitive), + .term.subj.set.mask = EcsSelf, + .events = {EcsOnSet}, + .callback = set_primitive + }); -const char* ecs_parse_json( - const ecs_world_t *world, - const char *ptr, - ecs_entity_t type, - void *data_out, - const ecs_parse_json_desc_t *desc) -{ - char token[ECS_MAX_TOKEN_SIZE]; - int depth = 0; + ecs_trigger_init(world, &(ecs_trigger_desc_t) { + .term.id = ecs_id(EcsMember), + .term.subj.set.mask = EcsSelf, + .events = {EcsOnSet}, + .callback = set_member + }); - const char *name = NULL; - const char *expr = NULL; + ecs_trigger_init(world, &(ecs_trigger_desc_t) { + .term.id = ecs_id(EcsEnum), + .term.subj.set.mask = EcsSelf, + .events = {EcsOnAdd}, + .callback = add_enum + }); - ptr = ecs_parse_fluff(ptr, NULL); + ecs_trigger_init(world, &(ecs_trigger_desc_t) { + .term.id = ecs_id(EcsBitmask), + .term.subj.set.mask = EcsSelf, + .events = {EcsOnAdd}, + .callback = add_bitmask + }); - ecs_meta_cursor_t cur = ecs_meta_cursor(world, type, data_out); - if (cur.valid == false) { - return NULL; - } + ecs_trigger_init(world, &(ecs_trigger_desc_t) { + .term.id = EcsConstant, + .term.subj.set.mask = EcsSelf, + .events = {EcsOnAdd}, + .callback = add_constant + }); - if (desc) { - name = desc->name; - expr = desc->expr; - } + ecs_trigger_init(world, &(ecs_trigger_desc_t) { + .term.id = ecs_pair(EcsConstant, EcsWildcard), + .term.subj.set.mask = EcsSelf, + .events = {EcsOnSet}, + .callback = add_constant + }); - while ((ptr = ecs_parse_expr_token(name, expr, ptr, token))) { + ecs_trigger_init(world, &(ecs_trigger_desc_t) { + .term.id = ecs_id(EcsArray), + .term.subj.set.mask = EcsSelf, + .events = {EcsOnSet}, + .callback = set_array + }); - ptr = ecs_parse_fluff(ptr, NULL); + ecs_trigger_init(world, &(ecs_trigger_desc_t) { + .term.id = ecs_id(EcsVector), + .term.subj.set.mask = EcsSelf, + .events = {EcsOnSet}, + .callback = set_vector + }); - if (!ecs_os_strcmp(token, "{")) { - depth ++; - if (ecs_meta_push(&cur) != 0) { - goto error; - } + ecs_trigger_init(world, &(ecs_trigger_desc_t) { + .term.id = ecs_id(EcsMetaType), + .term.subj.set.mask = EcsSelf, + .events = {EcsOnSet}, + .callback = ecs_meta_type_serialized_init + }); - if (ecs_meta_is_collection(&cur)) { - ecs_parser_error(name, expr, ptr - expr, "expected '['"); - return NULL; - } - } + ecs_trigger_init(world, &(ecs_trigger_desc_t) { + .term.id = ecs_id(EcsMetaType), + .term.subj.set.mask = EcsSelf, + .events = {EcsOnSet}, + .callback = ecs_meta_type_init_default_ctor + }); - else if (!ecs_os_strcmp(token, "}")) { - depth --; + /* Initialize primitive types */ + #define ECS_PRIMITIVE(world, type, primitive_kind)\ + ecs_entity_init(world, &(ecs_entity_desc_t) {\ + .entity = ecs_id(ecs_##type##_t),\ + .name = #type });\ + ecs_set(world, ecs_id(ecs_##type##_t), EcsPrimitive, {\ + .kind = primitive_kind\ + }); - if (ecs_meta_is_collection(&cur)) { - ecs_parser_error(name, expr, ptr - expr, "expected ']'"); - return NULL; - } + ECS_PRIMITIVE(world, bool, EcsBool); + ECS_PRIMITIVE(world, char, EcsChar); + ECS_PRIMITIVE(world, byte, EcsByte); + ECS_PRIMITIVE(world, u8, EcsU8); + ECS_PRIMITIVE(world, u16, EcsU16); + ECS_PRIMITIVE(world, u32, EcsU32); + ECS_PRIMITIVE(world, u64, EcsU64); + ECS_PRIMITIVE(world, uptr, EcsUPtr); + ECS_PRIMITIVE(world, i8, EcsI8); + ECS_PRIMITIVE(world, i16, EcsI16); + ECS_PRIMITIVE(world, i32, EcsI32); + ECS_PRIMITIVE(world, i64, EcsI64); + ECS_PRIMITIVE(world, iptr, EcsIPtr); + ECS_PRIMITIVE(world, f32, EcsF32); + ECS_PRIMITIVE(world, f64, EcsF64); + ECS_PRIMITIVE(world, string, EcsString); + ECS_PRIMITIVE(world, entity, EcsEntity); - if (ecs_meta_pop(&cur) != 0) { - goto error; - } - } + #undef ECS_PRIMITIVE - else if (!ecs_os_strcmp(token, "[")) { - depth ++; - if (ecs_meta_push(&cur) != 0) { - goto error; - } + /* Set default child components */ + ecs_add_pair(world, ecs_id(EcsStruct), + EcsDefaultChildComponent, ecs_id(EcsMember)); - if (!ecs_meta_is_collection(&cur)) { - ecs_parser_error(name, expr, ptr - expr, "expected '{'"); - return NULL; - } - } + ecs_add_pair(world, ecs_id(EcsMember), + EcsDefaultChildComponent, ecs_id(EcsMember)); - else if (!ecs_os_strcmp(token, "]")) { - depth --; + ecs_add_pair(world, ecs_id(EcsEnum), + EcsDefaultChildComponent, EcsConstant); - if (!ecs_meta_is_collection(&cur)) { - ecs_parser_error(name, expr, ptr - expr, "expected '}'"); - return NULL; - } + ecs_add_pair(world, ecs_id(EcsBitmask), + EcsDefaultChildComponent, EcsConstant); - if (ecs_meta_pop(&cur) != 0) { - goto error; - } + /* Initialize reflection data for meta components */ + ecs_entity_t type_kind = ecs_enum_init(world, &(ecs_enum_desc_t) { + .entity.name = "TypeKind", + .constants = { + {.name = "PrimitiveType"}, + {.name = "BitmaskType"}, + {.name = "EnumType"}, + {.name = "StructType"}, + {.name = "ArrayType"}, + {.name = "VectorType"} } + }); - else if (!ecs_os_strcmp(token, ",")) { - if (ecs_meta_next(&cur) != 0) { - goto error; - } + ecs_struct_init(world, &(ecs_struct_desc_t) { + .entity.entity = ecs_id(EcsMetaType), + .members = { + {.name = (char*)"kind", .type = type_kind} } + }); - else if (!ecs_os_strcmp(token, "null")) { - if (ecs_meta_set_null(&cur) != 0) { - goto error; - } + ecs_entity_t primitive_kind = ecs_enum_init(world, &(ecs_enum_desc_t) { + .entity.name = "PrimitiveKind", + .constants = { + {.name = "Bool", 1}, + {.name = "Char"}, + {.name = "Byte"}, + {.name = "U8"}, + {.name = "U16"}, + {.name = "U32"}, + {.name = "U64"}, + {.name = "I8"}, + {.name = "I16"}, + {.name = "I32"}, + {.name = "I64"}, + {.name = "F32"}, + {.name = "F64"}, + {.name = "UPtr"}, + {.name = "IPtr"}, + {.name = "String"}, + {.name = "Entity"} } + }); - else if (token[0] == '\"') { - if (ptr[0] == ':') { - /* Member assignment */ - ptr ++; - - /* Strip trailing " */ - ecs_size_t len = ecs_os_strlen(token); - if (token[len - 1] != '"') { - ecs_parser_error(name, expr, ptr - expr, "expected \""); - return NULL; - } else { - token[len - 1] = '\0'; - } - - if (ecs_meta_member(&cur, token + 1) != 0) { - goto error; - } - } else { - if (ecs_meta_set_string_literal(&cur, token) != 0) { - goto error; - } - } + ecs_struct_init(world, &(ecs_struct_desc_t) { + .entity.entity = ecs_id(EcsPrimitive), + .members = { + {.name = (char*)"kind", .type = primitive_kind} } + }); - else { - if (ecs_meta_set_string(&cur, token) != 0) { - goto error; - } + ecs_struct_init(world, &(ecs_struct_desc_t) { + .entity.entity = ecs_id(EcsMember), + .members = { + {.name = (char*)"type", .type = ecs_id(ecs_entity_t)}, + {.name = (char*)"count", .type = ecs_id(ecs_i32_t)} } + }); - if (!depth) { - break; + ecs_struct_init(world, &(ecs_struct_desc_t) { + .entity.entity = ecs_id(EcsArray), + .members = { + {.name = (char*)"type", .type = ecs_id(ecs_entity_t)}, + {.name = (char*)"count", .type = ecs_id(ecs_i32_t)}, } - } + }); - return ptr; -error: - return NULL; + ecs_struct_init(world, &(ecs_struct_desc_t) { + .entity.entity = ecs_id(EcsVector), + .members = { + {.name = (char*)"type", .type = ecs_id(ecs_entity_t)} + } + }); } #endif +#ifdef FLECS_META -#ifdef FLECS_REST +ecs_entity_t ecs_enum_init( + ecs_world_t *world, + const ecs_enum_desc_t *desc) +{ + ecs_entity_t t = ecs_entity_init(world, &desc->entity); + if (!t) { + return 0; + } -typedef struct { - ecs_world_t *world; - ecs_entity_t entity; - ecs_http_server_t *srv; - int32_t rc; -} ecs_rest_ctx_t; + ecs_add(world, t, EcsEnum); -static ECS_COPY(EcsRest, dst, src, { - ecs_rest_ctx_t *impl = src->impl; - if (impl) { - impl->rc ++; - } + ecs_entity_t old_scope = ecs_set_scope(world, t); - ecs_os_strset(&dst->ipaddr, src->ipaddr); - dst->port = src->port; - dst->impl = impl; -}) + int i; + for (i = 0; i < ECS_MEMBER_DESC_CACHE_SIZE; i ++) { + const ecs_enum_constant_t *m_desc = &desc->constants[i]; + if (!m_desc->name) { + break; + } -static ECS_MOVE(EcsRest, dst, src, { - *dst = *src; - src->ipaddr = NULL; - src->impl = NULL; -}) + ecs_entity_t c = ecs_entity_init(world, &(ecs_entity_desc_t) { + .name = m_desc->name + }); -static ECS_DTOR(EcsRest, ptr, { - ecs_rest_ctx_t *impl = ptr->impl; - if (impl) { - impl->rc --; - if (!impl->rc) { - ecs_http_server_fini(impl->srv); - ecs_os_free(impl); + if (!m_desc->value) { + ecs_add_id(world, c, EcsConstant); + } else { + ecs_set_pair_object(world, c, EcsConstant, ecs_i32_t, + {m_desc->value}); } } - ecs_os_free(ptr->ipaddr); -}) - -static char *rest_last_err; -static -void rest_capture_log( - int32_t level, - const char *file, - int32_t line, - const char *msg) -{ - (void)file; (void)line; + ecs_set_scope(world, old_scope); - if (!rest_last_err && level < 0) { - rest_last_err = ecs_os_strdup(msg); + if (i == 0) { + ecs_err("enum '%s' has no constants", ecs_get_name(world, t)); + ecs_delete(world, t); + return 0; } -} -static -char* rest_get_captured_log(void) { - char *result = rest_last_err; - rest_last_err = NULL; - return result; + return t; } -static -void reply_error( - ecs_http_reply_t *reply, - const char *msg) +ecs_entity_t ecs_bitmask_init( + ecs_world_t *world, + const ecs_bitmask_desc_t *desc) { - ecs_strbuf_append(&reply->body, "{\"error\":\"%s\"}", msg); -} + ecs_entity_t t = ecs_entity_init(world, &desc->entity); + if (!t) { + return 0; + } -static -bool rest_reply( - const ecs_http_request_t* req, - ecs_http_reply_t *reply, - void *ctx) -{ - ecs_rest_ctx_t *impl = ctx; - ecs_world_t *world = impl->world; + ecs_add(world, t, EcsBitmask); - ecs_strbuf_appendstr(&reply->headers, "Access-Control-Allow-Origin: *\r\n"); + ecs_entity_t old_scope = ecs_set_scope(world, t); - if (req->method == EcsHttpGet) { - /* Entity endpoint */ - if (!ecs_os_strncmp(req->path, "entity/", 7)) { - char *path = &req->path[7]; - ecs_dbg("rest: request entity '%s'", path); + int i; + for (i = 0; i < ECS_MEMBER_DESC_CACHE_SIZE; i ++) { + const ecs_bitmask_constant_t *m_desc = &desc->constants[i]; + if (!m_desc->name) { + break; + } - ecs_entity_t e = ecs_lookup_path_w_sep( - world, 0, path, "/", NULL, false); - if (!e) { - e = ecs_lookup_path_w_sep( - world, EcsFlecsCore, path, "/", NULL, false); - if (!e) { - ecs_dbg("rest: requested entity '%s' does not exist", path); - reply_error(reply, "entity not found"); - return true; - } - } + ecs_entity_t c = ecs_entity_init(world, &(ecs_entity_desc_t) { + .name = m_desc->name + }); - ecs_entity_to_json_buf(world, e, &reply->body); - return true; - - /* Query endpoint */ - } else if (!ecs_os_strcmp(req->path, "query")) { - const char *q = ecs_http_get_param(req, "q"); - if (!q) { - ecs_strbuf_appendstr(&reply->body, "Missing parameter 'q'"); - reply->code = 400; /* bad request */ - return true; - } + if (!m_desc->value) { + ecs_add_id(world, c, EcsConstant); + } else { + ecs_set_pair_object(world, c, EcsConstant, ecs_u32_t, + {m_desc->value}); + } + } - ecs_dbg("rest: request query '%s'", q); - bool prev_color = ecs_log_enable_colors(false); - ecs_os_api_log_t prev_log_ = ecs_os_api.log_; - ecs_os_api.log_ = rest_capture_log; + ecs_set_scope(world, old_scope); - ecs_rule_t *r = ecs_rule_init(world, &(ecs_filter_desc_t) { - .expr = q - }); - if (!r) { - char *err = rest_get_captured_log(); - char *escaped_err = ecs_astresc('"', err); - reply_error(reply, escaped_err); - ecs_os_free(escaped_err); - ecs_os_free(err); - } else { - ecs_iter_t it = ecs_rule_iter(world, r); - ecs_iter_to_json_buf(world, &it, &reply->body, NULL); - ecs_rule_fini(r); - } + if (i == 0) { + ecs_err("bitmask '%s' has no constants", ecs_get_name(world, t)); + ecs_delete(world, t); + return 0; + } + + return t; +} + +ecs_entity_t ecs_array_init( + ecs_world_t *world, + const ecs_array_desc_t *desc) +{ + ecs_entity_t t = ecs_entity_init(world, &desc->entity); + if (!t) { + return 0; + } - ecs_os_api.log_ = prev_log_; - ecs_log_enable_colors(prev_color); + ecs_set(world, t, EcsArray, { + .type = desc->type, + .count = desc->count + }); - return true; - } + return t; +} + +ecs_entity_t ecs_vector_init( + ecs_world_t *world, + const ecs_vector_desc_t *desc) +{ + ecs_entity_t t = ecs_entity_init(world, &desc->entity); + if (!t) { + return 0; } - return false; + ecs_set(world, t, EcsVector, { + .type = desc->type + }); + + return t; } -static -void on_set_rest( +ecs_entity_t ecs_struct_init( ecs_world_t *world, - ecs_entity_t component, - const ecs_entity_t *entities, - void *ptr, - size_t size, - int32_t count, - void *ctx) + const ecs_struct_desc_t *desc) { - EcsRest *rest = ptr; + ecs_entity_t t = ecs_entity_init(world, &desc->entity); + if (!t) { + return 0; + } - (void)component; - (void)size; - (void)ctx; + ecs_entity_t old_scope = ecs_set_scope(world, t); int i; - for(i = 0; i < count; i ++) { - if (!rest[i].port) { - rest[i].port = ECS_REST_DEFAULT_PORT; + for (i = 0; i < ECS_MEMBER_DESC_CACHE_SIZE; i ++) { + const ecs_member_t *m_desc = &desc->members[i]; + if (!m_desc->type) { + break; } - ecs_rest_ctx_t *srv_ctx = ecs_os_malloc_t(ecs_rest_ctx_t); - ecs_http_server_t *srv = ecs_http_server_init(&(ecs_http_server_desc_t){ - .ipaddr = rest[i].ipaddr, - .port = rest[i].port, - .callback = rest_reply, - .ctx = srv_ctx - }); - - if (!srv) { - const char *ipaddr = rest[i].ipaddr ? rest[i].ipaddr : "0.0.0.0"; - ecs_err("failed to create REST server on %s:%u", - ipaddr, rest[i].port); - ecs_os_free(srv_ctx); - continue; + if (!m_desc->name) { + ecs_err("member %d of struct '%s' does not have a name", i, + ecs_get_name(world, t)); + ecs_delete(world, t); + return 0; } - srv_ctx->world = world; - srv_ctx->entity = entities[i]; - srv_ctx->srv = srv; - srv_ctx->rc = 1; - - rest[i].impl = srv_ctx; + ecs_entity_t m = ecs_entity_init(world, &(ecs_entity_desc_t) { + .name = m_desc->name + }); - ecs_http_server_start(srv_ctx->srv); + ecs_set(world, m, EcsMember, { + .type = m_desc->type, + .count = m_desc->count + }); } -} -static -void DequeueRest(ecs_iter_t *it) { - EcsRest *rest = ecs_term(it, EcsRest, 1); + ecs_set_scope(world, old_scope); - if (it->delta_system_time > (FLECS_FLOAT)1.0) { - ecs_warn( - "detected large progress interval (%.2fs), REST request may timeout", - (double)it->delta_system_time); + if (i == 0) { + ecs_err("struct '%s' has no members", ecs_get_name(world, t)); + ecs_delete(world, t); + return 0; } - int32_t i; - for(i = 0; i < it->count; i ++) { - ecs_rest_ctx_t *ctx = rest[i].impl; - if (ctx) { - ecs_http_server_dequeue(ctx->srv, it->delta_time); - } - } -} - -void FlecsRestImport( - ecs_world_t *world) -{ - ECS_MODULE(world, FlecsRest); - - ecs_set_name_prefix(world, "Ecs"); - - flecs_bootstrap_component(world, EcsRest); - - ecs_set_component_actions(world, EcsRest, { - .ctor = ecs_default_ctor, - .move = ecs_move(EcsRest), - .copy = ecs_copy(EcsRest), - .dtor = ecs_dtor(EcsRest), - .on_set = on_set_rest - }); - - ECS_SYSTEM(world, DequeueRest, EcsPostFrame, EcsRest); + return t; } #endif +#ifdef FLECS_MODULE -#ifdef FLECS_COREDOC +char* ecs_module_path_from_c( + const char *c_name) +{ + ecs_strbuf_t str = ECS_STRBUF_INIT; + const char *ptr; + char ch; -#define URL_ROOT "https://flecs.docsforge.com/master/relations-manual/" + for (ptr = c_name; (ch = *ptr); ptr++) { + if (isupper(ch)) { + ch = flecs_ito(char, tolower(ch)); + if (ptr != c_name) { + ecs_strbuf_appendstrn(&str, ".", 1); + } + } -void FlecsCoreDocImport( - ecs_world_t *world) -{ - ECS_MODULE(world, FlecsCoreDoc); + ecs_strbuf_appendstrn(&str, &ch, 1); + } - ECS_IMPORT(world, FlecsMeta); - ECS_IMPORT(world, FlecsDoc); + return ecs_strbuf_get(&str); +} - ecs_set_name_prefix(world, "Ecs"); +ecs_entity_t ecs_import( + ecs_world_t *world, + ecs_module_action_t init_action, + const char *module_name) +{ + ecs_check(!world->is_readonly, ECS_INVALID_WHILE_ITERATING, NULL); - /* Initialize reflection data for core components */ + ecs_entity_t old_scope = ecs_set_scope(world, 0); + const char *old_name_prefix = world->name_prefix; - ecs_struct_init(world, &(ecs_struct_desc_t) { - .entity.entity = ecs_id(EcsComponent), - .members = { - {.name = (char*)"size", .type = ecs_id(ecs_i32_t)}, - {.name = (char*)"alignment", .type = ecs_id(ecs_i32_t)} - } - }); + char *path = ecs_module_path_from_c(module_name); + ecs_entity_t e = ecs_lookup_fullpath(world, path); + ecs_os_free(path); + + if (!e) { + ecs_trace("#[magenta]import#[reset] %s", module_name); + ecs_log_push(); - ecs_struct_init(world, &(ecs_struct_desc_t) { - .entity.entity = ecs_id(EcsDocDescription), - .members = { - {.name = "value", .type = ecs_id(ecs_string_t)} - } - }); + /* Load module */ + init_action(world); - /* Initialize documentation data for core components */ - ecs_doc_set_brief(world, EcsFlecs, "Flecs root module"); - ecs_doc_set_link(world, EcsFlecs, "https://github.com/SanderMertens/flecs"); + /* Lookup module entity (must be registered by module) */ + e = ecs_lookup_fullpath(world, module_name); + ecs_check(e != 0, ECS_MODULE_UNDEFINED, module_name); - ecs_doc_set_brief(world, EcsFlecsCore, "Flecs module with builtin components"); + ecs_log_pop(); + } - ecs_doc_set_brief(world, EcsWorld, "Entity associated with world"); + /* Restore to previous state */ + ecs_set_scope(world, old_scope); + world->name_prefix = old_name_prefix; - ecs_doc_set_brief(world, ecs_id(EcsComponent), "Component that is added to all components"); - ecs_doc_set_brief(world, EcsModule, "Tag that is added to modules"); - ecs_doc_set_brief(world, EcsPrefab, "Tag that is added to prefabs"); - ecs_doc_set_brief(world, EcsDisabled, "Tag that is added to disabled entities"); + return e; +error: + return 0; +} - ecs_doc_set_brief(world, ecs_id(EcsIdentifier), "Component used for entity names"); - ecs_doc_set_brief(world, EcsName, "Tag used with EcsIdentifier to signal entity name"); - ecs_doc_set_brief(world, EcsSymbol, "Tag used with EcsIdentifier to signal entity symbol"); +ecs_entity_t ecs_import_from_library( + ecs_world_t *world, + const char *library_name, + const char *module_name) +{ + ecs_check(library_name != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_doc_set_brief(world, EcsTransitive, "Transitive relation property"); - ecs_doc_set_brief(world, EcsTransitiveSelf, "TransitiveSelf relation property"); - ecs_doc_set_brief(world, EcsFinal, "Final relation property"); - ecs_doc_set_brief(world, EcsTag, "Tag relation property"); - ecs_doc_set_brief(world, EcsOnDelete, "OnDelete relation cleanup property"); - ecs_doc_set_brief(world, EcsOnDeleteObject, "OnDeleteObject relation cleanup property"); - ecs_doc_set_brief(world, EcsDefaultChildComponent, "Sets default component hint for children of entity"); - ecs_doc_set_brief(world, EcsRemove, "Remove relation cleanup property"); - ecs_doc_set_brief(world, EcsDelete, "Delete relation cleanup property"); - ecs_doc_set_brief(world, EcsThrow, "Throw relation cleanup property"); - ecs_doc_set_brief(world, EcsIsA, "Builtin IsA relation"); - ecs_doc_set_brief(world, EcsChildOf, "Builtin ChildOf relation"); - ecs_doc_set_brief(world, EcsOnAdd, "Builtin OnAdd event"); - ecs_doc_set_brief(world, EcsOnRemove, "Builtin OnRemove event"); - ecs_doc_set_brief(world, EcsOnSet, "Builtin OnSet event"); - ecs_doc_set_brief(world, EcsUnSet, "Builtin UnSet event"); + char *import_func = (char*)module_name; /* safe */ + char *module = (char*)module_name; - ecs_doc_set_link(world, EcsTransitive, URL_ROOT "#transitive-relations"); - ecs_doc_set_link(world, EcsTransitiveSelf, URL_ROOT "#inclusive-relations"); - ecs_doc_set_link(world, EcsFinal, URL_ROOT "#final-entities"); - ecs_doc_set_link(world, EcsTag, URL_ROOT "#tag-relations"); - ecs_doc_set_link(world, EcsOnDelete, URL_ROOT "#relation-cleanup-properties"); - ecs_doc_set_link(world, EcsOnDeleteObject, URL_ROOT "#relation-cleanup-properties"); - ecs_doc_set_link(world, EcsRemove, URL_ROOT "#relation-cleanup-properties"); - ecs_doc_set_link(world, EcsDelete, URL_ROOT "#relation-cleanup-properties"); - ecs_doc_set_link(world, EcsThrow, URL_ROOT "#relation-cleanup-properties"); - ecs_doc_set_link(world, EcsIsA, URL_ROOT "#the-isa-relation"); - ecs_doc_set_link(world, EcsChildOf, URL_ROOT "#the-childof-relation"); - - /* Initialize documentation for meta components */ - ecs_entity_t meta = ecs_lookup_fullpath(world, "flecs.meta"); - ecs_doc_set_brief(world, meta, "Flecs module with reflection components"); + if (!ecs_os_has_modules() || !ecs_os_has_dl()) { + ecs_err( + "library loading not supported, set module_to_dl, dlopen, dlclose " + "and dlproc os API callbacks first"); + return 0; + } - ecs_doc_set_brief(world, ecs_id(EcsMetaType), "Component added to types"); - ecs_doc_set_brief(world, ecs_id(EcsMetaTypeSerialized), "Component that stores reflection data in an optimized format"); - ecs_doc_set_brief(world, ecs_id(EcsPrimitive), "Component added to primitive types"); - ecs_doc_set_brief(world, ecs_id(EcsEnum), "Component added to enumeration types"); - ecs_doc_set_brief(world, ecs_id(EcsBitmask), "Component added to bitmask types"); - ecs_doc_set_brief(world, ecs_id(EcsMember), "Component added to struct members"); - ecs_doc_set_brief(world, ecs_id(EcsStruct), "Component added to struct types"); - ecs_doc_set_brief(world, ecs_id(EcsArray), "Component added to array types"); - ecs_doc_set_brief(world, ecs_id(EcsVector), "Component added to vector types"); + /* If no module name is specified, try default naming convention for loading + * the main module from the library */ + if (!import_func) { + import_func = ecs_os_malloc(ecs_os_strlen(library_name) + ECS_SIZEOF("Import")); + ecs_assert(import_func != NULL, ECS_OUT_OF_MEMORY, NULL); + + const char *ptr; + char ch, *bptr = import_func; + bool capitalize = true; + for (ptr = library_name; (ch = *ptr); ptr ++) { + if (ch == '.') { + capitalize = true; + } else { + if (capitalize) { + *bptr = flecs_ito(char, toupper(ch)); + bptr ++; + capitalize = false; + } else { + *bptr = flecs_ito(char, tolower(ch)); + bptr ++; + } + } + } - ecs_doc_set_brief(world, ecs_id(ecs_bool_t), "bool component"); - ecs_doc_set_brief(world, ecs_id(ecs_char_t), "char component"); - ecs_doc_set_brief(world, ecs_id(ecs_byte_t), "byte component"); - ecs_doc_set_brief(world, ecs_id(ecs_u8_t), "8 bit unsigned int component"); - ecs_doc_set_brief(world, ecs_id(ecs_u16_t), "16 bit unsigned int component"); - ecs_doc_set_brief(world, ecs_id(ecs_u32_t), "32 bit unsigned int component"); - ecs_doc_set_brief(world, ecs_id(ecs_u64_t), "64 bit unsigned int component"); - ecs_doc_set_brief(world, ecs_id(ecs_uptr_t), "word sized unsigned int component"); - ecs_doc_set_brief(world, ecs_id(ecs_i8_t), "8 bit signed int component"); - ecs_doc_set_brief(world, ecs_id(ecs_i16_t), "16 bit signed int component"); - ecs_doc_set_brief(world, ecs_id(ecs_i32_t), "32 bit signed int component"); - ecs_doc_set_brief(world, ecs_id(ecs_i64_t), "64 bit signed int component"); - ecs_doc_set_brief(world, ecs_id(ecs_iptr_t), "word sized signed int component"); - ecs_doc_set_brief(world, ecs_id(ecs_f32_t), "32 bit floating point component"); - ecs_doc_set_brief(world, ecs_id(ecs_f64_t), "64 bit floating point component"); - ecs_doc_set_brief(world, ecs_id(ecs_string_t), "string component"); - ecs_doc_set_brief(world, ecs_id(ecs_entity_t), "entity component"); + *bptr = '\0'; - /* Initialize documentation for doc components */ - ecs_entity_t doc = ecs_lookup_fullpath(world, "flecs.doc"); - ecs_doc_set_brief(world, doc, "Flecs module with documentation components"); + module = ecs_os_strdup(import_func); + ecs_assert(module != NULL, ECS_OUT_OF_MEMORY, NULL); - ecs_doc_set_brief(world, ecs_id(EcsDocDescription), "Component used to add documentation"); - ecs_doc_set_brief(world, EcsDocBrief, "Used as (Description, Brief) to add a brief description"); - ecs_doc_set_brief(world, EcsDocDetail, "Used as (Description, Detail) to add a detailed description"); - ecs_doc_set_brief(world, EcsDocLink, "Used as (Description, Link) to add a link"); -} + ecs_os_strcat(bptr, "Import"); + } -#endif + char *library_filename = ecs_os_module_to_dl(library_name); + if (!library_filename) { + ecs_err("failed to find library file for '%s'", library_name); + if (module != module_name) { + ecs_os_free(module); + } + return 0; + } else { + ecs_trace("found file '%s' for library '%s'", + library_filename, library_name); + } -/* This is a heavily modified version of the EmbeddableWebServer (see copyright - * below). This version has been stripped from everything not strictly necessary - * for receiving/replying to simple HTTP requests, and has been modified to use - * the Flecs OS API. */ + ecs_os_dl_t dl = ecs_os_dlopen(library_filename); + if (!dl) { + ecs_err("failed to load library '%s' ('%s')", + library_name, library_filename); + + ecs_os_free(library_filename); -/* EmbeddableWebServer Copyright (c) 2016, 2019, 2020 Forrest Heller, and - * CONTRIBUTORS (see below) - All rights reserved. - * - * CONTRIBUTORS: - * Martin Pulec - bug fixes, warning fixes, IPv6 support - * Daniel Barry - bug fix (ifa_addr != NULL) - * - * Released under the BSD 2-clause license: - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. THIS SOFTWARE IS - * PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS - * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN - * NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY - * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF - * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ + if (module != module_name) { + ecs_os_free(module); + } + return 0; + } else { + ecs_trace("library '%s' ('%s') loaded", + library_name, library_filename); + } -#ifdef FLECS_HTTP + ecs_module_action_t action = (ecs_module_action_t) + ecs_os_dlproc(dl, import_func); + if (!action) { + ecs_err("failed to load import function %s from library %s", + import_func, library_name); + ecs_os_free(library_filename); + ecs_os_dlclose(dl); + return 0; + } else { + ecs_trace("found import function '%s' in library '%s' for module '%s'", + import_func, library_name, module); + } -#ifdef _MSC_VER -#pragma comment(lib, "Ws2_32.lib") -#include -#include -#include -typedef SOCKET ecs_http_socket_t; -#else -#include -#include -#include -#include -#include -#include -typedef int ecs_http_socket_t; -#endif + /* Do not free id, as it will be stored as the component identifier */ + ecs_entity_t result = ecs_import(world, action, module); -/* Max length of request method */ -#define ECS_HTTP_METHOD_LEN_MAX (8) + if (import_func != module_name) { + ecs_os_free(import_func); + } -/* Timeout (s) before connection purge */ -#define ECS_HTTP_CONNECTION_PURGE_TIMEOUT (1.0) + if (module != module_name) { + ecs_os_free(module); + } -/* Minimum interval between dequeueing requests (ms) */ -#define ECS_HTTP_MIN_DEQUEUE_INTERVAL (100) + ecs_os_free(library_filename); -/* Max length of headers in reply */ -#define ECS_HTTP_REPLY_HEADER_SIZE (1024) + return result; +error: + return 0; +} -/* Receive buffer size */ -#define ECS_HTTP_SEND_RECV_BUFFER_SIZE (16 * 1024) +ecs_entity_t ecs_module_init( + ecs_world_t *world, + const ecs_component_desc_t *desc) +{ + ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_poly_assert(world, ecs_world_t); -/* Max length of request (path + query + headers + body) */ -#define ECS_HTTP_REQUEST_LEN_MAX (10 * 1024 * 1024) + const char *name = desc->entity.name; -/* HTTP server struct */ -struct ecs_http_server_t { - bool should_run; - bool running; + char *module_path = ecs_module_path_from_c(name); + ecs_entity_t e = ecs_new_from_fullpath(world, module_path); + ecs_set_symbol(world, e, module_path); + ecs_os_free(module_path); - ecs_http_socket_t sock; - ecs_os_mutex_t lock; - ecs_os_thread_t thread; + ecs_component_desc_t private_desc = *desc; + private_desc.entity.entity = e; + private_desc.entity.name = NULL; - ecs_http_reply_action_t callback; - void *ctx; + if (desc->size) { + ecs_entity_t result = ecs_component_init(world, &private_desc); + ecs_assert(result != 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(result == e, ECS_INTERNAL_ERROR, NULL); + (void)result; + } else { + ecs_entity_t result = ecs_entity_init(world, &private_desc.entity); + ecs_assert(result != 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(result == e, ECS_INTERNAL_ERROR, NULL); + (void)result; + } - ecs_sparse_t *connections; /* sparse */ - ecs_sparse_t *requests; /* sparse */ + return e; +error: + return 0; +} - bool initialized; +#endif - uint16_t port; - const char *ipaddr; +#ifdef FLECS_META_C - FLECS_FLOAT delta_time; /* used to not lock request queue too often */ -}; +#define ECS_META_IDENTIFIER_LENGTH (256) -/** Fragment state, used by HTTP request parser */ -typedef enum { - HttpFragStateBegin, - HttpFragStateMethod, - HttpFragStatePath, - HttpFragStateVersion, - HttpFragStateHeaderStart, - HttpFragStateHeaderName, - HttpFragStateHeaderValueStart, - HttpFragStateHeaderValue, - HttpFragStateCR, - HttpFragStateCRLF, - HttpFragStateCRLFCR, - HttpFragStateBody, - HttpFragStateDone -} HttpFragState; +#define ecs_meta_error(ctx, ptr, ...)\ + ecs_parser_error((ctx)->name, (ctx)->desc, ptr - (ctx)->desc, __VA_ARGS__); -/** A fragment is a partially received HTTP request */ -typedef struct { - HttpFragState state; - ecs_strbuf_t buf; - ecs_http_method_t method; - int32_t body_offset; - int32_t query_offset; - int32_t header_offsets[ECS_HTTP_HEADER_COUNT_MAX]; - int32_t header_value_offsets[ECS_HTTP_HEADER_COUNT_MAX]; - int32_t header_count; - int32_t param_offsets[ECS_HTTP_QUERY_PARAM_COUNT_MAX]; - int32_t param_value_offsets[ECS_HTTP_QUERY_PARAM_COUNT_MAX]; - int32_t param_count; - char header_buf[32]; - char *header_buf_ptr; - int32_t content_length; - bool parse_content_length; - bool invalid; -} ecs_http_fragment_t; +typedef char ecs_meta_token_t[ECS_META_IDENTIFIER_LENGTH]; + +typedef struct meta_parse_ctx_t { + const char *name; + const char *desc; +} meta_parse_ctx_t; -/** Extend public connection type with fragment data */ -typedef struct { - ecs_http_connection_t pub; - ecs_http_fragment_t frag; - ecs_http_socket_t sock; - FLECS_FLOAT dequeue_timeout; /* used to purge inactive connections */ -} ecs_http_connection_impl_t; +typedef struct meta_type_t { + ecs_meta_token_t type; + ecs_meta_token_t params; + bool is_const; + bool is_ptr; +} meta_type_t; -typedef struct { - ecs_http_request_t pub; - void *res; -} ecs_http_request_impl_t; +typedef struct meta_member_t { + meta_type_t type; + ecs_meta_token_t name; + int64_t count; + bool is_partial; +} meta_member_t; -static -ecs_size_t http_send( - ecs_http_socket_t sock, - const void *buf, - ecs_size_t size, - int flags) -{ -#ifndef _MSC_VER - ssize_t send_bytes = send(sock, buf, flecs_itosize(size), flags); - return flecs_itoi32(send_bytes); -#else - int send_bytes = send(sock, buf, size, flags); - return flecs_itoi32(send_bytes); -#endif -} +typedef struct meta_constant_t { + ecs_meta_token_t name; + int64_t value; + bool is_value_set; +} meta_constant_t; + +typedef struct meta_params_t { + meta_type_t key_type; + meta_type_t type; + int64_t count; + bool is_key_value; + bool is_fixed_size; +} meta_params_t; static -ecs_size_t http_recv( - ecs_http_socket_t sock, - void *buf, - ecs_size_t size, - int flags) -{ - ecs_size_t ret; -#ifndef _MSC_VER - ssize_t recv_bytes = recv(sock, buf, flecs_itosize(size), flags); - ret = flecs_itoi32(recv_bytes); -#else - int recv_bytes = recv(sock, buf, size, flags); - ret = flecs_itoi32(recv_bytes); -#endif - if (ret == -1) { - ecs_dbg("recv failed: %s (sock = %d)", ecs_os_strerror(errno), sock); - } else if (ret == 0) { - ecs_dbg("recv: received 0 bytes (sock = %d)", sock); +const char* skip_scope(const char *ptr, meta_parse_ctx_t *ctx) { + /* Keep track of which characters were used to open the scope */ + char stack[256]; + int32_t sp = 0; + char ch; + + while ((ch = *ptr)) { + if (ch == '(' || ch == '<') { + stack[sp] = ch; + + sp ++; + if (sp >= 256) { + ecs_meta_error(ctx, ptr, "maximum level of nesting reached"); + goto error; + } + } else if (ch == ')' || ch == '>') { + sp --; + if ((sp < 0) || (ch == '>' && stack[sp] != '<') || + (ch == ')' && stack[sp] != '(')) + { + ecs_meta_error(ctx, ptr, "mismatching %c in identifier", ch); + goto error; + } + } + + ptr ++; + + if (!sp) { + break; + } } - return ret; + return ptr; +error: + return NULL; } static -int http_getnameinfo( - const struct sockaddr* addr, - ecs_size_t addr_len, - char *host, - ecs_size_t host_len, - char *port, - ecs_size_t port_len, - int flags) +const char* parse_c_digit( + const char *ptr, + int64_t *value_out) { - ecs_assert(addr_len > 0, ECS_INTERNAL_ERROR, NULL); - ecs_assert(host_len > 0, ECS_INTERNAL_ERROR, NULL); - ecs_assert(port_len > 0, ECS_INTERNAL_ERROR, NULL); - return getnameinfo(addr, (uint32_t)addr_len, host, (uint32_t)host_len, - port, (uint32_t)port_len, flags); -} + char token[24]; + ptr = ecs_parse_eol_and_whitespace(ptr); + ptr = ecs_parse_digit(ptr, token); + if (!ptr) { + goto error; + } -static -int http_bind( - ecs_http_socket_t sock, - const struct sockaddr* addr, - ecs_size_t addr_len) -{ - ecs_assert(addr_len > 0, ECS_INTERNAL_ERROR, NULL); - return bind(sock, addr, (uint32_t)addr_len); -} + *value_out = strtol(token, NULL, 0); -static -void http_close( - ecs_http_socket_t sock) -{ -#ifdef _MSC_VER - closesocket(sock); -#else - shutdown(sock, SHUT_RDWR); - close(sock); -#endif + return ecs_parse_eol_and_whitespace(ptr); +error: + return NULL; } static -ecs_http_socket_t http_accept( - ecs_http_socket_t sock, - struct sockaddr* addr, - ecs_size_t *addr_len) +const char* parse_c_identifier( + const char *ptr, + char *buff, + char *params, + meta_parse_ctx_t *ctx) { - socklen_t len = (socklen_t)addr_len[0]; - ecs_http_socket_t result = accept(sock, addr, &len); - addr_len[0] = (ecs_size_t)len; - return result; -} + ecs_assert(ptr != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(buff != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(ctx != NULL, ECS_INTERNAL_ERROR, NULL); -static -void reply_free(ecs_http_reply_t* response) { - ecs_os_free(response->body.content); -} + char *bptr = buff, ch; -static -void request_free(ecs_http_request_impl_t *req) { - ecs_os_free(req->res); - flecs_sparse_remove(req->pub.conn->server->requests, req->pub.id); -} + if (params) { + params[0] = '\0'; + } -static -void connection_free(ecs_http_connection_impl_t *conn) { - if (conn->sock) { - http_close(conn->sock); + /* Ignore whitespaces */ + ptr = ecs_parse_eol_and_whitespace(ptr); + + if (!isalpha(*ptr)) { + ecs_meta_error(ctx, ptr, + "invalid identifier (starts with '%c')", *ptr); + goto error; } - flecs_sparse_remove(conn->pub.server->connections, conn->pub.id); -} -// https://stackoverflow.com/questions/10156409/convert-hex-string-char-to-int -static -char hex_2_int(char a, char b){ - a = (a <= '9') ? (char)(a - '0') : (char)((a & 0x7) + 9); - b = (b <= '9') ? (char)(b - '0') : (char)((b & 0x7) + 9); - return (char)((a << 4) + b); -} + while ((ch = *ptr) && !isspace(ch) && ch != ';' && ch != ',' && ch != ')' && ch != '>') { + /* Type definitions can contain macro's or templates */ + if (ch == '(' || ch == '<') { + if (!params) { + ecs_meta_error(ctx, ptr, "unexpected %c", *ptr); + goto error; + } -static -void decode_url_str( - char *str) -{ - char ch, *ptr, *dst = str; - for (ptr = str; (ch = *ptr); ptr++) { - if (ch == '%') { - dst[0] = hex_2_int(ptr[1], ptr[2]); - dst ++; - ptr += 2; + const char *end = skip_scope(ptr, ctx); + ecs_os_strncpy(params, ptr, (ecs_size_t)(end - ptr)); + params[end - ptr] = '\0'; + + ptr = end; } else { - dst[0] = ptr[0]; - dst ++; + *bptr = ch; + bptr ++; + ptr ++; } } - dst[0] = '\0'; -} -static -void parse_method( - ecs_http_fragment_t *frag) -{ - char *method = ecs_strbuf_get_small(&frag->buf); - if (!ecs_os_strcmp(method, "GET")) frag->method = EcsHttpGet; - else if (!ecs_os_strcmp(method, "POST")) frag->method = EcsHttpPost; - else if (!ecs_os_strcmp(method, "PUT")) frag->method = EcsHttpPut; - else if (!ecs_os_strcmp(method, "DELETE")) frag->method = EcsHttpDelete; - else { - frag->method = EcsHttpMethodUnsupported; - frag->invalid = true; - } - ecs_strbuf_reset(&frag->buf); -} + *bptr = '\0'; -static -bool header_writable( - ecs_http_fragment_t *frag) -{ - return frag->header_count < ECS_HTTP_HEADER_COUNT_MAX; -} + if (!ch) { + ecs_meta_error(ctx, ptr, "unexpected end of token"); + goto error; + } -static -void header_buf_reset( - ecs_http_fragment_t *frag) -{ - frag->header_buf[0] = '\0'; - frag->header_buf_ptr = frag->header_buf; + return ptr; +error: + return NULL; } static -void header_buf_append( - ecs_http_fragment_t *frag, - char ch) +const char * meta_open_scope( + const char *ptr, + meta_parse_ctx_t *ctx) { - if ((frag->header_buf_ptr - frag->header_buf) < - ECS_SIZEOF(frag->header_buf)) - { - frag->header_buf_ptr[0] = ch; - frag->header_buf_ptr ++; - } else { - frag->header_buf_ptr[0] = '\0'; - } -} + /* Skip initial whitespaces */ + ptr = ecs_parse_eol_and_whitespace(ptr); -static -void enqueue_request( - ecs_http_connection_impl_t *conn) -{ - ecs_http_server_t *srv = conn->pub.server; - ecs_http_fragment_t *frag = &conn->frag; + /* Is this the start of the type definition? */ + if (ctx->desc == ptr) { + if (*ptr != '{') { + ecs_meta_error(ctx, ptr, "missing '{' in struct definition"); + goto error; + } - if (frag->invalid) { /* invalid request received, don't enqueue */ - ecs_strbuf_reset(&frag->buf); - } else { - char *res = ecs_strbuf_get(&frag->buf); - if (res) { - ecs_os_mutex_lock(srv->lock); - ecs_http_request_impl_t *req = flecs_sparse_add( - srv->requests, ecs_http_request_impl_t); - req->pub.id = flecs_sparse_last_id(srv->requests); - ecs_os_mutex_unlock(srv->lock); + ptr ++; + ptr = ecs_parse_eol_and_whitespace(ptr); + } - req->pub.conn = (ecs_http_connection_t*)conn; - req->pub.method = frag->method; - req->pub.path = res + 1; - if (frag->body_offset) { - req->pub.body = &res[frag->body_offset]; - } - int32_t i, count = frag->header_count; - for (i = 0; i < count; i ++) { - req->pub.headers[i].key = &res[frag->header_offsets[i]]; - req->pub.headers[i].value = &res[frag->header_value_offsets[i]]; - } - count = frag->param_count; - for (i = 0; i < count; i ++) { - req->pub.params[i].key = &res[frag->param_offsets[i]]; - req->pub.params[i].value = &res[frag->param_value_offsets[i]]; - decode_url_str((char*)req->pub.params[i].value); - } + /* Is this the end of the type definition? */ + if (!*ptr) { + ecs_meta_error(ctx, ptr, "missing '}' at end of struct definition"); + goto error; + } - req->pub.header_count = frag->header_count; - req->pub.param_count = frag->param_count; - req->res = res; + /* Is this the end of the type definition? */ + if (*ptr == '}') { + ptr = ecs_parse_eol_and_whitespace(ptr + 1); + if (*ptr) { + ecs_meta_error(ctx, ptr, + "stray characters after struct definition"); + goto error; } + return NULL; } + + return ptr; +error: + return NULL; } static -bool parse_request( - ecs_http_connection_impl_t *conn, - const char* req_frag, - ecs_size_t req_frag_len) -{ - ecs_http_fragment_t *frag = &conn->frag; +const char* meta_parse_constant( + const char *ptr, + meta_constant_t *token, + meta_parse_ctx_t *ctx) +{ + ptr = meta_open_scope(ptr, ctx); + if (!ptr) { + return NULL; + } - int32_t i; - for (i = 0; i < req_frag_len; i++) { - char c = req_frag[i]; - switch (frag->state) { - case HttpFragStateBegin: - ecs_os_memset_t(frag, 0, ecs_http_fragment_t); - frag->buf.max = ECS_HTTP_METHOD_LEN_MAX; - frag->state = HttpFragStateMethod; - frag->header_buf_ptr = frag->header_buf; - /* fallthrough */ - case HttpFragStateMethod: - if (c == ' ') { - parse_method(frag); - frag->state = HttpFragStatePath; - frag->buf.max = ECS_HTTP_REQUEST_LEN_MAX; - } else { - ecs_strbuf_appendch(&frag->buf, c); - } - break; - case HttpFragStatePath: - if (c == ' ') { - frag->state = HttpFragStateVersion; - ecs_strbuf_appendch(&frag->buf, '\0'); - } else { - if (c == '?' || c == '=' || c == '&') { - ecs_strbuf_appendch(&frag->buf, '\0'); - int32_t offset = ecs_strbuf_written(&frag->buf); - if (c == '?' || c == '&') { - frag->param_offsets[frag->param_count] = offset; - } else { - frag->param_value_offsets[frag->param_count] = offset; - frag->param_count ++; - } - } else { - ecs_strbuf_appendch(&frag->buf, c); - } - } - break; - case HttpFragStateVersion: - if (c == '\r') { - frag->state = HttpFragStateCR; - } /* version is not stored */ - break; - case HttpFragStateHeaderStart: - if (header_writable(frag)) { - frag->header_offsets[frag->header_count] = - ecs_strbuf_written(&frag->buf); - } - header_buf_reset(frag); - frag->state = HttpFragStateHeaderName; - /* fallthrough */ - case HttpFragStateHeaderName: - if (c == ':') { - frag->state = HttpFragStateHeaderValueStart; - header_buf_append(frag, '\0'); - frag->parse_content_length = !ecs_os_strcmp( - frag->header_buf, "Content-Length"); + token->is_value_set = false; - if (header_writable(frag)) { - ecs_strbuf_appendch(&frag->buf, '\0'); - frag->header_value_offsets[frag->header_count] = - ecs_strbuf_written(&frag->buf); - } - } else if (c == '\r') { - frag->state = HttpFragStateCR; - } else { - header_buf_append(frag, c); - if (header_writable(frag)) { - ecs_strbuf_appendch(&frag->buf, c); - } - } - break; - case HttpFragStateHeaderValueStart: - header_buf_reset(frag); - frag->state = HttpFragStateHeaderValue; - if (c == ' ') { /* skip first space */ - break; - } - /* fallthrough */ - case HttpFragStateHeaderValue: - if (c == '\r') { - if (frag->parse_content_length) { - header_buf_append(frag, '\0'); - int32_t len = atoi(frag->header_buf); - if (len < 0) { - frag->invalid = true; - } else { - frag->content_length = len; - } - frag->parse_content_length = false; - } - if (header_writable(frag)) { - int32_t cur = ecs_strbuf_written(&frag->buf); - if (frag->header_offsets[frag->header_count] < cur && - frag->header_value_offsets[frag->header_count] < cur) - { - ecs_strbuf_appendch(&frag->buf, '\0'); - frag->header_count ++; - } - } - frag->state = HttpFragStateCR; - } else { - if (frag->parse_content_length) { - header_buf_append(frag, c); - } - if (header_writable(frag)) { - ecs_strbuf_appendch(&frag->buf, c); - } - } - break; - case HttpFragStateCR: - if (c == '\n') { - frag->state = HttpFragStateCRLF; - } else { - frag->state = HttpFragStateHeaderStart; - } - break; - case HttpFragStateCRLF: - if (c == '\r') { - frag->state = HttpFragStateCRLFCR; - } else { - frag->state = HttpFragStateHeaderStart; - i--; - } - break; - case HttpFragStateCRLFCR: - if (c == '\n') { - if (frag->content_length != 0) { - frag->body_offset = ecs_strbuf_written(&frag->buf); - frag->state = HttpFragStateBody; - } else { - frag->state = HttpFragStateDone; - } - } else { - frag->state = HttpFragStateHeaderStart; - } - break; - case HttpFragStateBody: { - ecs_strbuf_appendch(&frag->buf, c); - if ((ecs_strbuf_written(&frag->buf) - frag->body_offset) == - frag->content_length) - { - frag->state = HttpFragStateDone; - } - } - break; - case HttpFragStateDone: - break; - } + /* Parse token, constant identifier */ + ptr = parse_c_identifier(ptr, token->name, NULL, ctx); + ptr = ecs_parse_eol_and_whitespace(ptr); + + /* Explicit value assignment */ + if (*ptr == '=') { + int64_t value = 0; + ptr = parse_c_digit(ptr + 1, &value); + token->value = value; + token->is_value_set = true; } - if (frag->state == HttpFragStateDone) { - frag->state = HttpFragStateBegin; - enqueue_request(conn); - return true; + /* Expect a ',' or '}' */ + if (*ptr != ',' && *ptr != '}') { + ecs_meta_error(ctx, ptr, "missing , after enum constant"); + goto error; + } + + if (*ptr == ',') { + return ptr + 1; } else { - return false; + return ptr; } +error: + return NULL; } static -void append_send_headers( - ecs_strbuf_t *hdrs, - int code, - const char* status, - const char* content_type, - ecs_strbuf_t *extra_headers, - ecs_size_t content_len) +const char* meta_parse_type( + const char *ptr, + meta_type_t *token, + meta_parse_ctx_t *ctx) { - ecs_strbuf_appendstr(hdrs, "HTTP/1.1 "); - ecs_strbuf_append(hdrs, "%d ", code); - ecs_strbuf_appendstr(hdrs, status); - ecs_strbuf_appendstr(hdrs, "\r\n"); + token->is_ptr = false; + token->is_const = false; - ecs_strbuf_appendstr(hdrs, "Content-Type: "); - ecs_strbuf_appendstr(hdrs, content_type); - ecs_strbuf_appendstr(hdrs, "\r\n"); + ptr = ecs_parse_eol_and_whitespace(ptr); - ecs_strbuf_appendstr(hdrs, "Content-Length: "); - ecs_strbuf_append(hdrs, "%d", content_len); - ecs_strbuf_appendstr(hdrs, "\r\n"); + /* Parse token, expect type identifier or ECS_PROPERTY */ + ptr = parse_c_identifier(ptr, token->type, token->params, ctx); + if (!ptr) { + goto error; + } - ecs_strbuf_appendstr(hdrs, "Server: flecs\r\n"); + if (!strcmp(token->type, "ECS_PRIVATE")) { + /* Members from this point are not stored in metadata */ + ptr += ecs_os_strlen(ptr); + goto done; + } - ecs_strbuf_mergebuff(hdrs, extra_headers); + /* If token is const, set const flag and continue parsing type */ + if (!strcmp(token->type, "const")) { + token->is_const = true; - ecs_strbuf_appendstr(hdrs, "\r\n"); + /* Parse type after const */ + ptr = parse_c_identifier(ptr + 1, token->type, token->params, ctx); + } + + /* Check if type is a pointer */ + ptr = ecs_parse_eol_and_whitespace(ptr); + if (*ptr == '*') { + token->is_ptr = true; + ptr ++; + } + +done: + return ptr; +error: + return NULL; } static -void send_reply( - ecs_http_connection_impl_t* conn, - ecs_http_reply_t* reply) +const char* meta_parse_member( + const char *ptr, + meta_member_t *token, + meta_parse_ctx_t *ctx) { - char hdrs[ECS_HTTP_REPLY_HEADER_SIZE]; - ecs_strbuf_t hdr_buf = ECS_STRBUF_INIT; - hdr_buf.buf = hdrs; - hdr_buf.max = ECS_HTTP_REPLY_HEADER_SIZE; - hdr_buf.buf = hdrs; - - char *content = ecs_strbuf_get(&reply->body); - int32_t content_length = reply->body.length - 1; - - /* First, send the response HTTP headers */ - append_send_headers(&hdr_buf, reply->code, reply->status, - reply->content_type, &reply->headers, content_length); + ptr = meta_open_scope(ptr, ctx); + if (!ptr) { + return NULL; + } - ecs_size_t hdrs_len = ecs_strbuf_written(&hdr_buf); - hdrs[hdrs_len] = '\0'; - ecs_size_t written = http_send(conn->sock, hdrs, hdrs_len, 0); + token->count = 1; + token->is_partial = false; - if (written != hdrs_len) { - ecs_err("failed to write HTTP response headers to '%s:%d': %s", - conn->pub.host, conn->pub.port, ecs_os_strerror(errno)); - return; + /* Parse member type */ + ptr = meta_parse_type(ptr, &token->type, ctx); + if (!ptr) { + token->is_partial = true; + goto error; } - /* Second, send response body */ - if (content_length > 0) { - written = http_send(conn->sock, content, content_length, 0); - if (written != content_length) { - ecs_err("failed to write HTTP response body to '%s:%d': %s", - conn->pub.host, conn->pub.port, ecs_os_strerror(errno)); - } + /* Next token is the identifier */ + ptr = parse_c_identifier(ptr, token->name, NULL, ctx); + if (!ptr) { + goto error; } -} -static -void recv_request( - ecs_http_connection_impl_t *conn) -{ - ecs_size_t bytes_read; - char recv_buf[ECS_HTTP_SEND_RECV_BUFFER_SIZE]; + /* Skip whitespace between member and [ or ; */ + ptr = ecs_parse_eol_and_whitespace(ptr); - while ((bytes_read = http_recv( - conn->sock, recv_buf, ECS_SIZEOF(recv_buf), 0)) > 0) - { - if (parse_request(conn, recv_buf, bytes_read)) { - return; + /* Check if this is an array */ + char *array_start = strchr(token->name, '['); + if (!array_start) { + /* If the [ was separated by a space, it will not be parsed as part of + * the name */ + if (*ptr == '[') { + array_start = (char*)ptr; /* safe, will not be modified */ } } -} -static -void init_connection( - ecs_http_server_t *srv, - ecs_http_socket_t sock_conn, - struct sockaddr_storage *remote_addr, - ecs_size_t remote_addr_len) -{ - /* Create new connection */ - ecs_os_mutex_lock(srv->lock); - ecs_http_connection_impl_t *conn = flecs_sparse_add( - srv->connections, ecs_http_connection_impl_t); - conn->pub.id = flecs_sparse_last_id(srv->connections); - ecs_os_mutex_unlock(srv->lock); + if (array_start) { + /* Check if the [ matches with a ] */ + char *array_end = strchr(array_start, ']'); + if (!array_end) { + ecs_meta_error(ctx, ptr, "missing ']'"); + goto error; - char *remote_host = conn->pub.host; - char *remote_port = conn->pub.port; + } else if (array_end - array_start == 0) { + ecs_meta_error(ctx, ptr, "dynamic size arrays are not supported"); + goto error; + } - /* Fetch name & port info */ - if (http_getnameinfo((struct sockaddr*) remote_addr, remote_addr_len, - remote_host, ECS_SIZEOF(conn->pub.host), - remote_port, ECS_SIZEOF(conn->pub.port), - NI_NUMERICHOST | NI_NUMERICSERV)) - { - ecs_os_strcpy(remote_host, "unknown"); - ecs_os_strcpy(remote_port, "unknown"); - } + token->count = atoi(array_start + 1); - ecs_dbg("http: connection established from '%s:%s'", - remote_host, remote_port); + if (array_start == ptr) { + /* If [ was found after name, continue parsing after ] */ + ptr = array_end + 1; + } else { + /* If [ was fonud in name, replace it with 0 terminator */ + array_start[0] = '\0'; + } + } - conn->pub.server = srv; - conn->sock = sock_conn; - recv_request(conn); + /* Expect a ; */ + if (*ptr != ';') { + ecs_meta_error(ctx, ptr, "missing ; after member declaration"); + goto error; + } - ecs_dbg("http: request received from '%s:%s'", - remote_host, remote_port); + return ptr + 1; +error: + return NULL; } static -int accept_connections( - ecs_http_server_t* srv, - const struct sockaddr* addr, - ecs_size_t addr_len) +int meta_parse_desc( + const char *ptr, + meta_params_t *token, + meta_parse_ctx_t *ctx) { -#ifdef _MSC_VER - /* If on Windows, test if winsock needs to be initialized */ - SOCKET testsocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); - if (SOCKET_ERROR == testsocket && WSANOTINITIALISED == WSAGetLastError()) { - WSADATA data = { 0 }; - int result = WSAStartup(MAKEWORD(2, 2), &data); - if (result) { - ecs_warn("WSAStartup failed with GetLastError = %d\n", - GetLastError()); - return -1; - } - } else { - http_close(testsocket); + token->is_key_value = false; + token->is_fixed_size = false; + + ptr = ecs_parse_eol_and_whitespace(ptr); + if (*ptr != '(' && *ptr != '<') { + ecs_meta_error(ctx, ptr, + "expected '(' at start of collection definition"); + goto error; } -#endif - /* Resolve name + port (used for logging) */ - char addr_host[256]; - char addr_port[20]; + ptr ++; - if (http_getnameinfo( - addr, addr_len, addr_host, ECS_SIZEOF(addr_host), addr_port, - ECS_SIZEOF(addr_port), NI_NUMERICHOST | NI_NUMERICSERV)) - { - ecs_os_strcpy(addr_host, "unknown"); - ecs_os_strcpy(addr_port, "unknown"); + /* Parse type identifier */ + ptr = meta_parse_type(ptr, &token->type, ctx); + if (!ptr) { + goto error; } - srv->sock = socket(addr->sa_family, SOCK_STREAM, IPPROTO_TCP); - if (srv->sock <= 0) { - ecs_err("unable to create new connection socket: %s", - ecs_os_strerror(errno)); - return -1; - } + ptr = ecs_parse_eol_and_whitespace(ptr); - int reuse = 1; - int result = setsockopt(srv->sock, SOL_SOCKET, SO_REUSEADDR, - (char*)&reuse, ECS_SIZEOF(reuse)); - if (result) { - ecs_warn("failed to setsockopt: %s", ecs_os_strerror(errno)); - } + /* If next token is a ',' the first type was a key type */ + if (*ptr == ',') { + ptr = ecs_parse_eol_and_whitespace(ptr + 1); + + if (isdigit(*ptr)) { + int64_t value; + ptr = parse_c_digit(ptr, &value); + if (!ptr) { + goto error; + } - if (addr->sa_family == AF_INET6) { - int ipv6only = 0; - if (setsockopt(srv->sock, IPPROTO_IPV6, IPV6_V6ONLY, - (char*)&ipv6only, ECS_SIZEOF(ipv6only))) - { - ecs_warn("failed to setsockopt: %s", ecs_os_strerror(errno)); + token->count = value; + token->is_fixed_size = true; + } else { + token->key_type = token->type; + + /* Parse element type */ + ptr = meta_parse_type(ptr, &token->type, ctx); + ptr = ecs_parse_eol_and_whitespace(ptr); + + token->is_key_value = true; } } - - result = http_bind(srv->sock, addr, addr_len); - if (result) { - ecs_err("http: failed to bind to '%s:%s': %s", - addr_host, addr_port, ecs_os_strerror(errno)); - return -1; - } - result = listen(srv->sock, SOMAXCONN); - if (result) { - ecs_warn("http: could not listen for SOMAXCONN (%d) connections: %s", - SOMAXCONN, ecs_os_strerror(errno)); + if (*ptr != ')' && *ptr != '>') { + ecs_meta_error(ctx, ptr, + "expected ')' at end of collection definition"); + goto error; } - ecs_trace("http: listening for incoming connections on '%s:%s'", - addr_host, addr_port); + return 0; +error: + return -1; +} - ecs_http_socket_t sock_conn; - struct sockaddr_storage remote_addr; - ecs_size_t remote_addr_len; +static +ecs_entity_t meta_lookup( + ecs_world_t *world, + meta_type_t *token, + const char *ptr, + int64_t count, + meta_parse_ctx_t *ctx); - while (srv->should_run) { - remote_addr_len = ECS_SIZEOF(remote_addr); - sock_conn = http_accept(srv->sock, (struct sockaddr*) &remote_addr, - &remote_addr_len); +static +ecs_entity_t meta_lookup_array( + ecs_world_t *world, + ecs_entity_t e, + const char *params_decl, + meta_parse_ctx_t *ctx) +{ + meta_parse_ctx_t param_ctx = { + .name = ctx->name, + .desc = params_decl + }; - if (sock_conn == -1) { - ecs_dbg("http: connection attempt failed: %s", - ecs_os_strerror(errno)); - continue; - } + meta_params_t params; + if (meta_parse_desc(params_decl, ¶ms, ¶m_ctx)) { + goto error; + } + if (!params.is_fixed_size) { + ecs_meta_error(ctx, params_decl, "missing size for array"); + goto error; + } - init_connection(srv, sock_conn, &remote_addr, remote_addr_len); + if (!params.count) { + ecs_meta_error(ctx, params_decl, "invalid array size"); + goto error; } - if (srv->sock && errno != EBADF) { - http_close(srv->sock); + ecs_entity_t element_type = ecs_lookup_symbol(world, params.type.type, true); + if (!element_type) { + ecs_meta_error(ctx, params_decl, "unknown element type '%s'", + params.type.type); + } + + if (!e) { + e = ecs_set(world, 0, EcsMetaType, { EcsArrayType }); } + ecs_check(params.count <= INT32_MAX, ECS_INVALID_PARAMETER, NULL); + + return ecs_set(world, e, EcsArray, { element_type, (int32_t)params.count }); +error: return 0; } static -void* http_server_thread(void* arg) { - ecs_http_server_t *srv = arg; - struct sockaddr_in addr = { - .sin_family = AF_INET, - .sin_port = htons(srv->port) }; +ecs_entity_t meta_lookup_vector( + ecs_world_t *world, + ecs_entity_t e, + const char *params_decl, + meta_parse_ctx_t *ctx) +{ + meta_parse_ctx_t param_ctx = { + .name = ctx->name, + .desc = params_decl + }; - if (!srv->ipaddr) { - addr.sin_addr.s_addr = htonl(INADDR_ANY); - } else { - inet_pton(AF_INET, srv->ipaddr, &(addr.sin_addr)); + meta_params_t params; + if (meta_parse_desc(params_decl, ¶ms, ¶m_ctx)) { + goto error; } - accept_connections(srv, (struct sockaddr*)&addr, ECS_SIZEOF(addr)); - return NULL; -} + if (params.is_key_value) { + ecs_meta_error(ctx, params_decl, + "unexpected key value parameters for vector"); + goto error; + } -static -void handle_request( - ecs_http_server_t *srv, - ecs_http_request_impl_t *req) -{ - ecs_http_reply_t reply = ECS_HTTP_REPLY_INIT; - ecs_http_connection_impl_t *conn = - (ecs_http_connection_impl_t*)req->pub.conn; + ecs_entity_t element_type = meta_lookup( + world, ¶ms.type, params_decl, 1, ¶m_ctx); - if (srv->callback((ecs_http_request_t*)req, &reply, srv->ctx) == 0) { - reply.code = 404; - reply.status = "Resource not found"; + if (!e) { + e = ecs_set(world, 0, EcsMetaType, {EcsVectorType}); } - send_reply(conn, &reply); - ecs_dbg("http: reply sent to '%s:%s'", conn->pub.host, conn->pub.port); - - reply_free(&reply); - request_free(req); - connection_free(conn); + return ecs_set(world, e, EcsVector, { element_type }); +error: + return 0; } static -void dequeue_requests( - ecs_http_server_t *srv, - float delta_time) +ecs_entity_t meta_lookup_bitmask( + ecs_world_t *world, + ecs_entity_t e, + const char *params_decl, + meta_parse_ctx_t *ctx) { - ecs_os_mutex_lock(srv->lock); + (void)e; - int32_t i, count = flecs_sparse_count(srv->requests); - for (i = count - 1; i >= 0; i --) { - ecs_http_request_impl_t *req = flecs_sparse_get_dense( - srv->requests, ecs_http_request_impl_t, i); - handle_request(srv, req); + meta_parse_ctx_t param_ctx = { + .name = ctx->name, + .desc = params_decl + }; + + meta_params_t params; + if (meta_parse_desc(params_decl, ¶ms, ¶m_ctx)) { + goto error; } - count = flecs_sparse_count(srv->connections); - for (i = count - 1; i >= 0; i --) { - ecs_http_connection_impl_t *conn = flecs_sparse_get_dense( - srv->connections, ecs_http_connection_impl_t, i); - conn->dequeue_timeout += delta_time; - if (conn->dequeue_timeout > - (FLECS_FLOAT)ECS_HTTP_CONNECTION_PURGE_TIMEOUT) - { - ecs_dbg("http: purging connection '%s:%s' (sock = %d)", - conn->pub.host, conn->pub.port, conn->sock); - connection_free(conn); - } + if (params.is_key_value) { + ecs_meta_error(ctx, params_decl, + "unexpected key value parameters for bitmask"); + goto error; } - ecs_os_mutex_unlock(srv->lock); + if (params.is_fixed_size) { + ecs_meta_error(ctx, params_decl, + "unexpected size for bitmask"); + goto error; + } + + ecs_entity_t bitmask_type = meta_lookup( + world, ¶ms.type, params_decl, 1, ¶m_ctx); + ecs_check(bitmask_type != 0, ECS_INVALID_PARAMETER, NULL); + +#ifndef NDEBUG + /* Make sure this is a bitmask type */ + const EcsMetaType *type_ptr = ecs_get(world, bitmask_type, EcsMetaType); + ecs_check(type_ptr != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(type_ptr->kind == EcsBitmaskType, ECS_INVALID_PARAMETER, NULL); +#endif + + return bitmask_type; +error: + return 0; } -const char* ecs_http_get_header( - const ecs_http_request_t* req, - const char* name) +static +ecs_entity_t meta_lookup( + ecs_world_t *world, + meta_type_t *token, + const char *ptr, + int64_t count, + meta_parse_ctx_t *ctx) { - for (ecs_size_t i = 0; i < req->header_count; i++) { - if (!ecs_os_strcmp(req->headers[i].key, name)) { - return req->headers[i].value; - } - } - return NULL; -} + ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(token != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(ptr != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(ctx != NULL, ECS_INTERNAL_ERROR, NULL); + + const char *typename = token->type; + ecs_entity_t type = 0; + + /* Parse vector type */ + if (!token->is_ptr) { + if (!ecs_os_strcmp(typename, "ecs_array")) { + type = meta_lookup_array(world, 0, token->params, ctx); + + } else if (!ecs_os_strcmp(typename, "ecs_vector") || + !ecs_os_strcmp(typename, "flecs::vector")) + { + type = meta_lookup_vector(world, 0, token->params, ctx); -const char* ecs_http_get_param( - const ecs_http_request_t* req, - const char* name) -{ - for (ecs_size_t i = 0; i < req->param_count; i++) { - if (!ecs_os_strcmp(req->params[i].key, name)) { - return req->params[i].value; - } - } - return NULL; -} + } else if (!ecs_os_strcmp(typename, "flecs::bitmask")) { + type = meta_lookup_bitmask(world, 0, token->params, ctx); -ecs_http_server_t* ecs_http_server_init( - const ecs_http_server_desc_t *desc) -{ - ecs_check(ecs_os_has_threading(), ECS_UNSUPPORTED, - "missing OS API implementation"); + } else if (!ecs_os_strcmp(typename, "flecs::byte")) { + type = ecs_id(ecs_byte_t); - ecs_http_server_t* srv = ecs_os_calloc_t(ecs_http_server_t); - srv->lock = ecs_os_mutex_new(); + } else if (!ecs_os_strcmp(typename, "char")) { + type = ecs_id(ecs_char_t); - srv->should_run = false; - srv->initialized = true; + } else if (!ecs_os_strcmp(typename, "bool") || + !ecs_os_strcmp(typename, "_Bool")) + { + type = ecs_id(ecs_bool_t); - srv->callback = desc->callback; - srv->ctx = desc->ctx; - srv->port = desc->port; - srv->ipaddr = desc->ipaddr; + } else if (!ecs_os_strcmp(typename, "int8_t")) { + type = ecs_id(ecs_i8_t); + } else if (!ecs_os_strcmp(typename, "int16_t")) { + type = ecs_id(ecs_i16_t); + } else if (!ecs_os_strcmp(typename, "int32_t")) { + type = ecs_id(ecs_i32_t); + } else if (!ecs_os_strcmp(typename, "int64_t")) { + type = ecs_id(ecs_i64_t); - srv->connections = flecs_sparse_new(ecs_http_connection_impl_t); - srv->requests = flecs_sparse_new(ecs_http_request_impl_t); + } else if (!ecs_os_strcmp(typename, "uint8_t")) { + type = ecs_id(ecs_u8_t); + } else if (!ecs_os_strcmp(typename, "uint16_t")) { + type = ecs_id(ecs_u16_t); + } else if (!ecs_os_strcmp(typename, "uint32_t")) { + type = ecs_id(ecs_u32_t); + } else if (!ecs_os_strcmp(typename, "uint64_t")) { + type = ecs_id(ecs_u64_t); -#ifndef _MSC_VER - /* Ignore pipe signal. SIGPIPE can occur when a message is sent to a client - * but te client already disconnected. */ - signal(SIGPIPE, SIG_IGN); -#endif + } else if (!ecs_os_strcmp(typename, "float")) { + type = ecs_id(ecs_f32_t); + } else if (!ecs_os_strcmp(typename, "double")) { + type = ecs_id(ecs_f64_t); - return srv; -error: - return NULL; -} + } else if (!ecs_os_strcmp(typename, "ecs_entity_t")) { + type = ecs_id(ecs_entity_t); -void ecs_http_server_fini( - ecs_http_server_t* srv) -{ - ecs_http_server_stop(srv); - ecs_os_mutex_free(srv->lock); - flecs_sparse_free(srv->connections); - flecs_sparse_free(srv->requests); - ecs_os_free(srv); -} + } else if (!ecs_os_strcmp(typename, "char*")) { + type = ecs_id(ecs_string_t); + } else { + type = ecs_lookup_symbol(world, typename, true); + } + } else { + if (!ecs_os_strcmp(typename, "char")) { + typename = "flecs.meta.string"; + } else + if (token->is_ptr) { + typename = "flecs.meta.uptr"; + } else + if (!ecs_os_strcmp(typename, "char*") || + !ecs_os_strcmp(typename, "flecs::string")) + { + typename = "flecs.meta.string"; + } -int ecs_http_server_start( - ecs_http_server_t *srv) -{ - ecs_check(srv != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(srv->initialized, ECS_INVALID_PARAMETER, NULL); - ecs_check(!srv->should_run, ECS_INVALID_PARAMETER, NULL); - ecs_check(!srv->thread, ECS_INVALID_PARAMETER, NULL); + type = ecs_lookup_symbol(world, typename, true); + } - srv->should_run = true; + if (count != 1) { + ecs_check(count <= INT32_MAX, ECS_INVALID_PARAMETER, NULL); - srv->thread = ecs_os_thread_new(http_server_thread, srv); - if (!srv->thread) { + type = ecs_set(world, ecs_set(world, 0, + EcsMetaType, {EcsArrayType}), + EcsArray, {type, (int32_t)count}); + } + + if (!type) { + ecs_meta_error(ctx, ptr, "unknown type '%s'", typename); goto error; } - return 0; + return type; error: - return -1; + return 0; } -void ecs_http_server_stop( - ecs_http_server_t* srv) +static +int meta_parse_struct( + ecs_world_t *world, + ecs_entity_t t, + const char *desc) { - ecs_check(srv != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(srv->initialized, ECS_INVALID_OPERATION, NULL); - ecs_check(srv->should_run, ECS_INVALID_PARAMETER, NULL); + const char *ptr = desc; + const char *name = ecs_get_name(world, t); - /* Stop server thread */ - ecs_trace("http: shutting down server thread"); - srv->should_run = false; - if (srv->sock >= 0) { - http_close(srv->sock); - } + meta_member_t token; + meta_parse_ctx_t ctx = { + .name = name, + .desc = ptr + }; - ecs_os_thread_join(srv->thread); + ecs_entity_t old_scope = ecs_set_scope(world, t); - /* Close all connections */ - int i, count = flecs_sparse_count(srv->connections); - for (i = count - 1; i >= 0; i --) { - connection_free(flecs_sparse_get_dense( - srv->connections, ecs_http_connection_impl_t, i)); - } + while ((ptr = meta_parse_member(ptr, &token, &ctx)) && ptr[0]) { + ecs_entity_t m = ecs_entity_init(world, &(ecs_entity_desc_t) { + .name = token.name + }); - /* Cleanup all outstanding requests */ - count = flecs_sparse_count(srv->requests); - for (i = count - 1; i >= 0; i --) { - request_free(flecs_sparse_get_dense( - srv->requests, ecs_http_request_impl_t, i)); + ecs_entity_t type = meta_lookup( + world, &token.type, ptr, 1, &ctx); + if (!type) { + goto error; + } + + ecs_set(world, m, EcsMember, { + .type = type, + .count = (ecs_size_t)token.count + }); } - ecs_assert(flecs_sparse_count(srv->connections) == 0, - ECS_INTERNAL_ERROR, NULL); - ecs_assert(flecs_sparse_count(srv->requests) == 0, - ECS_INTERNAL_ERROR, NULL); + ecs_set_scope(world, old_scope); - srv->thread = 0; + return 0; error: - return; + return -1; } -void ecs_http_server_dequeue( - ecs_http_server_t* srv, - float delta_time) +static +int meta_parse_constants( + ecs_world_t *world, + ecs_entity_t t, + const char *desc, + bool is_bitmask) { - ecs_check(srv != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(srv->initialized, ECS_INVALID_PARAMETER, NULL); - ecs_check(srv->should_run, ECS_INVALID_PARAMETER, NULL); - - srv->delta_time += delta_time; - if ((1000 * srv->delta_time) > (FLECS_FLOAT)ECS_HTTP_MIN_DEQUEUE_INTERVAL) { - dequeue_requests(srv, srv->delta_time); - srv->delta_time = 0; - } + ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(t != 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(desc != NULL, ECS_INTERNAL_ERROR, NULL); -error: - return; -} + const char *ptr = desc; + const char *name = ecs_get_name(world, t); -#endif + meta_parse_ctx_t ctx = { + .name = name, + .desc = ptr + }; + meta_constant_t token; + int64_t last_value = 0; + ecs_entity_t old_scope = ecs_set_scope(world, t); -#ifdef FLECS_DOC + while ((ptr = meta_parse_constant(ptr, &token, &ctx))) { + if (token.is_value_set) { + last_value = token.value; + } else if (is_bitmask) { + ecs_meta_error(&ctx, ptr, + "bitmask requires explicit value assignment"); + goto error; + } -static ECS_COPY(EcsDocDescription, dst, src, { - ecs_os_strset((char**)&dst->value, src->value); + ecs_entity_t c = ecs_entity_init(world, &(ecs_entity_desc_t) { + .name = token.name + }); -}) + if (!is_bitmask) { + ecs_set_pair_object(world, c, EcsConstant, ecs_i32_t, + {(ecs_i32_t)last_value}); + } else { + ecs_set_pair_object(world, c, EcsConstant, ecs_u32_t, + {(ecs_u32_t)last_value}); + } -static ECS_MOVE(EcsDocDescription, dst, src, { - ecs_os_free((char*)dst->value); - dst->value = src->value; - src->value = NULL; -}) + last_value ++; + } -static ECS_DTOR(EcsDocDescription, ptr, { - ecs_os_free((char*)ptr->value); -}) + ecs_set_scope(world, old_scope); -void ecs_doc_set_brief( - ecs_world_t *world, - ecs_entity_t entity, - const char *description) -{ - ecs_set_pair(world, entity, EcsDocDescription, EcsDocBrief, { - .value = description - }); + return 0; +error: + return -1; } -void ecs_doc_set_detail( +static +int meta_parse_enum( ecs_world_t *world, - ecs_entity_t entity, - const char *description) + ecs_entity_t t, + const char *desc) { - ecs_set_pair(world, entity, EcsDocDescription, EcsDocDetail, { - .value = description - }); + ecs_add(world, t, EcsEnum); + return meta_parse_constants(world, t, desc, false); } -void ecs_doc_set_link( +static +int meta_parse_bitmask( ecs_world_t *world, - ecs_entity_t entity, - const char *link) -{ - ecs_set_pair(world, entity, EcsDocDescription, EcsDocLink, { - .value = link - }); -} - -const char* ecs_doc_get_brief( - const ecs_world_t *world, - ecs_entity_t entity) -{ - EcsDocDescription *ptr = ecs_get_pair( - world, entity, EcsDocDescription, EcsDocBrief); - if (ptr) { - return ptr->value; - } else { - return NULL; - } -} - -const char* ecs_doc_get_detail( - const ecs_world_t *world, - ecs_entity_t entity) + ecs_entity_t t, + const char *desc) { - EcsDocDescription *ptr = ecs_get_pair( - world, entity, EcsDocDescription, EcsDocDetail); - if (ptr) { - return ptr->value; - } else { - return NULL; - } + ecs_add(world, t, EcsBitmask); + return meta_parse_constants(world, t, desc, true); } -const char* ecs_doc_get_link( - const ecs_world_t *world, - ecs_entity_t entity) +int ecs_meta_from_desc( + ecs_world_t *world, + ecs_entity_t component, + ecs_type_kind_t kind, + const char *desc) { - EcsDocDescription *ptr = ecs_get_pair( - world, entity, EcsDocDescription, EcsDocLink); - if (ptr) { - return ptr->value; - } else { - return NULL; + switch(kind) { + case EcsStructType: + if (meta_parse_struct(world, component, desc)) { + goto error; + } + break; + case EcsEnumType: + if (meta_parse_enum(world, component, desc)) { + goto error; + } + break; + case EcsBitmaskType: + if (meta_parse_bitmask(world, component, desc)) { + goto error; + } + break; + default: + break; } -} - -void FlecsDocImport( - ecs_world_t *world) -{ - ECS_MODULE(world, FlecsDoc); - - ecs_set_name_prefix(world, "EcsDoc"); - - flecs_bootstrap_component(world, EcsDocDescription); - flecs_bootstrap_tag(world, EcsDocBrief); - flecs_bootstrap_tag(world, EcsDocDetail); - flecs_bootstrap_tag(world, EcsDocLink); - ecs_set_component_actions(world, EcsDocDescription, { - .ctor = ecs_default_ctor, - .move = ecs_move(EcsDocDescription), - .copy = ecs_copy(EcsDocDescription), - .dtor = ecs_dtor(EcsDocDescription) - }); + return 0; +error: + return -1; } #endif +#ifdef FLECS_LOG -#ifdef FLECS_PARSER - - -#define ECS_ANNOTATION_LENGTH_MAX (16) - -#define TOK_NEWLINE '\n' -#define TOK_COLON ':' -#define TOK_AND ',' -#define TOK_OR "||" -#define TOK_NOT '!' -#define TOK_OPTIONAL '?' -#define TOK_BITWISE_OR '|' -#define TOK_NAME_SEP '.' -#define TOK_BRACKET_OPEN '[' -#define TOK_BRACKET_CLOSE ']' -#define TOK_WILDCARD '*' -#define TOK_SINGLETON '$' -#define TOK_PAREN_OPEN '(' -#define TOK_PAREN_CLOSE ')' -#define TOK_AS_ENTITY '\\' - -#define TOK_SELF "self" -#define TOK_SUPERSET "super" -#define TOK_SUBSET "sub" -#define TOK_CASCADE "cascade" -#define TOK_PARENT "parent" -#define TOK_ALL "all" - -#define TOK_OWNED "OVERRIDE" - -#define TOK_ROLE_PAIR "PAIR" -#define TOK_ROLE_AND "AND" -#define TOK_ROLE_OR "OR" -#define TOK_ROLE_XOR "XOR" -#define TOK_ROLE_NOT "NOT" -#define TOK_ROLE_SWITCH "SWITCH" -#define TOK_ROLE_CASE "CASE" -#define TOK_ROLE_DISABLED "DISABLED" +static +char *ecs_vasprintf( + const char *fmt, + va_list args) +{ + ecs_size_t size = 0; + char *result = NULL; + va_list tmpa; -#define TOK_IN "in" -#define TOK_OUT "out" -#define TOK_INOUT "inout" -#define TOK_INOUT_FILTER "filter" + va_copy(tmpa, args); -#define ECS_MAX_TOKEN_SIZE (256) + size = vsnprintf(result, 0, fmt, tmpa); -typedef char ecs_token_t[ECS_MAX_TOKEN_SIZE]; + va_end(tmpa); -const char* ecs_parse_eol_and_whitespace( - const char *ptr) -{ - while (isspace(*ptr)) { - ptr ++; + if ((int32_t)size < 0) { + return NULL; } - return ptr; -} + result = (char *) ecs_os_malloc(size + 1); -/** Skip spaces when parsing signature */ -const char* ecs_parse_whitespace( - const char *ptr) -{ - while ((*ptr != '\n') && isspace(*ptr)) { - ptr ++; + if (!result) { + return NULL; } - return ptr; + ecs_os_vsprintf(result, fmt, args); + + return result; } -const char* ecs_parse_digit( - const char *ptr, - char *token) +static +void ecs_colorize_buf( + char *msg, + bool enable_colors, + ecs_strbuf_t *buf) { - char *tptr = token; - char ch = ptr[0]; - - if (!isdigit(ch) && ch != '-') { - ecs_parser_error(NULL, NULL, 0, "invalid start of number '%s'", ptr); - return NULL; - } + char *ptr, ch, prev = '\0'; + bool isNum = false; + char isStr = '\0'; + bool isVar = false; + bool overrideColor = false; + bool autoColor = true; + bool dontAppend = false; - tptr[0] = ch; - tptr ++; - ptr ++; + for (ptr = msg; (ch = *ptr); ptr++) { + dontAppend = false; - for (; (ch = *ptr); ptr ++) { - if (!isdigit(ch)) { - break; - } + if (!overrideColor) { + if (isNum && !isdigit(ch) && !isalpha(ch) && (ch != '.') && (ch != '%')) { + if (enable_colors) ecs_strbuf_appendstr(buf, ECS_NORMAL); + isNum = false; + } + if (isStr && (isStr == ch) && prev != '\\') { + isStr = '\0'; + } else if (((ch == '\'') || (ch == '"')) && !isStr && + !isalpha(prev) && (prev != '\\')) + { + if (enable_colors) ecs_strbuf_appendstr(buf, ECS_CYAN); + isStr = ch; + } - tptr[0] = ch; - tptr ++; - } + if ((isdigit(ch) || (ch == '%' && isdigit(prev)) || + (ch == '-' && isdigit(ptr[1]))) && !isNum && !isStr && !isVar && + !isalpha(prev) && !isdigit(prev) && (prev != '_') && + (prev != '.')) + { + if (enable_colors) ecs_strbuf_appendstr(buf, ECS_GREEN); + isNum = true; + } - tptr[0] = '\0'; - - return ptr; -} + if (isVar && !isalpha(ch) && !isdigit(ch) && ch != '_') { + if (enable_colors) ecs_strbuf_appendstr(buf, ECS_NORMAL); + isVar = false; + } -static -bool is_newline_comment( - const char *ptr) -{ - if (ptr[0] == '/' && ptr[1] == '/') { - return true; - } - return false; -} + if (!isStr && !isVar && ch == '$' && isalpha(ptr[1])) { + if (enable_colors) ecs_strbuf_appendstr(buf, ECS_CYAN); + isVar = true; + } + } -const char* ecs_parse_fluff( - const char *ptr, - char **last_comment) -{ - const char *last_comment_start = NULL; + if (!isVar && !isStr && !isNum && ch == '#' && ptr[1] == '[') { + bool isColor = true; + overrideColor = true; - do { - /* Skip whitespaces before checking for a comment */ - ptr = ecs_parse_whitespace(ptr); + /* Custom colors */ + if (!ecs_os_strncmp(&ptr[2], "]", ecs_os_strlen("]"))) { + autoColor = false; + } else if (!ecs_os_strncmp(&ptr[2], "green]", ecs_os_strlen("green]"))) { + if (enable_colors) ecs_strbuf_appendstr(buf, ECS_GREEN); + } else if (!ecs_os_strncmp(&ptr[2], "red]", ecs_os_strlen("red]"))) { + if (enable_colors) ecs_strbuf_appendstr(buf, ECS_RED); + } else if (!ecs_os_strncmp(&ptr[2], "blue]", ecs_os_strlen("red]"))) { + if (enable_colors) ecs_strbuf_appendstr(buf, ECS_BLUE); + } else if (!ecs_os_strncmp(&ptr[2], "magenta]", ecs_os_strlen("magenta]"))) { + if (enable_colors) ecs_strbuf_appendstr(buf, ECS_MAGENTA); + } else if (!ecs_os_strncmp(&ptr[2], "cyan]", ecs_os_strlen("cyan]"))) { + if (enable_colors) ecs_strbuf_appendstr(buf, ECS_CYAN); + } else if (!ecs_os_strncmp(&ptr[2], "yellow]", ecs_os_strlen("yellow]"))) { + if (enable_colors) ecs_strbuf_appendstr(buf, ECS_YELLOW); + } else if (!ecs_os_strncmp(&ptr[2], "grey]", ecs_os_strlen("grey]"))) { + if (enable_colors) ecs_strbuf_appendstr(buf, ECS_GREY); + } else if (!ecs_os_strncmp(&ptr[2], "white]", ecs_os_strlen("white]"))) { + if (enable_colors) ecs_strbuf_appendstr(buf, ECS_NORMAL); + } else if (!ecs_os_strncmp(&ptr[2], "bold]", ecs_os_strlen("bold]"))) { + if (enable_colors) ecs_strbuf_appendstr(buf, ECS_BOLD); + } else if (!ecs_os_strncmp(&ptr[2], "normal]", ecs_os_strlen("normal]"))) { + if (enable_colors) ecs_strbuf_appendstr(buf, ECS_NORMAL); + } else if (!ecs_os_strncmp(&ptr[2], "reset]", ecs_os_strlen("reset]"))) { + overrideColor = false; + if (enable_colors) ecs_strbuf_appendstr(buf, ECS_NORMAL); + } else { + isColor = false; + overrideColor = false; + } - /* Newline comment, skip until newline character */ - if (is_newline_comment(ptr)) { - ptr += 2; - last_comment_start = ptr; + if (isColor) { + ptr += 2; + while ((ch = *ptr) != ']') ptr ++; + dontAppend = true; + } + if (!autoColor) { + overrideColor = true; + } + } - while (ptr[0] && ptr[0] != TOK_NEWLINE) { - ptr ++; + if (ch == '\n') { + if (isNum || isStr || isVar || overrideColor) { + if (enable_colors) ecs_strbuf_appendstr(buf, ECS_NORMAL); + overrideColor = false; + isNum = false; + isStr = false; + isVar = false; } } - /* If a newline character is found, skip it */ - if (ptr[0] == TOK_NEWLINE) { - ptr ++; + if (!dontAppend) { + ecs_strbuf_appendstrn(buf, ptr, 1); } - } while (isspace(ptr[0]) || is_newline_comment(ptr)); + if (!overrideColor) { + if (((ch == '\'') || (ch == '"')) && !isStr) { + if (enable_colors) ecs_strbuf_appendstr(buf, ECS_NORMAL); + } + } - if (last_comment) { - *last_comment = (char*)last_comment_start; + prev = ch; } - return ptr; -} - -/* -- Private functions -- */ - -static -bool valid_identifier_start_char( - char ch) -{ - if (ch && (isalpha(ch) || (ch == '.') || (ch == '_') || (ch == '*') || - (ch == '0') || (ch == TOK_AS_ENTITY) || isdigit(ch))) - { - return true; + if (isNum || isStr || isVar || overrideColor) { + if (enable_colors) ecs_strbuf_appendstr(buf, ECS_NORMAL); } - - return false; } -static -bool valid_token_start_char( - char ch) +void _ecs_logv( + int level, + const char *file, + int32_t line, + const char *fmt, + va_list args) { - if ((ch == '"') || (ch == '{') || (ch == '}') || (ch == ',') || (ch == '-') - || (ch == '[') || (ch == ']') || valid_identifier_start_char(ch)) - { - return true; - } + (void)level; + (void)line; - return false; -} + ecs_strbuf_t msg_buf = ECS_STRBUF_INIT; -static -bool valid_token_char( - char ch) -{ - if (ch && - (isalpha(ch) || isdigit(ch) || ch == '_' || ch == '.' || ch == '"')) - { - return true; + if (level > ecs_os_api.log_level_) { + return; } - return false; + /* Apply color. Even if we don't want color, we still need to call the + * colorize function to get rid of the color tags (e.g. #[green]) */ + char *msg_nocolor = ecs_vasprintf(fmt, args); + ecs_colorize_buf(msg_nocolor, ecs_os_api.log_with_color_, &msg_buf); + ecs_os_free(msg_nocolor); + + char *msg = ecs_strbuf_get(&msg_buf); + ecs_os_api.log_(level, file, line, msg); + ecs_os_free(msg); } -static -bool valid_operator_char( - char ch) +void _ecs_log( + int level, + const char *file, + int32_t line, + const char *fmt, + ...) { - if (ch == TOK_OPTIONAL || ch == TOK_NOT) { - return true; - } + va_list args; + va_start(args, fmt); + _ecs_logv(level, file, line, fmt, args); + va_end(args); +} - return false; +void ecs_log_push(void) { + ecs_os_api.log_indent_ ++; } -static -const char* parse_digit( - const char *ptr, - char *token_out) -{ - ptr = ecs_parse_whitespace(ptr); - ptr = ecs_parse_digit(ptr, token_out); - return ecs_parse_whitespace(ptr); +void ecs_log_pop(void) { + ecs_os_api.log_indent_ --; } -const char* ecs_parse_token( +void _ecs_parser_errorv( const char *name, - const char *expr, - const char *ptr, - char *token_out) + const char *expr, + int64_t column_arg, + const char *fmt, + va_list args) { - int64_t column = ptr - expr; - - ptr = ecs_parse_whitespace(ptr); - char *tptr = token_out, ch = ptr[0]; + int32_t column = flecs_itoi32(column_arg); - if (!valid_token_start_char(ch)) { - if (ch == '\0' || ch == '\n') { - ecs_parser_error(name, expr, column, - "unexpected end of expression"); - } else { - ecs_parser_error(name, expr, column, - "invalid start of token '%s'", ptr); - } - return NULL; - } + if (ecs_os_api.log_level_ >= -2) { + ecs_strbuf_t msg_buf = ECS_STRBUF_INIT; - tptr[0] = ch; - tptr ++; - ptr ++; + ecs_strbuf_vappend(&msg_buf, fmt, args); - if (ch == '{' || ch == '}' || ch == '[' || ch == ']' || ch == ',') { - tptr[0] = 0; - return ptr; - } + if (expr) { + ecs_strbuf_appendstr(&msg_buf, "\n"); - int tmpl_nesting = 0; - bool in_str = ch == '"'; + /* Find start of line by taking column and looking for the + * last occurring newline */ + if (column != -1) { + const char *ptr = &expr[column]; + while (ptr[0] != '\n' && ptr > expr) { + ptr --; + } - for (; (ch = *ptr); ptr ++) { - if (ch == '<') { - tmpl_nesting ++; - } else if (ch == '>') { - if (!tmpl_nesting) { - break; + if (ptr == expr) { + /* ptr is already at start of line */ + } else { + column -= (int32_t)(ptr - expr + 1); + expr = ptr + 1; + } } - tmpl_nesting --; - } else if (ch == '"') { - in_str = !in_str; - } else - if (!valid_token_char(ch) && !in_str) { - break; - } - - tptr[0] = ch; - tptr ++; - } - - tptr[0] = '\0'; - - if (tmpl_nesting != 0) { - ecs_parser_error(name, expr, column, - "identifier '%s' has mismatching < > pairs", ptr); - return NULL; - } - - const char *next_ptr = ecs_parse_whitespace(ptr); - if (next_ptr[0] == ':' && next_ptr != ptr) { - /* Whitespace between token and : is significant */ - ptr = next_ptr - 1; - } else { - ptr = next_ptr; - } - - return ptr; -} - -static -const char* ecs_parse_identifier( - const char *name, - const char *expr, - const char *ptr, - char *token_out) -{ - if (!valid_identifier_start_char(ptr[0])) { - ecs_parser_error(name, expr, (ptr - expr), - "expected start of identifier"); - return NULL; - } - - ptr = ecs_parse_token(name, expr, ptr, token_out); - - return ptr; -} -static -int parse_identifier( - const char *token, - ecs_term_id_t *out) -{ - char ch = token[0]; + /* Strip newlines from current statement, if any */ + char *newline_ptr = strchr(expr, '\n'); + if (newline_ptr) { + /* Strip newline from expr */ + ecs_strbuf_appendstrn(&msg_buf, expr, + (int32_t)(newline_ptr - expr)); + } else { + ecs_strbuf_appendstr(&msg_buf, expr); + } - const char *tptr = token; - if (ch == TOK_AS_ENTITY) { - tptr ++; - } + ecs_strbuf_appendstr(&msg_buf, "\n"); - out->name = ecs_os_strdup(tptr); + if (column != -1) { + ecs_strbuf_append(&msg_buf, "%*s^", column, ""); + } + } - if (ch == TOK_AS_ENTITY) { - out->var = EcsVarIsEntity; + char *msg = ecs_strbuf_get(&msg_buf); + ecs_os_err(name, 0, msg); + ecs_os_free(msg); } - - return 0; } -static -ecs_entity_t parse_role( +void _ecs_parser_error( const char *name, - const char *sig, + const char *expr, int64_t column, - const char *token) + const char *fmt, + ...) { - if (!ecs_os_strcmp(token, TOK_ROLE_PAIR)) - { - return ECS_PAIR; - } else if (!ecs_os_strcmp(token, TOK_ROLE_AND)) { - return ECS_AND; - } else if (!ecs_os_strcmp(token, TOK_ROLE_OR)) { - return ECS_OR; - } else if (!ecs_os_strcmp(token, TOK_ROLE_XOR)) { - return ECS_XOR; - } else if (!ecs_os_strcmp(token, TOK_ROLE_NOT)) { - return ECS_NOT; - } else if (!ecs_os_strcmp(token, TOK_ROLE_SWITCH)) { - return ECS_SWITCH; - } else if (!ecs_os_strcmp(token, TOK_ROLE_CASE)) { - return ECS_CASE; - } else if (!ecs_os_strcmp(token, TOK_OWNED)) { - return ECS_OVERRIDE; - } else if (!ecs_os_strcmp(token, TOK_ROLE_DISABLED)) { - return ECS_DISABLED; - } else { - ecs_parser_error(name, sig, column, "invalid role '%s'", token); - return 0; + if (ecs_os_api.log_level_ >= -2) { + va_list args; + va_start(args, fmt); + _ecs_parser_errorv(name, expr, column, fmt, args); + va_end(args); } } -static -ecs_oper_kind_t parse_operator( - char ch) +void _ecs_abort( + int32_t err, + const char *file, + int32_t line, + const char *fmt, + ...) { - if (ch == TOK_OPTIONAL) { - return EcsOptional; - } else if (ch == TOK_NOT) { - return EcsNot; + if (fmt) { + va_list args; + va_start(args, fmt); + char *msg = ecs_vasprintf(fmt, args); + va_end(args); + _ecs_fatal(file, line, "%s (%s)", msg, ecs_strerror(err)); + ecs_os_free(msg); } else { - ecs_abort(ECS_INTERNAL_ERROR, NULL); + _ecs_fatal(file, line, "%s", ecs_strerror(err)); } + ecs_os_api.log_last_error_ = err; } -static -const char* parse_annotation( - const char *name, - const char *sig, - int64_t column, - const char *ptr, - ecs_inout_kind_t *inout_kind_out) +bool _ecs_assert( + bool condition, + int32_t err, + const char *cond_str, + const char *file, + int32_t line, + const char *fmt, + ...) { - char token[ECS_MAX_TOKEN_SIZE]; - - ptr = ecs_parse_identifier(name, sig, ptr, token); - if (!ptr) { - return NULL; - } - - if (!ecs_os_strcmp(token, TOK_IN)) { - *inout_kind_out = EcsIn; - } else - if (!ecs_os_strcmp(token, TOK_OUT)) { - *inout_kind_out = EcsOut; - } else - if (!ecs_os_strcmp(token, TOK_INOUT)) { - *inout_kind_out = EcsInOut; - } else if (!ecs_os_strcmp(token, TOK_INOUT_FILTER)) { - *inout_kind_out = EcsInOutFilter; - } - - ptr = ecs_parse_whitespace(ptr); - - if (ptr[0] != TOK_BRACKET_CLOSE) { - ecs_parser_error(name, sig, column, "expected ]"); - return NULL; + if (!condition) { + if (fmt) { + va_list args; + va_start(args, fmt); + char *msg = ecs_vasprintf(fmt, args); + va_end(args); + _ecs_fatal(file, line, "assert: %s %s (%s)", + cond_str, msg, ecs_strerror(err)); + ecs_os_free(msg); + } else { + _ecs_fatal(file, line, "assert: %s %s", + cond_str, ecs_strerror(err)); + } + ecs_os_api.log_last_error_ = err; } - return ptr + 1; + return condition; } -static -uint8_t parse_set_token( - const char *token) +void _ecs_deprecated( + const char *file, + int32_t line, + const char *msg) { - if (!ecs_os_strcmp(token, TOK_SELF)) { - return EcsSelf; - } else if (!ecs_os_strcmp(token, TOK_SUPERSET)) { - return EcsSuperSet; - } else if (!ecs_os_strcmp(token, TOK_SUBSET)) { - return EcsSubSet; - } else if (!ecs_os_strcmp(token, TOK_CASCADE)) { - return EcsCascade; - } else if (!ecs_os_strcmp(token, TOK_ALL)) { - return EcsAll; - } else if (!ecs_os_strcmp(token, TOK_PARENT)) { - return EcsParent; - } else { - return 0; - } + _ecs_err(file, line, "%s", msg); } -static -const char* parse_set_expr( - const ecs_world_t *world, - const char *name, - const char *expr, - int64_t column, - const char *ptr, - char *token, - ecs_term_id_t *id, - char tok_end) -{ - char token_buf[ECS_MAX_TOKEN_SIZE] = {0}; - if (!token) { - token = token_buf; - ptr = ecs_parse_identifier(name, expr, ptr, token); - if (!ptr) { - return NULL; - } - } - - do { - uint8_t tok = parse_set_token(token); - if (!tok) { - ecs_parser_error(name, expr, column, - "invalid set token '%s'", token); - return NULL; - } - - if (id->set.mask & tok) { - ecs_parser_error(name, expr, column, - "duplicate set token '%s'", token); - return NULL; - } +#define ECS_ERR_STR(code) case code: return &(#code[4]) - if ((tok == EcsSubSet && id->set.mask & EcsSuperSet) || - (tok == EcsSuperSet && id->set.mask & EcsSubSet)) - { - ecs_parser_error(name, expr, column, - "cannot mix super and sub", token); - return NULL; - } - - id->set.mask |= tok; +const char* ecs_strerror( + int32_t error_code) +{ + switch (error_code) { + ECS_ERR_STR(ECS_INVALID_PARAMETER); + ECS_ERR_STR(ECS_NOT_A_COMPONENT); + ECS_ERR_STR(ECS_INTERNAL_ERROR); + ECS_ERR_STR(ECS_ALREADY_DEFINED); + ECS_ERR_STR(ECS_INVALID_COMPONENT_SIZE); + ECS_ERR_STR(ECS_INVALID_COMPONENT_ALIGNMENT); + ECS_ERR_STR(ECS_OUT_OF_MEMORY); + ECS_ERR_STR(ECS_OPERATION_FAILED); + ECS_ERR_STR(ECS_INVALID_CONVERSION); + ECS_ERR_STR(ECS_MODULE_UNDEFINED); + ECS_ERR_STR(ECS_COLUMN_INDEX_OUT_OF_RANGE); + ECS_ERR_STR(ECS_COLUMN_IS_NOT_SHARED); + ECS_ERR_STR(ECS_COLUMN_IS_SHARED); + ECS_ERR_STR(ECS_COLUMN_TYPE_MISMATCH); + ECS_ERR_STR(ECS_INVALID_WHILE_ITERATING); + ECS_ERR_STR(ECS_INVALID_FROM_WORKER); + ECS_ERR_STR(ECS_OUT_OF_RANGE); + ECS_ERR_STR(ECS_MISSING_OS_API); + ECS_ERR_STR(ECS_UNSUPPORTED); + ECS_ERR_STR(ECS_COLUMN_ACCESS_VIOLATION); + ECS_ERR_STR(ECS_COMPONENT_NOT_REGISTERED); + ECS_ERR_STR(ECS_INCONSISTENT_COMPONENT_ID); + ECS_ERR_STR(ECS_TYPE_INVALID_CASE); + ECS_ERR_STR(ECS_INCONSISTENT_NAME); + ECS_ERR_STR(ECS_INCONSISTENT_COMPONENT_ACTION); + ECS_ERR_STR(ECS_INVALID_OPERATION); + ECS_ERR_STR(ECS_CONSTRAINT_VIOLATED); + ECS_ERR_STR(ECS_LOCKED_STORAGE); + } - if (ptr[0] == TOK_PAREN_OPEN) { - ptr ++; + return "unknown error code"; +} - /* Relationship (overrides IsA default) */ - if (!isdigit(ptr[0]) && valid_token_start_char(ptr[0])) { - ptr = ecs_parse_identifier(name, expr, ptr, token); - if (!ptr) { - return NULL; - } +#else - id->set.relation = ecs_lookup_fullpath(world, token); - if (!id->set.relation) { - ecs_parser_error(name, expr, column, - "unresolved identifier '%s'", token); - return NULL; - } +/* Empty bodies for when logging is disabled */ - if (ptr[0] == TOK_AND) { - ptr = ecs_parse_whitespace(ptr + 1); - } else if (ptr[0] != TOK_PAREN_CLOSE) { - ecs_parser_error(name, expr, column, - "expected ',' or ')'"); - return NULL; - } - } +FLECS_API +void _ecs_log( + int32_t level, + const char *file, + int32_t line, + const char *fmt, + ...) +{ + (void)level; + (void)file; + (void)line; + (void)fmt; +} - /* Max depth of search */ - if (isdigit(ptr[0])) { - ptr = parse_digit(ptr, token); - if (!ptr) { - return NULL; - } +FLECS_API +void _ecs_parser_error( + const char *name, + const char *expr, + int64_t column, + const char *fmt, + ...) +{ + (void)name; + (void)expr; + (void)column; + (void)fmt; +} - id->set.max_depth = atoi(token); - if (id->set.max_depth < 0) { - ecs_parser_error(name, expr, column, - "invalid negative depth"); - return NULL; - } +FLECS_API +void _ecs_parser_errorv( + const char *name, + const char *expr, + int64_t column, + const char *fmt, + va_list args) +{ + (void)name; + (void)expr; + (void)column; + (void)fmt; + (void)args; +} - if (ptr[0] == ',') { - ptr = ecs_parse_whitespace(ptr + 1); - } - } +FLECS_API +void _ecs_abort( + int32_t error_code, + const char *file, + int32_t line, + const char *fmt, + ...) +{ + (void)error_code; + (void)file; + (void)line; + (void)fmt; +} - /* If another digit is found, previous depth was min depth */ - if (isdigit(ptr[0])) { - ptr = parse_digit(ptr, token); - if (!ptr) { - return NULL; - } +FLECS_API +bool _ecs_assert( + bool condition, + int32_t error_code, + const char *condition_str, + const char *file, + int32_t line, + const char *fmt, + ...) +{ + (void)condition; + (void)error_code; + (void)condition_str; + (void)file; + (void)line; + (void)fmt; + return true; +} - id->set.min_depth = id->set.max_depth; - id->set.max_depth = atoi(token); - if (id->set.max_depth < 0) { - ecs_parser_error(name, expr, column, - "invalid negative depth"); - return NULL; - } - } +#endif - if (ptr[0] != TOK_PAREN_CLOSE) { - ecs_parser_error(name, expr, column, "expected ')', got '%c'", - ptr[0]); - return NULL; - } else { - ptr = ecs_parse_whitespace(ptr + 1); - if (ptr[0] != tok_end && ptr[0] != TOK_AND && ptr[0] != 0) { - ecs_parser_error(name, expr, column, - "expected end of set expr"); - return NULL; - } - } - } +int ecs_log_set_level( + int level) +{ + int prev = level; + ecs_os_api.log_level_ = level; + return prev; +} - /* Next token in set expression */ - if (ptr[0] == TOK_BITWISE_OR) { - ptr ++; - if (valid_token_start_char(ptr[0])) { - ptr = ecs_parse_identifier(name, expr, ptr, token); - if (!ptr) { - return NULL; - } - } +bool ecs_log_enable_colors( + bool enabled) +{ + bool prev = ecs_os_api.log_with_color_; + ecs_os_api.log_with_color_ = enabled; + return prev; +} - /* End of set expression */ - } else if (ptr[0] == tok_end || ptr[0] == TOK_AND || !ptr[0]) { - break; - } - } while (true); +int ecs_log_last_error(void) +{ + int result = ecs_os_api.log_last_error_; + ecs_os_api.log_last_error_ = 0; + return result; +} - if (id->set.mask & EcsCascade && !(id->set.mask & EcsSuperSet) && - !(id->set.mask & EcsSubSet)) - { - /* If cascade is used without specifying super or sub, assume - * super */ - id->set.mask |= EcsSuperSet; - } - if (id->set.mask & EcsSelf && id->set.min_depth != 0) { - ecs_parser_error(name, expr, column, - "min_depth must be zero for set expression with 'self'"); - return NULL; - } +#ifdef FLECS_JSON - return ptr; -} +void json_next( + ecs_strbuf_t *buf); -static -const char* parse_arguments( - const ecs_world_t *world, - const char *name, - const char *expr, - int64_t column, - const char *ptr, - char *token, - ecs_term_t *term) -{ - (void)column; +void json_literal( + ecs_strbuf_t *buf, + const char *value); - int32_t arg = 0; +void json_number( + ecs_strbuf_t *buf, + double value); - do { - if (valid_token_start_char(ptr[0])) { - if (arg == 2) { - ecs_parser_error(name, expr, (ptr - expr), - "too many arguments in term"); - return NULL; - } +void json_true( + ecs_strbuf_t *buf); - ptr = ecs_parse_identifier(name, expr, ptr, token); - if (!ptr) { - return NULL; - } +void json_false( + ecs_strbuf_t *buf); - ecs_term_id_t *term_id = NULL; +void json_array_push( + ecs_strbuf_t *buf); - if (arg == 0) { - term_id = &term->subj; - } else if (arg == 1) { - term_id = &term->obj; - } +void json_array_pop( + ecs_strbuf_t *buf); - /* If token is a colon, the token is an identifier followed by a - * set expression. */ - if (ptr[0] == TOK_COLON) { - if (parse_identifier(token, term_id)) { - ecs_parser_error(name, expr, (ptr - expr), - "invalid identifier '%s'", token); - return NULL; - } +void json_object_push( + ecs_strbuf_t *buf); - ptr = ecs_parse_whitespace(ptr + 1); - ptr = parse_set_expr(world, name, expr, (ptr - expr), ptr, - NULL, term_id, TOK_PAREN_CLOSE); - if (!ptr) { - return NULL; - } +void json_object_pop( + ecs_strbuf_t *buf); - /* If token is a self, super or sub token, this is a set - * expression */ - } else if (!ecs_os_strcmp(token, TOK_ALL) || - !ecs_os_strcmp(token, TOK_CASCADE) || - !ecs_os_strcmp(token, TOK_SELF) || - !ecs_os_strcmp(token, TOK_SUPERSET) || - !ecs_os_strcmp(token, TOK_SUBSET) || - !(ecs_os_strcmp(token, TOK_PARENT))) - { - ptr = parse_set_expr(world, name, expr, (ptr - expr), ptr, - token, term_id, TOK_PAREN_CLOSE); - if (!ptr) { - return NULL; - } +void json_string( + ecs_strbuf_t *buf, + const char *value); - /* Regular identifier */ - } else if (parse_identifier(token, term_id)) { - ecs_parser_error(name, expr, (ptr - expr), - "invalid identifier '%s'", token); - return NULL; - } +void json_member( + ecs_strbuf_t *buf, + const char *name); - if (ptr[0] == TOK_AND) { - ptr = ecs_parse_whitespace(ptr + 1); +void json_path( + ecs_strbuf_t *buf, + const ecs_world_t *world, + ecs_entity_t e); - term->role = ECS_PAIR; +void json_id( + ecs_strbuf_t *buf, + const ecs_world_t *world, + ecs_id_t id); - } else if (ptr[0] == TOK_PAREN_CLOSE) { - ptr = ecs_parse_whitespace(ptr + 1); - break; +ecs_primitive_kind_t json_op_to_primitive_kind( + ecs_meta_type_op_kind_t kind); - } else { - ecs_parser_error(name, expr, (ptr - expr), - "expected ',' or ')'"); - return NULL; - } +#endif - } else { - ecs_parser_error(name, expr, (ptr - expr), - "expected identifier or set expression"); - return NULL; - } +#ifdef FLECS_JSON - arg ++; +static +int json_ser_type( + const ecs_world_t *world, + ecs_vector_t *ser, + const void *base, + ecs_strbuf_t *str); - } while (true); +static +int json_ser_type_ops( + const ecs_world_t *world, + ecs_meta_type_op_t *ops, + int32_t op_count, + const void *base, + ecs_strbuf_t *str); - return ptr; -} +static +int json_ser_type_op( + const ecs_world_t *world, + ecs_meta_type_op_t *op, + const void *base, + ecs_strbuf_t *str); +/* Serialize enumeration */ static -void parser_unexpected_char( - const char *name, - const char *expr, - const char *ptr, - char ch) +int json_ser_enum( + const ecs_world_t *world, + ecs_meta_type_op_t *op, + const void *base, + ecs_strbuf_t *str) { - if (ch && (ch != '\n')) { - ecs_parser_error(name, expr, (ptr - expr), - "unexpected character '%c'", ch); - } else { - ecs_parser_error(name, expr, (ptr - expr), - "unexpected end of term"); + const EcsEnum *enum_type = ecs_get(world, op->type, EcsEnum); + ecs_check(enum_type != NULL, ECS_INVALID_PARAMETER, NULL); + + int32_t value = *(int32_t*)base; + + /* Enumeration constants are stored in a map that is keyed on the + * enumeration value. */ + ecs_enum_constant_t *constant = ecs_map_get( + enum_type->constants, ecs_enum_constant_t, value); + if (!constant) { + goto error; } + + ecs_strbuf_appendch(str, '"'); + ecs_strbuf_appendstr(str, ecs_get_name(world, constant->constant)); + ecs_strbuf_appendch(str, '"'); + + return 0; +error: + return -1; } +/* Serialize bitmask */ static -const char* parse_term( +int json_ser_bitmask( const ecs_world_t *world, - const char *name, - const char *expr, - ecs_term_t *term_out) + ecs_meta_type_op_t *op, + const void *ptr, + ecs_strbuf_t *str) { - const char *ptr = expr; - char token[ECS_MAX_TOKEN_SIZE] = {0}; - ecs_term_t term = { .move = true /* parser never owns resources */ }; + const EcsBitmask *bitmask_type = ecs_get(world, op->type, EcsBitmask); + ecs_check(bitmask_type != NULL, ECS_INVALID_PARAMETER, NULL); - ptr = ecs_parse_whitespace(ptr); + uint32_t value = *(uint32_t*)ptr; + ecs_map_key_t key; + ecs_bitmask_constant_t *constant; - /* Inout specifiers always come first */ - if (ptr[0] == TOK_BRACKET_OPEN) { - ptr = parse_annotation(name, expr, (ptr - expr), ptr + 1, &term.inout); - if (!ptr) { - goto error; - } - ptr = ecs_parse_whitespace(ptr); + if (!value) { + ecs_strbuf_appendch(str, '0'); + return 0; } - if (valid_operator_char(ptr[0])) { - term.oper = parse_operator(ptr[0]); - ptr = ecs_parse_whitespace(ptr + 1); - } + ecs_strbuf_list_push(str, "\"", "|"); - /* If next token is the start of an identifier, it could be either a type - * role, source or component identifier */ - if (valid_token_start_char(ptr[0])) { - ptr = ecs_parse_identifier(name, expr, ptr, token); - if (!ptr) { - goto error; + /* Multiple flags can be set at a given time. Iterate through all the flags + * and append the ones that are set. */ + ecs_map_iter_t it = ecs_map_iter(bitmask_type->constants); + while ((constant = ecs_map_next(&it, ecs_bitmask_constant_t, &key))) { + if ((value & key) == key) { + ecs_strbuf_list_appendstr(str, + ecs_get_name(world, constant->constant)); + value -= (uint32_t)key; } + } - /* Is token a type role? */ - if (ptr[0] == TOK_BITWISE_OR && ptr[1] != TOK_BITWISE_OR) { - ptr ++; - goto parse_role; - } + if (value != 0) { + /* All bits must have been matched by a constant */ + goto error; + } - /* Is token a predicate? */ - if (ptr[0] == TOK_PAREN_OPEN) { - goto parse_predicate; - } + ecs_strbuf_list_pop(str, "\""); - /* Next token must be a predicate */ - goto parse_predicate; + return 0; +error: + return -1; +} - /* If next token is a singleton, assign identifier to pred and subject */ - } else if (ptr[0] == TOK_SINGLETON) { - ptr ++; - if (valid_token_start_char(ptr[0])) { - ptr = ecs_parse_identifier(name, expr, ptr, token); - if (!ptr) { - goto error; - } +/* Serialize elements of a contiguous array */ +static +int json_ser_elements( + const ecs_world_t *world, + ecs_meta_type_op_t *ops, + int32_t op_count, + const void *base, + int32_t elem_count, + int32_t elem_size, + ecs_strbuf_t *str) +{ + json_array_push(str); - goto parse_singleton; + const void *ptr = base; - } else { - ecs_parser_error(name, expr, (ptr - expr), - "expected identifier after singleton operator"); - goto error; + int i; + for (i = 0; i < elem_count; i ++) { + ecs_strbuf_list_next(str); + if (json_ser_type_ops(world, ops, op_count, ptr, str)) { + return -1; } - - /* Pair with implicit subject */ - } else if (ptr[0] == TOK_PAREN_OPEN) { - goto parse_pair; - - /* Nothing else expected here */ - } else { - parser_unexpected_char(name, expr, ptr, ptr[0]); - goto error; - } - -parse_role: - term.role = parse_role(name, expr, (ptr - expr), token); - if (!term.role) { - goto error; + ptr = ECS_OFFSET(ptr, elem_size); } - ptr = ecs_parse_whitespace(ptr); - - /* If next token is the source token, this is an empty source */ - if (valid_token_start_char(ptr[0])) { - ptr = ecs_parse_identifier(name, expr, ptr, token); - if (!ptr) { - goto error; - } - - /* If not, it's a predicate */ - goto parse_predicate; + json_array_pop(str); - } else if (ptr[0] == TOK_PAREN_OPEN) { - goto parse_pair; - } else { - ecs_parser_error(name, expr, (ptr - expr), - "expected identifier after role"); - goto error; - } + return 0; +} -parse_predicate: - if (parse_identifier(token, &term.pred)) { - ecs_parser_error(name, expr, (ptr - expr), - "invalid identifier '%s'", token); - goto error; - } +static +int json_ser_type_elements( + const ecs_world_t *world, + ecs_entity_t type, + const void *base, + int32_t elem_count, + ecs_strbuf_t *str) +{ + const EcsMetaTypeSerialized *ser = ecs_get( + world, type, EcsMetaTypeSerialized); + ecs_assert(ser != NULL, ECS_INTERNAL_ERROR, NULL); - /* Set expression */ - if (ptr[0] == TOK_COLON) { - ptr = ecs_parse_whitespace(ptr + 1); - ptr = parse_set_expr(world, name, expr, (ptr - expr), ptr, NULL, - &term.pred, TOK_COLON); - if (!ptr) { - goto error; - } + const EcsComponent *comp = ecs_get(world, type, EcsComponent); + ecs_assert(comp != NULL, ECS_INTERNAL_ERROR, NULL); - ptr = ecs_parse_whitespace(ptr); + ecs_meta_type_op_t *ops = ecs_vector_first(ser->ops, ecs_meta_type_op_t); + int32_t op_count = ecs_vector_count(ser->ops); - if (ptr[0] == TOK_AND || !ptr[0]) { - goto parse_done; - } + return json_ser_elements( + world, ops, op_count, base, elem_count, comp->size, str); +} - if (ptr[0] != TOK_COLON) { - ecs_parser_error(name, expr, (ptr - expr), - "unexpected token '%c' after predicate set expression", ptr[0]); - goto error; - } +/* Serialize array */ +static +int json_ser_array( + const ecs_world_t *world, + ecs_meta_type_op_t *op, + const void *ptr, + ecs_strbuf_t *str) +{ + const EcsArray *a = ecs_get(world, op->type, EcsArray); + ecs_assert(a != NULL, ECS_INTERNAL_ERROR, NULL); - ptr = ecs_parse_whitespace(ptr + 1); - } else { - ptr = ecs_parse_whitespace(ptr); - } - - if (ptr[0] == TOK_PAREN_OPEN) { - ptr ++; - if (ptr[0] == TOK_PAREN_CLOSE) { - term.subj.set.mask = EcsNothing; - ptr ++; - ptr = ecs_parse_whitespace(ptr); - } else { - ptr = parse_arguments( - world, name, expr, (ptr - expr), ptr, token, &term); - } + return json_ser_type_elements( + world, a->type, ptr, a->count, str); +} - goto parse_done; +/* Serialize vector */ +static +int json_ser_vector( + const ecs_world_t *world, + ecs_meta_type_op_t *op, + const void *base, + ecs_strbuf_t *str) +{ + ecs_vector_t *value = *(ecs_vector_t**)base; + if (!value) { + ecs_strbuf_appendstr(str, "null"); + return 0; } - goto parse_done; + const EcsVector *v = ecs_get(world, op->type, EcsVector); + ecs_assert(v != NULL, ECS_INTERNAL_ERROR, NULL); -parse_pair: - ptr = ecs_parse_identifier(name, expr, ptr + 1, token); - if (!ptr) { - goto error; - } + const EcsComponent *comp = ecs_get(world, v->type, EcsComponent); + ecs_assert(comp != NULL, ECS_INTERNAL_ERROR, NULL); - if (ptr[0] == TOK_AND) { - ptr ++; - term.subj.entity = EcsThis; - goto parse_pair_predicate; - } else if (ptr[0] == TOK_PAREN_CLOSE) { - term.subj.entity = EcsThis; - goto parse_pair_predicate; - } else { - parser_unexpected_char(name, expr, ptr, ptr[0]); - goto error; - } + int32_t count = ecs_vector_count(value); + void *array = ecs_vector_first_t(value, comp->size, comp->alignment); -parse_pair_predicate: - if (parse_identifier(token, &term.pred)) { - ecs_parser_error(name, expr, (ptr - expr), - "invalid identifier '%s'", token); - goto error; - } + /* Serialize contiguous buffer of vector */ + return json_ser_type_elements(world, v->type, array, count, str); +} - ptr = ecs_parse_whitespace(ptr); - if (valid_token_start_char(ptr[0])) { - ptr = ecs_parse_identifier(name, expr, ptr, token); - if (!ptr) { +/* Forward serialization to the different type kinds */ +static +int json_ser_type_op( + const ecs_world_t *world, + ecs_meta_type_op_t *op, + const void *ptr, + ecs_strbuf_t *str) +{ + switch(op->kind) { + case EcsOpPush: + case EcsOpPop: + /* Should not be parsed as single op */ + ecs_throw(ECS_INVALID_PARAMETER, NULL); + break; + case EcsOpEnum: + if (json_ser_enum(world, op, ECS_OFFSET(ptr, op->offset), str)) { goto error; } - - if (ptr[0] == TOK_PAREN_CLOSE) { - ptr ++; - goto parse_pair_object; - } else { - parser_unexpected_char(name, expr, ptr, ptr[0]); + break; + case EcsOpBitmask: + if (json_ser_bitmask(world, op, ECS_OFFSET(ptr, op->offset), str)) { goto error; } - } else if (ptr[0] == TOK_PAREN_CLOSE) { - /* No object */ - ptr ++; - goto parse_done; - } else { - ecs_parser_error(name, expr, (ptr - expr), - "expected pair object or ')'"); - goto error; - } - -parse_pair_object: - if (parse_identifier(token, &term.obj)) { - ecs_parser_error(name, expr, (ptr - expr), - "invalid identifier '%s'", token); - goto error; + break; + case EcsOpArray: + if (json_ser_array(world, op, ECS_OFFSET(ptr, op->offset), str)) { + goto error; + } + break; + case EcsOpVector: + if (json_ser_vector(world, op, ECS_OFFSET(ptr, op->offset), str)) { + goto error; + } + break; + case EcsOpEntity: { + ecs_entity_t e = *(ecs_entity_t*)ptr; + if (!e) { + ecs_strbuf_appendch(str, '0'); + } else { + char *path = ecs_get_fullpath(world, e); + ecs_assert(path != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_strbuf_append(str, "\"%s\"", path); + ecs_os_free(path); + } + break; } - if (term.role != 0) { - if (term.role != ECS_PAIR && term.role != ECS_CASE) { - ecs_parser_error(name, expr, (ptr - expr), - "invalid combination of role '%s' with pair", - ecs_role_str(term.role)); - goto error; + default: + if (ecs_primitive_to_expr_buf(world, json_op_to_primitive_kind(op->kind), + ECS_OFFSET(ptr, op->offset), str)) + { + /* Unknown operation */ + ecs_throw(ECS_INTERNAL_ERROR, NULL); + return -1; } - } else { - term.role = ECS_PAIR; + break; } - ptr = ecs_parse_whitespace(ptr); - goto parse_done; + return 0; +error: + return -1; +} -parse_singleton: - if (parse_identifier(token, &term.pred)) { - ecs_parser_error(name, expr, (ptr - expr), - "invalid identifier '%s'", token); - goto error; - } +/* Iterate over a slice of the type ops array */ +static +int json_ser_type_ops( + const ecs_world_t *world, + ecs_meta_type_op_t *ops, + int32_t op_count, + const void *base, + ecs_strbuf_t *str) +{ + for (int i = 0; i < op_count; i ++) { + ecs_meta_type_op_t *op = &ops[i]; - parse_identifier(token, &term.subj); - goto parse_done; + if (op != ops) { + if (op->name) { + json_member(str, op->name); + } -parse_done: - *term_out = term; - return ptr; + int32_t elem_count = op->count; + if (elem_count > 1 && op != ops) { + /* Serialize inline array */ + if (json_ser_elements(world, op, op->op_count, base, + elem_count, op->size, str)) + { + return -1; + } + + i += op->op_count - 1; + continue; + } + } + + switch(op->kind) { + case EcsOpPush: + json_object_push(str); + break; + case EcsOpPop: + json_object_pop(str); + break; + default: + if (json_ser_type_op(world, op, base, str)) { + goto error; + } + break; + } + } + return 0; error: - ecs_term_fini(&term); - *term_out = (ecs_term_t){0}; - return NULL; + return -1; } +/* Iterate over the type ops of a type */ static -bool is_valid_end_of_term( - const char *ptr) +int json_ser_type( + const ecs_world_t *world, + ecs_vector_t *v_ops, + const void *base, + ecs_strbuf_t *str) { - if ((ptr[0] == TOK_AND) || /* another term with And operator */ - (ptr[0] == TOK_OR[0]) || /* another term with Or operator */ - (ptr[0] == '\n') || /* newlines are valid */ - (ptr[0] == '\0') || /* end of string */ - (ptr[0] == '/') || /* comment (in plecs) */ - (ptr[0] == '{') || /* scope (in plecs) */ - (ptr[0] == '}') || - (ptr[0] == ':') || /* inheritance (in plecs) */ - (ptr[0] == '=')) /* assignment (in plecs) */ - { - return true; - } - return false; + ecs_meta_type_op_t *ops = ecs_vector_first(v_ops, ecs_meta_type_op_t); + int32_t count = ecs_vector_count(v_ops); + return json_ser_type_ops(world, ops, count, base, str); } -char* ecs_parse_term( +static +int array_to_json_buf_w_type_data( const ecs_world_t *world, - const char *name, - const char *expr, - const char *ptr, - ecs_term_t *term) + const void *ptr, + int32_t count, + ecs_strbuf_t *buf, + const EcsComponent *comp, + const EcsMetaTypeSerialized *ser) { - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(ptr != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(term != NULL, ECS_INVALID_PARAMETER, NULL); + if (count) { + ecs_size_t size = comp->size; - ecs_term_id_t *subj = &term->subj; + json_array_push(buf); - bool prev_or = false; - if (ptr != expr) { - if (ptr[0]) { - if (ptr[0] == ',') { - ptr ++; - } else if (ptr[0] == '|') { - ptr += 2; - prev_or = true; - } else { - ecs_parser_error(name, expr, (ptr - expr), - "invalid preceding token"); + do { + ecs_strbuf_list_next(buf); + if (json_ser_type(world, ser->ops, ptr, buf)) { + return -1; } + + ptr = ECS_OFFSET(ptr, size); + } while (-- count); + + json_array_pop(buf); + } else { + if (json_ser_type(world, ser->ops, ptr, buf)) { + return -1; } } - - ptr = ecs_parse_eol_and_whitespace(ptr); - if (!ptr[0]) { - *term = (ecs_term_t){0}; - return (char*)ptr; + + return 0; +} + +int ecs_array_to_json_buf( + const ecs_world_t *world, + ecs_entity_t type, + const void *ptr, + int32_t count, + ecs_strbuf_t *buf) +{ + const EcsComponent *comp = ecs_get(world, type, EcsComponent); + if (!comp) { + char *path = ecs_get_fullpath(world, type); + ecs_err("cannot serialize to JSON, '%s' is not a component", path); + ecs_os_free(path); + return -1; } - if (ptr == expr && !strcmp(expr, "0")) { - return (char*)&ptr[1]; + const EcsMetaTypeSerialized *ser = ecs_get( + world, type, EcsMetaTypeSerialized); + if (!ser) { + char *path = ecs_get_fullpath(world, type); + ecs_err("cannot serialize to JSON, '%s' has no reflection data", path); + ecs_os_free(path); + return -1; } - int32_t prev_set = subj->set.mask; + return array_to_json_buf_w_type_data(world, ptr, count, buf, comp, ser); +} - /* Parse next element */ - ptr = parse_term(world, name, ptr, term); - if (!ptr) { - goto error; +char* ecs_array_to_json( + const ecs_world_t *world, + ecs_entity_t type, + const void* ptr, + int32_t count) +{ + ecs_strbuf_t str = ECS_STRBUF_INIT; + + if (ecs_array_to_json_buf(world, type, ptr, count, &str) != 0) { + ecs_strbuf_reset(&str); + return NULL; } - /* Post-parse consistency checks */ + return ecs_strbuf_get(&str); +} - /* If next token is OR, term is part of an OR expression */ - if (!ecs_os_strncmp(ptr, TOK_OR, 2) || prev_or) { - /* An OR operator must always follow an AND or another OR */ - if (term->oper != EcsAnd) { - ecs_parser_error(name, expr, (ptr - expr), - "cannot combine || with other operators"); - goto error; - } +int ecs_ptr_to_json_buf( + const ecs_world_t *world, + ecs_entity_t type, + const void *ptr, + ecs_strbuf_t *buf) +{ + return ecs_array_to_json_buf(world, type, ptr, 0, buf); +} - term->oper = EcsOr; - } +char* ecs_ptr_to_json( + const ecs_world_t *world, + ecs_entity_t type, + const void* ptr) +{ + return ecs_array_to_json(world, type, ptr, 0); +} - /* Term must either end in end of expression, AND or OR token */ - if (!is_valid_end_of_term(ptr)) { - ecs_parser_error(name, expr, (ptr - expr), - "expected end of expression or next term"); - goto error; - } +static +int append_type( + const ecs_world_t *world, + ecs_strbuf_t *buf, + ecs_entity_t ent, + ecs_entity_t inst, + const ecs_entity_to_json_desc_t *desc) +{ + ecs_type_t type = ecs_get_type(world, ent); + ecs_id_t *ids = ecs_vector_first(type, ecs_id_t); + int32_t i, count = ecs_vector_count(type); - /* If the term just contained a 0, the expression has nothing. Ensure - * that after the 0 nothing else follows */ - if (!ecs_os_strcmp(term->pred.name, "0")) { - if (ptr[0]) { - ecs_parser_error(name, expr, (ptr - expr), - "unexpected term after 0"); - goto error; + json_member(buf, "type"); + json_array_push(buf); + + for (i = 0; i < count; i ++) { + ecs_id_t id = ids[i]; + ecs_entity_t pred = 0, obj = 0, role = 0; + + if (ECS_HAS_RELATION(id, EcsIsA)) { + /* Skip IsA as they are already added in "inherits" */ + continue; } - if (subj->set.mask != EcsDefaultSet || - (subj->entity && subj->entity != EcsThis) || - (subj->name && ecs_os_strcmp(subj->name, "This"))) - { - ecs_parser_error(name, expr, (ptr - expr), - "invalid combination of 0 with non-default subject"); - goto error; + if (ent != inst) { + /* If not serializing the top level entity, skip components that are + * never inherited from a base entity */ + if (id == ecs_pair(ecs_id(EcsIdentifier), EcsName) || + ECS_PAIR_RELATION(id) == EcsChildOf || + id == EcsPrefab) + { + continue; + } } - subj->set.mask = EcsNothing; - ecs_os_free(term->pred.name); - term->pred.name = NULL; - } + if (ECS_HAS_ROLE(id, PAIR)) { + pred = ecs_pair_relation(world, id); + obj = ecs_pair_object(world, id); + } else { + pred = id & ECS_COMPONENT_MASK; + if (id & ECS_ROLE_MASK) { + role = id & ECS_ROLE_MASK; + } + } - /* Cannot combine EcsNothing with operators other than AND */ - if (term->oper != EcsAnd && subj->set.mask == EcsNothing) { - ecs_parser_error(name, expr, (ptr - expr), - "invalid operator for empty source"); - goto error; - } + ecs_strbuf_list_next(buf); + json_object_push(buf); - /* Verify consistency of OR expression */ - if (prev_or && term->oper == EcsOr) { - /* Set expressions must be the same for all OR terms */ - if (subj->set.mask != prev_set) { - ecs_parser_error(name, expr, (ptr - expr), - "cannot combine different sources in OR expression"); - goto error; + if (pred) { + char *str = ecs_get_fullpath(world, pred); + json_member(buf, "pred"); json_string(buf, str); + ecs_os_free(str); + } + if (obj) { + char *str = ecs_get_fullpath(world, obj); + json_member(buf, "obj"); + json_string(buf, str); + ecs_os_free(str); + } + if (role) { + json_member(buf, "obj"); + json_string(buf, ecs_role_str(role)); } - term->oper = EcsOr; - } + bool hidden = false; - /* Automatically assign This if entity is not assigned and the set is - * nothing */ - if (subj->set.mask != EcsNothing) { - if (!subj->name) { - if (!subj->entity) { - subj->entity = EcsThis; + if (ent != inst) { + if (ecs_get_object_for_id(world, inst, EcsIsA, id) != ent) { + hidden = true; + ecs_strbuf_list_appendstr(buf, "\"hidden\":true"); } } - } - - if (subj->name && !ecs_os_strcmp(subj->name, "0")) { - subj->entity = 0; - subj->set.mask = EcsNothing; - } - /* Process role */ - if (term->role == ECS_AND) { - term->oper = EcsAndFrom; - term->role = 0; - } else if (term->role == ECS_OR) { - term->oper = EcsOrFrom; - term->role = 0; - } else if (term->role == ECS_NOT) { - term->oper = EcsNotFrom; - term->role = 0; - } + if (!hidden) { + ecs_entity_t typeid = ecs_get_typeid(world, id); + if (typeid) { + if (!desc || desc->serialize_values) { + const EcsMetaTypeSerialized *ser = ecs_get( + world, typeid, EcsMetaTypeSerialized); + if (ser) { + const void *ptr = ecs_get_id(world, ent, id); + ecs_assert(ptr != NULL, ECS_INTERNAL_ERROR, NULL); + json_member(buf, "value"); + if (json_ser_type(world, ser->ops, ptr, buf) != 0) { + /* Entity contains invalid value */ + return -1; + } - ptr = ecs_parse_whitespace(ptr); + if (desc && desc->serialize_type_info) { + json_member(buf, "type_info"); + if (ecs_type_info_to_json_buf( + world, typeid, buf) != 0) + { + return -1; + } + } + } + } + } + } - return (char*)ptr; -error: - if (term) { - ecs_term_fini(term); + json_object_pop(buf); } - return NULL; -} -#endif + json_array_pop(buf); + + return 0; +} +static +int append_base( + const ecs_world_t *world, + ecs_strbuf_t *buf, + ecs_entity_t ent, + ecs_entity_t inst, + const ecs_entity_to_json_desc_t *desc) +{ + ecs_type_t type = ecs_get_type(world, ent); + ecs_id_t *ids = ecs_vector_first(type, ecs_id_t); + int32_t i, count = ecs_vector_count(type); -#ifdef FLECS_META_C + for (i = 0; i < count; i ++) { + ecs_id_t id = ids[i]; + if (ECS_HAS_RELATION(id, EcsIsA)) { + if (append_base(world, buf, ecs_pair_object(world, id), inst, desc)) + { + return -1; + } + } + } -#define ECS_META_IDENTIFIER_LENGTH (256) + char *path = ecs_get_fullpath(world, ent); + json_member(buf, path); + ecs_os_free(path); -#define ecs_meta_error(ctx, ptr, ...)\ - ecs_parser_error((ctx)->name, (ctx)->desc, ptr - (ctx)->desc, __VA_ARGS__); + json_object_push(buf); -typedef char ecs_meta_token_t[ECS_META_IDENTIFIER_LENGTH]; + if (append_type(world, buf, ent, inst, desc)) { + return -1; + } -typedef struct meta_parse_ctx_t { - const char *name; - const char *desc; -} meta_parse_ctx_t; + json_object_pop(buf); -typedef struct meta_type_t { - ecs_meta_token_t type; - ecs_meta_token_t params; - bool is_const; - bool is_ptr; -} meta_type_t; + return 0; +} -typedef struct meta_member_t { - meta_type_t type; - ecs_meta_token_t name; - int64_t count; - bool is_partial; -} meta_member_t; +int ecs_entity_to_json_buf( + const ecs_world_t *world, + ecs_entity_t entity, + ecs_strbuf_t *buf, + const ecs_entity_to_json_desc_t *desc) +{ + if (!entity || !ecs_is_valid(world, entity)) { + return -1; + } -typedef struct meta_constant_t { - ecs_meta_token_t name; - int64_t value; - bool is_value_set; -} meta_constant_t; + json_object_push(buf); -typedef struct meta_params_t { - meta_type_t key_type; - meta_type_t type; - int64_t count; - bool is_key_value; - bool is_fixed_size; -} meta_params_t; + if (!desc || desc->serialize_path) { + char *path = ecs_get_fullpath(world, entity); + json_member(buf, "path"); + json_string(buf, path); + ecs_os_free(path); + } -static -const char* skip_scope(const char *ptr, meta_parse_ctx_t *ctx) { - /* Keep track of which characters were used to open the scope */ - char stack[256]; - int32_t sp = 0; - char ch; + ecs_type_t type = ecs_get_type(world, entity); + ecs_id_t *ids = ecs_vector_first(type, ecs_id_t); + int32_t i, count = ecs_vector_count(type); - while ((ch = *ptr)) { - if (ch == '(' || ch == '<') { - stack[sp] = ch; + if (!desc || desc->serialize_base) { + if (ecs_has_pair(world, entity, EcsIsA, EcsWildcard)) { + json_member(buf, "is_a"); + json_object_push(buf); - sp ++; - if (sp >= 256) { - ecs_meta_error(ctx, ptr, "maximum level of nesting reached"); - goto error; - } - } else if (ch == ')' || ch == '>') { - sp --; - if ((sp < 0) || (ch == '>' && stack[sp] != '<') || - (ch == ')' && stack[sp] != '(')) - { - ecs_meta_error(ctx, ptr, "mismatching %c in identifier", ch); - goto error; + for (i = 0; i < count; i ++) { + ecs_id_t id = ids[i]; + if (ECS_HAS_RELATION(id, EcsIsA)) { + if (append_base( + world, buf, ecs_pair_object(world, id), entity, desc)) + { + return -1; + } + } } - } - - ptr ++; - if (!sp) { - break; + json_object_pop(buf); } } - return ptr; -error: - return NULL; -} - -static -const char* parse_c_digit( - const char *ptr, - int64_t *value_out) -{ - char token[24]; - ptr = ecs_parse_eol_and_whitespace(ptr); - ptr = ecs_parse_digit(ptr, token); - if (!ptr) { + if (append_type(world, buf, entity, entity, desc)) { goto error; } - *value_out = strtol(token, NULL, 0); + json_object_pop(buf); - return ecs_parse_eol_and_whitespace(ptr); + return 0; error: - return NULL; + return -1; } -static -const char* parse_c_identifier( - const char *ptr, - char *buff, - char *params, - meta_parse_ctx_t *ctx) +char* ecs_entity_to_json( + const ecs_world_t *world, + ecs_entity_t entity, + const ecs_entity_to_json_desc_t *desc) { - ecs_assert(ptr != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(buff != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(ctx != NULL, ECS_INTERNAL_ERROR, NULL); - - char *bptr = buff, ch; + ecs_strbuf_t buf = ECS_STRBUF_INIT; - if (params) { - params[0] = '\0'; + if (ecs_entity_to_json_buf(world, entity, &buf, desc) != 0) { + ecs_strbuf_reset(&buf); + return NULL; } - /* Ignore whitespaces */ - ptr = ecs_parse_eol_and_whitespace(ptr); + return ecs_strbuf_get(&buf); +} - if (!isalpha(*ptr)) { - ecs_meta_error(ctx, ptr, - "invalid identifier (starts with '%c')", *ptr); - goto error; +static +bool skip_variable( + const char *name) +{ + if (!name || name[0] == '_' || name[0] == '.') { + return true; + } else { + return false; } +} - while ((ch = *ptr) && !isspace(ch) && ch != ';' && ch != ',' && ch != ')' && ch != '>') { - /* Type definitions can contain macro's or templates */ - if (ch == '(' || ch == '<') { - if (!params) { - ecs_meta_error(ctx, ptr, "unexpected %c", *ptr); - goto error; - } - - const char *end = skip_scope(ptr, ctx); - ecs_os_strncpy(params, ptr, (ecs_size_t)(end - ptr)); - params[end - ptr] = '\0'; +static +void serialize_id( + const ecs_world_t *world, + ecs_id_t id, + ecs_strbuf_t *buf) +{ + json_id(buf, world, id); +} - ptr = end; - } else { - *bptr = ch; - bptr ++; - ptr ++; - } +static +void serialize_iter_ids( + const ecs_world_t *world, + const ecs_iter_t *it, + ecs_strbuf_t *buf) +{ + int32_t term_count = it->term_count; + if (!term_count) { + return; } - *bptr = '\0'; + json_member(buf, "ids"); + json_array_push(buf); - if (!ch) { - ecs_meta_error(ctx, ptr, "unexpected end of token"); - goto error; + for (int i = 0; i < term_count; i ++) { + json_next(buf); + serialize_id(world, it->terms[i].id, buf); } - return ptr; -error: - return NULL; + json_array_pop(buf); } static -const char * meta_open_scope( - const char *ptr, - meta_parse_ctx_t *ctx) +void serialize_type_info( + const ecs_world_t *world, + const ecs_iter_t *it, + ecs_strbuf_t *buf) { - /* Skip initial whitespaces */ - ptr = ecs_parse_eol_and_whitespace(ptr); - - /* Is this the start of the type definition? */ - if (ctx->desc == ptr) { - if (*ptr != '{') { - ecs_meta_error(ctx, ptr, "missing '{' in struct definition"); - goto error; - } - - ptr ++; - ptr = ecs_parse_eol_and_whitespace(ptr); + int32_t term_count = it->term_count; + if (!term_count) { + return; } - /* Is this the end of the type definition? */ - if (!*ptr) { - ecs_meta_error(ctx, ptr, "missing '}' at end of struct definition"); - goto error; - } + json_member(buf, "type_info"); + json_object_push(buf); - /* Is this the end of the type definition? */ - if (*ptr == '}') { - ptr = ecs_parse_eol_and_whitespace(ptr + 1); - if (*ptr) { - ecs_meta_error(ctx, ptr, - "stray characters after struct definition"); - goto error; + for (int i = 0; i < term_count; i ++) { + json_next(buf); + ecs_entity_t typeid = ecs_get_typeid(world, it->terms[i].id); + if (typeid) { + serialize_id(world, typeid, buf); + ecs_strbuf_appendstr(buf, ":"); + ecs_type_info_to_json_buf(world, typeid, buf); + } else { + serialize_id(world, it->terms[i].id, buf); + ecs_strbuf_appendstr(buf, ":"); + ecs_strbuf_appendstr(buf, "0"); } - return NULL; } - return ptr; -error: - return NULL; + json_object_pop(buf); } static -const char* meta_parse_constant( - const char *ptr, - meta_constant_t *token, - meta_parse_ctx_t *ctx) -{ - ptr = meta_open_scope(ptr, ctx); - if (!ptr) { - return NULL; - } +void serialize_iter_variables(ecs_iter_t *it, ecs_strbuf_t *buf) { + char **variable_names = it->variable_names; + int32_t var_count = it->variable_count; + int32_t actual_count = 0; - token->is_value_set = false; + for (int i = 0; i < var_count; i ++) { + const char *var_name = variable_names[i]; + if (skip_variable(var_name)) continue; - /* Parse token, constant identifier */ - ptr = parse_c_identifier(ptr, token->name, NULL, ctx); - ptr = ecs_parse_eol_and_whitespace(ptr); + if (!actual_count) { + json_member(buf, "vars"); + json_array_push(buf); + actual_count ++; + } - /* Explicit value assignment */ - if (*ptr == '=') { - int64_t value = 0; - ptr = parse_c_digit(ptr + 1, &value); - token->value = value; - token->is_value_set = true; + ecs_strbuf_list_next(buf); + json_string(buf, var_name); } - /* Expect a ',' or '}' */ - if (*ptr != ',' && *ptr != '}') { - ecs_meta_error(ctx, ptr, "missing , after enum constant"); - goto error; + if (actual_count) { + json_array_pop(buf); } +} - if (*ptr == ',') { - return ptr + 1; - } else { - return ptr; +static +void serialize_iter_result_ids( + const ecs_world_t *world, + const ecs_iter_t *it, + ecs_strbuf_t *buf) +{ + json_member(buf, "ids"); + json_array_push(buf); + + for (int i = 0; i < it->term_count; i ++) { + json_next(buf); + serialize_id(world, ecs_term_id(it, i + 1), buf); } -error: - return NULL; + + json_array_pop(buf); } static -const char* meta_parse_type( - const char *ptr, - meta_type_t *token, - meta_parse_ctx_t *ctx) +void serialize_iter_result_subjects( + const ecs_world_t *world, + const ecs_iter_t *it, + ecs_strbuf_t *buf) { - token->is_ptr = false; - token->is_const = false; - - ptr = ecs_parse_eol_and_whitespace(ptr); + json_member(buf, "subjects"); + json_array_push(buf); - /* Parse token, expect type identifier or ECS_PROPERTY */ - ptr = parse_c_identifier(ptr, token->type, token->params, ctx); - if (!ptr) { - goto error; + for (int i = 0; i < it->term_count; i ++) { + json_next(buf); + ecs_entity_t subj = it->subjects[i]; + if (subj) { + json_path(buf, world, subj); + } else { + json_literal(buf, "0"); + } } - if (!strcmp(token->type, "ECS_PRIVATE")) { - /* Members from this point are not stored in metadata */ - ptr += ecs_os_strlen(ptr); - goto done; - } + json_array_pop(buf); +} - /* If token is const, set const flag and continue parsing type */ - if (!strcmp(token->type, "const")) { - token->is_const = true; +static +void serialize_iter_result_is_set( + const ecs_iter_t *it, + ecs_strbuf_t *buf) +{ + json_member(buf, "is_set"); + json_array_push(buf); - /* Parse type after const */ - ptr = parse_c_identifier(ptr + 1, token->type, token->params, ctx); + for (int i = 0; i < it->term_count; i ++) { + ecs_strbuf_list_next(buf); + if (ecs_term_is_set(it, i + 1)) { + json_true(buf); + } else { + json_false(buf); + } } - /* Check if type is a pointer */ - ptr = ecs_parse_eol_and_whitespace(ptr); - if (*ptr == '*') { - token->is_ptr = true; - ptr ++; + json_array_pop(buf); +} + +static +void serialize_iter_result_variables( + const ecs_world_t *world, + const ecs_iter_t *it, + ecs_strbuf_t *buf) +{ + char **variable_names = it->variable_names; + ecs_entity_t *variables = it->variables; + int32_t var_count = it->variable_count; + int32_t actual_count = 0; + + for (int i = 0; i < var_count; i ++) { + const char *var_name = variable_names[i]; + if (skip_variable(var_name)) continue; + + if (!actual_count) { + json_member(buf, "vars"); + json_array_push(buf); + actual_count ++; + } + + ecs_strbuf_list_next(buf); + json_path(buf, world, variables[i]); } -done: - return ptr; -error: - return NULL; + if (actual_count) { + json_array_pop(buf); + } } static -const char* meta_parse_member( - const char *ptr, - meta_member_t *token, - meta_parse_ctx_t *ctx) +void serialize_iter_result_entities( + const ecs_world_t *world, + const ecs_iter_t *it, + ecs_strbuf_t *buf) { - ptr = meta_open_scope(ptr, ctx); - if (!ptr) { - return NULL; + int32_t count = it->count; + if (!it->count) { + return; } - token->count = 1; - token->is_partial = false; + json_member(buf, "entities"); + json_array_push(buf); - /* Parse member type */ - ptr = meta_parse_type(ptr, &token->type, ctx); - if (!ptr) { - token->is_partial = true; - goto error; + ecs_entity_t *entities = it->entities; + + for (int i = 0; i < count; i ++) { + json_next(buf); + json_path(buf, world, entities[i]); } - /* Next token is the identifier */ - ptr = parse_c_identifier(ptr, token->name, NULL, ctx); - if (!ptr) { - goto error; + json_array_pop(buf); +} + +static +void serialize_iter_result_values( + const ecs_world_t *world, + const ecs_iter_t *it, + ecs_strbuf_t *buf) +{ + int32_t count = it->count; + if (!it->count) { + return; } - /* Skip whitespace between member and [ or ; */ - ptr = ecs_parse_eol_and_whitespace(ptr); + json_member(buf, "values"); + json_array_push(buf); - /* Check if this is an array */ - char *array_start = strchr(token->name, '['); - if (!array_start) { - /* If the [ was separated by a space, it will not be parsed as part of - * the name */ - if (*ptr == '[') { - array_start = (char*)ptr; /* safe, will not be modified */ + int32_t i, term_count = it->term_count; + for (i = 0; i < term_count; i ++) { + ecs_strbuf_list_next(buf); + + const void *ptr = it->ptrs[i]; + if (!ptr) { + /* No data in column */ + json_literal(buf, "0"); + continue; } - } - if (array_start) { - /* Check if the [ matches with a ] */ - char *array_end = strchr(array_start, ']'); - if (!array_end) { - ecs_meta_error(ctx, ptr, "missing ']'"); - goto error; + /* Get component id (can be different in case of pairs) */ + ecs_entity_t type = ecs_get_typeid(world, it->ids[i]); + if (!type) { + /* Odd, we have a ptr but no Component? Not the place of the + * serializer to complain about that. */ + json_literal(buf, "0"); + continue; + } - } else if (array_end - array_start == 0) { - ecs_meta_error(ctx, ptr, "dynamic size arrays are not supported"); - goto error; + const EcsComponent *comp = ecs_get(world, type, EcsComponent); + if (!comp) { + /* Also odd, typeid but not a component? */ + json_literal(buf, "0"); + continue; } - token->count = atoi(array_start + 1); + const EcsMetaTypeSerialized *ser = ecs_get( + world, type, EcsMetaTypeSerialized); + if (!ser) { + /* Not odd, component just has no reflection data */ + json_literal(buf, "0"); + continue; + } - if (array_start == ptr) { - /* If [ was found after name, continue parsing after ] */ - ptr = array_end + 1; + if (ecs_term_is_owned(it, i + 1)) { + array_to_json_buf_w_type_data(world, ptr, count, buf, comp, ser); } else { - /* If [ was fonud in name, replace it with 0 terminator */ - array_start[0] = '\0'; + array_to_json_buf_w_type_data(world, ptr, 0, buf, comp, ser); } } - /* Expect a ; */ - if (*ptr != ';') { - ecs_meta_error(ctx, ptr, "missing ; after member declaration"); - goto error; - } - - return ptr + 1; -error: - return NULL; + json_array_pop(buf); } static -int meta_parse_desc( - const char *ptr, - meta_params_t *token, - meta_parse_ctx_t *ctx) +void serialize_iter_result( + const ecs_world_t *world, + const ecs_iter_t *it, + ecs_strbuf_t *buf, + const ecs_iter_to_json_desc_t *desc) { - token->is_key_value = false; - token->is_fixed_size = false; + json_next(buf); + json_object_push(buf); - ptr = ecs_parse_eol_and_whitespace(ptr); - if (*ptr != '(' && *ptr != '<') { - ecs_meta_error(ctx, ptr, - "expected '(' at start of collection definition"); - goto error; + /* Each result can be matched with different component ids. Add them to + * the result so clients know with which component an entity was matched */ + if (!desc || desc->serialize_ids) { + serialize_iter_result_ids(world, it, buf); } - ptr ++; - - /* Parse type identifier */ - ptr = meta_parse_type(ptr, &token->type, ctx); - if (!ptr) { - goto error; + /* Include information on which entity the term is matched with */ + if (!desc || desc->serialize_ids) { + serialize_iter_result_subjects(world, it, buf); } - ptr = ecs_parse_eol_and_whitespace(ptr); - - /* If next token is a ',' the first type was a key type */ - if (*ptr == ',') { - ptr = ecs_parse_eol_and_whitespace(ptr + 1); - - if (isdigit(*ptr)) { - int64_t value; - ptr = parse_c_digit(ptr, &value); - if (!ptr) { - goto error; - } - - token->count = value; - token->is_fixed_size = true; - } else { - token->key_type = token->type; + /* Write variable values for current result */ + if (!desc || desc->serialize_variables) { + serialize_iter_result_variables(world, it, buf); + } - /* Parse element type */ - ptr = meta_parse_type(ptr, &token->type, ctx); - ptr = ecs_parse_eol_and_whitespace(ptr); + /* Include information on which terms are set, to support optional terms */ + if (!desc || desc->serialize_is_set) { + serialize_iter_result_is_set(it, buf); + } - token->is_key_value = true; - } + /* Write entity ids for current result (for queries with This terms) */ + if (!desc || desc->serialize_entities) { + serialize_iter_result_entities(world, it, buf); } - if (*ptr != ')' && *ptr != '>') { - ecs_meta_error(ctx, ptr, - "expected ')' at end of collection definition"); - goto error; + /* Serialize component values */ + if (!desc || desc->serialize_values) { + serialize_iter_result_values(world, it, buf); } - return 0; -error: - return -1; + json_object_pop(buf); } -static -ecs_entity_t meta_lookup( - ecs_world_t *world, - meta_type_t *token, - const char *ptr, - int64_t count, - meta_parse_ctx_t *ctx); - -static -ecs_entity_t meta_lookup_array( - ecs_world_t *world, - ecs_entity_t e, - const char *params_decl, - meta_parse_ctx_t *ctx) +int ecs_iter_to_json_buf( + const ecs_world_t *world, + ecs_iter_t *it, + ecs_strbuf_t *buf, + const ecs_iter_to_json_desc_t *desc) { - meta_parse_ctx_t param_ctx = { - .name = ctx->name, - .desc = params_decl - }; - - meta_params_t params; - if (meta_parse_desc(params_decl, ¶ms, ¶m_ctx)) { - goto error; + ecs_time_t duration = {0}; + if (desc && desc->measure_eval_duration) { + ecs_time_measure(&duration); } - if (!params.is_fixed_size) { - ecs_meta_error(ctx, params_decl, "missing size for array"); - goto error; + + json_object_push(buf); + + /* Serialize component ids of the terms (usually provided by query) */ + if (!desc || desc->serialize_term_ids) { + serialize_iter_ids(world, it, buf); } - if (!params.count) { - ecs_meta_error(ctx, params_decl, "invalid array size"); - goto error; + /* Serialize type info if enabled */ + if (desc && desc->serialize_type_info) { + serialize_type_info(world, it, buf); } - ecs_entity_t element_type = ecs_lookup_symbol(world, params.type.type, true); - if (!element_type) { - ecs_meta_error(ctx, params_decl, "unknown element type '%s'", - params.type.type); + /* Serialize variable names, if iterator has any */ + serialize_iter_variables(it, buf); + + /* Serialize results */ + json_member(buf, "results"); + json_array_push(buf); + + /* Use instancing for improved performance */ + it->is_instanced = true; + + ecs_iter_next_action_t next = it->next; + while (next(it)) { + serialize_iter_result(world, it, buf, desc); } - if (!e) { - e = ecs_set(world, 0, EcsMetaType, { EcsArrayType }); + json_array_pop(buf); + + if (desc && desc->measure_eval_duration) { + double dt = ecs_time_measure(&duration); + json_member(buf, "eval_duration"); + json_number(buf, dt); } - ecs_check(params.count <= INT32_MAX, ECS_INVALID_PARAMETER, NULL); + json_object_pop(buf); - return ecs_set(world, e, EcsArray, { element_type, (int32_t)params.count }); -error: return 0; } -static -ecs_entity_t meta_lookup_vector( - ecs_world_t *world, - ecs_entity_t e, - const char *params_decl, - meta_parse_ctx_t *ctx) +char* ecs_iter_to_json( + const ecs_world_t *world, + ecs_iter_t *it, + const ecs_iter_to_json_desc_t *desc) { - meta_parse_ctx_t param_ctx = { - .name = ctx->name, - .desc = params_decl - }; + ecs_strbuf_t buf = ECS_STRBUF_INIT; - meta_params_t params; - if (meta_parse_desc(params_decl, ¶ms, ¶m_ctx)) { - goto error; + if (ecs_iter_to_json_buf(world, it, &buf, desc)) { + ecs_strbuf_reset(&buf); + return NULL; } - if (params.is_key_value) { - ecs_meta_error(ctx, params_decl, - "unexpected key value parameters for vector"); - goto error; - } + return ecs_strbuf_get(&buf); +} - ecs_entity_t element_type = meta_lookup( - world, ¶ms.type, params_decl, 1, ¶m_ctx); +#endif - if (!e) { - e = ecs_set(world, 0, EcsMetaType, {EcsVectorType}); - } - return ecs_set(world, e, EcsVector, { element_type }); -error: - return 0; -} +#ifdef FLECS_JSON -static -ecs_entity_t meta_lookup_bitmask( - ecs_world_t *world, - ecs_entity_t e, - const char *params_decl, - meta_parse_ctx_t *ctx) +const char* ecs_parse_json( + const ecs_world_t *world, + const char *ptr, + ecs_entity_t type, + void *data_out, + const ecs_parse_json_desc_t *desc) { - (void)e; + char token[ECS_MAX_TOKEN_SIZE]; + int depth = 0; - meta_parse_ctx_t param_ctx = { - .name = ctx->name, - .desc = params_decl - }; + const char *name = NULL; + const char *expr = NULL; - meta_params_t params; - if (meta_parse_desc(params_decl, ¶ms, ¶m_ctx)) { - goto error; - } + ptr = ecs_parse_fluff(ptr, NULL); - if (params.is_key_value) { - ecs_meta_error(ctx, params_decl, - "unexpected key value parameters for bitmask"); - goto error; + ecs_meta_cursor_t cur = ecs_meta_cursor(world, type, data_out); + if (cur.valid == false) { + return NULL; } - if (params.is_fixed_size) { - ecs_meta_error(ctx, params_decl, - "unexpected size for bitmask"); - goto error; + if (desc) { + name = desc->name; + expr = desc->expr; } - ecs_entity_t bitmask_type = meta_lookup( - world, ¶ms.type, params_decl, 1, ¶m_ctx); - ecs_check(bitmask_type != 0, ECS_INVALID_PARAMETER, NULL); + while ((ptr = ecs_parse_expr_token(name, expr, ptr, token))) { -#ifndef NDEBUG - /* Make sure this is a bitmask type */ - const EcsMetaType *type_ptr = ecs_get(world, bitmask_type, EcsMetaType); - ecs_check(type_ptr != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(type_ptr->kind == EcsBitmaskType, ECS_INVALID_PARAMETER, NULL); -#endif + ptr = ecs_parse_fluff(ptr, NULL); - return bitmask_type; -error: - return 0; -} + if (!ecs_os_strcmp(token, "{")) { + depth ++; + if (ecs_meta_push(&cur) != 0) { + goto error; + } -static -ecs_entity_t meta_lookup( - ecs_world_t *world, - meta_type_t *token, - const char *ptr, - int64_t count, - meta_parse_ctx_t *ctx) -{ - ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(token != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(ptr != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(ctx != NULL, ECS_INTERNAL_ERROR, NULL); + if (ecs_meta_is_collection(&cur)) { + ecs_parser_error(name, expr, ptr - expr, "expected '['"); + return NULL; + } + } - const char *typename = token->type; - ecs_entity_t type = 0; + else if (!ecs_os_strcmp(token, "}")) { + depth --; - /* Parse vector type */ - if (!token->is_ptr) { - if (!ecs_os_strcmp(typename, "ecs_array")) { - type = meta_lookup_array(world, 0, token->params, ctx); + if (ecs_meta_is_collection(&cur)) { + ecs_parser_error(name, expr, ptr - expr, "expected ']'"); + return NULL; + } - } else if (!ecs_os_strcmp(typename, "ecs_vector") || - !ecs_os_strcmp(typename, "flecs::vector")) - { - type = meta_lookup_vector(world, 0, token->params, ctx); + if (ecs_meta_pop(&cur) != 0) { + goto error; + } + } - } else if (!ecs_os_strcmp(typename, "flecs::bitmask")) { - type = meta_lookup_bitmask(world, 0, token->params, ctx); + else if (!ecs_os_strcmp(token, "[")) { + depth ++; + if (ecs_meta_push(&cur) != 0) { + goto error; + } - } else if (!ecs_os_strcmp(typename, "flecs::byte")) { - type = ecs_id(ecs_byte_t); + if (!ecs_meta_is_collection(&cur)) { + ecs_parser_error(name, expr, ptr - expr, "expected '{'"); + return NULL; + } + } - } else if (!ecs_os_strcmp(typename, "char")) { - type = ecs_id(ecs_char_t); + else if (!ecs_os_strcmp(token, "]")) { + depth --; - } else if (!ecs_os_strcmp(typename, "bool") || - !ecs_os_strcmp(typename, "_Bool")) - { - type = ecs_id(ecs_bool_t); + if (!ecs_meta_is_collection(&cur)) { + ecs_parser_error(name, expr, ptr - expr, "expected '}'"); + return NULL; + } - } else if (!ecs_os_strcmp(typename, "int8_t")) { - type = ecs_id(ecs_i8_t); - } else if (!ecs_os_strcmp(typename, "int16_t")) { - type = ecs_id(ecs_i16_t); - } else if (!ecs_os_strcmp(typename, "int32_t")) { - type = ecs_id(ecs_i32_t); - } else if (!ecs_os_strcmp(typename, "int64_t")) { - type = ecs_id(ecs_i64_t); + if (ecs_meta_pop(&cur) != 0) { + goto error; + } + } - } else if (!ecs_os_strcmp(typename, "uint8_t")) { - type = ecs_id(ecs_u8_t); - } else if (!ecs_os_strcmp(typename, "uint16_t")) { - type = ecs_id(ecs_u16_t); - } else if (!ecs_os_strcmp(typename, "uint32_t")) { - type = ecs_id(ecs_u32_t); - } else if (!ecs_os_strcmp(typename, "uint64_t")) { - type = ecs_id(ecs_u64_t); + else if (!ecs_os_strcmp(token, ",")) { + if (ecs_meta_next(&cur) != 0) { + goto error; + } + } - } else if (!ecs_os_strcmp(typename, "float")) { - type = ecs_id(ecs_f32_t); - } else if (!ecs_os_strcmp(typename, "double")) { - type = ecs_id(ecs_f64_t); + else if (!ecs_os_strcmp(token, "null")) { + if (ecs_meta_set_null(&cur) != 0) { + goto error; + } + } - } else if (!ecs_os_strcmp(typename, "ecs_entity_t")) { - type = ecs_id(ecs_entity_t); + else if (token[0] == '\"') { + if (ptr[0] == ':') { + /* Member assignment */ + ptr ++; - } else if (!ecs_os_strcmp(typename, "char*")) { - type = ecs_id(ecs_string_t); - } else { - type = ecs_lookup_symbol(world, typename, true); + /* Strip trailing " */ + ecs_size_t len = ecs_os_strlen(token); + if (token[len - 1] != '"') { + ecs_parser_error(name, expr, ptr - expr, "expected \""); + return NULL; + } else { + token[len - 1] = '\0'; + } + + if (ecs_meta_member(&cur, token + 1) != 0) { + goto error; + } + } else { + if (ecs_meta_set_string_literal(&cur, token) != 0) { + goto error; + } + } } - } else { - if (!ecs_os_strcmp(typename, "char")) { - typename = "flecs.meta.string"; - } else - if (token->is_ptr) { - typename = "flecs.meta.uptr"; - } else - if (!ecs_os_strcmp(typename, "char*") || - !ecs_os_strcmp(typename, "flecs::string")) - { - typename = "flecs.meta.string"; + + else { + if (ecs_meta_set_string(&cur, token) != 0) { + goto error; + } } - type = ecs_lookup_symbol(world, typename, true); + if (!depth) { + break; + } } - if (count != 1) { - ecs_check(count <= INT32_MAX, ECS_INVALID_PARAMETER, NULL); + return ptr; +error: + return NULL; +} - type = ecs_set(world, ecs_set(world, 0, - EcsMetaType, {EcsArrayType}), - EcsArray, {type, (int32_t)count}); - } +#endif - if (!type) { - ecs_meta_error(ctx, ptr, "unknown type '%s'", typename); - goto error; - } +#ifdef FLECS_JSON - return type; -error: - return 0; +void json_next( + ecs_strbuf_t *buf) +{ + ecs_strbuf_list_next(buf); } -static -int meta_parse_struct( - ecs_world_t *world, - ecs_entity_t t, - const char *desc) +void json_literal( + ecs_strbuf_t *buf, + const char *value) +{ + ecs_strbuf_appendstr(buf, value); +} + +void json_number( + ecs_strbuf_t *buf, + double value) +{ + ecs_strbuf_appendflt(buf, value); +} + +void json_true( + ecs_strbuf_t *buf) +{ + json_literal(buf, "true"); +} + +void json_false( + ecs_strbuf_t *buf) +{ + json_literal(buf, "false"); +} + +void json_array_push( + ecs_strbuf_t *buf) +{ + ecs_strbuf_list_push(buf, "[", ", "); +} + +void json_array_pop( + ecs_strbuf_t *buf) +{ + ecs_strbuf_list_pop(buf, "]"); +} + +void json_object_push( + ecs_strbuf_t *buf) +{ + ecs_strbuf_list_push(buf, "{", ", "); +} + +void json_object_pop( + ecs_strbuf_t *buf) +{ + ecs_strbuf_list_pop(buf, "}"); +} + +void json_string( + ecs_strbuf_t *buf, + const char *value) +{ + ecs_strbuf_appendstr(buf, "\""); + ecs_strbuf_appendstr(buf, value); + ecs_strbuf_appendstr(buf, "\""); +} + +void json_member( + ecs_strbuf_t *buf, + const char *name) +{ + ecs_strbuf_list_appendstr(buf, "\""); + ecs_strbuf_appendstr(buf, name); + ecs_strbuf_appendstr(buf, "\":"); +} + +void json_path( + ecs_strbuf_t *buf, + const ecs_world_t *world, + ecs_entity_t e) +{ + ecs_strbuf_appendch(buf, '"'); + ecs_get_fullpath_buf(world, e, buf); + ecs_strbuf_appendch(buf, '"'); +} + +void json_id( + ecs_strbuf_t *buf, + const ecs_world_t *world, + ecs_id_t id) +{ + ecs_strbuf_appendch(buf, '"'); + ecs_id_str_buf(world, id, buf); + ecs_strbuf_appendch(buf, '"'); +} + +ecs_primitive_kind_t json_op_to_primitive_kind( + ecs_meta_type_op_kind_t kind) { - const char *ptr = desc; - const char *name = ecs_get_name(world, t); + return kind - EcsOpPrimitive; +} - meta_member_t token; - meta_parse_ctx_t ctx = { - .name = name, - .desc = ptr - }; +#endif - ecs_entity_t old_scope = ecs_set_scope(world, t); +#ifdef FLECS_JSON - while ((ptr = meta_parse_member(ptr, &token, &ctx)) && ptr[0]) { - ecs_entity_t m = ecs_entity_init(world, &(ecs_entity_desc_t) { - .name = token.name - }); +static +int json_typeinfo_ser_type( + const ecs_world_t *world, + ecs_entity_t type, + ecs_strbuf_t *buf); - ecs_entity_t type = meta_lookup( - world, &token.type, ptr, 1, &ctx); - if (!type) { - goto error; - } +static +int json_typeinfo_ser_primitive( + ecs_primitive_kind_t kind, + ecs_strbuf_t *str) +{ + json_array_push(str); - ecs_set(world, m, EcsMember, { - .type = type, - .count = (ecs_size_t)token.count - }); + switch(kind) { + case EcsBool: + json_string(str, "bool"); + break; + case EcsChar: + case EcsString: + json_string(str, "text"); + break; + case EcsByte: + json_string(str, "byte"); + break; + case EcsU8: + case EcsU16: + case EcsU32: + case EcsU64: + case EcsI8: + case EcsI16: + case EcsI32: + case EcsI64: + case EcsIPtr: + case EcsUPtr: + json_string(str, "int"); + break; + case EcsF32: + case EcsF64: + json_string(str, "float"); + break; + case EcsEntity: + json_string(str, "entity"); + break; + default: + return -1; } - - ecs_set_scope(world, old_scope); + json_array_pop(str); return 0; -error: - return -1; } static -int meta_parse_constants( - ecs_world_t *world, - ecs_entity_t t, - const char *desc, - bool is_bitmask) +void json_typeinfo_ser_constants( + const ecs_world_t *world, + ecs_entity_t type, + ecs_strbuf_t *str) { - ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(t != 0, ECS_INTERNAL_ERROR, NULL); - ecs_assert(desc != NULL, ECS_INTERNAL_ERROR, NULL); - - const char *ptr = desc; - const char *name = ecs_get_name(world, t); - - meta_parse_ctx_t ctx = { - .name = name, - .desc = ptr - }; - - meta_constant_t token; - int64_t last_value = 0; - - ecs_entity_t old_scope = ecs_set_scope(world, t); + ecs_iter_t it = ecs_term_iter(world, &(ecs_term_t) { + .id = ecs_pair(EcsChildOf, type) + }); - while ((ptr = meta_parse_constant(ptr, &token, &ctx))) { - if (token.is_value_set) { - last_value = token.value; - } else if (is_bitmask) { - ecs_meta_error(&ctx, ptr, - "bitmask requires explicit value assignment"); - goto error; + while (ecs_term_next(&it)) { + int32_t i, count = it.count; + for (i = 0; i < count; i ++) { + json_next(str); + json_string(str, ecs_get_name(world, it.entities[i])); } + } +} - ecs_entity_t c = ecs_entity_init(world, &(ecs_entity_desc_t) { - .name = token.name - }); +static +void json_typeinfo_ser_enum( + const ecs_world_t *world, + ecs_entity_t type, + ecs_strbuf_t *str) +{ + json_array_push(str); + ecs_strbuf_list_appendstr(str, "\"enum\""); + json_typeinfo_ser_constants(world, type, str); + json_array_pop(str); +} - if (!is_bitmask) { - ecs_set_pair_object(world, c, EcsConstant, ecs_i32_t, - {(ecs_i32_t)last_value}); - } else { - ecs_set_pair_object(world, c, EcsConstant, ecs_u32_t, - {(ecs_u32_t)last_value}); - } +static +void json_typeinfo_ser_bitmask( + const ecs_world_t *world, + ecs_entity_t type, + ecs_strbuf_t *str) +{ + json_array_push(str); + ecs_strbuf_list_appendstr(str, "\"bitmask\""); + json_typeinfo_ser_constants(world, type, str); + json_array_pop(str); +} - last_value ++; +static +int json_typeinfo_ser_array( + const ecs_world_t *world, + ecs_entity_t elem_type, + int32_t count, + ecs_strbuf_t *str) +{ + json_array_push(str); + ecs_strbuf_list_appendstr(str, "\"array\""); + if (json_typeinfo_ser_type(world, elem_type, str)) { + goto error; } - ecs_set_scope(world, old_scope); + ecs_strbuf_list_append(str, "%u", count); + json_array_pop(str); return 0; error: return -1; } static -int meta_parse_enum( - ecs_world_t *world, - ecs_entity_t t, - const char *desc) +int json_typeinfo_ser_array_type( + const ecs_world_t *world, + ecs_entity_t type, + ecs_strbuf_t *str) { - ecs_add(world, t, EcsEnum); - return meta_parse_constants(world, t, desc, false); + const EcsArray *arr = ecs_get(world, type, EcsArray); + ecs_assert(arr != NULL, ECS_INTERNAL_ERROR, NULL); + if (json_typeinfo_ser_array(world, arr->type, arr->count, str)) { + goto error; + } + return 0; +error: + return -1; } static -int meta_parse_bitmask( - ecs_world_t *world, - ecs_entity_t t, - const char *desc) +int json_typeinfo_ser_vector( + const ecs_world_t *world, + ecs_entity_t type, + ecs_strbuf_t *str) { - ecs_add(world, t, EcsBitmask); - return meta_parse_constants(world, t, desc, true); + const EcsVector *arr = ecs_get(world, type, EcsVector); + ecs_assert(arr != NULL, ECS_INTERNAL_ERROR, NULL); + + json_array_push(str); + ecs_strbuf_list_appendstr(str, "\"vector\""); + if (json_typeinfo_ser_type(world, arr->type, str)) { + goto error; + } + + json_array_pop(str); + return 0; +error: + return -1; } -int ecs_meta_from_desc( - ecs_world_t *world, - ecs_entity_t component, - ecs_type_kind_t kind, - const char *desc) +/* Forward serialization to the different type kinds */ +static +int json_typeinfo_ser_type_op( + const ecs_world_t *world, + ecs_meta_type_op_t *op, + ecs_strbuf_t *str) { - switch(kind) { - case EcsStructType: - if (meta_parse_struct(world, component, desc)) { - goto error; - } + switch(op->kind) { + case EcsOpPush: + case EcsOpPop: + /* Should not be parsed as single op */ + ecs_throw(ECS_INVALID_PARAMETER, NULL); break; - case EcsEnumType: - if (meta_parse_enum(world, component, desc)) { - goto error; - } + case EcsOpEnum: + json_typeinfo_ser_enum(world, op->type, str); break; - case EcsBitmaskType: - if (meta_parse_bitmask(world, component, desc)) { - goto error; - } + case EcsOpBitmask: + json_typeinfo_ser_bitmask(world, op->type, str); + break; + case EcsOpArray: + json_typeinfo_ser_array_type(world, op->type, str); + break; + case EcsOpVector: + json_typeinfo_ser_vector(world, op->type, str); break; default: + if (json_typeinfo_ser_primitive( + json_op_to_primitive_kind(op->kind), str)) + { + /* Unknown operation */ + ecs_throw(ECS_INTERNAL_ERROR, NULL); + return -1; + } break; } @@ -29264,12304 +29300,12772 @@ int ecs_meta_from_desc( return -1; } -#endif - - -#ifdef FLECS_APP - +/* Iterate over a slice of the type ops array */ static -int default_run_action( - ecs_world_t *world, - ecs_app_desc_t *desc) +int json_typeinfo_ser_type_ops( + const ecs_world_t *world, + ecs_meta_type_op_t *ops, + int32_t op_count, + ecs_strbuf_t *str) { - if (desc->init) { - desc->init(world); + for (int i = 0; i < op_count; i ++) { + ecs_meta_type_op_t *op = &ops[i]; + + if (op != ops) { + if (op->name) { + json_member(str, op->name); + } + + int32_t elem_count = op->count; + if (elem_count > 1 && op != ops) { + json_typeinfo_ser_array(world, op->type, op->count, str); + i += op->op_count - 1; + continue; + } + } + + switch(op->kind) { + case EcsOpPush: + json_object_push(str); + break; + case EcsOpPop: + json_object_pop(str); + break; + default: + if (json_typeinfo_ser_type_op(world, op, str)) { + goto error; + } + break; + } } - int result; - while ((result = ecs_app_run_frame(world, desc)) == 0) { } - return result; + return 0; +error: + return -1; } static -int default_frame_action( - ecs_world_t *world, - const ecs_app_desc_t *desc) -{ - return !ecs_progress(world, desc->delta_time); -} - -static ecs_app_run_action_t run_action = default_run_action; -static ecs_app_frame_action_t frame_action = default_frame_action; - -int ecs_app_run( - ecs_world_t *world, - ecs_app_desc_t *desc) +int json_typeinfo_ser_type( + const ecs_world_t *world, + ecs_entity_t type, + ecs_strbuf_t *buf) { - /* Don't set FPS & threads if custom run action is set, as the platform on - * which the app is running may not support it. */ - if (!run_action) { - ecs_set_target_fps(world, desc->target_fps); - ecs_set_threads(world, desc->threads); + const EcsComponent *comp = ecs_get(world, type, EcsComponent); + if (!comp) { + ecs_strbuf_appendstr(buf, "0"); + return 0; } - /* REST server enables connecting to app with explorer */ - if (desc->enable_rest) { -#ifdef FLECS_REST - ecs_set(world, EcsWorld, EcsRest, {.port = 0}); -#else - ecs_warn("cannot enable remote API, REST addon not available"); -#endif + const EcsMetaTypeSerialized *ser = ecs_get( + world, type, EcsMetaTypeSerialized); + if (!ser) { + ecs_strbuf_appendstr(buf, "0"); + return 0; } - return run_action(world, desc); -} + ecs_meta_type_op_t *ops = ecs_vector_first(ser->ops, ecs_meta_type_op_t); + int32_t count = ecs_vector_count(ser->ops); -int ecs_app_run_frame( - ecs_world_t *world, - const ecs_app_desc_t *desc) -{ - return frame_action(world, desc); + return json_typeinfo_ser_type_ops(world, ops, count, buf); } -int ecs_app_set_run_action( - ecs_app_run_action_t callback) +int ecs_type_info_to_json_buf( + const ecs_world_t *world, + ecs_entity_t type, + ecs_strbuf_t *buf) { - if (run_action != default_run_action) { - ecs_err("run action already set"); - return -1; - } - - run_action = callback; - - return 0; + return json_typeinfo_ser_type(world, type, buf); } -int ecs_app_set_frame_action( - ecs_app_frame_action_t callback) +char* ecs_type_info_to_json( + const ecs_world_t *world, + ecs_entity_t type) { - if (frame_action != default_frame_action) { - ecs_err("frame action already set"); - return -1; - } + ecs_strbuf_t str = ECS_STRBUF_INIT; - frame_action = callback; + if (ecs_type_info_to_json_buf(world, type, &str) != 0) { + ecs_strbuf_reset(&str); + return NULL; + } - return 0; + return ecs_strbuf_get(&str); } #endif -/* Roles */ -const ecs_id_t ECS_CASE = (ECS_ROLE | (0x7Cull << 56)); -const ecs_id_t ECS_SWITCH = (ECS_ROLE | (0x7Bull << 56)); -const ecs_id_t ECS_PAIR = (ECS_ROLE | (0x7Aull << 56)); -const ecs_id_t ECS_OVERRIDE = (ECS_ROLE | (0x75ull << 56)); -const ecs_id_t ECS_DISABLED = (ECS_ROLE | (0x74ull << 56)); - -/** Builtin component ids */ -const ecs_entity_t ecs_id(EcsComponent) = 1; -const ecs_entity_t ecs_id(EcsComponentLifecycle) = 2; -const ecs_entity_t ecs_id(EcsType) = 3; -const ecs_entity_t ecs_id(EcsIdentifier) = 4; -const ecs_entity_t ecs_id(EcsTrigger) = 5; -const ecs_entity_t ecs_id(EcsQuery) = 6; -const ecs_entity_t ecs_id(EcsObserver) = 7; -const ecs_entity_t ecs_id(EcsIterable) = 8; +#ifdef FLECS_SYSTEM +#endif -/* System module component ids */ -const ecs_entity_t ecs_id(EcsSystem) = 10; -const ecs_entity_t ecs_id(EcsTickSource) = 11; +#ifdef FLECS_PIPELINE +#ifndef FLECS_PIPELINE_PRIVATE_H +#define FLECS_PIPELINE_PRIVATE_H -/** Pipeline module component ids */ -const ecs_entity_t ecs_id(EcsPipelineQuery) = 12; -/** Timer module component ids */ -const ecs_entity_t ecs_id(EcsTimer) = 13; -const ecs_entity_t ecs_id(EcsRateFilter) = 14; +/** Instruction data for pipeline. + * This type is the element type in the "ops" vector of a pipeline and contains + * information about the set of systems that need to be ran before a merge. */ +typedef struct ecs_pipeline_op_t { + int32_t count; /* Number of systems to run before merge */ + bool multi_threaded; /* Whether systems can be ran multi threaded */ + bool no_staging; /* Whether systems are staged or not */ +} ecs_pipeline_op_t; -/** Meta module component ids */ -const ecs_entity_t ecs_id(EcsMetaType) = 15; -const ecs_entity_t ecs_id(EcsMetaTypeSerialized) = 16; -const ecs_entity_t ecs_id(EcsPrimitive) = 17; -const ecs_entity_t ecs_id(EcsEnum) = 18; -const ecs_entity_t ecs_id(EcsBitmask) = 19; -const ecs_entity_t ecs_id(EcsMember) = 20; -const ecs_entity_t ecs_id(EcsStruct) = 21; -const ecs_entity_t ecs_id(EcsArray) = 22; -const ecs_entity_t ecs_id(EcsVector) = 23; +typedef struct EcsPipelineQuery { + ecs_query_t *query; + ecs_query_t *build_query; + ecs_vector_t *ops; + int32_t match_count; + int32_t rebuild_count; + ecs_entity_t last_system; +} EcsPipelineQuery; -/* Core scopes & entities */ -const ecs_entity_t EcsWorld = ECS_HI_COMPONENT_ID + 0; -const ecs_entity_t EcsFlecs = ECS_HI_COMPONENT_ID + 1; -const ecs_entity_t EcsFlecsCore = ECS_HI_COMPONENT_ID + 2; -const ecs_entity_t EcsModule = ECS_HI_COMPONENT_ID + 3; -const ecs_entity_t EcsPrefab = ECS_HI_COMPONENT_ID + 4; -const ecs_entity_t EcsDisabled = ECS_HI_COMPONENT_ID + 5; +//////////////////////////////////////////////////////////////////////////////// +//// Pipeline API +//////////////////////////////////////////////////////////////////////////////// -/* Relation properties */ -const ecs_entity_t EcsWildcard = ECS_HI_COMPONENT_ID + 10; -const ecs_entity_t EcsThis = ECS_HI_COMPONENT_ID + 11; -const ecs_entity_t EcsTransitive = ECS_HI_COMPONENT_ID + 12; -const ecs_entity_t EcsTransitiveSelf = ECS_HI_COMPONENT_ID + 13; -const ecs_entity_t EcsFinal = ECS_HI_COMPONENT_ID + 14; -const ecs_entity_t EcsTag = ECS_HI_COMPONENT_ID + 15; -const ecs_entity_t EcsExclusive = ECS_HI_COMPONENT_ID + 16; -const ecs_entity_t EcsAcyclic = ECS_HI_COMPONENT_ID + 17; +/** Update a pipeline (internal function). + * Before running a pipeline, it must be updated. During this update phase + * all systems in the pipeline are collected, ordered and sync points are + * inserted where necessary. This operation may only be called when staging is + * disabled. + * + * Because multiple threads may run a pipeline, preparing the pipeline must + * happen synchronously, which is why this function is separate from + * ecs_run_pipeline. Not running the prepare step may cause systems to not get + * ran, or ran in the wrong order. + * + * If 0 is provided for the pipeline id, the default pipeline will be ran (this + * is either the builtin pipeline or the pipeline set with set_pipeline()). + * + * @param world The world. + * @param pipeline The pipeline to run. + * @return The number of elements in the pipeline. + */ +bool ecs_pipeline_update( + ecs_world_t *world, + ecs_entity_t pipeline, + bool start_of_frame); -/* Builtin relations */ -const ecs_entity_t EcsChildOf = ECS_HI_COMPONENT_ID + 25; -const ecs_entity_t EcsIsA = ECS_HI_COMPONENT_ID + 26; +int32_t ecs_pipeline_reset_iter( + ecs_world_t *world, + const EcsPipelineQuery *pq, + ecs_iter_t *iter_out, + ecs_pipeline_op_t **op_out, + ecs_pipeline_op_t **last_op_out); -/* Identifier tags */ -const ecs_entity_t EcsName = ECS_HI_COMPONENT_ID + 27; -const ecs_entity_t EcsSymbol = ECS_HI_COMPONENT_ID + 28; +//////////////////////////////////////////////////////////////////////////////// +//// Worker API +//////////////////////////////////////////////////////////////////////////////// -/* Events */ -const ecs_entity_t EcsOnAdd = ECS_HI_COMPONENT_ID + 30; -const ecs_entity_t EcsOnRemove = ECS_HI_COMPONENT_ID + 31; -const ecs_entity_t EcsOnSet = ECS_HI_COMPONENT_ID + 32; -const ecs_entity_t EcsUnSet = ECS_HI_COMPONENT_ID + 33; -const ecs_entity_t EcsOnDelete = ECS_HI_COMPONENT_ID + 34; -const ecs_entity_t EcsOnCreateTable = ECS_HI_COMPONENT_ID + 35; -const ecs_entity_t EcsOnDeleteTable = ECS_HI_COMPONENT_ID + 36; -const ecs_entity_t EcsOnTableEmpty = ECS_HI_COMPONENT_ID + 37; -const ecs_entity_t EcsOnTableFilled = ECS_HI_COMPONENT_ID + 38; -const ecs_entity_t EcsOnCreateTrigger = ECS_HI_COMPONENT_ID + 39; -const ecs_entity_t EcsOnDeleteTrigger = ECS_HI_COMPONENT_ID + 40; -const ecs_entity_t EcsOnDeleteObservable = ECS_HI_COMPONENT_ID + 41; -const ecs_entity_t EcsOnComponentLifecycle = ECS_HI_COMPONENT_ID + 42; -const ecs_entity_t EcsOnDeleteObject = ECS_HI_COMPONENT_ID + 43; +void ecs_worker_begin( + ecs_world_t *world); -/* Actions */ -const ecs_entity_t EcsRemove = ECS_HI_COMPONENT_ID + 50; -const ecs_entity_t EcsDelete = ECS_HI_COMPONENT_ID + 51; -const ecs_entity_t EcsThrow = ECS_HI_COMPONENT_ID + 52; +int32_t ecs_worker_sync( + ecs_world_t *world, + const EcsPipelineQuery *pq, + ecs_iter_t *it, + int32_t i, + ecs_pipeline_op_t **op_out, + ecs_pipeline_op_t **last_op_out); -/* Misc */ -const ecs_entity_t EcsDefaultChildComponent = ECS_HI_COMPONENT_ID + 55; +void ecs_worker_end( + ecs_world_t *world); -/* Systems */ -const ecs_entity_t EcsMonitor = ECS_HI_COMPONENT_ID + 61; -const ecs_entity_t EcsInactive = ECS_HI_COMPONENT_ID + 63; -const ecs_entity_t EcsPipeline = ECS_HI_COMPONENT_ID + 64; -const ecs_entity_t EcsPreFrame = ECS_HI_COMPONENT_ID + 65; -const ecs_entity_t EcsOnLoad = ECS_HI_COMPONENT_ID + 66; -const ecs_entity_t EcsPostLoad = ECS_HI_COMPONENT_ID + 67; -const ecs_entity_t EcsPreUpdate = ECS_HI_COMPONENT_ID + 68; -const ecs_entity_t EcsOnUpdate = ECS_HI_COMPONENT_ID + 69; -const ecs_entity_t EcsOnValidate = ECS_HI_COMPONENT_ID + 70; -const ecs_entity_t EcsPostUpdate = ECS_HI_COMPONENT_ID + 71; -const ecs_entity_t EcsPreStore = ECS_HI_COMPONENT_ID + 72; -const ecs_entity_t EcsOnStore = ECS_HI_COMPONENT_ID + 73; -const ecs_entity_t EcsPostFrame = ECS_HI_COMPONENT_ID + 74; +void ecs_workers_progress( + ecs_world_t *world, + ecs_entity_t pipeline, + FLECS_FLOAT delta_time); -/* Meta primitive components (don't use low ids to save id space) */ -const ecs_entity_t EcsConstant = ECS_HI_COMPONENT_ID + 80; -const ecs_entity_t ecs_id(ecs_bool_t) = ECS_HI_COMPONENT_ID + 81; -const ecs_entity_t ecs_id(ecs_char_t) = ECS_HI_COMPONENT_ID + 82; -const ecs_entity_t ecs_id(ecs_byte_t) = ECS_HI_COMPONENT_ID + 83; -const ecs_entity_t ecs_id(ecs_u8_t) = ECS_HI_COMPONENT_ID + 84; -const ecs_entity_t ecs_id(ecs_u16_t) = ECS_HI_COMPONENT_ID + 85; -const ecs_entity_t ecs_id(ecs_u32_t) = ECS_HI_COMPONENT_ID + 86; -const ecs_entity_t ecs_id(ecs_u64_t) = ECS_HI_COMPONENT_ID + 87; -const ecs_entity_t ecs_id(ecs_uptr_t) = ECS_HI_COMPONENT_ID + 88; -const ecs_entity_t ecs_id(ecs_i8_t) = ECS_HI_COMPONENT_ID + 89; -const ecs_entity_t ecs_id(ecs_i16_t) = ECS_HI_COMPONENT_ID + 90; -const ecs_entity_t ecs_id(ecs_i32_t) = ECS_HI_COMPONENT_ID + 91; -const ecs_entity_t ecs_id(ecs_i64_t) = ECS_HI_COMPONENT_ID + 92; -const ecs_entity_t ecs_id(ecs_iptr_t) = ECS_HI_COMPONENT_ID + 93; -const ecs_entity_t ecs_id(ecs_f32_t) = ECS_HI_COMPONENT_ID + 94; -const ecs_entity_t ecs_id(ecs_f64_t) = ECS_HI_COMPONENT_ID + 95; -const ecs_entity_t ecs_id(ecs_string_t) = ECS_HI_COMPONENT_ID + 96; -const ecs_entity_t ecs_id(ecs_entity_t) = ECS_HI_COMPONENT_ID + 97; +#endif +#endif -/* Doc module components */ -const ecs_entity_t ecs_id(EcsDocDescription) =ECS_HI_COMPONENT_ID + 100; -const ecs_entity_t EcsDocBrief = ECS_HI_COMPONENT_ID + 101; -const ecs_entity_t EcsDocDetail = ECS_HI_COMPONENT_ID + 102; -const ecs_entity_t EcsDocLink = ECS_HI_COMPONENT_ID + 103; +#ifdef FLECS_STATS -/* REST module components */ -const ecs_entity_t ecs_id(EcsRest) = ECS_HI_COMPONENT_ID + 105; +static +int32_t t_next( + int32_t t) +{ + return (t + 1) % ECS_STAT_WINDOW; +} -/* -- Private functions -- */ +static +int32_t t_prev( + int32_t t) +{ + return (t - 1 + ECS_STAT_WINDOW) % ECS_STAT_WINDOW; +} -const ecs_stage_t* flecs_stage_from_readonly_world( - const ecs_world_t *world) +static +void _record_gauge( + ecs_gauge_t *m, + int32_t t, + float value) { - ecs_assert(ecs_poly_is(world, ecs_world_t) || - ecs_poly_is(world, ecs_stage_t), - ECS_INTERNAL_ERROR, - NULL); + m->avg[t] = value; + m->min[t] = value; + m->max[t] = value; +} - if (ecs_poly_is(world, ecs_world_t)) { - return &world->stage; +static +float _record_counter( + ecs_counter_t *m, + int32_t t, + float value) +{ + int32_t tp = t_prev(t); + float prev = m->value[tp]; + m->value[t] = value; + _record_gauge((ecs_gauge_t*)m, t, value - prev); + return value - prev; +} - } else if (ecs_poly_is(world, ecs_stage_t)) { - return (ecs_stage_t*)world; - } - - return NULL; +/* Macro's to silence conversion warnings without adding casts everywhere */ +#define record_gauge(m, t, value)\ + _record_gauge(m, t, (float)value) + +#define record_counter(m, t, value)\ + _record_counter(m, t, (float)value) + +static +void print_value( + const char *name, + float value) +{ + ecs_size_t len = ecs_os_strlen(name); + printf("%s: %*s %.2f\n", name, 32 - len, "", (double)value); } -ecs_stage_t *flecs_stage_from_world( - ecs_world_t **world_ptr) +static +void print_gauge( + const char *name, + int32_t t, + const ecs_gauge_t *m) { - ecs_world_t *world = *world_ptr; + print_value(name, m->avg[t]); +} - ecs_assert(ecs_poly_is(world, ecs_world_t) || - ecs_poly_is(world, ecs_stage_t), - ECS_INTERNAL_ERROR, - NULL); +static +void print_counter( + const char *name, + int32_t t, + const ecs_counter_t *m) +{ + print_value(name, m->rate.avg[t]); +} - if (ecs_poly_is(world, ecs_world_t)) { - ecs_assert(!world->is_readonly, ECS_INVALID_OPERATION, NULL); - return &world->stage; +void ecs_gauge_reduce( + ecs_gauge_t *dst, + int32_t t_dst, + ecs_gauge_t *src, + int32_t t_src) +{ + ecs_check(dst != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(src != NULL, ECS_INVALID_PARAMETER, NULL); - } else if (ecs_poly_is(world, ecs_stage_t)) { - ecs_stage_t *stage = (ecs_stage_t*)world; - *world_ptr = stage->world; - return stage; + bool min_set = false; + dst->min[t_dst] = 0; + dst->avg[t_dst] = 0; + dst->max[t_dst] = 0; + + int32_t i; + for (i = 0; i < ECS_STAT_WINDOW; i ++) { + int32_t t = (t_src + i) % ECS_STAT_WINDOW; + dst->avg[t_dst] += src->avg[t] / (float)ECS_STAT_WINDOW; + if (!min_set || (src->min[t] < dst->min[t_dst])) { + dst->min[t_dst] = src->min[t]; + min_set = true; + } + if ((src->max[t] > dst->max[t_dst])) { + dst->max[t_dst] = src->max[t]; + } } - - return NULL; +error: + return; } -/* Evaluate component monitor. If a monitored entity changed it will have set a - * flag in one of the world's component monitors. Queries can register - * themselves with component monitors to determine whether they need to rematch - * with tables. */ -static -void eval_component_monitor( - ecs_world_t *world) +void ecs_get_world_stats( + const ecs_world_t *world, + ecs_world_stats_t *s) { - ecs_relation_monitor_t *rm = &world->monitors; + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(s != NULL, ECS_INVALID_PARAMETER, NULL); - if (!rm->is_dirty) { - return; + world = ecs_get_world(world); + + int32_t t = s->t = t_next(s->t); + + float delta_world_time = record_counter(&s->world_time_total_raw, t, world->stats.world_time_total_raw); + record_counter(&s->world_time_total, t, world->stats.world_time_total); + record_counter(&s->frame_time_total, t, world->stats.frame_time_total); + record_counter(&s->system_time_total, t, world->stats.system_time_total); + record_counter(&s->merge_time_total, t, world->stats.merge_time_total); + + float delta_frame_count = record_counter(&s->frame_count_total, t, world->stats.frame_count_total); + record_counter(&s->merge_count_total, t, world->stats.merge_count_total); + record_counter(&s->pipeline_build_count_total, t, world->stats.pipeline_build_count_total); + record_counter(&s->systems_ran_frame, t, world->stats.systems_ran_frame); + + if (delta_world_time != 0.0f && delta_frame_count != 0.0f) { + record_gauge( + &s->fps, t, 1.0f / (delta_world_time / (float)delta_frame_count)); + } else { + record_gauge(&s->fps, t, 0); } - ecs_map_iter_t it = ecs_map_iter(rm->monitor_sets); - ecs_monitor_set_t *ms; + record_gauge(&s->entity_count, t, flecs_sparse_count(world->store.entity_index)); + record_gauge(&s->component_count, t, ecs_count_id(world, ecs_id(EcsComponent))); + record_gauge(&s->query_count, t, flecs_sparse_count(world->queries)); + record_gauge(&s->system_count, t, ecs_count_id(world, ecs_id(EcsSystem))); - while ((ms = ecs_map_next(&it, ecs_monitor_set_t, NULL))) { - if (!ms->is_dirty) { - continue; - } + record_counter(&s->new_count, t, world->new_count); + record_counter(&s->bulk_new_count, t, world->bulk_new_count); + record_counter(&s->delete_count, t, world->delete_count); + record_counter(&s->clear_count, t, world->clear_count); + record_counter(&s->add_count, t, world->add_count); + record_counter(&s->remove_count, t, world->remove_count); + record_counter(&s->set_count, t, world->set_count); + record_counter(&s->discard_count, t, world->discard_count); - if (ms->monitors) { - ecs_map_iter_t mit = ecs_map_iter(ms->monitors); - ecs_monitor_t *m; - while ((m = ecs_map_next(&mit, ecs_monitor_t, NULL))) { - if (!m->is_dirty) { - continue; - } + /* Compute table statistics */ + int32_t empty_table_count = 0; + int32_t singleton_table_count = 0; + int32_t matched_table_count = 0, matched_entity_count = 0; - ecs_vector_each(m->queries, ecs_query_t*, q_ptr, { - flecs_query_notify(world, *q_ptr, &(ecs_query_event_t) { - .kind = EcsQueryTableRematch - }); - }); + int32_t i, count = flecs_sparse_count(world->store.tables); + for (i = 0; i < count; i ++) { + ecs_table_t *table = flecs_sparse_get_dense(world->store.tables, ecs_table_t, i); + int32_t entity_count = ecs_table_count(table); - m->is_dirty = false; + if (!entity_count) { + empty_table_count ++; + } + + /* Singleton tables are tables that have just one entity that also has + * itself in the table type. */ + if (entity_count == 1) { + ecs_entity_t *entities = ecs_vector_first( + table->storage.entities, ecs_entity_t); + if (ecs_type_has_id(world, table->type, entities[0], false)) { + singleton_table_count ++; } } - ms->is_dirty = false; + /* If this table matches with queries and is not empty, increase the + * matched table & matched entity count. These statistics can be used to + * compute actual fragmentation ratio for queries. */ + int32_t queries_matched = ecs_vector_count(table->queries); + if (queries_matched && entity_count) { + matched_table_count ++; + matched_entity_count += entity_count; + } } - rm->is_dirty = false; + record_gauge(&s->matched_table_count, t, matched_table_count); + record_gauge(&s->matched_entity_count, t, matched_entity_count); + + record_gauge(&s->table_count, t, count); + record_gauge(&s->empty_table_count, t, empty_table_count); + record_gauge(&s->singleton_table_count, t, singleton_table_count); + +error: + return; } -void flecs_monitor_mark_dirty( - ecs_world_t *world, - ecs_entity_t relation, - ecs_entity_t id) +void ecs_get_query_stats( + const ecs_world_t *world, + const ecs_query_t *query, + ecs_query_stats_t *s) { - ecs_assert(world->monitors.monitor_sets != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(query != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(s != NULL, ECS_INVALID_PARAMETER, NULL); + (void)world; - /* Only flag if there are actually monitors registered, so that we - * don't waste cycles evaluating monitors if there's no interest */ - ecs_monitor_set_t *ms = ecs_map_get(world->monitors.monitor_sets, - ecs_monitor_set_t, relation); - if (ms && ms->monitors) { - ecs_monitor_t *m = ecs_map_get(ms->monitors, - ecs_monitor_t, id); - if (m) { - m->is_dirty = true; - ms->is_dirty = true; - world->monitors.is_dirty = true; - } - } + int32_t t = s->t = t_next(s->t); + + ecs_iter_t it = ecs_query_iter(world, (ecs_query_t*)query); + record_gauge(&s->matched_entity_count, t, ecs_iter_count(&it)); + record_gauge(&s->matched_table_count, t, ecs_query_table_count(query)); + record_gauge(&s->matched_empty_table_count, t, + ecs_query_empty_table_count(query)); +error: + return; } -void flecs_monitor_register( - ecs_world_t *world, - ecs_entity_t relation, - ecs_entity_t id, - ecs_query_t *query) +#ifdef FLECS_SYSTEM +bool ecs_get_system_stats( + const ecs_world_t *world, + ecs_entity_t system, + ecs_system_stats_t *s) { - ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(id != 0, ECS_INTERNAL_ERROR, NULL); - ecs_assert(query != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(world->monitors.monitor_sets != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(s != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(system != 0, ECS_INVALID_PARAMETER, NULL); - ecs_monitor_set_t *ms = ecs_map_ensure( - world->monitors.monitor_sets, ecs_monitor_set_t, relation); - ecs_assert(ms != NULL, ECS_INTERNAL_ERROR, NULL); + world = ecs_get_world(world); - if (!ms->monitors) { - ms->monitors = ecs_map_new(ecs_monitor_t, 1); + const EcsSystem *ptr = ecs_get(world, system, EcsSystem); + if (!ptr) { + return false; } - ecs_monitor_t *m = ecs_map_ensure(ms->monitors, ecs_monitor_t, id); - ecs_assert(m != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_get_query_stats(world, ptr->query, &s->query_stats); + int32_t t = s->query_stats.t; - ecs_query_t **q = ecs_vector_add(&m->queries, ecs_query_t*); - *q = query; + record_counter(&s->time_spent, t, ptr->time_spent); + record_counter(&s->invoke_count, t, ptr->invoke_count); + record_gauge(&s->active, t, !ecs_has_id(world, system, EcsInactive)); + record_gauge(&s->enabled, t, !ecs_has_id(world, system, EcsDisabled)); + + return true; +error: + return false; } +#endif -static -void monitors_init( - ecs_relation_monitor_t *rm) + +#ifdef FLECS_PIPELINE + +static +ecs_system_stats_t* get_system_stats( + ecs_map_t *systems, + ecs_entity_t system) { - rm->monitor_sets = ecs_map_new(ecs_monitor_t, 0); - rm->is_dirty = false; + ecs_check(systems != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(system != 0, ECS_INVALID_PARAMETER, NULL); + + ecs_system_stats_t *s = ecs_map_get(systems, ecs_system_stats_t, system); + if (!s) { + s = ecs_map_ensure(systems, ecs_system_stats_t, system); + } + + return s; +error: + return NULL; } -static -void monitors_fini( - ecs_relation_monitor_t *rm) +bool ecs_get_pipeline_stats( + ecs_world_t *stage, + ecs_entity_t pipeline, + ecs_pipeline_stats_t *s) { - ecs_map_iter_t it = ecs_map_iter(rm->monitor_sets); - ecs_monitor_set_t *ms; + ecs_check(stage != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(s != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(pipeline != 0, ECS_INVALID_PARAMETER, NULL); - while ((ms = ecs_map_next(&it, ecs_monitor_set_t, NULL))) { - if (ms->monitors) { - ecs_map_iter_t mit = ecs_map_iter(ms->monitors); - ecs_monitor_t *m; - while ((m = ecs_map_next(&mit, ecs_monitor_t, NULL))) { - ecs_vector_free(m->queries); - } + const ecs_world_t *world = ecs_get_world(stage); - ecs_map_free(ms->monitors); - } + const EcsPipelineQuery *pq = ecs_get(world, pipeline, EcsPipelineQuery); + if (!pq) { + return false; } - ecs_map_free(rm->monitor_sets); -} + int32_t sys_count = 0, active_sys_count = 0; -static -void init_store( - ecs_world_t *world) -{ - ecs_os_memset(&world->store, 0, ECS_SIZEOF(ecs_store_t)); - - /* Initialize entity index */ - world->store.entity_index = flecs_sparse_new(ecs_record_t); - flecs_sparse_set_id_source(world->store.entity_index, &world->stats.last_id); + /* Count number of active systems */ + ecs_iter_t it = ecs_query_iter(stage, pq->query); + while (ecs_query_next(&it)) { + active_sys_count += it.count; + } - /* Initialize root table */ - world->store.tables = flecs_sparse_new(ecs_table_t); + /* Count total number of systems in pipeline */ + it = ecs_query_iter(stage, pq->build_query); + while (ecs_query_next(&it)) { + sys_count += it.count; + } - /* Initialize table map */ - world->store.table_map = flecs_table_hashmap_new(); + /* Also count synchronization points */ + ecs_vector_t *ops = pq->ops; + ecs_pipeline_op_t *op = ecs_vector_first(ops, ecs_pipeline_op_t); + ecs_pipeline_op_t *op_last = ecs_vector_last(ops, ecs_pipeline_op_t); + int32_t pip_count = active_sys_count + ecs_vector_count(ops); - /* Initialize one root table per stage */ - flecs_init_root_table(world); + if (!sys_count) { + return false; + } + + if (s->system_stats && !sys_count) { + ecs_map_free(s->system_stats); + } + if (!s->system_stats && sys_count) { + s->system_stats = ecs_map_new(ecs_system_stats_t, sys_count); + } + if (!sys_count) { + s->system_stats = NULL; + } + + /* Make sure vector is large enough to store all systems & sync points */ + ecs_entity_t *systems = NULL; + if (pip_count) { + ecs_vector_set_count(&s->systems, ecs_entity_t, pip_count); + systems = ecs_vector_first(s->systems, ecs_entity_t); + + /* Populate systems vector, keep track of sync points */ + it = ecs_query_iter(stage, pq->query); + + int32_t i, i_system = 0, ran_since_merge = 0; + while (ecs_query_next(&it)) { + for (i = 0; i < it.count; i ++) { + systems[i_system ++] = it.entities[i]; + ran_since_merge ++; + if (op != op_last && ran_since_merge == op->count) { + ran_since_merge = 0; + op++; + systems[i_system ++] = 0; /* 0 indicates a merge point */ + } + } + } + + systems[i_system ++] = 0; /* Last merge */ + ecs_assert(pip_count == i_system, ECS_INTERNAL_ERROR, NULL); + } else { + ecs_vector_free(s->systems); + s->systems = NULL; + } + + /* Separately populate system stats map from build query, which includes + * systems that aren't currently active */ + it = ecs_query_iter(stage, pq->build_query); + while (ecs_query_next(&it)) { + int i; + for (i = 0; i < it.count; i ++) { + ecs_system_stats_t *sys_stats = get_system_stats( + s->system_stats, it.entities[i]); + ecs_get_system_stats(world, it.entities[i], sys_stats); + } + } + + return true; +error: + return false; } -static -void clean_tables( - ecs_world_t *world) +void ecs_pipeline_stats_fini( + ecs_pipeline_stats_t *stats) { - int32_t i, count = flecs_sparse_count(world->store.tables); + ecs_map_free(stats->system_stats); + ecs_vector_free(stats->systems); +} - for (i = 0; i < count; i ++) { - ecs_table_t *t = flecs_sparse_get_dense(world->store.tables, ecs_table_t, i); - flecs_table_free(world, t); - } +#endif - /* Free table types separately so that if application destructors rely on - * a type it's still valid. */ - for (i = 0; i < count; i ++) { - ecs_table_t *t = flecs_sparse_get_dense(world->store.tables, ecs_table_t, i); - flecs_table_free_type(t); - } +void ecs_dump_world_stats( + const ecs_world_t *world, + const ecs_world_stats_t *s) +{ + int32_t t = s->t; - /* Clear the root table */ - if (count) { - flecs_table_reset(world, &world->store.root); - } + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(s != NULL, ECS_INVALID_PARAMETER, NULL); + + world = ecs_get_world(world); + + print_counter("Frame", t, &s->frame_count_total); + printf("-------------------------------------\n"); + print_counter("pipeline rebuilds", t, &s->pipeline_build_count_total); + print_counter("systems ran last frame", t, &s->systems_ran_frame); + printf("\n"); + print_value("target FPS", world->stats.target_fps); + print_value("time scale", world->stats.time_scale); + printf("\n"); + print_gauge("actual FPS", t, &s->fps); + print_counter("frame time", t, &s->frame_time_total); + print_counter("system time", t, &s->system_time_total); + print_counter("merge time", t, &s->merge_time_total); + print_counter("simulation time elapsed", t, &s->world_time_total); + printf("\n"); + print_gauge("entity count", t, &s->entity_count); + print_gauge("component count", t, &s->component_count); + print_gauge("query count", t, &s->query_count); + print_gauge("system count", t, &s->system_count); + print_gauge("table count", t, &s->table_count); + print_gauge("singleton table count", t, &s->singleton_table_count); + print_gauge("empty table count", t, &s->empty_table_count); + printf("\n"); + print_counter("deferred new operations", t, &s->new_count); + print_counter("deferred bulk_new operations", t, &s->bulk_new_count); + print_counter("deferred delete operations", t, &s->delete_count); + print_counter("deferred clear operations", t, &s->clear_count); + print_counter("deferred add operations", t, &s->add_count); + print_counter("deferred remove operations", t, &s->remove_count); + print_counter("deferred set operations", t, &s->set_count); + print_counter("discarded operations", t, &s->discard_count); + printf("\n"); + +error: + return; } -static -void fini_store(ecs_world_t *world) { - clean_tables(world); - flecs_sparse_free(world->store.tables); - flecs_table_free(world, &world->store.root); - flecs_sparse_clear(world->store.entity_index); - flecs_hashmap_free(world->store.table_map); -} +#endif + +#ifdef FLECS_APP -/* Implementation for iterable mixin */ static -bool world_iter_next( - ecs_iter_t *it) +int default_run_action( + ecs_world_t *world, + ecs_app_desc_t *desc) { - if (it->is_valid) { - return it->is_valid = false; + if (desc->init) { + desc->init(world); } - ecs_world_t *world = it->real_world; - ecs_sparse_t *entity_index = world->store.entity_index; - it->entities = (ecs_entity_t*)flecs_sparse_ids(entity_index); - it->count = flecs_sparse_count(entity_index); - return it->is_valid = true; + int result; + while ((result = ecs_app_run_frame(world, desc)) == 0) { } + return result; } static -void world_iter_init( - const ecs_world_t *world, - const ecs_poly_t *poly, - ecs_iter_t *iter, - ecs_term_t *filter) +int default_frame_action( + ecs_world_t *world, + const ecs_app_desc_t *desc) { - ecs_poly_assert(poly, ecs_world_t); - (void)poly; - - if (filter) { - iter[0] = ecs_term_iter(world, filter); - } else { - iter[0] = (ecs_iter_t){ - .world = (ecs_world_t*)world, - .real_world = (ecs_world_t*)ecs_get_world(world), - .next = world_iter_next - }; - } + return !ecs_progress(world, desc->delta_time); } -static -void log_addons(void) { - ecs_trace("addons included in build:"); - ecs_log_push(); - #ifdef FLECS_CPP - ecs_trace("FLECS_CPP"); - #endif - #ifdef FLECS_MODULE - ecs_trace("FLECS_MODULE"); - #endif - #ifdef FLECS_PARSER - ecs_trace("FLECS_PARSER"); - #endif - #ifdef FLECS_PLECS - ecs_trace("FLECS_PLECS"); - #endif - #ifdef FLECS_RULES - ecs_trace("FLECS_RULES"); - #endif - #ifdef FLECS_SNAPSHOT - ecs_trace("FLECS_SNAPSHOT"); - #endif - #ifdef FLECS_STATS - ecs_trace("FLECS_STATS"); - #endif - #ifdef FLECS_SYSTEM - ecs_trace("FLECS_SYSTEM"); - #endif - #ifdef FLECS_PIPELINE - ecs_trace("FLECS_PIPELINE"); - #endif - #ifdef FLECS_TIMER - ecs_trace("FLECS_TIMER"); - #endif - #ifdef FLECS_META - ecs_trace("FLECS_META"); - #endif - #ifdef FLECS_META_C - ecs_trace("FLECS_META_C"); - #endif - #ifdef FLECS_EXPR - ecs_trace("FLECS_EXPR"); - #endif - #ifdef FLECS_JSON - ecs_trace("FLECS_JSON"); - #endif - #ifdef FLECS_DOC - ecs_trace("FLECS_DOC"); - #endif - #ifdef FLECS_COREDOC - ecs_trace("FLECS_COREDOC"); - #endif - #ifdef FLECS_LOG - ecs_trace("FLECS_LOG"); - #endif - #ifdef FLECS_APP - ecs_trace("FLECS_APP"); - #endif - #ifdef FLECS_OS_API_IMPL - ecs_trace("FLECS_OS_API_IMPL"); - #endif - #ifdef FLECS_HTTP - ecs_trace("FLECS_HTTP"); - #endif - #ifdef FLECS_REST - ecs_trace("FLECS_REST"); - #endif - ecs_log_pop(); -} +static ecs_app_run_action_t run_action = default_run_action; +static ecs_app_frame_action_t frame_action = default_frame_action; -/* -- Public functions -- */ +int ecs_app_run( + ecs_world_t *world, + ecs_app_desc_t *desc) +{ + /* Don't set FPS & threads if custom run action is set, as the platform on + * which the app is running may not support it. */ + if (!run_action) { + ecs_set_target_fps(world, desc->target_fps); + ecs_set_threads(world, desc->threads); + } -ecs_world_t *ecs_mini(void) { -#ifdef FLECS_OS_API_IMPL - ecs_set_os_api_impl(); + /* REST server enables connecting to app with explorer */ + if (desc->enable_rest) { +#ifdef FLECS_REST + ecs_set(world, EcsWorld, EcsRest, {.port = 0}); +#else + ecs_warn("cannot enable remote API, REST addon not available"); #endif - ecs_os_init(); + } - ecs_trace("#[bold]bootstrapping world"); - ecs_log_push(); + return run_action(world, desc); +} - ecs_trace("tracing enabled, call ecs_log_set_level(-1) to disable"); +int ecs_app_run_frame( + ecs_world_t *world, + const ecs_app_desc_t *desc) +{ + return frame_action(world, desc); +} - if (!ecs_os_has_heap()) { - ecs_abort(ECS_MISSING_OS_API, NULL); +int ecs_app_set_run_action( + ecs_app_run_action_t callback) +{ + if (run_action != default_run_action) { + ecs_err("run action already set"); + return -1; } - if (!ecs_os_has_threading()) { - ecs_trace("threading unavailable, to use threads set OS API first (see examples)"); - } + run_action = callback; - if (!ecs_os_has_time()) { - ecs_trace("time management not available"); + return 0; +} + +int ecs_app_set_frame_action( + ecs_app_frame_action_t callback) +{ + if (frame_action != default_frame_action) { + ecs_err("frame action already set"); + return -1; } - log_addons(); + frame_action = callback; + + return 0; +} -#ifndef NDEBUG - ecs_trace("debug build, rebuild with NDEBUG for improved performance"); -#else - ecs_trace("#[green]release#[reset] build"); #endif - ecs_world_t *world = ecs_os_calloc(sizeof(ecs_world_t)); - ecs_assert(world != NULL, ECS_OUT_OF_MEMORY, NULL); - ecs_poly_init(world, ecs_world_t); +#ifdef FLECS_RULES - world->self = world; - world->type_info = flecs_sparse_new(ecs_type_info_t); - world->id_index = ecs_map_new(ecs_id_record_t, 8); - flecs_observable_init(&world->observable); - world->iterable.init = world_iter_init; +/** Implementation of the rule query engine. + * + * A rule (terminology borrowed from prolog) is a list of constraints that + * specify which conditions must be met for an entity to match the rule. While + * this description matches any kind of ECS query, the rule engine has features + * that go beyond regular (flecs) ECS queries: + * + * - query for all components of an entity (vs. all entities for a component) + * - query for all relationship pairs of an entity + * - support for query variables that are resolved at evaluation time + * - automatic traversal of transitive relationships + * + * Query terms can have the following forms: + * + * - Component(Subject) + * - Relation(Subject, Object) + * + * Additionally the query parser supports the following shorthand notations: + * + * - Component // short for Component(This) + * - (Relation, Object) // short for Relation(This, Object) + * + * The subject, or first arugment of a term represents the entity on which the + * component or relation is matched. By default the subject is set to a builtin + * This variable, which causes the behavior to match a regular ECS query: + * + * - Position, Velocity + * + * Is equivalent to + * + * - Position(This), Velocity(This) + * + * The function of the variable is to ensure that all components are matched on + * the same entity. Conceptually the query first populates the This variable + * with all entities that have Position. When the query evaluates the Velocity + * term, the variable is populated and the entity it contains will be checked + * for whether it has Velocity. + * + * The actual implementation is more efficient and does not check per-entity. + * + * Custom variables can be used to join parts of different terms. For example, + * the following query can be used to find entities with a parent that has a + * Position component (note that variable names start with a _): + * + * - ChildOf(This, _Parent), Component(_Parent) + * + * The rule engine uses a backtracking algorithm to find the set of entities + * and variables that match all terms. As soon as the engine finds a term that + * does not match with the currently evaluated entity, the entity is discarded. + * When an entity is found for which all terms match, the entity is yielded to + * the iterator. + * + * While a rule is being evaluated, a variable can either contain a single + * entity or a table. The engine will attempt to work with tables as much as + * possible so entities can be eliminated/yielded in bulk. A rule may store + * both the table and entity version of a variable and only switch from table to + * entity when necessary. + * + * The rule engine has an algorithm for computing which variables should be + * resolved first. This algorithm works by finding a "root" variable, which is + * the subject variable that occurs in the term with the least dependencies. The + * remaining variables are then resolved based on their "distance" from the root + * with the closest variables being resolved first. + * + * This generally results in an ordering that resolves the variables with the + * least dependencies first and the most dependencies last, which is beneficial + * for two reasons: + * + * - it improves the average performance of all queries + * - it makes performance less dependent on how an application orders the terms + * + * A possible improvement would be for the query engine to also consider + * the number of tables that need to be evaluated for each term, as starting + * with the smallest term reduces the amount of work. Other than static variable + * analysis however, this can only be determined when the query is executed. + * + * Rules are "compiled" into a set of instructions that encode the operations + * the query needs to perform in order to find the right set of entities. + * Operations can either yield data, which progresses the program, or signal + * that there is no (more) matching data, which discards the current variables. + * + * An operation can yield multiple times, if there are multiple matches for its + * inputs. Operations are called with a redo flag, which can be either true or + * false. When redo is true the operation will yield the next result. When redo + * is false, the operation will reset its state and start from the first result. + * + * Operations can have an input, output and a filter. Most commonly an operation + * either matches the filter against an input and yields if it matches, or uses + * the filter to find all matching results and store the result in the output. + * + * Variables are resolved by matching a filter against the output of an + * operation. When a term contains variables, they are encoded as register ids + * in the filter. When the filter is evaluated, the most recent values of the + * register are used to match/lookup the output. + * + * For example, a filter could be (ChildOf, _Parent). When the program starts, + * the _Parent register is initialized with *, so that when this filter is first + * evaluated, the operation will find all tables with (ChildOf, *). The _Parent + * register is then populated by taking the actual value of the table. If the + * table has type [(ChildOf, Sun)], _Parent will be initialized with Sun. + * + * It is possible that a filter matches multiple times. Consider the filter + * (Likes, _Food), and a table [(Likes, Apples), (Likes, Pears)]. In this case + * an operation will yield the table twice, once with _Food=Apples, and once + * with _Food=Pears. + * + * If a rule contains a term with a transitive relation, it will automatically + * substitute the parts of the term to find a fact that matches. The following + * examples illustrate how transitivity is resolved: + * + * Query: + * LocatedIn(Bob, SanFrancisco) + * + * Expands to: + * LocatedIn(Bob, SanFrancisco:self|subset) + * + * Explanation: + * "Is Bob located in San Francisco" - This term is true if Bob is either + * located in San Francisco, or is located in anything that is itself located + * in (a subset of) San Francisco. + * + * + * Query: + * LocatedIn(Bob, X) + * + * Expands to: + * LocatedIn(Bob, X:self|superset) + * + * Explanation: + * "Where is Bob located?" - This term recursively returns all places that + * Bob is located in, which includes his location and the supersets of his + * location. When Bob is located in San Francisco, he is also located in + * the United States, North America etc. + * + * + * Query: + * LocatedIn(X, NorthAmerica) + * + * Expands to: + * LocatedIn(X, NorthAmerica:self|subset) + * + * Explanation: + * "What is located in North America?" - This term returns everything located + * in North America and its subsets, as something located in San Francisco is + * located in UnitedStates, which is located in NorthAmerica. + * + * + * Query: + * LocatedIn(X, Y) + * + * Expands to: + * LocatedIn(X, Y) + * + * Explanation: + * "Where is everything located" - This term returns everything that is + * located somewhere. No substitution is performed as this would explode the + * results while not yielding new information. + * + * + * In the above terms, the variable indicates the part of the term that is + * unknown at evaluation time. In an actual rule the picked strategy depends on + * whether the variable is known when the term is evaluated. For example, if + * variable X has been resolved by the time Located(X, Y) is evaluated, the + * strategy from the LocatedIn(Bob, X) example will be used. + */ - world->queries = flecs_sparse_new(ecs_query_t); - world->triggers = flecs_sparse_new(ecs_trigger_t); - world->observers = flecs_sparse_new(ecs_observer_t); - world->fini_tasks = ecs_vector_new(ecs_entity_t, 0); - world->aliases = flecs_string_hashmap_new(ecs_entity_t); - world->symbols = flecs_string_hashmap_new(ecs_entity_t); - world->type_handles = ecs_map_new(ecs_entity_t, 0); +#define ECS_RULE_MAX_VARIABLE_COUNT (256) - world->stats.time_scale = 1.0; - - monitors_init(&world->monitors); +#define RULE_PAIR_PREDICATE (1) +#define RULE_PAIR_OBJECT (2) - if (ecs_os_has_time()) { - ecs_os_get_time(&world->world_start_time); - } +/* A rule pair contains a predicate and object that can be stored in a register. */ +typedef struct ecs_rule_pair_t { + union { + int32_t reg; + ecs_entity_t ent; + } pred; + union { + int32_t reg; + ecs_entity_t ent; + } obj; + int32_t reg_mask; /* bit 1 = predicate, bit 2 = object, bit 4 = wildcard */ + bool transitive; /* Is predicate transitive */ + bool final; /* Is predicate final */ + bool inclusive; /* Is predicate inclusive */ + bool obj_0; +} ecs_rule_pair_t; - flecs_stage_init(world, &world->stage); - ecs_set_stages(world, 1); +/* Filter for evaluating & reifing types and variables. Filters are created ad- + * hoc from pairs, and take into account all variables that had been resolved + * up to that point. */ +typedef struct ecs_rule_filter_t { + ecs_id_t mask; /* Mask with wildcard in place of variables */ - init_store(world); - ecs_trace("table store initialized"); + bool wildcard; /* Does the filter contain wildcards */ + bool pred_wildcard; /* Is predicate a wildcard */ + bool obj_wildcard; /* Is object a wildcard */ + bool same_var; /* True if pred & obj are both the same variable */ - flecs_bootstrap(world); + int32_t hi_var; /* If hi part should be stored in var, this is the var id */ + int32_t lo_var; /* If lo part should be stored in var, this is the var id */ +} ecs_rule_filter_t; - ecs_trace("world ready!"); - ecs_log_pop(); +/* A rule register stores temporary values for rule variables */ +typedef enum ecs_rule_var_kind_t { + EcsRuleVarKindTable, /* Used for sorting, must be smallest */ + EcsRuleVarKindEntity, + EcsRuleVarKindUnknown +} ecs_rule_var_kind_t; - return world; -} +typedef struct ecs_rule_reg_t { + /* Used for table variable */ + ecs_table_t *table; + int32_t offset; + int32_t count; -ecs_world_t *ecs_init(void) { - ecs_world_t *world = ecs_mini(); + /* Used for entity variable. May also be set for table variable if it needs + * to store an empty entity. */ + ecs_entity_t entity; +} ecs_rule_reg_t; + +/* Operations describe how the rule should be evaluated */ +typedef enum ecs_rule_op_kind_t { + EcsRuleInput, /* Input placeholder, first instruction in every rule */ + EcsRuleSelect, /* Selects all ables for a given predicate */ + EcsRuleWith, /* Applies a filter to a table or entity */ + EcsRuleSubSet, /* Finds all subsets for transitive relationship */ + EcsRuleSuperSet, /* Finds all supersets for a transitive relationship */ + EcsRuleStore, /* Store entity in table or entity variable */ + EcsRuleEach, /* Forwards each entity in a table */ + EcsRuleSetJmp, /* Set label for jump operation to one of two values */ + EcsRuleJump, /* Jump to an operation label */ + EcsRuleNot, /* Invert result of an operation */ + EcsRuleYield /* Yield result */ +} ecs_rule_op_kind_t; -#ifdef FLECS_MODULE_H - ecs_trace("#[bold]import addons"); - ecs_log_push(); - ecs_trace("use ecs_mini to create world without importing addons"); -#ifdef FLECS_SYSTEM - ECS_IMPORT(world, FlecsSystem); -#endif -#ifdef FLECS_PIPELINE - ECS_IMPORT(world, FlecsPipeline); -#endif -#ifdef FLECS_TIMER - ECS_IMPORT(world, FlecsTimer); -#endif -#ifdef FLECS_META - ECS_IMPORT(world, FlecsMeta); -#endif -#ifdef FLECS_DOC - ECS_IMPORT(world, FlecsDoc); -#endif -#ifdef FLECS_COREDOC - ECS_IMPORT(world, FlecsCoreDoc); -#endif -#ifdef FLECS_REST - ECS_IMPORT(world, FlecsRest); -#endif - ecs_trace("addons imported!"); - ecs_log_pop(); -#endif - return world; -} +/* Single operation */ +typedef struct ecs_rule_op_t { + ecs_rule_op_kind_t kind; /* What kind of operation is it */ + ecs_rule_pair_t filter; /* Parameter that contains optional filter */ + ecs_entity_t subject; /* If set, operation has a constant subject */ -#define ARG(short, long, action)\ - if (i < argc) {\ - if (argv[i][0] == '-') {\ - if (argv[i][1] == '-') {\ - if (long && !strcmp(&argv[i][2], long ? long : "")) {\ - action;\ - parsed = true;\ - }\ - } else {\ - if (short && argv[i][1] == short) {\ - action;\ - parsed = true;\ - }\ - }\ - }\ - } + int32_t on_pass; /* Jump location when match succeeds */ + int32_t on_fail; /* Jump location when match fails */ + int32_t frame; /* Register frame */ + + int32_t term; /* Corresponding term index in signature */ + int32_t r_in; /* Optional In/Out registers */ + int32_t r_out; + + bool has_in, has_out; /* Keep track of whether operation uses input + * and/or output registers. This helps with + * debugging rule programs. */ +} ecs_rule_op_t; + +/* With context. Shared with select. */ +typedef struct ecs_rule_with_ctx_t { + ecs_id_record_t *idr; /* Currently evaluated table set */ + int32_t table_index; +} ecs_rule_with_ctx_t; -ecs_world_t* ecs_init_w_args( - int argc, - char *argv[]) -{ - ecs_world_t *world = ecs_init(); +/* Subset context */ +typedef struct ecs_rule_subset_frame_t { + ecs_rule_with_ctx_t with_ctx; + ecs_table_t *table; + int32_t row; + int32_t column; +} ecs_rule_subset_frame_t; - (void)argc; - (void) argv; +typedef struct ecs_rule_subset_ctx_t { + ecs_rule_subset_frame_t storage[16]; /* Alloc-free array for small trees */ + ecs_rule_subset_frame_t *stack; + int32_t sp; +} ecs_rule_subset_ctx_t; -#ifdef FLECS_DOC - if (argc) { - char *app = argv[0]; - char *last_elem = strrchr(app, '/'); - if (!last_elem) { - last_elem = strrchr(app, '\\'); - } - if (last_elem) { - app = last_elem + 1; - } - ecs_set_pair(world, EcsWorld, EcsDocDescription, EcsName, {app}); - } -#endif +/* Superset context */ +typedef struct ecs_rule_superset_frame_t { + ecs_table_t *table; + int32_t column; +} ecs_rule_superset_frame_t; - return world; -} +typedef struct ecs_rule_superset_ctx_t { + ecs_rule_superset_frame_t storage[16]; /* Alloc-free array for small trees */ + ecs_rule_superset_frame_t *stack; + ecs_id_record_t *idr; + int32_t sp; +} ecs_rule_superset_ctx_t; -void ecs_quit( - ecs_world_t *world) -{ - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - flecs_stage_from_world(&world); - world->should_quit = true; -error: - return; -} +/* Each context */ +typedef struct ecs_rule_each_ctx_t { + int32_t row; /* Currently evaluated row in evaluated table */ +} ecs_rule_each_ctx_t; -bool ecs_should_quit( - const ecs_world_t *world) -{ - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - world = ecs_get_world(world); - return world->should_quit; -error: - return true; -} +/* Jump context */ +typedef struct ecs_rule_setjmp_ctx_t { + int32_t label; /* Operation label to jump to */ +} ecs_rule_setjmp_ctx_t; -void flecs_notify_tables( - ecs_world_t *world, - ecs_id_t id, - ecs_table_event_t *event) -{ - ecs_poly_assert(world, ecs_world_t); +/* Operation context. This is a per-operation, per-iterator structure that + * stores information for stateful operations. */ +typedef struct ecs_rule_op_ctx_t { + union { + ecs_rule_subset_ctx_t subset; + ecs_rule_superset_ctx_t superset; + ecs_rule_with_ctx_t with; + ecs_rule_each_ctx_t each; + ecs_rule_setjmp_ctx_t setjmp; + } is; +} ecs_rule_op_ctx_t; - /* If no id is specified, broadcast to all tables */ - if (!id) { - ecs_sparse_t *tables = world->store.tables; - int32_t i, count = flecs_sparse_count(tables); - for (i = 0; i < count; i ++) { - ecs_table_t *table = flecs_sparse_get_dense(tables, ecs_table_t, i); - flecs_table_notify(world, table, event); - } +/* Rule variables allow for the rule to be parameterized */ +typedef struct ecs_rule_var_t { + ecs_rule_var_kind_t kind; + char *name; /* Variable name */ + int32_t id; /* Unique variable id */ + int32_t occurs; /* Number of occurrences (used for operation ordering) */ + int32_t depth; /* Depth in dependency tree (used for operation ordering) */ + bool marked; /* Used for cycle detection */ +} ecs_rule_var_t; - /* If id is specified, only broadcast to tables with id */ - } else { - ecs_id_record_t *idr = flecs_get_id_record(world, id); - if (!idr) { - return; - } +/* Top-level rule datastructure */ +struct ecs_rule_t { + ecs_header_t hdr; + + ecs_world_t *world; /* Ref to world so rule can be used by itself */ + ecs_rule_op_t *operations; /* Operations array */ + ecs_rule_var_t *variables; /* Variable array */ + ecs_filter_t filter; /* Filter of rule */ - const ecs_table_record_t *tables = flecs_id_record_tables(idr); - int32_t i, count = flecs_id_record_count(idr); - for (i = 0; i < count; i ++) { - flecs_table_notify(world, tables[i].table, event); - } + char **variable_names; /* Array with var names, used by iterators */ + int32_t *subject_variables; /* Variable id for term subject (if any) */ - tables = flecs_id_record_empty_tables(idr); - count = flecs_id_record_empty_count(idr); - for (i = 0; i < count; i ++) { - flecs_table_notify(world, tables[i].table, event); - } - } -} + int32_t variable_count; /* Number of variables in signature */ + int32_t subject_variable_count; + int32_t frame_count; /* Number of register frames */ + int32_t operation_count; /* Number of operations in rule */ -void ecs_default_ctor( - ecs_world_t *world, ecs_entity_t component, const ecs_entity_t *entity_ptr, - void *ptr, size_t size, int32_t count, void *ctx) -{ - (void)world; (void)component; (void)entity_ptr; (void)ctx; - ecs_os_memset(ptr, 0, flecs_uto(ecs_size_t, size) * count); -} + ecs_iterable_t iterable; /* Iterable mixin */ +}; -static -void default_copy_ctor( - ecs_world_t *world, ecs_entity_t component, - const EcsComponentLifecycle *callbacks, const ecs_entity_t *dst_entity, - const ecs_entity_t *src_entity, void *dst_ptr, const void *src_ptr, - size_t size, int32_t count, void *ctx) -{ - callbacks->ctor(world, component, dst_entity, dst_ptr, size, count, ctx); - callbacks->copy(world, component, dst_entity, src_entity, dst_ptr, src_ptr, - size, count, ctx); -} +/* ecs_rule_t mixins */ +ecs_mixins_t ecs_rule_t_mixins = { + .type_name = "ecs_rule_t", + .elems = { + [EcsMixinWorld] = offsetof(ecs_rule_t, world), + [EcsMixinIterable] = offsetof(ecs_rule_t, iterable) + } +}; static -void default_move_ctor( - ecs_world_t *world, ecs_entity_t component, - const EcsComponentLifecycle *callbacks, const ecs_entity_t *dst_entity, - const ecs_entity_t *src_entity, void *dst_ptr, void *src_ptr, size_t size, - int32_t count, void *ctx) +void rule_error( + const ecs_rule_t *rule, + const char *fmt, + ...) { - callbacks->ctor(world, component, dst_entity, dst_ptr, size, count, ctx); - callbacks->move(world, component, dst_entity, src_entity, dst_ptr, src_ptr, - size, count, ctx); + va_list valist; + va_start(valist, fmt); + ecs_parser_errorv(rule->filter.name, rule->filter.expr, -1, fmt, valist); + va_end(valist); } static -void default_ctor_w_move_w_dtor( - ecs_world_t *world, ecs_entity_t component, - const EcsComponentLifecycle *callbacks, const ecs_entity_t *dst_entity, - const ecs_entity_t *src_entity, void *dst_ptr, void *src_ptr, size_t size, - int32_t count, void *ctx) +bool subj_is_set( + ecs_term_t *term) { - callbacks->ctor(world, component, dst_entity, dst_ptr, size, count, ctx); - callbacks->move(world, component, dst_entity, src_entity, dst_ptr, src_ptr, - size, count, ctx); - callbacks->dtor(world, component, src_entity, src_ptr, size, count, ctx); + return ecs_term_id_is_set(&term->subj); } static -void default_move_ctor_w_dtor( - ecs_world_t *world, ecs_entity_t component, - const EcsComponentLifecycle *callbacks, const ecs_entity_t *dst_entity, - const ecs_entity_t *src_entity, void *dst_ptr, void *src_ptr, size_t size, - int32_t count, void *ctx) +bool obj_is_set( + ecs_term_t *term) { - callbacks->move_ctor(world, component, callbacks, dst_entity, src_entity, - dst_ptr, src_ptr, size, count, ctx); - callbacks->dtor(world, component, src_entity, src_ptr, size, count, ctx); + return ecs_term_id_is_set(&term->obj) || term->role == ECS_PAIR; } static -void default_move( - ecs_world_t *world, ecs_entity_t component, - const EcsComponentLifecycle *callbacks, const ecs_entity_t *dst_entity, - const ecs_entity_t *src_entity, void *dst_ptr, void *src_ptr, size_t size, - int32_t count, void *ctx) +ecs_rule_op_t* create_operation( + ecs_rule_t *rule) { - callbacks->move(world, component, dst_entity, src_entity, - dst_ptr, src_ptr, size, count, ctx); -} + int32_t cur = rule->operation_count ++; + rule->operations = ecs_os_realloc( + rule->operations, (cur + 1) * ECS_SIZEOF(ecs_rule_op_t)); -static -void default_dtor( - ecs_world_t *world, ecs_entity_t component, - const EcsComponentLifecycle *callbacks, const ecs_entity_t *dst_entity, - const ecs_entity_t *src_entity, void *dst_ptr, void *src_ptr, size_t size, - int32_t count, void *ctx) -{ - (void)callbacks; - (void)src_entity; + ecs_rule_op_t *result = &rule->operations[cur]; + ecs_os_memset_t(result, 0, ecs_rule_op_t); - /* When there is no move, destruct the destination component & memcpy the - * component to dst. The src component does not have to be destructed when - * a component has a trivial move. */ - callbacks->dtor(world, component, dst_entity, dst_ptr, size, count, ctx); - ecs_os_memcpy(dst_ptr, src_ptr, flecs_uto(ecs_size_t, size) * count); + return result; } static -void default_move_w_dtor( - ecs_world_t *world, ecs_entity_t component, - const EcsComponentLifecycle *callbacks, const ecs_entity_t *dst_entity, - const ecs_entity_t *src_entity, void *dst_ptr, void *src_ptr, size_t size, - int32_t count, void *ctx) -{ - /* If a component has a move, the move will take care of memcpying the data - * and destroying any data in dst. Because this is not a trivial move, the - * src component must also be destructed. */ - callbacks->move(world, component, dst_entity, src_entity, - dst_ptr, src_ptr, size, count, ctx); - callbacks->dtor(world, component, src_entity, src_ptr, size, count, ctx); +const char* get_var_name(const char *name) { + if (name && !ecs_os_strcmp(name, "This")) { + /* Make sure that both This and . resolve to the same variable */ + name = "."; + } + + return name; } -void ecs_set_component_actions_w_id( - ecs_world_t *world, - ecs_entity_t component, - EcsComponentLifecycle *lifecycle) +static +ecs_rule_var_t* create_variable( + ecs_rule_t *rule, + ecs_rule_var_kind_t kind, + const char *name) { - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - flecs_stage_from_world(&world); - - const EcsComponent *component_ptr = ecs_get(world, component, EcsComponent); - - /* Cannot register lifecycle actions for things that aren't a component */ - ecs_check(component_ptr != NULL, ECS_INVALID_PARAMETER, NULL); - - /* Cannot register lifecycle actions for components with size 0 */ - ecs_check(component_ptr->size != 0, ECS_INVALID_PARAMETER, NULL); + int32_t cur = ++ rule->variable_count; + rule->variables = ecs_os_realloc( + rule->variables, cur * ECS_SIZEOF(ecs_rule_var_t)); - ecs_type_info_t *c_info = flecs_get_or_create_c_info(world, component); - ecs_assert(c_info != NULL, ECS_INTERNAL_ERROR, NULL); + name = get_var_name(name); - if (c_info->lifecycle_set) { - ecs_assert(c_info->component == component, ECS_INTERNAL_ERROR, NULL); - ecs_check(c_info->lifecycle.ctor == lifecycle->ctor, - ECS_INCONSISTENT_COMPONENT_ACTION, NULL); - ecs_check(c_info->lifecycle.dtor == lifecycle->dtor, - ECS_INCONSISTENT_COMPONENT_ACTION, NULL); - ecs_check(c_info->lifecycle.copy == lifecycle->copy, - ECS_INCONSISTENT_COMPONENT_ACTION, NULL); - ecs_check(c_info->lifecycle.move == lifecycle->move, - ECS_INCONSISTENT_COMPONENT_ACTION, NULL); + ecs_rule_var_t *var = &rule->variables[cur - 1]; + if (name) { + var->name = ecs_os_strdup(name); } else { - c_info->component = component; - c_info->lifecycle = *lifecycle; - c_info->lifecycle_set = true; - c_info->size = component_ptr->size; - c_info->alignment = component_ptr->alignment; - - /* If no constructor is set, invoking any of the other lifecycle actions - * is not safe as they will potentially access uninitialized memory. For - * ease of use, if no constructor is specified, set a default one that - * initializes the component to 0. */ - if (!lifecycle->ctor && - (lifecycle->dtor || lifecycle->copy || lifecycle->move)) - { - c_info->lifecycle.ctor = ecs_default_ctor; - } - - /* Set default copy ctor, move ctor and merge */ - if (lifecycle->copy && !lifecycle->copy_ctor) { - c_info->lifecycle.copy_ctor = default_copy_ctor; - } + /* Anonymous register */ + char name_buff[32]; + ecs_os_sprintf(name_buff, "_%u", cur - 1); + var->name = ecs_os_strdup(name_buff); + } - if (lifecycle->move && !lifecycle->move_ctor) { - c_info->lifecycle.move_ctor = default_move_ctor; - } + var->kind = kind; - if (!lifecycle->ctor_move_dtor) { - if (lifecycle->move) { - if (lifecycle->dtor) { - if (lifecycle->move_ctor) { - /* If an explicit move ctor has been set, use callback - * that uses the move ctor vs. using a ctor+move */ - c_info->lifecycle.ctor_move_dtor = - default_move_ctor_w_dtor; - } else { - /* If no explicit move_ctor has been set, use - * combination of ctor + move + dtor */ - c_info->lifecycle.ctor_move_dtor = - default_ctor_w_move_w_dtor; - } - } else { - /* If no dtor has been set, this is just a move ctor */ - c_info->lifecycle.ctor_move_dtor = - c_info->lifecycle.move_ctor; - } - } - } + /* The variable id is the location in the variable array and also points to + * the register element that corresponds with the variable. */ + var->id = cur - 1; - if (!lifecycle->move_dtor) { - if (lifecycle->move) { - if (lifecycle->dtor) { - c_info->lifecycle.move_dtor = default_move_w_dtor; - } else { - c_info->lifecycle.move_dtor = default_move; - } - } else { - if (lifecycle->dtor) { - c_info->lifecycle.move_dtor = default_dtor; - } - } - } + /* Depth is used to calculate how far the variable is from the root, where + * the root is the variable with 0 dependencies. */ + var->depth = UINT8_MAX; + var->marked = false; + var->occurs = 0; - /* Broadcast to all tables since we need to register a ctor for every - * table that uses the component as itself, as predicate or as object. - * The latter is what makes selecting the right set of tables complex, - * as it depends on the predicate of a pair whether the object is used - * as the component type or not. - * A more selective approach requires a more expressive notification - * framework. */ - flecs_notify_tables(world, 0, &(ecs_table_event_t) { - .kind = EcsTableComponentInfo, - .component = component - }); - } -error: - return; + return var; } -bool ecs_component_has_actions( - const ecs_world_t *world, - ecs_entity_t component) +static +ecs_rule_var_t* create_anonymous_variable( + ecs_rule_t *rule, + ecs_rule_var_kind_t kind) { - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(component != 0, ECS_INVALID_PARAMETER, NULL); - - world = ecs_get_world(world); - const ecs_type_info_t *c_info = flecs_get_c_info(world, component); - return (c_info != NULL) && c_info->lifecycle_set; -error: - return false; + return create_variable(rule, kind, NULL); } -void ecs_atfini( - ecs_world_t *world, - ecs_fini_action_t action, - void *ctx) +/* Find variable with specified name and type. If Unknown is provided as type, + * the function will return any variable with the provided name. The root + * variable can occur both as a table and entity variable, as some rules + * require that each entity in a table is iterated. In this case, there are two + * variables, one for the table and one for the entities in the table, that both + * have the same name. */ +static +ecs_rule_var_t* find_variable( + const ecs_rule_t *rule, + ecs_rule_var_kind_t kind, + const char *name) { - ecs_poly_assert(world, ecs_world_t); - ecs_check(action != NULL, ECS_INVALID_PARAMETER, NULL); - - ecs_action_elem_t *elem = ecs_vector_add(&world->fini_actions, - ecs_action_elem_t); - ecs_assert(elem != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(rule != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(name != NULL, ECS_INTERNAL_ERROR, NULL); - elem->action = action; - elem->ctx = ctx; -error: - return; -} + name = get_var_name(name); -void ecs_run_post_frame( - ecs_world_t *world, - ecs_fini_action_t action, - void *ctx) -{ - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(action != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_rule_var_t *variables = rule->variables; + int32_t i, count = rule->variable_count; - ecs_stage_t *stage = flecs_stage_from_world(&world); - ecs_action_elem_t *elem = ecs_vector_add(&stage->post_frame_actions, - ecs_action_elem_t); - ecs_assert(elem != NULL, ECS_INTERNAL_ERROR, NULL); + for (i = 0; i < count; i ++) { + ecs_rule_var_t *variable = &variables[i]; + if (!ecs_os_strcmp(name, variable->name)) { + if (kind == EcsRuleVarKindUnknown || kind == variable->kind) { + return variable; + } + } + } - elem->action = action; - elem->ctx = ctx; -error: - return; + return NULL; } -/* Unset data in tables */ +/* Ensure variable with specified name and type exists. If an existing variable + * is found with an unknown type, its type will be overwritten with the + * specified type. During the variable ordering phase it is not yet clear which + * variable is the root. Which variable is the root determines its type, which + * is why during this phase variables are still untyped. */ static -void fini_unset_tables( - ecs_world_t *world) +ecs_rule_var_t* ensure_variable( + ecs_rule_t *rule, + ecs_rule_var_kind_t kind, + const char *name) { - ecs_sparse_t *tables = world->store.tables; - int32_t i, count = flecs_sparse_count(tables); - - for (i = 0; i < count; i ++) { - ecs_table_t *table = flecs_sparse_get_dense(tables, ecs_table_t, i); - flecs_table_remove_actions(world, table); + ecs_rule_var_t *var = find_variable(rule, kind, name); + if (!var) { + var = create_variable(rule, kind, name); + } else { + if (var->kind == EcsRuleVarKindUnknown) { + var->kind = kind; + } } + + return var; } -/* Invoke fini actions */ static -void fini_actions( - ecs_world_t *world) +bool term_id_is_variable( + ecs_term_id_t *term_id) { - ecs_vector_each(world->fini_actions, ecs_action_elem_t, elem, { - elem->action(world, elem->ctx); - }); + return term_id->var == EcsVarIsVariable; +} - ecs_vector_free(world->fini_actions); +static +const char *term_id_var_name( + ecs_term_id_t *term_id) +{ + if (term_id->var == EcsVarIsVariable) { + if (term_id->entity == EcsThis) { + return "."; + } else { + ecs_check(term_id->name != NULL, ECS_INVALID_PARAMETER, NULL); + return term_id->name; + } + } + +error: + return NULL; } -/* Cleanup component lifecycle callbacks & systems */ +/* Get variable from a term identifier */ static -void fini_component_lifecycle( - ecs_world_t *world) +ecs_rule_var_t* term_id_to_var( + ecs_rule_t *rule, + ecs_term_id_t *id) { - flecs_sparse_free(world->type_info); + if (id->var == EcsVarIsVariable) {; + return find_variable(rule, EcsRuleVarKindUnknown, term_id_var_name(id)); + } + return NULL; } -/* Cleanup queries */ +/* Get variable from a term predicate */ static -void fini_queries( - ecs_world_t *world) +ecs_rule_var_t* term_pred( + ecs_rule_t *rule, + ecs_term_t *term) { - int32_t i, count = flecs_sparse_count(world->queries); - for (i = 0; i < count; i ++) { - ecs_query_t *query = flecs_sparse_get_dense(world->queries, ecs_query_t, 0); - ecs_query_fini(query); - } - flecs_sparse_free(world->queries); + return term_id_to_var(rule, &term->pred); } +/* Get variable from a term subject */ static -void fini_observers( - ecs_world_t *world) +ecs_rule_var_t* term_subj( + ecs_rule_t *rule, + ecs_term_t *term) { - flecs_sparse_free(world->observers); + return term_id_to_var(rule, &term->subj); } -/* Cleanup stages */ +/* Get variable from a term object */ static -void fini_stages( - ecs_world_t *world) +ecs_rule_var_t* term_obj( + ecs_rule_t *rule, + ecs_term_t *term) { - flecs_stage_deinit(world, &world->stage); - ecs_set_stages(world, 0); + if (obj_is_set(term)) { + return term_id_to_var(rule, &term->obj); + } else { + return NULL; + } } -/* Cleanup id index */ +/* Return predicate variable from pair */ static -void fini_id_index( - ecs_world_t *world) +ecs_rule_var_t* pair_pred( + ecs_rule_t *rule, + const ecs_rule_pair_t *pair) { - ecs_map_iter_t it = ecs_map_iter(world->id_index); - ecs_id_record_t *idr; - while ((idr = ecs_map_next(&it, ecs_id_record_t, NULL))) { - ecs_table_cache_fini(&idr->cache); - ecs_map_free(idr->add_refs); - ecs_map_free(idr->remove_refs); + if (pair->reg_mask & RULE_PAIR_PREDICATE) { + return &rule->variables[pair->pred.reg]; + } else { + return NULL; } - - ecs_map_free(world->id_index); } -/* Cleanup aliases & symbols */ +/* Return object variable from pair */ static -void fini_aliases( - ecs_hashmap_t *map) +ecs_rule_var_t* pair_obj( + ecs_rule_t *rule, + const ecs_rule_pair_t *pair) { - flecs_hashmap_iter_t it = flecs_hashmap_iter(*map); - ecs_hashed_string_t *key; - while (flecs_hashmap_next_w_key(&it, ecs_hashed_string_t, &key, ecs_entity_t)) { - ecs_os_free(key->value); + if (pair->reg_mask & RULE_PAIR_OBJECT) { + return &rule->variables[pair->obj.reg]; + } else { + return NULL; } - - flecs_hashmap_free(*map); } -/* Cleanup misc structures */ +/* Create new frame for storing register values. Each operation that yields data + * gets its own register frame, which contains all variables reified up to that + * point. The preceding frame always contains the reified variables from the + * previous operation. Operations that do not yield data (such as control flow) + * do not have their own frames. */ static -void fini_misc( - ecs_world_t *world) +int32_t push_frame( + ecs_rule_t *rule) { - ecs_map_free(world->type_handles); - ecs_vector_free(world->fini_tasks); - monitors_fini(&world->monitors); + return rule->frame_count ++; } -/* The destroyer of worlds */ -int ecs_fini( - ecs_world_t *world) +/* Get register array for current stack frame. The stack frame is determined by + * the current operation that is evaluated. The register array contains the + * values for the reified variables. If a variable hasn't been reified yet, its + * register will store a wildcard. */ +static +ecs_rule_reg_t* get_register_frame( + ecs_rule_iter_t *it, + int32_t frame) { - ecs_poly_assert(world, ecs_world_t); - ecs_assert(!world->is_readonly, ECS_INVALID_OPERATION, NULL); - ecs_assert(!world->is_fini, ECS_INVALID_OPERATION, NULL); - - ecs_trace("#[bold]shutting down world"); - ecs_log_push(); - - world->is_fini = true; - - /* Operations invoked during UnSet/OnRemove/destructors are deferred and - * will be discarded after world cleanup */ - ecs_defer_begin(world); - - /* Run UnSet/OnRemove actions for components while the store is still - * unmodified by cleanup. */ - fini_unset_tables(world); - - /* Run fini actions (simple callbacks ran when world is deleted) before - * destroying the storage */ - fini_actions(world); - - /* This will destroy all entities and components. After this point no more - * user code is executed. */ - fini_store(world); - - /* Purge deferred operations from the queue. This discards operations but - * makes sure that any resources in the queue are freed */ - flecs_defer_purge(world, &world->stage); - - /* Entity index is kept alive until this point so that user code can do - * validity checks on entity ids, even though after store cleanup the index - * will be empty, so all entity ids are invalid. */ - flecs_sparse_free(world->store.entity_index); - - if (world->locking_enabled) { - ecs_os_mutex_free(world->mutex); + if (it->registers) { + return &it->registers[frame * it->rule->variable_count]; + } else { + return NULL; } +} - ecs_trace("table store deinitialized"); - - fini_stages(world); - - fini_component_lifecycle(world); - - fini_queries(world); - - fini_observers(world); - - fini_id_index(world); - - flecs_observable_fini(&world->observable); - - flecs_sparse_free(world->triggers); - - fini_aliases(&world->aliases); - - fini_aliases(&world->symbols); - - fini_misc(world); - - flecs_increase_timer_resolution(0); - - /* End of the world */ - ecs_poly_free(world, ecs_world_t); - - ecs_os_fini(); - - ecs_trace("world destroyed, bye!"); - ecs_log_pop(); - - return 0; +/* Get register array for current stack frame. The stack frame is determined by + * the current operation that is evaluated. The register array contains the + * values for the reified variables. If a variable hasn't been reified yet, its + * register will store a wildcard. */ +static +ecs_rule_reg_t* get_registers( + ecs_rule_iter_t *it, + ecs_rule_op_t *op) +{ + return get_register_frame(it, op->frame); } -void ecs_dim( - ecs_world_t *world, - int32_t entity_count) +/* Get columns array. Columns store, for each matched column in a table, the + * index at which it occurs. This reduces the amount of searching that + * operations need to do in a type, since select/with already provide it. */ +static +int32_t* rule_get_columns_frame( + ecs_rule_iter_t *it, + int32_t frame) { - ecs_poly_assert(world, ecs_world_t); - ecs_eis_set_size(world, entity_count + ECS_HI_COMPONENT_ID); + return &it->columns[frame * it->rule->filter.term_count]; } -void flecs_eval_component_monitors( - ecs_world_t *world) +static +int32_t* rule_get_columns( + ecs_rule_iter_t *it, + ecs_rule_op_t *op) { - ecs_poly_assert(world, ecs_world_t); - eval_component_monitor(world); + return rule_get_columns_frame(it, op->frame); } -void ecs_measure_frame_time( +static +ecs_table_t* table_from_entity( ecs_world_t *world, - bool enable) + ecs_entity_t e) { - ecs_poly_assert(world, ecs_world_t); - ecs_check(ecs_os_has_time(), ECS_MISSING_OS_API, NULL); - - if (world->stats.target_fps == 0.0f || enable) { - world->measure_frame_time = enable; + ecs_record_t *record = ecs_eis_get(world, e); + if (record) { + return record->table; + } else { + return NULL; } -error: - return; } -void ecs_measure_system_time( - ecs_world_t *world, - bool enable) +static +void entity_reg_set( + const ecs_rule_t *rule, + ecs_rule_reg_t *regs, + int32_t r, + ecs_entity_t entity) { - ecs_poly_assert(world, ecs_world_t); - ecs_check(ecs_os_has_time(), ECS_MISSING_OS_API, NULL); - world->measure_system_time = enable; + (void)rule; + ecs_assert(rule->variables[r].kind == EcsRuleVarKindEntity, + ECS_INTERNAL_ERROR, NULL); + ecs_check(ecs_is_valid(rule->world, entity), ECS_INVALID_PARAMETER, NULL); + regs[r].entity = entity; error: return; } -/* Increase timer resolution based on target fps */ -static -void set_timer_resolution( - FLECS_FLOAT fps) +static +ecs_entity_t entity_reg_get( + const ecs_rule_t *rule, + ecs_rule_reg_t *regs, + int32_t r) { - if(fps >= 60.0f) flecs_increase_timer_resolution(1); - else flecs_increase_timer_resolution(0); + (void)rule; + ecs_entity_t e = regs[r].entity; + if (!e) { + return EcsWildcard; + } + + ecs_check(ecs_is_valid(rule->world, e), ECS_INVALID_PARAMETER, NULL); + return e; +error: + return 0; } -void ecs_set_target_fps( - ecs_world_t *world, - FLECS_FLOAT fps) +static +void table_reg_set( + const ecs_rule_t *rule, + ecs_rule_reg_t *regs, + int32_t r, + ecs_table_t *table) { - ecs_poly_assert(world, ecs_world_t); - ecs_check(ecs_os_has_time(), ECS_MISSING_OS_API, NULL); + (void)rule; + ecs_assert(rule->variables[r].kind == EcsRuleVarKindTable, + ECS_INTERNAL_ERROR, NULL); - ecs_measure_frame_time(world, true); - world->stats.target_fps = fps; - set_timer_resolution(fps); -error: - return; + regs[r].table = table; + regs[r].offset = 0; + regs[r].count = 0; + regs[r].entity = 0; } -void* ecs_get_context( - const ecs_world_t *world) +static +ecs_table_t* table_reg_get( + const ecs_rule_t *rule, + ecs_rule_reg_t *regs, + int32_t r) { - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - world = ecs_get_world(world); - return world->context; -error: - return NULL; -} + (void)rule; + ecs_assert(rule->variables[r].kind == EcsRuleVarKindTable, + ECS_INTERNAL_ERROR, NULL); -void ecs_set_context( - ecs_world_t *world, - void *context) -{ - ecs_poly_assert(world, ecs_world_t); - world->context = context; + return regs[r].table; } -void ecs_set_entity_range( - ecs_world_t *world, - ecs_entity_t id_start, - ecs_entity_t id_end) +static +ecs_entity_t reg_get_entity( + const ecs_rule_t *rule, + ecs_rule_op_t *op, + ecs_rule_reg_t *regs, + int32_t r) { - ecs_poly_assert(world, ecs_world_t); - ecs_check(!id_end || id_end > id_start, ECS_INVALID_PARAMETER, NULL); - ecs_check(!id_end || id_end > world->stats.last_id, - ECS_INVALID_PARAMETER, NULL); + if (r == UINT8_MAX) { + ecs_assert(op->subject != 0, ECS_INTERNAL_ERROR, NULL); - if (world->stats.last_id < id_start) { - world->stats.last_id = id_start - 1; + /* The subject is referenced from the query string by string identifier. + * If subject entity is not valid, it could have been deletd by the + * application after the rule was created */ + ecs_check(ecs_is_valid(rule->world, op->subject), + ECS_INVALID_PARAMETER, NULL); + + return op->subject; } + if (rule->variables[r].kind == EcsRuleVarKindTable) { + int32_t offset = regs[r].offset; + + ecs_assert(regs[r].count == 1, ECS_INTERNAL_ERROR, NULL); + ecs_data_t *data = &table_reg_get(rule, regs, r)->storage; + ecs_assert(data != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_entity_t *entities = ecs_vector_first(data->entities, ecs_entity_t); + ecs_assert(entities != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(offset < ecs_vector_count(data->entities), + ECS_INTERNAL_ERROR, NULL); + ecs_check(ecs_is_valid(rule->world, entities[offset]), + ECS_INVALID_PARAMETER, NULL); + + return entities[offset]; + } + if (rule->variables[r].kind == EcsRuleVarKindEntity) { + return entity_reg_get(rule, regs, r); + } + + /* Must return an entity */ + ecs_assert(false, ECS_INTERNAL_ERROR, NULL); - world->stats.min_id = id_start; - world->stats.max_id = id_end; error: - return; + return 0; } -bool ecs_enable_range_check( - ecs_world_t *world, - bool enable) +static +ecs_table_t* reg_get_table( + const ecs_rule_t *rule, + ecs_rule_op_t *op, + ecs_rule_reg_t *regs, + int32_t r) { - ecs_poly_assert(world, ecs_world_t); - bool old_value = world->range_check_enabled; - world->range_check_enabled = enable; - return old_value; -} + if (r == UINT8_MAX) { + ecs_assert(op->subject != 0, ECS_INTERNAL_ERROR, NULL); + ecs_check(ecs_is_valid(rule->world, op->subject), + ECS_INVALID_PARAMETER, NULL); -int32_t ecs_get_threads( - ecs_world_t *world) -{ - return ecs_vector_count(world->worker_stages); + return table_from_entity(rule->world, op->subject); + } + if (rule->variables[r].kind == EcsRuleVarKindTable) { + return table_reg_get(rule, regs, r); + } + if (rule->variables[r].kind == EcsRuleVarKindEntity) { + return table_from_entity(rule->world, entity_reg_get(rule, regs, r)); + } +error: + return NULL; } -bool ecs_enable_locking( - ecs_world_t *world, - bool enable) +static +void reg_set_entity( + const ecs_rule_t *rule, + ecs_rule_reg_t *regs, + int32_t r, + ecs_entity_t entity) { - ecs_poly_assert(world, ecs_world_t); + if (rule->variables[r].kind == EcsRuleVarKindTable) { + ecs_world_t *world = rule->world; + ecs_check(ecs_is_valid(world, entity), ECS_INVALID_PARAMETER, NULL); - if (enable) { - if (!world->locking_enabled) { - world->mutex = ecs_os_mutex_new(); - world->thr_sync = ecs_os_mutex_new(); - world->thr_cond = ecs_os_cond_new(); + ecs_record_t *record = ecs_eis_get(world, entity); + if (!record || !record->table) { + regs[r].table = NULL; + regs[r].offset = 0; + regs[r].count = 0; + regs[r].entity = entity; + } else { + regs[r].table = record->table; + regs[r].offset = ECS_RECORD_TO_ROW(record->row); + regs[r].count = 1; + regs[r].entity = 0; } } else { - if (world->locking_enabled) { - ecs_os_mutex_free(world->mutex); - ecs_os_mutex_free(world->thr_sync); - ecs_os_cond_free(world->thr_cond); - } + entity_reg_set(rule, regs, r, entity); } - - bool old = world->locking_enabled; - world->locking_enabled = enable; - return old; +error: + return; } -void ecs_lock( - ecs_world_t *world) +/* This encodes a column expression into a pair. A pair stores information about + * the variable(s) associated with the column. Pairs are used by operations to + * apply filters, and when there is a match, to reify variables. */ +static +ecs_rule_pair_t term_to_pair( + ecs_rule_t *rule, + ecs_term_t *term) { - ecs_poly_assert(world, ecs_world_t); - ecs_assert(world->locking_enabled, ECS_INVALID_PARAMETER, NULL); - ecs_os_mutex_lock(world->mutex); -} + ecs_rule_pair_t result = {0}; -void ecs_unlock( - ecs_world_t *world) -{ - ecs_poly_assert(world, ecs_world_t); - ecs_assert(world->locking_enabled, ECS_INVALID_PARAMETER, NULL); - ecs_os_mutex_unlock(world->mutex); -} + /* Terms must always have at least one argument (the subject) */ + ecs_assert(subj_is_set(term), ECS_INTERNAL_ERROR, NULL); -void ecs_begin_wait( - ecs_world_t *world) -{ - ecs_poly_assert(world, ecs_world_t); - ecs_assert(world->locking_enabled, ECS_INVALID_PARAMETER, NULL); - ecs_os_mutex_lock(world->thr_sync); - ecs_os_cond_wait(world->thr_cond, world->thr_sync); -} + /* If the predicate id is a variable, find the variable and encode its id + * in the pair so the operation can find it later. */ + if (term->pred.var == EcsVarIsVariable) { + /* Always lookup the as an entity, as pairs never refer to tables */ + const ecs_rule_var_t *var = find_variable( + rule, EcsRuleVarKindEntity, term_id_var_name(&term->pred)); -void ecs_end_wait( - ecs_world_t *world) -{ - ecs_poly_assert(world, ecs_world_t); - ecs_assert(world->locking_enabled, ECS_INVALID_PARAMETER, NULL); - ecs_os_mutex_unlock(world->thr_sync); -} + /* Variables should have been declared */ + ecs_assert(var != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(var->kind == EcsRuleVarKindEntity, ECS_INTERNAL_ERROR, NULL); + result.pred.reg = var->id; -const ecs_type_info_t* flecs_get_c_info( - const ecs_world_t *world, - ecs_entity_t component) -{ - ecs_poly_assert(world, ecs_world_t); + /* Set flag so the operation can see that the predicate is a variable */ + result.reg_mask |= RULE_PAIR_PREDICATE; + result.final = true; + } else { + /* If the predicate is not a variable, simply store its id. */ + ecs_entity_t pred_id = term->pred.entity; + result.pred.ent = pred_id; + + /* Test if predicate is transitive. When evaluating the predicate, this + * will also take into account transitive relationships */ + if (ecs_has_id(rule->world, pred_id, EcsTransitive)) { + /* Transitive queries must have an object */ + if (obj_is_set(term)) { + result.transitive = true; + } + } + + if (ecs_has_id(rule->world, pred_id, EcsFinal)) { + result.final = true; + } + + if (ecs_has_id(rule->world, pred_id, EcsTransitiveSelf)) { + result.inclusive = true; + } + } + + /* The pair doesn't do anything with the subject (subjects are the things that + * are matched against pairs) so if the column does not have a object, + * there is nothing left to do. */ + if (!obj_is_set(term)) { + return result; + } - ecs_assert(component != 0, ECS_INTERNAL_ERROR, NULL); - ecs_assert(!(component & ECS_ROLE_MASK), ECS_INTERNAL_ERROR, NULL); + /* If arguments is higher than 2 this is not a pair but a nested rule */ + ecs_assert(obj_is_set(term), ECS_INTERNAL_ERROR, NULL); - return flecs_sparse_get(world->type_info, ecs_type_info_t, component); -} + /* Same as above, if the object is a variable, store it and flag it */ + if (term->obj.var == EcsVarIsVariable) { + const ecs_rule_var_t *var = find_variable( + rule, EcsRuleVarKindEntity, term_id_var_name(&term->obj)); -ecs_type_info_t* flecs_get_or_create_c_info( - ecs_world_t *world, - ecs_entity_t component) -{ - ecs_poly_assert(world, ecs_world_t); + /* Variables should have been declared */ + ecs_assert(var != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(var->kind == EcsRuleVarKindEntity, ECS_INTERNAL_ERROR, NULL); - const ecs_type_info_t *c_info = flecs_get_c_info(world, component); - ecs_type_info_t *c_info_mut = NULL; - if (!c_info) { - c_info_mut = flecs_sparse_ensure( - world->type_info, ecs_type_info_t, component); - ecs_assert(c_info_mut != NULL, ECS_INTERNAL_ERROR, NULL); + result.obj.reg = var->id; + result.reg_mask |= RULE_PAIR_OBJECT; } else { - c_info_mut = (ecs_type_info_t*)c_info; + /* If the object is not a variable, simply store its id */ + result.obj.ent = term->obj.entity; + if (!result.obj.ent) { + result.obj_0 = true; + } } - return c_info_mut; + return result; } +/* When an operation has a pair, it is used to filter its input. This function + * translates a pair back into an entity id, and in the process substitutes the + * variables that have already been filled out. It's one of the most important + * functions, as a lot of the filtering logic depends on having an entity that + * has all of the reified variables correctly filled out. */ static -FLECS_FLOAT insert_sleep( - ecs_world_t *world, - ecs_time_t *stop) +ecs_rule_filter_t pair_to_filter( + ecs_rule_iter_t *it, + ecs_rule_op_t *op, + ecs_rule_pair_t pair) { - ecs_poly_assert(world, ecs_world_t); + ecs_entity_t pred = pair.pred.ent; + ecs_entity_t obj = pair.obj.ent; + ecs_rule_filter_t result = { + .lo_var = -1, + .hi_var = -1 + }; - ecs_time_t start = *stop; - FLECS_FLOAT delta_time = (FLECS_FLOAT)ecs_time_measure(stop); + /* Get registers in case we need to resolve ids from registers. Get them + * from the previous, not the current stack frame as the current operation + * hasn't reified its variables yet. */ + ecs_rule_reg_t *regs = get_register_frame(it, op->frame - 1); - if (world->stats.target_fps == (FLECS_FLOAT)0.0) { - return delta_time; + if (pair.reg_mask & RULE_PAIR_OBJECT) { + obj = entity_reg_get(it->rule, regs, pair.obj.reg); + obj = ecs_entity_t_lo(obj); /* Filters don't have generations */ + + if (obj == EcsWildcard) { + result.wildcard = true; + result.obj_wildcard = true; + result.lo_var = pair.obj.reg; + } } - FLECS_FLOAT target_delta_time = - ((FLECS_FLOAT)1.0 / (FLECS_FLOAT)world->stats.target_fps); + if (pair.reg_mask & RULE_PAIR_PREDICATE) { + pred = entity_reg_get(it->rule, regs, pair.pred.reg); + pred = ecs_entity_t_lo(pred); /* Filters don't have generations */ - /* Calculate the time we need to sleep by taking the measured delta from the - * previous frame, and subtracting it from target_delta_time. */ - FLECS_FLOAT sleep = target_delta_time - delta_time; + if (pred == EcsWildcard) { + if (result.wildcard) { + result.same_var = pair.pred.reg == pair.obj.reg; + } - /* Pick a sleep interval that is 4 times smaller than the time one frame - * should take. */ - FLECS_FLOAT sleep_time = sleep / (FLECS_FLOAT)4.0; + result.wildcard = true; + result.pred_wildcard = true; - do { - /* Only call sleep when sleep_time is not 0. On some platforms, even - * a sleep with a timeout of 0 can cause stutter. */ - if (sleep_time != 0) { - ecs_sleepf((double)sleep_time); + if (obj) { + result.hi_var = pair.pred.reg; + } else { + result.lo_var = pair.pred.reg; + } } + } - ecs_time_t now = start; - delta_time = (FLECS_FLOAT)ecs_time_measure(&now); - } while ((target_delta_time - delta_time) > - (sleep_time / (FLECS_FLOAT)2.0)); + if (!obj && !pair.obj_0) { + result.mask = pred; + } else { + result.mask = ecs_pair(pred, obj); + } - return delta_time; + return result; } +/* This function is responsible for reifying the variables (filling them out + * with their actual values as soon as they are known). It uses the pair + * expression returned by pair_get_most_specific_var, and attempts to fill out each of the + * wildcards in the pair. If a variable isn't reified yet, the pair expression + * will still contain one or more wildcards, which is harmless as the respective + * registers will also point to a wildcard. */ static -FLECS_FLOAT start_measure_frame( - ecs_world_t *world, - FLECS_FLOAT user_delta_time) +void reify_variables( + ecs_rule_iter_t *it, + ecs_rule_op_t *op, + ecs_rule_filter_t *filter, + ecs_type_t type, + int32_t column) { - ecs_poly_assert(world, ecs_world_t); - - FLECS_FLOAT delta_time = 0; + const ecs_rule_t *rule = it->rule; + const ecs_rule_var_t *vars = rule->variables; + (void)vars; - if (world->measure_frame_time || (user_delta_time == 0)) { - ecs_time_t t = world->frame_start_time; - do { - if (world->frame_start_time.sec) { - delta_time = insert_sleep(world, &t); + ecs_rule_reg_t *regs = get_registers(it, op); + ecs_entity_t *elem = ecs_vector_get(type, ecs_entity_t, column); + ecs_assert(elem != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_time_measure(&t); - } else { - ecs_time_measure(&t); - if (world->stats.target_fps != 0) { - delta_time = (FLECS_FLOAT)1.0 / world->stats.target_fps; - } else { - /* Best guess */ - delta_time = (FLECS_FLOAT)1.0 / (FLECS_FLOAT)60.0; - } - } - - /* Keep trying while delta_time is zero */ - } while (delta_time == 0); + int32_t obj_var = filter->lo_var; + int32_t pred_var = filter->hi_var; - world->frame_start_time = t; + if (obj_var != -1) { + ecs_assert(vars[obj_var].kind == EcsRuleVarKindEntity, + ECS_INTERNAL_ERROR, NULL); - /* Keep track of total time passed in world */ - world->stats.world_time_total_raw += (FLECS_FLOAT)delta_time; + entity_reg_set(rule, regs, obj_var, + ecs_get_alive(rule->world, ECS_PAIR_OBJECT(*elem))); } - return (FLECS_FLOAT)delta_time; -} - -static -void stop_measure_frame( - ecs_world_t* world) -{ - ecs_poly_assert(world, ecs_world_t); + if (pred_var != -1) { + ecs_assert(vars[pred_var].kind == EcsRuleVarKindEntity, + ECS_INTERNAL_ERROR, NULL); - if (world->measure_frame_time) { - ecs_time_t t = world->frame_start_time; - world->stats.frame_time_total += (FLECS_FLOAT)ecs_time_measure(&t); + entity_reg_set(rule, regs, pred_var, + ecs_get_alive(rule->world, + ECS_PAIR_RELATION(*elem))); } } -FLECS_FLOAT ecs_frame_begin( - ecs_world_t *world, - FLECS_FLOAT user_delta_time) +/* Returns whether variable is a subject */ +static +bool is_subject( + ecs_rule_t *rule, + ecs_rule_var_t *var) { - ecs_poly_assert(world, ecs_world_t); - ecs_check(world->is_readonly == false, ECS_INVALID_OPERATION, NULL); - ecs_check(user_delta_time != 0 || ecs_os_has_time(), - ECS_MISSING_OS_API, "get_time"); + ecs_assert(rule != NULL, ECS_INTERNAL_ERROR, NULL); - if (world->locking_enabled) { - ecs_lock(world); + if (!var) { + return false; } - /* Start measuring total frame time */ - FLECS_FLOAT delta_time = start_measure_frame(world, user_delta_time); - if (user_delta_time == 0) { - user_delta_time = delta_time; - } - - world->stats.delta_time_raw = user_delta_time; - world->stats.delta_time = user_delta_time * world->stats.time_scale; - - /* Keep track of total scaled time passed in world */ - world->stats.world_time_total += world->stats.delta_time; - - flecs_eval_component_monitors(world); + if (var->id < rule->subject_variable_count) { + return true; + } - return world->stats.delta_time; -error: - return (FLECS_FLOAT)0; + return false; } -void ecs_frame_end( - ecs_world_t *world) -{ - ecs_poly_assert(world, ecs_world_t); - ecs_check(world->is_readonly == false, ECS_INVALID_OPERATION, NULL); - - world->stats.frame_count_total ++; - - ecs_vector_each(world->worker_stages, ecs_stage_t, stage, { - flecs_stage_merge_post_frame(world, stage); - }); - - if (world->locking_enabled) { - ecs_unlock(world); - - ecs_os_mutex_lock(world->thr_sync); - ecs_os_cond_broadcast(world->thr_cond); - ecs_os_mutex_unlock(world->thr_sync); +static +bool skip_term(ecs_term_t *term) { + if (term->subj.set.mask & EcsNothing) { + return true; + } + if (term->oper == EcsNot) { + return true; } - stop_measure_frame(world); -error: - return; + return false; } -const ecs_world_info_t* ecs_get_world_info( - const ecs_world_t *world) -{ - world = ecs_get_world(world); - return &world->stats; -} +static +int32_t get_variable_depth( + ecs_rule_t *rule, + ecs_rule_var_t *var, + ecs_rule_var_t *root, + int recur); -void flecs_notify_queries( - ecs_world_t *world, - ecs_query_event_t *event) +static +int32_t crawl_variable( + ecs_rule_t *rule, + ecs_rule_var_t *var, + ecs_rule_var_t *root, + int recur) { - ecs_poly_assert(world, ecs_world_t); + ecs_term_t *terms = rule->filter.terms; + int32_t i, count = rule->filter.term_count; - int32_t i, count = flecs_sparse_count(world->queries); for (i = 0; i < count; i ++) { - ecs_query_t *query = flecs_sparse_get_dense( - world->queries, ecs_query_t, i); - if (query->flags & EcsQueryIsSubquery) { + ecs_term_t *term = &terms[i]; + if (skip_term(term)) { continue; } - flecs_query_notify(world, query, event); - } -} + ecs_rule_var_t + *pred = term_pred(rule, term), + *subj = term_subj(rule, term), + *obj = term_obj(rule, term); -void flecs_delete_table( - ecs_world_t *world, - ecs_table_t *table) -{ - ecs_poly_assert(world, ecs_world_t); + /* Variable must at least appear once in term */ + if (var != pred && var != subj && var != obj) { + continue; + } - /* Notify queries that table is to be removed */ - flecs_notify_queries( - world, &(ecs_query_event_t){ - .kind = EcsQueryTableUnmatch, - .table = table - }); + if (pred && pred != var && !pred->marked) { + get_variable_depth(rule, pred, root, recur + 1); + } - uint64_t id = table->id; + if (subj && subj != var && !subj->marked) { + get_variable_depth(rule, subj, root, recur + 1); + } - /* Free resources associated with table */ - flecs_table_free(world, table); - flecs_table_free_type(table); + if (obj && obj != var && !obj->marked) { + get_variable_depth(rule, obj, root, recur + 1); + } + } - /* Remove table from sparse set */ - ecs_assert(id != 0, ECS_INTERNAL_ERROR, NULL); - flecs_sparse_remove(world->store.tables, id); + return 0; } static -void register_table( - ecs_world_t *world, - ecs_table_t *table, - ecs_id_t id, - int32_t column) +int32_t get_depth_from_var( + ecs_rule_t *rule, + ecs_rule_var_t *var, + ecs_rule_var_t *root, + int recur) { - ecs_id_record_t *idr = flecs_ensure_id_record(world, id); - ecs_assert(idr != NULL, ECS_INTERNAL_ERROR, NULL); - - ecs_table_record_t *tr = ecs_table_cache_get( - &idr->cache, ecs_table_record_t, table); - if (tr) { - /* Tables can be registered multiple times for wildcard caches */ - tr->count ++; - } else { - tr = ecs_table_cache_insert(&idr->cache, ecs_table_record_t, table); - tr->table = table; - tr->column = column; - tr->count = 1; + /* If variable is the root or if depth has been set, return depth + 1 */ + if (var == root || var->depth != UINT8_MAX) { + return var->depth + 1; } - /* Set flags if triggers are registered for table */ - if (flecs_triggers_for_id(world, id, EcsOnAdd)) { - table->flags |= EcsTableHasOnAdd; - } - if (flecs_triggers_for_id(world, id, EcsOnRemove)) { - table->flags |= EcsTableHasOnRemove; - } - if (flecs_triggers_for_id(world, id, EcsOnSet)) { - table->flags |= EcsTableHasOnSet; + /* Variable is already being evaluated, so this indicates a cycle. Stop */ + if (var->marked) { + return 0; } - if (flecs_triggers_for_id(world, id, EcsUnSet)) { - table->flags |= EcsTableHasUnSet; + + /* Variable is not yet being evaluated and depth has not yet been set. + * Calculate depth. */ + int32_t depth = get_variable_depth(rule, var, root, recur + 1); + if (depth == UINT8_MAX) { + return depth; + } else { + return depth + 1; } } static -void unregister_table( - ecs_world_t *world, - ecs_table_t *table, - ecs_id_t id, - int32_t column) +int32_t get_depth_from_term( + ecs_rule_t *rule, + ecs_rule_var_t *cur, + ecs_rule_var_t *pred, + ecs_rule_var_t *obj, + ecs_rule_var_t *root, + int recur) { - (void)column; - - ecs_id_record_t *idr = flecs_get_id_record(world, id); - if (!idr) { - return; - } + int32_t result = UINT8_MAX; - ecs_table_cache_remove(&idr->cache, ecs_table_record_t, table); + /* If neither of the other parts of the terms are variables, this + * variable is guaranteed to have no dependencies. */ + if (!pred && !obj) { + result = 0; + } else { + /* If this is a variable that is not the same as the current, + * we can use it to determine dependency depth. */ + if (pred && cur != pred) { + int32_t depth = get_depth_from_var(rule, pred, root, recur); + if (depth == UINT8_MAX) { + return UINT8_MAX; + } - if (ecs_table_cache_is_empty(&idr->cache)) { - flecs_clear_id_record(world, id); - } -} + /* If the found depth is lower than the depth found, overwrite it */ + if (depth < result) { + result = depth; + } + } -static -void set_empty( - ecs_world_t *world, - ecs_table_t *table, - ecs_id_t id, - int32_t column) -{ - (void)column; + /* Same for obj */ + if (obj && cur != obj) { + int32_t depth = get_depth_from_var(rule, obj, root, recur); + if (depth == UINT8_MAX) { + return UINT8_MAX; + } - ecs_id_record_t *idr = flecs_get_id_record(world, id); - if (idr) { - ecs_table_cache_set_empty(&idr->cache, table, - ecs_table_count(table) == 0); + if (depth < result) { + result = depth; + } + } } + + return result; } +/* Find the depth of the dependency tree from the variable to the root */ static -void for_each_id( - ecs_world_t *world, - ecs_table_t *table, - void(*action)(ecs_world_t*, ecs_table_t*, ecs_id_t, int32_t), - bool set_watch) +int32_t get_variable_depth( + ecs_rule_t *rule, + ecs_rule_var_t *var, + ecs_rule_var_t *root, + int recur) { - int32_t i, count = ecs_vector_count(table->type); - ecs_id_t *ids = ecs_vector_first(table->type, ecs_id_t); - bool has_childof = false; - - for (i = 0; i < count; i ++) { - ecs_entity_t id = ids[i]; + var->marked = true; - /* This check ensures that legacy CHILDOF works */ - if (ECS_HAS_RELATION(id, EcsChildOf)) { - has_childof = true; - } + /* Iterate columns, find all instances where 'var' is not used as subject. + * If the subject of that column is either the root or a variable for which + * the depth is known, the depth for this variable can be determined. */ + ecs_term_t *terms = rule->filter.terms; - action(world, table, ecs_strip_generation(id), i); - action(world, table, EcsWildcard, i); + int32_t i, count = rule->filter.term_count; + int32_t result = UINT8_MAX; - if (ECS_HAS_ROLE(id, PAIR)) { - ecs_entity_t pred_w_wildcard = ecs_pair( - ECS_PAIR_RELATION(id), EcsWildcard); - action(world, table, pred_w_wildcard, i); + for (i = 0; i < count; i ++) { + ecs_term_t *term = &terms[i]; + if (skip_term(term)) { + continue; + } - ecs_entity_t obj_w_wildcard = ecs_pair( - EcsWildcard, ECS_PAIR_OBJECT(id)); - action(world, table, obj_w_wildcard, i); + ecs_rule_var_t + *pred = term_pred(rule, term), + *subj = term_subj(rule, term), + *obj = term_obj(rule, term); - ecs_entity_t all_wildcard = ecs_pair(EcsWildcard, EcsWildcard); - action(world, table, all_wildcard, i); + if (subj != var) { + continue; + } - if (set_watch) { - ecs_entity_t rel = ecs_pair_relation(world, id); - ecs_entity_t obj = ecs_pair_object(world, id); - flecs_add_flag(world, rel, ECS_FLAG_OBSERVED_ID); - flecs_add_flag(world, obj, ECS_FLAG_OBSERVED_OBJECT); - if (ecs_has_id(world, rel, EcsAcyclic)) { - flecs_add_flag(world, obj, ECS_FLAG_OBSERVED_ACYCLIC); - } - } - } else { - if (id & ECS_ROLE_MASK) { - id &= ECS_COMPONENT_MASK; - action(world, table, ecs_pair(id, EcsWildcard), i); - } + if (!is_subject(rule, pred)) { + pred = NULL; + } - if (set_watch) { - flecs_add_flag(world, id, ECS_FLAG_OBSERVED_ID); - } + if (!is_subject(rule, obj)) { + obj = NULL; + } + + int32_t depth = get_depth_from_term(rule, var, pred, obj, root, recur); + if (depth < result) { + result = depth; } } - if (!has_childof) { - action(world, table, ecs_pair(EcsChildOf, 0), 0); + if (result == UINT8_MAX) { + result = 0; } -} -void flecs_register_table( - ecs_world_t *world, - ecs_table_t *table) -{ - for_each_id(world, table, register_table, true); -} + var->depth = result; -void flecs_unregister_table( - ecs_world_t *world, - ecs_table_t *table) -{ - for_each_id(world, table, unregister_table, false); + /* Dependencies are calculated from subject to (pred, obj). If there were + * subjects that are only related by object (like (X, Y), (Z, Y)) it is + * possible that those have not yet been found yet. To make sure those + * variables are found, loop again & follow predicate & object links */ + for (i = 0; i < count; i ++) { + ecs_term_t *term = &terms[i]; + if (skip_term(term)) { + continue; + } - ecs_ids_t key = { - .array = ecs_vector_first(table->type, ecs_id_t), - .count = ecs_vector_count(table->type) - }; + ecs_rule_var_t + *subj = term_subj(rule, term), + *pred = term_pred(rule, term), + *obj = term_obj(rule, term); - flecs_hashmap_remove(world->store.table_map, &key, ecs_table_t*); -} + /* Only evaluate pred & obj for current subject. This ensures that we + * won't evaluate variables that are unreachable from the root. This + * must be detected as unconstrained variables are not allowed. */ + if (subj != var) { + continue; + } -void flecs_table_set_empty( - ecs_world_t *world, - ecs_table_t *table) -{ - for_each_id(world, table, set_empty, false); -} + crawl_variable(rule, subj, root, recur); -ecs_id_record_t* flecs_ensure_id_record( - ecs_world_t *world, - ecs_id_t id) -{ - ecs_id_record_t *idr = ecs_map_ensure(world->id_index, ecs_id_record_t, - ecs_strip_generation(id)); + if (pred && pred != var) { + crawl_variable(rule, pred, root, recur); + } - if (!ecs_table_cache_is_initialized(&idr->cache)) { - ecs_table_cache_init(&idr->cache, ecs_table_record_t, world, NULL); + if (obj && obj != var) { + crawl_variable(rule, obj, root, recur); + } } - return idr; + return var->depth; } -ecs_id_record_t* flecs_get_id_record( - const ecs_world_t *world, - ecs_id_t id) +/* Compare function used for qsort. It ensures that variables are first ordered + * by depth, followed by how often they occur. */ +static +int compare_variable( + const void* ptr1, + const void *ptr2) { - return ecs_map_get(world->id_index, ecs_id_record_t, - ecs_strip_generation(id)); -} + const ecs_rule_var_t *v1 = ptr1; + const ecs_rule_var_t *v2 = ptr2; -ecs_table_record_t* flecs_get_table_record( - const ecs_world_t *world, - const ecs_table_t *table, - ecs_id_t id) -{ - ecs_id_record_t* idr = flecs_get_id_record(world, id); - if (!idr) { - return NULL; + if (v1->kind < v2->kind) { + return -1; + } else if (v1->kind > v2->kind) { + return 1; } - return ecs_table_cache_get(&idr->cache, ecs_table_record_t, table); -} - -void flecs_register_add_ref( - ecs_world_t *world, - const ecs_table_t *table, - ecs_id_t id) -{ - ecs_id_record_t *idr = flecs_ensure_id_record(world, id); - if (!idr->add_refs) { - idr->add_refs = ecs_map_new(ecs_table_t*, 1); + if (v1->depth < v2->depth) { + return -1; + } else if (v1->depth > v2->depth) { + return 1; } - ecs_table_t **ptr = ecs_map_ensure( - idr->add_refs, ecs_table_t*, table->id); - ptr[0] = (ecs_table_t*)table; -} - -void flecs_register_remove_ref( - ecs_world_t *world, - const ecs_table_t *table, - ecs_id_t id) -{ - ecs_id_record_t *idr = flecs_ensure_id_record(world, id); - if (!idr->remove_refs) { - idr->remove_refs = ecs_map_new(ecs_table_t*, 1); + if (v1->occurs < v2->occurs) { + return 1; + } else { + return -1; } - ecs_table_t **ptr = ecs_map_ensure( - idr->remove_refs, ecs_table_t*, table->id); - ptr[0] = (ecs_table_t*)table; + return (v1->id < v2->id) - (v1->id > v2->id); } -void flecs_clear_id_record( - ecs_world_t *world, - ecs_id_t id) +/* After all subject variables have been found, inserted and sorted, the + * remaining variables (predicate & object) still need to be inserted. This + * function serves two purposes. The first purpose is to ensure that all + * variables are known before operations are emitted. This ensures that the + * variables array won't be reallocated while emitting, which simplifies code. + * The second purpose of the function is to ensure that if the root variable + * (which, if it exists has now been created with a table type) is also inserted + * with an entity type if required. This is used later to decide whether the + * rule needs to insert an each instruction. */ +static +void ensure_all_variables( + ecs_rule_t *rule) { - if (world->is_fini) { - return; - } - - ecs_id_record_t *idr_ptr = flecs_get_id_record(world, id); - if (!idr_ptr) { - return; - } - - ecs_id_record_t idr = *idr_ptr; + ecs_term_t *terms = rule->filter.terms; + int32_t i, count = rule->filter.term_count; - ecs_map_remove(world->id_index, ecs_strip_generation(id)); + for (i = 0; i < count; i ++) { + ecs_term_t *term = &terms[i]; + if (skip_term(term)) { + continue; + } - ecs_table_cache_fini_delete_all(world, &idr.cache, ecs_table_record_t); + /* If predicate is a variable, make sure it has been registered */ + if (term->pred.var == EcsVarIsVariable) { + ensure_variable(rule, EcsRuleVarKindEntity, term_id_var_name(&term->pred)); + } - /* Remove add & remove references to id from tables */ - ecs_table_t *table; - ecs_map_iter_t it = ecs_map_iter(idr.add_refs); - while ((table = ecs_map_next_ptr(&it, ecs_table_t*, NULL))) { - flecs_table_clear_add_edge(table, id); - } + /* If subject is a variable and it is not This, make sure it is + * registered as an entity variable. This ensures that the program will + * correctly return all permutations */ + if (term->subj.var == EcsVarIsVariable) { + if (term->subj.entity != EcsThis) { + ensure_variable(rule, EcsRuleVarKindEntity, term_id_var_name(&term->subj)); + } + } - it = ecs_map_iter(idr.remove_refs); - while ((table = ecs_map_next_ptr(&it, ecs_table_t*, NULL))) { - flecs_table_clear_remove_edge(table, id); + /* If object is a variable, make sure it has been registered */ + if (obj_is_set(term) && (term->obj.var == EcsVarIsVariable)) { + ensure_variable(rule, EcsRuleVarKindEntity, term_id_var_name(&term->obj)); + } } - - ecs_map_free(idr.add_refs); - ecs_map_free(idr.remove_refs); } -const ecs_table_record_t* flecs_id_record_table( - ecs_id_record_t *idr, - ecs_table_t *table) +/* Scan for variables, put them in optimal dependency order. */ +static +int scan_variables( + ecs_rule_t *rule) { - if (!idr) { - return NULL; - } - return ecs_table_cache_get(&idr->cache, ecs_table_record_t, table); -} + /* Objects found in rule. One will be elected root */ + int32_t subject_count = 0; -const ecs_table_record_t* flecs_id_record_tables( - const ecs_id_record_t *idr) -{ - if (!idr) { - return NULL; - } - return ecs_table_cache_tables(&idr->cache, ecs_table_record_t); -} + /* If this (.) is found, it always takes precedence in root election */ + int32_t this_var = UINT8_MAX; -const ecs_table_record_t* flecs_id_record_empty_tables( - const ecs_id_record_t *idr) -{ - if (!idr) { - return NULL; - } - return ecs_table_cache_empty_tables(&idr->cache, ecs_table_record_t); -} + /* Keep track of the subject variable that occurs the most. In the absence of + * this (.) the variable with the most occurrences will be elected root. */ + int32_t max_occur = 0; + int32_t max_occur_var = UINT8_MAX; -int32_t flecs_id_record_count( - const ecs_id_record_t *idr) -{ - if (!idr) { - return 0; - } - return ecs_table_cache_count(&idr->cache); -} + /* Step 1: find all possible roots */ + ecs_term_t *terms = rule->filter.terms; + int32_t i, term_count = rule->filter.term_count; -int32_t flecs_id_record_empty_count( - const ecs_id_record_t *idr) -{ - if (!idr) { - return 0; + for (i = 0; i < term_count; i ++) { + ecs_term_t *term = &terms[i]; + + /* Evaluate the subject. The predicate and object are not evaluated, + * since they never can be elected as root. */ + if (term_id_is_variable(&term->subj)) { + const char *subj_name = term_id_var_name(&term->subj); + + ecs_rule_var_t *subj = find_variable( + rule, EcsRuleVarKindTable, subj_name); + if (!subj) { + subj = create_variable(rule, EcsRuleVarKindTable, subj_name); + if (subject_count >= ECS_RULE_MAX_VARIABLE_COUNT) { + rule_error(rule, "too many variables in rule"); + goto error; + } + } + + if (++ subj->occurs > max_occur) { + max_occur = subj->occurs; + max_occur_var = subj->id; + } + } } - return ecs_table_cache_empty_count(&idr->cache); -} + rule->subject_variable_count = rule->variable_count; -void flecs_observable_init( - ecs_observable_t *observable) -{ - observable->events = ecs_sparse_new(ecs_event_record_t); -} + ensure_all_variables(rule); -void flecs_observable_fini( - ecs_observable_t *observable) -{ - ecs_sparse_t *triggers = observable->events; - int32_t i, count = flecs_sparse_count(triggers); + /* Variables in a term with a literal subject have depth 0 */ + for (i = 0; i < term_count; i ++) { + ecs_term_t *term = &terms[i]; - for (i = 0; i < count; i ++) { - ecs_event_record_t *et = - ecs_sparse_get_dense(triggers, ecs_event_record_t, i); - ecs_assert(et != NULL, ECS_INTERNAL_ERROR, NULL); + if (term->subj.var == EcsVarIsEntity) { + ecs_rule_var_t + *pred = term_pred(rule, term), + *obj = term_obj(rule, term); - ecs_map_iter_t it = ecs_map_iter(et->event_ids); - ecs_event_id_record_t *idt; - while ((idt = ecs_map_next(&it, ecs_event_id_record_t, NULL))) { - ecs_map_free(idt->triggers); - ecs_map_free(idt->set_triggers); + if (pred) { + pred->depth = 0; + } + if (obj) { + obj->depth = 0; + } } - ecs_map_free(et->event_ids); } - flecs_sparse_free(observable->events); -} - -static -void notify_subset( - ecs_world_t *world, - ecs_iter_t *it, - ecs_observable_t *observable, - ecs_entity_t entity, - ecs_entity_t event, - ecs_ids_t *ids) -{ - ecs_id_t pair = ecs_pair(EcsWildcard, entity); - ecs_id_record_t *idr = flecs_get_id_record(world, pair); - if (!idr) { - return; + /* Elect a root. This is either this (.) or the variable with the most + * occurrences. */ + int32_t root_var = this_var; + if (root_var == UINT8_MAX) { + root_var = max_occur_var; + if (root_var == UINT8_MAX) { + /* If no subject variables have been found, the rule expression only + * operates on a fixed set of entities, in which case no root + * election is required. */ + goto done; + } } - ecs_table_record_t *trs = ecs_table_cache_tables( - &idr->cache, ecs_table_record_t); - int32_t i, count = ecs_table_cache_count(&idr->cache); + ecs_rule_var_t *root = &rule->variables[root_var]; + root->depth = get_variable_depth(rule, root, root, 0); - for (i = 0; i < count; i ++) { - ecs_table_record_t *tr = &trs[i]; - ecs_table_t *table = tr->table; - ecs_id_t id = ecs_vector_get(table->type, ecs_id_t, tr->column)[0]; - ecs_entity_t rel = ECS_PAIR_RELATION(id); + /* Verify that there are no unconstrained variables. Unconstrained variables + * are variables that are unreachable from the root. */ + for (i = 0; i < rule->subject_variable_count; i ++) { + if (rule->variables[i].depth == UINT8_MAX) { + rule_error(rule, "unconstrained variable '%s'", + rule->variables[i].name); + goto error; + } + } - if (ecs_is_valid(world, rel) && !ecs_has_id(world, rel, EcsAcyclic)) { - /* Only notify for acyclic relations */ + /* For each Not term, verify that variables are known */ + for (i = 0; i < term_count; i ++) { + ecs_term_t *term = &terms[i]; + if (term->oper != EcsNot) { continue; } - int32_t e, entity_count = ecs_table_count(table); - it->table = table; - it->type = table->type; - it->other_table = NULL; - it->offset = 0; - it->count = entity_count; + ecs_rule_var_t + *pred = term_pred(rule, term), + *obj = term_obj(rule, term); - flecs_set_triggers_notify(it, observable, ids, event, - ecs_pair(rel, EcsWildcard)); + if (!pred && term_id_is_variable(&term->pred)) { + rule_error(rule, "missing predicate variable '%s'", + term_id_var_name(&term->pred)); + goto error; + } + if (!obj && term_id_is_variable(&term->obj)) { + rule_error(rule, "missing object variable '%s'", + term_id_var_name(&term->obj)); + goto error; + } + } - ecs_entity_t *entities = ecs_vector_first( - table->storage.entities, ecs_entity_t); - ecs_record_t **records = ecs_vector_first( - table->storage.record_ptrs, ecs_record_t*); + /* Order variables by depth, followed by occurrence. The variable + * array will later be used to lead the iteration over the terms, and + * determine which operations get inserted first. */ + size_t var_count = flecs_itosize(rule->variable_count); + qsort(rule->variables, var_count, sizeof(ecs_rule_var_t), compare_variable); - for (e = 0; e < entity_count; e ++) { - uint32_t flags = ECS_RECORD_TO_ROW_FLAGS(records[e]->row); - if (flags & ECS_FLAG_OBSERVED_ACYCLIC) { - /* Only notify for entities that are used in pairs with - * acyclic relations */ - notify_subset(world, it, observable, entities[e], event, ids); - } - } + /* Iterate variables to correct ids after sort */ + for (i = 0; i < rule->variable_count; i ++) { + rule->variables[i].id = i; } + +done: + return 0; +error: + return -1; } -void ecs_emit( - ecs_world_t *world, - ecs_event_desc_t *desc) +/* Get entity variable from table variable */ +static +ecs_rule_var_t* to_entity( + ecs_rule_t *rule, + ecs_rule_var_t *var) { - 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); + if (!var) { + return NULL; + } - ecs_ids_t *ids = desc->ids; - ecs_entity_t event = desc->event; - ecs_table_t *table = desc->table; - int32_t row = desc->offset; - int32_t i, count = desc->count; + ecs_rule_var_t *evar = NULL; + if (var->kind == EcsRuleVarKindTable) { + evar = find_variable(rule, EcsRuleVarKindEntity, var->name); + } else { + evar = var; + } - if (!count) { - count = ecs_table_count(table) - row; + return evar; +} + +/* Ensure that if a table variable has been written, the corresponding entity + * variable is populated. The function will return the most specific, populated + * variable. */ +static +ecs_rule_var_t* most_specific_var( + ecs_rule_t *rule, + ecs_rule_var_t *var, + bool *written, + bool create) +{ + if (!var) { + return NULL; } - ecs_iter_t it = { - .world = world, - .table = table, - .type = table->type, - .term_count = 1, - .other_table = desc->other_table, - .offset = row, - .count = count, - .param = desc->param - }; + ecs_rule_var_t *tvar, *evar = to_entity(rule, var); + if (!evar) { + return var; + } - world->event_id ++; + if (var->kind == EcsRuleVarKindTable) { + tvar = var; + } else { + tvar = find_variable(rule, EcsRuleVarKindTable, var->name); + } - ecs_observable_t *observable = ecs_get_observable(desc->observable); - ecs_check(observable != NULL, ECS_INVALID_PARAMETER, NULL); + /* If variable is used as predicate or object, it should have been + * registered as an entity. */ + ecs_assert(evar != NULL, ECS_INTERNAL_ERROR, NULL); - flecs_triggers_notify(&it, observable, ids, event); + /* Usually table variables are resolved before they are used as a predicate + * or object, but in the case of cyclic dependencies this is not guaranteed. + * Only insert an each instruction of the table variable has been written */ + if (tvar && written[tvar->id]) { + /* If the variable has been written as a table but not yet + * as an entity, insert an each operation that yields each + * entity in the table. */ + if (evar) { + if (written[evar->id]) { + return evar; + } else if (create) { + ecs_rule_op_t *op = create_operation(rule); + op->kind = EcsRuleEach; + op->on_pass = rule->operation_count; + op->on_fail = rule->operation_count - 2; + op->frame = rule->frame_count; + op->has_in = true; + op->has_out = true; + op->r_in = tvar->id; + op->r_out = evar->id; - ecs_record_t **recs = ecs_vector_get( - table->storage.record_ptrs, ecs_record_t*, row); + /* Entity will either be written or has been written */ + written[evar->id] = true; - for (i = 0; i < count; i ++) { - uint32_t flags = ECS_RECORD_TO_ROW_FLAGS(recs[i]->row); - if (flags & ECS_FLAG_OBSERVED_ACYCLIC) { - notify_subset(world, &it, observable, ecs_vector_first( - table->storage.entities, ecs_entity_t)[row + i], - event, ids); + push_frame(rule); + + return evar; + } else { + return tvar; + } } - } - -error: - return; + } else if (evar && written[evar->id]) { + return evar; + } + + return var; +} + +/* Get most specific known variable */ +static +ecs_rule_var_t *get_most_specific_var( + ecs_rule_t *rule, + ecs_rule_var_t *var, + bool *written) +{ + return most_specific_var(rule, var, written, false); } +/* Get or create most specific known variable. This will populate an entity + * variable if a table variable is known but the entity variable isn't. */ +static +ecs_rule_var_t *ensure_most_specific_var( + ecs_rule_t *rule, + ecs_rule_var_t *var, + bool *written) +{ + return most_specific_var(rule, var, written, true); +} +/* Ensure that an entity variable is written before using it */ static -void term_error( - const ecs_world_t *world, - const ecs_term_t *term, - const char *name, - const char *fmt, - ...) +ecs_rule_var_t* ensure_entity_written( + ecs_rule_t *rule, + ecs_rule_var_t *var, + bool *written) { - va_list args; - va_start(args, fmt); + if (!var) { + return NULL; + } - char *expr = ecs_term_str(world, term); - ecs_parser_errorv(name, expr, 0, fmt, args); - ecs_os_free(expr); + /* Ensure we're working with the most specific version of subj we can get */ + ecs_rule_var_t *evar = ensure_most_specific_var(rule, var, written); - va_end(args); + /* The post condition of this function is that there is an entity variable, + * and that it is written. Make sure that the result is an entity */ + ecs_assert(evar != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(evar->kind == EcsRuleVarKindEntity, ECS_INTERNAL_ERROR, NULL); + + /* Make sure the variable has been written */ + ecs_assert(written[evar->id] == true, ECS_INTERNAL_ERROR, NULL); + + return evar; } static -int finalize_term_set( - const ecs_world_t *world, - ecs_term_t *term, - ecs_term_id_t *identifier, - const char *name) +ecs_rule_op_t* insert_operation( + ecs_rule_t *rule, + int32_t term_index, + bool *written) { - if (identifier->set.mask & EcsParent) { - identifier->set.mask |= EcsSuperSet; - identifier->set.relation = EcsChildOf; - } + ecs_rule_pair_t pair = {0}; - /* Default relation for superset/subset is EcsIsA */ - if (identifier->set.mask & (EcsSuperSet|EcsSubSet)) { - if (!identifier->set.relation) { - identifier->set.relation = EcsIsA; + /* Parse the term's type into a pair. A pair extracts the ids from + * the term, and replaces variables with wildcards which can then + * be matched against actual relationships. A pair retains the + * information about the variables, so that when a match happens, + * the pair can be used to reify the variable. */ + if (term_index != -1) { + ecs_term_t *term = &rule->filter.terms[term_index]; + + pair = term_to_pair(rule, term); + + /* If the pair contains entity variables that have not yet been written, + * insert each instructions in case their tables are known. Variables in + * a pair that are truly unknown will be populated by the operation, + * but an operation should never overwrite an entity variable if the + * corresponding table variable has already been resolved. */ + if (pair.reg_mask & RULE_PAIR_PREDICATE) { + ecs_rule_var_t *pred = &rule->variables[pair.pred.reg]; + pred = get_most_specific_var(rule, pred, written); + pair.pred.reg = pred->id; } - if (!(identifier->set.mask & EcsSelf)) { - if (!identifier->set.min_depth) { - identifier->set.min_depth = 1; - } + if (pair.reg_mask & RULE_PAIR_OBJECT) { + ecs_rule_var_t *obj = &rule->variables[pair.obj.reg]; + obj = get_most_specific_var(rule, obj, written); + pair.obj.reg = obj->id; } } else { - if (identifier->set.min_depth > 0) { - term_error(world, term, name, - "min depth cannnot be non-zero for Self term"); - return -1; - } - if (identifier->set.max_depth > 1) { - term_error(world, term, name, - "max depth cannnot be larger than 1 for Self term"); - return -1; - } - - identifier->set.max_depth = 1; + /* Not all operations have a filter (like Each) */ } - if ((identifier->set.mask != EcsNothing) && - (identifier->set.mask & EcsNothing)) - { - term_error(world, term, name, "invalid Nothing in set mask"); - return -1; - } + ecs_rule_op_t *op = create_operation(rule); + op->on_pass = rule->operation_count; + op->on_fail = rule->operation_count - 2; + op->frame = rule->frame_count; + op->filter = pair; - return 0; + /* Store corresponding signature term so we can correlate and + * store the table columns with signature columns. */ + op->term = term_index; + + return op; } +/* Insert first operation, which is always Input. This creates an entry in + * the register stack for the initial state. */ static -int finalize_term_var( - const ecs_world_t *world, - ecs_term_t *term, - ecs_term_id_t *identifier, - const char *name) +void insert_input( + ecs_rule_t *rule) { - if (identifier->var == EcsVarDefault) { - const char *var = ecs_identifier_is_var(identifier->name); - if (ecs_identifier_is_var(identifier->name)) { - char *var_id = ecs_os_strdup(var); - ecs_os_free(identifier->name); - identifier->name = var_id; - identifier->var = EcsVarIsVariable; - } - } + ecs_rule_op_t *op = create_operation(rule); + op->kind = EcsRuleInput; - if (identifier->var == EcsVarDefault && identifier->set.mask != EcsNothing){ - identifier->var = EcsVarIsEntity; - } + /* The first time Input is evaluated it goes to the next/first operation */ + op->on_pass = 1; - if (!identifier->name) { - return 0; - } + /* When Input is evaluated with redo = true it will return false, which will + * finish the program as op becomes -1. */ + op->on_fail = -1; - if (identifier->var != EcsVarIsVariable) { - if (ecs_identifier_is_0(identifier->name)) { - identifier->entity = 0; - } else { - ecs_entity_t e = ecs_lookup_symbol(world, identifier->name, true); - if (!e) { - term_error(world, term, name, - "unresolved identifier '%s'", identifier->name); - return -1; - } + push_frame(rule); +} - identifier->entity = e; - } +/* Insert last operation, which is always Yield. When the program hits Yield, + * data is returned to the application. */ +static +void insert_yield( + ecs_rule_t *rule) +{ + ecs_rule_op_t *op = create_operation(rule); + op->kind = EcsRuleYield; + op->has_in = true; + op->on_fail = rule->operation_count - 2; + /* Yield can only "fail" since it is the end of the program */ + + /* Find variable associated with this. It is possible that the variable + * exists both as a table and as an entity. This can happen when a rule + * first selects a table for this, but then subsequently needs to evaluate + * each entity in that table. In that case the yield instruction should + * return the entity, so look for that first. */ + ecs_rule_var_t *var = find_variable(rule, EcsRuleVarKindEntity, "."); + if (!var) { + var = find_variable(rule, EcsRuleVarKindTable, "."); } - if ((identifier->set.mask == EcsNothing) && - (identifier->var != EcsVarDefault)) - { - term_error(world, term, name, "Invalid Nothing with entity"); - return -1; + /* If there is no this, there is nothing to yield. In that case the rule + * simply returns true or false. */ + if (!var) { + op->r_in = UINT8_MAX; + } else { + op->r_in = var->id; } - return 0; + op->frame = push_frame(rule); } +/* Return superset/subset including the root */ static -int finalize_term_identifier( - const ecs_world_t *world, - ecs_term_t *term, - ecs_term_id_t *identifier, - const char *name) +void insert_inclusive_set( + ecs_rule_t *rule, + ecs_rule_op_kind_t op_kind, + ecs_rule_var_t *out, + const ecs_rule_pair_t pair, + int32_t c, + bool *written, + bool inclusive) { - if (finalize_term_set(world, term, identifier, name)) { - return -1; + ecs_assert(out != NULL, ECS_INTERNAL_ERROR, NULL); + + /* If the operation to be inserted is a superset, the output variable needs + * to be an entity as a superset is always resolved one at a time */ + ecs_assert((op_kind != EcsRuleSuperSet) || + out->kind == EcsRuleVarKindEntity, ECS_INTERNAL_ERROR, NULL); + + int32_t setjmp_lbl = rule->operation_count; + int32_t store_lbl = setjmp_lbl + 1; + int32_t set_lbl = setjmp_lbl + 2; + int32_t next_op = setjmp_lbl + 4; + int32_t prev_op = setjmp_lbl - 1; + + /* Insert 4 operations at once, so we don't have to worry about how + * the instruction array reallocs. If operation is not inclusive, we only + * need to insert the set operation. */ + if (inclusive) { + insert_operation(rule, -1, written); + insert_operation(rule, -1, written); + insert_operation(rule, -1, written); } - if (finalize_term_var(world, term, identifier, name)) { - return -1; + + ecs_rule_op_t *op = insert_operation(rule, -1, written); + ecs_rule_op_t *setjmp = op - 3; + ecs_rule_op_t *store = op - 2; + ecs_rule_op_t *set = op - 1; + ecs_rule_op_t *jump = op; + + if (!inclusive) { + set_lbl = setjmp_lbl; + set = op; + setjmp = NULL; + store = NULL; + jump = NULL; + next_op = set_lbl + 1; + prev_op = set_lbl - 1; } - return 0; -} -static -bool term_can_inherit( - ecs_term_t *term) -{ - /* Hardcoded components that can't be inherited. TODO: replace with - * relationship property. */ - if (term->pred.entity == EcsChildOf || - (term->id == ecs_pair(ecs_id(EcsIdentifier), EcsName)) || - (term->id == EcsPrefab) || - (term->id == EcsDisabled)) - { - return false; + /* The SetJmp operation stores a conditional jump label that either + * points to the Store or *Set operation */ + if (inclusive) { + setjmp->kind = EcsRuleSetJmp; + setjmp->on_pass = store_lbl; + setjmp->on_fail = set_lbl; } - return true; -} -static -ecs_entity_t term_id_entity( - const ecs_world_t *world, - ecs_term_id_t *term_id) -{ - if (term_id->entity && term_id->entity != EcsThis && - term_id->entity != EcsWildcard) - { - if (!(term_id->entity & ECS_ROLE_MASK)) { - return term_id->entity; + ecs_rule_var_t *pred = pair_pred(rule, &pair); + ecs_rule_var_t *obj = pair_obj(rule, &pair); + + /* The Store operation yields the root of the subtree. After yielding, + * this operation will fail and return to SetJmp, which will cause it + * to switch to the *Set operation. */ + if (inclusive) { + store->kind = EcsRuleStore; + store->on_pass = next_op; + store->on_fail = setjmp_lbl; + store->has_in = true; + store->has_out = true; + store->r_out = out->id; + store->term = c; + + if (!pred) { + store->filter.pred = pair.pred; } else { - return 0; + store->filter.pred.reg = pred->id; + store->filter.reg_mask |= RULE_PAIR_PREDICATE; } - } else if (term_id->name) { - if (term_id->var == EcsVarIsEntity || - (term_id->var == EcsVarDefault && - !ecs_identifier_is_var(term_id->name))) - { - ecs_entity_t e = ecs_lookup_fullpath(world, term_id->name); - if (e != EcsWildcard && e != EcsThis) { - return e; - } - return 0; + + /* If the object of the filter is not a variable, store literal */ + if (!obj) { + store->r_in = UINT8_MAX; + store->subject = ecs_get_alive(rule->world, pair.obj.ent); + store->filter.obj = pair.obj; } else { - return 0; + store->r_in = obj->id; + store->filter.obj.reg = obj->id; + store->filter.reg_mask |= RULE_PAIR_OBJECT; } - } else { - return 0; } -} -static -int finalize_term_vars( - const ecs_world_t *world, - ecs_term_t *term, - const char *name) -{ - if (finalize_term_var(world, term, &term->pred, name)) { - return -1; + /* This is either a SubSet or SuperSet operation */ + set->kind = op_kind; + set->on_pass = next_op; + set->on_fail = prev_op; + set->has_out = true; + set->r_out = out->id; + set->term = c; + + /* Predicate can be a variable if it's non-final */ + if (!pred) { + set->filter.pred = pair.pred; + } else { + set->filter.pred.reg = pred->id; + set->filter.reg_mask |= RULE_PAIR_PREDICATE; } - if (finalize_term_var(world, term, &term->subj, name)) { - return -1; + + if (!obj) { + set->filter.obj = pair.obj; + } else { + set->filter.obj.reg = obj->id; + set->filter.reg_mask |= RULE_PAIR_OBJECT; } - if (finalize_term_var(world, term, &term->obj, name)) { - return -1; + + if (inclusive) { + /* The jump operation jumps to either the store or subset operation, + * depending on whether the store operation already yielded. The + * operation is inserted last, so that the on_fail label of the next + * operation will point to it */ + jump->kind = EcsRuleJump; + + /* The pass/fail labels of the Jump operation are not used, since it + * jumps to a variable location. Instead, the pass label is (ab)used to + * store the label of the SetJmp operation, so that the jump can access + * the label it needs to jump to from the setjmp op_ctx. */ + jump->on_pass = setjmp_lbl; + jump->on_fail = -1; } - return 0; + + written[out->id] = true; } static -int finalize_term_identifiers( - const ecs_world_t *world, - ecs_term_t *term, - const char *name) +ecs_rule_var_t* store_inclusive_set( + ecs_rule_t *rule, + ecs_rule_op_kind_t op_kind, + ecs_rule_pair_t *pair, + bool *written, + bool inclusive) { - /* By default select subsets for predicates. For example, when the term - * matches "Tree", also include "Oak", "Pine", "Elm". */ - if (term->pred.set.mask == EcsDefaultSet) { - ecs_entity_t e = term_id_entity(world, &term->pred); + /* The subset operation returns tables */ + ecs_rule_var_kind_t var_kind = EcsRuleVarKindTable; - if (e && !ecs_has_id(world, e, EcsFinal)) { - term->pred.set.mask = EcsSelf|EcsSubSet; - } else { - /* If predicate is final, don't search subsets */ - term->pred.set.mask = EcsSelf; - } + /* The superset operation returns entities */ + if (op_kind == EcsRuleSuperSet) { + var_kind = EcsRuleVarKindEntity; } - /* By default select supersets for subjects. For example, when an entity has - * (IsA, SpaceShip), also search the components of SpaceShip. */ - if (term->subj.set.mask == EcsDefaultSet) { - term->subj.set.mask = EcsSelf|EcsSuperSet; - } + /* Create anonymous variable for storing the set */ + ecs_rule_var_t *av = create_anonymous_variable(rule, var_kind); + ecs_rule_var_t *ave = NULL; - /* By default select self for objects. */ - if (term->obj.set.mask == EcsDefaultSet) { - term->obj.set.mask = EcsSelf; + /* If the variable kind is a table, also create an entity variable as the + * result of the set operation should be returned as an entity */ + if (var_kind == EcsRuleVarKindTable) { + ave = create_variable(rule, EcsRuleVarKindEntity, av->name); + av = &rule->variables[ave->id - 1]; } - if (finalize_term_set(world, term, &term->pred, name)) { - return -1; - } - if (finalize_term_set(world, term, &term->subj, name)) { - return -1; - } - if (finalize_term_set(world, term, &term->obj, name)) { - return -1; + /* Ensure we're using the most specific version of obj */ + ecs_rule_var_t *obj = pair_obj(rule, pair); + if (obj) { + pair->obj.reg = obj->id; } - if (term->pred.set.mask & EcsNothing) { - term_error(world, term, name, - "invalid Nothing value for predicate set mask"); - return -1; - } + /* Generate the operations */ + insert_inclusive_set(rule, op_kind, av, *pair, -1, written, inclusive); - if (term->obj.set.mask & EcsNothing) { - term_error(world, term, name, - "invalid Nothing value for object set mask"); - return -1; - } + /* Make sure to return entity variable, and that it is populated */ + return ensure_entity_written(rule, av, written); +} - if (!(term->subj.set.mask & EcsNothing) && - !term->subj.entity && - term->subj.var == EcsVarIsEntity) - { - term->subj.entity = EcsThis; - } - - if (term->pred.entity == EcsThis) { - term->pred.var = EcsVarIsVariable; +static +bool is_known( + ecs_rule_var_t *var, + bool *written) +{ + if (!var) { + return true; + } else { + return written[var->id]; } - if (term->subj.entity == EcsThis) { - term->subj.var = EcsVarIsVariable; +} + +static +bool is_pair_known( + ecs_rule_t *rule, + ecs_rule_pair_t *pair, + bool *written) +{ + ecs_rule_var_t *pred_var = pair_pred(rule, pair); + if (!is_known(pred_var, written)) { + return false; } - if (term->obj.entity == EcsThis) { - term->obj.var = EcsVarIsVariable; + + ecs_rule_var_t *obj_var = pair_obj(rule, pair); + if (!is_known(obj_var, written)) { + return false; } - return 0; + return true; } static -ecs_entity_t entity_from_identifier( - const ecs_term_id_t *identifier) +void set_input_to_subj( + ecs_rule_t *rule, + ecs_rule_op_t *op, + ecs_term_t *term, + ecs_rule_var_t *var) { - if (identifier->var == EcsVarDefault) { - return 0; - } else if (identifier->var == EcsVarIsEntity) { - return identifier->entity; - } else if (identifier->var == EcsVarIsVariable) { - return EcsWildcard; + (void)rule; + + op->has_in = true; + if (!var) { + op->r_in = UINT8_MAX; + op->subject = term->subj.entity; + + /* Invalid entities should have been caught during parsing */ + ecs_assert(ecs_is_valid(rule->world, op->subject), + ECS_INTERNAL_ERROR, NULL); } else { - /* This should've been caught earlier */ - ecs_abort(ECS_INTERNAL_ERROR, NULL); + op->r_in = var->id; } } static -int finalize_term_id( - const ecs_world_t *world, +void set_output_to_subj( + ecs_rule_t *rule, + ecs_rule_op_t *op, ecs_term_t *term, - const char *name) + ecs_rule_var_t *var) { - ecs_entity_t pred = entity_from_identifier(&term->pred); - ecs_entity_t obj = entity_from_identifier(&term->obj); - ecs_id_t role = term->role; - - if (ECS_HAS_ROLE(pred, PAIR)) { - if (obj) { - term_error(world, term, name, - "cannot set term.pred to a pair and term.obj at the same time"); - return -1; - } - - obj = ECS_PAIR_OBJECT(pred); - pred = ECS_PAIR_RELATION(pred); - - term->pred.entity = pred; - term->obj.entity = obj; + (void)rule; - if (finalize_term_identifier(world, term, &term->obj, name)) { - return -1; - } - } + op->has_out = true; + if (!var) { + op->r_out = UINT8_MAX; + op->subject = term->subj.entity; - if (!obj && role != ECS_PAIR) { - term->id = pred | role; + /* Invalid entities should have been caught during parsing */ + ecs_assert(ecs_is_valid(rule->world, op->subject), + ECS_INTERNAL_ERROR, NULL); } else { - if (role) { - if (role && role != ECS_PAIR && role != ECS_CASE) { - term_error(world, term, name, "invalid role for pair"); - return -1; - } - - term->role = role; - } else { - term->role = ECS_PAIR; - } - - term->id = term->role | ecs_entity_t_comb(obj, pred); + op->r_out = var->id; } - - return 0; } static -int populate_from_term_id( - const ecs_world_t *world, +void insert_select_or_with( + ecs_rule_t *rule, + int32_t c, ecs_term_t *term, - const char *name) + ecs_rule_var_t *subj, + ecs_rule_pair_t *pair, + bool *written) { - ecs_entity_t pred = 0; - ecs_entity_t obj = 0; - ecs_id_t role = term->id & ECS_ROLE_MASK; - - if (!role && term->role) { - role = term->role; - term->id |= role; - } + ecs_rule_op_t *op; + bool eval_subject_supersets = false; + bool wildcard_subj = term->subj.entity == EcsWildcard; - if (term->role && term->role != role) { - term_error(world, term, name, "mismatch between term.id & term.role"); - return -1; + /* Find any entity and/or table variables for subject */ + ecs_rule_var_t *tvar = NULL, *evar = to_entity(rule, subj), *var = evar; + if (subj && subj->kind == EcsRuleVarKindTable) { + tvar = subj; + if (!evar) { + var = tvar; + } } - term->role = role; - - if (ECS_HAS_ROLE(term->id, PAIR)) { - pred = ECS_PAIR_RELATION(term->id); - obj = ECS_PAIR_OBJECT(term->id); - - if (!pred) { - term_error(world, term, name, "missing predicate in term.id pair"); - return -1; - } - if (!obj) { - if (pred != EcsChildOf) { - term_error(world, term, name, "missing object in term.id pair"); - return -1; - } - } + int32_t lbl_start = rule->operation_count; + ecs_rule_pair_t filter; + if (pair) { + filter = *pair; } else { - pred = term->id & ECS_COMPONENT_MASK; - if (!pred) { - term_error(world, term, name, "missing predicate in term.id"); - return -1; - } + filter = term_to_pair(rule, term); } - ecs_entity_t term_pred = entity_from_identifier(&term->pred); - if (term_pred) { - if (term_pred != pred) { - term_error(world, term, name, - "mismatch between term.id and term.pred"); - return -1; - } - } else { - term->pred.entity = pred; - if (finalize_term_identifier(world, term, &term->pred, name)) { - return -1; + if (!var && !wildcard_subj) { + /* Only insert implicit IsA if filter isn't already an IsA */ + if (!filter.transitive || filter.pred.ent != EcsIsA) { + ecs_rule_pair_t isa_pair = { + .pred.ent = EcsIsA, + .obj.ent = term->subj.entity + }; + evar = subj = store_inclusive_set( + rule, EcsRuleSuperSet, &isa_pair, written, true); + tvar = NULL; + eval_subject_supersets = true; } } - ecs_entity_t term_obj = entity_from_identifier(&term->obj); - if (term_obj) { - if (ecs_entity_t_lo(term_obj) != obj) { - term_error(world, term, name, - "mismatch between term.id and term.obj"); - return -1; - } + /* If no pair is provided, create operation from specified term */ + if (!pair) { + op = insert_operation(rule, c, written); + + /* If an explicit pair is provided, override the default one from the + * term. This allows for using a predicate or object variable different + * from what is in the term. One application of this is to substitute a + * predicate with its subsets, if it is non final */ } else { - term->obj.entity = obj; - if (finalize_term_identifier(world, term, &term->obj, name)) { - return -1; - } + op = insert_operation(rule, -1, written); + op->filter = *pair; + + /* Assign the term id, so that the operation will still be correctly + * associated with the correct expression term. */ + op->term = c; } - return 0; -} + /* If entity variable is known and resolved, create with for it */ + if (evar && is_known(evar, written)) { + op->kind = EcsRuleWith; + op->r_in = evar->id; + set_input_to_subj(rule, op, term, subj); -static -int verify_term_consistency( - const ecs_world_t *world, - const ecs_term_t *term, - const char *name) -{ - ecs_entity_t pred = entity_from_identifier(&term->pred); - ecs_entity_t obj = entity_from_identifier(&term->obj); - ecs_id_t role = term->role; - ecs_id_t id = term->id; - bool wildcard = pred == EcsWildcard || obj == EcsWildcard; + /* If table variable is known and resolved, create with for it */ + } else if (tvar && is_known(tvar, written)) { + op->kind = EcsRuleWith; + op->r_in = tvar->id; + set_input_to_subj(rule, op, term, subj); - if (obj && (!role || (role != ECS_PAIR && role != ECS_CASE))) { - term_error(world, term, name, - "invalid role for term with pair (expected ECS_PAIR)"); - return -1; - } + /* If subject is neither table nor entitiy, with operates on literal */ + } else if (!tvar && !evar && !wildcard_subj) { + op->kind = EcsRuleWith; + set_input_to_subj(rule, op, term, subj); - if (role == ECS_CASE && !obj) { - term_error(world, term, name, - "missing object for term with ECS_CASE role"); - return -1; + /* If subject is table or entity but not known, use select */ + } else { + ecs_assert(wildcard_subj || subj != NULL, ECS_INTERNAL_ERROR, NULL); + op->kind = EcsRuleSelect; + if (!wildcard_subj) { + set_output_to_subj(rule, op, term, subj); + written[subj->id] = true; + } } - if (!pred) { - term_error(world, term, name, "missing predicate for term"); - return -1; - } + /* If supersets of subject are being evaluated, and we're looking for a + * specific filter, stop as soon as the filter has been matched. */ + if (eval_subject_supersets && is_pair_known(rule, &op->filter, written)) { + op = insert_operation(rule, -1, written); - if (role != (id & ECS_ROLE_MASK)) { - term_error(world, term, name, "mismatch between term.role & term.id"); - return -1; + /* When the next operation returns, it will first hit SetJmp with a redo + * which will switch the jump label to the previous operation */ + op->kind = EcsRuleSetJmp; + op->on_pass = rule->operation_count; + op->on_fail = lbl_start - 1; } - if (obj && !ECS_HAS_ROLE(id, PAIR) && !ECS_HAS_ROLE(id, CASE)) { - term_error(world, term, name, "term has object but id is not a pair"); - return -1; + if (op->filter.reg_mask & RULE_PAIR_PREDICATE) { + written[op->filter.pred.reg] = true; } - if (ECS_HAS_ROLE(id, PAIR) || ECS_HAS_ROLE(id, CASE)) { - if (!wildcard) { - role = ECS_ROLE_MASK & id; - if (id != (role | ecs_entity_t_comb( - term->obj.entity, term->pred.entity))) - { - char *id_str = ecs_id_str(world, ecs_pair(pred, obj)); - term_error(world, term, name, - "term id does not match pred/obj (%s)", id_str); - ecs_os_free(id_str); - return -1; - } - } - } else if (term->pred.entity != (id & ECS_COMPONENT_MASK)) { - if (!wildcard) { - char *pred_str = ecs_get_fullpath(world, term->pred.entity); - term_error(world, term, name, "term id does not match pred '%s'", - pred_str); - ecs_os_free(pred_str); - return -1; - } + if (op->filter.reg_mask & RULE_PAIR_OBJECT) { + written[op->filter.obj.reg] = true; } - - return 0; } -bool ecs_identifier_is_0( - const char *id) +static +void prepare_predicate( + ecs_rule_t *rule, + ecs_rule_pair_t *pair, + bool *written) { - return id[0] == '0' && !id[1]; -} + /* If pair is not final, resolve term for all IsA relationships of the + * predicate. Note that if the pair has final set to true, it is guaranteed + * that the predicate can be used in an IsA query */ + if (!pair->final) { + ecs_rule_pair_t isa_pair = { + .pred.ent = EcsIsA, + .obj.ent = pair->pred.ent + }; -const char* ecs_identifier_is_var( - const char *id) -{ - if (!id) { - return NULL; - } + ecs_rule_var_t *pred = store_inclusive_set( + rule, EcsRuleSubSet, &isa_pair, written, true); - /* Variable identifiers cannot start with a number */ - if (isdigit(id[0])) { - return NULL; + pair->pred.reg = pred->id; + pair->reg_mask |= RULE_PAIR_PREDICATE; } +} - /* Identifiers that start with _ are variables */ - if (id[0] == '_') { - return &id[1]; +static +void insert_term_2( + ecs_rule_t *rule, + ecs_term_t *term, + ecs_rule_pair_t *filter, + int32_t c, + bool *written) +{ + int32_t subj_id = -1, obj_id = -1; + ecs_rule_var_t *subj = term_subj(rule, term); + if ((subj = get_most_specific_var(rule, subj, written))) { + subj_id = subj->id; } - /* Identifiers that have a single uppercase character are variables */ - if (ecs_os_strlen(id) == 1 && isupper(id[0])) { - return id; + ecs_rule_var_t *obj = term_obj(rule, term); + if ((obj = get_most_specific_var(rule, obj, written))) { + obj_id = obj->id; } - return NULL; -} + if (!filter->transitive) { + insert_select_or_with(rule, c, term, subj, filter, written); -bool ecs_id_match( - ecs_id_t id, - ecs_id_t pattern) -{ - if (id == pattern) { - return true; - } + } else if (filter->transitive) { + if (is_known(subj, written)) { + if (is_known(obj, written)) { + ecs_rule_var_t *obj_subsets = store_inclusive_set( + rule, EcsRuleSubSet, filter, written, true); - if (ECS_HAS_ROLE(pattern, PAIR)) { - if (!ECS_HAS_ROLE(id, PAIR)) { - return false; - } + if (subj) { + subj = &rule->variables[subj_id]; + } - ecs_entity_t id_rel = ECS_PAIR_RELATION(id); - ecs_entity_t id_obj = ECS_PAIR_OBJECT(id); - ecs_entity_t pattern_rel = ECS_PAIR_RELATION(pattern); - ecs_entity_t pattern_obj = ECS_PAIR_OBJECT(pattern); + ecs_rule_pair_t pair = *filter; + pair.obj.reg = obj_subsets->id; + pair.reg_mask |= RULE_PAIR_OBJECT; - ecs_check(id_rel != 0, ECS_INVALID_PARAMETER, NULL); - ecs_check(id_obj != 0, ECS_INVALID_PARAMETER, NULL); + insert_select_or_with(rule, c, term, subj, &pair, written); + } else { + ecs_assert(obj != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_check(pattern_rel != 0, ECS_INVALID_PARAMETER, NULL); - ecs_check(pattern_obj != 0, ECS_INVALID_PARAMETER, NULL); - - if (pattern_rel == EcsWildcard) { - if (pattern_obj == EcsWildcard || pattern_obj == id_obj) { - return true; - } - } else if (pattern_obj == EcsWildcard) { - if (pattern_rel == id_rel) { - return true; - } - } - } else { - if ((id & ECS_ROLE_MASK) != (pattern & ECS_ROLE_MASK)) { - return false; - } + /* If subject is literal, find supersets for subject */ + if (subj == NULL || subj->kind == EcsRuleVarKindEntity) { + obj = to_entity(rule, obj); - if ((ECS_COMPONENT_MASK & pattern) == EcsWildcard) { - return true; - } - } + ecs_rule_pair_t set_pair = *filter; + set_pair.reg_mask &= RULE_PAIR_PREDICATE; -error: - return false; -} + if (subj) { + set_pair.obj.reg = subj->id; + set_pair.reg_mask |= RULE_PAIR_OBJECT; + } else { + set_pair.obj.ent = term->subj.entity; + } -bool ecs_id_is_pair( - ecs_id_t id) -{ - return ECS_HAS_ROLE(id, PAIR); -} + insert_inclusive_set(rule, EcsRuleSuperSet, obj, set_pair, + c, written, filter->inclusive); -bool ecs_id_is_wildcard( - ecs_id_t id) -{ - if (id == EcsWildcard) { - return true; - } else if (ECS_HAS_ROLE(id, PAIR)) { - return ECS_PAIR_RELATION(id) == EcsWildcard || - ECS_PAIR_OBJECT(id) == EcsWildcard; - } + /* If subject is variable, first find matching pair for the + * evaluated entity(s) and return supersets */ + } else { + ecs_rule_var_t *av = create_anonymous_variable( + rule, EcsRuleVarKindEntity); - return false; -} + subj = &rule->variables[subj_id]; + obj = &rule->variables[obj_id]; + obj = to_entity(rule, obj); -bool ecs_term_id_is_set( - const ecs_term_id_t *id) -{ - return id->entity != 0 || id->name != NULL; -} + ecs_rule_pair_t set_pair = *filter; + set_pair.obj.reg = av->id; + set_pair.reg_mask |= RULE_PAIR_OBJECT; -bool ecs_term_is_initialized( - const ecs_term_t *term) -{ - return term->id != 0 || ecs_term_id_is_set(&term->pred); -} + /* Insert with to find initial object for relation */ + insert_select_or_with( + rule, c, term, subj, &set_pair, written); -bool ecs_term_is_trivial( - const ecs_term_t *term) -{ - if (term->inout != EcsInOutDefault) { - return false; - } + push_frame(rule); - if (term->subj.entity != EcsThis) { - return false; - } + /* Find supersets for returned initial object. Make sure + * this is always inclusive since it needs to return the + * object from the pair that the entity has itself. */ + insert_inclusive_set(rule, EcsRuleSuperSet, obj, set_pair, + c, written, true); + } + } - if (term->subj.set.mask && (term->subj.set.mask != EcsSelf)) { - return false; - } + /* subj is not known */ + } else { + ecs_assert(subj != NULL, ECS_INTERNAL_ERROR, NULL); - if (term->oper != EcsAnd && term->oper != EcsAndFrom) { - return false; - } + if (is_known(obj, written)) { + ecs_rule_pair_t set_pair = *filter; + set_pair.reg_mask &= RULE_PAIR_PREDICATE; /* clear object mask */ - if (term->name != NULL) { - return false; - } + if (obj) { + set_pair.obj.reg = obj->id; + set_pair.reg_mask |= RULE_PAIR_OBJECT; + } else { + set_pair.obj.ent = term->obj.entity; + } - return true; -} + insert_inclusive_set(rule, EcsRuleSubSet, subj, set_pair, c, + written, filter->inclusive); + } else if (subj == obj) { + insert_select_or_with(rule, c, term, subj, filter, written); + } else { + ecs_assert(obj != NULL, ECS_INTERNAL_ERROR, NULL); -int ecs_term_finalize( - const ecs_world_t *world, - const char *name, - ecs_term_t *term) -{ - if (finalize_term_vars(world, term, name)) { - return -1; - } + ecs_rule_var_t *av = create_anonymous_variable( + rule, EcsRuleVarKindEntity); - if (!term->id) { - if (finalize_term_id(world, term, name)) { - return -1; - } - } else { - if (populate_from_term_id(world, term, name)) { - return -1; - } - } + subj = &rule->variables[subj_id]; + obj = &rule->variables[obj_id]; + obj = to_entity(rule, obj); - if (finalize_term_identifiers(world, term, name)) { - return -1; - } + /* TODO: this instruction currently does not return inclusive + * results. For example, it will return IsA(XWing, Machine) and + * IsA(XWing, Thing), but not IsA(XWing, XWing). To enable + * inclusive behavior, we need to be able to find all subjects + * that have IsA relationships, without expanding to all + * IsA relationships. For this a new mode needs to be supported + * where an operation never does a redo. + * + * This select can then be used to find all subjects, and those + * same subjects can then be used to find all (inclusive) + * supersets for those subjects. */ - if (!term_can_inherit(term)) { - if (term->subj.set.relation == EcsIsA) { - term->subj.set.relation = 0; - term->subj.set.mask = EcsSelf; - } - } + /* Insert instruction to find all subjects and objects */ + ecs_rule_op_t *op = insert_operation(rule, -1, written); + op->kind = EcsRuleSelect; + set_output_to_subj(rule, op, term, subj); - if (verify_term_consistency(world, term, name)) { - return -1; - } + /* Set object to anonymous variable */ + op->filter.pred = filter->pred; + op->filter.obj.reg = av->id; + op->filter.reg_mask = filter->reg_mask | RULE_PAIR_OBJECT; - return 0; -} + written[subj->id] = true; + written[av->id] = true; -ecs_term_t ecs_term_copy( - const ecs_term_t *src) -{ - ecs_term_t dst = *src; - dst.name = ecs_os_strdup(src->name); - dst.pred.name = ecs_os_strdup(src->pred.name); - dst.subj.name = ecs_os_strdup(src->subj.name); - dst.obj.name = ecs_os_strdup(src->obj.name); - return dst; -} + /* Create new frame for operations that create inclusive set */ + push_frame(rule); -ecs_term_t ecs_term_move( - ecs_term_t *src) -{ - if (src->move) { - ecs_term_t dst = *src; - src->name = NULL; - src->pred.name = NULL; - src->subj.name = NULL; - src->obj.name = NULL; - return dst; - } else { - return ecs_term_copy(src); + /* Insert superset instruction to find all supersets */ + insert_inclusive_set(rule, EcsRuleSuperSet, obj, op->filter, c, + written, true); + } + } } } -void ecs_term_fini( - ecs_term_t *term) +static +void insert_term_1( + ecs_rule_t *rule, + ecs_term_t *term, + ecs_rule_pair_t *filter, + int32_t c, + bool *written) { - ecs_os_free(term->pred.name); - ecs_os_free(term->subj.name); - ecs_os_free(term->obj.name); - ecs_os_free(term->name); - - term->pred.name = NULL; - term->subj.name = NULL; - term->obj.name = NULL; - term->name = NULL; + ecs_rule_var_t *subj = term_subj(rule, term); + subj = get_most_specific_var(rule, subj, written); + insert_select_or_with(rule, c, term, subj, filter, written); } -int ecs_filter_finalize( - const ecs_world_t *world, - ecs_filter_t *f) +static +void insert_term( + ecs_rule_t *rule, + ecs_term_t *term, + int32_t c, + bool *written) { - int32_t i, term_count = f->term_count, actual_count = 0; - ecs_term_t *terms = f->terms; - bool is_or = false, prev_or = false; - int32_t filter_terms = 0; + bool obj_set = obj_is_set(term); - for (i = 0; i < term_count; i ++) { - ecs_term_t *term = &terms[i]; + ensure_most_specific_var(rule, term_pred(rule, term), written); + if (obj_set) { + ensure_most_specific_var(rule, term_obj(rule, term), written); + } - if (ecs_term_finalize(world, f->name, term)) { - return -1; - } + /* If term has Not operator, prepend Not which turns a fail into a pass */ + int32_t prev = rule->operation_count; + ecs_rule_op_t *not_pre; + if (term->oper == EcsNot) { + not_pre = insert_operation(rule, -1, written); + not_pre->kind = EcsRuleNot; + not_pre->has_in = false; + not_pre->has_out = false; + } - is_or = term->oper == EcsOr; - actual_count += !(is_or && prev_or); - term->index = actual_count - 1; - prev_or = is_or; + ecs_rule_pair_t filter = term_to_pair(rule, term); + prepare_predicate(rule, &filter, written); - if (term->subj.entity == EcsThis) { - f->match_this = true; - if (term->subj.set.mask != EcsSelf) { - f->match_only_this = false; - } - } else { - f->match_only_this = false; - } + if (subj_is_set(term) && !obj_set) { + insert_term_1(rule, term, &filter, c, written); + } else if (obj_set) { + insert_term_2(rule, term, &filter, c, written); + } - if (term->id == EcsPrefab) { - f->match_prefab = true; - } - if (term->id == EcsDisabled) { - f->match_disabled = true; - } + /* If term has Not operator, append Not which turns a pass into a fail */ + if (term->oper == EcsNot) { + ecs_rule_op_t *not_post = insert_operation(rule, -1, written); + not_post->kind = EcsRuleNot; + not_post->has_in = false; + not_post->has_out = false; - if (f->filter) { - term->inout = EcsInOutFilter; - } + not_post->on_pass = prev - 1; + not_post->on_fail = prev - 1; + not_pre = &rule->operations[prev]; + not_pre->on_fail = rule->operation_count; + } - if (term->inout == EcsInOutFilter) { - filter_terms ++; - } + if (term->oper == EcsOptional) { + /* Insert jump instruction that ensures that the optional term is only + * executed once */ + ecs_rule_op_t *jump = insert_operation(rule, -1, written); + jump->kind = EcsRuleNot; + jump->has_in = false; + jump->has_out = false; + jump->on_pass = rule->operation_count; + jump->on_fail = prev - 1; - if (term->oper != EcsNot || term->subj.entity != EcsThis) { - f->match_anything = false; + /* Find exit instruction for optional term, and make the fail label + * point to the Not operation, so that even when the operation fails, + * it won't discard the result */ + int i, min_fail = -1, exit_op = -1; + for (i = prev; i < rule->operation_count; i ++) { + ecs_rule_op_t *op = &rule->operations[i]; + if (min_fail == -1 || (op->on_fail >= 0 && op->on_fail < min_fail)){ + min_fail = op->on_fail; + exit_op = i; + } } - } - - f->term_count_actual = actual_count; - if (filter_terms == term_count) { - f->filter = true; + ecs_assert(exit_op != -1, ECS_INTERNAL_ERROR, NULL); + ecs_rule_op_t *op = &rule->operations[exit_op]; + op->on_fail = rule->operation_count - 1; } - return 0; + push_frame(rule); } -/* Implementation for iterable mixin */ +/* Create program from operations that will execute the query */ static -void filter_iter_init( - const ecs_world_t *world, - const ecs_poly_t *poly, - ecs_iter_t *iter, - ecs_term_t *filter) -{ - ecs_poly_assert(poly, ecs_filter_t); - - if (filter) { - iter[1] = ecs_filter_iter(world, (ecs_filter_t*)poly); - iter[0] = ecs_term_chain_iter(&iter[1], filter); - } else { - iter[0] = ecs_filter_iter(world, (ecs_filter_t*)poly); - } -} - -int ecs_filter_init( - const ecs_world_t *stage, - ecs_filter_t *filter_out, - const ecs_filter_desc_t *desc) +void compile_program( + ecs_rule_t *rule) { - ecs_filter_t f; - ecs_poly_init(&f, ecs_filter_t); + /* Trace which variables have been written while inserting instructions. + * This determines which instruction needs to be inserted */ + bool written[ECS_RULE_MAX_VARIABLE_COUNT] = { false }; - ecs_check(stage != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(filter_out != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_term_t *terms = rule->filter.terms; + int32_t v, c, term_count = rule->filter.term_count; + ecs_rule_op_t *op; - const ecs_world_t *world = ecs_get_world(stage); + /* Insert input, which is always the first instruction */ + insert_input(rule); - int i, term_count = 0; - ecs_term_t *terms = desc->terms_buffer; - const char *name = desc->name; - const char *expr = desc->expr; + /* First insert all instructions that do not have a variable subject. Such + * instructions iterate the type of an entity literal and are usually good + * candidates for quickly narrowing down the set of potential results. */ + for (c = 0; c < term_count; c ++) { + ecs_term_t *term = &terms[c]; + if (skip_term(term)) { + continue; + } - /* Temporarily set the fields to the values provided in desc, until the - * filter has been validated. */ - f.name = (char*)name; - f.expr = (char*)expr; - f.filter = desc->filter; - f.instanced = desc->instanced; - f.match_anything = true; + if (term->oper == EcsOptional) { + continue; + } - if (terms) { - terms = desc->terms_buffer; - term_count = desc->terms_buffer_count; - } else { - terms = (ecs_term_t*)desc->terms; - for (i = 0; i < ECS_TERM_DESC_CACHE_SIZE; i ++) { - if (!ecs_term_is_initialized(&terms[i])) { - break; - } + ecs_rule_var_t* subj = term_subj(rule, term); + if (subj) { + continue; + } - term_count ++; + if (term->subj.entity == EcsWildcard) { + continue; } - } - /* Temporarily set array from desc to filter, until the filter has been - * validated. */ - f.terms = terms; - f.term_count = term_count; + insert_term(rule, term, c, written); + } - if (expr) { -#ifdef FLECS_PARSER - int32_t buffer_count = 0; + /* Insert variables based on dependency order */ + for (v = 0; v < rule->subject_variable_count; v ++) { + ecs_rule_var_t *var = &rule->variables[v]; - /* If terms have already been set, copy buffer to allocated one */ - if (terms && term_count) { - terms = ecs_os_memdup(terms, term_count * ECS_SIZEOF(ecs_term_t)); - buffer_count = term_count; - } else { - terms = NULL; - } + ecs_assert(var->kind == EcsRuleVarKindTable, ECS_INTERNAL_ERROR, NULL); - /* Parse expression into array of terms */ - const char *ptr = desc->expr; - ecs_term_t term = {0}; - while (ptr[0] && (ptr = ecs_parse_term(world, name, expr, ptr, &term))){ - if (!ecs_term_is_initialized(&term)) { - break; - } - - if (term_count == buffer_count) { - buffer_count = buffer_count ? buffer_count * 2 : 8; - terms = ecs_os_realloc(terms, - buffer_count * ECS_SIZEOF(ecs_term_t)); + for (c = 0; c < term_count; c ++) { + ecs_term_t *term = &terms[c]; + if (skip_term(term)) { + continue; } - terms[term_count] = term; - term_count ++; + if (term->oper == EcsOptional) { + continue; + } - if (ptr[0] == '\n') { - break; + /* Only process columns for which variable is subject */ + ecs_rule_var_t* subj = term_subj(rule, term); + if (subj != var) { + continue; } - } - f.terms = terms; - f.term_count = term_count; + insert_term(rule, term, c, written); - if (!ptr) { - goto error; + var = &rule->variables[v]; } -#else - ecs_abort(ECS_UNSUPPORTED, "parser addon is not available"); -#endif - } - - /* Ensure all fields are consistent and properly filled out */ - if (ecs_filter_finalize(world, &f)) { - goto error; } - *filter_out = f; + /* Insert terms with wildcard subject */ + for (c = 0; c < term_count; c ++) { + ecs_term_t *term = &terms[c]; - /* Copy term resources. */ - if (term_count) { - if (!filter_out->expr) { - if (term_count < ECS_TERM_CACHE_SIZE) { - filter_out->terms = filter_out->term_cache; - filter_out->term_cache_used = true; - } else { - filter_out->terms = ecs_os_malloc_n(ecs_term_t, term_count); - } + if (term->subj.entity != EcsWildcard) { + continue; } - for (i = 0; i < term_count; i ++) { - filter_out->terms[i] = ecs_term_move(&terms[i]); - } - } else { - filter_out->terms = NULL; + insert_term(rule, term, c, written); } - filter_out->name = ecs_os_strdup(desc->name); - filter_out->expr = ecs_os_strdup(desc->expr); - - ecs_assert(!filter_out->term_cache_used || - filter_out->terms == filter_out->term_cache, - ECS_INTERNAL_ERROR, NULL); - - filter_out->iterable.init = filter_iter_init; + /* Insert terms with Not operators */ + for (c = 0; c < term_count; c ++) { + ecs_term_t *term = &terms[c]; + if (term->oper != EcsNot) { + continue; + } - return 0; -error: - /* NULL members that point to non-owned resources */ - if (!f.expr) { - f.terms = NULL; + insert_term(rule, term, c, written); } - f.name = NULL; - f.expr = NULL; - - ecs_filter_fini(&f); - - return -1; -} - -void ecs_filter_copy( - ecs_filter_t *dst, - const ecs_filter_t *src) -{ - if (src) { - *dst = *src; - - int32_t term_count = src->term_count; - - if (src->term_cache_used) { - dst->terms = dst->term_cache; - } else { - dst->terms = ecs_os_memdup_n(src->terms, ecs_term_t, term_count); + /* Insert terms with Optional operators last, as optional terms cannot + * eliminate results, and would just add overhead to evaluation of + * non-matching entities. */ + for (c = 0; c < term_count; c ++) { + ecs_term_t *term = &terms[c]; + if (term->oper != EcsOptional) { + continue; } + + insert_term(rule, term, c, written); + } - int i; - for (i = 0; i < term_count; i ++) { - dst->terms[i] = ecs_term_copy(&src->terms[i]); + /* Verify all subject variables have been written. Subject variables are of + * the table type, and a select/subset should have been inserted for each */ + for (v = 0; v < rule->subject_variable_count; v ++) { + if (!written[v]) { + /* If the table variable hasn't been written, this can only happen + * if an instruction wrote the variable before a select/subset could + * have been inserted for it. Make sure that this is the case by + * testing if an entity variable exists and whether it has been + * written. */ + ecs_rule_var_t *var = find_variable( + rule, EcsRuleVarKindEntity, rule->variables[v].name); + ecs_assert(written[var->id], ECS_INTERNAL_ERROR, var->name); + (void)var; } - } else { - ecs_os_memset_t(dst, 0, ecs_filter_t); } -} -void ecs_filter_move( - ecs_filter_t *dst, - ecs_filter_t *src) -{ - if (src) { - *dst = *src; + /* Make sure that all entity variables are written. With the exception of + * the this variable, which can be returned as a table, other variables need + * to be available as entities. This ensures that all permutations for all + * variables are correctly returned by the iterator. When an entity variable + * hasn't been written yet at this point, it is because it only constrained + * through a common predicate or object. */ + for (; v < rule->variable_count; v ++) { + if (!written[v]) { + ecs_rule_var_t *var = &rule->variables[v]; + ecs_assert(var->kind == EcsRuleVarKindEntity, + ECS_INTERNAL_ERROR, NULL); - if (src->term_cache_used) { - dst->terms = dst->term_cache; + ecs_rule_var_t *table_var = find_variable( + rule, EcsRuleVarKindTable, var->name); + + /* A table variable must exist if the variable hasn't been resolved + * yet. If there doesn't exist one, this could indicate an + * unconstrained variable which should have been caught earlier */ + ecs_assert(table_var != NULL, ECS_INTERNAL_ERROR, var->name); + + /* Insert each operation that takes the table variable as input, and + * yields each entity in the table */ + op = insert_operation(rule, -1, written); + op->kind = EcsRuleEach; + op->r_in = table_var->id; + op->r_out = var->id; + op->frame = rule->frame_count; + op->has_in = true; + op->has_out = true; + written[var->id] = true; + + push_frame(rule); } + } - src->terms = NULL; - src->term_count = 0; - } else { - ecs_os_memset_t(dst, 0, ecs_filter_t); - } + /* Insert yield, which is always the last operation */ + insert_yield(rule); } -void ecs_filter_fini( - ecs_filter_t *filter) +static +void create_variable_name_array( + ecs_rule_t *rule) { - if (filter->terms) { - int i, count = filter->term_count; - for (i = 0; i < count; i ++) { - ecs_term_fini(&filter->terms[i]); - } + if (rule->variable_count) { + rule->variable_names = ecs_os_malloc_n(char*, rule->variable_count); + int i; + for (i = 0; i < rule->variable_count; i ++) { + ecs_rule_var_t *var = &rule->variables[i]; - if (!filter->term_cache_used) { - ecs_os_free(filter->terms); + if (var->kind != EcsRuleVarKindEntity) { + /* Table variables are hidden for applications. */ + rule->variable_names[var->id] = NULL; + } else { + rule->variable_names[var->id] = var->name; + } } } - - ecs_os_free(filter->name); - ecs_os_free(filter->expr); } +/* Implementation for iterable mixin */ static -void filter_str_add_id( +void rule_iter_init( const ecs_world_t *world, - ecs_strbuf_t *buf, - const ecs_term_id_t *id, - bool is_subject, - uint8_t default_set_mask) + const ecs_poly_t *poly, + ecs_iter_t *iter, + ecs_term_t *filter) { - if (id->name) { - ecs_strbuf_appendstr(buf, id->name); - } else if (id->entity) { - bool id_added = false; - if (!is_subject || id->entity != EcsThis) { - char *path = ecs_get_fullpath(world, id->entity); - ecs_strbuf_appendstr(buf, path); - ecs_os_free(path); - id_added = true; - } - - if (id->set.mask != default_set_mask) { - if (id_added) { - ecs_strbuf_list_push(buf, ":", "|"); - } else { - ecs_strbuf_list_push(buf, "", "|"); - } - if (id->set.mask & EcsSelf) { - ecs_strbuf_list_appendstr(buf, "self"); - } - if (id->set.mask & EcsSuperSet) { - ecs_strbuf_list_appendstr(buf, "superset"); - } - if (id->set.mask & EcsSubSet) { - ecs_strbuf_list_appendstr(buf, "subset"); - } - - if (id->set.relation != EcsIsA) { - ecs_strbuf_list_push(buf, "(", ""); - - char *rel_path = ecs_get_fullpath(world, id->set.relation); - ecs_strbuf_appendstr(buf, rel_path); - ecs_os_free(rel_path); - - ecs_strbuf_list_pop(buf, ")"); - } + ecs_poly_assert(poly, ecs_rule_t); - ecs_strbuf_list_pop(buf, ""); - } + if (filter) { + iter[1] = ecs_rule_iter(world, (ecs_rule_t*)poly); + iter[0] = ecs_term_chain_iter(&iter[1], filter); } else { - ecs_strbuf_appendstr(buf, "0"); + iter[0] = ecs_rule_iter(world, (ecs_rule_t*)poly); } } -static -void term_str_w_strbuf( - const ecs_world_t *world, - const ecs_term_t *term, - ecs_strbuf_t *buf) +ecs_rule_t* ecs_rule_init( + ecs_world_t *world, + const ecs_filter_desc_t *desc) { - const ecs_term_id_t *subj = &term->subj; - const ecs_term_id_t *obj = &term->obj; + ecs_rule_t *result = ecs_poly_new(ecs_rule_t); - const uint8_t def_pred_mask = EcsSelf|EcsSubSet; - const uint8_t def_subj_mask = EcsSelf|EcsSuperSet; - const uint8_t def_obj_mask = EcsSelf; + /* Parse the signature expression. This initializes the columns array which + * contains the information about which components/pairs are requested. */ + if (ecs_filter_init(world, &result->filter, desc)) { + goto error; + } - bool pred_set = ecs_term_id_is_set(&term->pred); - bool subj_set = ecs_term_id_is_set(subj); - bool obj_set = ecs_term_id_is_set(obj); + result->world = world; - if (term->role && term->role != ECS_PAIR) { - ecs_strbuf_appendstr(buf, ecs_role_str(term->role)); - ecs_strbuf_appendstr(buf, " "); + /* Rule has no terms */ + if (!result->filter.term_count) { + rule_error(result, "rule has no terms"); + goto error; } - if (term->oper == EcsNot) { - ecs_strbuf_appendstr(buf, "!"); - } else if (term->oper == EcsOptional) { - ecs_strbuf_appendstr(buf, "?"); - } + ecs_term_t *terms = result->filter.terms; + int32_t i, term_count = result->filter.term_count; - if (!subj_set) { - filter_str_add_id(world, buf, &term->pred, false, def_pred_mask); - ecs_strbuf_appendstr(buf, "()"); - } else if (subj_set && subj->entity == EcsThis && subj->set.mask == def_subj_mask) - { - if (term->id) { - char *str = ecs_id_str(world, term->id); - ecs_strbuf_appendstr(buf, str); - ecs_os_free(str); - } else if (pred_set) { - filter_str_add_id(world, buf, &term->pred, false, def_pred_mask); - } - } else { - filter_str_add_id(world, buf, &term->pred, false, def_pred_mask); - ecs_strbuf_appendstr(buf, "("); - filter_str_add_id(world, buf, &term->subj, true, def_subj_mask); - if (obj_set) { - ecs_strbuf_appendstr(buf, ","); - filter_str_add_id(world, buf, &term->obj, false, def_obj_mask); + /* Make sure rule doesn't just have Not terms */ + for (i = 0; i < term_count; i++) { + ecs_term_t *term = &terms[i]; + if (term->oper != EcsNot) { + break; } - ecs_strbuf_appendstr(buf, ")"); } -} + if (i == term_count) { + rule_error(result, "rule cannot only have terms with Not operator"); + goto error; + } -char* ecs_term_str( - const ecs_world_t *world, - const ecs_term_t *term) -{ - ecs_strbuf_t buf = ECS_STRBUF_INIT; - term_str_w_strbuf(world, term, &buf); - return ecs_strbuf_get(&buf); -} + /* Find all variables & resolve dependencies */ + if (scan_variables(result) != 0) { + goto error; + } -char* ecs_filter_str( - const ecs_world_t *world, - const ecs_filter_t *filter) -{ - ecs_strbuf_t buf = ECS_STRBUF_INIT; + /* Generate the opcode array */ + compile_program(result); - ecs_check(!filter->term_cache_used || filter->terms == filter->term_cache, - ECS_INVALID_PARAMETER, NULL); + /* Create array with variable names so this can be easily accessed by + * iterators without requiring access to the ecs_rule_t */ + create_variable_name_array(result); - ecs_term_t *terms = filter->terms; - int32_t i, count = filter->term_count; - int32_t or_count = 0; + /* Create lookup array for subject variables */ + result->subject_variables = ecs_os_malloc_n(int32_t, term_count); - for (i = 0; i < count; i ++) { + for (i = 0; i < term_count; i ++) { ecs_term_t *term = &terms[i]; - - if (i) { - if (terms[i - 1].oper == EcsOr && term->oper == EcsOr) { - ecs_strbuf_appendstr(&buf, " || "); - } else { - ecs_strbuf_appendstr(&buf, ", "); - } - } - - if (or_count < 1) { - if (term->inout == EcsIn) { - ecs_strbuf_appendstr(&buf, "[in] "); - } else if (term->inout == EcsInOut) { - ecs_strbuf_appendstr(&buf, "[inout] "); - } else if (term->inout == EcsOut) { - ecs_strbuf_appendstr(&buf, "[out] "); - } - } - - if (term->oper == EcsOr) { - or_count ++; - } else { - or_count = 0; + if (term_id_is_variable(&term->subj)) { + const char *subj_name = term_id_var_name(&term->subj); + ecs_rule_var_t *subj = find_variable( + result, EcsRuleVarKindEntity, subj_name); + if (subj) { + result->subject_variables[i] = subj->id; + continue; + } } - term_str_w_strbuf(world, term, &buf); + result->subject_variables[i] = -1; } - return ecs_strbuf_get(&buf); + result->iterable.init = rule_iter_init; + + return result; error: + ecs_rule_fini(result); return NULL; } -bool flecs_term_match_table( - ecs_world_t *world, - const ecs_term_t *term, - const ecs_table_t *table, - ecs_type_t type, - ecs_id_t *id_out, - int32_t *column_out, - ecs_entity_t *subject_out, - int32_t *match_index_out, - bool first) +void ecs_rule_fini( + ecs_rule_t *rule) { - const ecs_term_id_t *subj = &term->subj; - ecs_oper_kind_t oper = term->oper; - const ecs_table_t *match_table = table; - ecs_type_t match_type = type; - - ecs_entity_t subj_entity = subj->entity; - if (!subj_entity) { - id_out[0] = term->id; /* no source corresponds with Nothing set mask */ - return true; + int32_t i; + for (i = 0; i < rule->variable_count; i ++) { + ecs_os_free(rule->variables[i].name); } - /* If source is not This, search in table of source */ - if (subj_entity != EcsThis) { - match_table = ecs_get_table(world, subj_entity); - if (match_table) { - match_type = match_table->type; - } else { - match_type = NULL; - } - } else { - /* If filter contains This terms, a table must be provided */ - ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); - } + ecs_os_free(rule->variables); + ecs_os_free(rule->operations); + ecs_os_free(rule->variable_names); + ecs_os_free(rule->subject_variables); - ecs_entity_t source; + ecs_filter_fini(&rule->filter); - /* If first = false, we're searching from an offset. This supports returning - * multiple results when using wildcard filters. */ - int32_t column = 0; - if (!first && column_out && column_out[0] != 0) { - column = column_out[0]; - if (column < 0) { - /* In case column is not from This, flip sign */ - column = -column; - } + ecs_os_free(rule); +} - /* Remove base 1 offset */ - column --; +const ecs_filter_t* ecs_rule_filter( + const ecs_rule_t *rule) +{ + return &rule->filter; +} + +/* Quick convenience function to get a variable from an id */ +static +ecs_rule_var_t* get_variable( + const ecs_rule_t *rule, + int32_t var_id) +{ + if (var_id == UINT8_MAX) { + return NULL; } - /* Find location, source and id of match in table type */ - column = ecs_type_match(world, match_table, match_type, - column, term->id, subj->set.relation, subj->set.min_depth, - subj->set.max_depth, &source, id_out, match_index_out); + return &rule->variables[var_id]; +} - bool result = column != -1; +/* Convert the program to a string. This can be useful to analyze how a rule is + * being evaluated. */ +char* ecs_rule_str( + ecs_rule_t *rule) +{ + ecs_check(rule != NULL, ECS_INVALID_PARAMETER, NULL); + + ecs_world_t *world = rule->world; + ecs_strbuf_t buf = ECS_STRBUF_INIT; + char filter_expr[256]; - if (oper == EcsNot) { - result = !result; - } + int32_t i, count = rule->operation_count; + for (i = 1; i < count; i ++) { + ecs_rule_op_t *op = &rule->operations[i]; + ecs_rule_pair_t pair = op->filter; + ecs_entity_t pred = pair.pred.ent; + ecs_entity_t obj = pair.obj.ent; + const char *pred_name = NULL, *obj_name = NULL; + char *pred_name_alloc = NULL, *obj_name_alloc = NULL; - if (oper == EcsOptional) { - result = true; - } + if (pair.reg_mask & RULE_PAIR_PREDICATE) { + ecs_assert(rule->variables != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_rule_var_t *type_var = &rule->variables[pair.pred.reg]; + pred_name = type_var->name; + } else if (pred) { + pred_name_alloc = ecs_get_fullpath(world, ecs_get_alive(world, pred)); + pred_name = pred_name_alloc; + } - if (!result) { - return false; - } + if (pair.reg_mask & RULE_PAIR_OBJECT) { + ecs_assert(rule->variables != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_rule_var_t *obj_var = &rule->variables[pair.obj.reg]; + obj_name = obj_var->name; + } else if (obj) { + obj_name_alloc = ecs_get_fullpath(world, ecs_get_alive(world, obj)); + obj_name = obj_name_alloc; + } else if (pair.obj_0) { + obj_name = "0"; + } - if (subj_entity != EcsThis) { - if (!source) { - source = subj_entity; + ecs_strbuf_append(&buf, "%2d: [S:%2d, P:%2d, F:%2d] ", i, + op->frame, op->on_pass, op->on_fail); + + bool has_filter = false; + + switch(op->kind) { + case EcsRuleSelect: + ecs_strbuf_append(&buf, "select "); + has_filter = true; + break; + case EcsRuleWith: + ecs_strbuf_append(&buf, "with "); + has_filter = true; + break; + case EcsRuleStore: + ecs_strbuf_append(&buf, "store "); + break; + case EcsRuleSuperSet: + ecs_strbuf_append(&buf, "superset "); + has_filter = true; + break; + case EcsRuleSubSet: + ecs_strbuf_append(&buf, "subset "); + has_filter = true; + break; + case EcsRuleEach: + ecs_strbuf_append(&buf, "each "); + break; + case EcsRuleSetJmp: + ecs_strbuf_append(&buf, "setjmp "); + break; + case EcsRuleJump: + ecs_strbuf_append(&buf, "jump "); + break; + case EcsRuleNot: + ecs_strbuf_append(&buf, "not "); + break; + case EcsRuleYield: + ecs_strbuf_append(&buf, "yield "); + break; + default: + continue; } - } - if (id_out && column < 0) { - id_out[0] = term->id; - } + if (op->has_out) { + ecs_rule_var_t *r_out = get_variable(rule, op->r_out); + if (r_out) { + ecs_strbuf_append(&buf, "O:%s%s ", + r_out->kind == EcsRuleVarKindTable ? "t" : "", + r_out->name); + } else if (op->subject) { + char *subj_path = ecs_get_fullpath(world, op->subject); + ecs_strbuf_append(&buf, "O:%s ", subj_path); + ecs_os_free(subj_path); + } + } - if (column_out) { - if (column >= 0) { - column ++; - if (source != 0) { - column *= -1; + if (op->has_in) { + ecs_rule_var_t *r_in = get_variable(rule, op->r_in); + if (r_in) { + ecs_strbuf_append(&buf, "I:%s%s ", + r_in->kind == EcsRuleVarKindTable ? "t" : "", + r_in->name); + } else if (op->subject) { + char *subj_path = ecs_get_fullpath(world, op->subject); + ecs_strbuf_append(&buf, "I:%s ", subj_path); + ecs_os_free(subj_path); } - column_out[0] = column; - } else { - column_out[0] = 0; } + + if (has_filter) { + if (!obj && !pair.obj_0) { + ecs_os_sprintf(filter_expr, "(%s)", pred_name); + } else { + ecs_os_sprintf(filter_expr, "(%s, %s)", pred_name, obj_name); + } + ecs_strbuf_append(&buf, "F:%s", filter_expr); + } + + ecs_strbuf_appendstr(&buf, "\n"); + + ecs_os_free(pred_name_alloc); + ecs_os_free(obj_name_alloc); } - if (subject_out) { - subject_out[0] = source; + return ecs_strbuf_get(&buf); +error: + return NULL; +} + +/* Public function that returns number of variables. This enables an application + * to iterate the variables and obtain their values. */ +int32_t ecs_rule_variable_count( + const ecs_rule_t *rule) +{ + ecs_assert(rule != NULL, ECS_INTERNAL_ERROR, NULL); + return rule->variable_count; +} + +/* Public function to find a variable by name */ +int32_t ecs_rule_find_variable( + const ecs_rule_t *rule, + const char *name) +{ + ecs_rule_var_t *v = find_variable(rule, EcsRuleVarKindEntity, name); + if (v) { + return v->id; + } else { + return -1; } +} - return result; +/* Public function to get the name of a variable. */ +const char* ecs_rule_variable_name( + const ecs_rule_t *rule, + int32_t var_id) +{ + return rule->variables[var_id].name; } -bool flecs_filter_match_table( - ecs_world_t *world, - const ecs_filter_t *filter, - const ecs_table_t *table, - ecs_id_t *ids, - int32_t *columns, - ecs_entity_t *subjects, - int32_t *match_indices, - int32_t *matches_left, - bool first, - int32_t skip_term) +/* Public function to get the type of a variable. */ +bool ecs_rule_variable_is_entity( + const ecs_rule_t *rule, + int32_t var_id) { - ecs_assert(!filter->term_cache_used || filter->terms == filter->term_cache, - ECS_INTERNAL_ERROR, NULL); + return rule->variables[var_id].kind == EcsRuleVarKindEntity; +} - ecs_type_t type = NULL; - if (table) { - type = table->type; +/* Public function to get the value of a variable. */ +ecs_entity_t ecs_rule_variable( + ecs_iter_t *iter, + int32_t var_id) +{ + ecs_rule_iter_t *it = &iter->iter.rule; + const ecs_rule_t *rule = it->rule; + + /* We can only return entity variables */ + if (rule->variables[var_id].kind == EcsRuleVarKindEntity) { + ecs_rule_reg_t *regs = get_register_frame(it, rule->frame_count - 1); + return entity_reg_get(rule, regs, var_id); + } else { + return 0; } +} - ecs_term_t *terms = filter->terms; - int32_t i, count = filter->term_count; +/* Create rule iterator */ +ecs_iter_t ecs_rule_iter( + const ecs_world_t *world, + const ecs_rule_t *rule) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(rule != NULL, ECS_INVALID_PARAMETER, NULL); - bool is_or = false; - bool or_result = false; - int32_t match_count = 0; + ecs_iter_t result = {0}; + int i; - for (i = 0; i < count; i ++) { - if (i == skip_term) { - continue; - } + result.world = (ecs_world_t*)world; + result.real_world = (ecs_world_t*)ecs_get_world(rule->world); - ecs_term_t *term = &terms[i]; - ecs_term_id_t *subj = &term->subj; - ecs_oper_kind_t oper = term->oper; - const ecs_table_t *match_table = table; - ecs_type_t match_type = type; - int32_t t_i = term->index; + ecs_rule_iter_t *it = &result.iter.rule; + it->rule = rule; - if (!is_or && oper == EcsOr) { - is_or = true; - or_result = false; - } else if (is_or && oper != EcsOr) { - if (!or_result) { - return false; - } + if (rule->operation_count) { + if (rule->variable_count) { + it->registers = ecs_os_malloc_n(ecs_rule_reg_t, + rule->operation_count * rule->variable_count); - is_or = false; + it->variables = ecs_os_malloc_n(ecs_entity_t, rule->variable_count); } + + it->op_ctx = ecs_os_calloc_n(ecs_rule_op_ctx_t, rule->operation_count); - ecs_entity_t subj_entity = subj->entity; - if (!subj_entity) { - if (ids) { - ids[t_i] = term->id; - } - continue; + if (rule->filter.term_count) { + it->columns = ecs_os_malloc_n(int32_t, + rule->operation_count * rule->filter.term_count); } - if (subj_entity != EcsThis) { - match_table = ecs_get_table(world, subj_entity); - if (match_table) { - match_type = match_table->type; - } else { - match_type = NULL; - } - } else { - /* If filter contains This terms, table must be provided */ - ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + for (i = 0; i < rule->filter.term_count; i ++) { + it->columns[i] = -1; } + } - bool result = flecs_term_match_table(world, term, match_table, - match_type, - ids ? &ids[t_i] : NULL, - columns ? &columns[t_i] : NULL, - subjects ? &subjects[t_i] : NULL, - match_indices ? &match_indices[t_i] : NULL, - first); - - if (is_or) { - or_result |= result; - } else if (!result) { - return false; - } + it->op = 0; - /* Match indices is populated with the number of matches for this term. - * This is used to determine whether to keep iterating this table. */ - if (first && match_indices && match_indices[t_i]) { - match_indices[t_i] --; - match_count += match_indices[t_i]; + for (i = 0; i < rule->variable_count; i ++) { + if (rule->variables[i].kind == EcsRuleVarKindEntity) { + entity_reg_set(rule, it->registers, i, EcsWildcard); + } else { + table_reg_set(rule, it->registers, i, NULL); } } - if (matches_left) { - *matches_left = match_count; - } + result.variable_names = rule->variable_names; + result.variable_count = rule->variable_count; + result.term_count = rule->filter.term_count; + result.terms = rule->filter.terms; + result.next = ecs_rule_next; + result.is_filter = rule->filter.filter; - return !is_or || or_result; + return result; } -static -void term_iter_init_no_data( - ecs_term_iter_t *iter) +void ecs_rule_iter_free( + ecs_iter_t *iter) { - iter->term = (ecs_term_t){ .index = -1 }; - iter->self_index = NULL; - iter->index = 0; + ecs_rule_iter_t *it = &iter->iter.rule; + ecs_os_free(it->registers); + ecs_os_free(it->columns); + ecs_os_free(it->op_ctx); + ecs_os_free(it->variables); + it->registers = NULL; + it->columns = NULL; + it->op_ctx = NULL; + flecs_iter_fini(iter); } +/* Edge case: if the filter has the same variable for both predicate and + * object, they are both resolved at the same time but at the time of + * evaluating the filter they're still wildcards which would match columns + * that have different predicates/objects. Do an additional scan to make + * sure the column we're returning actually matches. */ static -void term_iter_init_wildcard( - const ecs_world_t *world, - ecs_term_iter_t *iter) +int32_t find_next_same_var( + ecs_type_t type, + int32_t column, + ecs_id_t pattern) { - iter->term = (ecs_term_t){ .index = -1 }; - iter->self_index = flecs_get_id_record(world, EcsWildcard); - iter->cur = iter->self_index; - iter->index = 0; + /* If same_var is true, this has to be a wildcard pair. We cannot have + * the same variable in a pair, and one part of a pair resolved with + * another part unresolved. */ + ecs_assert(pattern == ecs_pair(EcsWildcard, EcsWildcard), + ECS_INTERNAL_ERROR, NULL); + (void)pattern; + + /* Keep scanning for an id where rel and obj are the same */ + ecs_id_t *ids = ecs_vector_first(type, ecs_id_t); + int32_t i, count = ecs_vector_count(type); + for (i = column + 1; i < count; i ++) { + ecs_id_t id = ids[i]; + if (!ECS_HAS_ROLE(id, PAIR)) { + /* If id is not a pair, this will definitely not match, and we + * will find no further matches. */ + return -1; + } + + if (ECS_PAIR_RELATION(id) == ECS_PAIR_OBJECT(id)) { + /* Found a match! */ + return i; + } + } + + /* No pairs found with same rel/obj */ + return -1; } static -void term_iter_init( +int32_t find_next_column( const ecs_world_t *world, - ecs_term_t *term, - ecs_term_iter_t *iter) -{ - const ecs_term_id_t *subj = &term->subj; + const ecs_table_t *table, + int32_t column, + ecs_rule_filter_t *filter) +{ + ecs_entity_t pattern = filter->mask; + ecs_type_t type = table->type; - iter->term = *term; + if (column == -1) { + ecs_table_record_t *tr = flecs_get_table_record(world, table, pattern); + if (!tr) { + return -1; + } + column = tr->column; + } else { + column ++; - if (subj->set.mask == EcsDefaultSet || subj->set.mask & EcsSelf) { - iter->self_index = flecs_get_id_record(world, term->id); - } + if (ecs_vector_count(table->type) <= column) { + return -1; + } - if (subj->set.mask & EcsSuperSet) { - iter->set_index = flecs_get_id_record(world, - ecs_pair(subj->set.relation, EcsWildcard)); + ecs_id_t *ids = ecs_vector_first(table->type, ecs_id_t); + if (!ecs_id_match(ids[column], pattern)) { + return -1; + } } - iter->index = 0; - if (iter->self_index) { - iter->cur = iter->self_index; - } else { - iter->cur = iter->set_index; + if (filter->same_var) { + column = find_next_same_var(type, column, filter->mask); } + + return column; } -ecs_iter_t ecs_term_iter( - const ecs_world_t *stage, - ecs_term_t *term) +/* This function finds the next table in a table set, and is used by the select + * operation. The function automatically skips empty tables, so that subsequent + * operations don't waste a lot of processing for nothing. */ +static +ecs_table_record_t find_next_table( + ecs_rule_filter_t *filter, + ecs_rule_with_ctx_t *op_ctx) { - ecs_check(stage != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(term != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(term->id != 0, ECS_INVALID_PARAMETER, NULL); + ecs_id_record_t *idr = op_ctx->idr; + const ecs_table_record_t *tables = flecs_id_record_tables(idr); + int32_t i = op_ctx->table_index, count = flecs_id_record_count(idr); + ecs_table_t *table = NULL; + int32_t column = -1; - const ecs_world_t *world = ecs_get_world(stage); + for (; i < count && (column == -1); i ++) { + const ecs_table_record_t *tr = &tables[i]; + table = tr->table; - if (ecs_term_finalize(world, NULL, term)) { - ecs_throw(ECS_INVALID_PARAMETER, NULL); + /* Should only iterate non-empty tables */ + ecs_assert(ecs_table_count(table) != 0, ECS_INTERNAL_ERROR, NULL); + + column = tr->column; + if (filter->same_var) { + column = find_next_same_var(table->type, column - 1, filter->mask); + } } - ecs_iter_t it = { - .real_world = (ecs_world_t*)world, - .world = (ecs_world_t*)stage, - .term_count = 1, - .next = ecs_term_next - }; + if (column == -1) { + table = NULL; + } - term_iter_init(world, term, &it.iter.term); + op_ctx->table_index = i; - return it; -error: - return (ecs_iter_t){ 0 }; + return (ecs_table_record_t){.table = table, .column = column}; } -ecs_iter_t ecs_term_chain_iter( - const ecs_iter_t *chain_it, - ecs_term_t *term) +static +ecs_id_record_t* find_tables( + ecs_world_t *world, + ecs_id_t id) { - ecs_check(chain_it != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(term != NULL, ECS_INVALID_PARAMETER, NULL); - - ecs_world_t *world = chain_it->real_world; - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - - if (ecs_term_finalize(world, NULL, term)) { - ecs_throw(ECS_INVALID_PARAMETER, NULL); + ecs_id_record_t *idr = flecs_get_id_record(world, id); + if (!flecs_id_record_count(idr)) { + /* Skip ids that don't have (non-empty) tables */ + return NULL; } - ecs_iter_t it = { - .chain_it = (ecs_iter_t*)chain_it, - .real_world = (ecs_world_t*)world, - .world = chain_it->world, - .terms = term, - .term_count = 1, - .next = ecs_term_next - }; - - term_iter_init(world, term, &it.iter.term); + return idr; +} - return it; -error: - return (ecs_iter_t){ 0 }; +static +ecs_id_t rule_get_column( + ecs_type_t type, + int32_t column) +{ + ecs_id_t *comp = ecs_vector_get(type, ecs_id_t, column); + ecs_assert(comp != NULL, ECS_INTERNAL_ERROR, NULL); + return *comp; } static -const ecs_table_record_t *next_table( - ecs_term_iter_t *iter) +void set_column( + ecs_iter_t *it, + ecs_rule_op_t *op, + ecs_type_t type, + int32_t column) { - const ecs_table_record_t *tables = flecs_id_record_tables(iter->cur); - int32_t count = flecs_id_record_count(iter->cur); - if (iter->index >= count) { - return NULL; + if (op->term == -1) { + /* If operation is not associated with a term, don't set anything */ + return; } - return &tables[iter->index ++]; + ecs_assert(op->term >= 0, ECS_INTERNAL_ERROR, NULL); + + if (type) { + it->ids[op->term] = rule_get_column(type, column); + } else { + it->ids[op->term] = 0; + } } static -bool term_iter_next( - ecs_world_t *world, - ecs_term_iter_t *iter, - bool match_prefab, - bool match_disabled) +void set_source( + ecs_iter_t *it, + ecs_rule_op_t *op, + ecs_rule_reg_t *regs, + int32_t r) { - ecs_table_t *table = iter->table; - ecs_entity_t source = 0; - const ecs_table_record_t *tr; - ecs_term_t *term = &iter->term; + if (op->term == -1) { + /* If operation is not associated with a term, don't set anything */ + return; + } - do { - if (table) { - iter->cur_match ++; - if (iter->cur_match >= iter->match_count) { - table = NULL; - } else { - iter->last_column = ecs_type_index_of( - table->type, iter->last_column + 1, term->id); - iter->column = iter->last_column + 1; - if (iter->last_column >= 0) { - iter->id = ecs_vector_get( - table->type, ecs_id_t, iter->last_column)[0]; - } - } - } + ecs_assert(op->term >= 0, ECS_INTERNAL_ERROR, NULL); - if (!table) { - if (!(tr = next_table(iter))) { - if (iter->cur != iter->set_index && iter->set_index != NULL) { - iter->cur = iter->set_index; - iter->index = 0; - tr = next_table(iter); - } + const ecs_rule_t *rule = it->iter.rule.rule; + if ((r != UINT8_MAX) && rule->variables[r].kind == EcsRuleVarKindEntity) { + it->subjects[op->term] = reg_get_entity(rule, op, regs, r); + } else { + it->subjects[op->term] = 0; + } +} - if (!tr) { - return false; - } - } +/* Input operation. The input operation acts as a placeholder for the start of + * the program, and creates an entry in the register array that can serve to + * store variables passed to an iterator. */ +static +bool eval_input( + ecs_iter_t *it, + ecs_rule_op_t *op, + int32_t op_index, + bool redo) +{ + (void)it; + (void)op; + (void)op_index; - table = tr->table; + if (!redo) { + /* First operation executed by the iterator. Always return true. */ + return true; + } else { + /* When Input is asked to redo, it means that all other operations have + * exhausted their results. Input itself does not yield anything, so + * return false. This will terminate rule execution. */ + return false; + } +} - if (!match_prefab && (table->flags & EcsTableIsPrefab)) { - continue; - } +static +bool eval_superset( + ecs_iter_t *it, + ecs_rule_op_t *op, + int32_t op_index, + bool redo) +{ + ecs_rule_iter_t *iter = &it->iter.rule; + const ecs_rule_t *rule = iter->rule; + ecs_world_t *world = rule->world; + ecs_rule_superset_ctx_t *op_ctx = &iter->op_ctx[op_index].is.superset; + ecs_rule_superset_frame_t *frame = NULL; + ecs_rule_reg_t *regs = get_registers(iter, op); - if (!match_disabled && (table->flags & EcsTableIsDisabled)) { - continue; - } + /* Get register indices for output */ + int32_t sp; + int32_t r = op->r_out; - if (!ecs_table_count(table)) { - continue; - } + /* Register cannot be a literal, since we need to store things in it */ + ecs_assert(r != UINT8_MAX, ECS_INTERNAL_ERROR, NULL); - iter->table = table; - iter->match_count = tr->count; - iter->cur_match = 0; - iter->last_column = tr->column; - iter->column = tr->column + 1; - iter->id = ecs_vector_get(table->type, ecs_id_t, tr->column)[0]; - } + /* Superset results are always stored in an entity variable */ + ecs_assert(rule->variables[r].kind == EcsRuleVarKindEntity, + ECS_INTERNAL_ERROR, NULL); - if (iter->cur == iter->set_index) { - const ecs_term_id_t *subj = &term->subj; + /* Get queried for id, fill out potential variables */ + ecs_rule_pair_t pair = op->filter; - if (iter->self_index) { - if (flecs_id_record_table(iter->self_index, table) != NULL) { - /* If the table has the id itself and this term matched Self - * we already matched it */ - continue; - } - } + ecs_rule_filter_t filter = pair_to_filter(iter, op, pair); + ecs_rule_filter_t super_filter = { + .mask = ecs_pair(ECS_PAIR_RELATION(filter.mask), EcsWildcard) + }; + ecs_table_t *table = NULL; - /* Test if following the relation finds the id */ - int32_t index = ecs_type_match(world, table, table->type, 0, - term->id, subj->set.relation, subj->set.min_depth, - subj->set.max_depth, &source, &iter->id, NULL); + if (!redo) { + op_ctx->stack = op_ctx->storage; + sp = op_ctx->sp = 0; + frame = &op_ctx->stack[sp]; - if (index == -1) { - source = 0; - continue; - } + /* Get table of object for which to get supersets */ + ecs_entity_t obj = ECS_PAIR_OBJECT(filter.mask); - ecs_assert(source != 0, ECS_INTERNAL_ERROR, NULL); + /* If obj is wildcard, there's nothing to determine a superset for */ + ecs_assert(obj != EcsWildcard, ECS_INTERNAL_ERROR, NULL); - iter->column = (index + 1) * -1; + /* Find first matching column in table */ + table = table_from_entity(world, obj); + int32_t column = find_next_column(world, table, -1, &super_filter); + + /* If no matching column was found, there are no supersets */ + if (column == -1) { + return false; } - break; - } while (true); + ecs_entity_t col_entity = rule_get_column(table->type, column); + ecs_entity_t col_obj = ecs_entity_t_lo(col_entity); - iter->subject = source; + entity_reg_set(rule, regs, r, col_obj); + set_column(it, op, table->type, column); - return true; -} + frame->table = table; + frame->column = column; -bool ecs_term_next( - ecs_iter_t *it) -{ - ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(it->next == ecs_term_next, ECS_INVALID_PARAMETER, NULL); + return true; + } - ecs_term_iter_t *iter = &it->iter.term; - ecs_term_t *term = &iter->term; - ecs_world_t *world = it->real_world; - ecs_table_t *table; + sp = op_ctx->sp; + frame = &op_ctx->stack[sp]; + table = frame->table; + int32_t column = frame->column; - it->ids = &iter->id; - it->subjects = &iter->subject; - it->columns = &iter->column; - it->terms = &iter->term; + ecs_entity_t col_entity = rule_get_column(table->type, column); + ecs_entity_t col_obj = ecs_entity_t_lo(col_entity); + ecs_table_t *next_table = table_from_entity(world, col_obj); - if (term->inout != EcsInOutFilter) { - it->sizes = &iter->size; - it->ptrs = &iter->ptr; - } else { - it->sizes = NULL; - it->ptrs = NULL; + if (next_table) { + sp ++; + frame = &op_ctx->stack[sp]; + frame->table = next_table; + frame->column = -1; } - ecs_iter_t *chain_it = it->chain_it; - if (chain_it) { - ecs_iter_next_action_t next = chain_it->next; - bool match; + do { + frame = &op_ctx->stack[sp]; + table = frame->table; + column = frame->column; - do { - if (!next(chain_it)) { - goto done; - } + column = find_next_column(world, table, column, &super_filter); + if (column != -1) { + op_ctx->sp = sp; + frame->column = column; + col_entity = rule_get_column(table->type, column); + col_obj = ecs_entity_t_lo(col_entity); - table = chain_it->table; - match = flecs_term_match_table(world, term, table, table->type, - it->ids, it->columns, it->subjects, it->match_indices, true); - } while (!match); - goto yield; + entity_reg_set(rule, regs, r, col_obj); + set_column(it, op, table->type, column); - } else { - if (!term_iter_next(world, iter, false, false)) { - goto done; + return true; } - table = iter->table; - - /* Source must either be 0 (EcsThis) or nonzero in case of substitution */ - ecs_assert(iter->subject || iter->cur != iter->set_index, - ECS_INTERNAL_ERROR, NULL); - ecs_assert(iter->table != NULL, ECS_INTERNAL_ERROR, NULL); - } + sp --; + } while (sp >= 0); -yield: - flecs_iter_populate_data(world, it, table, 0, 0, it->ptrs, it->sizes); - it->is_valid = true; - return true; -done: -error: return false; } static -const ecs_filter_t* init_filter_iter( - const ecs_world_t *world, +bool eval_subset( ecs_iter_t *it, - const ecs_filter_t *filter) -{ - ecs_filter_iter_t *iter = &it->iter.filter; - - if (filter) { - iter->filter = *filter; - - if (filter->term_cache_used) { - iter->filter.terms = iter->filter.term_cache; - } - - ecs_filter_finalize(world, &iter->filter); - - ecs_assert(!filter->term_cache_used || - filter->terms == filter->term_cache, ECS_INTERNAL_ERROR, NULL); - } else { - ecs_filter_init(world, &iter->filter, &(ecs_filter_desc_t) { - .terms = {{ .id = EcsWildcard }} - }); - - filter = &iter->filter; - } - - it->term_count = filter->term_count_actual; - - return filter; -} - -ecs_iter_t ecs_filter_iter( - const ecs_world_t *stage, - const ecs_filter_t *filter) + ecs_rule_op_t *op, + int32_t op_index, + bool redo) { - ecs_check(stage != NULL, ECS_INVALID_PARAMETER, NULL); - - const ecs_world_t *world = ecs_get_world(stage); + ecs_rule_iter_t *iter = &it->iter.rule; + const ecs_rule_t *rule = iter->rule; + ecs_world_t *world = rule->world; + ecs_rule_subset_ctx_t *op_ctx = &iter->op_ctx[op_index].is.subset; + ecs_rule_subset_frame_t *frame = NULL; + ecs_table_record_t table_record; + ecs_rule_reg_t *regs = get_registers(iter, op); - ecs_iter_t it = { - .real_world = (ecs_world_t*)world, - .world = (ecs_world_t*)stage, - .terms = filter ? filter->terms : NULL, - .next = ecs_filter_next, - .is_instanced = filter ? filter->instanced : false - }; + /* Get register indices for output */ + int32_t sp, row; + int32_t r = op->r_out; + ecs_assert(r != UINT8_MAX, ECS_INTERNAL_ERROR, NULL); - ecs_filter_iter_t *iter = &it.iter.filter; + /* Get queried for id, fill out potential variables */ + ecs_rule_pair_t pair = op->filter; + ecs_rule_filter_t filter = pair_to_filter(iter, op, pair); + ecs_id_record_t *idr; + ecs_table_t *table = NULL; - filter = init_filter_iter(world, &it, filter); + if (!redo) { + op_ctx->stack = op_ctx->storage; + sp = op_ctx->sp = 0; + frame = &op_ctx->stack[sp]; + idr = frame->with_ctx.idr = find_tables(world, filter.mask); + if (!idr) { + return false; + } - int32_t i, term_count = filter->term_count; - ecs_term_t *terms = filter->terms; - int32_t min_count = -1; - int32_t pivot_term = -1; + frame->with_ctx.table_index = 0; + table_record = find_next_table(&filter, &frame->with_ctx); + + /* If first table set has no non-empty table, yield nothing */ + if (!table_record.table) { + return false; + } - /* Find term that represents smallest superset */ - if (filter->match_this) { - iter->kind = EcsIterEvalIndex; + frame->row = 0; + frame->column = table_record.column; + table_reg_set(rule, regs, r, (frame->table = table_record.table)); + set_column(it, op, table_record.table->type, table_record.column); + return true; + } - for (i = 0; i < term_count; i ++) { - ecs_term_t *term = &terms[i]; + do { + sp = op_ctx->sp; + frame = &op_ctx->stack[sp]; + table = frame->table; + row = frame->row; - ecs_check(term != NULL, ECS_INVALID_PARAMETER, NULL); + /* If row exceeds number of elements in table, find next table in frame that + * still has entities */ + while ((sp >= 0) && (row >= ecs_table_count(table))) { + table_record = find_next_table(&filter, &frame->with_ctx); - if (term->oper != EcsAnd) { - continue; - } + if (table_record.table) { + table = frame->table = table_record.table; + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + frame->row = 0; + frame->column = table_record.column; + set_column(it, op, table_record.table->type, table_record.column); + table_reg_set(rule, regs, r, table); + return true; + } else { + sp = -- op_ctx->sp; + if (sp < 0) { + /* If none of the frames yielded anything, no more data */ + return false; + } + frame = &op_ctx->stack[sp]; + table = frame->table; + idr = frame->with_ctx.idr; + row = ++ frame->row; - if (term->subj.entity != EcsThis) { - continue; + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(idr != NULL, ECS_INTERNAL_ERROR, NULL); } + } - ecs_id_record_t *idr = flecs_get_id_record(world, term->id); - if (!idr) { - /* If one of the terms does not match with any data, iterator - * should not return anything */ - term_iter_init_no_data(&iter->term_iter); - return it; - } + int32_t row_count = ecs_table_count(table); - int32_t table_count = flecs_id_record_count(idr); - if (min_count == -1 || table_count < min_count) { - min_count = table_count; - pivot_term = i; - } - } + /* Table must have at least row elements */ + ecs_assert(row_count > row, ECS_INTERNAL_ERROR, NULL); - if (pivot_term == -1) { - term_iter_init_wildcard(world, &iter->term_iter); - } else { - term_iter_init(world, &terms[pivot_term], &iter->term_iter); - } - } else { - if (!filter->match_anything) { - iter->kind = EcsIterEvalCondition; - term_iter_init_no_data(&iter->term_iter); - } else { - iter->kind = EcsIterEvalNone; - } - } + ecs_entity_t *entities = ecs_vector_first( + table->storage.entities, ecs_entity_t); + ecs_assert(entities != NULL, ECS_INTERNAL_ERROR, NULL); - if (filter->terms == filter->term_cache) { - /* Because we're returning the iterator by value, the address of the - * term cache changes. The ecs_filter_next function will set the correct - * address when it detects that terms is set to NULL */ - iter->filter.terms = NULL; - } + /* The entity used to find the next table set */ + do { + ecs_entity_t e = entities[row]; - it.is_filter = filter->filter; + /* Create look_for expression with the resolved entity as object */ + pair.reg_mask &= ~RULE_PAIR_OBJECT; /* turn of bit because it's not a reg */ + pair.obj.ent = e; + filter = pair_to_filter(iter, op, pair); - return it; -error: - return (ecs_iter_t){ 0 }; -} + /* Find table set for expression */ + table = NULL; + idr = find_tables(world, filter.mask); -ecs_iter_t ecs_filter_chain_iter( - const ecs_iter_t *chain_it, - const ecs_filter_t *filter) -{ - ecs_iter_t it = { - .terms = filter->terms, - .term_count = filter->term_count, - .chain_it = (ecs_iter_t*)chain_it, - .next = ecs_filter_next, - .world = chain_it->world, - .real_world = chain_it->real_world - }; + /* If table set is found, find first non-empty table */ + if (idr) { + ecs_rule_subset_frame_t *new_frame = &op_ctx->stack[sp + 1]; + new_frame->with_ctx.idr = idr; + new_frame->with_ctx.table_index = 0; + table_record = find_next_table(&filter, &new_frame->with_ctx); - ecs_filter_iter_t *iter = &it.iter.filter; - init_filter_iter(it.world, &it, filter); + /* If set contains non-empty table, push it to stack */ + if (table_record.table) { + table = table_record.table; + op_ctx->sp ++; + new_frame->table = table; + new_frame->row = 0; + new_frame->column = table_record.column; + frame = new_frame; + } + } - iter->kind = EcsIterEvalChain; + /* If no table was found for the current entity, advance row */ + if (!table) { + row = ++ frame->row; + } + } while (!table && row < row_count); + } while (!table); - if (filter->terms == filter->term_cache) { - /* See ecs_filter_iter */ - iter->filter.terms = NULL; - } + table_reg_set(rule, regs, r, table); + set_column(it, op, table->type, frame->column); - return it; + return true; } -bool ecs_filter_next( - ecs_iter_t *it) +/* Select operation. The select operation finds and iterates a table set that + * corresponds to its pair expression. */ +static +bool eval_select( + ecs_iter_t *it, + ecs_rule_op_t *op, + int32_t op_index, + bool redo) { - ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(it->next == ecs_filter_next, ECS_INVALID_PARAMETER, NULL); - - if (flecs_iter_next_row(it)) { - return true; - } + ecs_rule_iter_t *iter = &it->iter.rule; + const ecs_rule_t *rule = iter->rule; + ecs_world_t *world = rule->world; + ecs_rule_with_ctx_t *op_ctx = &iter->op_ctx[op_index].is.with; + ecs_table_record_t table_record; + ecs_rule_reg_t *regs = get_registers(iter, op); - return flecs_iter_next_instanced(it, ecs_filter_next_instanced(it)); -error: - return false; -} + /* Get register indices for output */ + int32_t r = op->r_out; + ecs_assert(r != UINT8_MAX, ECS_INTERNAL_ERROR, NULL); -bool ecs_filter_next_instanced( - ecs_iter_t *it) -{ - ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(it->next == ecs_filter_next, ECS_INVALID_PARAMETER, NULL); - ecs_check(it->chain_it != it, ECS_INVALID_PARAMETER, NULL); + /* Get queried for id, fill out potential variables */ + ecs_rule_pair_t pair = op->filter; + ecs_rule_filter_t filter = pair_to_filter(iter, op, pair); + ecs_entity_t pattern = filter.mask; + int32_t *columns = rule_get_columns(iter, op); - ecs_filter_iter_t *iter = &it->iter.filter; - ecs_filter_t *filter = &iter->filter; - ecs_world_t *world = it->real_world; + int32_t column = -1; ecs_table_t *table = NULL; - bool match; - int i; + ecs_id_record_t *idr; - if (!filter->terms) { - filter->terms = filter->term_cache; + if (!redo && op->term != -1) { + it->ids[op->term] = pattern; + columns[op->term] = -1; } - flecs_iter_init(it); - - ecs_iter_t *chain_it = it->chain_it; - ecs_iter_kind_t kind = iter->kind; + /* If this is a redo, we already looked up the table set */ + if (redo) { + idr = op_ctx->idr; + + /* If this is not a redo lookup the table set. Even though this may not be + * the first time the operation is evaluated, variables may have changed + * since last time, which could change the table set to lookup. */ + } else { + /* A table set is a set of tables that all contain at least the + * requested look_for expression. What is returned is a table record, + * which in addition to the table also stores the first occurrance at + * which the requested expression occurs in the table. This reduces (and + * in most cases eliminates) any searching that needs to occur in a + * table type. Tables are also registered under wildcards, which is why + * this operation can simply use the look_for variable directly */ - if (chain_it) { - ecs_assert(kind == EcsIterEvalChain, ECS_INVALID_PARAMETER, NULL); - - ecs_iter_next_action_t next = chain_it->next; - do { - if (!next(chain_it)) { - goto done; - } + idr = op_ctx->idr = find_tables(world, pattern); + } - table = chain_it->table; - match = flecs_filter_match_table(world, filter, table, - it->ids, it->columns, it->subjects, it->match_indices, NULL, - true, -1); - } while (!match); + /* If no table set was found for queried for entity, there are no results */ + if (!idr) { + return false; + } - goto yield; - } else if (kind == EcsIterEvalIndex || kind == EcsIterEvalCondition) { - ecs_term_iter_t *term_iter = &iter->term_iter; - ecs_term_t *term = &term_iter->term; - int32_t pivot_term = term->index; - bool term_is_filter = term->inout == EcsInOutFilter; + /* If this is not a redo, start at the beginning */ + if (!redo) { + op_ctx->table_index = 0; - do { - int32_t matches_left = iter->matches_left; - if (matches_left < 0) { - goto done; - } + /* Return the first table_record in the table set. */ + table_record = find_next_table(&filter, op_ctx); + + /* If no table record was found, there are no results. */ + if (!table_record.table) { + return false; + } - bool first_match = matches_left == 0; - if (first_match) { - if (kind != EcsIterEvalCondition) { - /* Find new match, starting with the leading term */ - if (!term_iter_next(world, term_iter, - filter->match_prefab, filter->match_disabled)) - { - goto done; - } + table = table_record.table; - table = it->table = term_iter->table; - if (pivot_term != -1) { - it->ids[pivot_term] = term_iter->id; - it->subjects[pivot_term] = term_iter->subject; - it->columns[pivot_term] = term_iter->column; - } - } - } else { - /* Progress iterator to next match for table, if any */ - table = it->table; - first_match = false; + /* Set current column to first occurrence of queried for entity */ + column = columns[op->term] = table_record.column; - for (i = filter->term_count_actual - 1; i >= 0; i --) { - if (it->match_indices[i] > 0) { - it->match_indices[i] --; - if (!term_is_filter) { - it->columns[i] ++; - } - break; - } - } - } + /* Store table in register */ + table_reg_set(rule, regs, r, table); + + /* If this is a redo, progress to the next match */ + } else { + /* First test if there are any more matches for the current table, in + * case we're looking for a wildcard. */ + if (filter.wildcard) { + table = table_reg_get(rule, regs, r); + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); - /* Match the remainder of the terms */ - match = flecs_filter_match_table(world, filter, table, - it->ids, it->columns, it->subjects, - it->match_indices, &matches_left, first_match, - pivot_term); - - if (kind == EcsIterEvalCondition && !matches_left) { - matches_left --; - } + column = columns[op->term]; + column = find_next_column(world, table, column, &filter); + columns[op->term] = column; + } - /* Check if there are any terms which have more matching columns */ - if (!first_match) { - matches_left = 0; - for (i = 0; i < filter->term_count_actual; i ++) { - if (it->match_indices[i] > 0) { - matches_left += it->match_indices[i]; - } - } + /* If no next match was found for this table, move to next table */ + if (column == -1) { + table_record = find_next_table(&filter, op_ctx); + if (!table_record.table) { + return false; } - iter->matches_left = matches_left; - } while (!match); + /* Assign new table to table register */ + table_reg_set(rule, regs, r, (table = table_record.table)); - goto yield; + /* Assign first matching column */ + column = columns[op->term] = table_record.column; + } } -done: -error: - flecs_iter_fini(it); - return false; - -yield: - it->offset = 0; - flecs_iter_populate_data(world, it, table, 0, 0, it->ptrs, it->sizes); - it->is_valid = true; - return true; -} - - -static -void observer_callback(ecs_iter_t *it) { - ecs_observer_t *o = it->ctx; - ecs_world_t *world = it->world; + /* If we got here, we found a match. Table and column must be set */ + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(column != -1, ECS_INTERNAL_ERROR, NULL); - if (o->last_event_id == world->event_id) { - /* Already handled this event */ - return; + /* If this is a wildcard query, fill out the variable registers */ + if (filter.wildcard) { + reify_variables(iter, op, &filter, table->type, column); + } + + if (!pair.obj_0) { + set_column(it, op, table->type, column); } - ecs_iter_t user_it = *it; - user_it.term_count = o->filter.term_count_actual; - user_it.terms = o->filter.terms; - user_it.is_filter = o->filter.filter; - user_it.ids = NULL; - user_it.columns = NULL; - user_it.subjects = NULL; - user_it.sizes = NULL; - user_it.ptrs = NULL; + return true; +} - flecs_iter_init(&user_it); +/* With operation. The With operation always comes after either the Select or + * another With operation, and applies additional filters to the table. */ +static +bool eval_with( + ecs_iter_t *it, + ecs_rule_op_t *op, + int32_t op_index, + bool redo) +{ + ecs_rule_iter_t *iter = &it->iter.rule; + const ecs_rule_t *rule = iter->rule; + ecs_world_t *world = rule->world; + ecs_rule_with_ctx_t *op_ctx = &iter->op_ctx[op_index].is.with; + ecs_rule_reg_t *regs = get_registers(iter, op); - ecs_table_t *table = it->table; - ecs_table_t *prev_table = it->other_table; - int32_t pivot_term = it->term_index; - ecs_term_t *term = &o->filter.terms[pivot_term]; + /* Get register indices for input */ + int32_t r = op->r_in; - if (term->oper == EcsNot) { - table = it->other_table; - prev_table = it->table; - } + /* Get queried for id, fill out potential variables */ + ecs_rule_pair_t pair = op->filter; + ecs_rule_filter_t filter = pair_to_filter(iter, op, pair); + int32_t *columns = rule_get_columns(iter, op); - if (!table) { - table = &world->store.root; - } - if (!prev_table) { - prev_table = &world->store.root; + /* If looked for entity is not a wildcard (meaning there are no unknown/ + * unconstrained variables) and this is a redo, nothing more to yield. */ + if (redo && !filter.wildcard) { + return false; } - /* Populate the column for the term that triggered. This will allow the - * matching algorithm to pick the right column in case the term is a - * wildcard matching multiple columns. */ - user_it.columns[0] = 0; - user_it.columns[pivot_term] = it->columns[0]; - - if (flecs_filter_match_table(world, &o->filter, table, - user_it.ids, user_it.columns, user_it.subjects, NULL, NULL, false, -1)) - { - /* Monitor observers only trigger when the filter matches for the first - * time with an entity */ - if (o->is_monitor) { - if (world->is_fini) { - goto done; - } + int32_t column = -1; + ecs_table_t *table = NULL; + ecs_id_record_t *idr; - if (flecs_filter_match_table(world, &o->filter, prev_table, - NULL, NULL, NULL, NULL, NULL, true, -1)) - { - goto done; - } + if (!redo && op->term != -1) { + columns[op->term] = -1; + } - if (term->oper == EcsNot) { - /* Flip event if this is a Not, so OnAdd and OnRemove can be - * reliably used to check if we're entering or leaving the - * monitor */ - if (it->event == EcsOnAdd) { - user_it.event = EcsOnRemove; - } else if (it->event == EcsOnRemove) { - user_it.event = EcsOnAdd; + /* If this is a redo, we already looked up the table set */ + if (redo) { + idr = op_ctx->idr; + + /* If this is not a redo lookup the table set. Even though this may not be + * the first time the operation is evaluated, variables may have changed + * since last time, which could change the table set to lookup. */ + } else { + /* Transitive queries are inclusive, which means that if we have a + * transitive predicate which is provided with the same subject and + * object, it should return true. By default with will not return true + * as the subject likely does not have itself as a relationship, which + * is why this is a special case. + * + * TODO: might want to move this code to a separate with_inclusive + * instruction to limit branches for non-transitive queries (and to keep + * code more readable). + */ + if (pair.transitive && pair.inclusive) { + ecs_entity_t subj = 0, obj = 0; + + if (r == UINT8_MAX) { + subj = op->subject; + } else { + ecs_rule_var_t *v_subj = &rule->variables[r]; + if (v_subj->kind == EcsRuleVarKindEntity) { + subj = entity_reg_get(rule, regs, r); + + /* This is the input for the op, so should always be set */ + ecs_assert(subj != 0, ECS_INTERNAL_ERROR, NULL); + } + } + + /* If subj is set, it means that it is an entity. Try to also + * resolve the object. */ + if (subj) { + /* If the object is not a wildcard, it has been reified. Get the + * value from either the register or as a literal */ + if (!filter.obj_wildcard) { + obj = ecs_entity_t_lo(filter.mask); + if (subj == obj) { + it->ids[op->term] = filter.mask; + return true; + } } } } - flecs_iter_populate_data(world, &user_it, - it->table, it->offset, it->count, user_it.ptrs, user_it.sizes); + /* The With operation finds the table set that belongs to its pair + * filter. The table set is a sparse set that provides an O(1) operation + * to check whether the current table has the required expression. */ + idr = op_ctx->idr = find_tables(world, filter.mask); + } - user_it.ids[it->term_index] = it->event_id; - user_it.system = o->entity; - user_it.term_index = it->term_index; - user_it.self = o->self; - user_it.ctx = o->ctx; - user_it.term_count = o->filter.term_count_actual; + /* If no table set was found for queried for entity, there are no results. + * If this result is a transitive query, the table we're evaluating may not + * be in the returned table set. Regardless, if the filter that contains a + * transitive predicate does not have any tables associated with it, there + * can be no transitive matches for the filter. */ + if (!idr) { + return false; + } - o->action(&user_it); - o->last_event_id = world->event_id; + table = reg_get_table(rule, op, regs, r); + if (!table) { + return false; } -done: - flecs_iter_fini(&user_it); -} + /* If this is not a redo, start at the beginning */ + if (!redo) { + column = find_next_column(world, table, -1, &filter); + + /* If this is a redo, progress to the next match */ + } else { + if (!filter.wildcard) { + return false; + } + + /* Find the next match for the expression in the column. The columns + * array keeps track of the state for each With operation, so that + * even after redoing a With, the search doesn't have to start from + * the beginning. */ + column = find_next_column(world, table, columns[op->term], &filter); + } -ecs_entity_t ecs_observer_init( - ecs_world_t *world, - const ecs_observer_desc_t *desc) -{ - ecs_entity_t entity = 0; - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(!world->is_fini, ECS_INVALID_OPERATION, NULL); - ecs_check(desc->callback != NULL, ECS_INVALID_OPERATION, NULL); + /* If no next match was found for this table, no more data */ + if (column == -1) { + return false; + } - /* If entity is provided, create it */ - ecs_entity_t existing = desc->entity.entity; - entity = ecs_entity_init(world, &desc->entity); + columns[op->term] = column; - bool added = false; - EcsObserver *comp = ecs_get_mut(world, entity, EcsObserver, &added); - if (added) { - ecs_observer_t *observer = flecs_sparse_add( - world->observers, ecs_observer_t); - ecs_assert(observer != NULL, ECS_INTERNAL_ERROR, NULL); - observer->id = flecs_sparse_last_id(world->observers); + /* If we got here, we found a match. Table and column must be set */ + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(column != -1, ECS_INTERNAL_ERROR, NULL); - /* Make writeable copy of filter desc so that we can set name. This will - * make debugging easier, as any error messages related to creating the - * filter will have the name of the observer. */ - ecs_filter_desc_t filter_desc = desc->filter; - filter_desc.name = desc->entity.name; + /* If this is a wildcard query, fill out the variable registers */ + if (filter.wildcard) { + reify_variables(iter, op, &filter, table->type, column); + } - /* Parse filter */ - if (ecs_filter_init(world, &observer->filter, &filter_desc)) { - flecs_observer_fini(world, observer); - return 0; - } + if (!pair.obj_0) { + set_column(it, op, table->type, column); + } - ecs_filter_t *filter = &observer->filter; + set_source(it, op, regs, r); - int i; - for (i = 0; i < ECS_TRIGGER_DESC_EVENT_COUNT_MAX; i ++) { - ecs_entity_t event = desc->events[i]; - if (!event) { - break; - } + return true; +} - if (event == EcsMonitor) { - /* Monitor event must be first and last event */ - ecs_check(i == 0, ECS_INVALID_PARAMETER, NULL); +/* Each operation. The each operation is a simple operation that takes a table + * as input, and outputs each of the entities in a table. This operation is + * useful for rules that match a table, and where the entities of the table are + * used as predicate or object. If a rule contains an each operation, an + * iterator is guaranteed to yield an entity instead of a table. The input for + * an each operation can only be the root variable. */ +static +bool eval_each( + ecs_iter_t *it, + ecs_rule_op_t *op, + int32_t op_index, + bool redo) +{ + ecs_rule_iter_t *iter = &it->iter.rule; + ecs_rule_each_ctx_t *op_ctx = &iter->op_ctx[op_index].is.each; + ecs_rule_reg_t *regs = get_registers(iter, op); + int32_t r_in = op->r_in; + int32_t r_out = op->r_out; + ecs_entity_t e; - observer->events[0] = EcsOnAdd; - observer->events[1] = EcsOnRemove; - observer->event_count ++; - observer->is_monitor = true; - } else { - observer->events[i] = event; - } + /* Make sure in/out registers are of the correct kind */ + ecs_assert(iter->rule->variables[r_in].kind == EcsRuleVarKindTable, + ECS_INTERNAL_ERROR, NULL); + ecs_assert(iter->rule->variables[r_out].kind == EcsRuleVarKindEntity, + ECS_INTERNAL_ERROR, NULL); - observer->event_count ++; + /* Get table, make sure that it contains data. The select operation should + * ensure that empty tables are never forwarded. */ + ecs_table_t *table = table_reg_get(iter->rule, regs, r_in); + if (table) { + int32_t row, count = regs[r_in].count; + int32_t offset = regs[r_in].offset; + + if (!count) { + count = ecs_table_count(table); + ecs_assert(count != 0, ECS_INTERNAL_ERROR, NULL); + } else { + count += offset; } - /* Observer must have at least one event */ - ecs_check(observer->event_count != 0, ECS_INVALID_PARAMETER, NULL); + ecs_entity_t *entities = ecs_vector_first( + table->storage.entities, ecs_entity_t); + ecs_assert(entities != NULL, ECS_INTERNAL_ERROR, NULL); - /* Create a trigger for each term in the filter */ - observer->triggers = ecs_os_malloc_n(ecs_entity_t, - observer->filter.term_count); + /* If this is is not a redo, start from row 0, otherwise go to the + * next entity. */ + if (!redo) { + row = op_ctx->row = offset; + } else { + row = ++ op_ctx->row; + } - for (i = 0; i < filter->term_count; i ++) { - const ecs_term_t *terms = filter->terms; - const ecs_term_t *t = &terms[i]; + /* If row exceeds number of entities in table, return false */ + if (row >= count) { + return false; + } - if (terms[i].subj.entity != EcsThis) { - observer->triggers[i] = 0; - continue; + /* Skip builtin entities that could confuse operations */ + e = entities[row]; + while (e == EcsWildcard || e == EcsThis) { + row ++; + if (row == count) { + return false; } + e = entities[row]; + } + } else { + if (!redo) { + e = entity_reg_get(iter->rule, regs, r_in); + } else { + return false; + } + } - ecs_trigger_desc_t trigger_desc = { - .term = *t, - .callback = observer_callback, - .ctx = observer, - .binding_ctx = desc->binding_ctx, - .match_prefab = observer->filter.match_prefab, - .match_disabled = observer->filter.match_disabled - }; - - if (observer->filter.filter) { - trigger_desc.term.inout = EcsInOutFilter; - } + /* Assign entity */ + entity_reg_set(iter->rule, regs, r_out, e); - ecs_os_memcpy_n(trigger_desc.events, observer->events, ecs_entity_t, - observer->event_count); + return true; +} - observer->triggers[i] = ecs_trigger_init(world, &trigger_desc); - } +/* Store operation. Stores entity in register. This can either be an entity + * literal or an entity variable that will be stored in a table register. The + * latter facilitates scenarios where an iterator only need to return a single + * entity but where the Yield returns tables. */ +static +bool eval_store( + ecs_iter_t *it, + ecs_rule_op_t *op, + int32_t op_index, + bool redo) +{ + (void)op_index; - observer->action = desc->callback; - observer->self = desc->self; - observer->ctx = desc->ctx; - observer->binding_ctx = desc->binding_ctx; - observer->ctx_free = desc->ctx_free; - observer->binding_ctx_free = desc->binding_ctx_free; - observer->entity = entity; + if (redo) { + /* Only ever return result once */ + return false; + } - comp->observer = observer; + ecs_rule_iter_t *iter = &it->iter.rule; + const ecs_rule_t *rule = iter->rule; + ecs_rule_reg_t *regs = get_registers(iter, op); + int32_t r_in = op->r_in; + int32_t r_out = op->r_out; - if (desc->entity.name) { - ecs_trace("#[green]observer#[reset] %s created", - ecs_get_name(world, entity)); - } - } else { - ecs_assert(comp->observer != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_entity_t e = reg_get_entity(rule, op, regs, r_in); + reg_set_entity(rule, regs, r_out, e); - /* If existing entity handle was provided, override existing params */ - if (existing) { - if (desc->callback) { - ((ecs_observer_t*)comp->observer)->action = desc->callback; - } - if (desc->ctx) { - ((ecs_observer_t*)comp->observer)->ctx = desc->ctx; - } - if (desc->binding_ctx) { - ((ecs_observer_t*)comp->observer)->binding_ctx = - desc->binding_ctx; - } - } + if (op->term >= 0) { + ecs_rule_filter_t filter = pair_to_filter(iter, op, op->filter); + it->ids[op->term] = filter.mask; } - return entity; -error: - if (entity) { - ecs_delete(world, entity); - } - return 0; + return true; } -void flecs_observer_fini( - ecs_world_t *world, - ecs_observer_t *observer) +/* A setjmp operation sets the jump label for a subsequent jump label. When the + * operation is first evaluated (redo=false) it sets the label to the on_pass + * label, and returns true. When the operation is evaluated again (redo=true) + * the label is set to on_fail and the operation returns false. */ +static +bool eval_setjmp( + ecs_iter_t *it, + ecs_rule_op_t *op, + int32_t op_index, + bool redo) { - int i, count = observer->filter.term_count; - for (i = 0; i < count; i ++) { - ecs_entity_t trigger = observer->triggers[i]; - if (trigger) { - ecs_delete(world, trigger); - } + ecs_rule_iter_t *iter = &it->iter.rule; + ecs_rule_setjmp_ctx_t *ctx = &iter->op_ctx[op_index].is.setjmp; + + if (!redo) { + ctx->label = op->on_pass; + return true; + } else { + ctx->label = op->on_fail; + return false; } - ecs_os_free(observer->triggers); +} - ecs_filter_fini(&observer->filter); +/* The jump operation jumps to an operation label. The operation always returns + * true. Since the operation modifies the control flow of the program directly, + * the dispatcher does not look at the on_pass or on_fail labels of the jump + * instruction. Instead, the on_pass label is used to store the label of the + * operation that contains the label to jump to. */ +static +bool eval_jump( + ecs_iter_t *it, + ecs_rule_op_t *op, + int32_t op_index, + bool redo) +{ + (void)it; + (void)op; + (void)op_index; - if (observer->ctx_free) { - observer->ctx_free(observer->ctx); - } + /* Passthrough, result is not used for control flow */ + return !redo; +} - if (observer->binding_ctx_free) { - observer->binding_ctx_free(observer->binding_ctx); - } +/* The not operation reverts the result of the operation it embeds */ +static +bool eval_not( + ecs_iter_t *it, + ecs_rule_op_t *op, + int32_t op_index, + bool redo) +{ + (void)it; + (void)op; + (void)op_index; - flecs_sparse_remove(world->observers, observer->id); + return !redo; } -void* ecs_get_observer_ctx( - const ecs_world_t *world, - ecs_entity_t observer) +/* Yield operation. This is the simplest operation, as all it does is return + * false. This will move the solver back to the previous instruction which + * forces redo's on previous operations, for as long as there are matching + * results. */ +static +bool eval_yield( + ecs_iter_t *it, + ecs_rule_op_t *op, + int32_t op_index, + bool redo) { - const EcsObserver *o = ecs_get(world, observer, EcsObserver); - if (o) { - return o->observer->ctx; - } else { - return NULL; - } + (void)it; + (void)op; + (void)op_index; + (void)redo; + + /* Yield always returns false, because there are never any operations after + * a yield. */ + return false; } -void* ecs_get_observer_binding_ctx( - const ecs_world_t *world, - ecs_entity_t observer) +/* Dispatcher for operations */ +static +bool eval_op( + ecs_iter_t *it, + ecs_rule_op_t *op, + int32_t op_index, + bool redo) { - const EcsObserver *o = ecs_get(world, observer, EcsObserver); - if (o) { - return o->observer->binding_ctx; - } else { - return NULL; - } + switch(op->kind) { + case EcsRuleInput: + return eval_input(it, op, op_index, redo); + case EcsRuleSelect: + return eval_select(it, op, op_index, redo); + case EcsRuleWith: + return eval_with(it, op, op_index, redo); + case EcsRuleSubSet: + return eval_subset(it, op, op_index, redo); + case EcsRuleSuperSet: + return eval_superset(it, op, op_index, redo); + case EcsRuleEach: + return eval_each(it, op, op_index, redo); + case EcsRuleStore: + return eval_store(it, op, op_index, redo); + case EcsRuleSetJmp: + return eval_setjmp(it, op, op_index, redo); + case EcsRuleJump: + return eval_jump(it, op, op_index, redo); + case EcsRuleNot: + return eval_not(it, op, op_index, redo); + case EcsRuleYield: + return eval_yield(it, op, op_index, redo); + default: + return false; + } } +/* Utility to copy all registers to the next frame. Keeping track of register + * values for each operation is necessary, because if an operation is asked to + * redo matching, it must to be able to pick up from where it left of */ +static +void push_registers( + ecs_rule_iter_t *it, + int32_t cur, + int32_t next) +{ + if (!it->rule->variable_count) { + return; + } + + ecs_rule_reg_t *src_regs = get_register_frame(it, cur); + ecs_rule_reg_t *dst_regs = get_register_frame(it, next); -/* Move table from empty to non-empty list, or vice versa */ + ecs_os_memcpy_n(dst_regs, src_regs, + ecs_rule_reg_t, it->rule->variable_count); +} + +/* Utility to copy all columns to the next frame. Columns keep track of which + * columns are currently being evaluated for a table, and are populated by the + * Select and With operations. The columns array is important, as it is used + * to tell the application where to find component data. */ static -int32_t move_table( - ecs_table_cache_t *cache, - const ecs_table_t *table, - int32_t index, - ecs_vector_t **dst_array, - ecs_vector_t *src_array, - bool empty) +void push_columns( + ecs_rule_iter_t *it, + int32_t cur, + int32_t next) { - (void)table; + if (!it->rule->filter.term_count) { + return; + } - int32_t new_index = 0, old_index = 0; - ecs_size_t size = cache->size; - int32_t last_src_index = ecs_vector_count(src_array) - 1; - ecs_assert(last_src_index >= 0, ECS_INTERNAL_ERROR, NULL); + int32_t *src_cols = rule_get_columns_frame(it, cur); + int32_t *dst_cols = rule_get_columns_frame(it, next); - ecs_table_cache_hdr_t *elem = ecs_vector_last_t(src_array, size, 8); - - /* The last table of the source array will be moved to the location of the - * table to move, do some bookkeeping to keep things consistent. */ - if (last_src_index) { - int32_t *old_index_ptr = ecs_map_get(cache->index, - int32_t, elem->table->id); - ecs_assert(old_index_ptr != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_os_memcpy_n(dst_cols, src_cols, int32_t, it->rule->filter.term_count); +} - old_index = old_index_ptr[0]; - if (!empty) { - if (old_index >= 0) { - /* old_index should be negative if not empty, since - * we're moving from the empty list to the non-empty list. - * However, if the last table in the source array is also - * the table being moved, this can happen. */ - ecs_assert(table == elem->table, ECS_INTERNAL_ERROR, NULL); - } else { - /* If not empty, src = the empty list, and index should - * be negative. */ - old_index = old_index * -1 - 1; /* Normalize */ - } - } +/* Populate iterator with data before yielding to application */ +static +void populate_iterator( + const ecs_rule_t *rule, + ecs_iter_t *iter, + ecs_rule_iter_t *it, + ecs_rule_op_t *op) +{ + ecs_world_t *world = rule->world; + int32_t r = op->r_in; + ecs_rule_reg_t *regs = get_register_frame(it, op->frame); + ecs_table_t *table = NULL; + int32_t count = 0; + int32_t offset = 0; + + /* If the input register for the yield does not point to a variable, + * the rule doesn't contain a this (.) variable. In that case, the + * iterator doesn't contain any data, and this function will simply + * return true or false. An application will still be able to obtain + * the variables that were resolved. */ + if (r != UINT8_MAX) { + ecs_rule_var_t *var = &rule->variables[r]; + ecs_rule_reg_t *reg = ®s[r]; - if (old_index == last_src_index) { - old_index_ptr[0] = index; + if (var->kind == EcsRuleVarKindTable) { + table = table_reg_get(rule, regs, r); + count = regs[r].count; + offset = regs[r].offset; + } else { + /* If a single entity is returned, simply return the + * iterator with count 1 and a pointer to the entity id */ + ecs_assert(var->kind == EcsRuleVarKindEntity, + ECS_INTERNAL_ERROR, NULL); + + ecs_entity_t e = reg->entity; + ecs_record_t *record = ecs_eis_get(world, e); + offset = ECS_RECORD_TO_ROW(record->row); + + /* If an entity is not stored in a table, it could not have + * been matched by anything */ + ecs_assert(record != NULL, ECS_INTERNAL_ERROR, NULL); + table = record->table; + count = 1; } - } else { - /* If last_src_index is 0, the table to move was the only table in the - * src array, so no other administration needs to be updated. */ } - if (!empty) { - old_index = index * -1 - 1; - } else { - old_index = index; - } + int32_t i, variable_count = rule->variable_count; + int32_t term_count = rule->filter.term_count; + iter->variables = it->variables; - /* Actually move the table. Only move from src to dst if we have a - * dst_array, otherwise just remove it from src. */ - if (dst_array) { - new_index = ecs_vector_count(*dst_array); - ecs_vector_move_index_t(dst_array, src_array, size, 8, old_index); + for (i = 0; i < variable_count; i ++) { + if (rule->variables[i].kind == EcsRuleVarKindEntity) { + it->variables[i] = regs[i].entity; + } else { + it->variables[i] = 0; + } + } - /* Make sure table is where we expect it */ - elem = ecs_vector_last_t(*dst_array, size, 8); - ecs_assert(elem->table == table, ECS_INTERNAL_ERROR, NULL); - ecs_assert(ecs_vector_count(*dst_array) == (new_index + 1), - ECS_INTERNAL_ERROR, NULL); - elem->empty = empty; - } else { - ecs_vector_remove_t(src_array, size, 8, old_index); + for (i = 0; i < term_count; i ++) { + int32_t v = rule->subject_variables[i]; + if (v != -1) { + ecs_rule_var_t *var = &rule->variables[v]; + if (var->kind == EcsRuleVarKindEntity) { + iter->subjects[i] = regs[var->id].entity; + } + } } - /* Ensure that src array has now one element less */ - ecs_assert(ecs_vector_count(src_array) == last_src_index, - ECS_INTERNAL_ERROR, NULL); + /* Iterator expects column indices to start at 1 */ + iter->columns = rule_get_columns_frame(it, op->frame); + for (i = 0; i < term_count; i ++) { + ecs_entity_t subj = iter->subjects[i]; + int32_t c = ++ iter->columns[i]; - if (empty) { - /* Table is now empty, index is negative */ - new_index = new_index * -1 - 1; + if (!subj) { + if (iter->terms[i].subj.entity != EcsThis) { + iter->columns[i] = 0; + } + } else if (c) { + iter->columns[i] = -1; + } } - return new_index; + flecs_iter_populate_data(world, iter, table, offset, count, + iter->ptrs, iter->sizes); } static -void ensure_index( - ecs_table_cache_t *cache) +bool is_control_flow( + ecs_rule_op_t *op) { - if (!cache->index) { - cache->index = ecs_map_new(int32_t, 0); + switch(op->kind) { + case EcsRuleSetJmp: + case EcsRuleJump: + return true; + default: + return false; } } -void _ecs_table_cache_init( - ecs_table_cache_t *cache, - ecs_size_t size, - ecs_poly_t *parent, - void(*free_payload)(ecs_poly_t*, void*)) +/* Iterator next function. This evaluates the program until it reaches a Yield + * operation, and returns the intermediate result(s) to the application. An + * iterator can, depending on the program, either return a table, entity, or + * just true/false, in case a rule doesn't contain the this variable. */ +bool ecs_rule_next( + ecs_iter_t *it) { - ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(size >= ECS_SIZEOF(ecs_table_cache_hdr_t), - ECS_INTERNAL_ERROR, NULL); - cache->index = NULL; - cache->empty_tables = NULL; - cache->tables = NULL; - cache->size = size; - cache->parent = parent; - cache->free_payload = free_payload; -} + ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(it->next == ecs_rule_next, ECS_INVALID_PARAMETER, NULL); -static -void free_payload( - ecs_table_cache_t *cache, - ecs_vector_t *tables) -{ - void(*free_payload_func)(ecs_poly_t*, void*) = cache->free_payload; - if (free_payload_func) { - ecs_poly_t *parent = cache->parent; - ecs_size_t size = cache->size; - int32_t i, count = ecs_vector_count(tables); + ecs_rule_iter_t *iter = &it->iter.rule; + const ecs_rule_t *rule = iter->rule; + bool redo = iter->redo; + int32_t last_frame = -1; + bool init_subjects = it->subjects == NULL; - for (i = 0; i < count; i ++) { - void *ptr = ecs_vector_get_t(tables, size, 8, i); - free_payload_func(parent, ptr); + /* Can't iterate an iterator that's already depleted */ + ecs_check(iter->op != -1, ECS_INVALID_PARAMETER, NULL); + + flecs_iter_init(it); + + /* Make sure that if there are any terms with literal subjects, they're + * initialized in the subjects array */ + if (init_subjects) { + int32_t i; + for (i = 0; i < rule->filter.term_count; i ++) { + ecs_term_t *t = &rule->filter.terms[i]; + ecs_term_id_t *subj = &t->subj; + if (subj->var == EcsVarIsEntity && subj->entity != EcsThis) { + it->subjects[i] = subj->entity; + } + } + + for (i = 0; i < rule->filter.term_count; i ++) { + ecs_term_t *term = &rule->filter.terms[i]; + if (term->subj.set.mask & EcsNothing || + term->oper == EcsNot || + term->oper == EcsOptional || + term->id == ecs_pair(EcsChildOf, 0)) { + it->ids[i] = term->id; + } } } - ecs_vector_free(tables); -} + do { + /* Evaluate an operation. The result of an operation determines the + * flow of the program. If an operation returns true, the program + * continues to the operation pointed to by 'on_pass'. If the operation + * returns false, the program continues to the operation pointed to by + * 'on_fail'. + * + * In most scenarios, on_pass points to the next operation, and on_fail + * points to the previous operation. + * + * When an operation fails, the previous operation will be invoked with + * redo=true. This will cause the operation to continue its search from + * where it left off. When the operation succeeds, the next operation + * will be invoked with redo=false. This causes the operation to start + * from the beginning, which is necessary since it just received a new + * input. */ + int32_t op_index = iter->op; + ecs_rule_op_t *op = &rule->operations[op_index]; + int32_t cur = op->frame; -void ecs_table_cache_fini( - ecs_table_cache_t *cache) -{ - ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_map_free(cache->index); - free_payload(cache, cache->tables); - free_payload(cache, cache->empty_tables); -} + /* If this is not the first operation and is also not a control flow + * operation, push a new frame on the stack for the next operation */ + if (!redo && !is_control_flow(op) && cur && cur != last_frame) { + int32_t prev = cur - 1; + push_registers(iter, prev, cur); + push_columns(iter, prev, cur); + } -bool ecs_table_cache_is_initialized( - ecs_table_cache_t *cache) -{ - return cache->size != 0; + /* Dispatch the operation */ + bool result = eval_op(it, op, op_index, redo); + iter->op = result ? op->on_pass : op->on_fail; + + /* If the current operation is yield, return results */ + if (op->kind == EcsRuleYield) { + populate_iterator(rule, it, iter, op); + iter->redo = true; + return true; + } + + /* If the current operation is a jump, goto stored label */ + if (op->kind == EcsRuleJump) { + /* Label is stored in setjmp context */ + iter->op = iter->op_ctx[op->on_pass].is.setjmp.label; + } + + /* If jumping backwards, it's a redo */ + redo = iter->op <= op_index; + + if (!is_control_flow(op)) { + last_frame = op->frame; + } + } while (iter->op != -1); + + ecs_rule_iter_free(it); + +error: + return false; } -void* _ecs_table_cache_insert( - ecs_table_cache_t *cache, - ecs_size_t size, - const ecs_table_t *table) -{ - ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(size == cache->size, ECS_INTERNAL_ERROR, NULL); +#endif +/* This is a heavily modified version of the EmbeddableWebServer (see copyright + * below). This version has been stripped from everything not strictly necessary + * for receiving/replying to simple HTTP requests, and has been modified to use + * the Flecs OS API. */ - ecs_assert(!table || (_ecs_table_cache_get(cache, size, table) == NULL), - ECS_INTERNAL_ERROR, NULL); +/* EmbeddableWebServer Copyright (c) 2016, 2019, 2020 Forrest Heller, and + * CONTRIBUTORS (see below) - All rights reserved. + * + * CONTRIBUTORS: + * Martin Pulec - bug fixes, warning fixes, IPv6 support + * Daniel Barry - bug fix (ifa_addr != NULL) + * + * Released under the BSD 2-clause license: + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. THIS SOFTWARE IS + * PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN + * NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ - int32_t index; - ecs_table_cache_hdr_t *result; - bool empty; - if (!table) { - empty = false; - } else { - empty = ecs_table_count(table) == 0; - } +#ifdef FLECS_HTTP - if (empty) { - result = ecs_vector_add_t(&cache->empty_tables, size, 8); - index = -ecs_vector_count(cache->empty_tables); - } else { - index = ecs_vector_count(cache->tables); - result = ecs_vector_add_t(&cache->tables, size, 8); - } +#ifdef _MSC_VER +#pragma comment(lib, "Ws2_32.lib") +#include +#include +#include +typedef SOCKET ecs_http_socket_t; +#else +#include +#include +#include +#include +#include +#include +typedef int ecs_http_socket_t; +#endif - if (table) { - ensure_index(cache); - ecs_map_set(cache->index, table->id, &index); - } - - ecs_os_memset(result, 0, size); - result->table = (ecs_table_t*)table; - result->empty = empty; +/* Max length of request method */ +#define ECS_HTTP_METHOD_LEN_MAX (8) - return result; -} +/* Timeout (s) before connection purge */ +#define ECS_HTTP_CONNECTION_PURGE_TIMEOUT (1.0) -void _ecs_table_cache_remove( - ecs_table_cache_t *cache, - ecs_size_t size, - const ecs_table_t *table) -{ - ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(size == cache->size, ECS_INTERNAL_ERROR, NULL); - (void)size; +/* Minimum interval between dequeueing requests (ms) */ +#define ECS_HTTP_MIN_DEQUEUE_INTERVAL (100) - int32_t *index = ecs_map_get(cache->index, int32_t, table->id); - if (!index) { - return; - } +/* Max length of headers in reply */ +#define ECS_HTTP_REPLY_HEADER_SIZE (1024) - if (cache->free_payload) { - ecs_table_cache_hdr_t *elem = _ecs_table_cache_get( - cache, cache->size, table); - ecs_assert(elem != NULL, ECS_INTERNAL_ERROR, NULL); - cache->free_payload(cache->parent, elem); - } +/* Receive buffer size */ +#define ECS_HTTP_SEND_RECV_BUFFER_SIZE (16 * 1024) - if (index[0] < 0) { - move_table(cache, table, index[0], NULL, cache->empty_tables, false); - } else { - move_table(cache, table, index[0], NULL, cache->tables, true); - } +/* Max length of request (path + query + headers + body) */ +#define ECS_HTTP_REQUEST_LEN_MAX (10 * 1024 * 1024) - ecs_map_remove(cache->index, table->id); +/* HTTP server struct */ +struct ecs_http_server_t { + bool should_run; + bool running; - if (!ecs_map_count(cache->index)) { - ecs_assert(ecs_vector_count(cache->tables) == 0, - ECS_INTERNAL_ERROR, NULL); - ecs_assert(ecs_vector_count(cache->empty_tables) == 0, - ECS_INTERNAL_ERROR, NULL); - ecs_table_cache_fini(cache); + ecs_http_socket_t sock; + ecs_os_mutex_t lock; + ecs_os_thread_t thread; - cache->index = NULL; - cache->tables = NULL; - cache->empty_tables = NULL; - } -} + ecs_http_reply_action_t callback; + void *ctx; -void* _ecs_table_cache_get( - const ecs_table_cache_t *cache, - ecs_size_t size, - const ecs_table_t *table) -{ - ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(size == cache->size, ECS_INTERNAL_ERROR, NULL); + ecs_sparse_t *connections; /* sparse */ + ecs_sparse_t *requests; /* sparse */ - int32_t *index = ecs_map_get(cache->index, int32_t, table->id); - if (!index) { - return NULL; - } + bool initialized; - ecs_table_cache_hdr_t *result; - if (index[0] >= 0) { - result = ecs_vector_get_t(cache->tables, size, 8, index[0]); - } else { - result = ecs_vector_get_t( - cache->empty_tables, size, 8, index[0] * -1 - 1); - } + uint16_t port; + const char *ipaddr; - ecs_assert(!result || result->table == table, ECS_INTERNAL_ERROR, NULL); + FLECS_FLOAT delta_time; /* used to not lock request queue too often */ +}; - return result; -} +/** Fragment state, used by HTTP request parser */ +typedef enum { + HttpFragStateBegin, + HttpFragStateMethod, + HttpFragStatePath, + HttpFragStateVersion, + HttpFragStateHeaderStart, + HttpFragStateHeaderName, + HttpFragStateHeaderValueStart, + HttpFragStateHeaderValue, + HttpFragStateCR, + HttpFragStateCRLF, + HttpFragStateCRLFCR, + HttpFragStateBody, + HttpFragStateDone +} HttpFragState; -void ecs_table_cache_set_empty( - ecs_table_cache_t *cache, - const ecs_table_t *table, - bool empty) +/** A fragment is a partially received HTTP request */ +typedef struct { + HttpFragState state; + ecs_strbuf_t buf; + ecs_http_method_t method; + int32_t body_offset; + int32_t query_offset; + int32_t header_offsets[ECS_HTTP_HEADER_COUNT_MAX]; + int32_t header_value_offsets[ECS_HTTP_HEADER_COUNT_MAX]; + int32_t header_count; + int32_t param_offsets[ECS_HTTP_QUERY_PARAM_COUNT_MAX]; + int32_t param_value_offsets[ECS_HTTP_QUERY_PARAM_COUNT_MAX]; + int32_t param_count; + char header_buf[32]; + char *header_buf_ptr; + int32_t content_length; + bool parse_content_length; + bool invalid; +} ecs_http_fragment_t; + +/** Extend public connection type with fragment data */ +typedef struct { + ecs_http_connection_t pub; + ecs_http_fragment_t frag; + ecs_http_socket_t sock; + FLECS_FLOAT dequeue_timeout; /* used to purge inactive connections */ +} ecs_http_connection_impl_t; + +typedef struct { + ecs_http_request_t pub; + void *res; +} ecs_http_request_impl_t; + +static +ecs_size_t http_send( + ecs_http_socket_t sock, + const void *buf, + ecs_size_t size, + int flags) { - ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); +#ifndef _MSC_VER + ssize_t send_bytes = send(sock, buf, flecs_itosize(size), flags); + return flecs_itoi32(send_bytes); +#else + int send_bytes = send(sock, buf, size, flags); + return flecs_itoi32(send_bytes); +#endif +} - int32_t *index = ecs_map_get(cache->index, int32_t, table->id); - if (!index) { - return; +static +ecs_size_t http_recv( + ecs_http_socket_t sock, + void *buf, + ecs_size_t size, + int flags) +{ + ecs_size_t ret; +#ifndef _MSC_VER + ssize_t recv_bytes = recv(sock, buf, flecs_itosize(size), flags); + ret = flecs_itoi32(recv_bytes); +#else + int recv_bytes = recv(sock, buf, size, flags); + ret = flecs_itoi32(recv_bytes); +#endif + if (ret == -1) { + ecs_dbg("recv failed: %s (sock = %d)", ecs_os_strerror(errno), sock); + } else if (ret == 0) { + ecs_dbg("recv: received 0 bytes (sock = %d)", sock); } - /* If table is already in the correct array nothing needs to be done */ - if (empty && index[0] < 0) { - return; - } else if (!empty && index[0] >= 0) { - return; - } + return ret; +} - if (index[0] < 0) { - index[0] = move_table( - cache, table, index[0], &cache->tables, cache->empty_tables, empty); - } else { - index[0] = move_table( - cache, table, index[0], &cache->empty_tables, cache->tables, empty); - } +static +int http_getnameinfo( + const struct sockaddr* addr, + ecs_size_t addr_len, + char *host, + ecs_size_t host_len, + char *port, + ecs_size_t port_len, + int flags) +{ + ecs_assert(addr_len > 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(host_len > 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(port_len > 0, ECS_INTERNAL_ERROR, NULL); + return getnameinfo(addr, (uint32_t)addr_len, host, (uint32_t)host_len, + port, (uint32_t)port_len, flags); } -void* _ecs_table_cache_tables( - const ecs_table_cache_t *cache, - ecs_size_t size) +static +int http_bind( + ecs_http_socket_t sock, + const struct sockaddr* addr, + ecs_size_t addr_len) { - if (!cache) { - return NULL; - } - return ecs_vector_first_t(cache->tables, size, 8); + ecs_assert(addr_len > 0, ECS_INTERNAL_ERROR, NULL); + return bind(sock, addr, (uint32_t)addr_len); } -void* _ecs_table_cache_empty_tables( - const ecs_table_cache_t *cache, - ecs_size_t size) +static +void http_close( + ecs_http_socket_t sock) { - if (!cache) { - return NULL; - } - return ecs_vector_first_t(cache->empty_tables, size, 8); +#ifdef _MSC_VER + closesocket(sock); +#else + shutdown(sock, SHUT_RDWR); + close(sock); +#endif } -int32_t ecs_table_cache_count( - const ecs_table_cache_t *cache) +static +ecs_http_socket_t http_accept( + ecs_http_socket_t sock, + struct sockaddr* addr, + ecs_size_t *addr_len) { - if (!cache) { - return 0; - } - return ecs_vector_count(cache->tables); + socklen_t len = (socklen_t)addr_len[0]; + ecs_http_socket_t result = accept(sock, addr, &len); + addr_len[0] = (ecs_size_t)len; + return result; } -int32_t ecs_table_cache_empty_count( - const ecs_table_cache_t *cache) -{ - if (!cache) { - return 0; - } - return ecs_vector_count(cache->empty_tables); +static +void reply_free(ecs_http_reply_t* response) { + ecs_os_free(response->body.content); } -bool ecs_table_cache_is_empty( - const ecs_table_cache_t *cache) -{ - if (!cache) { - return true; - } - return ecs_map_count(cache->index) == 0; +static +void request_free(ecs_http_request_impl_t *req) { + ecs_os_free(req->res); + flecs_sparse_remove(req->pub.conn->server->requests, req->pub.id); } -void _ecs_table_cache_fini_delete_all( - ecs_world_t *world, - ecs_table_cache_t *cache, - ecs_size_t size) -{ - if (!cache || !cache->index) { - return; +static +void connection_free(ecs_http_connection_impl_t *conn) { + if (conn->sock) { + http_close(conn->sock); } + flecs_sparse_remove(conn->pub.server->connections, conn->pub.id); +} - /* Temporarily set index to NULL, so that when the table tries to remove - * itself from the cache it won't be able to. This keeps the arrays we're - * iterating over consistent */ - ecs_map_t *index = cache->index; - cache->index = NULL; +// https://stackoverflow.com/questions/10156409/convert-hex-string-char-to-int +static +char hex_2_int(char a, char b){ + a = (a <= '9') ? (char)(a - '0') : (char)((a & 0x7) + 9); + b = (b <= '9') ? (char)(b - '0') : (char)((b & 0x7) + 9); + return (char)((a << 4) + b); +} - int32_t i, count = ecs_vector_count(cache->tables); - for (i = 0; i < count; i ++) { - ecs_table_cache_hdr_t *ptr = ecs_vector_get_t( - cache->tables, size, 8, i); - flecs_delete_table(world, ptr->table); +static +void decode_url_str( + char *str) +{ + char ch, *ptr, *dst = str; + for (ptr = str; (ch = *ptr); ptr++) { + if (ch == '%') { + dst[0] = hex_2_int(ptr[1], ptr[2]); + dst ++; + ptr += 2; + } else { + dst[0] = ptr[0]; + dst ++; + } } + dst[0] = '\0'; +} - count = ecs_vector_count(cache->empty_tables); - for (i = 0; i < count; i ++) { - ecs_table_cache_hdr_t *ptr = ecs_vector_get_t( - cache->empty_tables, size, 8, i); - flecs_delete_table(world, ptr->table); +static +void parse_method( + ecs_http_fragment_t *frag) +{ + char *method = ecs_strbuf_get_small(&frag->buf); + if (!ecs_os_strcmp(method, "GET")) frag->method = EcsHttpGet; + else if (!ecs_os_strcmp(method, "POST")) frag->method = EcsHttpPost; + else if (!ecs_os_strcmp(method, "PUT")) frag->method = EcsHttpPut; + else if (!ecs_os_strcmp(method, "DELETE")) frag->method = EcsHttpDelete; + else { + frag->method = EcsHttpMethodUnsupported; + frag->invalid = true; } + ecs_strbuf_reset(&frag->buf); +} - cache->index = index; - - ecs_table_cache_fini(cache); +static +bool header_writable( + ecs_http_fragment_t *frag) +{ + return frag->header_count < ECS_HTTP_HEADER_COUNT_MAX; } +static +void header_buf_reset( + ecs_http_fragment_t *frag) +{ + frag->header_buf[0] = '\0'; + frag->header_buf_ptr = frag->header_buf; +} static -bool match_id( - const ecs_world_t *world, - ecs_entity_t id, - ecs_entity_t match_with) +void header_buf_append( + ecs_http_fragment_t *frag, + char ch) { - (void)world; - - if (ECS_HAS_ROLE(match_with, CASE)) { - ecs_entity_t sw = ECS_PAIR_RELATION(match_with); - if (id == (ECS_SWITCH | sw)) { -#ifdef FLECS_SANITIZE - ecs_entity_t sw_case = ECS_PAIR_OBJECT(match_with); - const EcsType *sw_type = ecs_get(world, sw, EcsType); - ecs_assert(sw_type != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(ecs_type_has_id(world, sw_type->normalized, - ecs_get_alive(world, sw_case), false) == true, - ECS_INVALID_PARAMETER, NULL); - (void)sw_case; - (void)sw_type; -#endif - return true; - } else { - return false; - } + if ((frag->header_buf_ptr - frag->header_buf) < + ECS_SIZEOF(frag->header_buf)) + { + frag->header_buf_ptr[0] = ch; + frag->header_buf_ptr ++; } else { - return ecs_id_match(id, match_with); + frag->header_buf_ptr[0] = '\0'; } } static -int32_t search_type( - const ecs_world_t *world, - const ecs_table_t *table, - ecs_type_t type, - int32_t offset, - ecs_id_t id, - ecs_entity_t rel, - int32_t min_depth, - int32_t max_depth, - int32_t depth, - ecs_entity_t *subject_out, - ecs_id_t *id_out, - int32_t *count_out) +void enqueue_request( + ecs_http_connection_impl_t *conn) { - ecs_assert(offset >= 0, ECS_INTERNAL_ERROR, NULL); - ecs_assert(depth >= 0, ECS_INTERNAL_ERROR, NULL); + ecs_http_server_t *srv = conn->pub.server; + ecs_http_fragment_t *frag = &conn->frag; - if (!id) { - return -1; - } + if (frag->invalid) { /* invalid request received, don't enqueue */ + ecs_strbuf_reset(&frag->buf); + } else { + char *res = ecs_strbuf_get(&frag->buf); + if (res) { + ecs_os_mutex_lock(srv->lock); + ecs_http_request_impl_t *req = flecs_sparse_add( + srv->requests, ecs_http_request_impl_t); + req->pub.id = flecs_sparse_last_id(srv->requests); + ecs_os_mutex_unlock(srv->lock); - if (!type) { - return -1; - } + req->pub.conn = (ecs_http_connection_t*)conn; + req->pub.method = frag->method; + req->pub.path = res + 1; + if (frag->body_offset) { + req->pub.body = &res[frag->body_offset]; + } + int32_t i, count = frag->header_count; + for (i = 0; i < count; i ++) { + req->pub.headers[i].key = &res[frag->header_offsets[i]]; + req->pub.headers[i].value = &res[frag->header_value_offsets[i]]; + } + count = frag->param_count; + for (i = 0; i < count; i ++) { + req->pub.params[i].key = &res[frag->param_offsets[i]]; + req->pub.params[i].value = &res[frag->param_value_offsets[i]]; + decode_url_str((char*)req->pub.params[i].value); + } - if (max_depth && depth > max_depth) { - return -1; + req->pub.header_count = frag->header_count; + req->pub.param_count = frag->param_count; + req->res = res; + } } +} - int32_t i, count = ecs_vector_count(type); - ecs_id_t *ids = ecs_vector_first(type, ecs_id_t), tid; +static +bool parse_request( + ecs_http_connection_impl_t *conn, + const char* req_frag, + ecs_size_t req_frag_len) +{ + ecs_http_fragment_t *frag = &conn->frag; - if (depth >= min_depth) { - if (table && !offset && !(ECS_HAS_ROLE(id, CASE))) { - ecs_table_record_t *tr = flecs_get_table_record(world, table, id); - if (tr) { - int32_t column = tr->column; - if (count_out) { - *count_out = tr->count; - } - if (id_out) { - *id_out = ids[column]; - } - return column; + int32_t i; + for (i = 0; i < req_frag_len; i++) { + char c = req_frag[i]; + switch (frag->state) { + case HttpFragStateBegin: + ecs_os_memset_t(frag, 0, ecs_http_fragment_t); + frag->buf.max = ECS_HTTP_METHOD_LEN_MAX; + frag->state = HttpFragStateMethod; + frag->header_buf_ptr = frag->header_buf; + /* fallthrough */ + case HttpFragStateMethod: + if (c == ' ') { + parse_method(frag); + frag->state = HttpFragStatePath; + frag->buf.max = ECS_HTTP_REQUEST_LEN_MAX; + } else { + ecs_strbuf_appendch(&frag->buf, c); } - } else { - for (i = offset; i < count; i ++) { - tid = ids[i]; - if (match_id(world, tid, id)) { - if (id_out) { - *id_out = tid; + break; + case HttpFragStatePath: + if (c == ' ') { + frag->state = HttpFragStateVersion; + ecs_strbuf_appendch(&frag->buf, '\0'); + } else { + if (c == '?' || c == '=' || c == '&') { + ecs_strbuf_appendch(&frag->buf, '\0'); + int32_t offset = ecs_strbuf_written(&frag->buf); + if (c == '?' || c == '&') { + frag->param_offsets[frag->param_count] = offset; + } else { + frag->param_value_offsets[frag->param_count] = offset; + frag->param_count ++; } - return i; + } else { + ecs_strbuf_appendch(&frag->buf, c); } } - } - } - - if (rel && id != EcsPrefab && id != EcsDisabled && - ECS_PAIR_RELATION(id) != ecs_id(EcsIdentifier) && - ECS_PAIR_RELATION(id) != EcsChildOf) - { - int32_t ret; - - for (i = 0; i < count; i ++) { - tid = ids[i]; - if (!ECS_HAS_RELATION(tid, rel)) { - continue; - } - - ecs_entity_t obj = ecs_pair_object(world, tid); - ecs_assert(obj != 0, ECS_INTERNAL_ERROR, NULL); - - ecs_table_t *obj_table = ecs_get_table(world, obj); - if (!obj_table) { - continue; + break; + case HttpFragStateVersion: + if (c == '\r') { + frag->state = HttpFragStateCR; + } /* version is not stored */ + break; + case HttpFragStateHeaderStart: + if (header_writable(frag)) { + frag->header_offsets[frag->header_count] = + ecs_strbuf_written(&frag->buf); } + header_buf_reset(frag); + frag->state = HttpFragStateHeaderName; + /* fallthrough */ + case HttpFragStateHeaderName: + if (c == ':') { + frag->state = HttpFragStateHeaderValueStart; + header_buf_append(frag, '\0'); + frag->parse_content_length = !ecs_os_strcmp( + frag->header_buf, "Content-Length"); - if ((ret = search_type(world, obj_table, obj_table->type, 0, id, - rel, min_depth, max_depth, depth + 1, subject_out, id_out, NULL)) != -1) - { - if (subject_out && !*subject_out) { - *subject_out = obj; + if (header_writable(frag)) { + ecs_strbuf_appendch(&frag->buf, '\0'); + frag->header_value_offsets[frag->header_count] = + ecs_strbuf_written(&frag->buf); } - if (id_out && !*id_out) { - *id_out = tid; + } else if (c == '\r') { + frag->state = HttpFragStateCR; + } else { + header_buf_append(frag, c); + if (header_writable(frag)) { + ecs_strbuf_appendch(&frag->buf, c); } - return ret; - - /* If the id could not be found on the object and the relationship - * is not IsA, try substituting the object type with IsA */ - } else if (rel != EcsIsA) { - if ((ret = search_type(world, obj_table, obj_table->type, 0, - id, EcsIsA, 1, 0, 0, subject_out, id_out, NULL)) != -1) - { - if (subject_out && !*subject_out) { - *subject_out = obj; + } + break; + case HttpFragStateHeaderValueStart: + header_buf_reset(frag); + frag->state = HttpFragStateHeaderValue; + if (c == ' ') { /* skip first space */ + break; + } + /* fallthrough */ + case HttpFragStateHeaderValue: + if (c == '\r') { + if (frag->parse_content_length) { + header_buf_append(frag, '\0'); + int32_t len = atoi(frag->header_buf); + if (len < 0) { + frag->invalid = true; + } else { + frag->content_length = len; } - if (id_out && !*id_out) { - *id_out = tid; + frag->parse_content_length = false; + } + if (header_writable(frag)) { + int32_t cur = ecs_strbuf_written(&frag->buf); + if (frag->header_offsets[frag->header_count] < cur && + frag->header_value_offsets[frag->header_count] < cur) + { + ecs_strbuf_appendch(&frag->buf, '\0'); + frag->header_count ++; } - return ret; + } + frag->state = HttpFragStateCR; + } else { + if (frag->parse_content_length) { + header_buf_append(frag, c); + } + if (header_writable(frag)) { + ecs_strbuf_appendch(&frag->buf, c); + } + } + break; + case HttpFragStateCR: + if (c == '\n') { + frag->state = HttpFragStateCRLF; + } else { + frag->state = HttpFragStateHeaderStart; + } + break; + case HttpFragStateCRLF: + if (c == '\r') { + frag->state = HttpFragStateCRLFCR; + } else { + frag->state = HttpFragStateHeaderStart; + i--; + } + break; + case HttpFragStateCRLFCR: + if (c == '\n') { + if (frag->content_length != 0) { + frag->body_offset = ecs_strbuf_written(&frag->buf); + frag->state = HttpFragStateBody; + } else { + frag->state = HttpFragStateDone; + } + } else { + frag->state = HttpFragStateHeaderStart; + } + break; + case HttpFragStateBody: { + ecs_strbuf_appendch(&frag->buf, c); + if ((ecs_strbuf_written(&frag->buf) - frag->body_offset) == + frag->content_length) + { + frag->state = HttpFragStateDone; } } + break; + case HttpFragStateDone: + break; } } - return -1; -} - -bool ecs_type_has_id( - const ecs_world_t *world, - ecs_type_t type, - ecs_id_t id, - bool owned) -{ - ecs_poly_assert(world, ecs_world_t); - - return search_type(world, NULL, type, 0, id, owned ? 0 : EcsIsA, 0, 0, 0, - NULL, NULL, NULL) != -1; -} - -int32_t ecs_type_index_of( - ecs_type_t type, - int32_t offset, - ecs_id_t id) -{ - return search_type(NULL, NULL, type, offset, id, 0, 0, 0, 0, - NULL, NULL, NULL); -} - -int32_t ecs_type_match( - const ecs_world_t *world, - const ecs_table_t *table, - ecs_type_t type, - int32_t offset, - ecs_id_t id, - ecs_entity_t rel, - int32_t min_depth, - int32_t max_depth, - ecs_entity_t *subject_out, - ecs_id_t *id_out, - int32_t *count_out) -{ - ecs_poly_assert(world, ecs_world_t); - - if (subject_out) { - *subject_out = 0; + if (frag->state == HttpFragStateDone) { + frag->state = HttpFragStateBegin; + enqueue_request(conn); + return true; + } else { + return false; } - - return search_type(world, table, type, offset, id, rel, min_depth, - max_depth, 0, subject_out, id_out, count_out); } -char* ecs_type_str( - const ecs_world_t *world, - ecs_type_t type) +static +void append_send_headers( + ecs_strbuf_t *hdrs, + int code, + const char* status, + const char* content_type, + ecs_strbuf_t *extra_headers, + ecs_size_t content_len) { - if (!type) { - return ecs_os_strdup(""); - } + ecs_strbuf_appendstr(hdrs, "HTTP/1.1 "); + ecs_strbuf_append(hdrs, "%d ", code); + ecs_strbuf_appendstr(hdrs, status); + ecs_strbuf_appendstr(hdrs, "\r\n"); - ecs_strbuf_t buf = ECS_STRBUF_INIT; - ecs_entity_t *ids = ecs_vector_first(type, ecs_entity_t); - int32_t i, count = ecs_vector_count(type); + ecs_strbuf_appendstr(hdrs, "Content-Type: "); + ecs_strbuf_appendstr(hdrs, content_type); + ecs_strbuf_appendstr(hdrs, "\r\n"); - for (i = 0; i < count; i ++) { - ecs_entity_t id = ids[i]; + ecs_strbuf_appendstr(hdrs, "Content-Length: "); + ecs_strbuf_append(hdrs, "%d", content_len); + ecs_strbuf_appendstr(hdrs, "\r\n"); - if (i) { - ecs_strbuf_appendch(&buf, ','); - ecs_strbuf_appendch(&buf, ' '); - } + ecs_strbuf_appendstr(hdrs, "Server: flecs\r\n"); - if (id == 1) { - ecs_strbuf_appendstr(&buf, "Component"); - } else { - ecs_id_str_buf(world, id, &buf); - } - } + ecs_strbuf_mergebuff(hdrs, extra_headers); - return ecs_strbuf_get(&buf); + ecs_strbuf_appendstr(hdrs, "\r\n"); } +static +void send_reply( + ecs_http_connection_impl_t* conn, + ecs_http_reply_t* reply) +{ + char hdrs[ECS_HTTP_REPLY_HEADER_SIZE]; + ecs_strbuf_t hdr_buf = ECS_STRBUF_INIT; + hdr_buf.buf = hdrs; + hdr_buf.max = ECS_HTTP_REPLY_HEADER_SIZE; + hdr_buf.buf = hdrs; -void ecs_os_api_impl(ecs_os_api_t *api); - -static bool ecs_os_api_initialized = false; -static int ecs_os_api_init_count = 0; + char *content = ecs_strbuf_get(&reply->body); + int32_t content_length = reply->body.length - 1; -ecs_os_api_t ecs_os_api = { - .log_with_color_ = true, - .log_level_ = -1 /* disable tracing by default, but enable >= warnings */ -}; + /* First, send the response HTTP headers */ + append_send_headers(&hdr_buf, reply->code, reply->status, + reply->content_type, &reply->headers, content_length); -int64_t ecs_os_api_malloc_count = 0; -int64_t ecs_os_api_realloc_count = 0; -int64_t ecs_os_api_calloc_count = 0; -int64_t ecs_os_api_free_count = 0; + ecs_size_t hdrs_len = ecs_strbuf_written(&hdr_buf); + hdrs[hdrs_len] = '\0'; + ecs_size_t written = http_send(conn->sock, hdrs, hdrs_len, 0); -void ecs_os_set_api( - ecs_os_api_t *os_api) -{ - if (!ecs_os_api_initialized) { - ecs_os_api = *os_api; - ecs_os_api_initialized = true; + if (written != hdrs_len) { + ecs_err("failed to write HTTP response headers to '%s:%d': %s", + conn->pub.host, conn->pub.port, ecs_os_strerror(errno)); + return; } -} -void ecs_os_init(void) -{ - if (!ecs_os_api_initialized) { - ecs_os_set_api_defaults(); - } - - if (!(ecs_os_api_init_count ++)) { - if (ecs_os_api.init_) { - ecs_os_api.init_(); + /* Second, send response body */ + if (content_length > 0) { + written = http_send(conn->sock, content, content_length, 0); + if (written != content_length) { + ecs_err("failed to write HTTP response body to '%s:%d': %s", + conn->pub.host, conn->pub.port, ecs_os_strerror(errno)); } } } -void ecs_os_fini(void) { - if (!--ecs_os_api_init_count) { - if (ecs_os_api.fini_) { - ecs_os_api.fini_(); +static +void recv_request( + ecs_http_connection_impl_t *conn) +{ + ecs_size_t bytes_read; + char recv_buf[ECS_HTTP_SEND_RECV_BUFFER_SIZE]; + + while ((bytes_read = http_recv( + conn->sock, recv_buf, ECS_SIZEOF(recv_buf), 0)) > 0) + { + if (parse_request(conn, recv_buf, bytes_read)) { + return; } } } -#if !defined(_MSC_VER) && !defined(__EMSCRIPTEN__) -#include -#define ECS_BT_BUF_SIZE 100 static -void dump_backtrace( - FILE *stream) +void init_connection( + ecs_http_server_t *srv, + ecs_http_socket_t sock_conn, + struct sockaddr_storage *remote_addr, + ecs_size_t remote_addr_len) { - int nptrs; - void *buffer[ECS_BT_BUF_SIZE]; - char **strings; + /* Create new connection */ + ecs_os_mutex_lock(srv->lock); + ecs_http_connection_impl_t *conn = flecs_sparse_add( + srv->connections, ecs_http_connection_impl_t); + conn->pub.id = flecs_sparse_last_id(srv->connections); + ecs_os_mutex_unlock(srv->lock); - nptrs = backtrace(buffer, ECS_BT_BUF_SIZE); + char *remote_host = conn->pub.host; + char *remote_port = conn->pub.port; - strings = backtrace_symbols(buffer, nptrs); - if (strings == NULL) { - return; + /* Fetch name & port info */ + if (http_getnameinfo((struct sockaddr*) remote_addr, remote_addr_len, + remote_host, ECS_SIZEOF(conn->pub.host), + remote_port, ECS_SIZEOF(conn->pub.port), + NI_NUMERICHOST | NI_NUMERICSERV)) + { + ecs_os_strcpy(remote_host, "unknown"); + ecs_os_strcpy(remote_port, "unknown"); } - for (int j = 3; j < nptrs; j++) { - fprintf(stream, "%s\n", strings[j]); - } + ecs_dbg("http: connection established from '%s:%s'", + remote_host, remote_port); - free(strings); -} -#else -static -void dump_backtrace( - FILE *stream) -{ - (void)stream; + conn->pub.server = srv; + conn->sock = sock_conn; + recv_request(conn); + + ecs_dbg("http: request received from '%s:%s'", + remote_host, remote_port); } -#endif static -void log_msg( - int32_t level, - const char *file, - int32_t line, - const char *msg) +int accept_connections( + ecs_http_server_t* srv, + const struct sockaddr* addr, + ecs_size_t addr_len) { - FILE *stream; - if (level >= 0) { - stream = stdout; +#ifdef _MSC_VER + /* If on Windows, test if winsock needs to be initialized */ + SOCKET testsocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + if (SOCKET_ERROR == testsocket && WSANOTINITIALISED == WSAGetLastError()) { + WSADATA data = { 0 }; + int result = WSAStartup(MAKEWORD(2, 2), &data); + if (result) { + ecs_warn("WSAStartup failed with GetLastError = %d\n", + GetLastError()); + return -1; + } } else { - stream = stderr; + http_close(testsocket); } +#endif - if (level >= 0) { - if (ecs_os_api.log_with_color_) fputs(ECS_MAGENTA, stream); - fputs("info", stream); - } else if (level == -2) { - if (ecs_os_api.log_with_color_) fputs(ECS_YELLOW, stream); - fputs("warning", stream); - } else if (level == -3) { - if (ecs_os_api.log_with_color_) fputs(ECS_RED, stream); - fputs("error", stream); - } else if (level == -4) { - if (ecs_os_api.log_with_color_) fputs(ECS_RED, stream); - fputs("fatal", stream); + /* Resolve name + port (used for logging) */ + char addr_host[256]; + char addr_port[20]; + + if (http_getnameinfo( + addr, addr_len, addr_host, ECS_SIZEOF(addr_host), addr_port, + ECS_SIZEOF(addr_port), NI_NUMERICHOST | NI_NUMERICSERV)) + { + ecs_os_strcpy(addr_host, "unknown"); + ecs_os_strcpy(addr_port, "unknown"); } - if (ecs_os_api.log_with_color_) fputs(ECS_NORMAL, stream); - fputs(": ", stream); + srv->sock = socket(addr->sa_family, SOCK_STREAM, IPPROTO_TCP); + if (srv->sock <= 0) { + ecs_err("unable to create new connection socket: %s", + ecs_os_strerror(errno)); + return -1; + } - if (level >= 0) { - if (ecs_os_api.log_indent_) { - char indent[32]; - int i; - for (i = 0; i < ecs_os_api.log_indent_; i ++) { - indent[i * 2] = '|'; - indent[i * 2 + 1] = ' '; - } - indent[i * 2] = '\0'; + int reuse = 1; + int result = setsockopt(srv->sock, SOL_SOCKET, SO_REUSEADDR, + (char*)&reuse, ECS_SIZEOF(reuse)); + if (result) { + ecs_warn("failed to setsockopt: %s", ecs_os_strerror(errno)); + } - fputs(indent, stream); + if (addr->sa_family == AF_INET6) { + int ipv6only = 0; + if (setsockopt(srv->sock, IPPROTO_IPV6, IPV6_V6ONLY, + (char*)&ipv6only, ECS_SIZEOF(ipv6only))) + { + ecs_warn("failed to setsockopt: %s", ecs_os_strerror(errno)); } } + + result = http_bind(srv->sock, addr, addr_len); + if (result) { + ecs_err("http: failed to bind to '%s:%s': %s", + addr_host, addr_port, ecs_os_strerror(errno)); + return -1; + } - if (level < 0) { - if (file) { - const char *file_ptr = strrchr(file, '/'); - if (!file_ptr) { - file_ptr = strrchr(file, '\\'); - } + result = listen(srv->sock, SOMAXCONN); + if (result) { + ecs_warn("http: could not listen for SOMAXCONN (%d) connections: %s", + SOMAXCONN, ecs_os_strerror(errno)); + } - if (file_ptr) { - file = file_ptr + 1; - } + ecs_trace("http: listening for incoming connections on '%s:%s'", + addr_host, addr_port); - fputs(file, stream); - fputs(": ", stream); - } + ecs_http_socket_t sock_conn; + struct sockaddr_storage remote_addr; + ecs_size_t remote_addr_len; - if (line) { - fprintf(stream, "%d: ", line); + while (srv->should_run) { + remote_addr_len = ECS_SIZEOF(remote_addr); + sock_conn = http_accept(srv->sock, (struct sockaddr*) &remote_addr, + &remote_addr_len); + + if (sock_conn == -1) { + ecs_dbg("http: connection attempt failed: %s", + ecs_os_strerror(errno)); + continue; } + + init_connection(srv, sock_conn, &remote_addr, remote_addr_len); } - fputs(msg, stream); + if (srv->sock && errno != EBADF) { + http_close(srv->sock); + } - fputs("\n", stream); + return 0; +} - if (level == -4) { - dump_backtrace(stream); +static +void* http_server_thread(void* arg) { + ecs_http_server_t *srv = arg; + struct sockaddr_in addr = { + .sin_family = AF_INET, + .sin_port = htons(srv->port) }; + + if (!srv->ipaddr) { + addr.sin_addr.s_addr = htonl(INADDR_ANY); + } else { + inet_pton(AF_INET, srv->ipaddr, &(addr.sin_addr)); } + + accept_connections(srv, (struct sockaddr*)&addr, ECS_SIZEOF(addr)); + return NULL; } -void ecs_os_dbg( - const char *file, - int32_t line, - const char *msg) +static +void handle_request( + ecs_http_server_t *srv, + ecs_http_request_impl_t *req) { -#ifndef NDEBUG - if (ecs_os_api.log_) { - ecs_os_api.log_(1, file, line, msg); + ecs_http_reply_t reply = ECS_HTTP_REPLY_INIT; + ecs_http_connection_impl_t *conn = + (ecs_http_connection_impl_t*)req->pub.conn; + + if (srv->callback((ecs_http_request_t*)req, &reply, srv->ctx) == 0) { + reply.code = 404; + reply.status = "Resource not found"; } -#else - (void)file; - (void)line; - (void)msg; -#endif + + send_reply(conn, &reply); + ecs_dbg("http: reply sent to '%s:%s'", conn->pub.host, conn->pub.port); + + reply_free(&reply); + request_free(req); + connection_free(conn); } -void ecs_os_trace( - const char *file, - int32_t line, - const char *msg) +static +void dequeue_requests( + ecs_http_server_t *srv, + float delta_time) { - if (ecs_os_api.log_) { - ecs_os_api.log_(0, file, line, msg); + ecs_os_mutex_lock(srv->lock); + + int32_t i, count = flecs_sparse_count(srv->requests); + for (i = count - 1; i >= 0; i --) { + ecs_http_request_impl_t *req = flecs_sparse_get_dense( + srv->requests, ecs_http_request_impl_t, i); + handle_request(srv, req); + } + + count = flecs_sparse_count(srv->connections); + for (i = count - 1; i >= 0; i --) { + ecs_http_connection_impl_t *conn = flecs_sparse_get_dense( + srv->connections, ecs_http_connection_impl_t, i); + conn->dequeue_timeout += delta_time; + if (conn->dequeue_timeout > + (FLECS_FLOAT)ECS_HTTP_CONNECTION_PURGE_TIMEOUT) + { + ecs_dbg("http: purging connection '%s:%s' (sock = %d)", + conn->pub.host, conn->pub.port, conn->sock); + connection_free(conn); + } } + + ecs_os_mutex_unlock(srv->lock); } -void ecs_os_warn( - const char *file, - int32_t line, - const char *msg) +const char* ecs_http_get_header( + const ecs_http_request_t* req, + const char* name) { - if (ecs_os_api.log_) { - ecs_os_api.log_(-2, file, line, msg); + for (ecs_size_t i = 0; i < req->header_count; i++) { + if (!ecs_os_strcmp(req->headers[i].key, name)) { + return req->headers[i].value; + } } + return NULL; } -void ecs_os_err( - const char *file, - int32_t line, - const char *msg) +const char* ecs_http_get_param( + const ecs_http_request_t* req, + const char* name) { - if (ecs_os_api.log_) { - ecs_os_api.log_(-3, file, line, msg); + for (ecs_size_t i = 0; i < req->param_count; i++) { + if (!ecs_os_strcmp(req->params[i].key, name)) { + return req->params[i].value; + } } + return NULL; } -void ecs_os_fatal( - const char *file, - int32_t line, - const char *msg) +ecs_http_server_t* ecs_http_server_init( + const ecs_http_server_desc_t *desc) { - if (ecs_os_api.log_) { - ecs_os_api.log_(-4, file, line, msg); - } + ecs_check(ecs_os_has_threading(), ECS_UNSUPPORTED, + "missing OS API implementation"); + + ecs_http_server_t* srv = ecs_os_calloc_t(ecs_http_server_t); + srv->lock = ecs_os_mutex_new(); + + srv->should_run = false; + srv->initialized = true; + + srv->callback = desc->callback; + srv->ctx = desc->ctx; + srv->port = desc->port; + srv->ipaddr = desc->ipaddr; + + srv->connections = flecs_sparse_new(ecs_http_connection_impl_t); + srv->requests = flecs_sparse_new(ecs_http_request_impl_t); + +#ifndef _MSC_VER + /* Ignore pipe signal. SIGPIPE can occur when a message is sent to a client + * but te client already disconnected. */ + signal(SIGPIPE, SIG_IGN); +#endif + + return srv; +error: + return NULL; } -static -void ecs_os_gettime(ecs_time_t *time) +void ecs_http_server_fini( + ecs_http_server_t* srv) { - uint64_t now = flecs_os_time_now(); - uint64_t sec = now / 1000000000; + ecs_http_server_stop(srv); + ecs_os_mutex_free(srv->lock); + flecs_sparse_free(srv->connections); + flecs_sparse_free(srv->requests); + ecs_os_free(srv); +} - assert(sec < UINT32_MAX); - assert((now - sec * 1000000000) < UINT32_MAX); +int ecs_http_server_start( + ecs_http_server_t *srv) +{ + ecs_check(srv != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(srv->initialized, ECS_INVALID_PARAMETER, NULL); + ecs_check(!srv->should_run, ECS_INVALID_PARAMETER, NULL); + ecs_check(!srv->thread, ECS_INVALID_PARAMETER, NULL); - time->sec = (uint32_t)sec; - time->nanosec = (uint32_t)(now - sec * 1000000000); + srv->should_run = true; + + srv->thread = ecs_os_thread_new(http_server_thread, srv); + if (!srv->thread) { + goto error; + } + + return 0; +error: + return -1; } -static -void* ecs_os_api_malloc(ecs_size_t size) { - ecs_os_api_malloc_count ++; - ecs_assert(size > 0, ECS_INVALID_PARAMETER, NULL); - return malloc((size_t)size); +void ecs_http_server_stop( + ecs_http_server_t* srv) +{ + ecs_check(srv != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(srv->initialized, ECS_INVALID_OPERATION, NULL); + ecs_check(srv->should_run, ECS_INVALID_PARAMETER, NULL); + + /* Stop server thread */ + ecs_trace("http: shutting down server thread"); + srv->should_run = false; + if (srv->sock >= 0) { + http_close(srv->sock); + } + + ecs_os_thread_join(srv->thread); + + /* Close all connections */ + int i, count = flecs_sparse_count(srv->connections); + for (i = count - 1; i >= 0; i --) { + connection_free(flecs_sparse_get_dense( + srv->connections, ecs_http_connection_impl_t, i)); + } + + /* Cleanup all outstanding requests */ + count = flecs_sparse_count(srv->requests); + for (i = count - 1; i >= 0; i --) { + request_free(flecs_sparse_get_dense( + srv->requests, ecs_http_request_impl_t, i)); + } + + ecs_assert(flecs_sparse_count(srv->connections) == 0, + ECS_INTERNAL_ERROR, NULL); + ecs_assert(flecs_sparse_count(srv->requests) == 0, + ECS_INTERNAL_ERROR, NULL); + + srv->thread = 0; +error: + return; } -static -void* ecs_os_api_calloc(ecs_size_t size) { - ecs_os_api_calloc_count ++; - ecs_assert(size > 0, ECS_INVALID_PARAMETER, NULL); - return calloc(1, (size_t)size); +void ecs_http_server_dequeue( + ecs_http_server_t* srv, + float delta_time) +{ + ecs_check(srv != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(srv->initialized, ECS_INVALID_PARAMETER, NULL); + ecs_check(srv->should_run, ECS_INVALID_PARAMETER, NULL); + + srv->delta_time += delta_time; + if ((1000 * srv->delta_time) > (FLECS_FLOAT)ECS_HTTP_MIN_DEQUEUE_INTERVAL) { + dequeue_requests(srv, srv->delta_time); + srv->delta_time = 0; + } + +error: + return; } -static -void* ecs_os_api_realloc(void *ptr, ecs_size_t size) { - ecs_assert(size > 0, ECS_INVALID_PARAMETER, NULL); +#endif - if (ptr) { - ecs_os_api_realloc_count ++; - } else { - /* If not actually reallocing, treat as malloc */ - ecs_os_api_malloc_count ++; - } - - return realloc(ptr, (size_t)size); -} +#ifdef FLECS_SNAPSHOT -static -void ecs_os_api_free(void *ptr) { - if (ptr) { - ecs_os_api_free_count ++; - } - free(ptr); -} + +/* World snapshot */ +struct ecs_snapshot_t { + ecs_world_t *world; + ecs_sparse_t *entity_index; + ecs_vector_t *tables; + ecs_entity_t last_id; + ecs_filter_t filter; +}; + +/** Small footprint data structure for storing data associated with a table. */ +typedef struct ecs_table_leaf_t { + ecs_table_t *table; + ecs_vector_t *type; + ecs_data_t *data; +} ecs_table_leaf_t; static -char* ecs_os_api_strdup(const char *str) { - if (str) { - int len = ecs_os_strlen(str); - char *result = ecs_os_malloc(len + 1); - ecs_assert(result != NULL, ECS_OUT_OF_MEMORY, NULL); - ecs_os_strcpy(result, str); - return result; - } else { +ecs_data_t* duplicate_data( + const ecs_world_t *world, + ecs_table_t *table, + ecs_data_t *main_data) +{ + if (!ecs_table_count(table)) { return NULL; } -} -/* Replace dots with underscores */ -static -char *module_file_base(const char *module, char sep) { - char *base = ecs_os_strdup(module); - ecs_size_t i, len = ecs_os_strlen(base); - for (i = 0; i < len; i ++) { - if (base[i] == '.') { - base[i] = sep; - } - } + ecs_data_t *result = ecs_os_calloc(ECS_SIZEOF(ecs_data_t)); - return base; -} + ecs_type_t storage_type = table->storage_type; + int32_t i, column_count = ecs_vector_count(storage_type); + ecs_entity_t *components = ecs_vector_first(storage_type, ecs_entity_t); -static -char* ecs_os_api_module_to_dl(const char *module) { - ecs_strbuf_t lib = ECS_STRBUF_INIT; + result->columns = ecs_os_memdup( + main_data->columns, ECS_SIZEOF(ecs_column_t) * column_count); - /* Best guess, use module name with underscores + OS library extension */ - char *file_base = module_file_base(module, '_'); + /* Copy entities */ + result->entities = ecs_vector_copy(main_data->entities, ecs_entity_t); + ecs_entity_t *entities = ecs_vector_first(result->entities, ecs_entity_t); -#if defined(ECS_OS_LINUX) - ecs_strbuf_appendstr(&lib, "lib"); - ecs_strbuf_appendstr(&lib, file_base); - ecs_strbuf_appendstr(&lib, ".so"); -#elif defined(ECS_OS_DARWIN) - ecs_strbuf_appendstr(&lib, "lib"); - ecs_strbuf_appendstr(&lib, file_base); - ecs_strbuf_appendstr(&lib, ".dylib"); -#elif defined(ECS_OS_WINDOWS) - ecs_strbuf_appendstr(&lib, file_base); - ecs_strbuf_appendstr(&lib, ".dll"); -#endif + /* Copy record ptrs */ + result->record_ptrs = ecs_vector_copy(main_data->record_ptrs, ecs_record_t*); - ecs_os_free(file_base); + /* Copy each column */ + for (i = 0; i < column_count; i ++) { + ecs_entity_t component = components[i]; + ecs_column_t *column = &result->columns[i]; - return ecs_strbuf_get(&lib); -} + component = ecs_get_typeid(world, component); -static -char* ecs_os_api_module_to_etc(const char *module) { - ecs_strbuf_t lib = ECS_STRBUF_INIT; + const ecs_type_info_t *cdata = flecs_get_c_info(world, component); + int16_t size = column->size; + int16_t alignment = column->alignment; + ecs_copy_t copy; - /* Best guess, use module name with dashes + /etc */ - char *file_base = module_file_base(module, '-'); + if (cdata && (copy = cdata->lifecycle.copy)) { + int32_t count = ecs_vector_count(column->data); + ecs_vector_t *dst_vec = ecs_vector_new_t(size, alignment, count); + ecs_vector_set_count_t(&dst_vec, size, alignment, count); + void *dst_ptr = ecs_vector_first_t(dst_vec, size, alignment); + void *ctx = cdata->lifecycle.ctx; + + ecs_xtor_t ctor = cdata->lifecycle.ctor; + if (ctor) { + ctor((ecs_world_t*)world, component, entities, dst_ptr, + flecs_itosize(size), count, ctx); + } - ecs_strbuf_appendstr(&lib, file_base); - ecs_strbuf_appendstr(&lib, "/etc"); + void *src_ptr = ecs_vector_first_t(column->data, size, alignment); + copy((ecs_world_t*)world, component, entities, entities, dst_ptr, + src_ptr, flecs_itosize(size), count, ctx); - ecs_os_free(file_base); + column->data = dst_vec; + } else { + column->data = ecs_vector_copy_t(column->data, size, alignment); + } + } - return ecs_strbuf_get(&lib); + return result; } -void ecs_os_set_api_defaults(void) +static +void snapshot_table( + const ecs_world_t *world, + ecs_snapshot_t *snapshot, + ecs_table_t *table) { - /* Don't overwrite if already initialized */ - if (ecs_os_api_initialized != 0) { + if (table->flags & EcsTableHasBuiltins) { return; } - flecs_os_time_setup(); + ecs_table_leaf_t *l = ecs_vector_get( + snapshot->tables, ecs_table_leaf_t, (int32_t)table->id); + ecs_assert(l != NULL, ECS_INTERNAL_ERROR, NULL); - /* Memory management */ - ecs_os_api.malloc_ = ecs_os_api_malloc; - ecs_os_api.free_ = ecs_os_api_free; - ecs_os_api.realloc_ = ecs_os_api_realloc; - ecs_os_api.calloc_ = ecs_os_api_calloc; - - /* Strings */ - ecs_os_api.strdup_ = ecs_os_api_strdup; - - /* Time */ - ecs_os_api.sleep_ = flecs_os_time_sleep; - ecs_os_api.get_time_ = ecs_os_gettime; + l->table = table; + l->type = ecs_vector_copy(table->type, ecs_id_t); + l->data = duplicate_data(world, table, &table->storage); +} - /* Logging */ - ecs_os_api.log_ = log_msg; +static +ecs_snapshot_t* snapshot_create( + const ecs_world_t *world, + const ecs_sparse_t *entity_index, + ecs_iter_t *iter, + ecs_iter_next_action_t next) +{ + ecs_snapshot_t *result = ecs_os_calloc_t(ecs_snapshot_t); + ecs_assert(result != NULL, ECS_OUT_OF_MEMORY, NULL); - /* Modules */ - if (!ecs_os_api.module_to_dl_) { - ecs_os_api.module_to_dl_ = ecs_os_api_module_to_dl; - } + result->world = (ecs_world_t*)world; - if (!ecs_os_api.module_to_etc_) { - ecs_os_api.module_to_etc_ = ecs_os_api_module_to_etc; + /* If no iterator is provided, the snapshot will be taken of the entire + * world, and we can simply copy the entity index as it will be restored + * entirely upon snapshote restore. */ + if (!iter && entity_index) { + result->entity_index = flecs_sparse_copy(entity_index); } - ecs_os_api.abort_ = abort; -} - -bool ecs_os_has_heap(void) { - return - (ecs_os_api.malloc_ != NULL) && - (ecs_os_api.calloc_ != NULL) && - (ecs_os_api.realloc_ != NULL) && - (ecs_os_api.free_ != NULL); -} + /* Create vector with as many elements as tables, so we can store the + * snapshot tables at their element ids. When restoring a snapshot, the code + * will run a diff between the tables in the world and the snapshot, to see + * which of the world tables still exist, no longer exist, or need to be + * deleted. */ + uint64_t t, table_count = flecs_sparse_last_id(world->store.tables) + 1; + result->tables = ecs_vector_new(ecs_table_leaf_t, (int32_t)table_count); + ecs_vector_set_count(&result->tables, ecs_table_leaf_t, (int32_t)table_count); + ecs_table_leaf_t *arr = ecs_vector_first(result->tables, ecs_table_leaf_t); -bool ecs_os_has_threading(void) { - return - (ecs_os_api.mutex_new_ != NULL) && - (ecs_os_api.mutex_free_ != NULL) && - (ecs_os_api.mutex_lock_ != NULL) && - (ecs_os_api.mutex_unlock_ != NULL) && - (ecs_os_api.cond_new_ != NULL) && - (ecs_os_api.cond_free_ != NULL) && - (ecs_os_api.cond_wait_ != NULL) && - (ecs_os_api.cond_signal_ != NULL) && - (ecs_os_api.cond_broadcast_ != NULL) && - (ecs_os_api.thread_new_ != NULL) && - (ecs_os_api.thread_join_ != NULL); -} + /* Array may have holes, so initialize with 0 */ + ecs_os_memset_n(arr, 0, ecs_table_leaf_t, table_count); -bool ecs_os_has_time(void) { - return - (ecs_os_api.get_time_ != NULL) && - (ecs_os_api.sleep_ != NULL); -} + /* Iterate tables in iterator */ + if (iter) { + while (next(iter)) { + ecs_table_t *table = iter->table; + snapshot_table(world, result, table); + } + } else { + for (t = 0; t < table_count; t ++) { + ecs_table_t *table = flecs_sparse_get( + world->store.tables, ecs_table_t, t); + snapshot_table(world, result, table); + } + } -bool ecs_os_has_logging(void) { - return (ecs_os_api.log_ != NULL); + return result; } -bool ecs_os_has_dl(void) { - return - (ecs_os_api.dlopen_ != NULL) && - (ecs_os_api.dlproc_ != NULL) && - (ecs_os_api.dlclose_ != NULL); -} +/** Create a snapshot */ +ecs_snapshot_t* ecs_snapshot_take( + ecs_world_t *stage) +{ + const ecs_world_t *world = ecs_get_world(stage); -bool ecs_os_has_modules(void) { - return - (ecs_os_api.module_to_dl_ != NULL) && - (ecs_os_api.module_to_etc_ != NULL); -} + ecs_snapshot_t *result = snapshot_create( + world, + world->store.entity_index, + NULL, + NULL); -#if defined(_MSC_VER) -static char error_str[255]; -#endif + result->last_id = world->stats.last_id; -const char* ecs_os_strerror(int err) { -#if defined(_MSC_VER) - strerror_s(error_str, 255, err); - return error_str; -#else - return strerror(err); -#endif + return result; } - -#ifdef FLECS_SYSTEM -#endif - -static -void compute_group_id( - ecs_query_t *query, - ecs_query_table_match_t *match) +/** Create a filtered snapshot */ +ecs_snapshot_t* ecs_snapshot_take_w_iter( + ecs_iter_t *iter) { - ecs_assert(match != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_world_t *world = iter->world; + ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); - if (query->group_by) { - ecs_table_t *table = match->table; - ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_snapshot_t *result = snapshot_create( + world, + world->store.entity_index, + iter, + iter ? iter->next : NULL); - match->group_id = query->group_by(query->world, table->type, - query->group_by_id, query->group_by_ctx); - } else { - match->group_id = 0; - } -} + result->last_id = world->stats.last_id; -static -ecs_query_table_list_t* get_group( - ecs_query_t *query, - uint64_t group_id) -{ - return ecs_map_get(query->groups, ecs_query_table_list_t, group_id); + return result; } +/* Restoring an unfiltered snapshot restores the world to the exact state it was + * when the snapshot was taken. */ static -ecs_query_table_list_t* ensure_group( - ecs_query_t *query, - uint64_t group_id) +void restore_unfiltered( + ecs_world_t *world, + ecs_snapshot_t *snapshot) { - return ecs_map_ensure(query->groups, ecs_query_table_list_t, group_id); -} + flecs_sparse_restore(world->store.entity_index, snapshot->entity_index); + flecs_sparse_free(snapshot->entity_index); + + world->stats.last_id = snapshot->last_id; -/* Find the last node of the group after which this group should be inserted */ -static -ecs_query_table_node_t* find_group_insertion_node( - ecs_query_t *query, - uint64_t group_id) -{ - /* Grouping must be enabled */ - ecs_assert(query->group_by != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_table_leaf_t *leafs = ecs_vector_first( + snapshot->tables, ecs_table_leaf_t); + int32_t i, count = (int32_t)flecs_sparse_last_id(world->store.tables); + int32_t snapshot_count = ecs_vector_count(snapshot->tables); - ecs_map_iter_t it = ecs_map_iter(query->groups); - ecs_query_table_list_t *list, *closest_list = NULL; - uint64_t id, closest_id = 0; + for (i = 0; i <= count; i ++) { + ecs_table_t *world_table = flecs_sparse_get( + world->store.tables, ecs_table_t, (uint32_t)i); - /* Find closest smaller group id */ - while ((list = ecs_map_next(&it, ecs_query_table_list_t, &id))) { - if (id >= group_id) { + if (world_table && (world_table->flags & EcsTableHasBuiltins)) { continue; } - if (!list->last) { - ecs_assert(list->first == NULL, ECS_INTERNAL_ERROR, NULL); - continue; + ecs_table_leaf_t *snapshot_table = NULL; + if (i < snapshot_count) { + snapshot_table = &leafs[i]; + if (!snapshot_table->table) { + snapshot_table = NULL; + } } - if (!closest_list || ((group_id - id) < (group_id - closest_id))) { - closest_id = id; - closest_list = list; + /* If the world table no longer exists but the snapshot table does, + * reinsert it */ + if (!world_table && snapshot_table) { + ecs_ids_t type = { + .array = ecs_vector_first(snapshot_table->type, ecs_id_t), + .count = ecs_vector_count(snapshot_table->type) + }; + + ecs_table_t *table = flecs_table_find_or_create(world, &type); + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + + if (snapshot_table->data) { + flecs_table_replace_data(world, table, snapshot_table->data); + } + + /* If the world table still exists, replace its data */ + } else if (world_table && snapshot_table) { + if (snapshot_table->data) { + flecs_table_replace_data( + world, world_table, snapshot_table->data); + } else { + flecs_table_clear_data( + world, world_table, &world_table->storage); + flecs_table_init_data(world, world_table); + } + + /* If the snapshot table doesn't exist, this table was created after the + * snapshot was taken and needs to be deleted */ + } else if (world_table && !snapshot_table) { + /* Deleting a table invokes OnRemove triggers & updates the entity + * index. That is not what we want, since entities may no longer be + * valid (if they don't exist in the snapshot) or may have been + * restored in a different table. Therefore first clear the data + * from the table (which doesn't invoke triggers), and then delete + * the table. */ + flecs_table_clear_data(world, world_table, &world_table->storage); + flecs_delete_table(world, world_table); + + /* If there is no world & snapshot table, nothing needs to be done */ + } else { } + + if (snapshot_table) { + ecs_os_free(snapshot_table->data); + ecs_os_free(snapshot_table->type); } } - if (closest_list) { - return closest_list->last; - } else { - return NULL; /* Group should be first in query */ + /* Now that all tables have been restored and world is in a consistent + * state, run OnSet systems */ + int32_t world_count = flecs_sparse_count(world->store.tables); + for (i = 0; i < world_count; i ++) { + ecs_table_t *table = flecs_sparse_get_dense( + world->store.tables, ecs_table_t, i); + if (table->flags & EcsTableHasBuiltins) { + continue; + } + + int32_t tcount = ecs_table_count(table); + if (tcount) { + flecs_notify_on_set(world, table, 0, tcount, NULL, true); + } } } -/* Initialize group with first node */ +/* Restoring a filtered snapshots only restores the entities in the snapshot + * to their previous state. */ static -void create_group( - ecs_query_t *query, - ecs_query_table_node_t *node) +void restore_filtered( + ecs_world_t *world, + ecs_snapshot_t *snapshot) { - ecs_query_table_match_t *match = node->match; - uint64_t group_id = match->group_id; + ecs_table_leaf_t *leafs = ecs_vector_first( + snapshot->tables, ecs_table_leaf_t); + int32_t l = 0, snapshot_count = ecs_vector_count(snapshot->tables); - /* If query has grouping enabled & this is a new/empty group, find - * the insertion point for the group */ - ecs_query_table_node_t *insert_after = find_group_insertion_node( - query, group_id); + for (l = 0; l < snapshot_count; l ++) { + ecs_table_leaf_t *snapshot_table = &leafs[l]; + ecs_table_t *table = snapshot_table->table; - if (!insert_after) { - /* This group should appear first in the query list */ - ecs_query_table_node_t *query_first = query->list.first; - if (query_first) { - /* If this is not the first match for the query, insert before it */ - node->next = query_first; - query_first->prev = node; - query->list.first = node; - } else { - /* If this is the first match of the query, initialize its list */ - ecs_assert(query->list.last == NULL, ECS_INTERNAL_ERROR, NULL); - query->list.first = node; - query->list.last = node; + if (!table) { + continue; } - } else { - ecs_assert(query->list.first != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(query->list.last != NULL, ECS_INTERNAL_ERROR, NULL); - /* This group should appear after another group */ - ecs_query_table_node_t *insert_before = insert_after->next; - node->prev = insert_after; - insert_after->next = node; - node->next = insert_before; - if (insert_before) { - insert_before->prev = node; - } else { - ecs_assert(query->list.last == insert_after, - ECS_INTERNAL_ERROR, NULL); - - /* This group should appear last in the query list */ - query->list.last = node; + ecs_data_t *data = snapshot_table->data; + if (!data) { + ecs_vector_free(snapshot_table->type); + continue; + } + + /* Delete entity from storage first, so that when we restore it to the + * current table we can be sure that there won't be any duplicates */ + int32_t i, entity_count = ecs_vector_count(data->entities); + ecs_entity_t *entities = ecs_vector_first( + snapshot_table->data->entities, ecs_entity_t); + for (i = 0; i < entity_count; i ++) { + ecs_entity_t e = entities[i]; + ecs_record_t *r = ecs_eis_get(world, e); + if (r && r->table) { + flecs_table_delete(world, r->table, &r->table->storage, + ECS_RECORD_TO_ROW(r->row), true); + } else { + /* Make sure that the entity has the same generation count */ + ecs_eis_set_generation(world, e); + } + } + + /* Merge data from snapshot table with world table */ + int32_t old_count = ecs_table_count(snapshot_table->table); + int32_t new_count = flecs_table_data_count(snapshot_table->data); + + flecs_table_merge(world, table, table, &table->storage, snapshot_table->data); + + /* Run OnSet systems for merged entities */ + if (new_count) { + flecs_notify_on_set( + world, table, old_count, new_count, NULL, true); } - } -} -static -void remove_group( - ecs_query_t *query, - uint64_t group_id) -{ - ecs_map_remove(query->groups, group_id); + ecs_os_free(snapshot_table->data->columns); + ecs_os_free(snapshot_table->data); + ecs_vector_free(snapshot_table->type); + } } -/* Find the list the node should be part of */ -static -ecs_query_table_list_t* get_node_list( - ecs_query_t *query, - ecs_query_table_node_t *node) +/** Restore a snapshot */ +void ecs_snapshot_restore( + ecs_world_t *world, + ecs_snapshot_t *snapshot) { - ecs_query_table_match_t *match = node->match; - if (query->group_by) { - return get_group(query, match->group_id); + if (snapshot->entity_index) { + /* Unfiltered snapshots have a copy of the entity index which is + * copied back entirely when the snapshot is restored */ + restore_unfiltered(world, snapshot); } else { - return &query->list; + restore_filtered(world, snapshot); } + + ecs_vector_free(snapshot->tables); + + ecs_os_free(snapshot); } -/* Find or create the list the node should be part of */ -static -ecs_query_table_list_t* ensure_node_list( - ecs_query_t *query, - ecs_query_table_node_t *node) +ecs_iter_t ecs_snapshot_iter( + ecs_snapshot_t *snapshot) { - ecs_query_table_match_t *match = node->match; - if (query->group_by) { - return ensure_group(query, match->group_id); - } else { - return &query->list; - } + ecs_snapshot_iter_t iter = { + .tables = snapshot->tables, + .index = 0 + }; + + return (ecs_iter_t){ + .world = snapshot->world, + .table_count = ecs_vector_count(snapshot->tables), + .iter.snapshot = iter, + .next = ecs_snapshot_next + }; } -/* Remove node from list */ -static -void remove_table_node( - ecs_query_t *query, - ecs_query_table_node_t *node) +bool ecs_snapshot_next( + ecs_iter_t *it) { - ecs_query_table_node_t *prev = node->prev; - ecs_query_table_node_t *next = node->next; - - ecs_assert(prev != node, ECS_INTERNAL_ERROR, NULL); - ecs_assert(next != node, ECS_INTERNAL_ERROR, NULL); - ecs_assert(!prev || prev != next, ECS_INTERNAL_ERROR, NULL); + ecs_snapshot_iter_t *iter = &it->iter.snapshot; + ecs_table_leaf_t *tables = ecs_vector_first(iter->tables, ecs_table_leaf_t); + int32_t count = ecs_vector_count(iter->tables); + int32_t i; - ecs_query_table_list_t *list = get_node_list(query, node); + for (i = iter->index; i < count; i ++) { + ecs_table_t *table = tables[i].table; + if (!table) { + continue; + } - if (!list || !list->first) { - /* If list contains no nodes, the node must be empty */ - ecs_assert(!list || list->last == NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(prev == NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(next == NULL, ECS_INTERNAL_ERROR, NULL); - return; - } + ecs_data_t *data = tables[i].data; - ecs_assert(prev != NULL || query->list.first == node, - ECS_INTERNAL_ERROR, NULL); - ecs_assert(next != NULL || query->list.last == node, - ECS_INTERNAL_ERROR, NULL); + it->table = table; + it->count = ecs_table_count(table); + if (data) { + it->entities = ecs_vector_first(data->entities, ecs_entity_t); + } else { + it->entities = NULL; + } - if (prev) { - prev->next = next; - } - if (next) { - next->prev = prev; + it->is_valid = true; + iter->index = i + 1; + + goto yield; } - ecs_assert(list->count > 0, ECS_INTERNAL_ERROR, NULL); - list->count --; - - if (query->group_by) { - ecs_query_table_match_t *match = node->match; - uint64_t group_id = match->group_id; - - /* Make sure query.list is updated if this is the first or last group */ - if (query->list.first == node) { - ecs_assert(prev == NULL, ECS_INTERNAL_ERROR, NULL); - query->list.first = next; - prev = next; - } - if (query->list.last == node) { - ecs_assert(next == NULL, ECS_INTERNAL_ERROR, NULL); - query->list.last = prev; - next = prev; - } - - ecs_assert(query->list.count > 0, ECS_INTERNAL_ERROR, NULL); - query->list.count --; + it->is_valid = false; + return false; - /* Make sure group list only contains nodes that belong to the group */ - if (prev && prev->match->group_id != group_id) { - /* The previous node belonged to another group */ - prev = next; - } - if (next && next->match->group_id != group_id) { - /* The next node belonged to another group */ - next = prev; - } +yield: + it->is_valid = true; + return true; +} - /* Do check again, in case both prev & next belonged to another group */ - if (prev && prev->match->group_id != group_id) { - /* There are no more matches left in this group */ - remove_group(query, group_id); - list = NULL; - } - } +/** Cleanup snapshot */ +void ecs_snapshot_free( + ecs_snapshot_t *snapshot) +{ + flecs_sparse_free(snapshot->entity_index); - if (list) { - if (list->first == node) { - list->first = next; - } - if (list->last == node) { - list->last = prev; + ecs_table_leaf_t *tables = ecs_vector_first(snapshot->tables, ecs_table_leaf_t); + int32_t i, count = ecs_vector_count(snapshot->tables); + for (i = 0; i < count; i ++) { + ecs_table_leaf_t *snapshot_table = &tables[i]; + ecs_table_t *table = snapshot_table->table; + if (table) { + ecs_data_t *data = snapshot_table->data; + if (data) { + flecs_table_clear_data(snapshot->world, table, data); + ecs_os_free(data); + } + ecs_vector_free(snapshot_table->type); } - } + } - node->prev = NULL; - node->next = NULL; + ecs_vector_free(snapshot->tables); + ecs_os_free(snapshot); +} -#ifdef FLECS_SYSTEM - if (query->list.first == NULL && query->system && !query->world->is_fini) { - ecs_system_activate(query->world, query->system, false, NULL); - } #endif - query->match_count ++; -} -/* Add node to list */ -static -void insert_table_node( - ecs_query_t *query, - ecs_query_table_node_t *node) -{ - /* Node should not be part of an existing list */ - ecs_assert(node->prev == NULL && node->next == NULL, - ECS_INTERNAL_ERROR, NULL); +#ifdef FLECS_DOC - /* If this is the first match, activate system */ -#ifdef FLECS_SYSTEM - if (!query->list.first && query->system) { - ecs_system_activate(query->world, query->system, true, NULL); - } -#endif +static ECS_COPY(EcsDocDescription, dst, src, { + ecs_os_strset((char**)&dst->value, src->value); - compute_group_id(query, node->match); +}) - ecs_query_table_list_t *list = ensure_node_list(query, node); - if (list->last) { - ecs_assert(query->list.first != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(query->list.last != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(list->first != NULL, ECS_INTERNAL_ERROR, NULL); +static ECS_MOVE(EcsDocDescription, dst, src, { + ecs_os_free((char*)dst->value); + dst->value = src->value; + src->value = NULL; +}) - ecs_query_table_node_t *last = list->last; - ecs_query_table_node_t *last_next = last->next; +static ECS_DTOR(EcsDocDescription, ptr, { + ecs_os_free((char*)ptr->value); +}) - node->prev = last; - node->next = last_next; - last->next = node; +void ecs_doc_set_brief( + ecs_world_t *world, + ecs_entity_t entity, + const char *description) +{ + ecs_set_pair(world, entity, EcsDocDescription, EcsDocBrief, { + .value = description + }); +} - if (last_next) { - last_next->prev = node; - } +void ecs_doc_set_detail( + ecs_world_t *world, + ecs_entity_t entity, + const char *description) +{ + ecs_set_pair(world, entity, EcsDocDescription, EcsDocDetail, { + .value = description + }); +} - list->last = node; +void ecs_doc_set_link( + ecs_world_t *world, + ecs_entity_t entity, + const char *link) +{ + ecs_set_pair(world, entity, EcsDocDescription, EcsDocLink, { + .value = link + }); +} - if (query->group_by) { - /* Make sure to update query list if this is the last group */ - if (query->list.last == last) { - query->list.last = node; - } - } +const char* ecs_doc_get_brief( + const ecs_world_t *world, + ecs_entity_t entity) +{ + EcsDocDescription *ptr = ecs_get_pair( + world, entity, EcsDocDescription, EcsDocBrief); + if (ptr) { + return ptr->value; } else { - ecs_assert(list->first == NULL, ECS_INTERNAL_ERROR, NULL); - - list->first = node; - list->last = node; + return NULL; + } +} - if (query->group_by) { - /* Initialize group with its first node */ - create_group(query, node); - } +const char* ecs_doc_get_detail( + const ecs_world_t *world, + ecs_entity_t entity) +{ + EcsDocDescription *ptr = ecs_get_pair( + world, entity, EcsDocDescription, EcsDocDetail); + if (ptr) { + return ptr->value; + } else { + return NULL; } +} - if (query->group_by) { - query->list.count ++; +const char* ecs_doc_get_link( + const ecs_world_t *world, + ecs_entity_t entity) +{ + EcsDocDescription *ptr = ecs_get_pair( + world, entity, EcsDocDescription, EcsDocLink); + if (ptr) { + return ptr->value; + } else { + return NULL; } +} - list->count ++; - query->match_count ++; +void FlecsDocImport( + ecs_world_t *world) +{ + ECS_MODULE(world, FlecsDoc); - ecs_assert(node->prev != node, ECS_INTERNAL_ERROR, NULL); - ecs_assert(node->next != node, ECS_INTERNAL_ERROR, NULL); + ecs_set_name_prefix(world, "EcsDoc"); - ecs_assert(list->first != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(list->last != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(list->last == node, ECS_INTERNAL_ERROR, NULL); - ecs_assert(query->list.first != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(query->list.last != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(query->list.first->prev == NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(query->list.last->next == NULL, ECS_INTERNAL_ERROR, NULL); + flecs_bootstrap_component(world, EcsDocDescription); + flecs_bootstrap_tag(world, EcsDocBrief); + flecs_bootstrap_tag(world, EcsDocDetail); + flecs_bootstrap_tag(world, EcsDocLink); + + ecs_set_component_actions(world, EcsDocDescription, { + .ctor = ecs_default_ctor, + .move = ecs_move(EcsDocDescription), + .copy = ecs_copy(EcsDocDescription), + .dtor = ecs_dtor(EcsDocDescription) + }); } -static -ecs_query_table_match_t* cache_add( - ecs_query_table_t *elem) -{ - ecs_query_table_match_t *result = ecs_os_calloc_t(ecs_query_table_match_t); - ecs_query_table_node_t *node = &result->node; +#endif - node->match = result; - if (!elem->first) { - elem->first = result; - elem->last = result; - } else { - ecs_assert(elem->last != NULL, ECS_INTERNAL_ERROR, NULL); - elem->last->next_match = result; - elem->last = result; - } +#ifdef FLECS_PLECS - return result; -} -/* Builtin group_by callback for Cascade terms. - * This function traces the hierarchy depth of an entity type by following a - * relation upwards (to its 'parents') for as long as those parents have the - * specified component id. - * The result of the function is the number of parents with the provided - * component for a given relation. */ -static -uint64_t group_by_cascade( - ecs_world_t *world, - ecs_type_t type, - ecs_entity_t component, - void *ctx) -{ - uint64_t result = 0; - int32_t i, count = ecs_vector_count(type); - ecs_entity_t *array = ecs_vector_first(type, ecs_entity_t); - ecs_term_t *term = ctx; - ecs_entity_t relation = term->subj.set.relation; +#define TOK_NEWLINE '\n' +#define TOK_WITH "with" +#define TOK_USING "using" - /* Cascade needs a relation to calculate depth from */ - ecs_check(relation != 0, ECS_INVALID_PARAMETER, NULL); +#define STACK_MAX_SIZE (64) - /* Should only be used with cascade terms */ - ecs_check(term->subj.set.mask & EcsCascade, ECS_INVALID_PARAMETER, NULL); +typedef struct { + const char *name; + const char *code; - /* Iterate back to front as relations are more likely to occur near the - * end of a type. */ - for (i = count - 1; i >= 0; i --) { - /* Find relation & relation object in entity type */ - if (ECS_HAS_RELATION(array[i], relation)) { - ecs_type_t obj_type = ecs_get_type(world, - ecs_pair_object(world, array[i])); - int32_t j, c_count = ecs_vector_count(obj_type); - ecs_entity_t *c_array = ecs_vector_first(obj_type, ecs_entity_t); + ecs_entity_t last_predicate; + ecs_entity_t last_subject; + ecs_entity_t last_object; - /* Iterate object type, check if it has the specified component */ - for (j = 0; j < c_count; j ++) { - /* If it has the component, it is part of the tree matched by - * the query, increase depth */ - if (c_array[j] == component) { - result ++; + ecs_id_t last_assign_id; + ecs_entity_t assign_to; - /* Recurse to test if the object has matching parents */ - result += group_by_cascade(world, obj_type, component, ctx); - break; - } - } + ecs_entity_t scope[STACK_MAX_SIZE]; + ecs_entity_t default_scope_type[STACK_MAX_SIZE]; + ecs_entity_t with[STACK_MAX_SIZE]; + ecs_entity_t using[STACK_MAX_SIZE]; + int32_t with_frames[STACK_MAX_SIZE]; + int32_t using_frames[STACK_MAX_SIZE]; + int32_t sp; + int32_t with_frame; + int32_t using_frame; - if (j != c_count) { - break; - } + char *comment; - /* If the id doesn't have a role set, we'll find no more relations */ - } else if (!(array[i] & ECS_ROLE_MASK)) { - break; - } - } + bool with_stmt; + bool scope_assign_stmt; + bool using_stmt; + bool assign_stmt; + bool isa_stmt; - return result; -error: - return 0; -} + int32_t errors; +} plecs_state_t; static -int get_comp_and_src( - ecs_world_t *world, - ecs_query_t *query, - int32_t t, - ecs_table_t *table_arg, - ecs_entity_t *component_out, - ecs_entity_t *entity_out, - bool *match_out) +ecs_entity_t plecs_lookup( + const ecs_world_t *world, + const char *path, + plecs_state_t *state, + bool is_subject) { - ecs_entity_t component = 0, entity = 0; - - ecs_term_t *terms = query->filter.terms; - int32_t term_count = query->filter.term_count; - ecs_term_t *term = &terms[t]; - ecs_term_id_t *subj = &term->subj; - ecs_oper_kind_t op = term->oper; - - *match_out = true; + ecs_entity_t e = 0; - if (op == EcsNot) { - entity = subj->entity; + if (!is_subject) { + int using_scope = state->using_frame - 1; + for (; using_scope >= 0; using_scope--) { + e = ecs_lookup_path_w_sep( + world, state->using[using_scope], path, NULL, NULL, false); + if (e) { + break; + } + } } - if (!subj->entity) { - component = term->id; - } else { - ecs_table_t *table = table_arg; - if (subj->entity != EcsThis) { - table = ecs_get_table(world, subj->entity); - } + if (!e) { + e = ecs_lookup_path_w_sep(world, 0, path, NULL, NULL, !is_subject); + } - ecs_type_t type = NULL; - if (table) { - type = table->type; - } + return e; +} - if (op == EcsOr) { - for (; t < term_count; t ++) { - term = &terms[t]; +/* Lookup action used for deserializing entity refs in component values */ +#ifdef FLECS_EXPR +static +ecs_entity_t plecs_lookup_action( + const ecs_world_t *world, + const char *path, + void *ctx) +{ + return plecs_lookup(world, path, ctx, false); +} +#endif - /* Keep iterating until the next non-OR expression */ - if (term->oper != EcsOr) { - t --; - break; - } +static +void clear_comment( + const char *expr, + const char *ptr, + plecs_state_t *state) +{ + if (state->comment) { + ecs_parser_error(state->name, expr, ptr - expr, "unused doc comment"); + ecs_os_free(state->comment); + state->comment = NULL; - if (!component) { - ecs_entity_t source = 0; - int32_t result = ecs_type_match(world, table, type, - 0, term->id, subj->set.relation, subj->set.min_depth, - subj->set.max_depth, &source, NULL, NULL); + state->errors ++; /* Non-fatal error */ + } +} - if (result != -1) { - component = term->id; - } +static +const char* parse_fluff( + const char *expr, + const char *ptr, + plecs_state_t *state) +{ + char *comment; + const char *next = ecs_parse_fluff(ptr, &comment); - if (source) { - entity = source; - } + if (comment && comment[0] == '/') { + comment = (char*)ecs_parse_fluff(comment + 1, NULL); + int32_t len = (ecs_size_t)(next - comment); + int32_t newline_count = 0; + + /* Trim trailing whitespaces */ + while (len >= 0 && (isspace(comment[len - 1]))) { + if (comment[len - 1] == '\n') { + newline_count ++; + if (newline_count > 1) { + /* If newline separates comment from statement, discard */ + len = -1; + break; } } - } else { - component = term->id; + len --; + } - ecs_entity_t source = 0; - bool result = ecs_type_match(world, table, type, 0, component, - subj->set.relation, subj->set.min_depth, subj->set.max_depth, - &source, NULL, NULL) != -1; + if (len > 0) { + clear_comment(expr, ptr, state); + state->comment = ecs_os_calloc_n(char, len + 1); + ecs_os_strncpy(state->comment, comment, len); + } else { + ecs_parser_error(state->name, expr, ptr - expr, + "unused doc comment"); + state->errors ++; + } + } else { + if (ptr != next && state->comment) { + clear_comment(expr, ptr, state); + } + } - *match_out = result; + return next; +} - if (op == EcsNot) { - result = !result; - } +static +ecs_entity_t ensure_entity( + ecs_world_t *world, + plecs_state_t *state, + const char *path, + bool is_subject) +{ + if (!path) { + return 0; + } - /* Optional terms may not have the component. *From terms contain - * the id of a type of which the contents must match, but the type - * itself does not need to match. */ - if (op == EcsOptional || op == EcsAndFrom || op == EcsOrFrom || - op == EcsNotFrom) - { - result = true; - } + ecs_entity_t e = plecs_lookup(world, path, state, is_subject); + if (!e) { + if (!is_subject) { + /* If this is not a subject create an existing empty id, which + * ensures that scope & with are not applied */ + e = ecs_new_id(world); + } - /* Table has already been matched, so unless column is optional - * any components matched from the table must be available. */ - if (table == table_arg) { - ecs_assert(result == true, ECS_INTERNAL_ERROR, NULL); + e = ecs_add_path(world, e, 0, path); + ecs_assert(e != 0, ECS_INTERNAL_ERROR, NULL); + } else { + /* If entity exists, make sure it gets the right scope and with */ + if (is_subject) { + ecs_entity_t scope = ecs_get_scope(world); + if (scope) { + ecs_add_pair(world, e, EcsChildOf, scope); } - if (source) { - entity = source; + ecs_entity_t with = ecs_get_with(world); + if (with) { + ecs_add_id(world, e, with); } } - - if (subj->entity != EcsThis) { - entity = subj->entity; - } } - if (entity == EcsThis) { - entity = 0; - } + return e; +} - *component_out = component; - *entity_out = entity; +static +bool pred_is_subj( + ecs_term_t *term, + plecs_state_t *state) +{ + if (term->subj.name != NULL) { + return false; + } + if (term->obj.name != NULL) { + return false; + } + if (term->subj.set.mask == EcsNothing) { + return false; + } + if (state->with_stmt) { + return false; + } + if (state->assign_stmt) { + return false; + } + if (state->isa_stmt) { + return false; + } + if (state->using_stmt) { + return false; + } - return t; + return true; } -typedef struct pair_offset_t { - int32_t index; - int32_t count; -} pair_offset_t; - -/* Get index for specified pair. Take into account that a pair can be matched - * multiple times per table, by keeping an offset of the last found index */ static -int32_t get_pair_index( - ecs_type_t table_type, - ecs_id_t pair, - int32_t column_index, - pair_offset_t *pair_offsets, - int32_t count) +int create_term( + ecs_world_t *world, + ecs_term_t *term, + const char *name, + const char *expr, + int64_t column, + plecs_state_t *state) { - int32_t result; + state->last_subject = 0; + state->last_predicate = 0; + state->last_object = 0; + state->last_assign_id = 0; - /* The count variable keeps track of the number of times a pair has been - * matched with the current table. Compare the count to check if the index - * was already resolved for this iteration */ - if (pair_offsets[column_index].count == count) { - /* If it was resolved, return the last stored index. Subtract one as the - * index is offset by one, to ensure we're not getting stuck on the same - * index. */ - result = pair_offsets[column_index].index - 1; - } else { - /* First time for this iteration that the pair index is resolved, look - * it up in the type. */ - result = ecs_type_index_of(table_type, - pair_offsets[column_index].index, pair); - pair_offsets[column_index].index = result + 1; - pair_offsets[column_index].count = count; + if (!ecs_term_id_is_set(&term->pred)) { + ecs_parser_error(name, expr, column, "missing predicate in expression"); + return -1; } - return result; -} + if (state->assign_stmt && term->subj.entity != EcsThis) { + ecs_parser_error(name, expr, column, + "invalid statement in assign statement"); + return -1; + } -static -int32_t get_component_index( - ecs_world_t *world, - ecs_type_t table_type, - ecs_entity_t *component_out, - int32_t column_index, - ecs_oper_kind_t op, - pair_offset_t *pair_offsets, - int32_t count) -{ - int32_t result = 0; - ecs_entity_t component = *component_out; + bool pred_as_subj = pred_is_subj(term, state); - ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_entity_t pred = ensure_entity(world, state, term->pred.name, pred_as_subj); + ecs_entity_t subj = ensure_entity(world, state, term->subj.name, true); + ecs_entity_t obj = 0; - if (component) { - /* If requested component is a case, find the corresponding switch to - * lookup in the table */ - if (ECS_HAS_ROLE(component, CASE)) { - ecs_entity_t sw = ECS_PAIR_RELATION(component); - result = ecs_type_index_of(table_type, 0, ECS_SWITCH | sw); - ecs_assert(result != -1, ECS_INTERNAL_ERROR, NULL); - } else - if (ECS_HAS_ROLE(component, PAIR)) { - ecs_entity_t rel = ECS_PAIR_RELATION(component); - ecs_entity_t obj = ECS_PAIR_OBJECT(component); + if (ecs_term_id_is_set(&term->obj)) { + obj = ensure_entity(world, state, term->obj.name, + state->assign_stmt == false); + } - /* Both the relationship and the object of the pair must be set */ - ecs_assert(rel != 0, ECS_INVALID_PARAMETER, NULL); - ecs_assert(obj != 0, ECS_INVALID_PARAMETER, NULL); + if (state->assign_stmt || state->isa_stmt) { + subj = state->assign_to; + } - if (rel == EcsWildcard || obj == EcsWildcard) { - ecs_assert(pair_offsets != NULL, ECS_INTERNAL_ERROR, NULL); + if (state->isa_stmt && obj) { + ecs_parser_error(name, expr, column, + "invalid object in inheritance statement"); + return -1; + } - /* Get index of pair. Start looking from the last pair index - * as this may not be the first instance of the pair. */ - result = get_pair_index( - table_type, component, column_index, pair_offsets, count); - - if (result != -1) { - /* If component of current column is a pair, get the actual - * pair type for the table, so the system can see which - * component the pair was applied to */ - ecs_entity_t *pair = ecs_vector_get( - table_type, ecs_entity_t, result); - *component_out = *pair; + if (state->using_stmt && (obj || subj)) { + ecs_parser_error(name, expr, column, + "invalid predicate/object in using statement"); + return -1; + } - /* Check if the pair is a tag or whether it has data */ - if (ecs_get(world, rel, EcsComponent) == NULL) { - /* If pair has no data associated with it, use the - * component to which the pair has been added */ - component = ECS_PAIR_OBJECT(*pair); - } else { - component = rel; - } - } - } else { + if (state->isa_stmt) { + pred = ecs_pair(EcsIsA, pred); + } - /* If the low part is a regular entity (component), then - * this query exactly matches a single pair instance. In - * this case we can simply do a lookup of the pair - * identifier in the table type. */ - result = ecs_type_index_of(table_type, 0, component); - } + if (subj) { + if (!obj) { + ecs_add_id(world, subj, pred); + state->last_assign_id = pred; } else { - /* Get column index for component */ - result = ecs_type_index_of(table_type, 0, component); + ecs_add_pair(world, subj, pred, obj); + state->last_object = obj; + state->last_assign_id = ecs_pair(pred, obj); } + state->last_predicate = pred; + state->last_subject = subj; - /* If column is found, add one to the index, as column zero in - * a table is reserved for entity id's */ - if (result != -1) { - result ++; - } - - /* ecs_table_column_offset may return -1 if the component comes - * from a prefab. If so, the component will be resolved as a - * reference (see below) */ + pred_as_subj = false; + } else { + if (!obj) { + /* If no subject or object were provided, use predicate as subj + * unless the expression explictly excluded the subject */ + if (pred_as_subj) { + state->last_subject = pred; + subj = pred; + } else { + state->last_predicate = pred; + pred_as_subj = false; + } + } else { + state->last_predicate = pred; + state->last_object = obj; + pred_as_subj = false; + } } - if (op == EcsAndFrom || op == EcsOrFrom || op == EcsNotFrom) { - result = 0; - } else if (op == EcsOptional) { - /* If table doesn't have the field, mark it as no data */ - if (!ecs_type_has_id(world, table_type, component, false)) { - result = 0; + /* If this is a with clause (the list of entities between 'with' and scope + * open), add subject to the array of with frames */ + if (state->with_stmt) { + ecs_assert(pred != 0, ECS_INTERNAL_ERROR, NULL); + ecs_id_t id; + + if (obj) { + id = ecs_pair(pred, obj); + } else { + id = pred; } - } - return result; -} + state->with[state->with_frame ++] = id; + + } else if (state->using_stmt) { + ecs_assert(pred != 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(obj == 0, ECS_INTERNAL_ERROR, NULL); -static -ecs_vector_t* add_ref( - ecs_world_t *world, - ecs_query_t *query, - ecs_vector_t *references, - ecs_term_t *term, - ecs_entity_t component, - ecs_entity_t entity) -{ - ecs_ref_t *ref = ecs_vector_add(&references, ecs_ref_t); - ecs_term_id_t *subj = &term->subj; + state->using[state->using_frame ++] = pred; + state->using_frames[state->sp] = state->using_frame; - if (!(subj->set.mask & EcsCascade)) { - ecs_assert(entity != 0, ECS_INTERNAL_ERROR, NULL); + /* If this is not a with/using clause, add with frames to subject */ + } else { + if (subj) { + int32_t i, frame_count = state->with_frames[state->sp]; + for (i = 0; i < frame_count; i ++) { + ecs_add_id(world, subj, state->with[i]); + } + } } - - *ref = (ecs_ref_t){0}; - ref->entity = entity; - ref->component = component; - const EcsComponent *c_info = flecs_component_from_id(world, component); - if (c_info) { - if (c_info->size && subj->entity != 0) { - if (entity) { - ecs_get_ref_w_id(world, ref, entity, component); - } + /* If an id was provided by itself, add default scope type to it */ + ecs_entity_t default_scope_type = state->default_scope_type[state->sp]; + if (pred_as_subj && default_scope_type) { + ecs_add_id(world, subj, default_scope_type); + } - query->flags |= EcsQueryHasRefs; - } + /* If a comment preceded the statement, add it as a brief description */ +#ifdef FLECS_DOC + if (subj && state->comment) { + ecs_doc_set_brief(world, subj, state->comment); + ecs_os_free(state->comment); + state->comment = NULL; } +#endif - return references; + return 0; } static -int32_t get_pair_count( - ecs_type_t type, - ecs_entity_t pair) +const char* parse_inherit_stmt( + const char *name, + const char *expr, + const char *ptr, + plecs_state_t *state) { - int32_t i = -1, result = 0; - while (-1 != (i = ecs_type_index_of(type, i + 1, pair))) { - result ++; + if (state->isa_stmt) { + ecs_parser_error(name, expr, ptr - expr, + "cannot nest inheritance"); + return NULL; } - return result; + if (!state->last_subject) { + ecs_parser_error(name, expr, ptr - expr, + "missing entity to assign inheritance to"); + return NULL; + } + + state->isa_stmt = true; + state->assign_to = state->last_subject; + + return ptr; } -/* For each pair that the query subscribes for, count the occurrences in the - * table. Cardinality of subscribed for pairs must be the same as in the table - * or else the table won't match. */ static -int32_t count_pairs( - const ecs_query_t *query, - ecs_type_t type) +const char* parse_assign_expr( + ecs_world_t *world, + const char *name, + const char *expr, + const char *ptr, + plecs_state_t *state) { - ecs_term_t *terms = query->filter.terms; - int32_t i, count = query->filter.term_count; - int32_t first_count = 0, pair_count = 0; + (void)world; + + if (!state->assign_stmt) { + ecs_parser_error(name, expr, ptr - expr, + "unexpected value outside of assignment statement"); + return NULL; + } - for (i = 0; i < count; i ++) { - ecs_term_t *term = &terms[i]; + ecs_id_t assign_id = state->last_assign_id; + if (!assign_id) { + ecs_parser_error(name, expr, ptr - expr, + "missing type for assignment statement"); + return NULL; + } - if (!ECS_HAS_ROLE(term->id, PAIR)) { - continue; - } +#ifndef FLECS_EXPR + ecs_parser_error(name, expr, ptr - expr, + "cannot parse value, missing FLECS_EXPR addon"); + return NULL; +#else + ecs_entity_t assign_to = state->assign_to; + if (!assign_to) { + assign_to = state->last_subject; + } - if (term->subj.entity != EcsThis) { - continue; - } + if (!assign_to) { + ecs_parser_error(name, expr, ptr - expr, + "missing entity to assign to"); + return NULL; + } - if (ecs_id_is_wildcard(term->id)) { - pair_count = get_pair_count(type, term->id); - if (!first_count) { - first_count = pair_count; - } else { - if (first_count != pair_count) { - /* The pairs that this query subscribed for occur in the - * table but don't have the same cardinality. Ignore the - * table. This could typically happen for empty tables along - * a path in the table graph. */ - return -1; - } - } - } + ecs_entity_t type = ecs_get_typeid(world, assign_id); + if (!type) { + char *id_str = ecs_id_str(world, assign_id); + ecs_parser_error(name, expr, ptr - expr, + "invalid assignment, '%s' is not a type", id_str); + ecs_os_free(id_str); + return NULL; } - return first_count; -} + void *value_ptr = ecs_get_mut_id( + world, assign_to, assign_id, NULL); -static -ecs_type_t get_term_type( - ecs_world_t *world, - ecs_term_t *term, - ecs_entity_t component) -{ - ecs_oper_kind_t oper = term->oper; - ecs_assert(oper == EcsAndFrom || oper == EcsOrFrom || oper == EcsNotFrom, - ECS_INTERNAL_ERROR, NULL); - (void)oper; + ptr = ecs_parse_expr(world, ptr, type, value_ptr, + &(ecs_parse_expr_desc_t) { + .name = name, + .expr = expr, + .lookup_action = plecs_lookup_action, + .lookup_ctx = state + }); + if (!ptr) { + return NULL; + } + + ecs_modified_id(world, assign_to, assign_id); +#endif - const EcsType *type = ecs_get(world, component, EcsType); - if (type) { - return type->normalized; - } else { - return ecs_get_type(world, component); - } + return ptr; } -/** Add table to system, compute offsets for system components in table it */ static -void add_table( +const char* parse_assign_stmt( ecs_world_t *world, - ecs_query_t *query, - ecs_table_t *table) + const char *name, + const char *expr, + const char *ptr, + plecs_state_t *state) { - ecs_type_t table_type = NULL; - ecs_term_t *terms = query->filter.terms; - int32_t t, c, term_count = query->filter.term_count; + (void)world; - if (table) { - table_type = table->type; + state->isa_stmt = false; + + /* Component scope (add components to entity) */ + if (!state->last_subject) { + ecs_parser_error(name, expr, ptr - expr, + "missing entity to assign to"); + return NULL; } - int32_t pair_cur = 0, pair_count = count_pairs(query, table_type); - - /* If the query has pairs, we need to account for the fact that a table may - * have multiple components to which the pair is applied, which means the - * table has to be registered with the query multiple times, with different - * table columns. If so, allocate a small array for each pair in which the - * last added table index of the pair is stored, so that in the next - * iteration we can start the search from the correct offset type. */ - pair_offset_t *pair_offsets = NULL; - if (pair_count) { - pair_offsets = ecs_os_calloc( - ECS_SIZEOF(pair_offset_t) * term_count); + if (state->assign_stmt) { + ecs_parser_error(name, expr, ptr - expr, + "invalid assign statement in assign statement"); + return NULL; } - /* From here we recurse */ - ecs_query_table_match_t *table_data; - ecs_vector_t *references = NULL; + if (!state->scope_assign_stmt) { + state->assign_to = state->last_subject; + } - ecs_query_table_t *qt = ecs_table_cache_insert( - &query->cache, ecs_query_table_t, table); + state->assign_stmt = true; + + /* Assignment without a preceding component */ + if (ptr[0] == '{') { + ecs_entity_t type = 0; -add_pair: - table_data = cache_add(qt); - table_data->table = table; - if (table) { - table_type = table->type; - } + if (state->scope_assign_stmt) { + ecs_assert(state->assign_to == ecs_get_scope(world), + ECS_INTERNAL_ERROR, NULL); + } - if (term_count) { - /* Array that contains the system column to table column mapping */ - table_data->columns = ecs_os_calloc_n(int32_t, query->filter.term_count_actual); - ecs_assert(table_data->columns != NULL, ECS_OUT_OF_MEMORY, NULL); + /* If we're in a scope & last_subject is a type, assign to scope */ + if (ecs_get_scope(world) != 0) { + type = ecs_get_typeid(world, state->last_subject); + if (type != 0) { + type = state->last_subject; + } + } - /* Store the components of the matched table. In the case of OR expressions, - * components may differ per matched table. */ - table_data->ids = ecs_os_calloc_n(ecs_entity_t, query->filter.term_count_actual); - ecs_assert(table_data->ids != NULL, ECS_OUT_OF_MEMORY, NULL); + /* If type hasn't been set yet, check if scope has default type */ + if (!type && !state->scope_assign_stmt) { + type = state->default_scope_type[state->sp]; + } - /* Cache subject (source) entity ids for components */ - table_data->subjects = ecs_os_calloc_n(ecs_entity_t, query->filter.term_count_actual); - ecs_assert(table_data->subjects != NULL, ECS_OUT_OF_MEMORY, NULL); + /* If no type has been found still, check if last with id is a type */ + if (!type && !state->scope_assign_stmt) { + int32_t with_frame_count = state->with_frames[state->sp]; + if (with_frame_count) { + type = state->with[with_frame_count - 1]; + } + } - /* Cache subject (source) entity ids for components */ - table_data->sizes = ecs_os_calloc_n(ecs_size_t, query->filter.term_count_actual); - ecs_assert(table_data->sizes != NULL, ECS_OUT_OF_MEMORY, NULL); + if (!type) { + ecs_parser_error(name, expr, ptr - expr, + "missing type for assignment"); + return NULL; + } + + state->last_assign_id = type; } - /* Walk columns parsed from the system signature */ - c = 0; - for (t = 0; t < term_count; t ++) { - ecs_term_t *term = &terms[t]; - ecs_term_id_t subj = term->subj; - ecs_entity_t entity = 0, component = 0; - ecs_oper_kind_t op = term->oper; + return ptr; +} - if (op == EcsNot) { - subj.entity = 0; - } +static +const char* parse_using_stmt( + const char *name, + const char *expr, + const char *ptr, + plecs_state_t *state) +{ + if (state->isa_stmt || state->assign_stmt) { + ecs_parser_error(name, expr, ptr - expr, + "invalid usage of using keyword"); + return NULL; + } - /* Get actual component and component source for current column */ - bool match; - t = get_comp_and_src(world, query, t, table, &component, &entity, &match); + /* Add following expressions to using list */ + state->using_stmt = true; - /* This column does not retrieve data from a static entity */ - if (!entity && subj.entity) { - int32_t index = get_component_index(world, table_type, - &component, c, op, pair_offsets, pair_cur + 1); + return ptr + 5; +} - if (index == -1) { - if (op == EcsOptional && subj.set.mask == EcsSelf) { - index = 0; - } - } else { - if (op == EcsOptional && !(subj.set.mask & EcsSelf)) { - index = 0; - } - } +static +const char* parse_with_stmt( + const char *name, + const char *expr, + const char *ptr, + plecs_state_t *state) +{ + if (state->isa_stmt) { + ecs_parser_error(name, expr, ptr - expr, + "invalid with after inheritance"); + return NULL; + } - table_data->columns[c] = index; + if (state->assign_stmt) { + ecs_parser_error(name, expr, ptr - expr, + "invalid with in assign_stmt"); + return NULL; + } - /* If the column is a case, we should only iterate the entities in - * the column for this specific case. Add a sparse column with the - * case id so we can find the correct entities when iterating */ - if (ECS_HAS_ROLE(component, CASE)) { - flecs_sparse_column_t *sc = ecs_vector_add( - &table_data->sparse_columns, flecs_sparse_column_t); - sc->signature_column_index = t; - sc->sw_case = ECS_PAIR_OBJECT(component); - sc->sw_column = NULL; - } + /* Add following expressions to with list */ + state->with_stmt = true; + return ptr + 5; +} - /* If table has a disabled bitmask for components, check if there is - * a disabled column for the queried for component. If so, cache it - * in a vector as the iterator will need to skip the entity when the - * component is disabled. */ - if (index && (table && table->flags & EcsTableHasDisabled)) { - ecs_entity_t bs_id = - (component & ECS_COMPONENT_MASK) | ECS_DISABLED; - int32_t bs_index = ecs_type_index_of(table->type, 0, bs_id); - if (bs_index != -1) { - flecs_bitset_column_t *elem = ecs_vector_add( - &table_data->bitset_columns, flecs_bitset_column_t); - elem->column_index = bs_index; - elem->bs_column = NULL; - } - } - } +static +const char* parse_scope_open( + ecs_world_t *world, + const char *name, + const char *expr, + const char *ptr, + plecs_state_t *state) +{ + state->isa_stmt = false; - ecs_entity_t type_id = ecs_get_typeid(world, component); - if (!type_id && !(ECS_ROLE_MASK & component)) { - type_id = component; - } + if (state->assign_stmt) { + ecs_parser_error(name, expr, ptr - expr, + "invalid scope in assign_stmt"); + return NULL; + } - if (entity || table_data->columns[c] == -1 || subj.set.mask & EcsCascade) { - if (type_id) { - references = add_ref(world, query, references, term, - component, entity); - table_data->columns[c] = -ecs_vector_count(references); - } + state->sp ++; - table_data->subjects[c] = entity; - flecs_add_flag(world, entity, ECS_FLAG_OBSERVED); + ecs_entity_t scope = 0; + ecs_entity_t default_scope_type = 0; - if (!match) { - ecs_ref_t *ref = ecs_vector_last(references, ecs_ref_t); - ref->entity = 0; - } - } + if (!state->with_stmt) { + if (state->last_subject) { + scope = state->last_subject; + ecs_set_scope(world, state->last_subject); - if (type_id) { - const EcsComponent *cptr = ecs_get(world, type_id, EcsComponent); - if (!cptr || !cptr->size) { - int32_t column = table_data->columns[c]; - if (column < 0) { - ecs_ref_t *r = ecs_vector_get( - references, ecs_ref_t, -column - 1); - r->component = 0; - } - } + /* Check if scope has a default child component */ + ecs_entity_t def_type_src = ecs_get_object_for_id(world, scope, + 0, ecs_pair(EcsDefaultChildComponent, EcsWildcard)); - if (cptr) { - table_data->sizes[c] = cptr->size; - } else { - table_data->sizes[c] = 0; + if (def_type_src) { + default_scope_type = ecs_get_object( + world, def_type_src, EcsDefaultChildComponent, 0); } } else { - table_data->sizes[c] = 0; + if (state->last_object) { + scope = ecs_pair( + state->last_predicate, state->last_object); + ecs_set_with(world, scope); + } else { + if (state->last_predicate) { + scope = ecs_pair(EcsChildOf, state->last_predicate); + } + ecs_set_scope(world, state->last_predicate); + } } + state->scope[state->sp] = scope; + state->default_scope_type[state->sp] = default_scope_type; + } else { + state->scope[state->sp] = state->scope[state->sp - 1]; + state->default_scope_type[state->sp] = + state->default_scope_type[state->sp - 1]; + } - if (ECS_HAS_ROLE(component, SWITCH)) { - table_data->sizes[c] = ECS_SIZEOF(ecs_entity_t); - } else if (ECS_HAS_ROLE(component, CASE)) { - table_data->sizes[c] = ECS_SIZEOF(ecs_entity_t); - } + state->using_frames[state->sp] = state->using_frame; + state->with_frames[state->sp] = state->with_frame; + state->with_stmt = false; - table_data->ids[c] = component; + return ptr; +} - c ++; +static +const char* parse_scope_close( + ecs_world_t *world, + const char *name, + const char *expr, + const char *ptr, + plecs_state_t *state) +{ + if (state->isa_stmt) { + ecs_parser_error(name, expr, ptr - expr, + "invalid '}' after inheritance statement"); + return NULL; } - if (references) { - ecs_size_t ref_size = ECS_SIZEOF(ecs_ref_t) * ecs_vector_count(references); - table_data->references = ecs_os_malloc(ref_size); - ecs_os_memcpy(table_data->references, - ecs_vector_first(references, ecs_ref_t), ref_size); - ecs_vector_free(references); - references = NULL; + if (state->assign_stmt) { + ecs_parser_error(name, expr, ptr - expr, + "unfinished assignment before }"); + return NULL; } - /* Insert match to iteration list if table is not empty */ - if (!table || ecs_table_count(table) != 0) { - ecs_assert(table == qt->hdr.table, ECS_INTERNAL_ERROR, NULL); - insert_table_node(query, &table_data->node); - } + state->scope[state->sp] = 0; + state->default_scope_type[state->sp] = 0; + state->sp --; - /* Use tail recursion when adding table for multiple pairs */ - pair_cur ++; - if (pair_cur < pair_count) { - goto add_pair; + if (state->sp < 0) { + ecs_parser_error(name, expr, ptr - expr, "invalid } without a {"); + return NULL; } - if (table && !(query->flags & EcsQueryIsSubquery)) { - flecs_table_notify(world, table, &(ecs_table_event_t){ - .kind = EcsTableQueryMatch, - .query = query - }); - } + ecs_id_t id = state->scope[state->sp]; - if (pair_offsets) { - ecs_os_free(pair_offsets); + if (!id || ECS_HAS_ROLE(id, PAIR)) { + ecs_set_with(world, id); } -} - -static -bool match_term( - const ecs_world_t *world, - const ecs_table_t *table, - ecs_term_t *term) -{ - ecs_term_id_t *subj = &term->subj; - /* If term has no subject, there's nothing to match */ - if (!subj->entity) { - return true; + if (!id || !ECS_HAS_ROLE(id, PAIR)) { + ecs_set_scope(world, id); } - if (term->subj.entity != EcsThis) { - table = ecs_get_table(world, subj->entity); - } + state->with_frame = state->with_frames[state->sp]; + state->using_frame = state->using_frames[state->sp]; + state->last_subject = 0; + state->assign_stmt = false; - return ecs_type_match( - world, table, table->type, 0, term->id, subj->set.relation, - subj->set.min_depth, subj->set.max_depth, NULL, NULL, NULL) != -1; + return ptr; } -/* Match table with query */ -bool flecs_query_match( - const ecs_world_t *world, - const ecs_table_t *table, - const ecs_query_t *query) +static +const char *parse_plecs_term( + ecs_world_t *world, + const char *name, + const char *expr, + const char *ptr, + plecs_state_t *state) { - if (!(query->flags & EcsQueryNeedsTables)) { - return false; - } - - ecs_type_t table_type = table->type; + ecs_term_t term = {0}; + ecs_entity_t scope = ecs_get_scope(world); - /* Don't match disabled entities */ - if (!(query->flags & EcsQueryMatchDisabled) && ecs_type_has_id( - world, table_type, EcsDisabled, true)) - { - return false; - } + /* If first character is a (, this should be interpreted as an id assigned + * to the current scope if: + * - this is not already an assignment: "Foo = (Hello, World)" + * - this is in a scope + */ + bool scope_assignment = (ptr[0] == '(') && !state->assign_stmt && scope != 0; - /* Don't match prefab entities */ - if (!(query->flags & EcsQueryMatchPrefab) && ecs_type_has_id( - world, table_type, EcsPrefab, true)) - { - return false; + ptr = ecs_parse_term(world, name, expr, ptr, &term); + if (!ptr) { + return NULL; } - /* Check if pair cardinality matches pairs in query, if any */ - if (count_pairs(query, table->type) == -1) { - return false; + if (!ecs_term_is_initialized(&term)) { + ecs_parser_error(name, expr, ptr - expr, "expected identifier"); + return NULL; /* No term found */ } - ecs_term_t *terms = query->filter.terms; - int32_t i, term_count = query->filter.term_count; - - for (i = 0; i < term_count; i ++) { - ecs_term_t *term = &terms[i]; - ecs_oper_kind_t oper = term->oper; - - if (oper == EcsAnd) { - if (!match_term(world, table, term)) { - return false; + /* Lookahead to check if this is an implicit scope assignment (no parens) */ + if (ptr[0] == '=') { + const char *tptr = ecs_parse_fluff(ptr + 1, NULL); + if (tptr[0] == '{') { + ecs_entity_t pred = plecs_lookup( + world, term.pred.name, state, false); + ecs_entity_t obj = plecs_lookup( + world, term.obj.name, state, false); + ecs_id_t id = 0; + if (pred && obj) { + id = ecs_pair(pred, obj); + } else if (pred) { + id = pred; } - } else if (oper == EcsNot) { - if (match_term(world, table, term)) { - return false; + if (id && (ecs_get_typeid(world, id) != 0)) { + scope_assignment = true; } + } + } - } else if (oper == EcsOr) { - bool match = false; + bool prev = state->assign_stmt; + if (scope_assignment) { + state->assign_stmt = true; + state->assign_to = scope; + } + if (create_term(world, &term, name, expr, (ptr - expr), state)) { + ecs_term_fini(&term); + return NULL; /* Failed to create term */ + } + if (scope_assignment) { + state->last_subject = state->last_assign_id; + state->scope_assign_stmt = true; + } + state->assign_stmt = prev; - for (; i < term_count; i ++) { - term = &terms[i]; - if (term->oper != EcsOr) { - i --; - break; - } + ecs_term_fini(&term); - if (!match && match_term( world, table, term)) { - match = true; - } - } + return ptr; +} - if (!match) { - return false; - } - - } else if (oper == EcsAndFrom || oper == EcsOrFrom || oper == EcsNotFrom) { - ecs_type_t type = get_term_type((ecs_world_t*)world, term, term->id); - int32_t match_count = 0, j, count = ecs_vector_count(type); - ecs_entity_t *ids = ecs_vector_first(type, ecs_entity_t); +static +const char* parse_stmt( + ecs_world_t *world, + const char *name, + const char *expr, + const char *ptr, + plecs_state_t *state) +{ + state->assign_stmt = false; + state->scope_assign_stmt = false; + state->isa_stmt = false; + state->with_stmt = false; + state->using_stmt = false; + state->last_subject = 0; + state->last_predicate = 0; + state->last_object = 0; - for (j = 0; j < count; j ++) { - ecs_term_t tmp_term = *term; - tmp_term.oper = EcsAnd; - tmp_term.id = ids[j]; - tmp_term.pred.entity = ids[j]; + ptr = parse_fluff(expr, ptr, state); - if (match_term(world, table, &tmp_term)) { - match_count ++; - } - } + char ch = ptr[0]; + + if (!ch) { + goto done; + } else if (ch == '{') { + ptr = parse_fluff(expr, ptr + 1, state); + goto scope_open; + } else if (ch == '}') { + ptr = parse_fluff(expr, ptr + 1, state); + goto scope_close; + } else if (ch == '(') { + goto term_expr; + } else if (!ecs_os_strncmp(ptr, TOK_USING " ", 5)) { + ptr = parse_using_stmt(name, expr, ptr, state); + if (!ptr) goto error; + goto term_expr; + } else if (!ecs_os_strncmp(ptr, TOK_WITH " ", 5)) { + ptr = parse_with_stmt(name, expr, ptr, state); + if (!ptr) goto error; + goto term_expr; + } else { + goto term_expr; + } - if (oper == EcsAndFrom && match_count != count) { - return false; - } - if (oper == EcsOrFrom && match_count == 0) { - return false; - } - if (oper == EcsNotFrom && match_count != 0) { - return false; - } - } +term_expr: + if (!ptr[0]) { + goto done; } - return true; -} + if (!(ptr = parse_plecs_term(world, name, ptr, ptr, state))) { + goto error; + } -/** Match existing tables against system (table is created before system) */ -static -void match_tables( - ecs_world_t *world, - ecs_query_t *query) -{ - int32_t i, count = flecs_sparse_count(world->store.tables); + ptr = parse_fluff(expr, ptr, state); - for (i = 0; i < count; i ++) { - ecs_table_t *table = flecs_sparse_get_dense( - world->store.tables, ecs_table_t, i); + if (ptr[0] == '{' && !isspace(ptr[-1])) { + /* A '{' directly after an identifier (no whitespace) is a literal */ + goto assign_expr; + } - if (flecs_query_match(world, table, query)) { - add_table(world, query, table); + if (!state->using_stmt) { + if (ptr[0] == ':') { + ptr = parse_fluff(expr, ptr + 1, state); + goto inherit_stmt; + } else if (ptr[0] == '=') { + ptr = parse_fluff(expr, ptr + 1, state); + goto assign_stmt; + } else if (ptr[0] == ',') { + ptr = parse_fluff(expr, ptr + 1, state); + goto term_expr; + } else if (ptr[0] == '{') { + state->assign_stmt = false; + ptr = parse_fluff(expr, ptr + 1, state); + goto scope_open; } } -} -#define ELEM(ptr, size, index) ECS_OFFSET(ptr, size * index) + state->assign_stmt = false; + goto done; -static -int32_t qsort_partition( - ecs_world_t *world, - ecs_table_t *table, - ecs_data_t *data, - ecs_entity_t *entities, - void *ptr, - int32_t elem_size, - int32_t lo, - int32_t hi, - ecs_order_by_action_t compare) -{ - int32_t p = (hi + lo) / 2; - void *pivot = ELEM(ptr, elem_size, p); - ecs_entity_t pivot_e = entities[p]; - int32_t i = lo - 1, j = hi + 1; - void *el; +inherit_stmt: + ptr = parse_inherit_stmt(name, expr, ptr, state); + if (!ptr) goto error; -repeat: - { - do { - i ++; - el = ELEM(ptr, elem_size, i); - } while ( compare(entities[i], el, pivot_e, pivot) < 0); + /* Expect base identifier */ + goto term_expr; - do { - j --; - el = ELEM(ptr, elem_size, j); - } while ( compare(entities[j], el, pivot_e, pivot) > 0); +assign_stmt: + ptr = parse_assign_stmt(world, name, expr, ptr, state); + if (!ptr) goto error; - if (i >= j) { - return j; - } + ptr = parse_fluff(expr, ptr, state); - flecs_table_swap(world, table, data, i, j); + /* Assignment without a preceding component */ + if (ptr[0] == '{') { + goto assign_expr; + } - if (p == i) { - pivot = ELEM(ptr, elem_size, j); - pivot_e = entities[j]; - } else if (p == j) { - pivot = ELEM(ptr, elem_size, i); - pivot_e = entities[i]; - } + /* Expect component identifiers */ + goto term_expr; - goto repeat; - } -} +assign_expr: + ptr = parse_assign_expr(world, name, expr, ptr, state); + if (!ptr) goto error; -static -void qsort_array( - ecs_world_t *world, - ecs_table_t *table, - ecs_data_t *data, - ecs_entity_t *entities, - void *ptr, - int32_t size, - int32_t lo, - int32_t hi, - ecs_order_by_action_t compare) -{ - if ((hi - lo) < 1) { - return; + ptr = parse_fluff(expr, ptr, state); + if (ptr[0] == ',') { + ptr ++; + goto term_expr; + } else if (ptr[0] == '{') { + state->assign_stmt = false; + ptr ++; + goto scope_open; + } else { + state->assign_stmt = false; + goto done; } - int32_t p = qsort_partition( - world, table, data, entities, ptr, size, lo, hi, compare); +scope_open: + ptr = parse_scope_open(world, name, expr, ptr, state); + if (!ptr) goto error; + goto done; - qsort_array(world, table, data, entities, ptr, size, lo, p, compare); +scope_close: + ptr = parse_scope_close(world, name, expr, ptr, state); + if (!ptr) goto error; + goto done; - qsort_array(world, table, data, entities, ptr, size, p + 1, hi, compare); +done: + return ptr; +error: + return NULL; } -static -void sort_table( +int ecs_plecs_from_str( ecs_world_t *world, - ecs_table_t *table, - int32_t column_index, - ecs_order_by_action_t compare) + const char *name, + const char *expr) { - ecs_data_t *data = &table->storage; - if (!data->entities) { - /* Nothing to sort */ - return; - } + const char *ptr = expr; + ecs_term_t term = {0}; + plecs_state_t state = {0}; - int32_t count = flecs_table_data_count(data); - if (count < 2) { - return; + if (!expr) { + return 0; } - ecs_entity_t *entities = ecs_vector_first(data->entities, ecs_entity_t); + state.scope[0] = ecs_get_scope(world); + ecs_entity_t prev_with = ecs_set_with(world, 0); - void *ptr = NULL; - int32_t size = 0; - if (column_index != -1) { - ecs_column_t *column = &data->columns[column_index]; - size = column->size; - ptr = ecs_vector_first_t(column->data, size, column->alignment); - } + do { + expr = ptr = parse_stmt(world, name, expr, ptr, &state); + if (!ptr) { + goto error; + } - qsort_array(world, table, data, entities, ptr, size, 0, count - 1, compare); -} + if (!ptr[0]) { + break; /* End of expression */ + } + } while (true); -/* Helper struct for building sorted table ranges */ -typedef struct sort_helper_t { - ecs_query_table_match_t *match; - ecs_entity_t *entities; - const void *ptr; - int32_t row; - int32_t elem_size; - int32_t count; - bool shared; -} sort_helper_t; + ecs_set_scope(world, state.scope[0]); + ecs_set_with(world, prev_with); + + clear_comment(expr, ptr, &state); -static -const void* ptr_from_helper( - sort_helper_t *helper) -{ - ecs_assert(helper->row < helper->count, ECS_INTERNAL_ERROR, NULL); - ecs_assert(helper->elem_size >= 0, ECS_INTERNAL_ERROR, NULL); - ecs_assert(helper->row >= 0, ECS_INTERNAL_ERROR, NULL); - if (helper->shared) { - return helper->ptr; - } else { - return ELEM(helper->ptr, helper->elem_size, helper->row); + if (state.sp != 0) { + ecs_parser_error(name, expr, 0, "missing end of scope"); + goto error; } -} -static -ecs_entity_t e_from_helper( - sort_helper_t *helper) -{ - if (helper->row < helper->count) { - return helper->entities[helper->row]; - } else { - return 0; + if (state.assign_stmt) { + ecs_parser_error(name, expr, 0, "unfinished assignment"); + goto error; } -} -static -void build_sorted_table_range( - ecs_query_t *query, - ecs_query_table_list_t *list) -{ - ecs_world_t *world = query->world; - ecs_entity_t component = query->order_by_component; - ecs_order_by_action_t compare = query->order_by; - - if (!list->count) { - return; + if (state.errors) { + goto error; } - int to_sort = 0; - sort_helper_t *helper = ecs_os_malloc_n(sort_helper_t, list->count); - ecs_query_table_node_t *cur, *end = list->last->next; - for (cur = list->first; cur != end; cur = cur->next) { - ecs_query_table_match_t *match = cur->match; - ecs_table_t *table = match->table; - ecs_data_t *data = &table->storage; - ecs_vector_t *entities; - if (!(entities = data->entities) || !ecs_table_count(table)) { - continue; - } - - int32_t index = ecs_type_index_of(table->storage_type, 0, component); - if (index != -1) { - ecs_column_t *column = &data->columns[index]; - int16_t size = column->size; - int16_t align = column->alignment; - helper[to_sort].ptr = ecs_vector_first_t(column->data, size, align); - helper[to_sort].elem_size = size; - helper[to_sort].shared = false; - } else if (component) { - /* Find component in prefab */ - ecs_entity_t base; - ecs_type_match(world, table, table->type, 0, component, - EcsIsA, 1, 0, &base, NULL, NULL); + return 0; +error: + ecs_set_scope(world, state.scope[0]); + ecs_set_with(world, prev_with); + ecs_term_fini(&term); + return -1; +} - /* If a base was not found, the query should not have allowed using - * the component for sorting */ - ecs_assert(base != 0, ECS_INTERNAL_ERROR, NULL); +int ecs_plecs_from_file( + ecs_world_t *world, + const char *filename) +{ + FILE* file; + char* content = NULL; + int32_t bytes; + size_t size; - const EcsComponent *cptr = ecs_get(world, component, EcsComponent); - ecs_assert(cptr != NULL, ECS_INTERNAL_ERROR, NULL); + /* Open file for reading */ + ecs_os_fopen(&file, filename, "r"); + if (!file) { + ecs_err("%s (%s)", ecs_os_strerror(errno), filename); + goto error; + } - helper[to_sort].ptr = ecs_get_id(world, base, component); - helper[to_sort].elem_size = cptr->size; - helper[to_sort].shared = true; - } else { - helper[to_sort].ptr = NULL; - helper[to_sort].elem_size = 0; - helper[to_sort].shared = false; - } + /* Determine file size */ + fseek(file, 0 , SEEK_END); + bytes = (int32_t)ftell(file); + if (bytes == -1) { + goto error; + } + rewind(file); - helper[to_sort].match = match; - helper[to_sort].entities = ecs_vector_first(entities, ecs_entity_t); - helper[to_sort].row = 0; - helper[to_sort].count = ecs_table_count(table); - to_sort ++; + /* Load contents in memory */ + content = ecs_os_malloc(bytes + 1); + size = (size_t)bytes; + if (!(size = fread(content, 1, size, file)) && bytes) { + ecs_err("%s: read zero bytes instead of %d", filename, size); + ecs_os_free(content); + content = NULL; + goto error; + } else { + content[size] = '\0'; } - bool proceed; - do { - int32_t j, min = 0; - proceed = true; + fclose(file); - ecs_entity_t e1; - while (!(e1 = e_from_helper(&helper[min]))) { - min ++; - if (min == to_sort) { - proceed = false; - break; - } - } + int result = ecs_plecs_from_str(world, filename, content); + ecs_os_free(content); + return result; +error: + ecs_os_free(content); + return -1; +} - if (!proceed) { - break; - } +#endif - for (j = min + 1; j < to_sort; j++) { - ecs_entity_t e2 = e_from_helper(&helper[j]); - if (!e2) { - continue; - } +#ifdef FLECS_PIPELINE - const void *ptr1 = ptr_from_helper(&helper[min]); - const void *ptr2 = ptr_from_helper(&helper[j]); +/* Worker thread */ +static +void* worker(void *arg) { + ecs_stage_t *stage = arg; + ecs_world_t *world = stage->world; - if (compare(e1, ptr1, e2, ptr2) > 0) { - min = j; - e1 = e_from_helper(&helper[min]); - } - } + /* Start worker thread, increase counter so main thread knows how many + * workers are ready */ + ecs_os_mutex_lock(world->sync_mutex); + world->workers_running ++; - sort_helper_t *cur_helper = &helper[min]; - if (!cur || cur->match != cur_helper->match) { - cur = ecs_vector_add(&query->table_slices, ecs_query_table_node_t); - ecs_assert(cur != NULL, ECS_INTERNAL_ERROR, NULL); - cur->match = cur_helper->match; - cur->offset = cur_helper->row; - cur->count = 1; - } else { - cur->count ++; - } + if (!world->quit_workers) { + ecs_os_cond_wait(world->worker_cond, world->sync_mutex); + } - cur_helper->row ++; - } while (proceed); + ecs_os_mutex_unlock(world->sync_mutex); - /* Iterate through the vector of slices to set the prev/next ptrs. This - * can't be done while building the vector, as reallocs may occur */ - int32_t i, count = ecs_vector_count(query->table_slices); - ecs_query_table_node_t *nodes = ecs_vector_first( - query->table_slices, ecs_query_table_node_t); - for (i = 0; i < count; i ++) { - nodes[i].prev = &nodes[i - 1]; - nodes[i].next = &nodes[i + 1]; + while (!world->quit_workers) { + ecs_entity_t old_scope = ecs_set_scope((ecs_world_t*)stage, 0); + + ecs_run_pipeline( + (ecs_world_t*)stage, + world->pipeline, + world->stats.delta_time); + + ecs_set_scope((ecs_world_t*)stage, old_scope); } - nodes[0].prev = NULL; - nodes[i - 1].next = NULL; + ecs_os_mutex_lock(world->sync_mutex); + world->workers_running --; + ecs_os_mutex_unlock(world->sync_mutex); - ecs_os_free(helper); + return NULL; } +/* Start threads */ static -void build_sorted_tables( - ecs_query_t *query) +void start_workers( + ecs_world_t *world, + int32_t threads) { - ecs_vector_clear(query->table_slices); + ecs_set_stages(world, threads); - if (query->group_by) { - /* Populate sorted node list in grouping order */ - ecs_query_table_node_t *cur = query->list.first; - if (cur) { - do { - /* Find list for current group */ - ecs_query_table_match_t *match = cur->match; - ecs_assert(match != NULL, ECS_INTERNAL_ERROR, NULL); - uint64_t group_id = match->group_id; - ecs_query_table_list_t *list = ecs_map_get(query->groups, - ecs_query_table_list_t, group_id); - ecs_assert(list != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(ecs_get_stage_count(world) == threads, ECS_INTERNAL_ERROR, NULL); - /* Sort tables in current group */ - build_sorted_table_range(query, list); - - /* Find next group to sort */ - cur = list->last->next; - } while (cur); - } - } else { - build_sorted_table_range(query, &query->list); + int32_t i; + for (i = 0; i < threads; i ++) { + ecs_stage_t *stage = (ecs_stage_t*)ecs_get_stage(world, i); + ecs_assert(stage != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_poly_assert(stage, ecs_stage_t); + + ecs_vector_get(world->worker_stages, ecs_stage_t, i); + stage->thread = ecs_os_thread_new(worker, stage); + ecs_assert(stage->thread != 0, ECS_OPERATION_FAILED, NULL); } } +/* Wait until all workers are running */ static -bool tables_dirty( - const ecs_query_t *query) +void wait_for_workers( + ecs_world_t *world) { - bool is_dirty = false; - - ecs_vector_t *vec = query->cache.tables; - ecs_query_table_t *tables = ecs_vector_first(vec, ecs_query_table_t); - int32_t i, count = ecs_vector_count(vec); - - for (i = 0; i < count; i ++) { - ecs_query_table_t *qt = &tables[i]; - ecs_table_t *table = qt->hdr.table; + int32_t stage_count = ecs_get_stage_count(world); + bool wait = true; - if (!qt->monitor) { - qt->monitor = flecs_table_get_monitor(table); - is_dirty = true; + do { + ecs_os_mutex_lock(world->sync_mutex); + if (world->workers_running == stage_count) { + wait = false; } + ecs_os_mutex_unlock(world->sync_mutex); + } while (wait); +} - int32_t *dirty_state = flecs_table_get_dirty_state(table); - int32_t t, storage_count = ecs_table_storage_count(table); - for (t = 0; t < storage_count + 1; t ++) { - is_dirty = is_dirty || (dirty_state[t] != qt->monitor[t]); - } - } +/* Synchronize worker threads */ +static +void sync_worker( + ecs_world_t *world) +{ + int32_t stage_count = ecs_get_stage_count(world); - is_dirty = is_dirty || (query->match_count != query->prev_match_count); + /* Signal that thread is waiting */ + ecs_os_mutex_lock(world->sync_mutex); + if (++ world->workers_waiting == stage_count) { + /* Only signal main thread when all threads are waiting */ + ecs_os_cond_signal(world->sync_cond); + } - return is_dirty; + /* Wait until main thread signals that thread can continue */ + ecs_os_cond_wait(world->worker_cond, world->sync_mutex); + ecs_os_mutex_unlock(world->sync_mutex); } +/* Wait until all threads are waiting on sync point */ static -void tables_reset_dirty( - ecs_query_t *query) +void wait_for_sync( + ecs_world_t *world) { - query->prev_match_count = query->match_count; - - ecs_vector_t *vec = query->cache.tables; - ecs_query_table_t *tables = ecs_vector_first(vec, ecs_query_table_t); - int32_t i, count = ecs_vector_count(vec); + int32_t stage_count = ecs_get_stage_count(world); - for (i = 0; i < count; i ++) { - ecs_query_table_t *qt = &tables[i]; - ecs_table_t *table = qt->hdr.table; + ecs_os_mutex_lock(world->sync_mutex); + if (world->workers_waiting != stage_count) { + ecs_os_cond_wait(world->sync_cond, world->sync_mutex); + } + + /* We should have been signalled unless all workers are waiting on sync */ + ecs_assert(world->workers_waiting == stage_count, + ECS_INTERNAL_ERROR, NULL); - if (!qt->monitor) { - /* If one table doesn't have a monitor, none of the tables will have - * a monitor, so early out. */ - return; - } + ecs_os_mutex_unlock(world->sync_mutex); +} - int32_t *dirty_state = flecs_table_get_dirty_state(table); - int32_t t, storage_count = ecs_table_storage_count(table); - for (t = 0; t < storage_count + 1; t ++) { - qt->monitor[t] = dirty_state[t]; - } - } +/* Signal workers that they can start/resume work */ +static +void signal_workers( + ecs_world_t *world) +{ + ecs_os_mutex_lock(world->sync_mutex); + ecs_os_cond_broadcast(world->worker_cond); + ecs_os_mutex_unlock(world->sync_mutex); } +/** Stop worker threads */ static -void sort_tables( - ecs_world_t *world, - ecs_query_t *query) +bool ecs_stop_threads( + ecs_world_t *world) { - ecs_order_by_action_t compare = query->order_by; - if (!compare) { - return; + bool threads_active = false; + + /* Test if threads are created. Cannot use workers_running, since this is + * a potential race if threads haven't spun up yet. */ + ecs_vector_each(world->worker_stages, ecs_stage_t, stage, { + if (stage->thread) { + threads_active = true; + break; + } + stage->thread = 0; + }); + + /* If no threads are active, just return */ + if (!threads_active) { + return false; } - - ecs_entity_t order_by_component = query->order_by_component; - /* Iterate over non-empty tables. Don't bother with empty tables as they - * have nothing to sort */ + /* Make sure all threads are running, to ensure they catch the signal */ + wait_for_workers(world); - bool tables_sorted = false; - ecs_vector_t *vec = query->cache.tables; - ecs_query_table_t *tables = ecs_vector_first(vec, ecs_query_table_t); - int32_t i, count = ecs_vector_count(vec); + /* Signal threads should quit */ + world->quit_workers = true; + signal_workers(world); - for (i = 0; i < count; i ++) { - ecs_query_table_t *qt = &tables[i]; - ecs_table_t *table = qt->hdr.table; + /* Join all threads with main */ + ecs_vector_each(world->worker_stages, ecs_stage_t, stage, { + ecs_os_thread_join(stage->thread); + stage->thread = 0; + }); - /* If no monitor had been created for the table yet, create it now */ - bool is_dirty = false; - if (!qt->monitor) { - qt->monitor = flecs_table_get_monitor(table); + world->quit_workers = false; + ecs_assert(world->workers_running == 0, ECS_INTERNAL_ERROR, NULL); - /* A new table is always dirty */ - is_dirty = true; - } + /* Deinitialize stages */ + ecs_set_stages(world, 0); - int32_t *dirty_state = flecs_table_get_dirty_state(table); - is_dirty = is_dirty || (dirty_state[0] != qt->monitor[0]); + return true; +} - int32_t index = -1; - if (order_by_component) { - /* Get index of sorted component. We only care if the component we're - * sorting on has changed or if entities have been added / re(moved) */ - index = ecs_type_index_of(table->storage_type, - 0, order_by_component); +/* -- Private functions -- */ - if (index != -1) { - ecs_assert(index < ecs_vector_count(table->type), - ECS_INTERNAL_ERROR, NULL); +void ecs_worker_begin( + ecs_world_t *world) +{ + flecs_stage_from_world(&world); + int32_t stage_count = ecs_get_stage_count(world); + ecs_assert(stage_count != 0, ECS_INTERNAL_ERROR, NULL); + + if (stage_count == 1) { + ecs_entity_t pipeline = world->pipeline; + const EcsPipelineQuery *pq = ecs_get(world, pipeline, EcsPipelineQuery); + ecs_assert(pq != NULL, ECS_INTERNAL_ERROR, NULL); - is_dirty = is_dirty || (dirty_state[index + 1] != qt->monitor[index + 1]); - } else { - /* Table does not contain component which means the sorted - * component is shared. Table does not need to be sorted */ - continue; - } - } - - /* Check both if entities have moved (element 0) or if the component - * we're sorting on has changed (index + 1) */ - if (is_dirty) { - /* Sort the table */ - sort_table(world, table, index, compare); - tables_sorted = true; + ecs_pipeline_op_t *op = ecs_vector_first(pq->ops, ecs_pipeline_op_t); + if (!op || !op->no_staging) { + ecs_staging_begin(world); } } - - if (tables_sorted || query->match_count != query->prev_match_count) { - build_sorted_tables(query); - query->match_count ++; /* Increase version if tables changed */ - } } -static -bool has_refs( - ecs_query_t *query) +int32_t ecs_worker_sync( + ecs_world_t *world, + const EcsPipelineQuery *pq, + ecs_iter_t *it, + int32_t i, + ecs_pipeline_op_t **op_out, + ecs_pipeline_op_t **last_op_out) { - ecs_term_t *terms = query->filter.terms; - int32_t i, count = query->filter.term_count; + int32_t stage_count = ecs_get_stage_count(world); + ecs_assert(stage_count != 0, ECS_INTERNAL_ERROR, NULL); + int32_t build_count = world->stats.pipeline_build_count_total; - for (i = 0; i < count; i ++) { - ecs_term_t *term = &terms[i]; - ecs_term_id_t *subj = &term->subj; + /* If there are no threads, merge in place */ + if (stage_count == 1) { + if (!op_out[0]->no_staging) { + ecs_staging_end(world); + } - if (term->oper == EcsNot && !subj->entity) { - /* Special case: if oper kind is Not and the query contained a - * shared expression, the expression is translated to FromEmpty to - * prevent resolving the ref */ - return true; - } else if (subj->entity && (subj->entity != EcsThis || subj->set.mask != EcsSelf)) { - /* If entity is not this, or if it can be substituted by other - * entities, the query can have references. */ - return true; - } - } + ecs_pipeline_update(world, world->pipeline, false); - return false; -} + /* Synchronize all workers. The last worker to reach the sync point will + * signal the main thread, which will perform the merge. */ + } else { + sync_worker(world); + } -static -bool has_pairs( - ecs_query_t *query) -{ - ecs_term_t *terms = query->filter.terms; - int32_t i, count = query->filter.term_count; + if (build_count != world->stats.pipeline_build_count_total) { + i = ecs_pipeline_reset_iter(world, pq, it, op_out, last_op_out); + } else { + op_out[0] ++; + } - for (i = 0; i < count; i ++) { - if (ecs_id_is_wildcard(terms[i].id)) { - return true; + if (stage_count == 1) { + if (!op_out[0]->no_staging) { + ecs_staging_begin(world); } } - return false; + return i; } -static -void register_monitors( - ecs_world_t *world, - ecs_query_t *query) +void ecs_worker_end( + ecs_world_t *world) { - ecs_term_t *terms = query->filter.terms; - int32_t i, count = query->filter.term_count; - - for (i = 0; i < count; i++) { - ecs_term_t *term = &terms[i]; - ecs_term_id_t *subj = &term->subj; + flecs_stage_from_world(&world); - /* If component is requested with EcsCascade register component as a - * parent monitor. Parent monitors keep track of whether an entity moved - * in the hierarchy, which potentially requires the query to reorder its - * tables. - * Also register a regular component monitor for EcsCascade columns. - * This ensures that when the component used in the EcsCascade column - * is added or removed tables are updated accordingly*/ - if (subj->set.mask & EcsSuperSet && subj->set.mask & EcsCascade && - subj->set.relation != EcsIsA) - { - if (term->oper != EcsOr) { - if (term->subj.set.relation != EcsIsA) { - flecs_monitor_register( - world, term->subj.set.relation, term->id, query); - } - flecs_monitor_register(world, 0, term->id, query); - } + int32_t stage_count = ecs_get_stage_count(world); + ecs_assert(stage_count != 0, ECS_INTERNAL_ERROR, NULL); - /* FromAny also requires registering a monitor, as FromAny columns can - * be matched with prefabs. The only term kinds that do not require - * registering a monitor are FromOwned and FromEmpty. */ - } else if ((subj->set.mask & EcsSuperSet) || (subj->entity != EcsThis)){ - if (term->oper != EcsOr) { - flecs_monitor_register(world, 0, term->id, query); - } + /* If there are no threads, merge in place */ + if (stage_count == 1) { + if (ecs_stage_is_readonly(world)) { + ecs_staging_end(world); } - }; + + /* Synchronize all workers. The last worker to reach the sync point will + * signal the main thread, which will perform the merge. */ + } else { + sync_worker(world); + } } -static -void process_signature( +void ecs_workers_progress( ecs_world_t *world, - ecs_query_t *query) + ecs_entity_t pipeline, + FLECS_FLOAT delta_time) { - ecs_term_t *terms = query->filter.terms; - int32_t i, count = query->filter.term_count; + ecs_poly_assert(world, ecs_world_t); + int32_t stage_count = ecs_get_stage_count(world); - for (i = 0; i < count; i ++) { - ecs_term_t *term = &terms[i]; - ecs_term_id_t *pred = &term->pred; - ecs_term_id_t *subj = &term->subj; - ecs_term_id_t *obj = &term->obj; - ecs_oper_kind_t op = term->oper; - ecs_inout_kind_t inout = term->inout; + ecs_time_t start = {0}; + if (world->measure_frame_time) { + ecs_time_measure(&start); + } - (void)pred; - (void)obj; + if (stage_count == 1) { + ecs_pipeline_update(world, pipeline, true); + ecs_entity_t old_scope = ecs_set_scope(world, 0); + ecs_world_t *stage = ecs_get_stage(world, 0); + ecs_run_pipeline(stage, pipeline, delta_time); + ecs_set_scope(world, old_scope); + } else { + ecs_pipeline_update(world, pipeline, true); - /* Queries do not support variables */ - ecs_check(pred->var != EcsVarIsVariable, - ECS_UNSUPPORTED, NULL); - ecs_check(subj->entity == EcsThis || subj->var != EcsVarIsVariable, - ECS_UNSUPPORTED, NULL); - ecs_check(obj->var != EcsVarIsVariable, - ECS_UNSUPPORTED, NULL); + const EcsPipelineQuery *pq = ecs_get(world, pipeline, EcsPipelineQuery); + ecs_vector_t *ops = pq->ops; + ecs_pipeline_op_t *op = ecs_vector_first(ops, ecs_pipeline_op_t); + ecs_pipeline_op_t *op_last = ecs_vector_last(ops, ecs_pipeline_op_t); - /* If self is not included in set, always start from depth 1 */ - if (!subj->set.min_depth && !(subj->set.mask & EcsSelf)) { - subj->set.min_depth = 1; - } + /* Make sure workers are running and ready */ + wait_for_workers(world); - if (inout != EcsIn) { - query->flags |= EcsQueryHasOutColumns; - } + /* Synchronize n times for each op in the pipeline */ + for (; op <= op_last; op ++) { + if (!op->no_staging) { + ecs_staging_begin(world); + } - if (op == EcsOptional) { - query->flags |= EcsQueryHasOptional; - } + /* Signal workers that they should start running systems */ + world->workers_waiting = 0; + signal_workers(world); - if (!(query->flags & EcsQueryMatchDisabled)) { - if (op == EcsAnd || op == EcsOr || op == EcsOptional) { - if (term->id == EcsDisabled) { - query->flags |= EcsQueryMatchDisabled; - } + /* Wait until all workers are waiting on sync point */ + wait_for_sync(world); + + /* Merge */ + if (!op->no_staging) { + ecs_staging_end(world); } - } - if (!(query->flags & EcsQueryMatchPrefab)) { - if (op == EcsAnd || op == EcsOr || op == EcsOptional) { - if (term->id == EcsPrefab) { - query->flags |= EcsQueryMatchPrefab; - } + if (ecs_pipeline_update(world, pipeline, false)) { + /* Refetch, in case pipeline itself has moved */ + pq = ecs_get(world, pipeline, EcsPipelineQuery); + + /* Pipeline has changed, reset position in pipeline */ + ecs_iter_t it; + ecs_pipeline_reset_iter(world, pq, &it, &op, &op_last); + op --; } } + } - if (subj->entity == EcsThis) { - query->flags |= EcsQueryNeedsTables; - } + if (world->measure_frame_time) { + world->stats.system_time_total += (float)ecs_time_measure(&start); + } +} - if (subj->set.mask & EcsCascade && term->oper == EcsOptional) { - /* Query can only have one cascade column */ - ecs_assert(query->cascade_by == 0, ECS_INVALID_PARAMETER, NULL); - query->cascade_by = i + 1; +/* -- Public functions -- */ + +void ecs_set_threads( + ecs_world_t *world, + int32_t threads) +{ + ecs_assert(threads <= 1 || ecs_os_has_threading(), ECS_MISSING_OS_API, NULL); + + int32_t stage_count = ecs_get_stage_count(world); + + if (stage_count != threads) { + /* Stop existing threads */ + if (stage_count > 1) { + if (ecs_stop_threads(world)) { + ecs_os_cond_free(world->worker_cond); + ecs_os_cond_free(world->sync_cond); + ecs_os_mutex_free(world->sync_mutex); + } } - if (subj->entity && subj->entity != EcsThis && - subj->set.mask == EcsSelf) - { - flecs_add_flag(world, term->subj.entity, ECS_FLAG_OBSERVED); + /* Start threads if number of threads > 1 */ + if (threads > 1) { + world->worker_cond = ecs_os_cond_new(); + world->sync_cond = ecs_os_cond_new(); + world->sync_mutex = ecs_os_mutex_new(); + start_workers(world, threads); } } +} - query->flags |= (ecs_flags32_t)(has_refs(query) * EcsQueryHasRefs); - query->flags |= (ecs_flags32_t)(has_pairs(query) * EcsQueryHasTraits); +#endif - if (!(query->flags & EcsQueryIsSubquery)) { - register_monitors(world, query); - } -error: - return; -} +#ifdef FLECS_PIPELINE + +static ECS_DTOR(EcsPipelineQuery, ptr, { + ecs_vector_free(ptr->ops); +}) static -bool match_table( - ecs_world_t *world, - ecs_query_t *query, - ecs_table_t *table) +int compare_entity( + ecs_entity_t e1, + const void *ptr1, + ecs_entity_t e2, + const void *ptr2) { - if (flecs_query_match(world, table, query)) { - add_table(world, query, table); - return true; - } - return false; + (void)ptr1; + (void)ptr2; + return (e1 > e2) - (e1 < e2); } -/** When a table becomes empty remove it from the query list, or vice versa. */ static -void update_table( - ecs_query_t *query, - ecs_table_t *table, - bool empty) +uint64_t group_by_phase( + ecs_world_t *world, + ecs_type_t type, + ecs_entity_t pipeline, + void *ctx) { - int32_t prev_count = ecs_query_table_count(query); - ecs_table_cache_set_empty(&query->cache, table, empty); - int32_t cur_count = ecs_query_table_count(query); + (void)ctx; + + const EcsType *pipeline_type = ecs_get(world, pipeline, EcsType); + ecs_assert(pipeline_type != NULL, ECS_INTERNAL_ERROR, NULL); - if (prev_count != cur_count) { - ecs_query_table_t *qt = ecs_table_cache_get( - &query->cache, ecs_query_table_t, table); - ecs_assert(qt != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_query_table_match_t *cur, *next; + /* Find tag in system that belongs to pipeline */ + ecs_entity_t *sys_comps = ecs_vector_first(type, ecs_entity_t); + int32_t c, t, count = ecs_vector_count(type); - for (cur = qt->first; cur != NULL; cur = next) { - next = cur->next_match; + ecs_entity_t *tags = ecs_vector_first(pipeline_type->normalized, ecs_entity_t); + int32_t tag_count = ecs_vector_count(pipeline_type->normalized); - if (empty) { - ecs_assert(ecs_table_count(table) == 0, - ECS_INTERNAL_ERROR, NULL); + ecs_entity_t result = 0; - remove_table_node(query, &cur->node); - } else { - ecs_assert(ecs_table_count(table) != 0, - ECS_INTERNAL_ERROR, NULL); - insert_table_node(query, &cur->node); + for (c = 0; c < count; c ++) { + ecs_entity_t comp = sys_comps[c]; + for (t = 0; t < tag_count; t ++) { + if (comp == tags[t]) { + result = comp; + break; } } + if (result) { + break; + } } - ecs_assert(cur_count || query->list.first == NULL, - ECS_INTERNAL_ERROR, NULL); + ecs_assert(result != 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(result < INT_MAX, ECS_INTERNAL_ERROR, NULL); + + return result; } -static -void add_subquery( - ecs_world_t *world, - ecs_query_t *parent, - ecs_query_t *subquery) -{ - ecs_query_t **elem = ecs_vector_add(&parent->subqueries, ecs_query_t*); - *elem = subquery; +typedef enum ComponentWriteState { + NotWritten = 0, + WriteToMain, + WriteToStage +} ComponentWriteState; - ecs_table_cache_t *cache = &parent->cache; - ecs_vector_t *tables = cache->tables, *empty = cache->empty_tables; +typedef struct write_state_t { + ecs_map_t *components; + bool wildcard; +} write_state_t; - ecs_query_table_t *elems = ecs_vector_first(tables, ecs_query_table_t); - int32_t i, count = ecs_vector_count(tables); - for (i = 0; i < count; i ++) { - match_table(world, subquery, elems[i].hdr.table); +static +int32_t get_write_state( + ecs_map_t *write_state, + ecs_entity_t component) +{ + int32_t *ptr = ecs_map_get(write_state, int32_t, component); + if (ptr) { + return *ptr; + } else { + return 0; } +} - elems = ecs_vector_first(empty, ecs_query_table_t); - count = ecs_vector_count(empty); - for (i = 0; i < count; i ++) { - match_table(world, subquery, elems[i].hdr.table); +static +void set_write_state( + write_state_t *write_state, + ecs_entity_t component, + int32_t value) +{ + if (component == EcsWildcard) { + ecs_assert(value == WriteToStage, ECS_INTERNAL_ERROR, NULL); + write_state->wildcard = true; + } else { + ecs_map_set(write_state->components, component, &value); } } static -void notify_subqueries( - ecs_world_t *world, - ecs_query_t *query, - ecs_query_event_t *event) +void reset_write_state( + write_state_t *write_state) { - if (query->subqueries) { - ecs_query_t **queries = ecs_vector_first(query->subqueries, ecs_query_t*); - int32_t i, count = ecs_vector_count(query->subqueries); + ecs_map_clear(write_state->components); + write_state->wildcard = false; +} - ecs_query_event_t sub_event = *event; - sub_event.parent_query = query; +static +int32_t get_any_write_state( + write_state_t *write_state) +{ + if (write_state->wildcard) { + return WriteToStage; + } - for (i = 0; i < count; i ++) { - ecs_query_t *sub = queries[i]; - flecs_query_notify(world, sub, &sub_event); + ecs_map_iter_t it = ecs_map_iter(write_state->components); + int32_t *elem; + while ((elem = ecs_map_next(&it, int32_t, NULL))) { + if (*elem == WriteToStage) { + return WriteToStage; } } + + return 0; } static -void resolve_cascade_subject_for_table( - ecs_world_t *world, - ecs_query_t *query, - const ecs_table_t *table, - ecs_type_t table_type, - ecs_query_table_match_t *table_data) +bool check_term_component( + ecs_term_t *term, + bool is_active, + ecs_entity_t component, + write_state_t *write_state) { - int32_t term_index = query->cascade_by - 1; - ecs_term_t *term = &query->filter.terms[term_index]; + int32_t state = get_write_state(write_state->components, component); - ecs_assert(table_data->references != 0, ECS_INTERNAL_ERROR, NULL); + ecs_term_id_t *subj = &term->subj; - /* Obtain reference index */ - int32_t *column_indices = table_data->columns; - int32_t ref_index = -column_indices[term_index] - 1; + if ((subj->set.mask & EcsSelf) && subj->entity == EcsThis && term->oper != EcsNot) { + switch(term->inout) { + case EcsInOutFilter: + /* Ignore terms that aren't read/written */ + break; + case EcsInOutDefault: + case EcsInOut: + case EcsIn: + if (state == WriteToStage || write_state->wildcard) { + return true; + } + // fall through + case EcsOut: + if (is_active && term->inout != EcsIn) { + set_write_state(write_state, component, WriteToMain); + } + }; - /* Obtain pointer to the reference data */ - ecs_ref_t *references = table_data->references; + } else if (!subj->entity || term->oper == EcsNot) { + bool needs_merge = false; - /* Find source for component */ - ecs_entity_t subject; - ecs_type_match(world, table, table_type, 0, term->id, - term->subj.set.relation, 1, 0, &subject, NULL, NULL); + switch(term->inout) { + case EcsInOutDefault: + case EcsIn: + case EcsInOut: + if (state == WriteToStage) { + needs_merge = true; + } + if (component == EcsWildcard) { + if (get_any_write_state(write_state) == WriteToStage) { + needs_merge = true; + } + } + break; + default: + break; + }; - /* If container was found, update the reference */ - if (subject) { - ecs_ref_t *ref = &references[ref_index]; - ecs_assert(ref->component == term->id, ECS_INTERNAL_ERROR, NULL); + switch(term->inout) { + case EcsInOutDefault: + if ((!(subj->set.mask & EcsSelf) || (subj->entity != EcsThis)) && (subj->set.mask != EcsNothing)) { + /* Default inout behavior is [inout] for This terms, and [in] + * for terms that match other entities */ + break; + } + // fall through + case EcsInOut: + case EcsOut: + if (is_active) { + set_write_state(write_state, component, WriteToStage); + } + break; + default: + break; + }; - references[ref_index].entity = ecs_get_alive(world, subject); - table_data->subjects[term_index] = subject; - ecs_get_ref_w_id(world, ref, subject, term->id); - } else { - references[ref_index].entity = 0; - table_data->subjects[term_index] = 0; + if (needs_merge) { + return true; + } } - if (ecs_table_count(table)) { - /* The subject (or depth of the subject) may have changed, so reinsert - * the node to make sure it's in the right group */ - remove_table_node(query, &table_data->node); - insert_table_node(query, &table_data->node); - } + return false; } static -void resolve_cascade_subject( - ecs_world_t *world, - ecs_query_t *query, - ecs_query_table_t *elem, - const ecs_table_t *table, - ecs_type_t table_type) +bool check_term( + ecs_term_t *term, + bool is_active, + write_state_t *write_state) { - ecs_query_table_match_t *cur; - for (cur = elem->first; cur != NULL; cur = cur->next_match) { - resolve_cascade_subject_for_table( - world, query, table, table_type, cur); - } + if (term->oper != EcsOr) { + return check_term_component( + term, is_active, term->id, write_state); + } + + return false; } -/* Remove table */ static -void remove_table( - ecs_poly_t *poly, - void *ptr) +bool check_terms( + ecs_filter_t *filter, + bool is_active, + write_state_t *ws) { - ecs_poly_assert(poly, ecs_query_t); - - ecs_query_t *query = poly; - ecs_query_table_t *elem = ptr; - ecs_query_table_match_t *cur, *next; - - for (cur = elem->first; cur != NULL; cur = next) { - ecs_os_free(cur->columns); - ecs_os_free(cur->ids); - ecs_os_free(cur->subjects); - ecs_os_free(cur->sizes); - ecs_os_free(cur->references); - ecs_os_free(cur->sparse_columns); - ecs_os_free(cur->bitset_columns); + bool needs_merge = false; + ecs_term_t *terms = filter->terms; + int32_t t, term_count = filter->term_count; - if (!elem->hdr.empty) { - remove_table_node(query, &cur->node); + /* Check This terms first. This way if a term indicating writing to a stage + * was added before the term, it won't cause merging. */ + for (t = 0; t < term_count; t ++) { + ecs_term_t *term = &terms[t]; + if (term->subj.entity == EcsThis) { + needs_merge |= check_term(term, is_active, ws); } - - next = cur->next_match; - - ecs_os_free(cur); } - ecs_os_free(elem->monitor); - - elem->first = NULL; -} + /* Now check staged terms */ + for (t = 0; t < term_count; t ++) { + ecs_term_t *term = &terms[t]; + if (term->subj.entity != EcsThis) { + needs_merge |= check_term(term, is_active, ws); + } + } -static -void unmatch_table( - ecs_query_t *query, - ecs_table_t *table) -{ - ecs_table_cache_remove(&query->cache, ecs_query_table_t, table); + return needs_merge; } static -void rematch_table( +bool build_pipeline( ecs_world_t *world, - ecs_query_t *query, - ecs_table_t *table) + ecs_entity_t pipeline, + EcsPipelineQuery *pq) { - ecs_query_table_t *match = ecs_table_cache_get( - &query->cache, ecs_query_table_t, table); + (void)pipeline; - if (flecs_query_match(world, table, query)) { - /* If the table matches, and it is not currently matched, add */ - if (match == NULL) { - add_table(world, query, table); + ecs_query_iter(world, pq->query); - /* If table still matches and has cascade column, reevaluate the - * sources of references. This may have changed in case - * components were added/removed to container entities */ - } else if (query->cascade_by) { - resolve_cascade_subject(world, query, match, table, table->type); + if (pq->match_count == pq->query->match_count) { + /* No need to rebuild the pipeline */ + return false; + } - /* If query has optional columns, it is possible that a column that - * previously had data no longer has data, or vice versa. Do a full - * rematch to make sure data is consistent. */ - } else if (query->flags & EcsQueryHasOptional) { - unmatch_table(query, table); - if (!(query->flags & EcsQueryIsSubquery)) { - flecs_table_notify(world, table, &(ecs_table_event_t){ - .kind = EcsTableQueryUnmatch, - .query = query - }); - } - add_table(world, query, table); - } - } else { - /* Table no longer matches, remove */ - if (match != NULL) { - unmatch_table(query, table); - if (!(query->flags & EcsQueryIsSubquery)) { - flecs_table_notify(world, table, &(ecs_table_event_t){ - .kind = EcsTableQueryUnmatch, - .query = query - }); - } - notify_subqueries(world, query, &(ecs_query_event_t){ - .kind = EcsQueryTableUnmatch, - .table = table - }); - } + world->stats.pipeline_build_count_total ++; + pq->rebuild_count ++; + + write_state_t ws = { + .components = ecs_map_new(int32_t, ECS_HI_COMPONENT_ID), + .wildcard = false + }; + + ecs_pipeline_op_t *op = NULL; + ecs_vector_t *ops = NULL; + ecs_query_t *query = pq->build_query; + + if (pq->ops) { + ecs_vector_free(pq->ops); } -} -static -bool satisfy_constraints( - ecs_world_t *world, - const ecs_filter_t *filter) -{ - ecs_term_t *terms = filter->terms; - int32_t i, count = filter->term_count; + bool multi_threaded = false; + bool no_staging = false; + bool first = true; - for (i = 0; i < count; i ++) { - ecs_term_t *term = &terms[i]; - ecs_term_id_t *subj = &term->subj; - ecs_oper_kind_t oper = term->oper; + /* Iterate systems in pipeline, add ops for running / merging */ + ecs_iter_t it = ecs_query_iter(world, query); + while (ecs_query_next(&it)) { + EcsSystem *sys = ecs_term(&it, EcsSystem, 1); - if (oper == EcsOptional) { - continue; - } + int i; + for (i = 0; i < it.count; i ++) { + ecs_query_t *q = sys[i].query; + if (!q) { + continue; + } - if (subj->entity != EcsThis && subj->set.mask & EcsSelf) { - ecs_type_t type = ecs_get_type(world, subj->entity); + bool needs_merge = false; + bool is_active = !ecs_has_id( + world, it.entities[i], EcsInactive); + needs_merge = check_terms(&q->filter, is_active, &ws); - if (ecs_type_has_id(world, type, term->id, false)) { - if (oper == EcsNot) { - return false; + if (is_active) { + if (first) { + multi_threaded = sys[i].multi_threaded; + no_staging = sys[i].no_staging; + first = false; } - } else { - if (oper != EcsNot) { - return false; + + if (sys[i].multi_threaded != multi_threaded) { + needs_merge = true; + multi_threaded = sys[i].multi_threaded; + } + if (sys[i].no_staging != no_staging) { + needs_merge = true; + no_staging = sys[i].no_staging; } } - } - } - return true; -} + if (needs_merge) { + /* After merge all components will be merged, so reset state */ + reset_write_state(&ws); + op = NULL; -/* Rematch system with tables after a change happened to a watched entity */ -static -void rematch_tables( - ecs_world_t *world, - ecs_query_t *query, - ecs_query_t *parent_query) -{ - if (parent_query) { - ecs_query_table_t *tables = ecs_vector_first( - parent_query->cache.tables, ecs_query_table_t); - int32_t i, count = ecs_vector_count(parent_query->cache.tables); - for (i = 0; i < count; i ++) { - rematch_table(world, query, tables[i].hdr.table); - } + /* Re-evaluate columns to set write flags if system is active. + * If system is inactive, it can't write anything and so it + * should not insert unnecessary merges. */ + needs_merge = false; + if (is_active) { + needs_merge = check_terms(&q->filter, true, &ws); + } - tables = ecs_vector_first( - parent_query->cache.empty_tables, ecs_query_table_t); - count = ecs_vector_count(parent_query->cache.empty_tables); - for (i = 0; i < count; i ++) { - rematch_table(world, query, tables[i].hdr.table); - } - } else { - ecs_sparse_t *tables = world->store.tables; - int32_t i, count = flecs_sparse_count(tables); + /* The component states were just reset, so if we conclude that + * another merge is needed something is wrong. */ + ecs_assert(needs_merge == false, ECS_INTERNAL_ERROR, NULL); + } - for (i = 0; i < count; i ++) { - /* Is the system currently matched with the table? */ - ecs_table_t *table = flecs_sparse_get_dense(tables, ecs_table_t, i); - rematch_table(world, query, table); + if (!op) { + op = ecs_vector_add(&ops, ecs_pipeline_op_t); + op->count = 0; + op->multi_threaded = false; + op->no_staging = false; + } + + /* Don't increase count for inactive systems, as they are ignored by + * the query used to run the pipeline. */ + if (is_active) { + if (!op->count) { + op->multi_threaded = multi_threaded; + op->no_staging = no_staging; + } + op->count ++; + } } } - /* Enable/disable system if constraints are (not) met. If the system is - * already dis/enabled this operation has no side effects. */ - query->constraints_satisfied = satisfy_constraints(world, &query->filter); -} - -static -void remove_subquery( - ecs_query_t *parent, - ecs_query_t *sub) -{ - ecs_assert(parent != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(sub != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(parent->subqueries != NULL, ECS_INTERNAL_ERROR, NULL); - - int32_t i, count = ecs_vector_count(parent->subqueries); - ecs_query_t **sq = ecs_vector_first(parent->subqueries, ecs_query_t*); + ecs_map_free(ws.components); - for (i = 0; i < count; i ++) { - if (sq[i] == sub) { - break; - } - } + /* Find the system ran last this frame (helps workers reset iter) */ + ecs_entity_t last_system = 0; + op = ecs_vector_first(ops, ecs_pipeline_op_t); + int32_t i, ran_since_merge = 0, op_index = 0; - ecs_vector_remove(parent->subqueries, ecs_query_t*, i); -} + /* Add schedule to debug tracing */ + ecs_dbg("#[green]pipeline#[reset] rebuild:"); + ecs_log_push(); -/* -- Private API -- */ + ecs_dbg("#[green]schedule#[reset]: threading: %d, staging: %d:", + op->multi_threaded, !op->no_staging); + ecs_log_push(); + + it = ecs_query_iter(world, pq->query); + while (ecs_query_next(&it)) { + EcsSystem *sys = ecs_term(&it, EcsSystem, 1); + for (i = 0; i < it.count; i ++) { +#ifndef NDEBUG + char *path = ecs_get_fullpath(world, it.entities[i]); + ecs_dbg("#[green]system#[reset] %s", path); + ecs_os_free(path); +#endif + ran_since_merge ++; + if (ran_since_merge == op[op_index].count) { + ecs_dbg("#[magenta]merge#[reset]"); + ecs_log_pop(); + ran_since_merge = 0; + op_index ++; + if (op_index < ecs_vector_count(ops)) { + ecs_dbg("#[green]schedule#[reset]: threading: %d, staging: %d:", + op[op_index].multi_threaded, !op[op_index].no_staging); + } + ecs_log_push(); + } -void flecs_query_notify( - ecs_world_t *world, - ecs_query_t *query, - ecs_query_event_t *event) -{ - bool notify = true; + if (sys[i].last_frame == (world->stats.frame_count_total + 1)) { + last_system = it.entities[i]; - switch(event->kind) { - case EcsQueryTableMatch: - /* Creation of new table */ - if (match_table(world, query, event->table)) { - if (query->subqueries) { - notify_subqueries(world, query, event); + /* Can't break from loop yet. It's possible that previously + * inactive systems that ran before the last ran system are now + * active. */ } } - notify = false; - break; - case EcsQueryTableUnmatch: - /* Deletion of table */ - unmatch_table(query, event->table); - break; - case EcsQueryTableRematch: - /* Rematch tables of query */ - rematch_tables(world, query, event->parent_query); - break; - case EcsQueryTableEmpty: - /* Table is empty, deactivate */ - update_table(query, event->table, true); - break; - case EcsQueryTableNonEmpty: - /* Table is non-empty, activate */ - update_table(query, event->table, false); - break; - case EcsQueryOrphan: - ecs_assert(query->flags & EcsQueryIsSubquery, ECS_INTERNAL_ERROR, NULL); - query->flags |= EcsQueryIsOrphaned; - query->parent = NULL; - break; } - if (notify) { - notify_subqueries(world, query, event); - } -} + ecs_log_pop(); + ecs_log_pop(); -static -void query_order_by( - ecs_world_t *world, - ecs_query_t *query, - ecs_entity_t order_by_component, - ecs_order_by_action_t order_by) -{ - ecs_check(query != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(!(query->flags & EcsQueryIsOrphaned), ECS_INVALID_PARAMETER, NULL); - ecs_check(query->flags & EcsQueryNeedsTables, ECS_INVALID_PARAMETER, NULL); + /* Force sort of query as this could increase the match_count */ + pq->match_count = pq->query->match_count; + pq->ops = ops; + pq->last_system = last_system; + + return true; +} - query->order_by_component = order_by_component; - query->order_by = order_by; +int32_t ecs_pipeline_reset_iter( + ecs_world_t *world, + const EcsPipelineQuery *pq, + ecs_iter_t *iter_out, + ecs_pipeline_op_t **op_out, + ecs_pipeline_op_t **last_op_out) +{ + ecs_pipeline_op_t *op = ecs_vector_first(pq->ops, ecs_pipeline_op_t); + int32_t i, ran_since_merge = 0, op_index = 0; - ecs_vector_free(query->table_slices); - query->table_slices = NULL; + if (!pq->last_system) { + /* It's possible that all systems that were ran were removed entirely + * from the pipeline (they could have been deleted or disabled). In that + * case (which should be very rare) the pipeline can't make assumptions + * about where to continue, so end frame. */ + return -1; + } - sort_tables(world, query); + /* Move iterator to last ran system */ + *iter_out = ecs_query_iter(world, pq->query); + while (ecs_query_next(iter_out)) { + for (i = 0; i < iter_out->count; i ++) { + ran_since_merge ++; + if (ran_since_merge == op[op_index].count) { + ran_since_merge = 0; + op_index ++; + } - if (!query->table_slices) { - build_sorted_tables(query); + if (iter_out->entities[i] == pq->last_system) { + *op_out = &op[op_index]; + *last_op_out = ecs_vector_last(pq->ops, ecs_pipeline_op_t); + return i; + } + } } -error: - return; -} -static -void query_group_by( - ecs_query_t *query, - ecs_entity_t sort_component, - ecs_group_by_action_t group_by) -{ - /* Cannot change grouping once a query has been created */ - ecs_check(query->group_by_id == 0, ECS_INVALID_OPERATION, NULL); - ecs_check(query->group_by == 0, ECS_INVALID_OPERATION, NULL); + ecs_abort(ECS_INTERNAL_ERROR, NULL); - query->group_by_id = sort_component; - query->group_by = group_by; - query->groups = ecs_map_new(ecs_query_table_list_t, 16); -error: - return; + return -1; } -/* Implementation for iterable mixin */ -static -void query_iter_init( - const ecs_world_t *world, - const ecs_poly_t *poly, - ecs_iter_t *iter, - ecs_term_t *filter) +bool ecs_pipeline_update( + ecs_world_t *world, + ecs_entity_t pipeline, + bool start_of_frame) { - ecs_poly_assert(poly, ecs_query_t); + ecs_poly_assert(world, ecs_world_t); + ecs_assert(!world->is_readonly, ECS_INVALID_OPERATION, NULL); + ecs_assert(pipeline != 0, ECS_INTERNAL_ERROR, NULL); - if (filter) { - iter[1] = ecs_query_iter(world, (ecs_query_t*)poly); - iter[0] = ecs_term_chain_iter(&iter[1], filter); - } else { - iter[0] = ecs_query_iter(world, (ecs_query_t*)poly); + /* If any entity mutations happened that could have affected query matching + * notify appropriate queries so caches are up to date. This includes the + * pipeline query. */ + if (start_of_frame) { + flecs_eval_component_monitors(world); } -} -/* -- Public API -- */ + bool added = false; + EcsPipelineQuery *pq = ecs_get_mut(world, pipeline, EcsPipelineQuery, &added); + ecs_assert(added == false, ECS_INTERNAL_ERROR, NULL); + ecs_assert(pq != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(pq->query != NULL, ECS_INTERNAL_ERROR, NULL); -ecs_query_t* ecs_query_init( + return build_pipeline(world, pipeline, pq); +} + +void ecs_run_pipeline( ecs_world_t *world, - const ecs_query_desc_t *desc) + ecs_entity_t pipeline, + FLECS_FLOAT delta_time) { - ecs_query_t *result = NULL; - ecs_check(world != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_check(!world->is_fini, ECS_INVALID_OPERATION, NULL); + ecs_assert(world != NULL, ECS_INVALID_OPERATION, NULL); - result = flecs_sparse_add(world->queries, ecs_query_t); - ecs_poly_init(result, ecs_query_t); + if (!pipeline) { + pipeline = world->pipeline; + } - result->id = flecs_sparse_last_id(world->queries); + /* If the world is passed to ecs_run_pipeline, the function will take care + * of staging, so the world should not be in staged mode when called. */ + if (ecs_poly_is(world, ecs_world_t)) { + ecs_assert(!world->is_readonly, ECS_INVALID_OPERATION, NULL); - if (ecs_filter_init(world, &result->filter, &desc->filter)) { - flecs_sparse_remove(world->queries, result->id); - return NULL; + /* Forward to worker_progress. This function handles staging, threading + * and synchronization across workers. */ + ecs_workers_progress(world, pipeline, delta_time); + return; + + /* If a stage is passed, the function could be ran from a worker thread. In + * that case the main thread should manage staging, and staging should be + * enabled. */ + } else { + ecs_poly_assert(world, ecs_stage_t); } - result->world = world; - result->iterable.init = query_iter_init; - result->system = desc->system; - result->prev_match_count = -1; + ecs_stage_t *stage = flecs_stage_from_world(&world); + + const EcsPipelineQuery *pq = ecs_get(world, pipeline, EcsPipelineQuery); + ecs_assert(pq != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(pq->query != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_table_cache_init( - &result->cache, ecs_query_table_t, result, remove_table); + ecs_vector_t *ops = pq->ops; + ecs_pipeline_op_t *op = ecs_vector_first(ops, ecs_pipeline_op_t); + ecs_pipeline_op_t *op_last = ecs_vector_last(ops, ecs_pipeline_op_t); + int32_t ran_since_merge = 0; - process_signature(world, result); + int32_t stage_index = ecs_get_stage_id(stage->thread_ctx); + int32_t stage_count = ecs_get_stage_count(world); - /* Group before matching so we won't have to move tables around later */ - int32_t cascade_by = result->cascade_by; - if (cascade_by) { - query_group_by(result, result->filter.terms[cascade_by - 1].id, - group_by_cascade); - result->group_by_ctx = &result->filter.terms[cascade_by - 1]; - } + ecs_worker_begin(stage->thread_ctx); - if (desc->group_by) { - /* Can't have a cascade term and group by at the same time, as cascade - * uses the group_by mechanism */ - ecs_check(!result->cascade_by, ECS_INVALID_PARAMETER, NULL); - query_group_by(result, desc->group_by_id, desc->group_by); - result->group_by_ctx = desc->group_by_ctx; - result->group_by_ctx_free = desc->group_by_ctx_free; - } + ecs_iter_t it = ecs_query_iter(world, pq->query); + while (ecs_query_next(&it)) { + EcsSystem *sys = ecs_term(&it, EcsSystem, 1); - if (desc->parent != NULL) { - result->flags |= EcsQueryIsSubquery; - } + int32_t i; + for(i = 0; i < it.count; i ++) { + ecs_entity_t e = it.entities[i]; - /* If a system is specified, ensure that if there are any subjects in the - * filter that refer to the system, the component is added */ - if (desc->system) { - int32_t t, term_count = result->filter.term_count; - ecs_term_t *terms = result->filter.terms; + if (!stage_index) { + ecs_dbg_3("pipeline: run system %s", ecs_get_name(world, e)); + } - for (t = 0; t < term_count; t ++) { - ecs_term_t *term = &terms[t]; - if (term->subj.entity == desc->system) { - ecs_add_id(world, desc->system, term->id); + if (!stage_index || op->multi_threaded) { + ecs_stage_t *s = NULL; + if (!op->no_staging) { + s = stage; + } + + ecs_run_intern(world, s, e, &sys[i], stage_index, + stage_count, delta_time, 0, 0, NULL); } - } - } -#ifndef NDEBUG - char *filter_expr = ecs_filter_str(world, &result->filter); - ecs_dbg_1("#[green]query#[normal] [%s] created", filter_expr); - ecs_os_free(filter_expr); -#endif + sys[i].last_frame = world->stats.frame_count_total + 1; - ecs_log_push(); + ran_since_merge ++; + world->stats.systems_ran_frame ++; - if (!desc->parent) { - if (result->flags & EcsQueryNeedsTables) { - match_tables(world, result); - } else { - /* Add stub table that resolves references (if any) so everything is - * preprocessed when the query is evaluated. */ - add_table(world, result, NULL); - } - } else { - add_subquery(world, desc->parent, result); - result->parent = desc->parent; - } + if (op != op_last && ran_since_merge == op->count) { + ran_since_merge = 0; - if (desc->order_by) { - query_order_by( - world, result, desc->order_by_component, desc->order_by); + if (!stage_index) { + ecs_dbg_3("merge"); + } + + /* If the set of matched systems changed as a result of the + * merge, we have to reset the iterator and move it to our + * current position (system). If there are a lot of systems + * in the pipeline this can be an expensive operation, but + * should happen infrequently. */ + i = ecs_worker_sync(world, pq, &it, i, &op, &op_last); + sys = ecs_term(&it, EcsSystem, 1); + } + } } - result->constraints_satisfied = satisfy_constraints(world, &result->filter); + ecs_worker_end(stage->thread_ctx); +} - ecs_log_pop(); +static +void add_pipeline_tags_to_sig( + ecs_world_t *world, + ecs_term_t *terms, + ecs_type_t type) +{ + (void)world; + + int32_t i, count = ecs_vector_count(type); + ecs_entity_t *entities = ecs_vector_first(type, ecs_entity_t); - return result; -error: - if (result) { - flecs_sparse_remove(world->queries, result->id); + for (i = 0; i < count; i ++) { + terms[i] = (ecs_term_t){ + .inout = EcsIn, + .oper = EcsOr, + .pred.entity = entities[i], + .subj = { + .entity = EcsThis, + .set.mask = EcsSelf | EcsSuperSet + } + }; } - return NULL; } -void ecs_query_fini( - ecs_query_t *query) +static +ecs_query_t* build_pipeline_query( + ecs_world_t *world, + ecs_entity_t pipeline, + const char *name, + bool not_inactive) { - ecs_poly_assert(query, ecs_query_t); - ecs_world_t *world = query->world; - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + const EcsType *type_ptr = ecs_get(world, pipeline, EcsType); + ecs_assert(type_ptr != NULL, ECS_INTERNAL_ERROR, NULL); - if (query->group_by_ctx_free) { - if (query->group_by_ctx) { - query->group_by_ctx_free(query->group_by_ctx); - } - } + int32_t type_count = ecs_vector_count(type_ptr->normalized); + int32_t term_count = 1; - if ((query->flags & EcsQueryIsSubquery) && - !(query->flags & EcsQueryIsOrphaned)) - { - remove_subquery(query->parent, query); + if (not_inactive) { + term_count ++; } - notify_subqueries(world, query, &(ecs_query_event_t){ - .kind = EcsQueryOrphan - }); - - ecs_vector_each(query->cache.empty_tables, ecs_query_table_t, table, { - if (!(query->flags & EcsQueryIsSubquery)) { - flecs_table_notify(world, table->hdr.table, &(ecs_table_event_t){ - .kind = EcsTableQueryUnmatch, - .query = query - }); - } - }); + ecs_term_t *terms = ecs_os_malloc( + (type_count + term_count) * ECS_SIZEOF(ecs_term_t)); - ecs_vector_each(query->cache.tables, ecs_query_table_t, table, { - if (!(query->flags & EcsQueryIsSubquery)) { - flecs_table_notify(world, table->hdr.table, &(ecs_table_event_t){ - .kind = EcsTableQueryUnmatch, - .query = query - }); + terms[0] = (ecs_term_t){ + .inout = EcsIn, + .oper = EcsAnd, + .pred.entity = ecs_id(EcsSystem), + .subj = { + .entity = EcsThis, + .set.mask = EcsSelf | EcsSuperSet } - }); + }; - ecs_table_cache_fini(&query->cache); - ecs_map_free(query->groups); + if (not_inactive) { + terms[1] = (ecs_term_t){ + .inout = EcsIn, + .oper = EcsNot, + .pred.entity = EcsInactive, + .subj = { + .entity = EcsThis, + .set.mask = EcsSelf | EcsSuperSet + } + }; + } - ecs_vector_free(query->subqueries); - ecs_vector_free(query->table_slices); - ecs_filter_fini(&query->filter); + add_pipeline_tags_to_sig(world, &terms[term_count], type_ptr->normalized); - ecs_poly_fini(query, ecs_query_t); - - /* Remove query from storage */ - flecs_sparse_remove(world->queries, query->id); -error: - return; -} + ecs_query_t *result = ecs_query_init(world, &(ecs_query_desc_t){ + .filter = { + .name = name, + .terms_buffer = terms, + .terms_buffer_count = term_count + type_count + }, + .order_by = compare_entity, + .group_by = group_by_phase, + .group_by_id = pipeline + }); -const ecs_filter_t* ecs_query_get_filter( - ecs_query_t *query) -{ - ecs_poly_assert(query, ecs_query_t); - return &query->filter; + ecs_assert(result != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_os_free(terms); + + return result; } -/* Create query iterator */ -ecs_iter_t ecs_query_iter_page( - const ecs_world_t *stage, - ecs_query_t *query, - int32_t offset, - int32_t limit) +static +void OnUpdatePipeline( + ecs_iter_t *it) { - ecs_poly_assert(query, ecs_query_t); - ecs_check(!(query->flags & EcsQueryIsOrphaned), - ECS_INVALID_PARAMETER, NULL); + ecs_world_t *world = it->world; + ecs_entity_t *entities = it->entities; - ecs_world_t *world = (ecs_world_t*)ecs_get_world(stage); + int32_t i; + for (i = it->count - 1; i >= 0; i --) { + ecs_entity_t pipeline = entities[i]; + + ecs_trace("#[green]pipeline#[reset] %s created", + ecs_get_name(world, pipeline)); + ecs_log_push(); - sort_tables(world, query); + /* Build signature for pipeline query that matches EcsSystems, has the + * pipeline phases as OR columns, and ignores systems with EcsInactive. + * Note that EcsDisabled is automatically ignored + * by the regular query matching */ + ecs_query_t *query = build_pipeline_query( + world, pipeline, "BuiltinPipelineQuery", true); + ecs_assert(query != NULL, ECS_INTERNAL_ERROR, NULL); - if (!world->is_readonly && query->flags & EcsQueryHasRefs) { - flecs_eval_component_monitors(world); - } + /* Build signature for pipeline build query. The build query includes + * systems that are inactive, as an inactive system may become active as + * a result of another system, and as a result the correct merge + * operations need to be put in place. */ + ecs_query_t *build_query = build_pipeline_query( + world, pipeline, "BuiltinPipelineBuildQuery", false); + ecs_assert(build_query != NULL, ECS_INTERNAL_ERROR, NULL); - tables_reset_dirty(query); + bool added = false; + EcsPipelineQuery *pq = ecs_get_mut( + world, pipeline, EcsPipelineQuery, &added); + ecs_assert(pq != NULL, ECS_INTERNAL_ERROR, NULL); - int32_t table_count; - if (query->table_slices) { - table_count = ecs_vector_count(query->table_slices); - } else { - table_count = ecs_vector_count(query->cache.tables); - } + if (added) { + /* Should not modify pipeline after it has been used */ + ecs_assert(pq->ops == NULL, ECS_INVALID_OPERATION, NULL); - ecs_query_iter_t it = { - .query = query, - .node = query->list.first, - .page_iter = { - .offset = offset, - .limit = limit, - .remaining = limit + if (pq->query) { + ecs_query_fini(pq->query); + } + if (pq->build_query) { + ecs_query_fini(pq->build_query); + } } - }; - - if (query->order_by && query->list.count) { - it.node = ecs_vector_first(query->table_slices, ecs_query_table_node_t); - } - - return (ecs_iter_t){ - .real_world = world, - .world = (ecs_world_t*)stage, - .terms = query->filter.terms, - .term_count = query->filter.term_count_actual, - .table_count = table_count, - .is_filter = query->filter.filter, - .is_instanced = query->filter.instanced, - .iter.query = it, - .next = ecs_query_next, - }; -error: - return (ecs_iter_t){ 0 }; -} - -ecs_iter_t ecs_query_iter( - const ecs_world_t *world, - ecs_query_t *query) -{ - ecs_poly_assert(query, ecs_query_t); - return ecs_query_iter_page(world, query, 0, 0); -} - -static -int ecs_page_iter_next( - ecs_page_iter_t *it, - ecs_page_cursor_t *cur) -{ - int32_t offset = it->offset; - int32_t limit = it->limit; - if (!(offset || limit)) { - return cur->count == 0; - } - - int32_t count = cur->count; - int32_t remaining = it->remaining; - if (offset) { - if (offset > count) { - /* No entities to iterate in current table */ - it->offset -= count; - return 1; - } else { - cur->first += offset; - count = cur->count -= offset; - it->offset = 0; - } - } + pq->query = query; + pq->build_query = build_query; + pq->match_count = -1; + pq->ops = NULL; + pq->last_system = 0; - if (remaining) { - if (remaining > count) { - it->remaining -= count; - } else { - count = cur->count = remaining; - it->remaining = 0; - } - } else if (limit) { - /* Limit hit: no more entities left to iterate */ - return -1; + ecs_log_pop(); } - - return count == 0; } -static -int find_smallest_column( - ecs_table_t *table, - ecs_query_table_match_t *table_data, - ecs_vector_t *sparse_columns) +/* -- Public API -- */ + +bool ecs_progress( + ecs_world_t *world, + FLECS_FLOAT user_delta_time) { - flecs_sparse_column_t *sparse_column_array = - ecs_vector_first(sparse_columns, flecs_sparse_column_t); - int32_t i, count = ecs_vector_count(sparse_columns); - int32_t min = INT_MAX, index = 0; + float delta_time = ecs_frame_begin(world, user_delta_time); - for (i = 0; i < count; i ++) { - /* The array with sparse queries for the matched table */ - flecs_sparse_column_t *sparse_column = &sparse_column_array[i]; + ecs_dbg_3("#[normal]begin progress(dt = %.2f)", (double)delta_time); - /* Pointer to the switch column struct of the table */ - ecs_sw_column_t *sc = sparse_column->sw_column; + ecs_run_pipeline(world, 0, delta_time); - /* If the sparse column pointer hadn't been retrieved yet, do it now */ - if (!sc) { - /* Get the table column index from the signature column index */ - int32_t table_column_index = table_data->columns[ - sparse_column->signature_column_index]; + ecs_dbg_3("#[normal]end progress"); - /* Translate the table column index to switch column index */ - table_column_index -= table->sw_column_offset; - ecs_assert(table_column_index >= 1, ECS_INTERNAL_ERROR, NULL); + ecs_frame_end(world); - /* Get the sparse column */ - ecs_data_t *data = &table->storage; - sc = sparse_column->sw_column = - &data->sw_columns[table_column_index - 1]; - } + return !world->should_quit; +} - /* Find the smallest column */ - ecs_switch_t *sw = sc->data; - int32_t case_count = flecs_switch_case_count(sw, sparse_column->sw_case); - if (case_count < min) { - min = case_count; - index = i + 1; - } - } +void ecs_set_time_scale( + ecs_world_t *world, + FLECS_FLOAT scale) +{ + world->stats.time_scale = scale; +} - return index; +void ecs_reset_clock( + ecs_world_t *world) +{ + world->stats.world_time_total = 0; + world->stats.world_time_total_raw = 0; } -static -int sparse_column_next( - ecs_table_t *table, - ecs_query_table_match_t *matched_table, - ecs_vector_t *sparse_columns, - ecs_query_iter_t *iter, - ecs_page_cursor_t *cur, - bool filter) +void ecs_deactivate_systems( + ecs_world_t *world) { - bool first_iteration = false; - int32_t sparse_smallest; + ecs_assert(!world->is_readonly, ECS_INVALID_WHILE_ITERATING, NULL); - if (!(sparse_smallest = iter->sparse_smallest)) { - sparse_smallest = iter->sparse_smallest = find_smallest_column( - table, matched_table, sparse_columns); - first_iteration = true; - } + ecs_entity_t pipeline = world->pipeline; + const EcsPipelineQuery *pq = ecs_get( world, pipeline, EcsPipelineQuery); + ecs_assert(pq != NULL, ECS_INTERNAL_ERROR, NULL); - sparse_smallest -= 1; + /* Iterate over all systems, add EcsInvalid tag if queries aren't matched + * with any tables */ + ecs_iter_t it = ecs_query_iter(world, pq->build_query); - flecs_sparse_column_t *columns = ecs_vector_first( - sparse_columns, flecs_sparse_column_t); - flecs_sparse_column_t *column = &columns[sparse_smallest]; - ecs_switch_t *sw, *sw_smallest = column->sw_column->data; - ecs_entity_t case_smallest = column->sw_case; + /* Make sure that we defer adding the inactive tags until after iterating + * the query */ + flecs_defer_none(world, &world->stage); - /* Find next entity to iterate in sparse column */ - int32_t first, sparse_first = iter->sparse_first; + while( ecs_query_next(&it)) { + EcsSystem *sys = ecs_term(&it, EcsSystem, 1); - if (!filter) { - if (first_iteration) { - first = flecs_switch_first(sw_smallest, case_smallest); - } else { - first = flecs_switch_next(sw_smallest, sparse_first); - } - } else { - int32_t cur_first = cur->first, cur_count = cur->count; - first = cur_first; - while (flecs_switch_get(sw_smallest, first) != case_smallest) { - first ++; - if (first >= (cur_first + cur_count)) { - first = -1; - break; + int32_t i; + for (i = 0; i < it.count; i ++) { + ecs_query_t *query = sys[i].query; + if (query) { + if (!ecs_query_table_count(query)) { + ecs_add_id(world, it.entities[i], EcsInactive); + } } } } - if (first == -1) { - goto done; - } - - /* Check if entity matches with other sparse columns, if any */ - int32_t i, count = ecs_vector_count(sparse_columns); - do { - for (i = 0; i < count; i ++) { - if (i == sparse_smallest) { - /* Already validated this one */ - continue; - } - - column = &columns[i]; - sw = column->sw_column->data; + flecs_defer_flush(world, &world->stage); +} - if (flecs_switch_get(sw, first) != column->sw_case) { - first = flecs_switch_next(sw_smallest, first); - if (first == -1) { - goto done; - } - } - } - } while (i != count); +void ecs_set_pipeline( + ecs_world_t *world, + ecs_entity_t pipeline) +{ + ecs_poly_assert(world, ecs_world_t); + ecs_check( ecs_get(world, pipeline, EcsPipelineQuery) != NULL, + ECS_INVALID_PARAMETER, NULL); - cur->first = iter->sparse_first = first; - cur->count = 1; + world->pipeline = pipeline; +error: + return; +} +ecs_entity_t ecs_get_pipeline( + const ecs_world_t *world) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + world = ecs_get_world(world); + return world->pipeline; +error: return 0; -done: - /* Iterated all elements in the sparse list, we should move to the - * next matched table. */ - iter->sparse_smallest = 0; - iter->sparse_first = 0; - - return -1; } -#define BS_MAX ((uint64_t)0xFFFFFFFFFFFFFFFF) +/* -- Module implementation -- */ static -int bitset_column_next( - ecs_table_t *table, - ecs_vector_t *bitset_columns, - ecs_query_iter_t *iter, - ecs_page_cursor_t *cur) +void FlecsPipelineFini( + ecs_world_t *world, + void *ctx) { - /* Precomputed single-bit test */ - static const uint64_t bitmask[64] = { - (uint64_t)1 << 0, (uint64_t)1 << 1, (uint64_t)1 << 2, (uint64_t)1 << 3, - (uint64_t)1 << 4, (uint64_t)1 << 5, (uint64_t)1 << 6, (uint64_t)1 << 7, - (uint64_t)1 << 8, (uint64_t)1 << 9, (uint64_t)1 << 10, (uint64_t)1 << 11, - (uint64_t)1 << 12, (uint64_t)1 << 13, (uint64_t)1 << 14, (uint64_t)1 << 15, - (uint64_t)1 << 16, (uint64_t)1 << 17, (uint64_t)1 << 18, (uint64_t)1 << 19, - (uint64_t)1 << 20, (uint64_t)1 << 21, (uint64_t)1 << 22, (uint64_t)1 << 23, - (uint64_t)1 << 24, (uint64_t)1 << 25, (uint64_t)1 << 26, (uint64_t)1 << 27, - (uint64_t)1 << 28, (uint64_t)1 << 29, (uint64_t)1 << 30, (uint64_t)1 << 31, - (uint64_t)1 << 32, (uint64_t)1 << 33, (uint64_t)1 << 34, (uint64_t)1 << 35, - (uint64_t)1 << 36, (uint64_t)1 << 37, (uint64_t)1 << 38, (uint64_t)1 << 39, - (uint64_t)1 << 40, (uint64_t)1 << 41, (uint64_t)1 << 42, (uint64_t)1 << 43, - (uint64_t)1 << 44, (uint64_t)1 << 45, (uint64_t)1 << 46, (uint64_t)1 << 47, - (uint64_t)1 << 48, (uint64_t)1 << 49, (uint64_t)1 << 50, (uint64_t)1 << 51, - (uint64_t)1 << 52, (uint64_t)1 << 53, (uint64_t)1 << 54, (uint64_t)1 << 55, - (uint64_t)1 << 56, (uint64_t)1 << 57, (uint64_t)1 << 58, (uint64_t)1 << 59, - (uint64_t)1 << 60, (uint64_t)1 << 61, (uint64_t)1 << 62, (uint64_t)1 << 63 - }; - - /* Precomputed test to verify if remainder of block is set (or not) */ - static const uint64_t bitmask_remain[64] = { - BS_MAX, BS_MAX - (BS_MAX >> 63), BS_MAX - (BS_MAX >> 62), - BS_MAX - (BS_MAX >> 61), BS_MAX - (BS_MAX >> 60), BS_MAX - (BS_MAX >> 59), - BS_MAX - (BS_MAX >> 58), BS_MAX - (BS_MAX >> 57), BS_MAX - (BS_MAX >> 56), - BS_MAX - (BS_MAX >> 55), BS_MAX - (BS_MAX >> 54), BS_MAX - (BS_MAX >> 53), - BS_MAX - (BS_MAX >> 52), BS_MAX - (BS_MAX >> 51), BS_MAX - (BS_MAX >> 50), - BS_MAX - (BS_MAX >> 49), BS_MAX - (BS_MAX >> 48), BS_MAX - (BS_MAX >> 47), - BS_MAX - (BS_MAX >> 46), BS_MAX - (BS_MAX >> 45), BS_MAX - (BS_MAX >> 44), - BS_MAX - (BS_MAX >> 43), BS_MAX - (BS_MAX >> 42), BS_MAX - (BS_MAX >> 41), - BS_MAX - (BS_MAX >> 40), BS_MAX - (BS_MAX >> 39), BS_MAX - (BS_MAX >> 38), - BS_MAX - (BS_MAX >> 37), BS_MAX - (BS_MAX >> 36), BS_MAX - (BS_MAX >> 35), - BS_MAX - (BS_MAX >> 34), BS_MAX - (BS_MAX >> 33), BS_MAX - (BS_MAX >> 32), - BS_MAX - (BS_MAX >> 31), BS_MAX - (BS_MAX >> 30), BS_MAX - (BS_MAX >> 29), - BS_MAX - (BS_MAX >> 28), BS_MAX - (BS_MAX >> 27), BS_MAX - (BS_MAX >> 26), - BS_MAX - (BS_MAX >> 25), BS_MAX - (BS_MAX >> 24), BS_MAX - (BS_MAX >> 23), - BS_MAX - (BS_MAX >> 22), BS_MAX - (BS_MAX >> 21), BS_MAX - (BS_MAX >> 20), - BS_MAX - (BS_MAX >> 19), BS_MAX - (BS_MAX >> 18), BS_MAX - (BS_MAX >> 17), - BS_MAX - (BS_MAX >> 16), BS_MAX - (BS_MAX >> 15), BS_MAX - (BS_MAX >> 14), - BS_MAX - (BS_MAX >> 13), BS_MAX - (BS_MAX >> 12), BS_MAX - (BS_MAX >> 11), - BS_MAX - (BS_MAX >> 10), BS_MAX - (BS_MAX >> 9), BS_MAX - (BS_MAX >> 8), - BS_MAX - (BS_MAX >> 7), BS_MAX - (BS_MAX >> 6), BS_MAX - (BS_MAX >> 5), - BS_MAX - (BS_MAX >> 4), BS_MAX - (BS_MAX >> 3), BS_MAX - (BS_MAX >> 2), - BS_MAX - (BS_MAX >> 1) - }; + (void)ctx; + if (ecs_get_stage_count(world)) { + ecs_set_threads(world, 0); + } +} - int32_t i, count = ecs_vector_count(bitset_columns); - flecs_bitset_column_t *columns = ecs_vector_first( - bitset_columns, flecs_bitset_column_t); - int32_t bs_offset = table->bs_column_offset; +void FlecsPipelineImport( + ecs_world_t *world) +{ + ECS_MODULE(world, FlecsPipeline); - int32_t first = iter->bitset_first; - int32_t last = 0; + ECS_IMPORT(world, FlecsSystem); - for (i = 0; i < count; i ++) { - flecs_bitset_column_t *column = &columns[i]; - ecs_bs_column_t *bs_column = columns[i].bs_column; + ecs_set_name_prefix(world, "Ecs"); - if (!bs_column) { - int32_t index = column->column_index; - ecs_assert((index - bs_offset >= 0), ECS_INTERNAL_ERROR, NULL); - bs_column = &table->storage.bs_columns[index - bs_offset]; - columns[i].bs_column = bs_column; - } - - ecs_bitset_t *bs = &bs_column->data; - int32_t bs_elem_count = bs->count; - int32_t bs_block = first >> 6; - int32_t bs_block_count = ((bs_elem_count - 1) >> 6) + 1; + flecs_bootstrap_tag(world, EcsPipeline); + flecs_bootstrap_component(world, EcsPipelineQuery); - if (bs_block >= bs_block_count) { - goto done; - } + /* Phases of the builtin pipeline are regular entities. Names are set so + * they can be resolved by type expressions. */ + flecs_bootstrap_tag(world, EcsPreFrame); + flecs_bootstrap_tag(world, EcsOnLoad); + flecs_bootstrap_tag(world, EcsPostLoad); + flecs_bootstrap_tag(world, EcsPreUpdate); + flecs_bootstrap_tag(world, EcsOnUpdate); + flecs_bootstrap_tag(world, EcsOnValidate); + flecs_bootstrap_tag(world, EcsPostUpdate); + flecs_bootstrap_tag(world, EcsPreStore); + flecs_bootstrap_tag(world, EcsOnStore); + flecs_bootstrap_tag(world, EcsPostFrame); - uint64_t *data = bs->data; - int32_t bs_start = first & 0x3F; + /* Set ctor and dtor for PipelineQuery */ + ecs_set(world, ecs_id(EcsPipelineQuery), EcsComponentLifecycle, { + .ctor = ecs_default_ctor, + .dtor = ecs_dtor(EcsPipelineQuery) + }); - /* Step 1: find the first non-empty block */ - uint64_t v = data[bs_block]; - uint64_t remain = bitmask_remain[bs_start]; - while (!(v & remain)) { - /* If no elements are remaining, move to next block */ - if ((++bs_block) >= bs_block_count) { - /* No non-empty blocks left */ - goto done; - } + /* When the Pipeline tag is added a pipeline will be created */ + ecs_observer_init(world, &(ecs_observer_desc_t) { + .entity.name = "OnUpdatePipeline", + .filter.terms = { + { .id = EcsPipeline }, + { .id = ecs_id(EcsType) } + }, + .events = { EcsOnSet }, + .callback = OnUpdatePipeline + }); - bs_start = 0; - remain = BS_MAX; /* Test the full block */ - v = data[bs_block]; - } + /* Create the builtin pipeline */ + world->pipeline = ecs_type_init(world, &(ecs_type_desc_t){ + .entity = { + .name = "BuiltinPipeline", + .add = {EcsPipeline} + }, + .ids = { + EcsPreFrame, EcsOnLoad, EcsPostLoad, EcsPreUpdate, EcsOnUpdate, + EcsOnValidate, EcsPostUpdate, EcsPreStore, EcsOnStore, EcsPostFrame + } + }); - /* Step 2: find the first non-empty element in the block */ - while (!(v & bitmask[bs_start])) { - bs_start ++; + /* Cleanup thread administration when world is destroyed */ + ecs_atfini(world, FlecsPipelineFini, NULL); +} - /* Block was not empty, so bs_start must be smaller than 64 */ - ecs_assert(bs_start < 64, ECS_INTERNAL_ERROR, NULL); - } - - /* Step 3: Find number of contiguous enabled elements after start */ - int32_t bs_end = bs_start, bs_block_end = bs_block; - - remain = bitmask_remain[bs_end]; - while ((v & remain) == remain) { - bs_end = 0; - bs_block_end ++; +#endif - if (bs_block_end == bs_block_count) { - break; - } +#ifdef FLECS_OS_API_IMPL +#ifdef _MSC_VER +#include - v = data[bs_block_end]; - remain = BS_MAX; /* Test the full block */ - } +static +ecs_os_thread_t win_thread_new( + ecs_os_thread_callback_t callback, + void *arg) +{ + HANDLE *thread = ecs_os_malloc_t(HANDLE); + *thread = CreateThread( + NULL, 0, (LPTHREAD_START_ROUTINE)callback, arg, 0, NULL); + return (ecs_os_thread_t)(uintptr_t)thread; +} - /* Step 4: find remainder of enabled elements in current block */ - if (bs_block_end != bs_block_count) { - while ((v & bitmask[bs_end])) { - bs_end ++; - } - } +static +void* win_thread_join( + ecs_os_thread_t thr) +{ + HANDLE *thread = (HANDLE*)(uintptr_t)thr; + WaitForSingleObject(thread, INFINITE); + ecs_os_free(thread); + return NULL; +} - /* Block was not 100% occupied, so bs_start must be smaller than 64 */ - ecs_assert(bs_end < 64, ECS_INTERNAL_ERROR, NULL); +static +int32_t win_ainc( + int32_t *count) +{ + return InterlockedIncrement(count); +} - /* Step 5: translate to element start/end and make sure that each column - * range is a subset of the previous one. */ - first = bs_block * 64 + bs_start; - int32_t cur_last = bs_block_end * 64 + bs_end; - - /* No enabled elements found in table */ - if (first == cur_last) { - goto done; - } +static +int32_t win_adec( + int32_t *count) +{ + return InterlockedDecrement(count); +} - /* If multiple bitsets are evaluated, make sure each subsequent range - * is equal or a subset of the previous range */ - if (i) { - /* If the first element of a subsequent bitset is larger than the - * previous last value, start over. */ - if (first >= last) { - i = -1; - continue; - } +static +ecs_os_mutex_t win_mutex_new(void) { + CRITICAL_SECTION *mutex = ecs_os_malloc_t(CRITICAL_SECTION); + InitializeCriticalSection(mutex); + return (ecs_os_mutex_t)(uintptr_t)mutex; +} - /* Make sure the last element of the range doesn't exceed the last - * element of the previous range. */ - if (cur_last > last) { - cur_last = last; - } - } - - last = cur_last; - int32_t elem_count = last - first; +static +void win_mutex_free( + ecs_os_mutex_t m) +{ + CRITICAL_SECTION *mutex = (CRITICAL_SECTION*)(intptr_t)m; + DeleteCriticalSection(mutex); + ecs_os_free(mutex); +} - /* Make sure last element doesn't exceed total number of elements in - * the table */ - if (elem_count > (bs_elem_count - first)) { - elem_count = (bs_elem_count - first); - if (!elem_count) { - iter->bitset_first = 0; - goto done; - } - } - - cur->first = first; - cur->count = elem_count; - iter->bitset_first = first; - } - - /* Keep track of last processed element for iteration */ - iter->bitset_first = last; +static +void win_mutex_lock( + ecs_os_mutex_t m) +{ + CRITICAL_SECTION *mutex = (CRITICAL_SECTION*)(intptr_t)m; + EnterCriticalSection(mutex); +} - return 0; -done: - iter->sparse_smallest = 0; - iter->sparse_first = 0; - return -1; +static +void win_mutex_unlock( + ecs_os_mutex_t m) +{ + CRITICAL_SECTION *mutex = (CRITICAL_SECTION*)(intptr_t)m; + LeaveCriticalSection(mutex); } static -void mark_columns_dirty( - ecs_query_t *query, - ecs_query_table_match_t *table_data) +ecs_os_cond_t win_cond_new(void) { + CONDITION_VARIABLE *cond = ecs_os_malloc_t(CONDITION_VARIABLE); + InitializeConditionVariable(cond); + return (ecs_os_cond_t)(uintptr_t)cond; +} + +static +void win_cond_free( + ecs_os_cond_t c) { - ecs_table_t *table = table_data->table; + (void)c; +} - if (table && table->dirty_state) { - ecs_term_t *terms = query->filter.terms; - int32_t i, count = query->filter.term_count_actual; - for (i = 0; i < count; i ++) { - ecs_term_t *term = &terms[i]; - int32_t ti = term->index; +static +void win_cond_signal( + ecs_os_cond_t c) +{ + CONDITION_VARIABLE *cond = (CONDITION_VARIABLE*)(intptr_t)c; + WakeConditionVariable(cond); +} - if (term->inout == EcsIn) { - /* Don't mark readonly terms dirty */ - continue; - } +static +void win_cond_broadcast( + ecs_os_cond_t c) +{ + CONDITION_VARIABLE *cond = (CONDITION_VARIABLE*)(intptr_t)c; + WakeAllConditionVariable(cond); +} - if (table_data->subjects[ti] != 0) { - /* Don't mark table dirty if term is not from the table */ - continue; - } +static +void win_cond_wait( + ecs_os_cond_t c, + ecs_os_mutex_t m) +{ + CRITICAL_SECTION *mutex = (CRITICAL_SECTION*)(intptr_t)m; + CONDITION_VARIABLE *cond = (CONDITION_VARIABLE*)(intptr_t)c; + SleepConditionVariableCS(cond, mutex, INFINITE); +} - int32_t index = table_data->columns[ti]; - if (index <= 0) { - /* If term is not set, there's nothing to mark dirty */ - continue; - } +void ecs_set_os_api_impl(void) { + ecs_os_set_api_defaults(); + + ecs_os_api_t api = ecs_os_api; + + api.thread_new_ = win_thread_new; + api.thread_join_ = win_thread_join; + api.ainc_ = win_ainc; + api.adec_ = win_adec; + api.mutex_new_ = win_mutex_new; + api.mutex_free_ = win_mutex_free; + api.mutex_lock_ = win_mutex_lock; + api.mutex_unlock_ = win_mutex_unlock; + api.cond_new_ = win_cond_new; + api.cond_free_ = win_cond_free; + api.cond_signal_ = win_cond_signal; + api.cond_broadcast_ = win_cond_broadcast; + api.cond_wait_ = win_cond_wait; - /* Potential candidate for marking table dirty, if a component */ - int32_t storage_index = ecs_table_type_to_storage_index( - table, index - 1); - if (storage_index >= 0) { - table->dirty_state[storage_index + 1] ++; - } - } - } + ecs_os_set_api(&api); } +#else -bool ecs_query_next( - ecs_iter_t *it) +#include "pthread.h" + +static +ecs_os_thread_t posix_thread_new( + ecs_os_thread_callback_t callback, + void *arg) { - ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(it->next == ecs_query_next, ECS_INVALID_PARAMETER, NULL); + pthread_t *thread = ecs_os_malloc(sizeof(pthread_t)); - if (flecs_iter_next_row(it)) { - return true; + if (pthread_create (thread, NULL, callback, arg) != 0) { + ecs_os_abort(); } - return flecs_iter_next_instanced(it, ecs_query_next_instanced(it)); -error: - return false; + return (ecs_os_thread_t)(uintptr_t)thread; } -bool ecs_query_next_instanced( - ecs_iter_t *it) +static +void* posix_thread_join( + ecs_os_thread_t thread) { - ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(it->next == ecs_query_next, ECS_INVALID_PARAMETER, NULL); - - ecs_query_iter_t *iter = &it->iter.query; - ecs_page_iter_t *piter = &iter->page_iter; - ecs_query_t *query = iter->query; - ecs_world_t *world = query->world; - (void)world; + void *arg; + pthread_t *thr = (pthread_t*)(uintptr_t)thread; + pthread_join(*thr, &arg); + ecs_os_free(thr); + return arg; +} - it->is_valid = true; +static +int32_t posix_ainc( + int32_t *count) +{ + int value; +#ifdef __GNUC__ + value = __sync_add_and_fetch (count, 1); + return value; +#else + /* Unsupported */ + abort(); +#endif +} - ecs_poly_assert(world, ecs_world_t); +static +int32_t posix_adec( + int32_t *count) +{ + int value; +#ifdef __GNUC__ + value = __sync_sub_and_fetch (count, 1); + return value; +#else + /* Unsupported */ + abort(); +#endif +} - if (!query->constraints_satisfied) { - goto done; +static +ecs_os_mutex_t posix_mutex_new(void) { + pthread_mutex_t *mutex = ecs_os_malloc(sizeof(pthread_mutex_t)); + if (pthread_mutex_init(mutex, NULL)) { + abort(); } - - ecs_page_cursor_t cur; - int32_t prev_count = it->total_count; - - ecs_query_table_node_t *node, *next; - for (node = iter->node; node != NULL; node = next) { - ecs_query_table_match_t *match = node->match; - ecs_table_t *table = match->table; - - next = node->next; + return (ecs_os_mutex_t)(uintptr_t)mutex; +} - if (table) { - cur.first = node->offset; - cur.count = node->count; - if (!cur.count) { - cur.count = ecs_table_count(table); +static +void posix_mutex_free( + ecs_os_mutex_t m) +{ + pthread_mutex_t *mutex = (pthread_mutex_t*)(intptr_t)m; + pthread_mutex_destroy(mutex); + ecs_os_free(mutex); +} - /* List should never contain empty tables */ - ecs_assert(cur.count != 0, ECS_INTERNAL_ERROR, NULL); - } +static +void posix_mutex_lock( + ecs_os_mutex_t m) +{ + pthread_mutex_t *mutex = (pthread_mutex_t*)(intptr_t)m; + if (pthread_mutex_lock(mutex)) { + abort(); + } +} - ecs_vector_t *bitset_columns = match->bitset_columns; - ecs_vector_t *sparse_columns = match->sparse_columns; +static +void posix_mutex_unlock( + ecs_os_mutex_t m) +{ + pthread_mutex_t *mutex = (pthread_mutex_t*)(intptr_t)m; + if (pthread_mutex_unlock(mutex)) { + abort(); + } +} - if (bitset_columns || sparse_columns) { - bool found = false; +static +ecs_os_cond_t posix_cond_new(void) { + pthread_cond_t *cond = ecs_os_malloc(sizeof(pthread_cond_t)); + if (pthread_cond_init(cond, NULL)) { + abort(); + } + return (ecs_os_cond_t)(uintptr_t)cond; +} - do { - found = false; +static +void posix_cond_free( + ecs_os_cond_t c) +{ + pthread_cond_t *cond = (pthread_cond_t*)(intptr_t)c; + if (pthread_cond_destroy(cond)) { + abort(); + } + ecs_os_free(cond); +} - if (bitset_columns) { - if (bitset_column_next(table, bitset_columns, iter, - &cur) == -1) - { - /* No more enabled components for table */ - break; - } else { - found = true; - next = node; - } - } +static +void posix_cond_signal( + ecs_os_cond_t c) +{ + pthread_cond_t *cond = (pthread_cond_t*)(intptr_t)c; + if (pthread_cond_signal(cond)) { + abort(); + } +} - if (sparse_columns) { - if (sparse_column_next(table, match, - sparse_columns, iter, &cur, found) == -1) - { - /* No more elements in sparse column */ - if (found) { - /* Try again */ - next = node->next; - found = false; - } else { - /* Nothing found */ - break; - } - } else { - found = true; - next = node; - iter->bitset_first = cur.first + cur.count; - } - } - } while (!found); +static +void posix_cond_broadcast( + ecs_os_cond_t c) +{ + pthread_cond_t *cond = (pthread_cond_t*)(intptr_t)c; + if (pthread_cond_broadcast(cond)) { + abort(); + } +} - if (!found) { - continue; - } - } +static +void posix_cond_wait( + ecs_os_cond_t c, + ecs_os_mutex_t m) +{ + pthread_cond_t *cond = (pthread_cond_t*)(intptr_t)c; + pthread_mutex_t *mutex = (pthread_mutex_t*)(intptr_t)m; + if (pthread_cond_wait(cond, mutex)) { + abort(); + } +} - int ret = ecs_page_iter_next(piter, &cur); - if (ret < 0) { - goto done; - } else if (ret > 0) { - continue; - } +void ecs_set_os_api_impl(void) { + ecs_os_set_api_defaults(); - it->total_count = cur.count; - } else { - cur.count = 0; - cur.first = 0; - } + ecs_os_api_t api = ecs_os_api; - it->ids = match->ids; - it->columns = match->columns; - it->subjects = match->subjects; - it->sizes = match->sizes; - it->references = match->references; - it->frame_offset += prev_count; - it->instance_count = 0; + api.thread_new_ = posix_thread_new; + api.thread_join_ = posix_thread_join; + api.ainc_ = posix_ainc; + api.adec_ = posix_adec; + api.mutex_new_ = posix_mutex_new; + api.mutex_free_ = posix_mutex_free; + api.mutex_lock_ = posix_mutex_lock; + api.mutex_unlock_ = posix_mutex_unlock; + api.cond_new_ = posix_cond_new; + api.cond_free_ = posix_cond_free; + api.cond_signal_ = posix_cond_signal; + api.cond_broadcast_ = posix_cond_broadcast; + api.cond_wait_ = posix_cond_wait; - flecs_iter_init(it); - flecs_iter_populate_data(world, it, match->table, cur.first, cur.count, it->ptrs, NULL); + ecs_os_set_api(&api); +} +#endif +#endif - if (query->flags & EcsQueryHasOutColumns) { - if (table) { - mark_columns_dirty(query, match); - } - } - iter->node = next; +#ifdef FLECS_COREDOC - goto yield; - } +#define URL_ROOT "https://flecs.docsforge.com/master/relations-manual/" -done: -error: - flecs_iter_fini(it); - return false; - -yield: - return true; -} +void FlecsCoreDocImport( + ecs_world_t *world) +{ + ECS_MODULE(world, FlecsCoreDoc); -bool ecs_query_next_worker( - ecs_iter_t *it, - int32_t current, - int32_t total) -{ - int32_t per_worker, instances_per_worker, first, prev_offset = it->offset; - ecs_world_t *world = it->world; + ECS_IMPORT(world, FlecsMeta); + ECS_IMPORT(world, FlecsDoc); - do { - if (!ecs_query_next(it)) { - return false; - } + ecs_set_name_prefix(world, "Ecs"); - int32_t count = it->count; - int32_t instance_count = it->instance_count; - per_worker = count / total; - instances_per_worker = instance_count / total; - first = per_worker * current; - count -= per_worker * total; + /* Initialize reflection data for core components */ - if (count) { - if (current < count) { - per_worker ++; - first += current; - } else { - first += count; - } + ecs_struct_init(world, &(ecs_struct_desc_t) { + .entity.entity = ecs_id(EcsComponent), + .members = { + {.name = (char*)"size", .type = ecs_id(ecs_i32_t)}, + {.name = (char*)"alignment", .type = ecs_id(ecs_i32_t)} } + }); - if (!per_worker && !(it->iter.query.query->flags & EcsQueryNeedsTables)) { - if (current == 0) { - flecs_iter_populate_data(world, it, it->table, it->offset, it->count, it->ptrs, NULL); - return true; - } else { - return false; - } + ecs_struct_init(world, &(ecs_struct_desc_t) { + .entity.entity = ecs_id(EcsDocDescription), + .members = { + {.name = "value", .type = ecs_id(ecs_string_t)} } - } while (!per_worker); + }); - it->instance_count = instances_per_worker; - it->frame_offset -= prev_offset; - it->frame_offset += first; + /* Initialize documentation data for core components */ + ecs_doc_set_brief(world, EcsFlecs, "Flecs root module"); + ecs_doc_set_link(world, EcsFlecs, "https://github.com/SanderMertens/flecs"); - flecs_iter_populate_data( - world, it, it->table, it->offset + first, per_worker, it->ptrs, NULL); + ecs_doc_set_brief(world, EcsFlecsCore, "Flecs module with builtin components"); - return true; -} + ecs_doc_set_brief(world, EcsWorld, "Entity associated with world"); -bool ecs_query_changed( - const ecs_query_t *query) -{ - ecs_poly_assert(query, ecs_query_t); - ecs_check(!(query->flags & EcsQueryIsOrphaned), - ECS_INVALID_PARAMETER, NULL); - return tables_dirty(query); -error: - return false; -} + ecs_doc_set_brief(world, ecs_id(EcsComponent), "Component that is added to all components"); + ecs_doc_set_brief(world, EcsModule, "Tag that is added to modules"); + ecs_doc_set_brief(world, EcsPrefab, "Tag that is added to prefabs"); + ecs_doc_set_brief(world, EcsDisabled, "Tag that is added to disabled entities"); -bool ecs_query_orphaned( - ecs_query_t *query) -{ - ecs_poly_assert(query, ecs_query_t); - return query->flags & EcsQueryIsOrphaned; -} + ecs_doc_set_brief(world, ecs_id(EcsIdentifier), "Component used for entity names"); + ecs_doc_set_brief(world, EcsName, "Tag used with EcsIdentifier to signal entity name"); + ecs_doc_set_brief(world, EcsSymbol, "Tag used with EcsIdentifier to signal entity symbol"); + ecs_doc_set_brief(world, EcsTransitive, "Transitive relation property"); + ecs_doc_set_brief(world, EcsTransitiveSelf, "TransitiveSelf relation property"); + ecs_doc_set_brief(world, EcsFinal, "Final relation property"); + ecs_doc_set_brief(world, EcsTag, "Tag relation property"); + ecs_doc_set_brief(world, EcsOnDelete, "OnDelete relation cleanup property"); + ecs_doc_set_brief(world, EcsOnDeleteObject, "OnDeleteObject relation cleanup property"); + ecs_doc_set_brief(world, EcsDefaultChildComponent, "Sets default component hint for children of entity"); + ecs_doc_set_brief(world, EcsRemove, "Remove relation cleanup property"); + ecs_doc_set_brief(world, EcsDelete, "Delete relation cleanup property"); + ecs_doc_set_brief(world, EcsThrow, "Throw relation cleanup property"); + ecs_doc_set_brief(world, EcsIsA, "Builtin IsA relation"); + ecs_doc_set_brief(world, EcsChildOf, "Builtin ChildOf relation"); + ecs_doc_set_brief(world, EcsOnAdd, "Builtin OnAdd event"); + ecs_doc_set_brief(world, EcsOnRemove, "Builtin OnRemove event"); + ecs_doc_set_brief(world, EcsOnSet, "Builtin OnSet event"); + ecs_doc_set_brief(world, EcsUnSet, "Builtin UnSet event"); -static -uint64_t ids_hash(const void *ptr) { - const ecs_ids_t *type = ptr; - ecs_id_t *ids = type->array; - int32_t count = type->count; - uint64_t hash = flecs_hash(ids, count * ECS_SIZEOF(ecs_id_t)); - return hash; -} + ecs_doc_set_link(world, EcsTransitive, URL_ROOT "#transitive-relations"); + ecs_doc_set_link(world, EcsTransitiveSelf, URL_ROOT "#inclusive-relations"); + ecs_doc_set_link(world, EcsFinal, URL_ROOT "#final-entities"); + ecs_doc_set_link(world, EcsTag, URL_ROOT "#tag-relations"); + ecs_doc_set_link(world, EcsOnDelete, URL_ROOT "#relation-cleanup-properties"); + ecs_doc_set_link(world, EcsOnDeleteObject, URL_ROOT "#relation-cleanup-properties"); + ecs_doc_set_link(world, EcsRemove, URL_ROOT "#relation-cleanup-properties"); + ecs_doc_set_link(world, EcsDelete, URL_ROOT "#relation-cleanup-properties"); + ecs_doc_set_link(world, EcsThrow, URL_ROOT "#relation-cleanup-properties"); + ecs_doc_set_link(world, EcsIsA, URL_ROOT "#the-isa-relation"); + ecs_doc_set_link(world, EcsChildOf, URL_ROOT "#the-childof-relation"); + + /* Initialize documentation for meta components */ + ecs_entity_t meta = ecs_lookup_fullpath(world, "flecs.meta"); + ecs_doc_set_brief(world, meta, "Flecs module with reflection components"); -static -int ids_compare(const void *ptr_1, const void *ptr_2) { - const ecs_ids_t *type_1 = ptr_1; - const ecs_ids_t *type_2 = ptr_2; + ecs_doc_set_brief(world, ecs_id(EcsMetaType), "Component added to types"); + ecs_doc_set_brief(world, ecs_id(EcsMetaTypeSerialized), "Component that stores reflection data in an optimized format"); + ecs_doc_set_brief(world, ecs_id(EcsPrimitive), "Component added to primitive types"); + ecs_doc_set_brief(world, ecs_id(EcsEnum), "Component added to enumeration types"); + ecs_doc_set_brief(world, ecs_id(EcsBitmask), "Component added to bitmask types"); + ecs_doc_set_brief(world, ecs_id(EcsMember), "Component added to struct members"); + ecs_doc_set_brief(world, ecs_id(EcsStruct), "Component added to struct types"); + ecs_doc_set_brief(world, ecs_id(EcsArray), "Component added to array types"); + ecs_doc_set_brief(world, ecs_id(EcsVector), "Component added to vector types"); - int32_t count_1 = type_1->count; - int32_t count_2 = type_2->count; + ecs_doc_set_brief(world, ecs_id(ecs_bool_t), "bool component"); + ecs_doc_set_brief(world, ecs_id(ecs_char_t), "char component"); + ecs_doc_set_brief(world, ecs_id(ecs_byte_t), "byte component"); + ecs_doc_set_brief(world, ecs_id(ecs_u8_t), "8 bit unsigned int component"); + ecs_doc_set_brief(world, ecs_id(ecs_u16_t), "16 bit unsigned int component"); + ecs_doc_set_brief(world, ecs_id(ecs_u32_t), "32 bit unsigned int component"); + ecs_doc_set_brief(world, ecs_id(ecs_u64_t), "64 bit unsigned int component"); + ecs_doc_set_brief(world, ecs_id(ecs_uptr_t), "word sized unsigned int component"); + ecs_doc_set_brief(world, ecs_id(ecs_i8_t), "8 bit signed int component"); + ecs_doc_set_brief(world, ecs_id(ecs_i16_t), "16 bit signed int component"); + ecs_doc_set_brief(world, ecs_id(ecs_i32_t), "32 bit signed int component"); + ecs_doc_set_brief(world, ecs_id(ecs_i64_t), "64 bit signed int component"); + ecs_doc_set_brief(world, ecs_id(ecs_iptr_t), "word sized signed int component"); + ecs_doc_set_brief(world, ecs_id(ecs_f32_t), "32 bit floating point component"); + ecs_doc_set_brief(world, ecs_id(ecs_f64_t), "64 bit floating point component"); + ecs_doc_set_brief(world, ecs_id(ecs_string_t), "string component"); + ecs_doc_set_brief(world, ecs_id(ecs_entity_t), "entity component"); - if (count_1 != count_2) { - return (count_1 > count_2) - (count_1 < count_2); - } + /* Initialize documentation for doc components */ + ecs_entity_t doc = ecs_lookup_fullpath(world, "flecs.doc"); + ecs_doc_set_brief(world, doc, "Flecs module with documentation components"); - const ecs_id_t *ids_1 = type_1->array; - const ecs_id_t *ids_2 = type_2->array; - - int32_t i; - for (i = 0; i < count_1; i ++) { - ecs_id_t id_1 = ids_1[i]; - ecs_id_t id_2 = ids_2[i]; + ecs_doc_set_brief(world, ecs_id(EcsDocDescription), "Component used to add documentation"); + ecs_doc_set_brief(world, EcsDocBrief, "Used as (Description, Brief) to add a brief description"); + ecs_doc_set_brief(world, EcsDocDetail, "Used as (Description, Detail) to add a detailed description"); + ecs_doc_set_brief(world, EcsDocLink, "Used as (Description, Link) to add a link"); +} - if (id_1 != id_2) { - return (id_1 > id_2) - (id_1 < id_2); - } - } +#endif - return 0; -} +#ifdef FLECS_PARSER -ecs_hashmap_t flecs_table_hashmap_new(void) { - return flecs_hashmap_new(ecs_ids_t, ecs_table_t*, ids_hash, ids_compare); -} -const EcsComponent* flecs_component_from_id( - const ecs_world_t *world, - ecs_entity_t e) -{ - ecs_entity_t pair = 0; +#define ECS_ANNOTATION_LENGTH_MAX (16) - /* If this is a pair, get the pair component from the identifier */ - if (ECS_HAS_ROLE(e, PAIR)) { - pair = e; - e = ecs_get_alive(world, ECS_PAIR_RELATION(e)); +#define TOK_NEWLINE '\n' +#define TOK_COLON ':' +#define TOK_AND ',' +#define TOK_OR "||" +#define TOK_NOT '!' +#define TOK_OPTIONAL '?' +#define TOK_BITWISE_OR '|' +#define TOK_NAME_SEP '.' +#define TOK_BRACKET_OPEN '[' +#define TOK_BRACKET_CLOSE ']' +#define TOK_WILDCARD '*' +#define TOK_SINGLETON '$' +#define TOK_PAREN_OPEN '(' +#define TOK_PAREN_CLOSE ')' +#define TOK_AS_ENTITY '\\' - if (ecs_has_id(world, e, EcsTag)) { - return NULL; - } - } +#define TOK_SELF "self" +#define TOK_SUPERSET "super" +#define TOK_SUBSET "sub" +#define TOK_CASCADE "cascade" +#define TOK_PARENT "parent" +#define TOK_ALL "all" - if (e & ECS_ROLE_MASK) { - return NULL; - } +#define TOK_OWNED "OVERRIDE" - const EcsComponent *component = ecs_get(world, e, EcsComponent); - if ((!component || !component->size) && pair) { - /* If this is a pair column and the pair is not a component, use - * the component type of the component the pair is applied to. */ - e = ECS_PAIR_OBJECT(pair); +#define TOK_ROLE_PAIR "PAIR" +#define TOK_ROLE_AND "AND" +#define TOK_ROLE_OR "OR" +#define TOK_ROLE_XOR "XOR" +#define TOK_ROLE_NOT "NOT" +#define TOK_ROLE_SWITCH "SWITCH" +#define TOK_ROLE_CASE "CASE" +#define TOK_ROLE_DISABLED "DISABLED" - /* Because generations are not stored in the pair, get the currently - * alive id */ - e = ecs_get_alive(world, e); +#define TOK_IN "in" +#define TOK_OUT "out" +#define TOK_INOUT "inout" +#define TOK_INOUT_FILTER "filter" - /* If a pair is used with a not alive id, the pair is not valid */ - ecs_assert(e != 0, ECS_INTERNAL_ERROR, NULL); +#define ECS_MAX_TOKEN_SIZE (256) - component = ecs_get(world, e, EcsComponent); +typedef char ecs_token_t[ECS_MAX_TOKEN_SIZE]; + +const char* ecs_parse_eol_and_whitespace( + const char *ptr) +{ + while (isspace(*ptr)) { + ptr ++; } - return component; + return ptr; } -/* Ensure the ids used in the columns exist */ -static -int32_t ensure_columns( - ecs_world_t *world, - ecs_table_t *table) +/** Skip spaces when parsing signature */ +const char* ecs_parse_whitespace( + const char *ptr) { - int32_t count = 0; - ecs_vector_each(table->type, ecs_entity_t, c_ptr, { - ecs_entity_t component = *c_ptr; - - if (ECS_HAS_ROLE(component, PAIR)) { - ecs_entity_t rel = ECS_PAIR_RELATION(component); - ecs_entity_t obj = ECS_PAIR_OBJECT(component); - ecs_ensure(world, rel); - ecs_ensure(world, obj); - } else if (component & ECS_ROLE_MASK) { - ecs_entity_t e = ECS_PAIR_OBJECT(component); - ecs_ensure(world, e); - } else { - ecs_ensure(world, component); - } - }); + while ((*ptr != '\n') && isspace(*ptr)) { + ptr ++; + } - return count; + return ptr; } -static -ecs_type_t ids_to_type( - ecs_ids_t *entities) +const char* ecs_parse_digit( + const char *ptr, + char *token) { - if (entities->count) { - ecs_vector_t *result = NULL; - ecs_vector_set_count(&result, ecs_entity_t, entities->count); - ecs_entity_t *array = ecs_vector_first(result, ecs_entity_t); - ecs_os_memcpy_n(array, entities->array, ecs_entity_t, entities->count); - return result; - } else { + char *tptr = token; + char ch = ptr[0]; + + if (!isdigit(ch) && ch != '-') { + ecs_parser_error(NULL, NULL, 0, "invalid start of number '%s'", ptr); return NULL; } -} -static -ecs_edge_t* get_edge( - ecs_graph_edges_t *edges, - ecs_id_t id) -{ - if (id < ECS_HI_COMPONENT_ID) { - if (!edges->lo) { - return NULL; - } - return &edges->lo[id]; - } else { - if (!edges->hi) { - return NULL; - } - return ecs_map_get(edges->hi, ecs_edge_t, id); - } -} + tptr[0] = ch; + tptr ++; + ptr ++; -static -ecs_edge_t* ensure_edge( - ecs_graph_edges_t *edges, - ecs_id_t id) -{ - if (id < ECS_HI_COMPONENT_ID) { - if (!edges->lo) { - edges->lo = ecs_os_calloc_n(ecs_edge_t, ECS_HI_COMPONENT_ID); - } - return &edges->lo[id]; - } else { - if (!edges->hi) { - edges->hi = ecs_map_new(ecs_edge_t, 1); + for (; (ch = *ptr); ptr ++) { + if (!isdigit(ch)) { + break; } - return ecs_map_ensure(edges->hi, ecs_edge_t, id); + + tptr[0] = ch; + tptr ++; } -} -static -void init_edges( - ecs_graph_edges_t *edges) -{ - edges->lo = NULL; - edges->hi = NULL; + tptr[0] = '\0'; + + return ptr; } static -void init_node( - ecs_graph_node_t *node) +bool is_newline_comment( + const char *ptr) { - init_edges(&node->add); - init_edges(&node->remove); + if (ptr[0] == '/' && ptr[1] == '/') { + return true; + } + return false; } -static -void init_flags( - ecs_world_t *world, - ecs_table_t *table) +const char* ecs_parse_fluff( + const char *ptr, + char **last_comment) { - ecs_id_t *ids = ecs_vector_first(table->type, ecs_id_t); - int32_t count = ecs_vector_count(table->type); - - /* Iterate components to initialize table flags */ - int32_t i; - for (i = 0; i < count; i ++) { - ecs_id_t id = ids[i]; + const char *last_comment_start = NULL; - /* As we're iterating over the table components, also set the table - * flags. These allow us to quickly determine if the table contains - * data that needs to be handled in a special way, like prefabs or - * containers */ - if (id <= EcsLastInternalComponentId) { - table->flags |= EcsTableHasBuiltins; - } + do { + /* Skip whitespaces before checking for a comment */ + ptr = ecs_parse_whitespace(ptr); - if (id == EcsModule) { - table->flags |= EcsTableHasBuiltins; - table->flags |= EcsTableHasModule; - } + /* Newline comment, skip until newline character */ + if (is_newline_comment(ptr)) { + ptr += 2; + last_comment_start = ptr; - if (id == EcsPrefab) { - table->flags |= EcsTableIsPrefab; + while (ptr[0] && ptr[0] != TOK_NEWLINE) { + ptr ++; + } } - /* If table contains disabled entities, mark it as disabled */ - if (id == EcsDisabled) { - table->flags |= EcsTableIsDisabled; + /* If a newline character is found, skip it */ + if (ptr[0] == TOK_NEWLINE) { + ptr ++; } - /* Does table have exclusive or columns */ - if (ECS_HAS_ROLE(id, XOR)) { - table->flags |= EcsTableHasXor; - } + } while (isspace(ptr[0]) || is_newline_comment(ptr)); - /* Does table have IsA relations */ - if (ECS_HAS_RELATION(id, EcsIsA)) { - table->flags |= EcsTableHasIsA; - } + if (last_comment) { + *last_comment = (char*)last_comment_start; + } - /* Does table have switch columns */ - if (ECS_HAS_ROLE(id, SWITCH)) { - table->flags |= EcsTableHasSwitch; - } + return ptr; +} - /* Does table support component disabling */ - if (ECS_HAS_ROLE(id, DISABLED)) { - table->flags |= EcsTableHasDisabled; - } +/* -- Private functions -- */ - /* Does table have ChildOf relations */ - if (ECS_HAS_RELATION(id, EcsChildOf)) { - ecs_entity_t obj = ecs_pair_object(world, id); - if (obj == EcsFlecs || obj == EcsFlecsCore || - ecs_has_id(world, obj, EcsModule)) - { - /* If table contains entities that are inside one of the builtin - * modules, it contains builtin entities */ - table->flags |= EcsTableHasBuiltins; - table->flags |= EcsTableHasModule; - } - } +static +bool valid_identifier_start_char( + char ch) +{ + if (ch && (isalpha(ch) || (ch == '.') || (ch == '_') || (ch == '*') || + (ch == '0') || (ch == TOK_AS_ENTITY) || isdigit(ch))) + { + return true; } + + return false; } static -void init_table( - ecs_world_t *world, - ecs_table_t *table, - ecs_ids_t *entities) +bool valid_token_start_char( + char ch) { - table->type = ids_to_type(entities); - table->c_info = NULL; - table->flags = 0; - table->dirty_state = NULL; - table->alloc_count = 0; - table->lock = 0; - - /* Ensure the component ids for the table exist */ - ensure_columns(world, table); - - table->queries = NULL; - - init_node(&table->node); - init_flags(world, table); - - flecs_register_table(world, table); - flecs_table_init_data(world, table); + if ((ch == '"') || (ch == '{') || (ch == '}') || (ch == ',') || (ch == '-') + || (ch == '[') || (ch == ']') || valid_identifier_start_char(ch)) + { + return true; + } - /* Register component info flags for all columns */ - flecs_table_notify(world, table, &(ecs_table_event_t){ - .kind = EcsTableComponentInfo - }); + return false; } static -ecs_table_t *create_table( - ecs_world_t *world, - ecs_ids_t *entities, - flecs_hashmap_result_t table_elem) +bool valid_token_char( + char ch) { - ecs_table_t *result = flecs_sparse_add(world->store.tables, ecs_table_t); - ecs_assert(result != NULL, ECS_INTERNAL_ERROR, NULL); - result->id = flecs_sparse_last_id(world->store.tables); - init_table(world, result, entities); - -#ifndef NDEBUG - char *expr = ecs_type_str(world, result->type); - ecs_dbg_2("#[green]table#[normal] [%s] created with id %d", expr, result->id); - ecs_os_free(expr); -#endif - ecs_log_push(); - - /* Store table in table hashmap */ - *(ecs_table_t**)table_elem.value = result; - - /* Set keyvalue to one that has the same lifecycle as the table */ - ecs_ids_t key = { - .array = ecs_vector_first(result->type, ecs_id_t), - .count = ecs_vector_count(result->type) - }; - *(ecs_ids_t*)table_elem.key = key; - - flecs_notify_queries(world, &(ecs_query_event_t) { - .kind = EcsQueryTableMatch, - .table = result - }); - - ecs_log_pop(); + if (ch && + (isalpha(ch) || isdigit(ch) || ch == '_' || ch == '.' || ch == '"')) + { + return true; + } - return result; + return false; } static -void add_id_to_ids( - ecs_type_t type, - ecs_entity_t add, - ecs_ids_t *out, - ecs_entity_t r_exclusive) +bool valid_operator_char( + char ch) { - int32_t count = ecs_vector_count(type); - ecs_id_t *array = ecs_vector_first(type, ecs_id_t); - bool added = false; - - int32_t i, el = 0; - for (i = 0; i < count; i ++) { - ecs_id_t e = array[i]; - - if (!added) { - if (r_exclusive && ECS_HAS_ROLE(e, PAIR)) { - if (ECS_PAIR_RELATION(e) == r_exclusive) { - out->array[el ++] = add; - added = true; - continue; /* don't add original element */ - } - } - - if (e >= add) { - if (e != add) { - out->array[el ++] = add; - } - added = true; - } - } - - out->array[el ++] = e; - ecs_assert(el <= out->count, ECS_INTERNAL_ERROR, NULL); - } - - if (!added) { - out->array[el ++] = add; + if (ch == TOK_OPTIONAL || ch == TOK_NOT) { + return true; } - out->count = el; + return false; } static -void remove_id_from_ids( - ecs_type_t type, - ecs_entity_t remove, - ecs_ids_t *out) +const char* parse_digit( + const char *ptr, + char *token_out) { - int32_t count = ecs_vector_count(type); - ecs_id_t *array = ecs_vector_first(type, ecs_id_t); - - int32_t i, el = 0; - for (i = 0; i < count; i ++) { - ecs_id_t e = array[i]; - if (e != remove) { - out->array[el ++] = e; - ecs_assert(el <= count, ECS_INTERNAL_ERROR, NULL); - } - } - - out->count = el; + ptr = ecs_parse_whitespace(ptr); + ptr = ecs_parse_digit(ptr, token_out); + return ecs_parse_whitespace(ptr); } -int32_t flecs_table_switch_from_case( - const ecs_world_t *world, - const ecs_table_t *table, - ecs_entity_t add) +const char* ecs_parse_token( + const char *name, + const char *expr, + const char *ptr, + char *token_out) { - ecs_type_t type = table->type; - ecs_entity_t *array = ecs_vector_first(type, ecs_entity_t); + int64_t column = ptr - expr; - int32_t i, count = table->sw_column_count; - ecs_assert(count != 0, ECS_INTERNAL_ERROR, NULL); + ptr = ecs_parse_whitespace(ptr); + char *tptr = token_out, ch = ptr[0]; - add = add & ECS_COMPONENT_MASK; + if (!valid_token_start_char(ch)) { + if (ch == '\0' || ch == '\n') { + ecs_parser_error(name, expr, column, + "unexpected end of expression"); + } else { + ecs_parser_error(name, expr, column, + "invalid start of token '%s'", ptr); + } + return NULL; + } - ecs_sw_column_t *sw_columns = NULL; + tptr[0] = ch; + tptr ++; + ptr ++; - if ((sw_columns = table->storage.sw_columns)) { - /* Fast path, we can get the switch type from the column data */ - for (i = 0; i < count; i ++) { - ecs_type_t sw_type = sw_columns[i].type; - if (ecs_type_has_id(world, sw_type, add, true)) { - return i; - } - } - } else { - /* Slow path, table is empty, so we'll have to get the switch types by - * actually inspecting the switch type entities. */ - for (i = 0; i < count; i ++) { - ecs_entity_t e = array[i + table->sw_column_offset]; - ecs_assert(ECS_HAS_ROLE(e, SWITCH), ECS_INTERNAL_ERROR, NULL); - e = e & ECS_COMPONENT_MASK; + if (ch == '{' || ch == '}' || ch == '[' || ch == ']' || ch == ',') { + tptr[0] = 0; + return ptr; + } - const EcsType *type_ptr = ecs_get(world, e, EcsType); - ecs_assert(type_ptr != NULL, ECS_INTERNAL_ERROR, NULL); + int tmpl_nesting = 0; + bool in_str = ch == '"'; - if (ecs_type_has_id( - world, type_ptr->normalized, add, true)) - { - return i; + for (; (ch = *ptr); ptr ++) { + if (ch == '<') { + tmpl_nesting ++; + } else if (ch == '>') { + if (!tmpl_nesting) { + break; } + tmpl_nesting --; + } else if (ch == '"') { + in_str = !in_str; + } else + if (!valid_token_char(ch) && !in_str) { + break; } + + tptr[0] = ch; + tptr ++; } - /* If a table was not found, this is an invalid switch case */ - ecs_abort(ECS_TYPE_INVALID_CASE, NULL); + tptr[0] = '\0'; - return -1; -} + if (tmpl_nesting != 0) { + ecs_parser_error(name, expr, column, + "identifier '%s' has mismatching < > pairs", ptr); + return NULL; + } -static -void ids_append( - ecs_ids_t *ids, - ecs_id_t id) -{ - ids->array = ecs_os_realloc_n(ids->array, ecs_id_t, ids->count + 1); - ids->array[ids->count ++] = id; + const char *next_ptr = ecs_parse_whitespace(ptr); + if (next_ptr[0] == ':' && next_ptr != ptr) { + /* Whitespace between token and : is significant */ + ptr = next_ptr - 1; + } else { + ptr = next_ptr; + } + + return ptr; } static -void diff_insert_isa( - ecs_world_t *world, - ecs_table_t *table, - ecs_table_diff_t *base_diff, - ecs_ids_t *append_to, - ecs_ids_t *append_from, - ecs_id_t add) +const char* ecs_parse_identifier( + const char *name, + const char *expr, + const char *ptr, + char *token_out) { - ecs_entity_t base = ecs_pair_object(world, add); - ecs_table_t *base_table = ecs_get_table(world, base); - if (!base_table) { - return; - } - - ecs_type_t base_type = base_table->type, type = table->type; - ecs_table_t *table_wo_base = base_table; - - /* If the table does not have a component from the base, it should - * trigger an OnSet */ - ecs_id_t *ids = ecs_vector_first(base_type, ecs_id_t); - int32_t j, i, count = ecs_vector_count(base_type); - for (i = 0; i < count; i ++) { - ecs_id_t id = ids[i]; - - if (ECS_HAS_RELATION(id, EcsIsA)) { - /* The base has an IsA relation. Find table without the base, which - * gives us the list of ids the current base inherits and doesn't - * override. This saves us from having to recursively check for each - * base in the hierarchy whether the component is overridden. */ - table_wo_base = flecs_table_traverse_remove( - world, table_wo_base, &id, base_diff); - - /* Because we removed, the ids are stored in un_set vs. on_set */ - for (j = 0; j < append_from->count; j ++) { - ecs_id_t base_id = append_from->array[j]; - /* We still have to make sure the id isn't overridden by the - * current table */ - if (ecs_type_match(world, table, type, 0, base_id, 0, 0, 0, - NULL, NULL, NULL) == -1) - { - ids_append(append_to, base_id); - } - } - - continue; - } - - /* Identifiers are not inherited */ - if (ECS_HAS_RELATION(id, ecs_id(EcsIdentifier))) { - continue; - } + if (!valid_identifier_start_char(ptr[0])) { + ecs_parser_error(name, expr, (ptr - expr), + "expected start of identifier"); + return NULL; + } - if (!ecs_get_typeid(world, id)) { - continue; - } + ptr = ecs_parse_token(name, expr, ptr, token_out); - if (ecs_type_match(world, table, type, 0, id, 0, 0, 0, - NULL, NULL, NULL) == -1) - { - ids_append(append_to, id); - } - } + return ptr; } static -void diff_insert_added_isa( - ecs_world_t *world, - ecs_table_t *table, - ecs_table_diff_t *diff, - ecs_id_t id) +int parse_identifier( + const char *token, + ecs_term_id_t *out) { - ecs_table_diff_t base_diff; - diff_insert_isa(world, table, &base_diff, &diff->on_set, - &base_diff.un_set, id); + char ch = token[0]; + + const char *tptr = token; + if (ch == TOK_AS_ENTITY) { + tptr ++; + } + + out->name = ecs_os_strdup(tptr); + + if (ch == TOK_AS_ENTITY) { + out->var = EcsVarIsEntity; + } + + return 0; } static -void diff_insert_removed_isa( - ecs_world_t *world, - ecs_table_t *table, - ecs_table_diff_t *diff, - ecs_id_t id) +ecs_entity_t parse_role( + const char *name, + const char *sig, + int64_t column, + const char *token) { - ecs_table_diff_t base_diff; - diff_insert_isa(world, table, &base_diff, &diff->un_set, - &base_diff.un_set, id); + if (!ecs_os_strcmp(token, TOK_ROLE_PAIR)) + { + return ECS_PAIR; + } else if (!ecs_os_strcmp(token, TOK_ROLE_AND)) { + return ECS_AND; + } else if (!ecs_os_strcmp(token, TOK_ROLE_OR)) { + return ECS_OR; + } else if (!ecs_os_strcmp(token, TOK_ROLE_XOR)) { + return ECS_XOR; + } else if (!ecs_os_strcmp(token, TOK_ROLE_NOT)) { + return ECS_NOT; + } else if (!ecs_os_strcmp(token, TOK_ROLE_SWITCH)) { + return ECS_SWITCH; + } else if (!ecs_os_strcmp(token, TOK_ROLE_CASE)) { + return ECS_CASE; + } else if (!ecs_os_strcmp(token, TOK_OWNED)) { + return ECS_OVERRIDE; + } else if (!ecs_os_strcmp(token, TOK_ROLE_DISABLED)) { + return ECS_DISABLED; + } else { + ecs_parser_error(name, sig, column, "invalid role '%s'", token); + return 0; + } } static -void diff_insert_added( - ecs_world_t *world, - ecs_table_t *table, - ecs_table_diff_t *diff, - ecs_id_t id) +ecs_oper_kind_t parse_operator( + char ch) { - diff->added.array[diff->added.count ++] = id; - - if (ECS_HAS_RELATION(id, EcsIsA)) { - diff_insert_added_isa(world, table, diff, id); + if (ch == TOK_OPTIONAL) { + return EcsOptional; + } else if (ch == TOK_NOT) { + return EcsNot; + } else { + ecs_abort(ECS_INTERNAL_ERROR, NULL); } } static -void diff_insert_removed( - ecs_world_t *world, - ecs_table_t *table, - ecs_table_diff_t *diff, - ecs_id_t id) +const char* parse_annotation( + const char *name, + const char *sig, + int64_t column, + const char *ptr, + ecs_inout_kind_t *inout_kind_out) { - diff->removed.array[diff->removed.count ++] = id; + char token[ECS_MAX_TOKEN_SIZE]; - if (ECS_HAS_RELATION(id, EcsIsA)) { - /* Removing an IsA relation also "removes" all components from the - * instance. Any id from base that's not overridden should be UnSet. */ - diff_insert_removed_isa(world, table, diff, id); - return; + ptr = ecs_parse_identifier(name, sig, ptr, token); + if (!ptr) { + return NULL; } - if (table->flags & EcsTableHasIsA) { - if (!ecs_get_typeid(world, id)) { - /* Do nothing if id is not a component */ - return; - } + if (!ecs_os_strcmp(token, TOK_IN)) { + *inout_kind_out = EcsIn; + } else + if (!ecs_os_strcmp(token, TOK_OUT)) { + *inout_kind_out = EcsOut; + } else + if (!ecs_os_strcmp(token, TOK_INOUT)) { + *inout_kind_out = EcsInOut; + } else if (!ecs_os_strcmp(token, TOK_INOUT_FILTER)) { + *inout_kind_out = EcsInOutFilter; + } - /* If next table has a base and component is removed, check if - * the removed component was an override. Removed overrides reexpose the - * base component, thus "changing" the value which requires an OnSet. */ - if (ecs_type_match(world, table, table->type, 0, id, EcsIsA, - 1, 0, NULL, NULL, NULL) != -1) - { - ids_append(&diff->on_set, id); - return; - } + ptr = ecs_parse_whitespace(ptr); + + if (ptr[0] != TOK_BRACKET_CLOSE) { + ecs_parser_error(name, sig, column, "expected ]"); + return NULL; } - if (ecs_get_typeid(world, id) != 0) { - ids_append(&diff->un_set, id); + return ptr + 1; +} + +static +uint8_t parse_set_token( + const char *token) +{ + if (!ecs_os_strcmp(token, TOK_SELF)) { + return EcsSelf; + } else if (!ecs_os_strcmp(token, TOK_SUPERSET)) { + return EcsSuperSet; + } else if (!ecs_os_strcmp(token, TOK_SUBSET)) { + return EcsSubSet; + } else if (!ecs_os_strcmp(token, TOK_CASCADE)) { + return EcsCascade; + } else if (!ecs_os_strcmp(token, TOK_ALL)) { + return EcsAll; + } else if (!ecs_os_strcmp(token, TOK_PARENT)) { + return EcsParent; + } else { + return 0; } } static -void compute_table_diff( - ecs_world_t *world, - ecs_table_t *node, - ecs_table_t *next, - ecs_edge_t *edge, - ecs_id_t id) +const char* parse_set_expr( + const ecs_world_t *world, + const char *name, + const char *expr, + int64_t column, + const char *ptr, + char *token, + ecs_term_id_t *id, + char tok_end) { - if (node == next) { - return; + char token_buf[ECS_MAX_TOKEN_SIZE] = {0}; + if (!token) { + token = token_buf; + ptr = ecs_parse_identifier(name, expr, ptr, token); + if (!ptr) { + return NULL; + } } - ecs_type_t node_type = node->type; - ecs_type_t next_type = next->type; + do { + uint8_t tok = parse_set_token(token); + if (!tok) { + ecs_parser_error(name, expr, column, + "invalid set token '%s'", token); + return NULL; + } - ecs_id_t *ids_node = ecs_vector_first(node_type, ecs_id_t); - ecs_id_t *ids_next = ecs_vector_first(next_type, ecs_id_t); - int32_t i_node = 0, node_count = ecs_vector_count(node_type); - int32_t i_next = 0, next_count = ecs_vector_count(next_type); - int32_t added_count = 0; - int32_t removed_count = 0; - bool trivial_edge = !ECS_HAS_RELATION(id, EcsIsA) && - !(node->flags & EcsTableHasIsA) && !(next->flags & EcsTableHasIsA); + if (id->set.mask & tok) { + ecs_parser_error(name, expr, column, + "duplicate set token '%s'", token); + return NULL; + } - /* First do a scan to see how big the diff is, so we don't have to realloc - * or alloc more memory than required. */ - for (; i_node < node_count && i_next < next_count; ) { - ecs_id_t id_node = ids_node[i_node]; - ecs_id_t id_next = ids_next[i_next]; + if ((tok == EcsSubSet && id->set.mask & EcsSuperSet) || + (tok == EcsSuperSet && id->set.mask & EcsSubSet)) + { + ecs_parser_error(name, expr, column, + "cannot mix super and sub", token); + return NULL; + } + + id->set.mask |= tok; - bool added = id_next < id_node; - bool removed = id_node < id_next; + if (ptr[0] == TOK_PAREN_OPEN) { + ptr ++; - trivial_edge &= !added || id_next == id; - trivial_edge &= !removed || id_node == id; + /* Relationship (overrides IsA default) */ + if (!isdigit(ptr[0]) && valid_token_start_char(ptr[0])) { + ptr = ecs_parse_identifier(name, expr, ptr, token); + if (!ptr) { + return NULL; + } - added_count += added; - removed_count += removed; + id->set.relation = ecs_lookup_fullpath(world, token); + if (!id->set.relation) { + ecs_parser_error(name, expr, column, + "unresolved identifier '%s'", token); + return NULL; + } - i_node += id_node <= id_next; - i_next += id_next <= id_node; - } + if (ptr[0] == TOK_AND) { + ptr = ecs_parse_whitespace(ptr + 1); + } else if (ptr[0] != TOK_PAREN_CLOSE) { + ecs_parser_error(name, expr, column, + "expected ',' or ')'"); + return NULL; + } + } - added_count += next_count - i_next; - removed_count += node_count - i_node; + /* Max depth of search */ + if (isdigit(ptr[0])) { + ptr = parse_digit(ptr, token); + if (!ptr) { + return NULL; + } - trivial_edge &= (added_count + removed_count) <= 1; + id->set.max_depth = atoi(token); + if (id->set.max_depth < 0) { + ecs_parser_error(name, expr, column, + "invalid negative depth"); + return NULL; + } - if (trivial_edge) { - /* If edge is trivial there's no need to create a diff element for it. - * Encode in the id whether the id is a tag or not, so that wen can - * still tell whether an UnSet handler should be called or not. */ - edge->diff_index = -1 * (ecs_get_typeid(world, id) == 0); - return; - } + if (ptr[0] == ',') { + ptr = ecs_parse_whitespace(ptr + 1); + } + } - ecs_table_diff_t *diff = ecs_vector_add(&node->node.diffs, ecs_table_diff_t); - ecs_os_memset_t(diff, 0, ecs_table_diff_t); - edge->diff_index = ecs_vector_count(node->node.diffs); - if (added_count) { - diff->added.array = ecs_os_malloc_n(ecs_id_t, added_count); - diff->added.count = 0; - diff->added.size = added_count; - } - if (removed_count) { - diff->removed.array = ecs_os_malloc_n(ecs_id_t, removed_count); - diff->removed.count = 0; - diff->removed.size = removed_count; - } + /* If another digit is found, previous depth was min depth */ + if (isdigit(ptr[0])) { + ptr = parse_digit(ptr, token); + if (!ptr) { + return NULL; + } - for (i_node = 0, i_next = 0; i_node < node_count && i_next < next_count; ) { - ecs_id_t id_node = ids_node[i_node]; - ecs_id_t id_next = ids_next[i_next]; + id->set.min_depth = id->set.max_depth; + id->set.max_depth = atoi(token); + if (id->set.max_depth < 0) { + ecs_parser_error(name, expr, column, + "invalid negative depth"); + return NULL; + } + } - if (id_next < id_node) { - diff_insert_added(world, node, diff, id_next); - } else if (id_node < id_next) { - diff_insert_removed(world, next, diff, id_node); + if (ptr[0] != TOK_PAREN_CLOSE) { + ecs_parser_error(name, expr, column, "expected ')', got '%c'", + ptr[0]); + return NULL; + } else { + ptr = ecs_parse_whitespace(ptr + 1); + if (ptr[0] != tok_end && ptr[0] != TOK_AND && ptr[0] != 0) { + ecs_parser_error(name, expr, column, + "expected end of set expr"); + return NULL; + } + } } - i_node += id_node <= id_next; - i_next += id_next <= id_node; - } + /* Next token in set expression */ + if (ptr[0] == TOK_BITWISE_OR) { + ptr ++; + if (valid_token_start_char(ptr[0])) { + ptr = ecs_parse_identifier(name, expr, ptr, token); + if (!ptr) { + return NULL; + } + } - for (; i_next < next_count; i_next ++) { - diff_insert_added(world, node, diff, ids_next[i_next]); + /* End of set expression */ + } else if (ptr[0] == tok_end || ptr[0] == TOK_AND || !ptr[0]) { + break; + } + } while (true); + + if (id->set.mask & EcsCascade && !(id->set.mask & EcsSuperSet) && + !(id->set.mask & EcsSubSet)) + { + /* If cascade is used without specifying super or sub, assume + * super */ + id->set.mask |= EcsSuperSet; } - for (; i_node < node_count; i_node ++) { - diff_insert_removed(world, next, diff, ids_node[i_node]); + + if (id->set.mask & EcsSelf && id->set.min_depth != 0) { + ecs_parser_error(name, expr, column, + "min_depth must be zero for set expression with 'self'"); + return NULL; } - ecs_assert(diff->added.count == added_count, ECS_INTERNAL_ERROR, NULL); - ecs_assert(diff->removed.count == removed_count, ECS_INTERNAL_ERROR, NULL); + return ptr; } static -ecs_table_t* find_or_create_table_with_id( - ecs_world_t *world, - ecs_table_t *node, - ecs_entity_t id) +const char* parse_arguments( + const ecs_world_t *world, + const char *name, + const char *expr, + int64_t column, + const char *ptr, + char *token, + ecs_term_t *term) { - /* If table has one or more switches and this is a case, return self */ - if (ECS_HAS_ROLE(id, CASE)) { - ecs_assert((node->flags & EcsTableHasSwitch) != 0, - ECS_TYPE_INVALID_CASE, NULL); - return node; - } else { - ecs_type_t type = node->type; - int32_t count = ecs_vector_count(type); - ecs_entity_t r_exclusive = 0; + (void)column; - if (ECS_HAS_ROLE(id, PAIR)) { - ecs_entity_t r = ecs_pair_relation(world, id); - if (ecs_has_id(world, r, EcsExclusive)) { - r_exclusive = (uint32_t)r; + int32_t arg = 0; + + do { + if (valid_token_start_char(ptr[0])) { + if (arg == 2) { + ecs_parser_error(name, expr, (ptr - expr), + "too many arguments in term"); + return NULL; + } + + ptr = ecs_parse_identifier(name, expr, ptr, token); + if (!ptr) { + return NULL; + } + + ecs_term_id_t *term_id = NULL; + + if (arg == 0) { + term_id = &term->subj; + } else if (arg == 1) { + term_id = &term->obj; + } + + /* If token is a colon, the token is an identifier followed by a + * set expression. */ + if (ptr[0] == TOK_COLON) { + if (parse_identifier(token, term_id)) { + ecs_parser_error(name, expr, (ptr - expr), + "invalid identifier '%s'", token); + return NULL; + } + + ptr = ecs_parse_whitespace(ptr + 1); + ptr = parse_set_expr(world, name, expr, (ptr - expr), ptr, + NULL, term_id, TOK_PAREN_CLOSE); + if (!ptr) { + return NULL; + } + + /* If token is a self, super or sub token, this is a set + * expression */ + } else if (!ecs_os_strcmp(token, TOK_ALL) || + !ecs_os_strcmp(token, TOK_CASCADE) || + !ecs_os_strcmp(token, TOK_SELF) || + !ecs_os_strcmp(token, TOK_SUPERSET) || + !ecs_os_strcmp(token, TOK_SUBSET) || + !(ecs_os_strcmp(token, TOK_PARENT))) + { + ptr = parse_set_expr(world, name, expr, (ptr - expr), ptr, + token, term_id, TOK_PAREN_CLOSE); + if (!ptr) { + return NULL; + } + + /* Regular identifier */ + } else if (parse_identifier(token, term_id)) { + ecs_parser_error(name, expr, (ptr - expr), + "invalid identifier '%s'", token); + return NULL; + } + + if (ptr[0] == TOK_AND) { + ptr = ecs_parse_whitespace(ptr + 1); + + term->role = ECS_PAIR; + + } else if (ptr[0] == TOK_PAREN_CLOSE) { + ptr = ecs_parse_whitespace(ptr + 1); + break; + + } else { + ecs_parser_error(name, expr, (ptr - expr), + "expected ',' or ')'"); + return NULL; } + + } else { + ecs_parser_error(name, expr, (ptr - expr), + "expected identifier or set expression"); + return NULL; } - ecs_ids_t ids = { - .array = ecs_os_alloca_n(ecs_id_t, count + 1), - .count = count + 1 - }; + arg ++; - add_id_to_ids(type, id, &ids, r_exclusive); + } while (true); - return flecs_table_find_or_create(world, &ids); - } + return ptr; } static -ecs_table_t* find_or_create_table_without_id( - ecs_world_t *world, - ecs_table_t *node, - ecs_entity_t id) +void parser_unexpected_char( + const char *name, + const char *expr, + const char *ptr, + char ch) { - /* If table has one or more switches and this is a case, return self */ - if (ECS_HAS_ROLE(id, CASE)) { - ecs_assert((node->flags & EcsTableHasSwitch) != 0, - ECS_TYPE_INVALID_CASE, NULL); - return node; + if (ch && (ch != '\n')) { + ecs_parser_error(name, expr, (ptr - expr), + "unexpected character '%c'", ch); } else { - ecs_type_t type = node->type; - int32_t count = ecs_vector_count(type); + ecs_parser_error(name, expr, (ptr - expr), + "unexpected end of term"); + } +} - ecs_ids_t ids = { - .array = ecs_os_alloca_n(ecs_id_t, count), - .count = count - }; +static +const char* parse_term( + const ecs_world_t *world, + const char *name, + const char *expr, + ecs_term_t *term_out) +{ + const char *ptr = expr; + char token[ECS_MAX_TOKEN_SIZE] = {0}; + ecs_term_t term = { .move = true /* parser never owns resources */ }; - remove_id_from_ids(type, id, &ids); + ptr = ecs_parse_whitespace(ptr); - return flecs_table_find_or_create(world, &ids);; + /* Inout specifiers always come first */ + if (ptr[0] == TOK_BRACKET_OPEN) { + ptr = parse_annotation(name, expr, (ptr - expr), ptr + 1, &term.inout); + if (!ptr) { + goto error; + } + ptr = ecs_parse_whitespace(ptr); } -} -static -ecs_table_t* find_or_create_table_with_isa( - ecs_world_t *world, - ecs_table_t *node, - ecs_entity_t base) -{ - ecs_type_t base_type = ecs_get_type(world, base); - ecs_id_t *ids = ecs_vector_first(base_type, ecs_id_t); - int32_t i, count = ecs_vector_count(base_type); + if (valid_operator_char(ptr[0])) { + term.oper = parse_operator(ptr[0]); + ptr = ecs_parse_whitespace(ptr + 1); + } - /* Start from back, as roles have high ids */ - for (i = count - 1; i >= 0; i --) { - ecs_id_t id = ids[i]; - if (!(id & ECS_ROLE_MASK)) { /* early out if we found everything */ - break; + /* If next token is the start of an identifier, it could be either a type + * role, source or component identifier */ + if (valid_token_start_char(ptr[0])) { + ptr = ecs_parse_identifier(name, expr, ptr, token); + if (!ptr) { + goto error; } - if (ECS_HAS_RELATION(id, EcsIsA)) { - ecs_entity_t base_of_base = ecs_pair_object(world, id); - node = find_or_create_table_with_isa(world, node, base_of_base); + /* Is token a type role? */ + if (ptr[0] == TOK_BITWISE_OR && ptr[1] != TOK_BITWISE_OR) { + ptr ++; + goto parse_role; } - if (ECS_HAS_ROLE(id, OVERRIDE)) { - /* Override found, add it to table */ - id &= ECS_COMPONENT_MASK; - node = flecs_table_traverse_add(world, node, &id, NULL); + /* Is token a predicate? */ + if (ptr[0] == TOK_PAREN_OPEN) { + goto parse_predicate; } - } - return node; -} + /* Next token must be a predicate */ + goto parse_predicate; -static -ecs_table_t* find_or_create_table_without( - ecs_world_t *world, - ecs_table_t *node, - ecs_edge_t *edge, - ecs_id_t id) -{ - ecs_table_t *next = find_or_create_table_without_id(world, node, id); + /* If next token is a singleton, assign identifier to pred and subject */ + } else if (ptr[0] == TOK_SINGLETON) { + ptr ++; + if (valid_token_start_char(ptr[0])) { + ptr = ecs_parse_identifier(name, expr, ptr, token); + if (!ptr) { + goto error; + } - edge->next = next; + goto parse_singleton; - compute_table_diff(world, node, next, edge, id); + } else { + ecs_parser_error(name, expr, (ptr - expr), + "expected identifier after singleton operator"); + goto error; + } - if (node != next) { - flecs_register_remove_ref(world, node, id); + /* Pair with implicit subject */ + } else if (ptr[0] == TOK_PAREN_OPEN) { + goto parse_pair; + + /* Nothing else expected here */ + } else { + parser_unexpected_char(name, expr, ptr, ptr[0]); + goto error; } - return next; -} +parse_role: + term.role = parse_role(name, expr, (ptr - expr), token); + if (!term.role) { + goto error; + } -static -ecs_table_t* find_or_create_table_with( - ecs_world_t *world, - ecs_table_t *node, - ecs_edge_t *edge, - ecs_id_t id) -{ - ecs_table_t *next = find_or_create_table_with_id(world, node, id); + ptr = ecs_parse_whitespace(ptr); - if (ECS_HAS_ROLE(id, PAIR) && ECS_PAIR_RELATION(id) == EcsIsA) { - ecs_entity_t base = ecs_pair_object(world, id); - next = find_or_create_table_with_isa(world, next, base); - } + /* If next token is the source token, this is an empty source */ + if (valid_token_start_char(ptr[0])) { + ptr = ecs_parse_identifier(name, expr, ptr, token); + if (!ptr) { + goto error; + } - edge->next = next; + /* If not, it's a predicate */ + goto parse_predicate; - compute_table_diff(world, node, next, edge, id); + } else if (ptr[0] == TOK_PAREN_OPEN) { + goto parse_pair; + } else { + ecs_parser_error(name, expr, (ptr - expr), + "expected identifier after role"); + goto error; + } - if (node != next) { - flecs_register_add_ref(world, node, id); +parse_predicate: + if (parse_identifier(token, &term.pred)) { + ecs_parser_error(name, expr, (ptr - expr), + "invalid identifier '%s'", token); + goto error; } - return next; -} + /* Set expression */ + if (ptr[0] == TOK_COLON) { + ptr = ecs_parse_whitespace(ptr + 1); + ptr = parse_set_expr(world, name, expr, (ptr - expr), ptr, NULL, + &term.pred, TOK_COLON); + if (!ptr) { + goto error; + } -static -void populate_diff( - ecs_table_t *table, - ecs_edge_t *edge, - ecs_id_t *add_ptr, - ecs_id_t *remove_ptr, - ecs_table_diff_t *out) -{ - if (out) { - int32_t di = edge->diff_index; - if (di > 0) { - ecs_assert(!add_ptr || !ECS_HAS_ROLE(add_ptr[0], CASE), - ECS_INTERNAL_ERROR, NULL); - ecs_assert(!remove_ptr || !ECS_HAS_ROLE(remove_ptr[0], CASE), - ECS_INTERNAL_ERROR, NULL); - *out = ecs_vector_first(table->node.diffs, ecs_table_diff_t)[di - 1]; - } else { - out->on_set.count = 0; + ptr = ecs_parse_whitespace(ptr); - if (add_ptr) { - out->added.array = add_ptr; - out->added.count = 1; - } else { - out->added.count = 0; - } + if (ptr[0] == TOK_AND || !ptr[0]) { + goto parse_done; + } - if (remove_ptr) { - out->removed.array = remove_ptr; - out->removed.count = 1; - if (di == 0) { - out->un_set.array = remove_ptr; - out->un_set.count = 1; - } else { - out->un_set.count = 0; - } - } else { - out->removed.count = 0; - out->un_set.count = 0; - } + if (ptr[0] != TOK_COLON) { + ecs_parser_error(name, expr, (ptr - expr), + "unexpected token '%c' after predicate set expression", ptr[0]); + goto error; } - } -} -ecs_table_t* flecs_table_traverse_remove( - ecs_world_t *world, - ecs_table_t *node, - ecs_id_t *id_ptr, - ecs_table_diff_t *diff) -{ - ecs_poly_assert(world, ecs_world_t); + ptr = ecs_parse_whitespace(ptr + 1); + } else { + ptr = ecs_parse_whitespace(ptr); + } + + if (ptr[0] == TOK_PAREN_OPEN) { + ptr ++; + if (ptr[0] == TOK_PAREN_CLOSE) { + term.subj.set.mask = EcsNothing; + ptr ++; + ptr = ecs_parse_whitespace(ptr); + } else { + ptr = parse_arguments( + world, name, expr, (ptr - expr), ptr, token, &term); + } - node = node ? node : &world->store.root; + goto parse_done; + } - /* Removing 0 from an entity is not valid */ - ecs_check(id_ptr != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(id_ptr[0] != 0, ECS_INVALID_PARAMETER, NULL); + goto parse_done; - ecs_id_t id = id_ptr[0]; - ecs_edge_t *edge = ensure_edge(&node->node.remove, id); - ecs_table_t *next = edge->next; +parse_pair: + ptr = ecs_parse_identifier(name, expr, ptr + 1, token); + if (!ptr) { + goto error; + } - if (!next) { - next = find_or_create_table_without(world, node, edge, id); - ecs_assert(next != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(edge->next != NULL, ECS_INTERNAL_ERROR, NULL); + if (ptr[0] == TOK_AND) { + ptr ++; + term.subj.entity = EcsThis; + goto parse_pair_predicate; + } else if (ptr[0] == TOK_PAREN_CLOSE) { + term.subj.entity = EcsThis; + goto parse_pair_predicate; + } else { + parser_unexpected_char(name, expr, ptr, ptr[0]); + goto error; } - populate_diff(node, edge, NULL, id_ptr, diff); +parse_pair_predicate: + if (parse_identifier(token, &term.pred)) { + ecs_parser_error(name, expr, (ptr - expr), + "invalid identifier '%s'", token); + goto error; + } - return next; -error: - return NULL; -} + ptr = ecs_parse_whitespace(ptr); + if (valid_token_start_char(ptr[0])) { + ptr = ecs_parse_identifier(name, expr, ptr, token); + if (!ptr) { + goto error; + } -ecs_table_t* flecs_table_traverse_add( - ecs_world_t *world, - ecs_table_t *node, - ecs_id_t *id_ptr, - ecs_table_diff_t *diff) -{ - ecs_poly_assert(world, ecs_world_t); + if (ptr[0] == TOK_PAREN_CLOSE) { + ptr ++; + goto parse_pair_object; + } else { + parser_unexpected_char(name, expr, ptr, ptr[0]); + goto error; + } + } else if (ptr[0] == TOK_PAREN_CLOSE) { + /* No object */ + ptr ++; + goto parse_done; + } else { + ecs_parser_error(name, expr, (ptr - expr), + "expected pair object or ')'"); + goto error; + } - node = node ? node : &world->store.root; +parse_pair_object: + if (parse_identifier(token, &term.obj)) { + ecs_parser_error(name, expr, (ptr - expr), + "invalid identifier '%s'", token); + goto error; + } - /* Adding 0 to an entity is not valid */ - ecs_check(id_ptr != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(id_ptr[0] != 0, ECS_INVALID_PARAMETER, NULL); + if (term.role != 0) { + if (term.role != ECS_PAIR && term.role != ECS_CASE) { + ecs_parser_error(name, expr, (ptr - expr), + "invalid combination of role '%s' with pair", + ecs_role_str(term.role)); + goto error; + } + } else { + term.role = ECS_PAIR; + } - ecs_id_t id = id_ptr[0]; - ecs_edge_t *edge = ensure_edge(&node->node.add, id); - ecs_table_t *next = edge->next; + ptr = ecs_parse_whitespace(ptr); + goto parse_done; - if (!next) { - next = find_or_create_table_with(world, node, edge, id); - ecs_assert(next != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(edge->next != NULL, ECS_INTERNAL_ERROR, NULL); +parse_singleton: + if (parse_identifier(token, &term.pred)) { + ecs_parser_error(name, expr, (ptr - expr), + "invalid identifier '%s'", token); + goto error; } - populate_diff(node, edge, id_ptr, NULL, diff); + parse_identifier(token, &term.subj); + goto parse_done; + +parse_done: + *term_out = term; + return ptr; - return next; error: + ecs_term_fini(&term); + *term_out = (ecs_term_t){0}; return NULL; } static -bool ecs_entity_array_is_ordered( - const ecs_ids_t *entities) +bool is_valid_end_of_term( + const char *ptr) { - ecs_entity_t prev = 0; - ecs_entity_t *array = entities->array; - int32_t i, count = entities->count; - - for (i = 0; i < count; i ++) { - if (!array[i] && !prev) { - continue; - } - if (array[i] <= prev) { - return false; - } - prev = array[i]; - } - - return true; + if ((ptr[0] == TOK_AND) || /* another term with And operator */ + (ptr[0] == TOK_OR[0]) || /* another term with Or operator */ + (ptr[0] == '\n') || /* newlines are valid */ + (ptr[0] == '\0') || /* end of string */ + (ptr[0] == '/') || /* comment (in plecs) */ + (ptr[0] == '{') || /* scope (in plecs) */ + (ptr[0] == '}') || + (ptr[0] == ':') || /* inheritance (in plecs) */ + (ptr[0] == '=')) /* assignment (in plecs) */ + { + return true; + } + return false; } -static -int32_t ecs_entity_array_dedup( - ecs_entity_t *array, - int32_t count) +char* ecs_parse_term( + const ecs_world_t *world, + const char *name, + const char *expr, + const char *ptr, + ecs_term_t *term) { - int32_t j, k; - ecs_entity_t prev = array[0]; + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(ptr != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(term != NULL, ECS_INVALID_PARAMETER, NULL); - for (k = j = 1; k < count; j ++, k++) { - ecs_entity_t e = array[k]; - if (e == prev) { - k ++; - } + ecs_term_id_t *subj = &term->subj; - array[j] = e; - prev = e; + bool prev_or = false; + if (ptr != expr) { + if (ptr[0]) { + if (ptr[0] == ',') { + ptr ++; + } else if (ptr[0] == '|') { + ptr += 2; + prev_or = true; + } else { + ecs_parser_error(name, expr, (ptr - expr), + "invalid preceding token"); + } + } } - - return count - (k - j); -} - -static -ecs_table_t* find_or_create( - ecs_world_t *world, - const ecs_ids_t *ids) -{ - ecs_poly_assert(world, ecs_world_t); - - /* Make sure array is ordered and does not contain duplicates */ - int32_t type_count = ids->count; - ecs_id_t *ordered = NULL; - - if (!type_count) { - return &world->store.root; + + ptr = ecs_parse_eol_and_whitespace(ptr); + if (!ptr[0]) { + *term = (ecs_term_t){0}; + return (char*)ptr; } - if (!ecs_entity_array_is_ordered(ids)) { - ecs_size_t size = ECS_SIZEOF(ecs_entity_t) * type_count; - ordered = ecs_os_alloca(size); - ecs_os_memcpy(ordered, ids->array, size); - qsort(ordered, (size_t)type_count, sizeof(ecs_entity_t), - flecs_entity_compare_qsort); - type_count = ecs_entity_array_dedup(ordered, type_count); - } else { - ordered = ids->array; + if (ptr == expr && !strcmp(expr, "0")) { + return (char*)&ptr[1]; } - ecs_ids_t ordered_ids = { - .array = ordered, - .count = type_count - }; + int32_t prev_set = subj->set.mask; - ecs_table_t *table; - flecs_hashmap_result_t elem = flecs_hashmap_ensure( - world->store.table_map, &ordered_ids, ecs_table_t*); - if ((table = *(ecs_table_t**)elem.value)) { - return table; + /* Parse next element */ + ptr = parse_term(world, name, ptr, term); + if (!ptr) { + goto error; } - /* If we get here, table needs to be created which is only allowed when the - * application is not currently in progress */ - ecs_assert(!world->is_readonly, ECS_INTERNAL_ERROR, NULL); - - /* If we get here, the table has not been found, so create it. */ - ecs_table_t *result = create_table(world, &ordered_ids, elem); - - ecs_assert(ordered_ids.count == ecs_vector_count(result->type), - ECS_INTERNAL_ERROR, NULL); + /* Post-parse consistency checks */ - return result; -} + /* If next token is OR, term is part of an OR expression */ + if (!ecs_os_strncmp(ptr, TOK_OR, 2) || prev_or) { + /* An OR operator must always follow an AND or another OR */ + if (term->oper != EcsAnd) { + ecs_parser_error(name, expr, (ptr - expr), + "cannot combine || with other operators"); + goto error; + } -ecs_table_t* flecs_table_find_or_create( - ecs_world_t *world, - const ecs_ids_t *components) -{ - ecs_poly_assert(world, ecs_world_t); - return find_or_create(world, components); -} + term->oper = EcsOr; + } -void flecs_init_root_table( - ecs_world_t *world) -{ - ecs_poly_assert(world, ecs_world_t); + /* Term must either end in end of expression, AND or OR token */ + if (!is_valid_end_of_term(ptr)) { + ecs_parser_error(name, expr, (ptr - expr), + "expected end of expression or next term"); + goto error; + } - ecs_ids_t entities = { - .array = NULL, - .count = 0 - }; + /* If the term just contained a 0, the expression has nothing. Ensure + * that after the 0 nothing else follows */ + if (!ecs_os_strcmp(term->pred.name, "0")) { + if (ptr[0]) { + ecs_parser_error(name, expr, (ptr - expr), + "unexpected term after 0"); + goto error; + } - init_table(world, &world->store.root, &entities); + if (subj->set.mask != EcsDefaultSet || + (subj->entity && subj->entity != EcsThis) || + (subj->name && ecs_os_strcmp(subj->name, "This"))) + { + ecs_parser_error(name, expr, (ptr - expr), + "invalid combination of 0 with non-default subject"); + goto error; + } - /* Ensure table indices start at 1, as 0 is reserved for the root */ - uint64_t new_id = flecs_sparse_new_id(world->store.tables); - ecs_assert(new_id == 0, ECS_INTERNAL_ERROR, NULL); - (void)new_id; -} + subj->set.mask = EcsNothing; + ecs_os_free(term->pred.name); + term->pred.name = NULL; + } -void flecs_table_clear_edges( - ecs_world_t *world, - ecs_table_t *table) -{ - (void)world; - ecs_poly_assert(world, ecs_world_t); + /* Cannot combine EcsNothing with operators other than AND */ + if (term->oper != EcsAnd && subj->set.mask == EcsNothing) { + ecs_parser_error(name, expr, (ptr - expr), + "invalid operator for empty source"); + goto error; + } - int32_t i; - ecs_graph_node_t *node = &table->node; + /* Verify consistency of OR expression */ + if (prev_or && term->oper == EcsOr) { + /* Set expressions must be the same for all OR terms */ + if (subj->set.mask != prev_set) { + ecs_parser_error(name, expr, (ptr - expr), + "cannot combine different sources in OR expression"); + goto error; + } - ecs_os_free(node->add.lo); - ecs_os_free(node->remove.lo); - ecs_map_free(node->add.hi); - ecs_map_free(node->remove.hi); - node->add.lo = NULL; - node->remove.lo = NULL; - node->add.hi = NULL; - node->remove.hi = NULL; + term->oper = EcsOr; + } - int32_t count = ecs_vector_count(node->diffs); - ecs_table_diff_t *diffs = ecs_vector_first(node->diffs, ecs_table_diff_t); - for (i = 0; i < count; i ++) { - ecs_table_diff_t *diff = &diffs[i]; - ecs_os_free(diff->added.array); - ecs_os_free(diff->removed.array); - ecs_os_free(diff->on_set.array); - ecs_os_free(diff->un_set.array); + /* Automatically assign This if entity is not assigned and the set is + * nothing */ + if (subj->set.mask != EcsNothing) { + if (!subj->name) { + if (!subj->entity) { + subj->entity = EcsThis; + } + } } - ecs_vector_free(node->diffs); - node->diffs = NULL; -} + if (subj->name && !ecs_os_strcmp(subj->name, "0")) { + subj->entity = 0; + subj->set.mask = EcsNothing; + } -void flecs_table_clear_add_edge( - ecs_table_t *table, - ecs_id_t id) -{ - ecs_edge_t *edge = get_edge(&table->node.add, id); - if (edge) { - edge->next = NULL; - edge->diff_index = 0; + /* Process role */ + if (term->role == ECS_AND) { + term->oper = EcsAndFrom; + term->role = 0; + } else if (term->role == ECS_OR) { + term->oper = EcsOrFrom; + term->role = 0; + } else if (term->role == ECS_NOT) { + term->oper = EcsNotFrom; + term->role = 0; } -} -void flecs_table_clear_remove_edge( - ecs_table_t *table, - ecs_id_t id) -{ - ecs_edge_t *edge = get_edge(&table->node.remove, id); - if (edge) { - edge->next = NULL; - edge->diff_index = 0; + ptr = ecs_parse_whitespace(ptr); + + return (char*)ptr; +error: + if (term) { + ecs_term_fini(term); } + return NULL; } -/* Public convenience functions for traversing table graph */ -ecs_table_t* ecs_table_add_id( +#endif + +#ifdef FLECS_SYSTEM + + +static +void invoke_status_action( ecs_world_t *world, - ecs_table_t *table, - ecs_id_t id) + ecs_entity_t system, + const EcsSystem *system_data, + ecs_system_status_t status) { - return flecs_table_traverse_add(world, table, &id, NULL); + ecs_system_status_action_t action = system_data->status_action; + if (action) { + action(world, system, status, system_data->status_ctx); + } } -ecs_table_t* ecs_table_remove_id( +/* Invoked when system becomes active or inactive */ +void ecs_system_activate( ecs_world_t *world, - ecs_table_t *table, - ecs_id_t id) + ecs_entity_t system, + bool activate, + const EcsSystem *system_data) { - return flecs_table_traverse_remove(world, table, &id, NULL); -} - + ecs_assert(!world->is_readonly, ECS_INTERNAL_ERROR, NULL); -#define INIT_CACHE(it, f, term_count)\ - if (!it->f && term_count) {\ - if (term_count < ECS_TERM_CACHE_SIZE) {\ - it->f = it->cache.f;\ - it->cache.f##_alloc = false;\ - } else {\ - it->f = ecs_os_calloc(ECS_SIZEOF(*(it->f)) * term_count);\ - it->cache.f##_alloc = true;\ - }\ + if (activate) { + /* If activating system, ensure that it doesn't have the Inactive tag. + * Systems are implicitly activated so they are kept out of the main + * loop as long as they aren't used. They are not implicitly deactivated + * to prevent overhead in case of oscillating app behavior. + * After activation, systems that aren't matched with anything can be + * deactivated again by explicitly calling ecs_deactivate_systems. + */ + ecs_remove_id(world, system, EcsInactive); } -#define FINI_CACHE(it, f)\ - if (it->f) {\ - if (it->cache.f##_alloc) {\ - ecs_os_free((void*)it->f);\ - }\ - } - -void flecs_iter_init( - ecs_iter_t *it) -{ - INIT_CACHE(it, ids, it->term_count); - INIT_CACHE(it, subjects, it->term_count); - INIT_CACHE(it, match_indices, it->term_count); - INIT_CACHE(it, columns, it->term_count); - - if (!it->is_filter) { - INIT_CACHE(it, sizes, it->term_count); - INIT_CACHE(it, ptrs, it->term_count); - } else { - it->sizes = NULL; - it->ptrs = NULL; + if (!system_data) { + system_data = ecs_get(world, system, EcsSystem); + } + if (!system_data || !system_data->query) { + return; } - it->is_valid = true; -} + if (!activate) { + if (ecs_has_id(world, system, EcsDisabled)) { + if (!ecs_query_table_count(system_data->query)) { + /* If deactivating a disabled system that isn't matched with + * any active tables, there is nothing to deactivate. */ + return; + } + } + } -void flecs_iter_fini( - ecs_iter_t *it) -{ - ecs_check(it->is_valid == true, ECS_INVALID_PARAMETER, NULL); - it->is_valid = false; + /* Invoke system status action */ + invoke_status_action(world, system, system_data, + activate ? EcsSystemActivated : EcsSystemDeactivated); - FINI_CACHE(it, ids); - FINI_CACHE(it, columns); - FINI_CACHE(it, subjects); - FINI_CACHE(it, sizes); - FINI_CACHE(it, ptrs); - FINI_CACHE(it, match_indices); -error: - return; + ecs_dbg_1("#[green]system#[reset] %s %s", + ecs_get_name(world, system), + activate ? "activated" : "deactivated"); } +/* Actually enable or disable system */ static -bool flecs_iter_populate_term_data( +void ecs_enable_system( ecs_world_t *world, - ecs_iter_t *it, - int32_t t, - int32_t column, - void **ptr_out, - ecs_size_t *size_out) + ecs_entity_t system, + EcsSystem *system_data, + bool enabled) { - bool is_shared = false; + ecs_poly_assert(world, ecs_world_t); + ecs_assert(!world->is_readonly, ECS_INTERNAL_ERROR, NULL); - if (!column) { - /* Term has no data. This includes terms that have Not operators. */ - goto no_data; + ecs_query_t *query = system_data->query; + if (!query) { + return; } - ecs_assert(it->terms != NULL, ECS_INTERNAL_ERROR, NULL); - - /* Filter terms may match with data but don't return it */ - if (it->terms[t].inout == EcsInOutFilter) { - goto no_data; + if (ecs_query_table_count(query)) { + /* Only (de)activate system if it has non-empty tables. */ + ecs_system_activate(world, system, enabled, system_data); + system_data = ecs_get_mut(world, system, EcsSystem, NULL); } + + /* Invoke action for enable/disable status */ + invoke_status_action( + world, system, system_data, + enabled ? EcsSystemEnabled : EcsSystemDisabled); +} - ecs_table_t *table; - ecs_vector_t *vec; - ecs_size_t size; - ecs_size_t align; - int32_t row; +/* -- Public API -- */ - if (column < 0) { - is_shared = true; +ecs_entity_t ecs_run_intern( + ecs_world_t *world, + ecs_stage_t *stage, + ecs_entity_t system, + EcsSystem *system_data, + int32_t stage_current, + int32_t stage_count, + FLECS_FLOAT delta_time, + int32_t offset, + int32_t limit, + void *param) +{ + FLECS_FLOAT time_elapsed = delta_time; + ecs_entity_t tick_source = system_data->tick_source; - /* Data is not from This */ - if (it->references) { - /* Iterator provides cached references for non-This terms */ - ecs_ref_t *ref = &it->references[-column - 1]; - if (ptr_out) ptr_out[0] = (void*)ecs_get_ref_w_id( - world, ref, ref->entity, ref->component); + /* Support legacy behavior */ + if (!param) { + param = system_data->ctx; + } - /* If cached references were provided, the code that populated - * the iterator also had a chance to cache sizes, so size array - * should already have been assigned. This saves us from having - * to do additional lookups to find the component size. */ - ecs_assert(size_out == NULL, ECS_INTERNAL_ERROR, NULL); - return true; + if (tick_source) { + const EcsTickSource *tick = ecs_get( + world, tick_source, EcsTickSource); + + if (tick) { + time_elapsed = tick->time_elapsed; + + /* If timer hasn't fired we shouldn't run the system */ + if (!tick->tick) { + return 0; + } } else { - ecs_entity_t subj = it->subjects[t]; - ecs_assert(subj != 0, ECS_INTERNAL_ERROR, NULL); + /* If a timer has been set but the timer entity does not have the + * EcsTimer component, don't run the system. This can be the result + * of a single-shot timer that has fired already. Not resetting the + * timer field of the system will ensure that the system won't be + * ran after the timer has fired. */ + return 0; + } + } - /* Don't use ecs_get_id directly. Instead, go directly to the - * storage so that we can get both the pointer and size */ - ecs_record_t *r = ecs_eis_get(world, subj); - ecs_assert(r != NULL && r->table != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_time_t time_start; + bool measure_time = world->measure_system_time; + if (measure_time) { + ecs_os_get_time(&time_start); + } - row = ECS_RECORD_TO_ROW(r->row); - table = r->table; + ecs_world_t *thread_ctx = world; + if (stage) { + thread_ctx = stage->thread_ctx; + } - ecs_id_t id = it->ids[t]; - ecs_table_t *s_table = table->storage_table; - ecs_table_record_t *tr; + ecs_defer_begin(thread_ctx); - if (!s_table || !(tr = flecs_get_table_record(world, s_table, id))){ - /* The entity has no components or the id is not a component */ - - ecs_id_t term_id = it->terms[t].id; - if (ECS_HAS_ROLE(term_id, SWITCH) || ECS_HAS_ROLE(term_id, CASE)) { - /* Edge case: if this is a switch. Find switch column in - * actual table, as its not in the storage table */ - tr = flecs_get_table_record(world, table, id); - ecs_assert(tr != NULL, ECS_INTERNAL_ERROR, NULL); - column = tr->column; - goto has_switch; - } else { - goto no_data; - } - } + /* Prepare the query iterator */ + ecs_iter_t it = ecs_query_iter_page( + thread_ctx, system_data->query, offset, limit); - /* We now have row and column, so we can get the storage for the id - * which gives us the pointer and size */ - column = tr->column; - ecs_column_t *s = &table->storage.columns[column]; - size = s->size; - align = s->alignment; - vec = s->data; - /* Fallthrough to has_data */ + it.system = system; + it.self = system_data->self; + it.delta_time = delta_time; + it.delta_system_time = time_elapsed; + it.frame_offset = offset; + it.param = param; + it.ctx = system_data->ctx; + it.binding_ctx = system_data->binding_ctx; + + ecs_iter_action_t action = system_data->action; + + /* If no filter is provided, just iterate tables & invoke action */ + if (stage_count <= 1 || !system_data->multi_threaded) { + while (ecs_query_next(&it)) { + action(&it); } } else { - /* Data is from This, use table from iterator */ - table = it->table; - if (!table) { - goto no_data; - } - - row = it->offset; - - int32_t storage_column = ecs_table_type_to_storage_index( - table, column - 1); - if (storage_column == -1) { - ecs_id_t id = it->terms[t].id; - if (ECS_HAS_ROLE(id, SWITCH) || ECS_HAS_ROLE(id, CASE)) { - goto has_switch; - } - goto no_data; + while (ecs_query_next_worker(&it, stage_current, stage_count)) { + action(&it); } + } - ecs_column_t *s = &table->storage.columns[storage_column]; - size = s->size; - align = s->alignment; - vec = s->data; - /* Fallthrough to has_data */ + if (measure_time) { + system_data->time_spent += (float)ecs_time_measure(&time_start); } -has_data: - if (ptr_out) ptr_out[0] = ecs_vector_get_t(vec, size, align, row); - if (size_out) size_out[0] = size; - return is_shared; + system_data->invoke_count ++; -has_switch: { - /* Edge case: if column is a switch we should return the vector with case - * identifiers. Will be replaced in the future with pluggable storage */ - ecs_switch_t *sw = table->storage.sw_columns[ - (column - 1) - table->sw_column_offset].data; - vec = flecs_switch_values(sw); - size = ECS_SIZEOF(ecs_entity_t); - align = ECS_ALIGNOF(ecs_entity_t); - goto has_data; - } + ecs_defer_end(thread_ctx); -no_data: - if (ptr_out) ptr_out[0] = NULL; - if (size_out) size_out[0] = 0; - return false; + return it.interrupted_by; } -void flecs_iter_populate_data( +/* -- Public API -- */ + +ecs_entity_t ecs_run_w_filter( ecs_world_t *world, - ecs_iter_t *it, - ecs_table_t *table, + ecs_entity_t system, + FLECS_FLOAT delta_time, int32_t offset, - int32_t count, - void **ptrs, - ecs_size_t *sizes) + int32_t limit, + void *param) { - it->table = table; - it->offset = offset; - it->count = count; - - if (table) { - it->type = it->table->type; - if (!count) { - count = it->count = ecs_table_count(table); - } - if (count) { - it->entities = ecs_vector_get( - table->storage.entities, ecs_entity_t, offset); - } else { - it->entities = NULL; - } - } - - if (it->is_filter) { - it->has_shared = false; - return; - } - - int t, term_count = it->term_count; - bool has_shared = false; + ecs_stage_t *stage = flecs_stage_from_world(&world); - if (ptrs && sizes) { - for (t = 0; t < term_count; t ++) { - int32_t column = it->columns[t]; - has_shared |= flecs_iter_populate_term_data(world, it, t, column, - &ptrs[t], - &sizes[t]); - } - } else { - for (t = 0; t < term_count; t ++) { - int32_t column = it->columns[t]; - void **ptr = NULL; - if (ptrs) { - ptr = &ptrs[t]; - } - ecs_size_t *size = NULL; - if (sizes) { - size = &sizes[t]; - } - has_shared |= flecs_iter_populate_term_data(world, it, t, column, - ptr, size); - } - } + EcsSystem *system_data = (EcsSystem*)ecs_get( + world, system, EcsSystem); + assert(system_data != NULL); - it->has_shared = has_shared; + return ecs_run_intern(world, stage, system, system_data, 0, 0, delta_time, + offset, limit, param); } -bool flecs_iter_next_row( - ecs_iter_t *it) +ecs_entity_t ecs_run_worker( + ecs_world_t *world, + ecs_entity_t system, + int32_t stage_current, + int32_t stage_count, + FLECS_FLOAT delta_time, + void *param) { - ecs_assert(it != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_stage_t *stage = flecs_stage_from_world(&world); - bool is_instanced = it->is_instanced; - if (!is_instanced) { - int32_t instance_count = it->instance_count; - int32_t count = it->count; - int32_t offset = it->offset; + EcsSystem *system_data = (EcsSystem*)ecs_get( + world, system, EcsSystem); + assert(system_data != NULL); - if (instance_count > count && offset < (instance_count - 1)) { - ecs_assert(count == 1, ECS_INTERNAL_ERROR, NULL); - int t, term_count = it->term_count; - for (t = 0; t < term_count; t ++) { - int32_t column = it->columns[t]; - if (column >= 0) { - void *ptr = it->ptrs[t]; - if (ptr) { - it->ptrs[t] = ECS_OFFSET(ptr, it->sizes[t]); - } - } - } + return ecs_run_intern( + world, stage, system, system_data, stage_current, stage_count, + delta_time, 0, 0, param); +} - if (it->entities) { - it->entities ++; - } - it->offset ++; +ecs_entity_t ecs_run( + ecs_world_t *world, + ecs_entity_t system, + FLECS_FLOAT delta_time, + void *param) +{ + return ecs_run_w_filter(world, system, delta_time, 0, 0, param); +} - return true; +ecs_query_t* ecs_system_get_query( + const ecs_world_t *world, + ecs_entity_t system) +{ + const EcsQuery *q = ecs_get(world, system, EcsQuery); + if (q) { + return q->query; + } else { + const EcsSystem *s = ecs_get(world, system, EcsSystem); + if (s) { + return s->query; + } else { + return NULL; } } - - return false; } -bool flecs_iter_next_instanced( - ecs_iter_t *it, - bool result) +void* ecs_get_system_ctx( + const ecs_world_t *world, + ecs_entity_t system) { - it->instance_count = it->count; - if (result && !it->is_instanced && it->count && it->has_shared) { - it->count = 1; - } - return result; + const EcsSystem *s = ecs_get(world, system, EcsSystem); + if (s) { + return s->ctx; + } else { + return NULL; + } } -/* --- Public API --- */ +void* ecs_get_system_binding_ctx( + const ecs_world_t *world, + ecs_entity_t system) +{ + const EcsSystem *s = ecs_get(world, system, EcsSystem); + if (s) { + return s->binding_ctx; + } else { + return NULL; + } +} -void* ecs_term_w_size( - const ecs_iter_t *it, +/* Generic constructor to initialize a component to 0 */ +static +void sys_ctor_init_zero( + ecs_world_t *world, + ecs_entity_t component, + const ecs_entity_t *entities, + void *ptr, size_t size, - int32_t term) + int32_t count, + void *ctx) { - ecs_check(it->is_valid, ECS_INVALID_PARAMETER, NULL); - ecs_check(!size || ecs_term_size(it, term) == size, - ECS_INVALID_PARAMETER, NULL); + (void)world; + (void)component; + (void)entities; + (void)ctx; + memset(ptr, 0, size * (size_t)count); +} +/* System destructor */ +static +void ecs_colsystem_dtor( + ecs_world_t *world, + ecs_entity_t component, + const ecs_entity_t *entities, + void *ptr, + size_t size, + int32_t count, + void *ctx) +{ + (void)component; + (void)ctx; (void)size; - if (!term) { - return it->entities; - } + EcsSystem *system_data = ptr; - if (!it->ptrs) { - return NULL; - } + int i; + for (i = 0; i < count; i ++) { + EcsSystem *system = &system_data[i]; + ecs_entity_t e = entities[i]; - return it->ptrs[term - 1]; -error: - return NULL; -} + if (!ecs_is_alive(world, e)) { + /* This can happen when a set is deferred while a system is being + * cleaned up. The operation will be discarded, but the destructor + * still needs to be invoked for the value */ + continue; + } -bool ecs_term_is_readonly( - const ecs_iter_t *it, - int32_t term_index) -{ - ecs_check(it->is_valid, ECS_INVALID_PARAMETER, NULL); - ecs_check(term_index > 0, ECS_INVALID_PARAMETER, NULL); + /* Invoke Deactivated action for active systems */ + if (system->query && ecs_query_table_count(system->query)) { + invoke_status_action(world, e, ptr, EcsSystemDeactivated); + } - ecs_term_t *term = &it->terms[term_index - 1]; - ecs_check(term != NULL, ECS_INVALID_PARAMETER, NULL); - - if (term->inout == EcsIn) { - return true; - } else { - ecs_term_id_t *subj = &term->subj; + /* Invoke Disabled action for enabled systems */ + if (!ecs_has_id(world, e, EcsDisabled)) { + invoke_status_action(world, e, ptr, EcsSystemDisabled); + } - if (term->inout == EcsInOutDefault) { - if (subj->entity != EcsThis) { - return true; - } + if (system->ctx_free) { + system->ctx_free(system->ctx); + } - if (!(subj->set.mask & EcsSelf)) { - return true; - } + if (system->status_ctx_free) { + system->status_ctx_free(system->status_ctx); } - } -error: - return false; -} + if (system->binding_ctx_free) { + system->binding_ctx_free(system->binding_ctx); + } -int32_t ecs_iter_find_column( - const ecs_iter_t *it, - ecs_entity_t component) -{ - ecs_check(it->is_valid, ECS_INVALID_PARAMETER, NULL); - ecs_check(it->table != NULL, ECS_INVALID_PARAMETER, NULL); - return ecs_type_index_of(it->table->type, 0, component); -error: - return -1; + if (system->query) { + ecs_query_fini(system->query); + } + } } -bool ecs_term_is_set( - const ecs_iter_t *it, - int32_t index) +static +void EnableMonitor( + ecs_iter_t *it) { - ecs_check(it->is_valid, ECS_INVALID_PARAMETER, NULL); + EcsSystem *sys = ecs_term(it, EcsSystem, 1); - int32_t column = it->columns[index - 1]; - if (!column) { - return false; - } else if (column < 0) { - if (it->references) { - column = -column - 1; - ecs_ref_t *ref = &it->references[column]; - return ref->entity != 0; - } else { - return true; + int32_t i; + for (i = 0; i < it->count; i ++) { + if (it->event == EcsOnAdd) { + ecs_enable_system(it->world, it->entities[i], &sys[i], true); + } else if (it->event == EcsOnRemove) { + ecs_enable_system(it->world, it->entities[i], &sys[i], false); } } - - return true; -error: - return false; } -void* ecs_iter_column_w_size( - const ecs_iter_t *it, - size_t size, - int32_t index) +ecs_entity_t ecs_system_init( + ecs_world_t *world, + const ecs_system_desc_t *desc) { - ecs_check(it->is_valid, ECS_INVALID_PARAMETER, NULL); - ecs_check(it->table != NULL, ECS_INVALID_PARAMETER, NULL); - (void)size; - - ecs_table_t *table = it->table; - int32_t storage_index = ecs_table_type_to_storage_index(table, index); - if (storage_index == -1) { - return NULL; - } - - ecs_column_t *columns = table->storage.columns; - ecs_column_t *column = &columns[storage_index]; - ecs_check(!size || (ecs_size_t)size == column->size, - ECS_INVALID_PARAMETER, NULL); - - void *ptr = ecs_vector_first_t( - column->data, column->size, column->alignment); - - return ECS_OFFSET(ptr, column->size * it->offset); -error: - return NULL; -} + ecs_poly_assert(world, ecs_world_t); + ecs_check(!world->is_readonly, ECS_INVALID_WHILE_ITERATING, NULL); -size_t ecs_iter_column_size( - const ecs_iter_t *it, - int32_t index) -{ - ecs_check(it->is_valid, ECS_INVALID_PARAMETER, NULL); - ecs_check(it->table != NULL, ECS_INVALID_PARAMETER, NULL); - - ecs_table_t *table = it->table; - int32_t storage_index = ecs_table_type_to_storage_index(table, index); - if (storage_index == -1) { + ecs_entity_t existing = desc->entity.entity; + ecs_entity_t result = ecs_entity_init(world, &desc->entity); + if (!result) { return 0; } - ecs_column_t *columns = table->storage.columns; - ecs_column_t *column = &columns[storage_index]; - - return flecs_ito(size_t, column->size); -error: - return 0; -} + bool added = false; + EcsSystem *system = ecs_get_mut(world, result, EcsSystem, &added); + if (added) { + ecs_check(desc->callback != NULL, ECS_INVALID_PARAMETER, NULL); -char* ecs_iter_str( - const ecs_iter_t *it) -{ - ecs_world_t *world = it->world; - ecs_strbuf_t buf = ECS_STRBUF_INIT; - int i; + memset(system, 0, sizeof(EcsSystem)); - if (it->term_count) { - ecs_strbuf_list_push(&buf, "term: ", ","); - for (i = 0; i < it->term_count; i ++) { - ecs_id_t id = ecs_term_id(it, i + 1); - char *str = ecs_id_str(world, id); - ecs_strbuf_list_appendstr(&buf, str); - ecs_os_free(str); - } - ecs_strbuf_list_pop(&buf, "\n"); + ecs_query_desc_t query_desc = desc->query; + query_desc.filter.name = desc->entity.name; + query_desc.system = result; - ecs_strbuf_list_push(&buf, "subj: ", ","); - for (i = 0; i < it->term_count; i ++) { - ecs_entity_t subj = ecs_term_source(it, i + 1); - char *str = ecs_get_fullpath(world, subj); - ecs_strbuf_list_appendstr(&buf, str); - ecs_os_free(str); + ecs_query_t *query = ecs_query_init(world, &query_desc); + if (!query) { + ecs_delete(world, result); + return 0; } - ecs_strbuf_list_pop(&buf, "\n"); - } - if (it->variable_count) { - int32_t actual_count = 0; - for (i = 0; i < it->variable_count; i ++) { - const char *var_name = it->variable_names[i]; - if (!var_name || var_name[0] == '_' || var_name[0] == '.') { - /* Skip anonymous variables */ - continue; - } + /* Re-obtain pointer, as query may have added components */ + system = ecs_get_mut(world, result, EcsSystem, &added); + ecs_assert(added == false, ECS_INTERNAL_ERROR, NULL); - ecs_entity_t var = it->variables[i]; - if (!var) { - /* Skip table variables */ - continue; - } + /* Prevent the system from moving while we're initializing */ + ecs_defer_begin(world); - if (!actual_count) { - ecs_strbuf_list_push(&buf, "vars: ", ","); - } + system->entity = result; + system->query = query; - char *str = ecs_get_fullpath(world, var); - ecs_strbuf_list_append(&buf, "%s=%s", var_name, str); - ecs_os_free(str); + system->action = desc->callback; + system->status_action = desc->status_callback; - actual_count ++; - } - if (actual_count) { - ecs_strbuf_list_pop(&buf, "\n"); - } - } + system->self = desc->self; + system->ctx = desc->ctx; + system->status_ctx = desc->status_ctx; + system->binding_ctx = desc->binding_ctx; - if (it->count) { - ecs_strbuf_appendstr(&buf, "this:\n"); - for (i = 0; i < it->count; i ++) { - ecs_entity_t e = it->entities[i]; - char *str = ecs_get_fullpath(world, e); - ecs_strbuf_appendstr(&buf, " - "); - ecs_strbuf_appendstr(&buf, str); - ecs_strbuf_appendstr(&buf, "\n"); - ecs_os_free(str); - } - } + system->ctx_free = desc->ctx_free; + system->status_ctx_free = desc->status_ctx_free; + system->binding_ctx_free = desc->binding_ctx_free; - return ecs_strbuf_get(&buf); -} + system->tick_source = desc->tick_source; -void ecs_iter_poly( - const ecs_world_t *world, - const ecs_poly_t *poly, - ecs_iter_t *iter_out, - ecs_term_t *filter) -{ - ecs_iterable_t *iterable = ecs_get_iterable(poly); - iterable->init(world, poly, iter_out, filter); -} + system->multi_threaded = desc->multi_threaded; + system->no_staging = desc->no_staging; -bool ecs_iter_next( - ecs_iter_t *iter) -{ - ecs_check(iter != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(iter->next != NULL, ECS_INVALID_PARAMETER, NULL); - return iter->next(iter); -error: - return false; -} + /* If tables have been matched with this system it is active, and we + * should activate the in terms, if any. This will ensure that any + * OnDemand systems get enabled. */ + if (ecs_query_table_count(query)) { + ecs_system_activate(world, result, true, system); + } else { + /* If system isn't matched with any tables, mark it as inactive. This + * causes it to be ignored by the main loop. When the system matches + * with a table it will be activated. */ + ecs_add_id(world, result, EcsInactive); + } -bool ecs_iter_count( - ecs_iter_t *it) -{ - ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); - int32_t count = 0; - while (ecs_iter_next(it)) { - count += it->count; - } - return count; -error: - return 0; -} + if (!ecs_has_id(world, result, EcsDisabled)) { + /* If system is already enabled, generate enable status. The API + * should guarantee that it exactly matches enable-disable + * notifications and activate-deactivate notifications. */ + invoke_status_action(world, result, system, EcsSystemEnabled); + /* If column system has active (non-empty) tables, also generate the + * activate status. */ + if (ecs_query_table_count(system->query)) { + invoke_status_action(world, result, system, EcsSystemActivated); + } + } -static -int32_t count_events( - const ecs_entity_t *events) -{ - int32_t i; + if (desc->interval != 0 || desc->rate != 0 || desc->tick_source != 0) { +#ifdef FLECS_TIMER + if (desc->interval != 0) { + ecs_set_interval(world, result, desc->interval); + } - for (i = 0; i < ECS_TRIGGER_DESC_EVENT_COUNT_MAX; i ++) { - if (!events[i]) { - break; + if (desc->rate) { + ecs_set_rate(world, result, desc->rate, desc->tick_source); + } else if (desc->tick_source) { + ecs_set_tick_source(world, result, desc->tick_source); + } +#else + ecs_abort(ECS_UNSUPPORTED, "timer module not available"); +#endif } - } - return i; -} + ecs_modified(world, result, EcsSystem); -static -ecs_entity_t get_actual_event( - ecs_trigger_t *trigger, - ecs_entity_t event) -{ - /* If operator is Not, reverse the event */ - if (trigger->term.oper == EcsNot) { - if (event == EcsOnAdd) { - event = EcsOnRemove; - } else if (event == EcsOnRemove) { - event = EcsOnAdd; + if (desc->entity.name) { + ecs_trace("#[green]system#[reset] %s created", + ecs_get_name(world, result)); } - } - return event; -} + ecs_defer_end(world); + } else { + const char *expr_desc = desc->query.filter.expr; + const char *expr_sys = system->query->filter.expr; -static -void unregister_event_trigger( - ecs_event_record_t *evt, - ecs_id_t id) -{ - if (ecs_map_remove(evt->event_ids, id) == 0) { - ecs_map_free(evt->event_ids); - evt->event_ids = NULL; - } -} + /* Only check expression if it's set */ + if (expr_desc) { + if (expr_sys && !strcmp(expr_sys, "0")) expr_sys = NULL; + if (expr_desc && !strcmp(expr_desc, "0")) expr_desc = NULL; -static -void inc_trigger_count( - ecs_world_t *world, - ecs_entity_t event, - ecs_event_record_t *evt, - ecs_id_t id, - int32_t value) -{ - ecs_event_id_record_t *idt = ecs_map_ensure( - evt->event_ids, ecs_event_id_record_t, id); - ecs_assert(idt != NULL, ECS_INTERNAL_ERROR, NULL); - - int32_t result = idt->trigger_count + value; - if (result == 1) { - /* Notify framework that there are triggers for the event/id. This - * allows parts of the code to skip event evaluation early */ - flecs_notify_tables(world, id, &(ecs_table_event_t){ - .kind = EcsTableTriggersForId, - .event = event - }); - } else if (result == 0) { - /* Ditto, but the reverse */ - flecs_notify_tables(world, id, &(ecs_table_event_t){ - .kind = EcsTableNoTriggersForId, - .event = event - }); + if (expr_sys && expr_desc) { + if (strcmp(expr_sys, expr_desc)) { + ecs_abort(ECS_ALREADY_DEFINED, desc->entity.name); + } + } else { + if (expr_sys != expr_desc) { + ecs_abort(ECS_ALREADY_DEFINED, desc->entity.name); + } + } - /* Remove admin for id for event */ - if (!idt->triggers && !idt->set_triggers) { - unregister_event_trigger(evt, id); + /* If expr_desc is not set, and this is an existing system, don't throw + * an error because we could be updating existing parameters of the + * system such as the context or system callback. However, if no + * entity handle was provided, we have to assume that the application is + * trying to redeclare the system. */ + } else if (!existing) { + if (expr_sys) { + ecs_abort(ECS_ALREADY_DEFINED, desc->entity.name); + } } - } -} - -static -void register_trigger_for_id( - ecs_world_t *world, - ecs_observable_t *observable, - ecs_trigger_t *trigger, - ecs_id_t id, - bool register_for_set) -{ - ecs_sparse_t *events = observable->events; - ecs_assert(events != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_id_t term_id = trigger->term.id; - int i; - for (i = 0; i < trigger->event_count; i ++) { - ecs_entity_t event = get_actual_event(trigger, trigger->events[i]); + if (desc->callback) { + system->action = desc->callback; + } + if (desc->ctx) { + system->ctx = desc->ctx; + } + if (desc->binding_ctx) { + system->binding_ctx = desc->binding_ctx; + } + if (desc->query.filter.instanced) { + system->query->filter.instanced = true; + } + if (desc->multi_threaded) { + system->multi_threaded = desc->multi_threaded; + } + if (desc->no_staging) { + system->no_staging = desc->no_staging; + } + } - /* Get triggers for event */ - ecs_event_record_t *evt = flecs_sparse_ensure( - events, ecs_event_record_t, event); - ecs_assert(evt != NULL, ECS_INTERNAL_ERROR, NULL); + return result; +error: + return 0; +} - if (!evt->event_ids) { - evt->event_ids = ecs_map_new(ecs_event_id_record_t, 1); - } +void FlecsSystemImport( + ecs_world_t *world) +{ + ECS_MODULE(world, FlecsSystem); - /* Get triggers for (component) id for event */ - ecs_event_id_record_t *idt = ecs_map_ensure( - evt->event_ids, ecs_event_id_record_t, id); - ecs_assert(idt != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_set_name_prefix(world, "Ecs"); - ecs_map_t **triggers; - if (!register_for_set) { - triggers = &idt->triggers; - } else { - triggers = &idt->set_triggers; - } + flecs_bootstrap_component(world, EcsSystem); + flecs_bootstrap_component(world, EcsTickSource); - if (!triggers[0]) { - triggers[0] = ecs_map_new(ecs_trigger_t*, 1); - } + /* Put following tags in flecs.core so they can be looked up + * without using the flecs.systems prefix. */ + ecs_entity_t old_scope = ecs_set_scope(world, EcsFlecsCore); + flecs_bootstrap_tag(world, EcsInactive); + flecs_bootstrap_tag(world, EcsMonitor); + ecs_set_scope(world, old_scope); - ecs_map_ensure(triggers[0], ecs_trigger_t*, trigger->id)[0] = trigger; + /* Bootstrap ctor and dtor for EcsSystem */ + ecs_set_component_actions_w_id(world, ecs_id(EcsSystem), + &(EcsComponentLifecycle) { + .ctor = sys_ctor_init_zero, + .dtor = ecs_colsystem_dtor + }); - inc_trigger_count(world, event, evt, term_id, 1); - } + ecs_observer_init(world, &(ecs_observer_desc_t) { + .entity.name = "EnableMonitor", + .filter.terms = { + { .id = ecs_id(EcsSystem) }, + { .id = EcsDisabled, .oper = EcsNot }, + }, + .events = {EcsMonitor}, + .callback = EnableMonitor + }); } -static -void register_trigger( - ecs_world_t *world, - ecs_observable_t *observable, - ecs_trigger_t *trigger) -{ - ecs_term_t *term = &trigger->term; - if (term->subj.set.mask & EcsSelf) { - register_trigger_for_id(world, observable, trigger, term->id, false); - } - if (trigger->term.subj.set.mask & EcsSuperSet) { - ecs_id_t pair = ecs_pair(term->subj.set.relation, EcsWildcard); - register_trigger_for_id(world, observable, trigger, pair, true); - } -} +#endif -static -void unregister_trigger_for_id( - ecs_world_t *world, - ecs_observable_t *observable, - ecs_trigger_t *trigger, - ecs_id_t id, - bool unregister_for_set) -{ - ecs_sparse_t *events = observable->events; - ecs_assert(events != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_id_t term_id = trigger->term.id; +#ifdef FLECS_DEPRECATED - int i; - for (i = 0; i < trigger->event_count; i ++) { - ecs_entity_t event = get_actual_event(trigger, trigger->events[i]); - /* Get triggers for event */ - ecs_event_record_t *evt = flecs_sparse_get( - events, ecs_event_record_t, event); - ecs_assert(evt != NULL, ECS_INTERNAL_ERROR, NULL); +#endif - /* Get triggers for (component) id */ - ecs_event_id_record_t *idt = ecs_map_get( - evt->event_ids, ecs_event_id_record_t, id); - ecs_assert(idt != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_map_t **id_triggers; - if (unregister_for_set) { - id_triggers = &idt->set_triggers; - } else { - id_triggers = &idt->triggers; - } +#define ECS_NAME_BUFFER_LENGTH (64) - if (ecs_map_remove(id_triggers[0], trigger->id) == 0) { - ecs_map_free(id_triggers[0]); - id_triggers[0] = NULL; - } +static +bool path_append( + const ecs_world_t *world, + ecs_entity_t parent, + ecs_entity_t child, + const char *sep, + const char *prefix, + ecs_strbuf_t *buf) +{ + ecs_poly_assert(world, ecs_world_t); - inc_trigger_count(world, event, evt, term_id, -1); + ecs_entity_t cur = 0; + char buff[22]; + const char *name; - if (id != term_id) { - /* Id is different from term_id in case of a set trigger. If they're - * the same, inc_trigger_count could already have done cleanup */ - if (!idt->triggers && !idt->set_triggers && !idt->trigger_count) { - unregister_event_trigger(evt, id); + if (ecs_is_valid(world, child)) { + cur = ecs_get_object(world, child, EcsChildOf, 0); + if (cur) { + if (cur != parent && cur != EcsFlecsCore) { + path_append(world, parent, cur, sep, prefix, buf); + ecs_strbuf_appendstr(buf, sep); } + } else if (prefix) { + ecs_strbuf_appendstr(buf, prefix); } - } -} -static -void unregister_trigger( - ecs_world_t *world, - ecs_observable_t *observable, - ecs_trigger_t *trigger) -{ - ecs_term_t *term = &trigger->term; - if (term->subj.set.mask & EcsSelf) { - unregister_trigger_for_id(world, observable, trigger, term->id, false); + name = ecs_get_name(world, child); + if (!name || !ecs_os_strlen(name)) { + ecs_os_sprintf(buff, "%u", (uint32_t)child); + name = buff; + } } else { - ecs_id_t pair = ecs_pair(term->subj.set.relation, EcsWildcard); - unregister_trigger_for_id(world, observable, trigger, pair, true); + ecs_os_sprintf(buff, "%u", (uint32_t)child); + name = buff; } + + ecs_strbuf_appendstr(buf, name); + + return cur != 0; } -static -ecs_map_t* get_triggers_for_event( - const ecs_observable_t *observable, - ecs_entity_t event) +ecs_hashed_string_t ecs_get_hashed_string( + const char *name, + ecs_size_t length, + uint64_t hash) { - ecs_check(observable != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_assert(event != 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(!length || length == ecs_os_strlen(name), + ECS_INTERNAL_ERROR, NULL); - ecs_sparse_t *events = observable->events; - ecs_assert(events != NULL, ECS_INTERNAL_ERROR, NULL); + if (!length) { + length = ecs_os_strlen(name); + } - const ecs_event_record_t *evt = flecs_sparse_get( - events, ecs_event_record_t, event); - - if (evt) { - return evt->event_ids; + ecs_assert(!hash || hash == flecs_hash(name, length), + ECS_INTERNAL_ERROR, NULL); + + if (!hash) { + hash = flecs_hash(name, length); } -error: - return NULL; + return (ecs_hashed_string_t) { + .value = (char*)name, + .length = length, + .hash = hash + }; } static -ecs_event_id_record_t* get_triggers_for_id( - const ecs_map_t *evt, - ecs_id_t id) +ecs_entity_t find_by_name( + const ecs_hashmap_t *map, + const char *name, + ecs_size_t length, + uint64_t hash) { - return ecs_map_get(evt, ecs_event_id_record_t, id); -} + ecs_hashed_string_t key = ecs_get_hashed_string(name, length, hash); -ecs_event_id_record_t* flecs_triggers_for_id( - const ecs_poly_t *object, - ecs_id_t id, - ecs_entity_t event) -{ - ecs_observable_t *observable = ecs_get_observable(object); - const ecs_map_t *evt = get_triggers_for_event(observable, event); - if (!evt) { - return NULL; + ecs_entity_t *e = flecs_hashmap_get(*map, &key, ecs_entity_t); + + if (!e) { + return 0; } - return get_triggers_for_id(evt, id); + return *e; } static -void init_iter( - ecs_iter_t *it, - bool *iter_set) +void register_by_name( + ecs_hashmap_t *map, + ecs_entity_t entity, + const char *name, + ecs_size_t length, + uint64_t hash) { - ecs_assert(it != NULL, ECS_INTERNAL_ERROR, NULL); - - if (*iter_set) { - return; - } - - flecs_iter_init(it); - - *iter_set = true; - - it->ids[0] = it->event_id; - - ecs_assert(it->table != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(!it->world->is_readonly, ECS_INTERNAL_ERROR, NULL); - ecs_assert(it->count > 0, ECS_INTERNAL_ERROR, NULL); - ecs_assert(it->offset < ecs_table_count(it->table), - ECS_INTERNAL_ERROR, NULL); - ecs_assert((it->offset + it->count) <= ecs_table_count(it->table), - ECS_INTERNAL_ERROR, NULL); + ecs_check(entity != 0, ECS_INVALID_PARAMETER, NULL); + ecs_check(name != NULL, ECS_INVALID_PARAMETER, NULL); - int32_t index = ecs_type_match(it->world, it->table, it->type, 0, - it->event_id, EcsIsA, 0, 0, it->subjects, NULL, NULL); + ecs_hashed_string_t key = ecs_get_hashed_string(name, length, hash); - if (index == -1) { - it->columns[0] = 0; - } else if (it->subjects[0]) { - it->columns[0] = -index - 1; + ecs_entity_t existing = find_by_name(map, name, key.length, key.hash); + if (existing) { + if (existing != entity) { + ecs_abort(ECS_ALREADY_DEFINED, + "conflicting entity registered with name '%s'", name); + } } else { - it->columns[0] = index + 1; + key.value = ecs_os_strdup(key.value); } - ecs_term_t term = { - .id = it->event_id - }; + flecs_hashmap_result_t hmr = flecs_hashmap_ensure( + *map, &key, ecs_entity_t); - it->terms = &term; - it->term_count = 1; - flecs_iter_populate_data(it->world, it, it->table, it->offset, - it->count, it->ptrs, it->sizes); + *((ecs_entity_t*)hmr.value) = entity; +error: + return; } static -bool ignore_table( - ecs_trigger_t *t, - ecs_table_t *table) +bool is_number( + const char *name) { - if (!table) { + ecs_assert(name != NULL, ECS_INTERNAL_ERROR, NULL); + + if (!isdigit(name[0])) { return false; } - if (!t->match_prefab && (table->flags & EcsTableIsPrefab)) { - return true; - } - if (!t->match_disabled && (table->flags & EcsTableIsDisabled)) { - return true; - } - - return false; -} - -static -void notify_self_triggers( - ecs_iter_t *it, - const ecs_map_t *triggers) -{ - ecs_assert(triggers != NULL, ECS_INTERNAL_ERROR, NULL); - - void **ptrs = it->ptrs; - ecs_size_t *sizes = it->sizes; - - ecs_map_iter_t mit = ecs_map_iter(triggers); - ecs_trigger_t *t; - while ((t = ecs_map_next_ptr(&mit, ecs_trigger_t*, NULL))) { - if (ignore_table(t, it->table)) { - continue; - } - - bool is_filter = t->term.inout == EcsInOutFilter; - - it->system = t->entity; - it->self = t->self; - it->ctx = t->ctx; - it->binding_ctx = t->binding_ctx; - it->term_index = t->term.index; - it->terms = &t->term; - it->is_filter = is_filter; + ecs_size_t i, length = ecs_os_strlen(name); + for (i = 1; i < length; i ++) { + char ch = name[i]; - if (is_filter) { - it->ptrs = NULL; - it->sizes = NULL; + if (!isdigit(ch)) { + break; } - - t->action(it); - - it->ptrs = ptrs; - it->sizes = sizes; } -} - -static -void notify_set_triggers( - ecs_iter_t *it, - const ecs_map_t *triggers) -{ - ecs_assert(triggers != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(it->count != 0, ECS_INTERNAL_ERROR, NULL); - - ecs_map_iter_t mit = ecs_map_iter(triggers); - ecs_trigger_t *t; - while ((t = ecs_map_next_ptr(&mit, ecs_trigger_t*, NULL))) { - if (ignore_table(t, it->table)) { - continue; - } - - if (flecs_term_match_table(it->world, &t->term, it->table, it->type, - it->ids, it->columns, it->subjects, NULL, true)) - { - if (!it->subjects[0]) { - /* Do not match owned components */ - continue; - } - - ecs_entity_t event_id = it->event_id; - it->event_id = t->term.id; - - it->ids[0] = t->term.id; - it->system = t->entity; - it->self = t->self; - it->ctx = t->ctx; - it->binding_ctx = t->binding_ctx; - it->term_index = t->term.index; - it->terms = &t->term; - - if (it->count == 1 || t->instanced || !it->sizes[0]) { - it->is_instanced = t->instanced; - t->action(it); - it->is_instanced = false; - } else { - int32_t i, count = it->count; - ecs_entity_t *entities = it->entities; - it->count = 1; - for (i = 0; i < count; i ++) { - it->entities = &entities[i]; - t->action(it); - } - it->count = count; - it->entities = entities; - } - it->event_id = event_id; - } - } + return i >= length; } -static -void notify_triggers_for_id( - const ecs_map_t *evt, - ecs_id_t event_id, - ecs_iter_t *it, - bool *iter_set) +static +ecs_entity_t name_to_id( + const ecs_world_t *world, + const char *name) { - const ecs_event_id_record_t *idt = get_triggers_for_id(evt, event_id); - if (idt) { - if (idt->triggers) { - init_iter(it, iter_set); - notify_self_triggers(it, idt->triggers); - } - if (idt->set_triggers) { - init_iter(it, iter_set); - notify_set_triggers(it, idt->set_triggers); - } + long int result = atol(name); + ecs_assert(result >= 0, ECS_INTERNAL_ERROR, NULL); + ecs_entity_t alive = ecs_get_alive(world, (ecs_entity_t)result); + if (alive) { + return alive; + } else { + return (ecs_entity_t)result; } } static -void notify_set_triggers_for_id( - const ecs_map_t *evt, - ecs_iter_t *it, - bool *iter_set, - ecs_id_t set_id) +ecs_entity_t get_builtin( + const char *name) { - const ecs_event_id_record_t *idt = get_triggers_for_id(evt, set_id); - if (idt && idt->set_triggers) { - init_iter(it, iter_set); - notify_set_triggers(it, idt->set_triggers); + if (name[0] == '.' && name[1] == '\0') { + return EcsThis; + } else if (name[0] == '*' && name[1] == '\0') { + return EcsWildcard; } + + return 0; } static -void trigger_yield_existing( - ecs_world_t *world, - ecs_trigger_t *trigger) +bool is_sep( + const char **ptr, + const char *sep) { - ecs_iter_action_t callback = trigger->action; - - /* If yield existing is enabled, trigger for each thing that matches - * the event, if the event is iterable. */ - int i, count = trigger->event_count; - for (i = 0; i < count; i ++) { - ecs_entity_t evt = trigger->events[i]; - const EcsIterable *iterable = ecs_get(world, evt, EcsIterable); - if (!iterable) { - continue; - } - - ecs_iter_t it; - iterable->init(world, world, &it, &trigger->term); - it.system = trigger->entity; - it.ctx = trigger->ctx; - it.binding_ctx = trigger->binding_ctx; + ecs_size_t len = ecs_os_strlen(sep); - ecs_iter_next_action_t next = it.next; - ecs_assert(next != NULL, ECS_INTERNAL_ERROR, NULL); - while (next(&it)) { - callback(&it); - } + if (!ecs_os_strncmp(*ptr, sep, len)) { + *ptr += len; + return true; + } else { + return false; } } -void flecs_triggers_notify( - ecs_iter_t *it, - ecs_observable_t *observable, - ecs_ids_t *ids, - ecs_entity_t event) +static +const char* path_elem( + const char *path, + const char *sep, + int32_t *len) { - ecs_assert(ids != NULL && ids->count != 0, ECS_INTERNAL_ERROR, NULL); - ecs_assert(ids->array != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_entity_t events[2] = {event, EcsWildcard}; - int32_t e, i, ids_count = ids->count; - ecs_id_t *ids_array = ids->array; + const char *ptr; + char ch; + int32_t template_nesting = 0; + int32_t count = 0; - for (e = 0; e < 2; e ++) { - event = events[e]; - const ecs_map_t *evt = get_triggers_for_event(observable, event); - if (!evt) { - continue; + for (ptr = path; (ch = *ptr); ptr ++) { + if (ch == '<') { + template_nesting ++; + } else if (ch == '>') { + template_nesting --; } - it->event = event; - - for (i = 0; i < ids_count; i ++) { - ecs_id_t id = ids_array[i]; - bool iter_set = false; - - it->event_id = id; - - notify_triggers_for_id(evt, id, it, &iter_set); - - if (ECS_HAS_ROLE(id, PAIR)) { - ecs_entity_t pred = ECS_PAIR_RELATION(id); - ecs_entity_t obj = ECS_PAIR_OBJECT(id); - - notify_triggers_for_id(evt, ecs_pair(pred, EcsWildcard), - it, &iter_set); - - notify_triggers_for_id(evt, ecs_pair(EcsWildcard, obj), - it, &iter_set); + ecs_check(template_nesting >= 0, ECS_INVALID_PARAMETER, path); - notify_triggers_for_id(evt, ecs_pair(EcsWildcard, EcsWildcard), - it, &iter_set); - } else { - notify_triggers_for_id(evt, EcsWildcard, it, &iter_set); - } + if (!template_nesting && is_sep(&ptr, sep)) { + break; } + + count ++; + } + + if (len) { + *len = count; + } + + if (count) { + return ptr; + } else { + return NULL; } +error: + return NULL; } -void flecs_set_triggers_notify( - ecs_iter_t *it, - ecs_observable_t *observable, - ecs_ids_t *ids, - ecs_entity_t event, - ecs_id_t set_id) +static +ecs_entity_t get_parent_from_path( + const ecs_world_t *world, + ecs_entity_t parent, + const char **path_ptr, + const char *prefix, + bool new_entity) { - ecs_assert(ids != NULL && ids->count != 0, ECS_INTERNAL_ERROR, NULL); - ecs_assert(ids->array != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_entity_t events[2] = {event, EcsWildcard}; - int32_t e, i, ids_count = ids->count; - ecs_id_t *ids_array = ids->array; - - for (e = 0; e < 2; e ++) { - event = events[e]; - const ecs_map_t *evt = get_triggers_for_event(observable, event); - if (!evt) { - continue; + bool start_from_root = false; + const char *path = *path_ptr; + + if (prefix) { + ecs_size_t len = ecs_os_strlen(prefix); + if (!ecs_os_strncmp(path, prefix, len)) { + path += len; + parent = 0; + start_from_root = true; } + } - it->event = event; + if (!start_from_root && !parent && new_entity) { + parent = ecs_get_scope(world); + } - for (i = 0; i < ids_count; i ++) { - ecs_id_t id = ids_array[i]; - bool iter_set = false; + *path_ptr = path; - it->event_id = id; + return parent; +} - notify_set_triggers_for_id(evt, it, &iter_set, set_id); - } +static +void on_set_symbol(ecs_iter_t *it) { + EcsIdentifier *n = ecs_term(it, EcsIdentifier, 1); + ecs_world_t *world = it->world; + + int i; + for (i = 0; i < it->count; i ++) { + ecs_entity_t e = it->entities[i]; + register_by_name( + &world->symbols, e, n[i].value, n[i].length, n[i].hash); } } -ecs_entity_t ecs_trigger_init( - ecs_world_t *world, - const ecs_trigger_desc_t *desc) +static +uint64_t string_hash( + const void *ptr) { - char *name = NULL; - - ecs_poly_assert(world, ecs_world_t); - ecs_check(!world->is_readonly, ECS_INVALID_OPERATION, NULL); - ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(!world->is_fini, ECS_INVALID_OPERATION, NULL); + const ecs_hashed_string_t *str = ptr; + ecs_assert(str->hash != 0, ECS_INTERNAL_ERROR, NULL); + return str->hash; +} - const char *expr = desc->expr; - - ecs_observable_t *observable = desc->observable; - if (!observable) { - observable = ecs_get_observable(world); +static +int string_compare( + const void *ptr1, + const void *ptr2) +{ + const ecs_hashed_string_t *str1 = ptr1; + const ecs_hashed_string_t *str2 = ptr2; + ecs_size_t len1 = str1->length; + ecs_size_t len2 = str2->length; + if (len1 != len2) { + return (len1 > len2) - (len1 < len2); } - /* If entity is provided, create it */ - ecs_entity_t existing = desc->entity.entity; - ecs_entity_t entity = ecs_entity_init(world, &desc->entity); - - bool added = false; - EcsTrigger *comp = ecs_get_mut(world, entity, EcsTrigger, &added); - if (added) { - ecs_check(desc->callback != NULL, ECS_INVALID_PARAMETER, NULL); - - /* Something went wrong with the construction of the entity */ - ecs_check(entity != 0, ECS_INVALID_PARAMETER, NULL); - name = ecs_get_fullpath(world, entity); + return ecs_os_memcmp(str1->value, str2->value, len1); +} - ecs_term_t term; - if (expr) { - #ifdef FLECS_PARSER - const char *ptr = ecs_parse_term(world, name, expr, expr, &term); - if (!ptr) { - goto error; - } +ecs_hashmap_t _flecs_string_hashmap_new(ecs_size_t size) { + return _flecs_hashmap_new(ECS_SIZEOF(ecs_hashed_string_t), size, + string_hash, + string_compare); +} - if (!ecs_term_is_initialized(&term)) { - ecs_parser_error( - name, expr, 0, "invalid empty trigger expression"); - goto error; - } +void flecs_bootstrap_hierarchy(ecs_world_t *world) { + ecs_trigger_init(world, &(ecs_trigger_desc_t){ + .term = {.id = ecs_pair(ecs_id(EcsIdentifier), EcsSymbol)}, + .callback = on_set_symbol, + .events = {EcsOnSet}, + .yield_existing = true + }); +} - if (ptr[0]) { - ecs_parser_error(name, expr, 0, - "too many terms in trigger expression (expected 1)"); - goto error; - } - #else - ecs_abort(ECS_UNSUPPORTED, "parser addon is not available"); - #endif - } else { - term = ecs_term_copy(&desc->term); - } - if (ecs_term_finalize(world, name, &term)) { - goto error; - } +/* Public functions */ - /* Currently triggers are not supported for specific entities */ - ecs_check(term.subj.entity == EcsThis, ECS_UNSUPPORTED, NULL); +void ecs_get_path_w_sep_buf( + const ecs_world_t *world, + ecs_entity_t parent, + ecs_entity_t child, + const char *sep, + const char *prefix, + ecs_strbuf_t *buf) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(buf != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_trigger_t *trigger = flecs_sparse_add(world->triggers, ecs_trigger_t); - trigger->id = flecs_sparse_last_id(world->triggers); - trigger->term = ecs_term_move(&term); - trigger->action = desc->callback; - trigger->ctx = desc->ctx; - trigger->binding_ctx = desc->binding_ctx; - trigger->ctx_free = desc->ctx_free; - trigger->binding_ctx_free = desc->binding_ctx_free; - trigger->event_count = count_events(desc->events); - ecs_os_memcpy(trigger->events, desc->events, - trigger->event_count * ECS_SIZEOF(ecs_entity_t)); - trigger->entity = entity; - trigger->self = desc->self; - trigger->observable = observable; - trigger->match_prefab = desc->match_prefab; - trigger->match_disabled = desc->match_disabled; - trigger->instanced = desc->instanced; + world = ecs_get_world(world); - if (trigger->term.id == EcsPrefab) { - trigger->match_prefab = true; - } - if (trigger->term.id == EcsDisabled) { - trigger->match_disabled = true; - } + if (!sep) { + sep = "."; + } - comp->trigger = trigger; + if (!child || parent != child) { + path_append(world, parent, child, sep, prefix, buf); + } else { + ecs_strbuf_appendstr(buf, ""); + } - /* Trigger must have at least one event */ - ecs_check(trigger->event_count != 0, ECS_INVALID_PARAMETER, NULL); +error: + return; +} - register_trigger(world, observable, trigger); +char* ecs_get_path_w_sep( + const ecs_world_t *world, + ecs_entity_t parent, + ecs_entity_t child, + const char *sep, + const char *prefix) +{ + ecs_strbuf_t buf = ECS_STRBUF_INIT; + ecs_get_path_w_sep_buf(world, parent, child, sep, prefix, &buf); + return ecs_strbuf_get(&buf); +} - ecs_term_fini(&term); +ecs_entity_t ecs_lookup_child( + const ecs_world_t *world, + ecs_entity_t parent, + const char *name) +{ + ecs_check(world != NULL, ECS_INTERNAL_ERROR, NULL); - if (desc->entity.name) { - ecs_trace("#[green]trigger#[reset] %s created", - ecs_get_name(world, entity)); - } + if (is_number(name)) { + return name_to_id(world, name); + } - if (desc->yield_existing) { - trigger_yield_existing(world, trigger); + ecs_filter_t f; + int ret = ecs_filter_init(world, &f, &(ecs_filter_desc_t) { + .terms = { + { .id = ecs_pair( ecs_id(EcsIdentifier), EcsName) }, + { .id = ecs_pair(EcsChildOf, parent) }, + { .id = EcsDisabled, .oper = EcsOptional }, + { .id = EcsPrefab, .oper = EcsOptional } } - } else { - ecs_assert(comp->trigger != NULL, ECS_INTERNAL_ERROR, NULL); + }); + + ecs_check(ret == 0, ECS_INTERNAL_ERROR, NULL); + (void)ret; - /* If existing entity handle was provided, override existing params */ - if (existing) { - if (desc->callback) { - ((ecs_trigger_t*)comp->trigger)->action = desc->callback; - } - if (desc->ctx) { - ((ecs_trigger_t*)comp->trigger)->ctx = desc->ctx; - } - if (desc->binding_ctx) { - ((ecs_trigger_t*)comp->trigger)->binding_ctx = desc->binding_ctx; + ecs_iter_t it = ecs_filter_iter(world, &f); + while (ecs_filter_next(&it)) { + EcsIdentifier *ids = ecs_term(&it, EcsIdentifier, 1); + int i; + for (i = 0; i < it.count; i ++) { + char *cur_name = ids[i].value; + if (cur_name && !ecs_os_strcmp(cur_name, name)) { + ecs_filter_fini(&f); + return it.entities[i]; } } } - ecs_os_free(name); - return entity; + ecs_filter_fini(&f); error: - ecs_os_free(name); return 0; } -void* ecs_get_trigger_ctx( +ecs_entity_t ecs_lookup( const ecs_world_t *world, - ecs_entity_t trigger) -{ - const EcsTrigger *t = ecs_get(world, trigger, EcsTrigger); - if (t) { - return t->trigger->ctx; - } else { - return NULL; - } + const char *name) +{ + if (!name) { + return 0; + } + + ecs_check(world != NULL, ECS_INTERNAL_ERROR, NULL); + world = ecs_get_world(world); + + ecs_entity_t e = get_builtin(name); + if (e) { + return e; + } + + if (is_number(name)) { + return name_to_id(world, name); + } + + e = find_by_name(&world->aliases, name, 0, 0); + if (e) { + return e; + } + + return ecs_lookup_child(world, 0, name); +error: + return 0; } -void* ecs_get_trigger_binding_ctx( +ecs_entity_t ecs_lookup_symbol( const ecs_world_t *world, - ecs_entity_t trigger) -{ - const EcsTrigger *t = ecs_get(world, trigger, EcsTrigger); - if (t) { - return t->trigger->binding_ctx; - } else { - return NULL; - } -} + const char *name, + bool lookup_as_path) +{ + if (!name) { + return 0; + } -void flecs_trigger_fini( - ecs_world_t *world, - ecs_trigger_t *trigger) -{ - unregister_trigger(world, trigger->observable, trigger); - ecs_term_fini(&trigger->term); + ecs_check(world != NULL, ECS_INTERNAL_ERROR, NULL); + world = ecs_get_world(world); - if (trigger->ctx_free) { - trigger->ctx_free(trigger->ctx); + ecs_entity_t e = find_by_name(&world->symbols, name, 0, 0); + if (e) { + return e; } - if (trigger->binding_ctx_free) { - trigger->binding_ctx_free(trigger->binding_ctx); + if (lookup_as_path) { + return ecs_lookup_fullpath(world, name); } - flecs_sparse_remove(world->triggers, trigger->id); +error: + return 0; } +ecs_entity_t ecs_lookup_path_w_sep( + const ecs_world_t *world, + ecs_entity_t parent, + const char *path, + const char *sep, + const char *prefix, + bool recursive) +{ + if (!path) { + return 0; + } -#ifndef NDEBUG -static int64_t s_min[] = { - [1] = INT8_MIN, [2] = INT16_MIN, [4] = INT32_MIN, [8] = INT64_MIN }; -static int64_t s_max[] = { - [1] = INT8_MAX, [2] = INT16_MAX, [4] = INT32_MAX, [8] = INT64_MAX }; -static uint64_t u_max[] = { - [1] = UINT8_MAX, [2] = UINT16_MAX, [4] = UINT32_MAX, [8] = UINT64_MAX }; + if (!sep) { + sep = "."; + } -uint64_t _flecs_ito( - size_t size, - bool is_signed, - bool lt_zero, - uint64_t u, - const char *err) -{ - union { - uint64_t u; - int64_t s; - } v; + ecs_check(world != NULL, ECS_INTERNAL_ERROR, NULL); + world = ecs_get_world(world); - v.u = u; + ecs_entity_t e = get_builtin(path); + if (e) { + return e; + } - if (is_signed) { - ecs_assert(v.s >= s_min[size], ECS_INVALID_CONVERSION, err); - ecs_assert(v.s <= s_max[size], ECS_INVALID_CONVERSION, err); - } else { - ecs_assert(lt_zero == false, ECS_INVALID_CONVERSION, err); - ecs_assert(u <= u_max[size], ECS_INVALID_CONVERSION, err); + e = find_by_name(&world->aliases, path, 0, 0); + if (e) { + return e; + } + + char buff[ECS_NAME_BUFFER_LENGTH]; + const char *ptr, *ptr_start; + char *elem = buff; + int32_t len, size = ECS_NAME_BUFFER_LENGTH; + ecs_entity_t cur; + bool core_searched = false; + + if (!sep) { + sep = "."; } - return u; -} -#endif + parent = get_parent_from_path(world, parent, &path, prefix, true); -int32_t flecs_next_pow_of_2( - int32_t n) -{ - n --; - n |= n >> 1; - n |= n >> 2; - n |= n >> 4; - n |= n >> 8; - n |= n >> 16; - n ++; +retry: + cur = parent; + ptr_start = ptr = path; - return n; -} + while ((ptr = path_elem(ptr, sep, &len))) { + if (len < size) { + ecs_os_memcpy(elem, ptr_start, len); + } else { + if (size == ECS_NAME_BUFFER_LENGTH) { + elem = NULL; + } -/** Convert time to double */ -double ecs_time_to_double( - ecs_time_t t) -{ - double result; - result = t.sec; - return result + (double)t.nanosec / (double)1000000000; -} + elem = ecs_os_realloc(elem, len + 1); + ecs_os_memcpy(elem, ptr_start, len); + size = len + 1; + } -ecs_time_t ecs_time_sub( - ecs_time_t t1, - ecs_time_t t2) -{ - ecs_time_t result; + elem[len] = '\0'; + ptr_start = ptr; - if (t1.nanosec >= t2.nanosec) { - result.nanosec = t1.nanosec - t2.nanosec; - result.sec = t1.sec - t2.sec; - } else { - result.nanosec = t1.nanosec - t2.nanosec + 1000000000; - result.sec = t1.sec - t2.sec - 1; + cur = ecs_lookup_child(world, cur, elem); + if (!cur) { + goto tail; + } } - return result; -} +tail: + if (!cur && recursive) { + if (!core_searched) { + if (parent) { + parent = ecs_get_object(world, parent, EcsChildOf, 0); + } else { + parent = EcsFlecsCore; + core_searched = true; + } + goto retry; + } + } -void ecs_sleepf( - double t) -{ - if (t > 0) { - int sec = (int)t; - int nsec = (int)((t - sec) * 1000000000); - ecs_os_sleep(sec, nsec); + if (elem != buff) { + ecs_os_free(elem); } -} -double ecs_time_measure( - ecs_time_t *start) -{ - ecs_time_t stop, temp; - ecs_os_get_time(&stop); - temp = stop; - stop = ecs_time_sub(stop, *start); - *start = temp; - return ecs_time_to_double(stop); + return cur; +error: + return 0; } -void* ecs_os_memdup( - const void *src, - ecs_size_t size) +ecs_entity_t ecs_set_scope( + ecs_world_t *world, + ecs_entity_t scope) { - if (!src) { - return NULL; - } - - void *dst = ecs_os_malloc(size); - ecs_assert(dst != NULL, ECS_OUT_OF_MEMORY, NULL); - ecs_os_memcpy(dst, src, size); - return dst; -} + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_stage_t *stage = flecs_stage_from_world(&world); + + ecs_entity_t cur = stage->scope; + stage->scope = scope; + + if (scope) { + ecs_id_t id = ecs_pair(EcsChildOf, scope); + stage->scope_table = flecs_table_traverse_add( + world, &world->store.root, &id, NULL); + } else { + stage->scope_table = &world->store.root; + } -int flecs_entity_compare( - ecs_entity_t e1, - const void *ptr1, - ecs_entity_t e2, - const void *ptr2) -{ - (void)ptr1; - (void)ptr2; - return (e1 > e2) - (e1 < e2); + return cur; +error: + return 0; } -int flecs_entity_compare_qsort( - const void *e1, - const void *e2) +ecs_entity_t ecs_get_scope( + const ecs_world_t *world) { - ecs_entity_t v1 = *(ecs_entity_t*)e1; - ecs_entity_t v2 = *(ecs_entity_t*)e2; - return flecs_entity_compare(v1, NULL, v2, NULL); + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + const ecs_stage_t *stage = flecs_stage_from_readonly_world(world); + return stage->scope; +error: + return 0; } -uint64_t flecs_string_hash( - const void *ptr) +const char* ecs_set_name_prefix( + ecs_world_t *world, + const char *prefix) { - const ecs_hashed_string_t *str = ptr; - ecs_assert(str->hash != 0, ECS_INTERNAL_ERROR, NULL); - return str->hash; + ecs_poly_assert(world, ecs_world_t); + const char *old_prefix = world->name_prefix; + world->name_prefix = prefix; + return old_prefix; } -/* - This code was taken from sokol_time.h - - zlib/libpng license - Copyright (c) 2018 Andre Weissflog - This software is provided 'as-is', without any express or implied warranty. - In no event will the authors be held liable for any damages arising from the - use of this software. - Permission is granted to anyone to use this software for any purpose, - including commercial applications, and to alter it and redistribute it - freely, subject to the following restrictions: - 1. The origin of this software must not be misrepresented; you must not - claim that you wrote the original software. If you use this software in a - product, an acknowledgment in the product documentation would be - appreciated but is not required. - 2. Altered source versions must be plainly marked as such, and must not - be misrepresented as being the original software. - 3. This notice may not be removed or altered from any source - distribution. -*/ - +ecs_entity_t ecs_add_path_w_sep( + ecs_world_t *world, + ecs_entity_t entity, + ecs_entity_t parent, + const char *path, + const char *sep, + const char *prefix) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); -static int ecs_os_time_initialized; + if (!sep) { + sep = "."; + } -#if defined(_WIN32) -#ifndef WIN32_LEAN_AND_MEAN -#define WIN32_LEAN_AND_MEAN -#endif -#include -static double _ecs_os_time_win_freq; -static LARGE_INTEGER _ecs_os_time_win_start; -#elif defined(__APPLE__) && defined(__MACH__) -#include -static mach_timebase_info_data_t _ecs_os_time_osx_timebase; -static uint64_t _ecs_os_time_osx_start; -#else /* anything else, this will need more care for non-Linux platforms */ -#include -static uint64_t _ecs_os_time_posix_start; -#endif + if (!path) { + if (!entity) { + entity = ecs_new_id(world); + } -/* prevent 64-bit overflow when computing relative timestamp - see https://gist.github.com/jspohr/3dc4f00033d79ec5bdaf67bc46c813e3 -*/ -#if defined(_WIN32) || (defined(__APPLE__) && defined(__MACH__)) -int64_t int64_muldiv(int64_t value, int64_t numer, int64_t denom) { - int64_t q = value / denom; - int64_t r = value % denom; - return q * numer + r * numer / denom; -} -#endif + if (parent) { + ecs_add_pair(world, entity, EcsChildOf, entity); + } -void flecs_os_time_setup(void) { - if ( ecs_os_time_initialized) { - return; + return entity; } - - ecs_os_time_initialized = 1; - #if defined(_WIN32) - LARGE_INTEGER freq; - QueryPerformanceFrequency(&freq); - QueryPerformanceCounter(&_ecs_os_time_win_start); - _ecs_os_time_win_freq = (double)freq.QuadPart / 1000000000.0; - #elif defined(__APPLE__) && defined(__MACH__) - mach_timebase_info(&_ecs_os_time_osx_timebase); - _ecs_os_time_osx_start = mach_absolute_time(); - #else - struct timespec ts; - clock_gettime(CLOCK_MONOTONIC, &ts); - _ecs_os_time_posix_start = (uint64_t)ts.tv_sec*1000000000 + (uint64_t)ts.tv_nsec; - #endif -} -uint64_t flecs_os_time_now(void) { - ecs_assert(ecs_os_time_initialized != 0, ECS_INTERNAL_ERROR, NULL); + char buff[ECS_NAME_BUFFER_LENGTH]; + const char *ptr = path; + const char *ptr_start = path; + char *elem = buff; + int32_t len, size = ECS_NAME_BUFFER_LENGTH; - uint64_t now; + parent = get_parent_from_path(world, parent, &path, prefix, entity == 0); - #if defined(_WIN32) - LARGE_INTEGER qpc_t; - QueryPerformanceCounter(&qpc_t); - now = (uint64_t)(qpc_t.QuadPart / _ecs_os_time_win_freq); - #elif defined(__APPLE__) && defined(__MACH__) - now = (uint64_t) int64_muldiv((int64_t)mach_absolute_time(), (int64_t)_ecs_os_time_osx_timebase.numer, (int64_t)_ecs_os_time_osx_timebase.denom); - #else - struct timespec ts; - clock_gettime(CLOCK_MONOTONIC, &ts); - now = ((uint64_t)ts.tv_sec * 1000000000 + (uint64_t)ts.tv_nsec); - #endif + ecs_entity_t cur = parent; - return now; -} + char *name = NULL; -void flecs_os_time_sleep( - int32_t sec, - int32_t nanosec) -{ -#ifndef _WIN32 - struct timespec sleepTime; - ecs_assert(sec >= 0, ECS_INTERNAL_ERROR, NULL); - ecs_assert(nanosec >= 0, ECS_INTERNAL_ERROR, NULL); + while ((ptr = path_elem(ptr, sep, &len))) { + if (len < size) { + ecs_os_memcpy(elem, ptr_start, len); + } else { + if (size == ECS_NAME_BUFFER_LENGTH) { + elem = NULL; + } - sleepTime.tv_sec = sec; - sleepTime.tv_nsec = nanosec; - if (nanosleep(&sleepTime, NULL)) { - ecs_err("nanosleep failed"); - } -#else - HANDLE timer; - LARGE_INTEGER ft; + elem = ecs_os_realloc(elem, len + 1); + ecs_os_memcpy(elem, ptr_start, len); + size = len + 1; + } - ft.QuadPart = -((int64_t)sec * 10000000 + (int64_t)nanosec / 100); + elem[len] = '\0'; + ptr_start = ptr; - timer = CreateWaitableTimer(NULL, TRUE, NULL); - SetWaitableTimer(timer, &ft, 0, NULL, NULL, 0); - WaitForSingleObject(timer, INFINITE); - CloseHandle(timer); -#endif -} + ecs_entity_t e = ecs_lookup_child(world, cur, elem); + if (!e) { + if (name) { + ecs_os_free(name); + } -#if defined(_WIN32) + name = ecs_os_strdup(elem); -static ULONG win32_current_resolution; + /* If this is the last entity in the path, use the provided id */ + bool last_elem = false; + if (!path_elem(ptr, sep, NULL)) { + e = entity; + last_elem = true; + } -void flecs_increase_timer_resolution(bool enable) -{ - HMODULE hntdll = GetModuleHandle((LPCTSTR)"ntdll.dll"); - if (!hntdll) { - return; - } + if (!e) { + if (last_elem) { + ecs_entity_t prev = ecs_set_scope(world, 0); + e = ecs_new(world, 0); + ecs_set_scope(world, prev); + } else { + e = ecs_new_id(world); + } + } - LONG (__stdcall *pNtSetTimerResolution)( - ULONG desired, BOOLEAN set, ULONG * current); + ecs_set_name(world, e, name); - pNtSetTimerResolution = (LONG(__stdcall*)(ULONG, BOOLEAN, ULONG*)) - GetProcAddress(hntdll, "NtSetTimerResolution"); + if (cur) { + ecs_add_pair(world, e, EcsChildOf, cur); + } + } - if(!pNtSetTimerResolution) { - return; + cur = e; } - ULONG current, resolution = 10000; /* 1 ms */ + if (entity && (cur != entity)) { + if (name) { + ecs_os_free(name); + } - if (!enable && win32_current_resolution) { - pNtSetTimerResolution(win32_current_resolution, 0, ¤t); - win32_current_resolution = 0; - return; - } else if (!enable) { - return; - } + name = ecs_os_strdup(elem); - if (resolution == win32_current_resolution) { - return; + ecs_set_name(world, entity, name); } - if (win32_current_resolution) { - pNtSetTimerResolution(win32_current_resolution, 0, ¤t); + if (name) { + ecs_os_free(name); } - if (pNtSetTimerResolution(resolution, 1, ¤t)) { - /* Try setting a lower resolution */ - resolution *= 2; - if(pNtSetTimerResolution(resolution, 1, ¤t)) return; + if (elem != buff) { + ecs_os_free(elem); } - win32_current_resolution = resolution; + return cur; +error: + return 0; } -#else -void flecs_increase_timer_resolution(bool enable) +ecs_entity_t ecs_new_from_path_w_sep( + ecs_world_t *world, + ecs_entity_t parent, + const char *path, + const char *sep, + const char *prefix) { - (void)enable; - return; -} -#endif + if (!sep) { + sep = "."; + } + return ecs_add_path_w_sep(world, 0, parent, path, sep, prefix); +} -static -ecs_vector_t* sort_and_dedup( - ecs_vector_t *result) +void ecs_use( + ecs_world_t *world, + ecs_entity_t entity, + const char *name) { - /* Sort vector */ - ecs_vector_sort(result, ecs_id_t, flecs_entity_compare_qsort); - - /* Ensure vector doesn't contain duplicates */ - ecs_id_t *ids = ecs_vector_first(result, ecs_id_t); - int32_t i, offset = 0, count = ecs_vector_count(result); - - for (i = 0; i < count; i ++) { - if (i && ids[i] == ids[i - 1]) { - offset ++; - } - - if (i + offset >= count) { - break; - } - - ids[i] = ids[i + offset]; - } - - ecs_vector_set_count(&result, ecs_id_t, i - offset); + register_by_name(&world->aliases, entity, name, 0, 0); +} +static +ecs_defer_op_t* new_defer_op(ecs_stage_t *stage) { + ecs_defer_op_t *result = ecs_vector_add(&stage->defer_queue, ecs_defer_op_t); + ecs_os_memset(result, 0, ECS_SIZEOF(ecs_defer_op_t)); return result; } -/** Parse callback that adds type to type identifier */ static -ecs_vector_t* expr_to_ids( +bool defer_add_remove( ecs_world_t *world, - const char *name, - const char *expr) + ecs_stage_t *stage, + ecs_defer_op_kind_t op_kind, + ecs_entity_t entity, + ecs_id_t id) { -#ifdef FLECS_PARSER - ecs_vector_t *result = NULL; - const char *ptr = expr; - ecs_term_t term = {0}; - - if (!ptr) { - return NULL; - } - - while (ptr[0] && (ptr = ecs_parse_term(world, name, expr, ptr, &term))) { - if (term.name) { - ecs_parser_error(name, expr, (ptr - expr), - "column names not supported in type expression"); - goto error; + if (stage->defer) { + if (!id) { + return true; } - if (term.oper != EcsAnd && term.oper != EcsAndFrom) { - ecs_parser_error(name, expr, (ptr - expr), - "operator other than AND not supported in type expression"); - goto error; - } + ecs_defer_op_t *op = new_defer_op(stage); + op->kind = op_kind; + op->id = id; + op->is._1.entity = entity; - if (term.pred.set.mask == EcsDefaultSet) { - term.pred.set.mask = EcsSelf; - } - if (term.subj.set.mask == EcsDefaultSet) { - term.subj.set.mask = EcsSelf; - } - if (term.obj.set.mask == EcsDefaultSet) { - term.obj.set.mask = EcsSelf; + if (op_kind == EcsOpNew) { + world->new_count ++; + } else if (op_kind == EcsOpAdd) { + world->add_count ++; + } else if (op_kind == EcsOpRemove) { + world->remove_count ++; } - if (ecs_term_finalize(world, name, &term)) { - goto error; - } + return true; + } else { + stage->defer ++; + } + + return false; +} - if (term.subj.entity == 0) { - /* Empty term */ - goto done; - } +static +void merge_stages( + ecs_world_t *world, + bool force_merge) +{ + bool is_stage = ecs_poly_is(world, ecs_stage_t); + ecs_stage_t *stage = flecs_stage_from_world(&world); - if (term.subj.set.mask != EcsSelf) { - ecs_parser_error(name, expr, (ptr - expr), - "source modifiers not supported for type expressions"); - goto error; - } + bool measure_frame_time = world->measure_frame_time; - if (term.subj.entity != EcsThis) { - ecs_parser_error(name, expr, (ptr - expr), - "subject other than this not supported in type expression"); - goto error; - } + ecs_time_t t_start; + if (measure_frame_time) { + ecs_os_get_time(&t_start); + } - if (term.oper == EcsAndFrom) { - term.role = ECS_AND; + if (is_stage) { + /* Check for consistency if force_merge is enabled. In practice this + * function will never get called with force_merge disabled for just + * a single stage. */ + if (force_merge || stage->auto_merge) { + ecs_defer_end((ecs_world_t*)stage); } - - if (term.role == ECS_CASE) { - term.id = ECS_PAIR_OBJECT(term.id); + } else { + /* Merge stages. Only merge if the stage has auto_merging turned on, or + * if this is a forced merge (like when ecs_merge is called) */ + int32_t i, count = ecs_get_stage_count(world); + for (i = 0; i < count; i ++) { + ecs_stage_t *s = (ecs_stage_t*)ecs_get_stage(world, i); + ecs_poly_assert(s, ecs_stage_t); + if (force_merge || s->auto_merge) { + ecs_defer_end((ecs_world_t*)s); + } } + } - ecs_id_t* elem = ecs_vector_add(&result, ecs_id_t); - *elem = term.id | term.role; + flecs_eval_component_monitors(world); - ecs_term_fini(&term); + if (measure_frame_time) { + world->stats.merge_time_total += (float)ecs_time_measure(&t_start); } - result = sort_and_dedup(result); + world->stats.merge_count_total ++; -done: - return result; -error: - ecs_term_fini(&term); - ecs_vector_free(result); - return NULL; -#else - (void)world; - (void)name; - (void)expr; - ecs_abort(ECS_UNSUPPORTED, "parser addon is not available"); - return NULL; -#endif + /* If stage is asynchronous, deferring is always enabled */ + if (stage->asynchronous) { + ecs_defer_begin((ecs_world_t*)stage); + } } -/* Create normalized type. A normalized type resolves all elements with an - * AND flag and appends them to the resulting type, where the default type - * maintains the original type hierarchy. */ static -ecs_vector_t* ids_to_normalized_ids( - ecs_world_t *world, - ecs_vector_t *ids) +void do_auto_merge( + ecs_world_t *world) { - ecs_vector_t *result = NULL; + merge_stages(world, false); +} - ecs_entity_t *array = ecs_vector_first(ids, ecs_id_t); - int32_t i, count = ecs_vector_count(ids); +static +void do_manual_merge( + ecs_world_t *world) +{ + merge_stages(world, true); +} - for (i = 0; i < count; i ++) { - ecs_entity_t e = array[i]; - if (ECS_HAS_ROLE(e, AND)) { - ecs_entity_t entity = ECS_PAIR_OBJECT(e); +bool flecs_defer_none( + ecs_world_t *world, + ecs_stage_t *stage) +{ + (void)world; + return (++ stage->defer) == 1; +} - const EcsType *type_ptr = ecs_get(world, entity, EcsType); - ecs_assert(type_ptr != NULL, ECS_INVALID_PARAMETER, - "flag must be applied to type"); +bool flecs_defer_modified( + ecs_world_t *world, + ecs_stage_t *stage, + ecs_entity_t entity, + ecs_id_t id) +{ + (void)world; + if (stage->defer) { + ecs_defer_op_t *op = new_defer_op(stage); + op->kind = EcsOpModified; + op->id = id; + op->is._1.entity = entity; + return true; + } else { + stage->defer ++; + } + + return false; +} - ecs_vector_each(type_ptr->normalized, ecs_id_t, c_ptr, { - ecs_entity_t *el = ecs_vector_add(&result, ecs_id_t); - *el = *c_ptr; - }) - } else { - ecs_entity_t *el = ecs_vector_add(&result, ecs_id_t); - *el = e; - } +bool flecs_defer_clone( + ecs_world_t *world, + ecs_stage_t *stage, + ecs_entity_t entity, + ecs_entity_t src, + bool clone_value) +{ + (void)world; + if (stage->defer) { + ecs_defer_op_t *op = new_defer_op(stage); + op->kind = EcsOpClone; + op->id = src; + op->is._1.entity = entity; + op->is._1.clone_value = clone_value; + return true; + } else { + stage->defer ++; } - - return sort_and_dedup(result); + + return false; } -static -ecs_table_t* table_from_ids( +bool flecs_defer_delete( ecs_world_t *world, - ecs_vector_t *ids) + ecs_stage_t *stage, + ecs_entity_t entity) { - ecs_ids_t ids_array = flecs_type_to_ids(ids); - ecs_table_t *result = flecs_table_find_or_create(world, &ids_array); - return result; + (void)world; + if (stage->defer) { + ecs_defer_op_t *op = new_defer_op(stage); + op->kind = EcsOpDelete; + op->is._1.entity = entity; + world->delete_count ++; + return true; + } else { + stage->defer ++; + } + return false; } -/* If a name prefix is set with ecs_set_name_prefix, check if the entity name - * has the prefix, and if so remove it. This enables using prefixed names in C - * for components / systems while storing a canonical / language independent - * identifier. */ -const char* flecs_name_from_symbol( +bool flecs_defer_clear( ecs_world_t *world, - const char *type_name) + ecs_stage_t *stage, + ecs_entity_t entity) { - const char *prefix = world->name_prefix; - if (type_name && prefix) { - ecs_size_t len = ecs_os_strlen(prefix); - if (!ecs_os_strncmp(type_name, prefix, len) && - (isupper(type_name[len]) || type_name[len] == '_')) - { - if (type_name[len] == '_') { - return type_name + len + 1; - } else { - return type_name + len; - } - } + (void)world; + if (stage->defer) { + ecs_defer_op_t *op = new_defer_op(stage); + op->kind = EcsOpClear; + op->is._1.entity = entity; + world->clear_count ++; + return true; + } else { + stage->defer ++; } - - return type_name; + return false; } -/* -- Public functions -- */ - -ecs_type_t ecs_type_from_str( +bool flecs_defer_on_delete_action( ecs_world_t *world, - const char *expr) + ecs_stage_t *stage, + ecs_id_t id, + ecs_entity_t action) { - ecs_vector_t *ids = expr_to_ids(world, NULL, expr); - if (!ids) { - return NULL; + (void)world; + if (stage->defer) { + ecs_defer_op_t *op = new_defer_op(stage); + op->kind = EcsOpOnDeleteAction; + op->id = id; + op->is._1.entity = action; + world->clear_count ++; + return true; + } else { + stage->defer ++; } - - ecs_vector_t *normalized_ids = ids_to_normalized_ids(world, ids); - ecs_vector_free(ids); - - ecs_table_t *table = table_from_ids(world, normalized_ids); - ecs_vector_free(normalized_ids); - - return table->type; + return false; } -ecs_table_t* ecs_table_from_str( +bool flecs_defer_enable( ecs_world_t *world, - const char *expr) + ecs_stage_t *stage, + ecs_entity_t entity, + ecs_id_t id, + bool enable) { - ecs_poly_assert(world, ecs_world_t); - - ecs_vector_t *ids = expr_to_ids(world, NULL, expr); - if (!ids) { - return NULL; + (void)world; + if (stage->defer) { + ecs_defer_op_t *op = new_defer_op(stage); + op->kind = enable ? EcsOpEnable : EcsOpDisable; + op->is._1.entity = entity; + op->id = id; + return true; + } else { + stage->defer ++; } - - ecs_table_t *result = table_from_ids(world, ids); - ecs_vector_free(ids); - - return result; + return false; } +bool flecs_defer_bulk_new( + ecs_world_t *world, + ecs_stage_t *stage, + int32_t count, + ecs_id_t id, + const ecs_entity_t **ids_out) +{ + if (stage->defer) { + ecs_entity_t *ids = ecs_os_malloc(count * ECS_SIZEOF(ecs_entity_t)); + world->bulk_new_count ++; -/* -- Component lifecycle -- */ - -/* Component lifecycle actions for EcsIdentifier */ -static ECS_CTOR(EcsIdentifier, ptr, { - ptr->value = NULL; - ptr->hash = 0; - ptr->length = 0; -}) - -static ECS_DTOR(EcsIdentifier, ptr, { - ecs_os_strset(&ptr->value, NULL); -}) - -static ECS_COPY(EcsIdentifier, dst, src, { - ecs_os_strset(&dst->value, src->value); - dst->hash = src->hash; - dst->length = src->length; -}) - -static ECS_MOVE(EcsIdentifier, dst, src, { - ecs_os_strset(&dst->value, NULL); - dst->value = src->value; - dst->hash = src->hash; - dst->length = src->length; + /* Use ecs_new_id as this is thread safe */ + int i; + for (i = 0; i < count; i ++) { + ids[i] = ecs_new_id(world); + } - src->value = NULL; - src->hash = 0; - src->length = 0; + *ids_out = ids; -}) + /* Store data in op */ + ecs_defer_op_t *op = new_defer_op(stage); + op->kind = EcsOpBulkNew; + op->id = id; + op->is._n.entities = ids; + op->is._n.count = count; -static ECS_ON_SET(EcsIdentifier, ptr, { - if (ptr->value) { - ptr->length = ecs_os_strlen(ptr->value); - ptr->hash = flecs_hash(ptr->value, ptr->length); + return true; } else { - ptr->length = 0; - ptr->hash = 0; - } -}) - -/* Component lifecycle actions for EcsTrigger */ -static ECS_CTOR(EcsTrigger, ptr, { - ptr->trigger = NULL; -}) - -static ECS_DTOR(EcsTrigger, ptr, { - if (ptr->trigger) { - flecs_trigger_fini(world, (ecs_trigger_t*)ptr->trigger); + stage->defer ++; } -}) -static ECS_COPY(EcsTrigger, dst, src, { - ecs_abort(ECS_INVALID_OPERATION, "Trigger component cannot be copied"); -}) + return false; +} -static ECS_MOVE(EcsTrigger, dst, src, { - if (dst->trigger) { - flecs_trigger_fini(world, (ecs_trigger_t*)dst->trigger); - } - dst->trigger = src->trigger; - src->trigger = NULL; -}) +bool flecs_defer_new( + ecs_world_t *world, + ecs_stage_t *stage, + ecs_entity_t entity, + ecs_id_t id) +{ + return defer_add_remove(world, stage, EcsOpNew, entity, id); +} -/* Component lifecycle actions for EcsObserver */ -static ECS_CTOR(EcsObserver, ptr, { - ptr->observer = NULL; -}) +bool flecs_defer_add( + ecs_world_t *world, + ecs_stage_t *stage, + ecs_entity_t entity, + ecs_id_t id) +{ + return defer_add_remove(world, stage, EcsOpAdd, entity, id); +} -static ECS_DTOR(EcsObserver, ptr, { - if (ptr->observer) { - flecs_observer_fini(world, (ecs_observer_t*)ptr->observer); - } -}) +bool flecs_defer_remove( + ecs_world_t *world, + ecs_stage_t *stage, + ecs_entity_t entity, + ecs_id_t id) +{ + return defer_add_remove(world, stage, EcsOpRemove, entity, id); +} -static ECS_COPY(EcsObserver, dst, src, { - ecs_abort(ECS_INVALID_OPERATION, "Observer component cannot be copied"); -}) +bool flecs_defer_set( + ecs_world_t *world, + ecs_stage_t *stage, + ecs_defer_op_kind_t op_kind, + ecs_entity_t entity, + ecs_id_t id, + ecs_size_t size, + const void *value, + void **value_out, + bool *is_added) +{ + if (stage->defer) { + world->set_count ++; + if (!size) { + const EcsComponent *cptr = flecs_component_from_id(world, id); + ecs_check(cptr != NULL, ECS_INVALID_PARAMETER, NULL); + size = cptr->size; + } -static ECS_MOVE(EcsObserver, dst, src, { - if (dst->observer) { - flecs_observer_fini(world, (ecs_observer_t*)dst->observer); - } - dst->observer = src->observer; - src->observer = NULL; -}) + ecs_defer_op_t *op = new_defer_op(stage); + op->kind = op_kind; + op->id = id; + op->is._1.entity = entity; + op->is._1.size = size; + op->is._1.value = ecs_os_malloc(size); + if (!value) { + value = ecs_get_id(world, entity, id); + if (is_added) { + *is_added = value == NULL; + } + } -/* -- Builtin triggers -- */ + const ecs_type_info_t *c_info = NULL; + ecs_entity_t real_id = ecs_get_typeid(world, id); + if (real_id) { + c_info = flecs_get_c_info(world, real_id); + } -static -void register_on_delete(ecs_iter_t *it) { - ecs_world_t *world = it->world; - ecs_id_t id = ecs_term_id(it, 1); - - int i, count = it->count; - for (i = 0; i < count; i ++) { - ecs_entity_t e = it->entities[i]; - ecs_id_record_t *r = flecs_ensure_id_record(world, e); - ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); - r->on_delete = ECS_PAIR_OBJECT(id); + if (value) { + ecs_copy_ctor_t copy; + if (c_info && (copy = c_info->lifecycle.copy_ctor)) { + copy(world, id, &c_info->lifecycle, &entity, &entity, + op->is._1.value, value, flecs_itosize(size), 1, + c_info->lifecycle.ctx); + } else { + ecs_os_memcpy(op->is._1.value, value, size); + } + } else { + ecs_xtor_t ctor; + if (c_info && (ctor = c_info->lifecycle.ctor)) { + ctor(world, id, &entity, op->is._1.value, + flecs_itosize(size), 1, c_info->lifecycle.ctx); + } + } - r = flecs_ensure_id_record(world, ecs_pair(e, EcsWildcard)); - ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); - r->on_delete = ECS_PAIR_OBJECT(id); + if (value_out) { + *value_out = op->is._1.value; + } - flecs_add_flag(world, e, ECS_FLAG_OBSERVED_ID); + return true; + } else { + stage->defer ++; } -} - -static -void register_on_delete_object(ecs_iter_t *it) { - ecs_world_t *world = it->world; - ecs_id_t id = ecs_term_id(it, 1); - - int i, count = it->count; - for (i = 0; i < count; i ++) { - ecs_entity_t e = it->entities[i]; - ecs_id_record_t *r = flecs_ensure_id_record(world, e); - ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); - r->on_delete_object = ECS_PAIR_OBJECT(id); - - flecs_add_flag(world, e, ECS_FLAG_OBSERVED_ID); - } -} - -static -void on_set_component_lifecycle(ecs_iter_t *it) { - ecs_world_t *world = it->world; - EcsComponentLifecycle *cl = ecs_term(it, EcsComponentLifecycle, 1); - int i, count = it->count; - for (i = 0; i < count; i ++) { - ecs_entity_t e = it->entities[i]; - ecs_set_component_actions_w_id(world, e, &cl[i]); - } +error: + return false; } -static -void ensure_module_tag(ecs_iter_t *it) { - ecs_world_t *world = it->world; +void flecs_stage_merge_post_frame( + ecs_world_t *world, + ecs_stage_t *stage) +{ + /* Execute post frame actions */ + ecs_vector_each(stage->post_frame_actions, ecs_action_elem_t, action, { + action->action(world, action->ctx); + }); - int i, count = it->count; - for (i = 0; i < count; i ++) { - ecs_entity_t e = it->entities[i]; - ecs_entity_t parent = ecs_get_object(world, e, EcsChildOf, 0); - if (parent) { - ecs_add_id(world, parent, EcsModule); - } - } + ecs_vector_free(stage->post_frame_actions); + stage->post_frame_actions = NULL; } - -/* -- Iterable mixins -- */ - -static -void on_event_iterable_init( - const ecs_world_t *world, - const ecs_poly_t *poly, /* Observable */ - ecs_iter_t *it, - ecs_term_t *filter) +void flecs_stage_init( + ecs_world_t *world, + ecs_stage_t *stage) { - ecs_iter_poly(world, poly, it, filter); - it->event = EcsOnAdd; - it->event_id = filter->id; -} - + ecs_poly_assert(world, ecs_world_t); -/* -- Bootstrapping -- */ + ecs_poly_init(stage, ecs_stage_t); -#define bootstrap_component(world, table, name)\ - _bootstrap_component(world, table, ecs_id(name), #name, sizeof(name),\ - ECS_ALIGNOF(name)) + stage->world = world; + stage->thread_ctx = world; + stage->auto_merge = true; + stage->asynchronous = false; +} -static -void _bootstrap_component( +void flecs_stage_deinit( ecs_world_t *world, - ecs_table_t *table, - ecs_entity_t entity, - const char *symbol, - ecs_size_t size, - ecs_size_t alignment) + ecs_stage_t *stage) { - ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); - - ecs_column_t *columns = table->storage.columns; - ecs_assert(columns != NULL, ECS_INTERNAL_ERROR, NULL); - - ecs_record_t *record = ecs_eis_ensure(world, entity); - record->table = table; - - int32_t index = flecs_table_append(world, table, &table->storage, - entity, record, false); - record->row = ECS_ROW_TO_RECORD(index, 0); - - EcsComponent *component = ecs_vector_first(columns[0].data, EcsComponent); - component[index].size = size; - component[index].alignment = alignment; + (void)world; + ecs_poly_assert(world, ecs_world_t); + ecs_poly_assert(stage, ecs_stage_t); - const char *name = &symbol[3]; /* Strip 'Ecs' */ - ecs_size_t symbol_length = ecs_os_strlen(symbol); - ecs_size_t name_length = symbol_length - 3; + /* Make sure stage has no unmerged data */ + ecs_assert(ecs_vector_count(stage->defer_queue) == 0, + ECS_INTERNAL_ERROR, NULL); - EcsIdentifier *name_col = ecs_vector_first(columns[1].data, EcsIdentifier); - name_col[index].value = ecs_os_strdup(name); - name_col[index].length = name_length; - name_col[index].hash = flecs_hash(name, name_length); + ecs_poly_fini(stage, ecs_stage_t); - EcsIdentifier *symbol_col = ecs_vector_first(columns[2].data, EcsIdentifier); - symbol_col[index].value = ecs_os_strdup(symbol); - symbol_col[index].length = symbol_length; - symbol_col[index].hash = flecs_hash(symbol, symbol_length); + ecs_vector_free(stage->defer_queue); } -/** Create type for component */ -ecs_type_t flecs_bootstrap_type( +void ecs_set_stages( ecs_world_t *world, - ecs_entity_t entity) + int32_t stage_count) { - ecs_table_t *table = flecs_table_find_or_create(world, &(ecs_ids_t){ - .array = (ecs_entity_t[]){entity}, - .count = 1 - }); + ecs_poly_assert(world, ecs_world_t); - ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(table->type != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_stage_t *stages; + int32_t i, count = ecs_vector_count(world->worker_stages); - return table->type; -} + if (count && count != stage_count) { + stages = ecs_vector_first(world->worker_stages, ecs_stage_t); -/** Initialize component table. This table is manually constructed to bootstrap - * flecs. After this function has been called, the builtin components can be - * created. - * The reason this table is constructed manually is because it requires the size - * and alignment of the EcsComponent and EcsIdentifier components, which haven't - * been created yet */ -static -ecs_table_t* bootstrap_component_table( - ecs_world_t *world) -{ - ecs_entity_t entities[] = { - ecs_id(EcsComponent), - ecs_pair(ecs_id(EcsIdentifier), EcsName), - ecs_pair(ecs_id(EcsIdentifier), EcsSymbol), - ecs_pair(EcsChildOf, EcsFlecsCore) - }; - - ecs_ids_t array = { - .array = entities, - .count = 4 - }; + for (i = 0; i < count; i ++) { + /* If stage contains a thread handle, ecs_set_threads was used to + * create the stages. ecs_set_threads and ecs_set_stages should not + * be mixed. */ + ecs_poly_assert(&stages[i], ecs_stage_t); + ecs_check(stages[i].thread == 0, ECS_INVALID_OPERATION, NULL); + flecs_stage_deinit(world, &stages[i]); + } - ecs_table_t *result = flecs_table_find_or_create(world, &array); - ecs_data_t *data = &result->storage; + ecs_vector_free(world->worker_stages); + } + + if (stage_count) { + world->worker_stages = ecs_vector_new(ecs_stage_t, stage_count); - /* Preallocate enough memory for initial components */ - data->entities = ecs_vector_new(ecs_entity_t, EcsFirstUserComponentId); - data->record_ptrs = ecs_vector_new(ecs_record_t*, EcsFirstUserComponentId); + for (i = 0; i < stage_count; i ++) { + ecs_stage_t *stage = ecs_vector_add( + &world->worker_stages, ecs_stage_t); + flecs_stage_init(world, stage); + stage->id = 1 + i; /* 0 is reserved for main/temp stage */ - data->columns[0].data = ecs_vector_new(EcsComponent, EcsFirstUserComponentId); - data->columns[1].data = ecs_vector_new(EcsIdentifier, EcsFirstUserComponentId); - data->columns[2].data = ecs_vector_new(EcsIdentifier, EcsFirstUserComponentId); - - return result; + /* Set thread_ctx to stage, as this stage might be used in a + * multithreaded context */ + stage->thread_ctx = (ecs_world_t*)stage; + } + } else { + /* Set to NULL to prevent double frees */ + world->worker_stages = NULL; + } + + /* Regardless of whether the stage was just initialized or not, when the + * ecs_set_stages function is called, all stages inherit the auto_merge + * property from the world */ + for (i = 0; i < stage_count; i ++) { + ecs_stage_t *stage = (ecs_stage_t*)ecs_get_stage(world, i); + stage->auto_merge = world->stage.auto_merge; + } +error: + return; } -static -void bootstrap_entity( - ecs_world_t *world, - ecs_entity_t id, - const char *name, - ecs_entity_t parent) +int32_t ecs_get_stage_count( + const ecs_world_t *world) { - char symbol[256]; - ecs_os_strcpy(symbol, "flecs.core."); - ecs_os_strcat(symbol, name); + world = ecs_get_world(world); + return ecs_vector_count(world->worker_stages); +} - ecs_set_name(world, id, name); - ecs_set_symbol(world, id, symbol); +int32_t ecs_get_stage_id( + const ecs_world_t *world) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_assert(ecs_get_name(world, id) != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_add_pair(world, id, EcsChildOf, parent); + if (ecs_poly_is(world, ecs_stage_t)) { + ecs_stage_t *stage = (ecs_stage_t*)world; - if (!parent || parent == EcsFlecsCore) { - ecs_assert(ecs_lookup_fullpath(world, name) == id, - ECS_INTERNAL_ERROR, NULL); + /* Index 0 is reserved for main stage */ + return stage->id - 1; + } else if (ecs_poly_is(world, ecs_world_t)) { + return 0; + } else { + ecs_throw(ECS_INTERNAL_ERROR, NULL); } +error: + return 0; } -void flecs_bootstrap( - ecs_world_t *world) +ecs_world_t* ecs_get_stage( + const ecs_world_t *world, + int32_t stage_id) { - ecs_log_push(); - - ecs_set_name_prefix(world, "Ecs"); + ecs_poly_assert(world, ecs_world_t); + ecs_check(ecs_vector_count(world->worker_stages) > stage_id, + ECS_INVALID_PARAMETER, NULL); - /* Create table for initial components */ - ecs_table_t *table = bootstrap_component_table(world); - assert(table != NULL); + return (ecs_world_t*)ecs_vector_get( + world->worker_stages, ecs_stage_t, stage_id); +error: + return NULL; +} - bootstrap_component(world, table, EcsIdentifier); - bootstrap_component(world, table, EcsComponent); - bootstrap_component(world, table, EcsComponentLifecycle); +bool ecs_staging_begin( + ecs_world_t *world) +{ + ecs_poly_assert(world, ecs_world_t); - bootstrap_component(world, table, EcsType); - bootstrap_component(world, table, EcsQuery); - bootstrap_component(world, table, EcsTrigger); - bootstrap_component(world, table, EcsObserver); - bootstrap_component(world, table, EcsIterable); + int32_t i, count = ecs_get_stage_count(world); + for (i = 0; i < count; i ++) { + ecs_defer_begin(ecs_get_stage(world, i)); + } - ecs_set_component_actions(world, EcsComponent, { .ctor = ecs_default_ctor }); + bool is_readonly = world->is_readonly; - ecs_set_component_actions(world, EcsIdentifier, { - .ctor = ecs_ctor(EcsIdentifier), - .dtor = ecs_dtor(EcsIdentifier), - .copy = ecs_copy(EcsIdentifier), - .move = ecs_move(EcsIdentifier), - .on_set = ecs_on_set(EcsIdentifier) - }); + /* From this point on, the world is "locked" for mutations, and it is only + * allowed to enqueue commands from stages */ + world->is_readonly = true; - ecs_set_component_actions(world, EcsTrigger, { - .ctor = ecs_ctor(EcsTrigger), - .dtor = ecs_dtor(EcsTrigger), - .copy = ecs_copy(EcsTrigger), - .move = ecs_move(EcsTrigger) - }); + ecs_dbg_3("staging: begin"); - ecs_set_component_actions(world, EcsObserver, { - .ctor = ecs_ctor(EcsObserver), - .dtor = ecs_dtor(EcsObserver), - .copy = ecs_copy(EcsObserver), - .move = ecs_move(EcsObserver) - }); + return is_readonly; +} - world->stats.last_component_id = EcsFirstUserComponentId; - world->stats.last_id = EcsFirstUserEntityId; - world->stats.min_id = 0; - world->stats.max_id = 0; +void ecs_staging_end( + ecs_world_t *world) +{ + ecs_poly_assert(world, ecs_world_t); + ecs_check(world->is_readonly == true, ECS_INVALID_OPERATION, NULL); - /* Populate core module */ - ecs_set_scope(world, EcsFlecsCore); + /* After this it is safe again to mutate the world directly */ + world->is_readonly = false; - flecs_bootstrap_tag(world, EcsName); - flecs_bootstrap_tag(world, EcsSymbol); + ecs_dbg_3("staging: end"); - flecs_bootstrap_tag(world, EcsModule); - flecs_bootstrap_tag(world, EcsPrefab); - flecs_bootstrap_tag(world, EcsDisabled); + do_auto_merge(world); +error: + return; +} - /* Initialize builtin modules */ - ecs_set_name(world, EcsFlecs, "flecs"); - ecs_add_id(world, EcsFlecs, EcsModule); - ecs_set_name(world, EcsFlecsCore, "core"); - ecs_add_id(world, EcsFlecsCore, EcsModule); - ecs_add_pair(world, EcsFlecsCore, EcsChildOf, EcsFlecs); +void ecs_merge( + ecs_world_t *world) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(ecs_poly_is(world, ecs_world_t) || + ecs_poly_is(world, ecs_stage_t), ECS_INVALID_PARAMETER, NULL); + do_manual_merge(world); +error: + return; +} - /* Initialize builtin entities */ - bootstrap_entity(world, EcsWorld, "World", EcsFlecsCore); - bootstrap_entity(world, EcsThis, "This", EcsFlecsCore); - bootstrap_entity(world, EcsWildcard, "*", EcsFlecsCore); +void ecs_set_automerge( + ecs_world_t *world, + bool auto_merge) +{ + /* If a world is provided, set auto_merge globally for the world. This + * doesn't actually do anything (the main stage never merges) but it serves + * as the default for when stages are created. */ + if (ecs_poly_is(world, ecs_world_t)) { + world->stage.auto_merge = auto_merge; - /* Component/relationship properties */ - flecs_bootstrap_tag(world, EcsTransitive); - flecs_bootstrap_tag(world, EcsTransitiveSelf); - flecs_bootstrap_tag(world, EcsFinal); - flecs_bootstrap_tag(world, EcsTag); - flecs_bootstrap_tag(world, EcsExclusive); - flecs_bootstrap_tag(world, EcsAcyclic); + /* Propagate change to all stages */ + int i, stage_count = ecs_get_stage_count(world); + for (i = 0; i < stage_count; i ++) { + ecs_stage_t *stage = (ecs_stage_t*)ecs_get_stage(world, i); + stage->auto_merge = auto_merge; + } - flecs_bootstrap_tag(world, EcsOnDelete); - flecs_bootstrap_tag(world, EcsOnDeleteObject); - flecs_bootstrap_tag(world, EcsRemove); - flecs_bootstrap_tag(world, EcsDelete); - flecs_bootstrap_tag(world, EcsThrow); + /* If a stage is provided, override the auto_merge value for the individual + * stage. This allows an application to control per-stage which stage should + * be automatically merged and which one shouldn't */ + } else { + ecs_poly_assert(world, ecs_stage_t); + ecs_stage_t *stage = (ecs_stage_t*)world; + stage->auto_merge = auto_merge; + } +} - flecs_bootstrap_tag(world, EcsDefaultChildComponent); +bool ecs_stage_is_readonly( + const ecs_world_t *stage) +{ + const ecs_world_t *world = ecs_get_world(stage); - /* Builtin relations */ - flecs_bootstrap_tag(world, EcsIsA); - flecs_bootstrap_tag(world, EcsChildOf); + if (ecs_poly_is(stage, ecs_stage_t)) { + if (((ecs_stage_t*)stage)->asynchronous) { + return false; + } + } - /* Builtin events */ - bootstrap_entity(world, EcsOnAdd, "OnAdd", EcsFlecsCore); - bootstrap_entity(world, EcsOnRemove, "OnRemove", EcsFlecsCore); - bootstrap_entity(world, EcsOnSet, "OnSet", EcsFlecsCore); - bootstrap_entity(world, EcsUnSet, "UnSet", EcsFlecsCore); - bootstrap_entity(world, EcsOnTableEmpty, "OnTableEmpty", EcsFlecsCore); - bootstrap_entity(world, EcsOnTableFilled, "OnTableFilled", EcsFlecsCore); - // bootstrap_entity(world, EcsOnCreateTable, "OnCreateTable", EcsFlecsCore); - // bootstrap_entity(world, EcsOnDeleteTable, "OnDeleteTable", EcsFlecsCore); - // bootstrap_entity(world, EcsOnCreateTrigger, "OnCreateTrigger", EcsFlecsCore); - // bootstrap_entity(world, EcsOnDeleteTrigger, "OnDeleteTrigger", EcsFlecsCore); - // bootstrap_entity(world, EcsOnDeleteObservable, "OnDeleteObservable", EcsFlecsCore); - // bootstrap_entity(world, EcsOnComponentLifecycle, "OnComponentLifecycle", EcsFlecsCore); + if (world->is_readonly) { + if (ecs_poly_is(stage, ecs_world_t)) { + return true; + } + } else { + if (ecs_poly_is(stage, ecs_stage_t)) { + return true; + } + } - /* Transitive relations */ - ecs_add_id(world, EcsIsA, EcsTransitive); - ecs_add_id(world, EcsIsA, EcsTransitiveSelf); + return false; +} - /* Tag relations (relations that should never have data) */ - ecs_add_id(world, EcsIsA, EcsTag); - ecs_add_id(world, EcsChildOf, EcsTag); - ecs_add_id(world, EcsDefaultChildComponent, EcsTag); +ecs_world_t* ecs_async_stage_new( + ecs_world_t *world) +{ + ecs_stage_t *stage = ecs_os_calloc(sizeof(ecs_stage_t)); + flecs_stage_init(world, stage); - /* Final components/relations */ - ecs_add_id(world, ecs_id(EcsComponent), EcsFinal); - ecs_add_id(world, ecs_id(EcsComponentLifecycle), EcsFinal); - ecs_add_id(world, ecs_id(EcsIdentifier), EcsFinal); - ecs_add_id(world, EcsModule, EcsFinal); - ecs_add_id(world, EcsDisabled, EcsFinal); - ecs_add_id(world, EcsPrefab, EcsFinal); - ecs_add_id(world, EcsTransitive, EcsFinal); - ecs_add_id(world, EcsFinal, EcsFinal); - ecs_add_id(world, EcsTag, EcsFinal); - ecs_add_id(world, EcsExclusive, EcsFinal); - ecs_add_id(world, EcsAcyclic, EcsFinal); - ecs_add_id(world, EcsIsA, EcsFinal); - ecs_add_id(world, EcsChildOf, EcsFinal); - ecs_add_id(world, EcsOnDelete, EcsFinal); - ecs_add_id(world, EcsOnDeleteObject, EcsFinal); - ecs_add_id(world, EcsDefaultChildComponent, EcsFinal); + stage->id = -1; + stage->auto_merge = false; + stage->asynchronous = true; - /* Acyclic relations */ - ecs_add_id(world, EcsIsA, EcsAcyclic); - ecs_add_id(world, EcsChildOf, EcsAcyclic); + ecs_defer_begin((ecs_world_t*)stage); - /* Exclusive properties */ - ecs_add_id(world, EcsChildOf, EcsExclusive); - ecs_add_id(world, EcsOnDelete, EcsExclusive); - ecs_add_id(world, EcsOnDeleteObject, EcsExclusive); - ecs_add_id(world, EcsDefaultChildComponent, EcsExclusive); + return (ecs_world_t*)stage; +} - /* Make EcsOnAdd, EcsOnSet events iterable to enable .yield_existing */ - ecs_set(world, EcsOnAdd, EcsIterable, { .init = on_event_iterable_init }); - ecs_set(world, EcsOnSet, EcsIterable, { .init = on_event_iterable_init }); +void ecs_async_stage_free( + ecs_world_t *world) +{ + ecs_poly_assert(world, ecs_stage_t); + ecs_stage_t *stage = (ecs_stage_t*)world; + ecs_check(stage->asynchronous == true, ECS_INVALID_PARAMETER, NULL); + flecs_stage_deinit(stage->world, stage); + ecs_os_free(stage); +error: + return; +} - /* Define triggers for when relationship cleanup rules are assigned */ - ecs_trigger_init(world, &(ecs_trigger_desc_t){ - .term = {.id = ecs_pair(EcsOnDelete, EcsWildcard)}, - .callback = register_on_delete, - .events = {EcsOnAdd} - }); +bool ecs_stage_is_async( + ecs_world_t *stage) +{ + if (!stage) { + return false; + } + + if (!ecs_poly_is(stage, ecs_stage_t)) { + return false; + } - ecs_trigger_init(world, &(ecs_trigger_desc_t){ - .term = {.id = ecs_pair(EcsOnDeleteObject, EcsWildcard)}, - .callback = register_on_delete_object, - .events = {EcsOnAdd} - }); + return ((ecs_stage_t*)stage)->asynchronous; +} - /* Define trigger to make sure that adding a module to a child entity also - * adds it to the parent. */ - ecs_trigger_init(world, &(ecs_trigger_desc_t){ - .term = {.id = EcsModule}, - .callback = ensure_module_tag, - .events = {EcsOnAdd} - }); +bool ecs_is_deferred( + const ecs_world_t *world) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + const ecs_stage_t *stage = flecs_stage_from_readonly_world(world); + return stage->defer != 0; +error: + return false; +} - /* Define trigger for when component lifecycle is set for component */ - ecs_trigger_init(world, &(ecs_trigger_desc_t){ - .term = {.id = ecs_id(EcsComponentLifecycle)}, - .callback = on_set_component_lifecycle, - .events = {EcsOnSet} - }); +void ecs_os_api_impl(ecs_os_api_t *api); - /* Removal of ChildOf objects (parents) deletes the subject (child) */ - ecs_add_pair(world, EcsChildOf, EcsOnDeleteObject, EcsDelete); +static bool ecs_os_api_initialized = false; +static int ecs_os_api_init_count = 0; - /* Run bootstrap functions for other parts of the code */ - flecs_bootstrap_hierarchy(world); +ecs_os_api_t ecs_os_api = { + .log_with_color_ = true, + .log_level_ = -1 /* disable tracing by default, but enable >= warnings */ +}; - ecs_set_scope(world, 0); +int64_t ecs_os_api_malloc_count = 0; +int64_t ecs_os_api_realloc_count = 0; +int64_t ecs_os_api_calloc_count = 0; +int64_t ecs_os_api_free_count = 0; - ecs_log_pop(); +void ecs_os_set_api( + ecs_os_api_t *os_api) +{ + if (!ecs_os_api_initialized) { + ecs_os_api = *os_api; + ecs_os_api_initialized = true; + } } - - -#define ECS_NAME_BUFFER_LENGTH (64) - -static -bool path_append( - const ecs_world_t *world, - ecs_entity_t parent, - ecs_entity_t child, - const char *sep, - const char *prefix, - ecs_strbuf_t *buf) +void ecs_os_init(void) { - ecs_poly_assert(world, ecs_world_t); - - ecs_entity_t cur = 0; - char buff[22]; - const char *name; - - if (ecs_is_valid(world, child)) { - cur = ecs_get_object(world, child, EcsChildOf, 0); - if (cur) { - if (cur != parent && cur != EcsFlecsCore) { - path_append(world, parent, cur, sep, prefix, buf); - ecs_strbuf_appendstr(buf, sep); - } - } else if (prefix) { - ecs_strbuf_appendstr(buf, prefix); + if (!ecs_os_api_initialized) { + ecs_os_set_api_defaults(); + } + + if (!(ecs_os_api_init_count ++)) { + if (ecs_os_api.init_) { + ecs_os_api.init_(); } - - name = ecs_get_name(world, child); - if (!name || !ecs_os_strlen(name)) { - ecs_os_sprintf(buff, "%u", (uint32_t)child); - name = buff; - } - } else { - ecs_os_sprintf(buff, "%u", (uint32_t)child); - name = buff; } +} - ecs_strbuf_appendstr(buf, name); - - return cur != 0; +void ecs_os_fini(void) { + if (!--ecs_os_api_init_count) { + if (ecs_os_api.fini_) { + ecs_os_api.fini_(); + } + } } -ecs_hashed_string_t ecs_get_hashed_string( - const char *name, - ecs_size_t length, - uint64_t hash) +#if !defined(_MSC_VER) && !defined(__EMSCRIPTEN__) +#include +#define ECS_BT_BUF_SIZE 100 +static +void dump_backtrace( + FILE *stream) { - ecs_assert(!length || length == ecs_os_strlen(name), - ECS_INTERNAL_ERROR, NULL); + int nptrs; + void *buffer[ECS_BT_BUF_SIZE]; + char **strings; - if (!length) { - length = ecs_os_strlen(name); - } + nptrs = backtrace(buffer, ECS_BT_BUF_SIZE); - ecs_assert(!hash || hash == flecs_hash(name, length), - ECS_INTERNAL_ERROR, NULL); + strings = backtrace_symbols(buffer, nptrs); + if (strings == NULL) { + return; + } - if (!hash) { - hash = flecs_hash(name, length); + for (int j = 3; j < nptrs; j++) { + fprintf(stream, "%s\n", strings[j]); } - return (ecs_hashed_string_t) { - .value = (char*)name, - .length = length, - .hash = hash - }; + free(strings); } +#else +static +void dump_backtrace( + FILE *stream) +{ + (void)stream; +} +#endif static -ecs_entity_t find_by_name( - const ecs_hashmap_t *map, - const char *name, - ecs_size_t length, - uint64_t hash) +void log_msg( + int32_t level, + const char *file, + int32_t line, + const char *msg) { - ecs_hashed_string_t key = ecs_get_hashed_string(name, length, hash); - - ecs_entity_t *e = flecs_hashmap_get(*map, &key, ecs_entity_t); + FILE *stream; + if (level >= 0) { + stream = stdout; + } else { + stream = stderr; + } - if (!e) { - return 0; + if (level >= 0) { + if (ecs_os_api.log_with_color_) fputs(ECS_MAGENTA, stream); + fputs("info", stream); + } else if (level == -2) { + if (ecs_os_api.log_with_color_) fputs(ECS_YELLOW, stream); + fputs("warning", stream); + } else if (level == -3) { + if (ecs_os_api.log_with_color_) fputs(ECS_RED, stream); + fputs("error", stream); + } else if (level == -4) { + if (ecs_os_api.log_with_color_) fputs(ECS_RED, stream); + fputs("fatal", stream); } - return *e; -} + if (ecs_os_api.log_with_color_) fputs(ECS_NORMAL, stream); + fputs(": ", stream); -static -void register_by_name( - ecs_hashmap_t *map, - ecs_entity_t entity, - const char *name, - ecs_size_t length, - uint64_t hash) -{ - ecs_check(entity != 0, ECS_INVALID_PARAMETER, NULL); - ecs_check(name != NULL, ECS_INVALID_PARAMETER, NULL); + if (level >= 0) { + if (ecs_os_api.log_indent_) { + char indent[32]; + int i; + for (i = 0; i < ecs_os_api.log_indent_; i ++) { + indent[i * 2] = '|'; + indent[i * 2 + 1] = ' '; + } + indent[i * 2] = '\0'; - ecs_hashed_string_t key = ecs_get_hashed_string(name, length, hash); - - ecs_entity_t existing = find_by_name(map, name, key.length, key.hash); - if (existing) { - if (existing != entity) { - ecs_abort(ECS_ALREADY_DEFINED, - "conflicting entity registered with name '%s'", name); + fputs(indent, stream); } - } else { - key.value = ecs_os_strdup(key.value); } - flecs_hashmap_result_t hmr = flecs_hashmap_ensure( - *map, &key, ecs_entity_t); - - *((ecs_entity_t*)hmr.value) = entity; -error: - return; -} + if (level < 0) { + if (file) { + const char *file_ptr = strrchr(file, '/'); + if (!file_ptr) { + file_ptr = strrchr(file, '\\'); + } -static -bool is_number( - const char *name) -{ - ecs_assert(name != NULL, ECS_INTERNAL_ERROR, NULL); - - if (!isdigit(name[0])) { - return false; - } + if (file_ptr) { + file = file_ptr + 1; + } - ecs_size_t i, length = ecs_os_strlen(name); - for (i = 1; i < length; i ++) { - char ch = name[i]; + fputs(file, stream); + fputs(": ", stream); + } - if (!isdigit(ch)) { - break; + if (line) { + fprintf(stream, "%d: ", line); } } - return i >= length; + fputs(msg, stream); + + fputs("\n", stream); + + if (level == -4) { + dump_backtrace(stream); + } } -static -ecs_entity_t name_to_id( - const ecs_world_t *world, - const char *name) +void ecs_os_dbg( + const char *file, + int32_t line, + const char *msg) { - long int result = atol(name); - ecs_assert(result >= 0, ECS_INTERNAL_ERROR, NULL); - ecs_entity_t alive = ecs_get_alive(world, (ecs_entity_t)result); - if (alive) { - return alive; - } else { - return (ecs_entity_t)result; +#ifndef NDEBUG + if (ecs_os_api.log_) { + ecs_os_api.log_(1, file, line, msg); } +#else + (void)file; + (void)line; + (void)msg; +#endif } -static -ecs_entity_t get_builtin( - const char *name) +void ecs_os_trace( + const char *file, + int32_t line, + const char *msg) { - if (name[0] == '.' && name[1] == '\0') { - return EcsThis; - } else if (name[0] == '*' && name[1] == '\0') { - return EcsWildcard; + if (ecs_os_api.log_) { + ecs_os_api.log_(0, file, line, msg); } +} - return 0; +void ecs_os_warn( + const char *file, + int32_t line, + const char *msg) +{ + if (ecs_os_api.log_) { + ecs_os_api.log_(-2, file, line, msg); + } } -static -bool is_sep( - const char **ptr, - const char *sep) +void ecs_os_err( + const char *file, + int32_t line, + const char *msg) { - ecs_size_t len = ecs_os_strlen(sep); + if (ecs_os_api.log_) { + ecs_os_api.log_(-3, file, line, msg); + } +} - if (!ecs_os_strncmp(*ptr, sep, len)) { - *ptr += len; - return true; - } else { - return false; +void ecs_os_fatal( + const char *file, + int32_t line, + const char *msg) +{ + if (ecs_os_api.log_) { + ecs_os_api.log_(-4, file, line, msg); } } static -const char* path_elem( - const char *path, - const char *sep, - int32_t *len) +void ecs_os_gettime(ecs_time_t *time) { - const char *ptr; - char ch; - int32_t template_nesting = 0; - int32_t count = 0; - - for (ptr = path; (ch = *ptr); ptr ++) { - if (ch == '<') { - template_nesting ++; - } else if (ch == '>') { - template_nesting --; - } - - ecs_check(template_nesting >= 0, ECS_INVALID_PARAMETER, path); + uint64_t now = flecs_os_time_now(); + uint64_t sec = now / 1000000000; - if (!template_nesting && is_sep(&ptr, sep)) { - break; - } + assert(sec < UINT32_MAX); + assert((now - sec * 1000000000) < UINT32_MAX); - count ++; - } + time->sec = (uint32_t)sec; + time->nanosec = (uint32_t)(now - sec * 1000000000); +} - if (len) { - *len = count; - } +static +void* ecs_os_api_malloc(ecs_size_t size) { + ecs_os_api_malloc_count ++; + ecs_assert(size > 0, ECS_INVALID_PARAMETER, NULL); + return malloc((size_t)size); +} - if (count) { - return ptr; - } else { - return NULL; - } -error: - return NULL; +static +void* ecs_os_api_calloc(ecs_size_t size) { + ecs_os_api_calloc_count ++; + ecs_assert(size > 0, ECS_INVALID_PARAMETER, NULL); + return calloc(1, (size_t)size); } static -ecs_entity_t get_parent_from_path( - const ecs_world_t *world, - ecs_entity_t parent, - const char **path_ptr, - const char *prefix, - bool new_entity) -{ - bool start_from_root = false; - const char *path = *path_ptr; - - if (prefix) { - ecs_size_t len = ecs_os_strlen(prefix); - if (!ecs_os_strncmp(path, prefix, len)) { - path += len; - parent = 0; - start_from_root = true; - } - } +void* ecs_os_api_realloc(void *ptr, ecs_size_t size) { + ecs_assert(size > 0, ECS_INVALID_PARAMETER, NULL); - if (!start_from_root && !parent && new_entity) { - parent = ecs_get_scope(world); + if (ptr) { + ecs_os_api_realloc_count ++; + } else { + /* If not actually reallocing, treat as malloc */ + ecs_os_api_malloc_count ++; } - - *path_ptr = path; - - return parent; + + return realloc(ptr, (size_t)size); } static -void on_set_symbol(ecs_iter_t *it) { - EcsIdentifier *n = ecs_term(it, EcsIdentifier, 1); - ecs_world_t *world = it->world; - - int i; - for (i = 0; i < it->count; i ++) { - ecs_entity_t e = it->entities[i]; - register_by_name( - &world->symbols, e, n[i].value, n[i].length, n[i].hash); +void ecs_os_api_free(void *ptr) { + if (ptr) { + ecs_os_api_free_count ++; } + free(ptr); } static -uint64_t string_hash( - const void *ptr) -{ - const ecs_hashed_string_t *str = ptr; - ecs_assert(str->hash != 0, ECS_INTERNAL_ERROR, NULL); - return str->hash; +char* ecs_os_api_strdup(const char *str) { + if (str) { + int len = ecs_os_strlen(str); + char *result = ecs_os_malloc(len + 1); + ecs_assert(result != NULL, ECS_OUT_OF_MEMORY, NULL); + ecs_os_strcpy(result, str); + return result; + } else { + return NULL; + } } +/* Replace dots with underscores */ static -int string_compare( - const void *ptr1, - const void *ptr2) -{ - const ecs_hashed_string_t *str1 = ptr1; - const ecs_hashed_string_t *str2 = ptr2; - ecs_size_t len1 = str1->length; - ecs_size_t len2 = str2->length; - if (len1 != len2) { - return (len1 > len2) - (len1 < len2); +char *module_file_base(const char *module, char sep) { + char *base = ecs_os_strdup(module); + ecs_size_t i, len = ecs_os_strlen(base); + for (i = 0; i < len; i ++) { + if (base[i] == '.') { + base[i] = sep; + } } - return ecs_os_memcmp(str1->value, str2->value, len1); + return base; } -ecs_hashmap_t _flecs_string_hashmap_new(ecs_size_t size) { - return _flecs_hashmap_new(ECS_SIZEOF(ecs_hashed_string_t), size, - string_hash, - string_compare); -} +static +char* ecs_os_api_module_to_dl(const char *module) { + ecs_strbuf_t lib = ECS_STRBUF_INIT; -void flecs_bootstrap_hierarchy(ecs_world_t *world) { - ecs_trigger_init(world, &(ecs_trigger_desc_t){ - .term = {.id = ecs_pair(ecs_id(EcsIdentifier), EcsSymbol)}, - .callback = on_set_symbol, - .events = {EcsOnSet}, - .yield_existing = true - }); -} + /* Best guess, use module name with underscores + OS library extension */ + char *file_base = module_file_base(module, '_'); +#if defined(ECS_OS_LINUX) + ecs_strbuf_appendstr(&lib, "lib"); + ecs_strbuf_appendstr(&lib, file_base); + ecs_strbuf_appendstr(&lib, ".so"); +#elif defined(ECS_OS_DARWIN) + ecs_strbuf_appendstr(&lib, "lib"); + ecs_strbuf_appendstr(&lib, file_base); + ecs_strbuf_appendstr(&lib, ".dylib"); +#elif defined(ECS_OS_WINDOWS) + ecs_strbuf_appendstr(&lib, file_base); + ecs_strbuf_appendstr(&lib, ".dll"); +#endif -/* Public functions */ + ecs_os_free(file_base); -void ecs_get_path_w_sep_buf( - const ecs_world_t *world, - ecs_entity_t parent, - ecs_entity_t child, - const char *sep, - const char *prefix, - ecs_strbuf_t *buf) -{ - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(buf != NULL, ECS_INVALID_PARAMETER, NULL); + return ecs_strbuf_get(&lib); +} - world = ecs_get_world(world); +static +char* ecs_os_api_module_to_etc(const char *module) { + ecs_strbuf_t lib = ECS_STRBUF_INIT; - if (!sep) { - sep = "."; - } + /* Best guess, use module name with dashes + /etc */ + char *file_base = module_file_base(module, '-'); - if (!child || parent != child) { - path_append(world, parent, child, sep, prefix, buf); - } else { - ecs_strbuf_appendstr(buf, ""); - } + ecs_strbuf_appendstr(&lib, file_base); + ecs_strbuf_appendstr(&lib, "/etc"); -error: - return; -} + ecs_os_free(file_base); -char* ecs_get_path_w_sep( - const ecs_world_t *world, - ecs_entity_t parent, - ecs_entity_t child, - const char *sep, - const char *prefix) -{ - ecs_strbuf_t buf = ECS_STRBUF_INIT; - ecs_get_path_w_sep_buf(world, parent, child, sep, prefix, &buf); - return ecs_strbuf_get(&buf); + return ecs_strbuf_get(&lib); } -ecs_entity_t ecs_lookup_child( - const ecs_world_t *world, - ecs_entity_t parent, - const char *name) +void ecs_os_set_api_defaults(void) { - ecs_check(world != NULL, ECS_INTERNAL_ERROR, NULL); - - if (is_number(name)) { - return name_to_id(world, name); + /* Don't overwrite if already initialized */ + if (ecs_os_api_initialized != 0) { + return; } - ecs_filter_t f; - int ret = ecs_filter_init(world, &f, &(ecs_filter_desc_t) { - .terms = { - { .id = ecs_pair( ecs_id(EcsIdentifier), EcsName) }, - { .id = ecs_pair(EcsChildOf, parent) }, - { .id = EcsDisabled, .oper = EcsOptional }, - { .id = EcsPrefab, .oper = EcsOptional } - } - }); + flecs_os_time_setup(); - ecs_check(ret == 0, ECS_INTERNAL_ERROR, NULL); - (void)ret; - - ecs_iter_t it = ecs_filter_iter(world, &f); - while (ecs_filter_next(&it)) { - EcsIdentifier *ids = ecs_term(&it, EcsIdentifier, 1); - int i; - for (i = 0; i < it.count; i ++) { - char *cur_name = ids[i].value; - if (cur_name && !ecs_os_strcmp(cur_name, name)) { - ecs_filter_fini(&f); - return it.entities[i]; - } - } - } + /* Memory management */ + ecs_os_api.malloc_ = ecs_os_api_malloc; + ecs_os_api.free_ = ecs_os_api_free; + ecs_os_api.realloc_ = ecs_os_api_realloc; + ecs_os_api.calloc_ = ecs_os_api_calloc; - ecs_filter_fini(&f); -error: - return 0; -} + /* Strings */ + ecs_os_api.strdup_ = ecs_os_api_strdup; -ecs_entity_t ecs_lookup( - const ecs_world_t *world, - const char *name) -{ - if (!name) { - return 0; - } + /* Time */ + ecs_os_api.sleep_ = flecs_os_time_sleep; + ecs_os_api.get_time_ = ecs_os_gettime; - ecs_check(world != NULL, ECS_INTERNAL_ERROR, NULL); - world = ecs_get_world(world); + /* Logging */ + ecs_os_api.log_ = log_msg; - ecs_entity_t e = get_builtin(name); - if (e) { - return e; + /* Modules */ + if (!ecs_os_api.module_to_dl_) { + ecs_os_api.module_to_dl_ = ecs_os_api_module_to_dl; } - if (is_number(name)) { - return name_to_id(world, name); + if (!ecs_os_api.module_to_etc_) { + ecs_os_api.module_to_etc_ = ecs_os_api_module_to_etc; } - e = find_by_name(&world->aliases, name, 0, 0); - if (e) { - return e; - } - - return ecs_lookup_child(world, 0, name); -error: - return 0; + ecs_os_api.abort_ = abort; } -ecs_entity_t ecs_lookup_symbol( - const ecs_world_t *world, - const char *name, - bool lookup_as_path) -{ - if (!name) { - return 0; - } +bool ecs_os_has_heap(void) { + return + (ecs_os_api.malloc_ != NULL) && + (ecs_os_api.calloc_ != NULL) && + (ecs_os_api.realloc_ != NULL) && + (ecs_os_api.free_ != NULL); +} - ecs_check(world != NULL, ECS_INTERNAL_ERROR, NULL); - world = ecs_get_world(world); +bool ecs_os_has_threading(void) { + return + (ecs_os_api.mutex_new_ != NULL) && + (ecs_os_api.mutex_free_ != NULL) && + (ecs_os_api.mutex_lock_ != NULL) && + (ecs_os_api.mutex_unlock_ != NULL) && + (ecs_os_api.cond_new_ != NULL) && + (ecs_os_api.cond_free_ != NULL) && + (ecs_os_api.cond_wait_ != NULL) && + (ecs_os_api.cond_signal_ != NULL) && + (ecs_os_api.cond_broadcast_ != NULL) && + (ecs_os_api.thread_new_ != NULL) && + (ecs_os_api.thread_join_ != NULL); +} - ecs_entity_t e = find_by_name(&world->symbols, name, 0, 0); - if (e) { - return e; - } +bool ecs_os_has_time(void) { + return + (ecs_os_api.get_time_ != NULL) && + (ecs_os_api.sleep_ != NULL); +} - if (lookup_as_path) { - return ecs_lookup_fullpath(world, name); - } +bool ecs_os_has_logging(void) { + return (ecs_os_api.log_ != NULL); +} -error: - return 0; +bool ecs_os_has_dl(void) { + return + (ecs_os_api.dlopen_ != NULL) && + (ecs_os_api.dlproc_ != NULL) && + (ecs_os_api.dlclose_ != NULL); } -ecs_entity_t ecs_lookup_path_w_sep( - const ecs_world_t *world, - ecs_entity_t parent, - const char *path, - const char *sep, - const char *prefix, - bool recursive) +bool ecs_os_has_modules(void) { + return + (ecs_os_api.module_to_dl_ != NULL) && + (ecs_os_api.module_to_etc_ != NULL); +} + +#if defined(_MSC_VER) +static char error_str[255]; +#endif + +const char* ecs_os_strerror(int err) { +#if defined(_MSC_VER) + strerror_s(error_str, 255, err); + return error_str; +#else + return strerror(err); +#endif +} + +static +ecs_vector_t* sort_and_dedup( + ecs_vector_t *result) { - if (!path) { - return 0; - } + /* Sort vector */ + ecs_vector_sort(result, ecs_id_t, flecs_entity_compare_qsort); - if (!sep) { - sep = "."; - } + /* Ensure vector doesn't contain duplicates */ + ecs_id_t *ids = ecs_vector_first(result, ecs_id_t); + int32_t i, offset = 0, count = ecs_vector_count(result); - ecs_check(world != NULL, ECS_INTERNAL_ERROR, NULL); - world = ecs_get_world(world); + for (i = 0; i < count; i ++) { + if (i && ids[i] == ids[i - 1]) { + offset ++; + } - ecs_entity_t e = get_builtin(path); - if (e) { - return e; + if (i + offset >= count) { + break; + } + + ids[i] = ids[i + offset]; } - e = find_by_name(&world->aliases, path, 0, 0); - if (e) { - return e; - } + ecs_vector_set_count(&result, ecs_id_t, i - offset); - char buff[ECS_NAME_BUFFER_LENGTH]; - const char *ptr, *ptr_start; - char *elem = buff; - int32_t len, size = ECS_NAME_BUFFER_LENGTH; - ecs_entity_t cur; - bool core_searched = false; + return result; +} - if (!sep) { - sep = "."; +/** Parse callback that adds type to type identifier */ +static +ecs_vector_t* expr_to_ids( + ecs_world_t *world, + const char *name, + const char *expr) +{ +#ifdef FLECS_PARSER + ecs_vector_t *result = NULL; + const char *ptr = expr; + ecs_term_t term = {0}; + + if (!ptr) { + return NULL; } - parent = get_parent_from_path(world, parent, &path, prefix, true); + while (ptr[0] && (ptr = ecs_parse_term(world, name, expr, ptr, &term))) { + if (term.name) { + ecs_parser_error(name, expr, (ptr - expr), + "column names not supported in type expression"); + goto error; + } -retry: - cur = parent; - ptr_start = ptr = path; + if (term.oper != EcsAnd && term.oper != EcsAndFrom) { + ecs_parser_error(name, expr, (ptr - expr), + "operator other than AND not supported in type expression"); + goto error; + } - while ((ptr = path_elem(ptr, sep, &len))) { - if (len < size) { - ecs_os_memcpy(elem, ptr_start, len); - } else { - if (size == ECS_NAME_BUFFER_LENGTH) { - elem = NULL; - } + if (term.pred.set.mask == EcsDefaultSet) { + term.pred.set.mask = EcsSelf; + } + if (term.subj.set.mask == EcsDefaultSet) { + term.subj.set.mask = EcsSelf; + } + if (term.obj.set.mask == EcsDefaultSet) { + term.obj.set.mask = EcsSelf; + } - elem = ecs_os_realloc(elem, len + 1); - ecs_os_memcpy(elem, ptr_start, len); - size = len + 1; + if (ecs_term_finalize(world, name, &term)) { + goto error; } - elem[len] = '\0'; - ptr_start = ptr; + if (term.subj.entity == 0) { + /* Empty term */ + goto done; + } - cur = ecs_lookup_child(world, cur, elem); - if (!cur) { - goto tail; + if (term.subj.set.mask != EcsSelf) { + ecs_parser_error(name, expr, (ptr - expr), + "source modifiers not supported for type expressions"); + goto error; } - } -tail: - if (!cur && recursive) { - if (!core_searched) { - if (parent) { - parent = ecs_get_object(world, parent, EcsChildOf, 0); - } else { - parent = EcsFlecsCore; - core_searched = true; - } - goto retry; + if (term.subj.entity != EcsThis) { + ecs_parser_error(name, expr, (ptr - expr), + "subject other than this not supported in type expression"); + goto error; } - } - if (elem != buff) { - ecs_os_free(elem); + if (term.oper == EcsAndFrom) { + term.role = ECS_AND; + } + + if (term.role == ECS_CASE) { + term.id = ECS_PAIR_OBJECT(term.id); + } + + ecs_id_t* elem = ecs_vector_add(&result, ecs_id_t); + *elem = term.id | term.role; + + ecs_term_fini(&term); } - return cur; + result = sort_and_dedup(result); + +done: + return result; error: - return 0; + ecs_term_fini(&term); + ecs_vector_free(result); + return NULL; +#else + (void)world; + (void)name; + (void)expr; + ecs_abort(ECS_UNSUPPORTED, "parser addon is not available"); + return NULL; +#endif } -ecs_entity_t ecs_set_scope( +/* Create normalized type. A normalized type resolves all elements with an + * AND flag and appends them to the resulting type, where the default type + * maintains the original type hierarchy. */ +static +ecs_vector_t* ids_to_normalized_ids( ecs_world_t *world, - ecs_entity_t scope) + ecs_vector_t *ids) { - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_stage_t *stage = flecs_stage_from_world(&world); + ecs_vector_t *result = NULL; - ecs_entity_t cur = stage->scope; - stage->scope = scope; + ecs_entity_t *array = ecs_vector_first(ids, ecs_id_t); + int32_t i, count = ecs_vector_count(ids); - if (scope) { - ecs_id_t id = ecs_pair(EcsChildOf, scope); - stage->scope_table = flecs_table_traverse_add( - world, &world->store.root, &id, NULL); - } else { - stage->scope_table = &world->store.root; + for (i = 0; i < count; i ++) { + ecs_entity_t e = array[i]; + if (ECS_HAS_ROLE(e, AND)) { + ecs_entity_t entity = ECS_PAIR_OBJECT(e); + + const EcsType *type_ptr = ecs_get(world, entity, EcsType); + ecs_assert(type_ptr != NULL, ECS_INVALID_PARAMETER, + "flag must be applied to type"); + + ecs_vector_each(type_ptr->normalized, ecs_id_t, c_ptr, { + ecs_entity_t *el = ecs_vector_add(&result, ecs_id_t); + *el = *c_ptr; + }) + } else { + ecs_entity_t *el = ecs_vector_add(&result, ecs_id_t); + *el = e; + } } - return cur; -error: - return 0; + return sort_and_dedup(result); } -ecs_entity_t ecs_get_scope( - const ecs_world_t *world) +static +ecs_table_t* table_from_ids( + ecs_world_t *world, + ecs_vector_t *ids) { - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - const ecs_stage_t *stage = flecs_stage_from_readonly_world(world); - return stage->scope; -error: - return 0; + ecs_ids_t ids_array = flecs_type_to_ids(ids); + ecs_table_t *result = flecs_table_find_or_create(world, &ids_array); + return result; } -const char* ecs_set_name_prefix( +/* If a name prefix is set with ecs_set_name_prefix, check if the entity name + * has the prefix, and if so remove it. This enables using prefixed names in C + * for components / systems while storing a canonical / language independent + * identifier. */ +const char* flecs_name_from_symbol( ecs_world_t *world, - const char *prefix) + const char *type_name) { - ecs_poly_assert(world, ecs_world_t); - const char *old_prefix = world->name_prefix; - world->name_prefix = prefix; - return old_prefix; + const char *prefix = world->name_prefix; + if (type_name && prefix) { + ecs_size_t len = ecs_os_strlen(prefix); + if (!ecs_os_strncmp(type_name, prefix, len) && + (isupper(type_name[len]) || type_name[len] == '_')) + { + if (type_name[len] == '_') { + return type_name + len + 1; + } else { + return type_name + len; + } + } + } + + return type_name; } -ecs_entity_t ecs_add_path_w_sep( +/* -- Public functions -- */ + +ecs_type_t ecs_type_from_str( ecs_world_t *world, - ecs_entity_t entity, - ecs_entity_t parent, - const char *path, - const char *sep, - const char *prefix) + const char *expr) { - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_vector_t *ids = expr_to_ids(world, NULL, expr); + if (!ids) { + return NULL; + } - if (!sep) { - sep = "."; - } + ecs_vector_t *normalized_ids = ids_to_normalized_ids(world, ids); + ecs_vector_free(ids); - if (!path) { - if (!entity) { - entity = ecs_new_id(world); - } + ecs_table_t *table = table_from_ids(world, normalized_ids); + ecs_vector_free(normalized_ids); - if (parent) { - ecs_add_pair(world, entity, EcsChildOf, entity); - } + return table->type; +} - return entity; +ecs_table_t* ecs_table_from_str( + ecs_world_t *world, + const char *expr) +{ + ecs_poly_assert(world, ecs_world_t); + + ecs_vector_t *ids = expr_to_ids(world, NULL, expr); + if (!ids) { + return NULL; } - char buff[ECS_NAME_BUFFER_LENGTH]; - const char *ptr = path; - const char *ptr_start = path; - char *elem = buff; - int32_t len, size = ECS_NAME_BUFFER_LENGTH; + ecs_table_t *result = table_from_ids(world, ids); + ecs_vector_free(ids); - parent = get_parent_from_path(world, parent, &path, prefix, entity == 0); + return result; +} - ecs_entity_t cur = parent; +static +bool match_id( + const ecs_world_t *world, + ecs_entity_t id, + ecs_entity_t match_with) +{ + (void)world; + + if (ECS_HAS_ROLE(match_with, CASE)) { + ecs_entity_t sw = ECS_PAIR_RELATION(match_with); + if (id == (ECS_SWITCH | sw)) { +#ifdef FLECS_SANITIZE + ecs_entity_t sw_case = ECS_PAIR_OBJECT(match_with); + const EcsType *sw_type = ecs_get(world, sw, EcsType); + ecs_assert(sw_type != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(ecs_type_has_id(world, sw_type->normalized, + ecs_get_alive(world, sw_case), false) == true, + ECS_INVALID_PARAMETER, NULL); + (void)sw_case; + (void)sw_type; +#endif + return true; + } else { + return false; + } + } else { + return ecs_id_match(id, match_with); + } +} - char *name = NULL; +static +int32_t search_type( + const ecs_world_t *world, + const ecs_table_t *table, + ecs_type_t type, + int32_t offset, + ecs_id_t id, + ecs_entity_t rel, + int32_t min_depth, + int32_t max_depth, + int32_t depth, + ecs_entity_t *subject_out, + ecs_id_t *id_out, + int32_t *count_out) +{ + ecs_assert(offset >= 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(depth >= 0, ECS_INTERNAL_ERROR, NULL); - while ((ptr = path_elem(ptr, sep, &len))) { - if (len < size) { - ecs_os_memcpy(elem, ptr_start, len); + if (!id) { + return -1; + } + + if (!type) { + return -1; + } + + if (max_depth && depth > max_depth) { + return -1; + } + + int32_t i, count = ecs_vector_count(type); + ecs_id_t *ids = ecs_vector_first(type, ecs_id_t), tid; + + if (depth >= min_depth) { + if (table && !offset && !(ECS_HAS_ROLE(id, CASE))) { + ecs_table_record_t *tr = flecs_get_table_record(world, table, id); + if (tr) { + int32_t column = tr->column; + if (count_out) { + *count_out = tr->count; + } + if (id_out) { + *id_out = ids[column]; + } + return column; + } } else { - if (size == ECS_NAME_BUFFER_LENGTH) { - elem = NULL; + for (i = offset; i < count; i ++) { + tid = ids[i]; + if (match_id(world, tid, id)) { + if (id_out) { + *id_out = tid; + } + return i; + } } - - elem = ecs_os_realloc(elem, len + 1); - ecs_os_memcpy(elem, ptr_start, len); - size = len + 1; } + } - elem[len] = '\0'; - ptr_start = ptr; + if (rel && id != EcsPrefab && id != EcsDisabled && + ECS_PAIR_RELATION(id) != ecs_id(EcsIdentifier) && + ECS_PAIR_RELATION(id) != EcsChildOf) + { + int32_t ret; - ecs_entity_t e = ecs_lookup_child(world, cur, elem); - if (!e) { - if (name) { - ecs_os_free(name); + for (i = 0; i < count; i ++) { + tid = ids[i]; + if (!ECS_HAS_RELATION(tid, rel)) { + continue; } - name = ecs_os_strdup(elem); + ecs_entity_t obj = ecs_pair_object(world, tid); + ecs_assert(obj != 0, ECS_INTERNAL_ERROR, NULL); - /* If this is the last entity in the path, use the provided id */ - bool last_elem = false; - if (!path_elem(ptr, sep, NULL)) { - e = entity; - last_elem = true; + ecs_table_t *obj_table = ecs_get_table(world, obj); + if (!obj_table) { + continue; } - if (!e) { - if (last_elem) { - ecs_entity_t prev = ecs_set_scope(world, 0); - e = ecs_new(world, 0); - ecs_set_scope(world, prev); - } else { - e = ecs_new_id(world); + if ((ret = search_type(world, obj_table, obj_table->type, 0, id, + rel, min_depth, max_depth, depth + 1, subject_out, id_out, NULL)) != -1) + { + if (subject_out && !*subject_out) { + *subject_out = obj; } - } - - ecs_set_name(world, e, name); + if (id_out && !*id_out) { + *id_out = tid; + } + return ret; - if (cur) { - ecs_add_pair(world, e, EcsChildOf, cur); + /* If the id could not be found on the object and the relationship + * is not IsA, try substituting the object type with IsA */ + } else if (rel != EcsIsA) { + if ((ret = search_type(world, obj_table, obj_table->type, 0, + id, EcsIsA, 1, 0, 0, subject_out, id_out, NULL)) != -1) + { + if (subject_out && !*subject_out) { + *subject_out = obj; + } + if (id_out && !*id_out) { + *id_out = tid; + } + return ret; + } } } - - cur = e; } - if (entity && (cur != entity)) { - if (name) { - ecs_os_free(name); - } - - name = ecs_os_strdup(elem); + return -1; +} - ecs_set_name(world, entity, name); - } +bool ecs_type_has_id( + const ecs_world_t *world, + ecs_type_t type, + ecs_id_t id, + bool owned) +{ + ecs_poly_assert(world, ecs_world_t); + + return search_type(world, NULL, type, 0, id, owned ? 0 : EcsIsA, 0, 0, 0, + NULL, NULL, NULL) != -1; +} - if (name) { - ecs_os_free(name); - } +int32_t ecs_type_index_of( + ecs_type_t type, + int32_t offset, + ecs_id_t id) +{ + return search_type(NULL, NULL, type, offset, id, 0, 0, 0, 0, + NULL, NULL, NULL); +} - if (elem != buff) { - ecs_os_free(elem); +int32_t ecs_type_match( + const ecs_world_t *world, + const ecs_table_t *table, + ecs_type_t type, + int32_t offset, + ecs_id_t id, + ecs_entity_t rel, + int32_t min_depth, + int32_t max_depth, + ecs_entity_t *subject_out, + ecs_id_t *id_out, + int32_t *count_out) +{ + ecs_poly_assert(world, ecs_world_t); + + if (subject_out) { + *subject_out = 0; } - return cur; -error: - return 0; + return search_type(world, table, type, offset, id, rel, min_depth, + max_depth, 0, subject_out, id_out, count_out); } -ecs_entity_t ecs_new_from_path_w_sep( - ecs_world_t *world, - ecs_entity_t parent, - const char *path, - const char *sep, - const char *prefix) +char* ecs_type_str( + const ecs_world_t *world, + ecs_type_t type) { - if (!sep) { - sep = "."; + if (!type) { + return ecs_os_strdup(""); } - return ecs_add_path_w_sep(world, 0, parent, path, sep, prefix); -} + ecs_strbuf_t buf = ECS_STRBUF_INIT; + ecs_entity_t *ids = ecs_vector_first(type, ecs_entity_t); + int32_t i, count = ecs_vector_count(type); -void ecs_use( - ecs_world_t *world, - ecs_entity_t entity, - const char *name) -{ - register_by_name(&world->aliases, entity, name, 0, 0); -} + for (i = 0; i < count; i ++) { + ecs_entity_t id = ids[i]; + + if (i) { + ecs_strbuf_appendch(&buf, ','); + ecs_strbuf_appendch(&buf, ' '); + } + + if (id == 1) { + ecs_strbuf_appendstr(&buf, "Component"); + } else { + ecs_id_str_buf(world, id, &buf); + } + } + return ecs_strbuf_get(&buf); +} diff --git a/flecs.h b/flecs.h index a7b2218f31..54959f4d4e 100644 --- a/flecs.h +++ b/flecs.h @@ -134,7 +134,6 @@ #endif - #ifdef __cplusplus extern "C" { #endif @@ -430,7 +429,6 @@ typedef int32_t ecs_size_t; #endif #endif - /** * @file log.h * @brief Logging addon. @@ -831,7 +829,6 @@ int ecs_log_last_error(void); #endif // FLECS_LOG_H - /** * @file vector.h * @brief Vector datastructure. @@ -1372,7 +1369,6 @@ class vector { #endif #endif - /** * @file map.h * @brief Map datastructure. @@ -1633,7 +1629,6 @@ class map { #endif #endif - /** * @file strbuf.h * @brief Utility for constructing strings. @@ -1836,7 +1831,6 @@ int32_t ecs_strbuf_written( #endif #endif - /** * @file os_api.h * @brief Operating system abstraction API. @@ -2324,7 +2318,6 @@ bool ecs_os_has_modules(void); #endif - #ifdef __cplusplus extern "C" { #endif @@ -3108,7 +3101,6 @@ typedef void (*ecs_on_set_t)( #endif - /** * @file api_support.h * @brief Support functions and constants. @@ -3190,7 +3182,6 @@ void ecs_default_ctor( #endif #endif - /** * @file hashmap.h * @brief Hashmap datastructure. @@ -3319,7 +3310,6 @@ void* _flecs_hashmap_next( #endif #endif - /** * @file type.h * @brief Type API. @@ -3379,7 +3369,6 @@ int32_t ecs_type_match( #endif - /** * @defgroup desc_types Types used for creating API constructs * @{ @@ -3756,7 +3745,6 @@ extern "C" { #endif #endif - #endif @@ -6777,7 +6765,6 @@ int ecs_app_set_frame_action( #endif #endif // FLECS_APP - #endif #ifdef FLECS_REST /** @@ -6799,6 +6786,26 @@ int ecs_app_set_frame_action( * /entity/parent/child * /entity/420 * + * Parameters: + * The following parameters can be added to the endpoint: + * + * - path : bool + * Add path (name) for entity. + * Default: true + * + * - base : bool + * Add base components. + * Default: true + * + * - values : bool + * Add component values. + * Default: true + * + * - type_info : bool + * Add reflection data for component types. Requires values=true. + * Default: false + * + * * /query?q= * The query endpoint requests data for a query. The implementation uses the * rules query engine. The format of the response is the same as what is @@ -6807,6 +6814,53 @@ int ecs_app_set_frame_action( * Example: * /query?q=Position * /query?q=Position%2CVelocity + * + * Parameters: + * The following parameters can be added to the endpoint: + * + * - term_ids : bool + * Add top-level "ids" array with components as specified by query. + * Default: true + * + * - ids : bool + * Add result-specific "ids" array with components as matched. Can be + * different from top-level "ids" array for queries with wildcards. + * Default: true + * + * - subjects : bool + * Add result-specific "subjects" array with component source. A 0 element + * indicates the component is matched on the current (This) entity. + * Default: true + * + * - variables : bool + * Add result-specific "variables" array with values for variables, if any. + * Default: true + * + * - is_set : bool + * Add result-specific "is_set" array with boolean elements indicating + * whether component was matched (used for optional terms). + * Default: true + * + * - values : bool + * Add result-specific "values" array with component values. A 0 element + * indicates a component that could not be serialized, which can be either + * because no reflection data was registered, because the component has no + * data, or because the query didn't request it. + * Default: true + * + * - entities : bool + * Add result-specific "entities" array with matched entities. + * Default: true + * + * - duration : bool + * Include measurement on how long it took to serialize result. + * Default: false + * + * - type_info : bool + * Add top-level "type_info" array with reflection data on the type in + * the query. If a query element has a component that has no reflection + * data, a 0 element is added to the array. + * Default: false */ #ifdef FLECS_REST @@ -6861,7 +6915,6 @@ void FlecsRestImport( #endif #endif - #endif #ifdef FLECS_TIMER /** @@ -7107,7 +7160,6 @@ void FlecsTimerImport( #endif #endif - #endif #ifdef FLECS_PIPELINE /** @@ -7290,7 +7342,6 @@ void FlecsPipelineImport( #endif #endif - #endif #ifdef FLECS_SYSTEM /** @@ -7575,7 +7626,6 @@ void FlecsSystemImport( #endif #endif - #endif #ifdef FLECS_COREDOC /** @@ -7616,7 +7666,6 @@ void FlecsCoreDocImport( #endif #endif - #endif #ifdef FLECS_DOC /** @@ -7732,7 +7781,6 @@ void FlecsDocImport( #endif #endif - #endif #ifdef FLECS_JSON /** @@ -7850,6 +7898,46 @@ int ecs_ptr_to_json_buf( const void *data, ecs_strbuf_t *buf_out); +/** Serialize type info to JSON. + * This serializes type information to JSON, and can be used to store/transmit + * the structure of a (component) value. + * + * If the provided type does not have reflection data, "0" will be returned. + * + * @param world The world. + * @param type The type to serialize to JSON. + * @return A JSON string with the serialized type info, or NULL if failed. + */ +FLECS_API +char* ecs_type_info_to_json( + const ecs_world_t *world, + ecs_entity_t type); + +/** Serialize type info into JSON string buffer. + * Same as ecs_type_info_to_json, but serializes to an ecs_strbuf_t instance. + * + * @param world The world. + * @param type The type to serialize. + * @param buf_out The strbuf to append the string to. + * @return Zero if success, non-zero if failed. + */ +FLECS_API +int ecs_type_info_to_json_buf( + const ecs_world_t *world, + ecs_entity_t type, + ecs_strbuf_t *buf_out); + +/** Used with ecs_iter_to_json. */ +typedef struct ecs_entity_to_json_desc_t { + bool serialize_path; /* Serialize full pathname */ + bool serialize_base; /* Serialize base components */ + bool serialize_values; /* Serialize component values */ + bool serialize_type_info; /* Serialize type info (requires serialize_values) */ +} ecs_entity_to_json_desc_t; + +#define ECS_ENTITY_TO_JSON_INIT (ecs_entity_to_json_desc_t) {\ + true, true, true, false } + /** Serialize entity into JSON string. * This creates a JSON object with the entity's (path) name, which components * and tags the entity has, and the component values. @@ -7863,7 +7951,8 @@ int ecs_ptr_to_json_buf( FLECS_API char* ecs_entity_to_json( const ecs_world_t *world, - ecs_entity_t entity); + ecs_entity_t entity, + const ecs_entity_to_json_desc_t *desc); /** Serialize entity into JSON string buffer. * Same as ecs_entity_to_json, but serializes to an ecs_strbuf_t instance. @@ -7877,20 +7966,25 @@ FLECS_API int ecs_entity_to_json_buf( const ecs_world_t *world, ecs_entity_t entity, - ecs_strbuf_t *buf_out); + ecs_strbuf_t *buf_out, + const ecs_entity_to_json_desc_t *desc); /** Used with ecs_iter_to_json. */ typedef struct ecs_iter_to_json_desc_t { - bool dont_serialize_term_ids; /* Exclude term (query) component ids from result */ - bool dont_serialize_ids; /* Exclude actual (matched) component ids from result */ - bool dont_serialize_subjects; /* Exclude subjects from result */ - bool dont_serialize_variables; /* Exclude variables from result */ - bool dont_serialize_is_set; /* Exclude is_set (for optional terms) */ - bool dont_serialize_values; /* Exclude component values from result */ - bool dont_serialize_entities; /* Exclude entities (for This terms) */ - bool measure_eval_duration; /* Include evaluation duration */ + bool serialize_term_ids; /* Exclude term (query) component ids from result */ + bool serialize_ids; /* Exclude actual (matched) component ids from result */ + bool serialize_subjects; /* Exclude subjects from result */ + bool serialize_variables; /* Exclude variables from result */ + bool serialize_is_set; /* Exclude is_set (for optional terms) */ + bool serialize_values; /* Exclude component values from result */ + bool serialize_entities; /* Exclude entities (for This terms) */ + bool measure_eval_duration; /* Include evaluation duration */ + bool serialize_type_info; /* Include type information */ } ecs_iter_to_json_desc_t; +#define ECS_ITER_TO_JSON_INIT (ecs_iter_to_json_desc_t) {\ + true, true, true, true, true, true, true, false, false } + /** Serialize iterator into JSON string. * This operation will iterate the contents of the iterator and serialize them * to JSON. The function acccepts iterators from any source. @@ -7927,7 +8021,6 @@ int ecs_iter_to_json_buf( #endif #endif - #endif #if defined(FLECS_EXPR) || defined(FLECS_META_C) #define FLECS_META @@ -8429,7 +8522,6 @@ void FlecsMetaImport( #endif #endif - #endif #ifdef FLECS_EXPR /** @@ -8639,7 +8731,6 @@ const char *ecs_parse_expr_token( #endif #endif - #endif #ifdef FLECS_META_C /** @@ -8794,7 +8885,6 @@ int ecs_meta_from_desc( #endif // FLECS_META_C_H #endif // FLECS_META_C - #endif #ifdef FLECS_PLECS /** @@ -8877,7 +8967,6 @@ int ecs_plecs_from_file( #endif #endif - #endif #ifdef FLECS_RULES @@ -8964,7 +9053,6 @@ char* ecs_rule_str( #endif // FLECS_RULES_H #endif // FLECS_RULES - #endif #ifdef FLECS_SNAPSHOT /** @@ -9065,7 +9153,6 @@ void ecs_snapshot_free( #endif #endif - #endif #ifdef FLECS_STATS /** @@ -9273,7 +9360,6 @@ void ecs_gauge_reduce( #endif #endif - #endif #ifdef FLECS_PARSER /** @@ -9395,7 +9481,6 @@ char* ecs_parse_term( #endif // FLECS_PARSER_H #endif // FLECS_PARSER - #endif #ifdef FLECS_HTTP /** @@ -9585,7 +9670,6 @@ const char* ecs_http_get_param( #endif // FLECS_HTTP_H #endif // FLECS_HTTP - #endif #ifdef FLECS_OS_API_IMPL /** @@ -9612,7 +9696,6 @@ void ecs_set_os_api_impl(void); #endif // FLECS_OS_API_IMPL_H #endif // FLECS_OS_API_IMPL - #endif #ifdef FLECS_MODULE /** @@ -9726,7 +9809,6 @@ ecs_entity_t ecs_module_init( #endif #endif - #endif /** @@ -10142,7 +10224,6 @@ ecs_entity_t ecs_module_init( #endif // FLECS_C_ - #ifdef __cplusplus } @@ -10370,7 +10451,6 @@ struct array> final { }; } - // String utility that doesn't implicitly allocate memory. namespace flecs { @@ -10506,7 +10586,6 @@ struct string_view : string { }; } - // Wrapper around ecs_strbuf_t that provides a simple stringstream like API. namespace flecs { @@ -10551,7 +10630,6 @@ struct stringstream { } - // Neat utility to inspect arguments & returntype of a function type // Code from: https://stackoverflow.com/questions/27024238/c-template-mechanism-to-get-the-number-of-function-arguments-which-would-work @@ -10680,7 +10758,6 @@ template using first_arg_t = typename first_arg::type; } // flecs - // Utility for adding features (mixins) to a class without modifying its // definition. @@ -10730,8 +10807,6 @@ struct extendable : extendable_impl { } - - // Forward declarations namespace flecs { @@ -10869,7 +10944,6 @@ static const flecs::entity_t Throw = EcsThrow; } - // Mixin forward declarations #pragma once @@ -11035,7 +11109,6 @@ struct id { }; } - #pragma once namespace flecs { @@ -11066,7 +11139,6 @@ struct entity_m : mixin { using entity_m_world = entity_m; } - #pragma once namespace flecs { @@ -11088,7 +11160,6 @@ struct component_m : mixin { using component_m_world = component_m; } - #pragma once namespace flecs { @@ -11112,7 +11183,6 @@ struct type_m : mixin { using type_m_world = type_m; } - #pragma once namespace flecs { @@ -11148,7 +11218,6 @@ struct term_m : mixin { using term_m_world = term_m; } - #pragma once namespace flecs { @@ -11207,7 +11276,6 @@ using filter_m_world = filter_m; using filter_m_entity_view = filter_m; } - #pragma once namespace flecs { @@ -11250,7 +11318,6 @@ struct query_m : mixin { using query_m_world = query_m; } - #pragma once namespace flecs { @@ -11282,7 +11349,6 @@ struct trigger_m : mixin { using trigger_m_world = trigger_m; } - #pragma once namespace flecs { @@ -11314,7 +11380,6 @@ struct observer_m : mixin { using observer_m_world = observer_m; } - #ifdef FLECS_MODULE #pragma once @@ -11349,7 +11414,6 @@ struct module_m : mixin { using module_m_world = module_m; } - #endif #ifdef FLECS_SYSTEM #pragma once @@ -11393,7 +11457,6 @@ struct system_m : mixin { using system_m_world = system_m; } - #endif #ifdef FLECS_PIPELINE #pragma once @@ -11500,7 +11563,6 @@ struct pipeline_m : mixin { using pipeline_m_world = pipeline_m; } - #endif #ifdef FLECS_TIMER #pragma once @@ -11579,7 +11641,6 @@ using timer_m_world = timer_m; using timer_m_system = timer_m; } - #endif #ifdef FLECS_SNAPSHOT #pragma once @@ -11607,7 +11668,6 @@ flecs::snapshot snapshot(Args &&... args) const; using snapshot_m_world = snapshot_m; } - #endif #ifdef FLECS_DOC #pragma once @@ -11633,7 +11693,6 @@ struct doc_m : mixin { using doc_m_world = doc_m; } - #endif #ifdef FLECS_REST #pragma once @@ -11657,7 +11716,6 @@ struct rest_m : mixin { using rest_m_world = rest_m; } - #endif // Mixins (remove from list to disable) @@ -11732,7 +11790,6 @@ inline void err(const char *fmt, ...) { } } - namespace flecs { namespace _ { @@ -11852,7 +11909,6 @@ struct is_actual { }; } // flecs - namespace flecs { @@ -12301,7 +12357,6 @@ ecs_move_ctor_t move_dtor() { } // _ } // flecs - namespace flecs { @@ -13011,7 +13066,6 @@ struct world final : extendable { } // namespace flecs - namespace flecs { @@ -13462,7 +13516,6 @@ struct iter { }; } // namespace flecs - namespace flecs { @@ -13514,7 +13567,6 @@ struct ref { }; } - #pragma once #pragma once @@ -14103,7 +14155,6 @@ struct entity_builder_i { }; } - #pragma once namespace flecs @@ -14653,7 +14704,6 @@ struct entity_view : public id { } - namespace flecs { @@ -14924,7 +14974,6 @@ struct entity final : entity_base { } // namespace flecs - //////////////////////////////////////////////////////////////////////////////// //// Utility class to invoke a system each //////////////////////////////////////////////////////////////////////////////// @@ -15417,7 +15466,6 @@ struct entity_with_invoker::value > > } // namespace _ } // namespace flecs - #pragma once namespace flecs { @@ -16054,7 +16102,6 @@ flecs::entity_t type_id() { } } - #pragma once namespace flecs { @@ -16246,7 +16293,6 @@ struct type : type_base { } - // Mixin implementations namespace flecs { @@ -16345,7 +16391,6 @@ inline flecs::id id_m_world::pair(entity_t r, entity_t o) const { #undef flecs_me_ } - #pragma once namespace flecs { @@ -16546,7 +16591,6 @@ inline flecs::entity entity_m_world::prefab(Args &&... args) const { #undef flecs_me_ } - #pragma once namespace flecs { @@ -16562,7 +16606,6 @@ inline flecs::entity component_m_world::component(Args &&... args) const { #undef flecs_me_ } // namespace flecs - #pragma once namespace flecs { @@ -16578,7 +16621,6 @@ inline flecs::type type_m_world::type(Args &&... args) const { #undef flecs_me_ } - #pragma once #pragma once @@ -16936,7 +16978,6 @@ struct term_builder_i : term_id_builder_i { }; } - namespace flecs { #define flecs_me_ this->me() @@ -17089,7 +17130,6 @@ inline Base& term_builder_i::id(const flecs::type& type, id_t o) { #undef flecs_me_ } - #pragma once #pragma once @@ -17250,7 +17290,6 @@ struct filter_builder_i : term_builder_i { } - namespace flecs { @@ -17319,7 +17358,6 @@ struct filter_builder final : filter_builder_base { } - namespace flecs { @@ -17598,7 +17636,6 @@ inline filter filter_builder_base::build() const #undef flecs_me_ } - #pragma once #pragma once @@ -17711,7 +17748,6 @@ struct query_builder_i : filter_builder_i { } - namespace flecs { @@ -17778,7 +17814,6 @@ struct query_builder final : query_builder_base { } - namespace flecs { #define flecs_me_ this->me() @@ -17966,7 +18001,6 @@ inline Base& query_builder_i::parent(const query_base& par #undef flecs_me_ } // namespace flecs - #pragma once #pragma once @@ -18026,7 +18060,6 @@ struct trigger_builder_i : term_builder_i { } - namespace flecs { @@ -18090,7 +18123,6 @@ struct trigger_builder final } - namespace flecs { @@ -18142,7 +18174,6 @@ inline trigger trigger_builder::each(Func&& func) { } } // namespace flecs - #pragma once #pragma once @@ -18201,7 +18232,6 @@ struct observer_builder_i : filter_builder_i { } - namespace flecs { @@ -18268,7 +18298,6 @@ struct observer_builder final } - namespace flecs { @@ -18320,7 +18349,6 @@ inline observer observer_builder::each(Func&& func) const { } } // namespace flecs - #ifdef FLECS_MODULE #pragma once @@ -18399,7 +18427,6 @@ inline flecs::entity module_m_world::import() { } } - #endif #ifdef FLECS_SYSTEM #pragma once @@ -18526,7 +18553,6 @@ struct system_builder_i : query_builder_i { } - namespace flecs { @@ -18597,7 +18623,6 @@ struct system_builder final } - namespace flecs { @@ -18734,7 +18759,6 @@ inline system system_builder::each(Func&& func) const { } } // namespace flecs - #endif #ifdef FLECS_PIPELINE #pragma once @@ -18814,7 +18838,6 @@ inline int32_t pipeline_m_world::get_threads() const { } } - #endif #ifdef FLECS_TIMER #pragma once @@ -18880,7 +18903,6 @@ inline void timer_m_base::set_tick_source(flecs::entity e) { #undef flecs_me_ } - #endif #ifdef FLECS_SNAPSHOT #pragma once @@ -18972,7 +18994,6 @@ inline flecs::snapshot snapshot_m_world::snapshot(Args &&... args) const { #undef flecs_me_ } - #endif #ifdef FLECS_DOC #pragma once @@ -19017,7 +19038,6 @@ inline void doc_m_world::init() { #undef flecs_me_ } - #endif #ifdef FLECS_REST #pragma once @@ -19034,7 +19054,6 @@ inline void rest_m_world::init() { #undef flecs_me_ } - #endif @@ -19063,7 +19082,6 @@ inline void ctor_world_entity_impl( } // _ } // flecs - namespace flecs { @@ -19116,7 +19134,6 @@ inline flecs::type iter::type() const { } } // namespace flecs - #pragma once namespace flecs @@ -19237,12 +19254,8 @@ void world::set(const Func& func) { } } // namespace flecs - - - #endif #endif #endif - diff --git a/include/flecs/addons/json.h b/include/flecs/addons/json.h index f7060755a6..f6b4b10f01 100644 --- a/include/flecs/addons/json.h +++ b/include/flecs/addons/json.h @@ -113,6 +113,46 @@ int ecs_ptr_to_json_buf( const void *data, ecs_strbuf_t *buf_out); +/** Serialize type info to JSON. + * This serializes type information to JSON, and can be used to store/transmit + * the structure of a (component) value. + * + * If the provided type does not have reflection data, "0" will be returned. + * + * @param world The world. + * @param type The type to serialize to JSON. + * @return A JSON string with the serialized type info, or NULL if failed. + */ +FLECS_API +char* ecs_type_info_to_json( + const ecs_world_t *world, + ecs_entity_t type); + +/** Serialize type info into JSON string buffer. + * Same as ecs_type_info_to_json, but serializes to an ecs_strbuf_t instance. + * + * @param world The world. + * @param type The type to serialize. + * @param buf_out The strbuf to append the string to. + * @return Zero if success, non-zero if failed. + */ +FLECS_API +int ecs_type_info_to_json_buf( + const ecs_world_t *world, + ecs_entity_t type, + ecs_strbuf_t *buf_out); + +/** Used with ecs_iter_to_json. */ +typedef struct ecs_entity_to_json_desc_t { + bool serialize_path; /* Serialize full pathname */ + bool serialize_base; /* Serialize base components */ + bool serialize_values; /* Serialize component values */ + bool serialize_type_info; /* Serialize type info (requires serialize_values) */ +} ecs_entity_to_json_desc_t; + +#define ECS_ENTITY_TO_JSON_INIT (ecs_entity_to_json_desc_t) {\ + true, true, true, false } + /** Serialize entity into JSON string. * This creates a JSON object with the entity's (path) name, which components * and tags the entity has, and the component values. @@ -126,7 +166,8 @@ int ecs_ptr_to_json_buf( FLECS_API char* ecs_entity_to_json( const ecs_world_t *world, - ecs_entity_t entity); + ecs_entity_t entity, + const ecs_entity_to_json_desc_t *desc); /** Serialize entity into JSON string buffer. * Same as ecs_entity_to_json, but serializes to an ecs_strbuf_t instance. @@ -140,20 +181,25 @@ FLECS_API int ecs_entity_to_json_buf( const ecs_world_t *world, ecs_entity_t entity, - ecs_strbuf_t *buf_out); + ecs_strbuf_t *buf_out, + const ecs_entity_to_json_desc_t *desc); /** Used with ecs_iter_to_json. */ typedef struct ecs_iter_to_json_desc_t { - bool dont_serialize_term_ids; /* Exclude term (query) component ids from result */ - bool dont_serialize_ids; /* Exclude actual (matched) component ids from result */ - bool dont_serialize_subjects; /* Exclude subjects from result */ - bool dont_serialize_variables; /* Exclude variables from result */ - bool dont_serialize_is_set; /* Exclude is_set (for optional terms) */ - bool dont_serialize_values; /* Exclude component values from result */ - bool dont_serialize_entities; /* Exclude entities (for This terms) */ - bool measure_eval_duration; /* Include evaluation duration */ + bool serialize_term_ids; /* Exclude term (query) component ids from result */ + bool serialize_ids; /* Exclude actual (matched) component ids from result */ + bool serialize_subjects; /* Exclude subjects from result */ + bool serialize_variables; /* Exclude variables from result */ + bool serialize_is_set; /* Exclude is_set (for optional terms) */ + bool serialize_values; /* Exclude component values from result */ + bool serialize_entities; /* Exclude entities (for This terms) */ + bool measure_eval_duration; /* Include evaluation duration */ + bool serialize_type_info; /* Include type information */ } ecs_iter_to_json_desc_t; +#define ECS_ITER_TO_JSON_INIT (ecs_iter_to_json_desc_t) {\ + true, true, true, true, true, true, true, false, false } + /** Serialize iterator into JSON string. * This operation will iterate the contents of the iterator and serialize them * to JSON. The function acccepts iterators from any source. diff --git a/include/flecs/addons/rest.h b/include/flecs/addons/rest.h index d2719097cd..2ee19459fd 100644 --- a/include/flecs/addons/rest.h +++ b/include/flecs/addons/rest.h @@ -17,6 +17,26 @@ * /entity/parent/child * /entity/420 * + * Parameters: + * The following parameters can be added to the endpoint: + * + * - path : bool + * Add path (name) for entity. + * Default: true + * + * - base : bool + * Add base components. + * Default: true + * + * - values : bool + * Add component values. + * Default: true + * + * - type_info : bool + * Add reflection data for component types. Requires values=true. + * Default: false + * + * * /query?q= * The query endpoint requests data for a query. The implementation uses the * rules query engine. The format of the response is the same as what is @@ -25,6 +45,53 @@ * Example: * /query?q=Position * /query?q=Position%2CVelocity + * + * Parameters: + * The following parameters can be added to the endpoint: + * + * - term_ids : bool + * Add top-level "ids" array with components as specified by query. + * Default: true + * + * - ids : bool + * Add result-specific "ids" array with components as matched. Can be + * different from top-level "ids" array for queries with wildcards. + * Default: true + * + * - subjects : bool + * Add result-specific "subjects" array with component source. A 0 element + * indicates the component is matched on the current (This) entity. + * Default: true + * + * - variables : bool + * Add result-specific "variables" array with values for variables, if any. + * Default: true + * + * - is_set : bool + * Add result-specific "is_set" array with boolean elements indicating + * whether component was matched (used for optional terms). + * Default: true + * + * - values : bool + * Add result-specific "values" array with component values. A 0 element + * indicates a component that could not be serialized, which can be either + * because no reflection data was registered, because the component has no + * data, or because the query didn't request it. + * Default: true + * + * - entities : bool + * Add result-specific "entities" array with matched entities. + * Default: true + * + * - duration : bool + * Include measurement on how long it took to serialize result. + * Default: false + * + * - type_info : bool + * Add top-level "type_info" array with reflection data on the type in + * the query. If a query element has a component that has no reflection + * data, a 0 element is added to the array. + * Default: false */ #ifdef FLECS_REST diff --git a/meson.build b/meson.build index 07b717f3fc..cd87047d7b 100644 --- a/meson.build +++ b/meson.build @@ -20,6 +20,8 @@ flecs_src = files( 'src/addons/http.c', 'src/addons/json/deserialize.c', 'src/addons/json/serialize.c', + 'src/addons/json/serialize_type_info.c', + 'src/addons/json/json.c', 'src/addons/log.c', 'src/addons/meta/api.c', 'src/addons/meta/meta.c', diff --git a/src/addons/json/json.c b/src/addons/json/json.c new file mode 100644 index 0000000000..eb66e61469 --- /dev/null +++ b/src/addons/json/json.c @@ -0,0 +1,105 @@ +#include "json.h" + +#ifdef FLECS_JSON + +void json_next( + ecs_strbuf_t *buf) +{ + ecs_strbuf_list_next(buf); +} + +void json_literal( + ecs_strbuf_t *buf, + const char *value) +{ + ecs_strbuf_appendstr(buf, value); +} + +void json_number( + ecs_strbuf_t *buf, + double value) +{ + ecs_strbuf_appendflt(buf, value); +} + +void json_true( + ecs_strbuf_t *buf) +{ + json_literal(buf, "true"); +} + +void json_false( + ecs_strbuf_t *buf) +{ + json_literal(buf, "false"); +} + +void json_array_push( + ecs_strbuf_t *buf) +{ + ecs_strbuf_list_push(buf, "[", ", "); +} + +void json_array_pop( + ecs_strbuf_t *buf) +{ + ecs_strbuf_list_pop(buf, "]"); +} + +void json_object_push( + ecs_strbuf_t *buf) +{ + ecs_strbuf_list_push(buf, "{", ", "); +} + +void json_object_pop( + ecs_strbuf_t *buf) +{ + ecs_strbuf_list_pop(buf, "}"); +} + +void json_string( + ecs_strbuf_t *buf, + const char *value) +{ + ecs_strbuf_appendstr(buf, "\""); + ecs_strbuf_appendstr(buf, value); + ecs_strbuf_appendstr(buf, "\""); +} + +void json_member( + ecs_strbuf_t *buf, + const char *name) +{ + ecs_strbuf_list_appendstr(buf, "\""); + ecs_strbuf_appendstr(buf, name); + ecs_strbuf_appendstr(buf, "\":"); +} + +void json_path( + ecs_strbuf_t *buf, + const ecs_world_t *world, + ecs_entity_t e) +{ + ecs_strbuf_appendch(buf, '"'); + ecs_get_fullpath_buf(world, e, buf); + ecs_strbuf_appendch(buf, '"'); +} + +void json_id( + ecs_strbuf_t *buf, + const ecs_world_t *world, + ecs_id_t id) +{ + ecs_strbuf_appendch(buf, '"'); + ecs_id_str_buf(world, id, buf); + ecs_strbuf_appendch(buf, '"'); +} + +ecs_primitive_kind_t json_op_to_primitive_kind( + ecs_meta_type_op_kind_t kind) +{ + return kind - EcsOpPrimitive; +} + +#endif diff --git a/src/addons/json/json.h b/src/addons/json/json.h new file mode 100644 index 0000000000..bf62a519ce --- /dev/null +++ b/src/addons/json/json.h @@ -0,0 +1,55 @@ +#include "../../private_api.h" + +#ifdef FLECS_JSON + +void json_next( + ecs_strbuf_t *buf); + +void json_literal( + ecs_strbuf_t *buf, + const char *value); + +void json_number( + ecs_strbuf_t *buf, + double value); + +void json_true( + ecs_strbuf_t *buf); + +void json_false( + ecs_strbuf_t *buf); + +void json_array_push( + ecs_strbuf_t *buf); + +void json_array_pop( + ecs_strbuf_t *buf); + +void json_object_push( + ecs_strbuf_t *buf); + +void json_object_pop( + ecs_strbuf_t *buf); + +void json_string( + ecs_strbuf_t *buf, + const char *value); + +void json_member( + ecs_strbuf_t *buf, + const char *name); + +void json_path( + ecs_strbuf_t *buf, + const ecs_world_t *world, + ecs_entity_t e); + +void json_id( + ecs_strbuf_t *buf, + const ecs_world_t *world, + ecs_id_t id); + +ecs_primitive_kind_t json_op_to_primitive_kind( + ecs_meta_type_op_kind_t kind); + +#endif diff --git a/src/addons/json/serialize.c b/src/addons/json/serialize.c index e8f940e374..b85556badc 100644 --- a/src/addons/json/serialize.c +++ b/src/addons/json/serialize.c @@ -1,9 +1,8 @@ -#include "../../private_api.h" +#include "json.h" #ifdef FLECS_JSON - static int json_ser_type( const ecs_world_t *world, @@ -26,118 +25,6 @@ int json_ser_type_op( const void *base, ecs_strbuf_t *str); -static -void json_next( - ecs_strbuf_t *buf) -{ - ecs_strbuf_list_next(buf); -} - -static -void json_literal( - ecs_strbuf_t *buf, - const char *value) -{ - ecs_strbuf_appendstr(buf, value); -} - -static -void json_number( - ecs_strbuf_t *buf, - double value) -{ - ecs_strbuf_appendflt(buf, value); -} - -static -void json_true( - ecs_strbuf_t *buf) -{ - json_literal(buf, "true"); -} - -static -void json_false( - ecs_strbuf_t *buf) -{ - json_literal(buf, "false"); -} - -static -void json_array_push( - ecs_strbuf_t *buf) -{ - ecs_strbuf_list_push(buf, "[", ", "); -} - -static -void json_array_pop( - ecs_strbuf_t *buf) -{ - ecs_strbuf_list_pop(buf, "]"); -} - -static -void json_object_push( - ecs_strbuf_t *buf) -{ - ecs_strbuf_list_push(buf, "{", ", "); -} - -static -void json_object_pop( - ecs_strbuf_t *buf) -{ - ecs_strbuf_list_pop(buf, "}"); -} - -static -void json_string( - ecs_strbuf_t *buf, - const char *value) -{ - ecs_strbuf_appendstr(buf, "\""); - ecs_strbuf_appendstr(buf, value); - ecs_strbuf_appendstr(buf, "\""); -} - -static -void json_member( - ecs_strbuf_t *buf, - const char *name) -{ - ecs_strbuf_list_appendstr(buf, "\""); - ecs_strbuf_appendstr(buf, name); - ecs_strbuf_appendstr(buf, "\":"); -} - -static -void json_path( - ecs_strbuf_t *buf, - const ecs_world_t *world, - ecs_entity_t e) -{ - ecs_strbuf_appendch(buf, '"'); - ecs_get_fullpath_buf(world, e, buf); - ecs_strbuf_appendch(buf, '"'); -} - -static -void json_id( - ecs_strbuf_t *buf, - const ecs_world_t *world, - ecs_id_t id) -{ - ecs_strbuf_appendch(buf, '"'); - ecs_id_str_buf(world, id, buf); - ecs_strbuf_appendch(buf, '"'); -} - -static -ecs_primitive_kind_t json_op_to_primitive_kind(ecs_meta_type_op_kind_t kind) { - return kind - EcsOpPrimitive; -} - /* Serialize enumeration */ static int json_ser_enum( @@ -531,7 +418,8 @@ int append_type( const ecs_world_t *world, ecs_strbuf_t *buf, ecs_entity_t ent, - ecs_entity_t inst) + ecs_entity_t inst, + const ecs_entity_to_json_desc_t *desc) { ecs_type_t type = ecs_get_type(world, ent); ecs_id_t *ids = ecs_vector_first(type, ecs_id_t); @@ -601,15 +489,26 @@ int append_type( if (!hidden) { ecs_entity_t typeid = ecs_get_typeid(world, id); if (typeid) { - const EcsMetaTypeSerialized *ser = ecs_get( - world, typeid, EcsMetaTypeSerialized); - if (ser) { - const void *ptr = ecs_get_id(world, ent, id); - ecs_assert(ptr != NULL, ECS_INTERNAL_ERROR, NULL); - json_member(buf, "value"); - if (json_ser_type(world, ser->ops, ptr, buf) != 0) { - /* Entity contains invalid value */ - return -1; + if (!desc || desc->serialize_values) { + const EcsMetaTypeSerialized *ser = ecs_get( + world, typeid, EcsMetaTypeSerialized); + if (ser) { + const void *ptr = ecs_get_id(world, ent, id); + ecs_assert(ptr != NULL, ECS_INTERNAL_ERROR, NULL); + json_member(buf, "value"); + if (json_ser_type(world, ser->ops, ptr, buf) != 0) { + /* Entity contains invalid value */ + return -1; + } + + if (desc && desc->serialize_type_info) { + json_member(buf, "type_info"); + if (ecs_type_info_to_json_buf( + world, typeid, buf) != 0) + { + return -1; + } + } } } } @@ -628,7 +527,8 @@ int append_base( const ecs_world_t *world, ecs_strbuf_t *buf, ecs_entity_t ent, - ecs_entity_t inst) + ecs_entity_t inst, + const ecs_entity_to_json_desc_t *desc) { ecs_type_t type = ecs_get_type(world, ent); ecs_id_t *ids = ecs_vector_first(type, ecs_id_t); @@ -637,7 +537,8 @@ int append_base( for (i = 0; i < count; i ++) { ecs_id_t id = ids[i]; if (ECS_HAS_RELATION(id, EcsIsA)) { - if (append_base(world, buf, ecs_pair_object(world, id), inst)) { + if (append_base(world, buf, ecs_pair_object(world, id), inst, desc)) + { return -1; } } @@ -649,7 +550,7 @@ int append_base( json_object_push(buf); - if (append_type(world, buf, ent, inst)) { + if (append_type(world, buf, ent, inst, desc)) { return -1; } @@ -661,7 +562,8 @@ int append_base( int ecs_entity_to_json_buf( const ecs_world_t *world, ecs_entity_t entity, - ecs_strbuf_t *buf) + ecs_strbuf_t *buf, + const ecs_entity_to_json_desc_t *desc) { if (!entity || !ecs_is_valid(world, entity)) { return -1; @@ -669,34 +571,38 @@ int ecs_entity_to_json_buf( json_object_push(buf); - char *path = ecs_get_fullpath(world, entity); - json_member(buf, "path"); - json_string(buf, path); - ecs_os_free(path); + if (!desc || desc->serialize_path) { + char *path = ecs_get_fullpath(world, entity); + json_member(buf, "path"); + json_string(buf, path); + ecs_os_free(path); + } ecs_type_t type = ecs_get_type(world, entity); ecs_id_t *ids = ecs_vector_first(type, ecs_id_t); int32_t i, count = ecs_vector_count(type); - if (ecs_has_pair(world, entity, EcsIsA, EcsWildcard)) { - json_member(buf, "is_a"); - json_object_push(buf); - - for (i = 0; i < count; i ++) { - ecs_id_t id = ids[i]; - if (ECS_HAS_RELATION(id, EcsIsA)) { - if (append_base( - world, buf, ecs_pair_object(world, id), entity)) - { - return -1; + if (!desc || desc->serialize_base) { + if (ecs_has_pair(world, entity, EcsIsA, EcsWildcard)) { + json_member(buf, "is_a"); + json_object_push(buf); + + for (i = 0; i < count; i ++) { + ecs_id_t id = ids[i]; + if (ECS_HAS_RELATION(id, EcsIsA)) { + if (append_base( + world, buf, ecs_pair_object(world, id), entity, desc)) + { + return -1; + } } } - } - json_object_pop(buf); + json_object_pop(buf); + } } - if (append_type(world, buf, entity, entity)) { + if (append_type(world, buf, entity, entity, desc)) { goto error; } @@ -709,11 +615,12 @@ int ecs_entity_to_json_buf( char* ecs_entity_to_json( const ecs_world_t *world, - ecs_entity_t entity) + ecs_entity_t entity, + const ecs_entity_to_json_desc_t *desc) { ecs_strbuf_t buf = ECS_STRBUF_INIT; - if (ecs_entity_to_json_buf(world, entity, &buf) != 0) { + if (ecs_entity_to_json_buf(world, entity, &buf, desc) != 0) { ecs_strbuf_reset(&buf); return NULL; } @@ -763,6 +670,37 @@ void serialize_iter_ids( json_array_pop(buf); } +static +void serialize_type_info( + const ecs_world_t *world, + const ecs_iter_t *it, + ecs_strbuf_t *buf) +{ + int32_t term_count = it->term_count; + if (!term_count) { + return; + } + + json_member(buf, "type_info"); + json_object_push(buf); + + for (int i = 0; i < term_count; i ++) { + json_next(buf); + ecs_entity_t typeid = ecs_get_typeid(world, it->terms[i].id); + if (typeid) { + serialize_id(world, typeid, buf); + ecs_strbuf_appendstr(buf, ":"); + ecs_type_info_to_json_buf(world, typeid, buf); + } else { + serialize_id(world, it->terms[i].id, buf); + ecs_strbuf_appendstr(buf, ":"); + ecs_strbuf_appendstr(buf, "0"); + } + } + + json_object_pop(buf); +} + static void serialize_iter_variables(ecs_iter_t *it, ecs_strbuf_t *buf) { char **variable_names = it->variable_names; @@ -972,32 +910,32 @@ void serialize_iter_result( /* Each result can be matched with different component ids. Add them to * the result so clients know with which component an entity was matched */ - if (!desc || !desc->dont_serialize_ids) { + if (!desc || desc->serialize_ids) { serialize_iter_result_ids(world, it, buf); } /* Include information on which entity the term is matched with */ - if (!desc || !desc->dont_serialize_ids) { + if (!desc || desc->serialize_ids) { serialize_iter_result_subjects(world, it, buf); } /* Write variable values for current result */ - if (!desc || !desc->dont_serialize_variables) { + if (!desc || desc->serialize_variables) { serialize_iter_result_variables(world, it, buf); } /* Include information on which terms are set, to support optional terms */ - if (!desc || !desc->dont_serialize_is_set) { + if (!desc || desc->serialize_is_set) { serialize_iter_result_is_set(it, buf); } /* Write entity ids for current result (for queries with This terms) */ - if (!desc || !desc->dont_serialize_entities) { + if (!desc || desc->serialize_entities) { serialize_iter_result_entities(world, it, buf); } /* Serialize component values */ - if (!desc || !desc->dont_serialize_values) { + if (!desc || desc->serialize_values) { serialize_iter_result_values(world, it, buf); } @@ -1018,10 +956,15 @@ int ecs_iter_to_json_buf( json_object_push(buf); /* Serialize component ids of the terms (usually provided by query) */ - if (!desc || !desc->dont_serialize_term_ids) { + if (!desc || desc->serialize_term_ids) { serialize_iter_ids(world, it, buf); } + /* Serialize type info if enabled */ + if (desc && desc->serialize_type_info) { + serialize_type_info(world, it, buf); + } + /* Serialize variable names, if iterator has any */ serialize_iter_variables(it, buf); diff --git a/src/addons/json/serialize_type_info.c b/src/addons/json/serialize_type_info.c new file mode 100644 index 0000000000..f351e9b4d8 --- /dev/null +++ b/src/addons/json/serialize_type_info.c @@ -0,0 +1,289 @@ +#include "json.h" + +#ifdef FLECS_JSON + +static +int json_typeinfo_ser_type( + const ecs_world_t *world, + ecs_entity_t type, + ecs_strbuf_t *buf); + +static +int json_typeinfo_ser_primitive( + ecs_primitive_kind_t kind, + ecs_strbuf_t *str) +{ + json_array_push(str); + + switch(kind) { + case EcsBool: + json_string(str, "bool"); + break; + case EcsChar: + case EcsString: + json_string(str, "text"); + break; + case EcsByte: + json_string(str, "byte"); + break; + case EcsU8: + case EcsU16: + case EcsU32: + case EcsU64: + case EcsI8: + case EcsI16: + case EcsI32: + case EcsI64: + case EcsIPtr: + case EcsUPtr: + json_string(str, "int"); + break; + case EcsF32: + case EcsF64: + json_string(str, "float"); + break; + case EcsEntity: + json_string(str, "entity"); + break; + default: + return -1; + } + json_array_pop(str); + + return 0; +} + +static +void json_typeinfo_ser_constants( + const ecs_world_t *world, + ecs_entity_t type, + ecs_strbuf_t *str) +{ + ecs_iter_t it = ecs_term_iter(world, &(ecs_term_t) { + .id = ecs_pair(EcsChildOf, type) + }); + + while (ecs_term_next(&it)) { + int32_t i, count = it.count; + for (i = 0; i < count; i ++) { + json_next(str); + json_string(str, ecs_get_name(world, it.entities[i])); + } + } +} + +static +void json_typeinfo_ser_enum( + const ecs_world_t *world, + ecs_entity_t type, + ecs_strbuf_t *str) +{ + json_array_push(str); + ecs_strbuf_list_appendstr(str, "\"enum\""); + json_typeinfo_ser_constants(world, type, str); + json_array_pop(str); +} + +static +void json_typeinfo_ser_bitmask( + const ecs_world_t *world, + ecs_entity_t type, + ecs_strbuf_t *str) +{ + json_array_push(str); + ecs_strbuf_list_appendstr(str, "\"bitmask\""); + json_typeinfo_ser_constants(world, type, str); + json_array_pop(str); +} + +static +int json_typeinfo_ser_array( + const ecs_world_t *world, + ecs_entity_t elem_type, + int32_t count, + ecs_strbuf_t *str) +{ + json_array_push(str); + ecs_strbuf_list_appendstr(str, "\"array\""); + if (json_typeinfo_ser_type(world, elem_type, str)) { + goto error; + } + + ecs_strbuf_list_append(str, "%u", count); + + json_array_pop(str); + return 0; +error: + return -1; +} + +static +int json_typeinfo_ser_array_type( + const ecs_world_t *world, + ecs_entity_t type, + ecs_strbuf_t *str) +{ + const EcsArray *arr = ecs_get(world, type, EcsArray); + ecs_assert(arr != NULL, ECS_INTERNAL_ERROR, NULL); + if (json_typeinfo_ser_array(world, arr->type, arr->count, str)) { + goto error; + } + return 0; +error: + return -1; +} + +static +int json_typeinfo_ser_vector( + const ecs_world_t *world, + ecs_entity_t type, + ecs_strbuf_t *str) +{ + const EcsVector *arr = ecs_get(world, type, EcsVector); + ecs_assert(arr != NULL, ECS_INTERNAL_ERROR, NULL); + + json_array_push(str); + ecs_strbuf_list_appendstr(str, "\"vector\""); + if (json_typeinfo_ser_type(world, arr->type, str)) { + goto error; + } + + json_array_pop(str); + return 0; +error: + return -1; +} + +/* Forward serialization to the different type kinds */ +static +int json_typeinfo_ser_type_op( + const ecs_world_t *world, + ecs_meta_type_op_t *op, + ecs_strbuf_t *str) +{ + switch(op->kind) { + case EcsOpPush: + case EcsOpPop: + /* Should not be parsed as single op */ + ecs_throw(ECS_INVALID_PARAMETER, NULL); + break; + case EcsOpEnum: + json_typeinfo_ser_enum(world, op->type, str); + break; + case EcsOpBitmask: + json_typeinfo_ser_bitmask(world, op->type, str); + break; + case EcsOpArray: + json_typeinfo_ser_array_type(world, op->type, str); + break; + case EcsOpVector: + json_typeinfo_ser_vector(world, op->type, str); + break; + default: + if (json_typeinfo_ser_primitive( + json_op_to_primitive_kind(op->kind), str)) + { + /* Unknown operation */ + ecs_throw(ECS_INTERNAL_ERROR, NULL); + return -1; + } + break; + } + + return 0; +error: + return -1; +} + +/* Iterate over a slice of the type ops array */ +static +int json_typeinfo_ser_type_ops( + const ecs_world_t *world, + ecs_meta_type_op_t *ops, + int32_t op_count, + ecs_strbuf_t *str) +{ + for (int i = 0; i < op_count; i ++) { + ecs_meta_type_op_t *op = &ops[i]; + + if (op != ops) { + if (op->name) { + json_member(str, op->name); + } + + int32_t elem_count = op->count; + if (elem_count > 1 && op != ops) { + json_typeinfo_ser_array(world, op->type, op->count, str); + i += op->op_count - 1; + continue; + } + } + + switch(op->kind) { + case EcsOpPush: + json_object_push(str); + break; + case EcsOpPop: + json_object_pop(str); + break; + default: + if (json_typeinfo_ser_type_op(world, op, str)) { + goto error; + } + break; + } + } + + return 0; +error: + return -1; +} + +static +int json_typeinfo_ser_type( + const ecs_world_t *world, + ecs_entity_t type, + ecs_strbuf_t *buf) +{ + const EcsComponent *comp = ecs_get(world, type, EcsComponent); + if (!comp) { + ecs_strbuf_appendstr(buf, "0"); + return 0; + } + + const EcsMetaTypeSerialized *ser = ecs_get( + world, type, EcsMetaTypeSerialized); + if (!ser) { + ecs_strbuf_appendstr(buf, "0"); + return 0; + } + + ecs_meta_type_op_t *ops = ecs_vector_first(ser->ops, ecs_meta_type_op_t); + int32_t count = ecs_vector_count(ser->ops); + + return json_typeinfo_ser_type_ops(world, ops, count, buf); +} + +int ecs_type_info_to_json_buf( + const ecs_world_t *world, + ecs_entity_t type, + ecs_strbuf_t *buf) +{ + return json_typeinfo_ser_type(world, type, buf); +} + +char* ecs_type_info_to_json( + const ecs_world_t *world, + ecs_entity_t type) +{ + ecs_strbuf_t str = ECS_STRBUF_INIT; + + if (ecs_type_info_to_json_buf(world, type, &str) != 0) { + ecs_strbuf_reset(&str); + return NULL; + } + + return ecs_strbuf_get(&str); +} + +#endif diff --git a/src/addons/rest.c b/src/addons/rest.c index e81cc18c78..57bbef2f5a 100644 --- a/src/addons/rest.c +++ b/src/addons/rest.c @@ -69,6 +69,49 @@ void reply_error( ecs_strbuf_append(&reply->body, "{\"error\":\"%s\"}", msg); } +static +void rest_bool_param( + const ecs_http_request_t *req, + const char *name, + bool *value_out) +{ + const char *value = ecs_http_get_param(req, name); + if (value) { + if (!ecs_os_strcmp(value, "true")) { + value_out[0] = true; + } else { + value_out[0] = false; + } + } +} + +static +void rest_parse_json_ser_entity_params( + ecs_entity_to_json_desc_t *desc, + const ecs_http_request_t *req) +{ + rest_bool_param(req, "path", &desc->serialize_path); + rest_bool_param(req, "base", &desc->serialize_base); + rest_bool_param(req, "values", &desc->serialize_values); + rest_bool_param(req, "type_info", &desc->serialize_type_info); +} + +static +void rest_parse_json_ser_iter_params( + ecs_iter_to_json_desc_t *desc, + const ecs_http_request_t *req) +{ + rest_bool_param(req, "term_ids", &desc->serialize_term_ids); + rest_bool_param(req, "ids", &desc->serialize_ids); + rest_bool_param(req, "subjects", &desc->serialize_subjects); + rest_bool_param(req, "variables", &desc->serialize_variables); + rest_bool_param(req, "is_set", &desc->serialize_is_set); + rest_bool_param(req, "values", &desc->serialize_values); + rest_bool_param(req, "entities", &desc->serialize_entities); + rest_bool_param(req, "duration", &desc->measure_eval_duration); + rest_bool_param(req, "type_info", &desc->serialize_type_info); +} + static bool rest_reply( const ecs_http_request_t* req, @@ -98,7 +141,10 @@ bool rest_reply( } } - ecs_entity_to_json_buf(world, e, &reply->body); + ecs_entity_to_json_desc_t desc = ECS_ENTITY_TO_JSON_INIT; + rest_parse_json_ser_entity_params(&desc, req); + + ecs_entity_to_json_buf(world, e, &reply->body, &desc); return true; /* Query endpoint */ @@ -125,8 +171,11 @@ bool rest_reply( ecs_os_free(escaped_err); ecs_os_free(err); } else { + ecs_iter_to_json_desc_t desc = ECS_ITER_TO_JSON_INIT; + rest_parse_json_ser_iter_params(&desc, req); + ecs_iter_t it = ecs_rule_iter(world, r); - ecs_iter_to_json_buf(world, &it, &reply->body, NULL); + ecs_iter_to_json_buf(world, &it, &reply->body, &desc); ecs_rule_fini(r); } diff --git a/src/private_types.h b/src/private_types.h index b133d509dd..39109724e9 100644 --- a/src/private_types.h +++ b/src/private_types.h @@ -395,6 +395,9 @@ typedef struct ecs_event_id_record_t { /* Triggers for SuperSet, SubSet */ ecs_map_t *set_triggers; /* map */ + /* Triggers for Self with non-This subject */ + ecs_map_t *entity_triggers; /* map */ + /* Number of active triggers for (component) id */ int32_t trigger_count; } ecs_event_id_record_t; diff --git a/src/trigger.c b/src/trigger.c index f6a31cf46a..2afac50278 100644 --- a/src/trigger.c +++ b/src/trigger.c @@ -83,7 +83,7 @@ void register_trigger_for_id( ecs_observable_t *observable, ecs_trigger_t *trigger, ecs_id_t id, - bool register_for_set) + size_t triggers_offset) { ecs_sparse_t *events = observable->events; ecs_assert(events != NULL, ECS_INTERNAL_ERROR, NULL); @@ -107,13 +107,7 @@ void register_trigger_for_id( evt->event_ids, ecs_event_id_record_t, id); ecs_assert(idt != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_map_t **triggers; - if (!register_for_set) { - triggers = &idt->triggers; - } else { - triggers = &idt->set_triggers; - } - + ecs_map_t **triggers = ECS_OFFSET(idt, triggers_offset); if (!triggers[0]) { triggers[0] = ecs_map_new(ecs_trigger_t*, 1); } @@ -132,11 +126,18 @@ void register_trigger( { ecs_term_t *term = &trigger->term; if (term->subj.set.mask & EcsSelf) { - register_trigger_for_id(world, observable, trigger, term->id, false); + if (term->subj.entity == EcsThis) { + register_trigger_for_id(world, observable, trigger, term->id, + offsetof(ecs_event_id_record_t, triggers)); + } else { + register_trigger_for_id(world, observable, trigger, term->id, + offsetof(ecs_event_id_record_t, entity_triggers)); + } } if (trigger->term.subj.set.mask & EcsSuperSet) { ecs_id_t pair = ecs_pair(term->subj.set.relation, EcsWildcard); - register_trigger_for_id(world, observable, trigger, pair, true); + register_trigger_for_id(world, observable, trigger, pair, + offsetof(ecs_event_id_record_t, set_triggers)); } } @@ -146,7 +147,7 @@ void unregister_trigger_for_id( ecs_observable_t *observable, ecs_trigger_t *trigger, ecs_id_t id, - bool unregister_for_set) + size_t triggers_offset) { ecs_sparse_t *events = observable->events; ecs_assert(events != NULL, ECS_INTERNAL_ERROR, NULL); @@ -166,12 +167,7 @@ void unregister_trigger_for_id( evt->event_ids, ecs_event_id_record_t, id); ecs_assert(idt != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_map_t **id_triggers; - if (unregister_for_set) { - id_triggers = &idt->set_triggers; - } else { - id_triggers = &idt->triggers; - } + ecs_map_t **id_triggers = ECS_OFFSET(idt, triggers_offset); if (ecs_map_remove(id_triggers[0], trigger->id) == 0) { ecs_map_free(id_triggers[0]); @@ -198,10 +194,17 @@ void unregister_trigger( { ecs_term_t *term = &trigger->term; if (term->subj.set.mask & EcsSelf) { - unregister_trigger_for_id(world, observable, trigger, term->id, false); + if (term->subj.entity == EcsThis) { + unregister_trigger_for_id(world, observable, trigger, term->id, + offsetof(ecs_event_id_record_t, triggers)); + } else { + unregister_trigger_for_id(world, observable, trigger, term->id, + offsetof(ecs_event_id_record_t, entity_triggers)); + } } else { ecs_id_t pair = ecs_pair(term->subj.set.relation, EcsWildcard); - unregister_trigger_for_id(world, observable, trigger, pair, true); + unregister_trigger_for_id(world, observable, trigger, pair, + offsetof(ecs_event_id_record_t, set_triggers)); } } @@ -353,6 +356,103 @@ void notify_self_triggers( } } +static +void notify_entity_triggers( + ecs_iter_t *it, + const ecs_map_t *triggers) +{ + ecs_assert(triggers != NULL, ECS_INTERNAL_ERROR, NULL); + + void **ptrs = it->ptrs; + ecs_size_t *sizes = it->sizes; + + ecs_map_iter_t mit = ecs_map_iter(triggers); + ecs_trigger_t *t; + int32_t offset = it->offset, count = it->count; + ecs_entity_t *entities = it->entities; + + ecs_entity_t dummy = 0; + it->entities = &dummy; + + while ((t = ecs_map_next_ptr(&mit, ecs_trigger_t*, NULL))) { + if (ignore_table(t, it->table)) { + continue; + } + + int32_t i, entity_count = it->count; + for (i = 0; i < entity_count; i ++) { + if (entities[i] != t->term.subj.entity) { + continue; + } + + bool is_filter = t->term.inout == EcsInOutFilter; + + it->system = t->entity; + it->self = t->self; + it->ctx = t->ctx; + it->binding_ctx = t->binding_ctx; + it->term_index = t->term.index; + it->terms = &t->term; + it->is_filter = is_filter; + + if (is_filter) { + it->ptrs = NULL; + it->sizes = NULL; + } + + it->offset = i; + it->count = 1; + it->subjects[0] = entities[i]; + t->action(it); + + it->ptrs = ptrs; + it->sizes = sizes; + } + } + + it->offset = offset; + it->count = count; + it->entities = entities; + it->subjects[0] = 0; +} + +static +void notify_set_base_triggers( + ecs_iter_t *it, + const ecs_map_t *triggers) +{ + ecs_assert(triggers != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(it->count != 0, ECS_INTERNAL_ERROR, NULL); + + ecs_map_iter_t mit = ecs_map_iter(triggers); + ecs_trigger_t *t; + while ((t = ecs_map_next_ptr(&mit, ecs_trigger_t*, NULL))) { + if (ignore_table(t, it->table)) { + continue; + } + + if (flecs_term_match_table(it->world, &t->term, it->table, it->type, + it->ids, it->columns, it->subjects, NULL, true)) + { + if (!it->subjects[0]) { + /* Do not match owned components */ + continue; + } + + it->event_id = t->term.id; + it->ids[0] = t->term.id; + it->system = t->entity; + it->self = t->self; + it->ctx = t->ctx; + it->binding_ctx = t->binding_ctx; + it->term_index = t->term.index; + it->terms = &t->term; + + t->action(it); + } + } +} + static void notify_set_triggers( ecs_iter_t *it, @@ -368,6 +468,33 @@ void notify_set_triggers( continue; } + /* Make sure trigger id matches event id */ + if (!ecs_id_match(it->event_id, t->term.id)) { + continue; + } + + ecs_entity_t subj = it->entities[0]; + int32_t i, count = it->count; + ecs_entity_t term_subj = t->term.subj.entity; + + /* If trigger is for a specific entity, make sure it is in the table + * being triggered for */ + if (term_subj != EcsThis) { + for (i = 0; i < count; i ++) { + if (it->entities[i] == term_subj) { + break; + } + } + + if (i == count) { + continue; + } + + /* If the entity matches, trigger for no other entities */ + it->entities[0] = 0; + it->count = 1; + } + if (flecs_term_match_table(it->world, &t->term, it->table, it->type, it->ids, it->columns, it->subjects, NULL, true)) { @@ -387,24 +514,26 @@ void notify_set_triggers( it->term_index = t->term.index; it->terms = &t->term; + /* Triggers for supersets can be instanced */ if (it->count == 1 || t->instanced || !it->sizes[0]) { it->is_instanced = t->instanced; t->action(it); it->is_instanced = false; } else { - int32_t i, count = it->count; ecs_entity_t *entities = it->entities; it->count = 1; for (i = 0; i < count; i ++) { it->entities = &entities[i]; t->action(it); } - it->count = count; it->entities = entities; } it->event_id = event_id; - } + } + + it->entities[0] = subj; + it->count = count; } } @@ -421,9 +550,13 @@ void notify_triggers_for_id( init_iter(it, iter_set); notify_self_triggers(it, idt->triggers); } + if (idt->entity_triggers) { + init_iter(it, iter_set); + notify_entity_triggers(it, idt->entity_triggers); + } if (idt->set_triggers) { init_iter(it, iter_set); - notify_set_triggers(it, idt->set_triggers); + notify_set_base_triggers(it, idt->set_triggers); } } } @@ -615,9 +748,6 @@ ecs_entity_t ecs_trigger_init( goto error; } - /* Currently triggers are not supported for specific entities */ - ecs_check(term.subj.entity == EcsThis, ECS_UNSUPPORTED, NULL); - ecs_trigger_t *trigger = flecs_sparse_add(world->triggers, ecs_trigger_t); trigger->id = flecs_sparse_last_id(world->triggers); trigger->term = ecs_term_move(&term); diff --git a/test/api/project.json b/test/api/project.json index b44d280155..acdd54b29c 100644 --- a/test/api/project.json +++ b/test/api/project.json @@ -1553,7 +1553,12 @@ "on_add_base_superset_trigger_2_lvls", "on_add_base_2_entities", "on_set_base_w_value_2_entities", - "on_set_base_w_value_2_entities_instanced" + "on_set_base_w_value_2_entities_instanced", + "on_add_base_w_override", + "on_set_base_w_override", + "entity_source_1_trigger", + "entity_source_2_triggers", + "entity_source_base_set" ] }, { "id": "Observer", diff --git a/test/api/src/Trigger.c b/test/api/src/Trigger.c index 17ad94fb73..6cb580db48 100644 --- a/test/api/src/Trigger.c +++ b/test/api/src/Trigger.c @@ -19,6 +19,20 @@ void Trigger_w_value(ecs_iter_t *it) { test_int(p->y, 20); } +static +void Trigger_w_value_from_entity(ecs_iter_t *it) { + probe_system_w_ctx(it, it->ctx); + + test_int(it->count, 1); + test_assert(it->entities != NULL); + test_assert(it->entities[0] == 0); + test_assert(it->subjects[0] != 0); + + Position *p = ecs_term(it, Position, 1); + test_int(p->x, 10); + test_int(p->y, 20); +} + static void Trigger_w_value_instanced(ecs_iter_t *it) { probe_system_w_ctx(it, it->ctx); @@ -2898,6 +2912,7 @@ void Trigger_on_add_base() { ecs_world_t *world = ecs_init(); ECS_TAG(world, TagA); + ECS_TAG(world, TagB); /* Create trigger before table */ Probe ctx = {0}; @@ -2925,15 +2940,19 @@ void Trigger_on_add_base() { ctx = (Probe){0}; ecs_add(world, base, TagA); + ecs_add(world, base, TagB); test_int(ctx.invoked, 0); ecs_fini(world); + + test_int(ctx.invoked, 0); } void Trigger_on_remove_base() { ecs_world_t *world = ecs_init(); ECS_TAG(world, TagA); + ECS_TAG(world, TagB); /* Create trigger before table */ Probe ctx = {0}; @@ -2949,6 +2968,7 @@ void Trigger_on_remove_base() { test_int(ctx.invoked, 0); ecs_add(world, base, TagA); + ecs_add(world, base, TagB); test_int(ctx.invoked, 0); ecs_remove(world, base, TagA); @@ -2964,15 +2984,19 @@ void Trigger_on_remove_base() { ctx = (Probe){0}; ecs_remove(world, base, TagA); + ecs_remove(world, base, TagB); test_int(ctx.invoked, 0); ecs_fini(world); + + test_int(ctx.invoked, 0); } void Trigger_on_set_base() { ecs_world_t *world = ecs_init(); ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); /* Create trigger before table */ Probe ctx = {0}; @@ -3009,13 +3033,21 @@ void Trigger_on_set_base() { test_null(ctx.param); test_int(ctx.e[0], e); + ctx = (Probe){0}; + + ecs_set(world, base, Velocity, {1, 2}); + test_int(ctx.invoked, 0); + ecs_fini(world); + + test_int(ctx.invoked, 0); } void Trigger_on_unset_base() { ecs_world_t *world = ecs_init(); ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); /* Create trigger before table */ Probe ctx = {0}; @@ -3031,6 +3063,7 @@ void Trigger_on_unset_base() { test_int(ctx.invoked, 0); ecs_set(world, base, Position, {10, 20}); + ecs_set(world, base, Velocity, {1, 2}); test_int(ctx.invoked, 0); ecs_remove(world, base, Position); @@ -3046,15 +3079,19 @@ void Trigger_on_unset_base() { ctx = (Probe){0}; ecs_remove(world, base, Position); + ecs_remove(world, base, Velocity); test_int(ctx.invoked, 0); ecs_fini(world); + + test_int(ctx.invoked, 0); } void Trigger_on_add_base_superset_trigger() { ecs_world_t *world = ecs_init(); ECS_TAG(world, TagA); + ECS_TAG(world, TagB); Probe ctx = {0}; ecs_entity_t t = ecs_trigger_init(world, &(ecs_trigger_desc_t){ @@ -3082,6 +3119,7 @@ void Trigger_on_add_base_superset_trigger() { ctx = (Probe){0}; ecs_add(world, base, TagA); + ecs_add(world, base, TagB); test_int(ctx.invoked, 0); ecs_fini(world); @@ -3091,6 +3129,7 @@ void Trigger_on_add_base_superset_trigger_2_lvls() { ecs_world_t *world = ecs_init(); ECS_TAG(world, TagA); + ECS_TAG(world, TagB); Probe ctx = {0}; ecs_entity_t t = ecs_trigger_init(world, &(ecs_trigger_desc_t){ @@ -3121,15 +3160,19 @@ void Trigger_on_add_base_superset_trigger_2_lvls() { ctx = (Probe){0}; ecs_add(world, base_of_base, TagA); + ecs_add(world, base_of_base, TagB); test_int(ctx.invoked, 0); ecs_fini(world); + + test_int(ctx.invoked, 0); } void Trigger_on_add_base_2_entities() { ecs_world_t *world = ecs_init(); ECS_TAG(world, TagA); + ECS_TAG(world, TagB); /* Create trigger before table */ Probe ctx = {0}; @@ -3159,15 +3202,19 @@ void Trigger_on_add_base_2_entities() { ctx = (Probe){0}; ecs_add(world, base, TagA); + ecs_add(world, base, TagB); test_int(ctx.invoked, 0); ecs_fini(world); + + test_int(ctx.invoked, 0); } void Trigger_on_set_base_w_value_2_entities() { ecs_world_t *world = ecs_init(); ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); /* Create trigger before table */ Probe ctx = {0}; @@ -3207,6 +3254,11 @@ void Trigger_on_set_base_w_value_2_entities() { test_int(ctx.e[0], e1); test_int(ctx.e[1], e2); + ctx = (Probe){0}; + + ecs_set(world, base, Velocity, {1, 2}); + test_int(ctx.invoked, 0); + ecs_fini(world); } @@ -3214,6 +3266,7 @@ void Trigger_on_set_base_w_value_2_entities_instanced() { ecs_world_t *world = ecs_init(); ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); /* Create trigger before table */ Probe ctx = {0}; @@ -3254,5 +3307,253 @@ void Trigger_on_set_base_w_value_2_entities_instanced() { test_int(ctx.e[0], e1); test_int(ctx.e[1], e2); + ctx = (Probe){0}; + + ecs_set(world, base, Velocity, {1, 2}); + test_int(ctx.invoked, 0); + + ecs_fini(world); +} + +void Trigger_on_add_base_w_override() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, TagA); + ECS_TAG(world, TagB); + + /* Create trigger before table */ + Probe ctx = {0}; + ecs_entity_t t = ecs_trigger_init(world, &(ecs_trigger_desc_t){ + .term.id = TagA, /* Implicitly also listens to IsA */ + .events = {EcsOnAdd}, + .callback = Trigger, + .ctx = &ctx + }); + + ecs_entity_t base = ecs_new_w_id(world, EcsPrefab); + ecs_entity_t e = ecs_new_w_pair(world, EcsIsA, base); + test_int(ctx.invoked, 0); + + ecs_add(world, base, TagA); + test_int(ctx.invoked, 1); + test_int(ctx.count, 1); + test_int(ctx.system, t); + test_int(ctx.event, EcsOnAdd); + test_int(ctx.event_id, TagA); + test_int(ctx.term_count, 1); + test_null(ctx.param); + test_int(ctx.e[0], e); + test_int(ctx.s[0][0], base); + + ctx = (Probe){0}; + + ecs_add(world, e, TagA); + test_int(ctx.invoked, 1); + test_int(ctx.count, 1); + test_int(ctx.system, t); + test_int(ctx.event, EcsOnAdd); + test_int(ctx.event_id, TagA); + test_int(ctx.term_count, 1); + test_null(ctx.param); + test_int(ctx.e[0], e); + test_int(ctx.s[0][0], 0); + + ctx = (Probe){0}; + + ecs_remove(world, base, TagA); + ecs_add(world, base, TagA); + ecs_add(world, base, TagB); + test_int(ctx.invoked, 0); + + ecs_fini(world); + + test_int(ctx.invoked, 0); +} + +void Trigger_on_set_base_w_override() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + /* Create trigger before table */ + Probe ctx = {0}; + ecs_entity_t t = ecs_trigger_init(world, &(ecs_trigger_desc_t){ + .term.id = ecs_id(Position), /* Implicitly also listens to IsA */ + .events = {EcsOnSet}, + .callback = Trigger_w_value, + .ctx = &ctx + }); + + ecs_entity_t base = ecs_new_w_id(world, EcsPrefab); + ecs_entity_t e = ecs_new_w_pair(world, EcsIsA, base); + test_int(ctx.invoked, 0); + + ecs_set(world, base, Position, {10, 20}); + test_int(ctx.invoked, 1); + test_int(ctx.count, 1); + test_int(ctx.system, t); + test_int(ctx.event, EcsOnSet); + test_int(ctx.event_id, ecs_id(Position)); + test_int(ctx.term_count, 1); + test_null(ctx.param); + test_int(ctx.e[0], e); + test_int(ctx.s[0][0], base); + + ctx = (Probe){0}; + + ecs_set(world, e, Position, {10, 20}); + test_int(ctx.invoked, 1); + test_int(ctx.count, 1); + test_int(ctx.system, t); + test_int(ctx.event, EcsOnSet); + test_int(ctx.event_id, ecs_id(Position)); + test_int(ctx.term_count, 1); + test_null(ctx.param); + test_int(ctx.e[0], e); + test_int(ctx.s[0][0], 0); + + ctx = (Probe){0}; + + ecs_set(world, base, Position, {10, 20}); + test_int(ctx.invoked, 0); // no trigger, overridden + + ecs_set(world, base, Velocity, {1, 2}); + test_int(ctx.invoked, 0); + + ecs_fini(world); + + test_int(ctx.invoked, 0); +} + +void Trigger_entity_source_1_trigger() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_entity_t subj = ecs_new_id(world); + ecs_entity_t dummy = ecs_new_id(world); + + /* Create trigger before table */ + Probe ctx = {0}; + ecs_entity_t t = ecs_trigger_init(world, &(ecs_trigger_desc_t){ + .term.id = ecs_id(Position), + .term.subj.entity = subj, + .events = {EcsOnSet}, + .callback = Trigger_w_value_from_entity, + .ctx = &ctx + }); + + ecs_set(world, dummy, Position, {10, 20}); + test_int(ctx.invoked, 0); + + ecs_set(world, subj, Position, {10, 20}); + test_int(ctx.invoked, 1); + test_int(ctx.count, 1); + test_int(ctx.system, t); + test_int(ctx.event, EcsOnSet); + test_int(ctx.event_id, ecs_id(Position)); + test_int(ctx.term_count, 1); + test_null(ctx.param); + test_int(ctx.e[0], 0); + test_int(ctx.s[0][0], subj); + + ecs_fini(world); +} + +void Trigger_entity_source_2_triggers() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_entity_t subj_a = ecs_new_id(world); + ecs_entity_t subj_b = ecs_new_id(world); + ecs_entity_t dummy = ecs_new_id(world); + + /* Create trigger before table */ + Probe ctx = {0}; + ecs_entity_t t1 = ecs_trigger_init(world, &(ecs_trigger_desc_t){ + .term.id = ecs_id(Position), + .term.subj.entity = subj_a, + .events = {EcsOnSet}, + .callback = Trigger_w_value_from_entity, + .ctx = &ctx + }); + + ecs_entity_t t2 = ecs_trigger_init(world, &(ecs_trigger_desc_t){ + .term.id = ecs_id(Position), + .term.subj.entity = subj_b, + .events = {EcsOnSet}, + .callback = Trigger_w_value_from_entity, + .ctx = &ctx + }); + + ecs_set(world, dummy, Position, {10, 20}); + test_int(ctx.invoked, 0); + + ecs_set(world, subj_a, Position, {10, 20}); + test_int(ctx.invoked, 1); + test_int(ctx.count, 1); + test_int(ctx.system, t1); + test_int(ctx.event, EcsOnSet); + test_int(ctx.event_id, ecs_id(Position)); + test_int(ctx.term_count, 1); + test_null(ctx.param); + test_int(ctx.e[0], 0); + test_int(ctx.s[0][0], subj_a); + + ctx = (Probe){0}; + + ecs_set(world, subj_b, Position, {10, 20}); + test_int(ctx.invoked, 1); + test_int(ctx.count, 1); + test_int(ctx.system, t2); + test_int(ctx.event, EcsOnSet); + test_int(ctx.event_id, ecs_id(Position)); + test_int(ctx.term_count, 1); + test_null(ctx.param); + test_int(ctx.e[0], 0); + test_int(ctx.s[0][0], subj_b); + + ecs_fini(world); +} + +void Trigger_entity_source_base_set() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_entity_t base = ecs_new_w_id(world, EcsPrefab); + ecs_entity_t subj = ecs_new_w_pair(world, EcsIsA, base); + ecs_entity_t dummy = ecs_new_w_pair(world, EcsIsA, base); + ecs_entity_t dummy_no_base = ecs_new_w_pair(world, EcsIsA, base); + + /* Create trigger before table */ + Probe ctx = {0}; + ecs_entity_t t = ecs_trigger_init(world, &(ecs_trigger_desc_t){ + .term.id = ecs_id(Position), + .term.subj.entity = subj, + .events = {EcsOnSet}, + .callback = Trigger_w_value_from_entity, + .ctx = &ctx + }); + + ecs_set(world, dummy, Position, {10, 20}); + test_int(ctx.invoked, 0); + + ecs_set(world, dummy_no_base, Position, {10, 20}); + test_int(ctx.invoked, 0); + + ecs_set(world, base, Position, {10, 20}); + test_int(ctx.invoked, 1); + test_int(ctx.count, 1); + test_int(ctx.system, t); + test_int(ctx.event, EcsOnSet); + test_int(ctx.event_id, ecs_id(Position)); + test_int(ctx.term_count, 1); + test_null(ctx.param); + test_int(ctx.e[0], 0); + test_int(ctx.s[0][0], base); + ecs_fini(world); } diff --git a/test/api/src/main.c b/test/api/src/main.c index eb5e54418a..88d2f06b09 100644 --- a/test/api/src/main.c +++ b/test/api/src/main.c @@ -1484,6 +1484,11 @@ void Trigger_on_add_base_superset_trigger_2_lvls(void); void Trigger_on_add_base_2_entities(void); void Trigger_on_set_base_w_value_2_entities(void); void Trigger_on_set_base_w_value_2_entities_instanced(void); +void Trigger_on_add_base_w_override(void); +void Trigger_on_set_base_w_override(void); +void Trigger_entity_source_1_trigger(void); +void Trigger_entity_source_2_triggers(void); +void Trigger_entity_source_base_set(void); // Testsuite 'Observer' void Observer_2_terms_w_on_add(void); @@ -7982,6 +7987,26 @@ bake_test_case Trigger_testcases[] = { { "on_set_base_w_value_2_entities_instanced", Trigger_on_set_base_w_value_2_entities_instanced + }, + { + "on_add_base_w_override", + Trigger_on_add_base_w_override + }, + { + "on_set_base_w_override", + Trigger_on_set_base_w_override + }, + { + "entity_source_1_trigger", + Trigger_entity_source_1_trigger + }, + { + "entity_source_2_triggers", + Trigger_entity_source_2_triggers + }, + { + "entity_source_base_set", + Trigger_entity_source_base_set } }; @@ -11175,7 +11200,7 @@ static bake_test_suite suites[] = { "Trigger", NULL, NULL, - 81, + 86, Trigger_testcases }, { diff --git a/test/meta/include/meta.h b/test/meta/include/meta.h index f13eb73e7a..47cf4fd2ec 100644 --- a/test/meta/include/meta.h +++ b/test/meta/include/meta.h @@ -40,7 +40,7 @@ void _meta_test_member( typedef struct { int32_t x, y; -} Position; +} Position, Velocity; typedef struct { int32_t value; diff --git a/test/meta/project.json b/test/meta/project.json index 3378fb5980..8a646deccb 100644 --- a/test/meta/project.json +++ b/test/meta/project.json @@ -458,6 +458,7 @@ "serialize_entity_w_enum_component", "serialize_entity_w_struct_and_enum_component", "serialize_entity_w_invalid_enum_component", + "serialize_entity_w_type_info", "serialize_iterator_1_comps_empty", "serialize_iterator_1_comps_2_ents_same_table", "serialize_iterator_1_tag_2_ents_same_table", @@ -467,7 +468,39 @@ "serialize_iterator_2_comps_1_owned_2_ents", "serialize_iterator_w_pair_wildcard", "serialize_iterator_w_var", - "serialize_iterator_w_2_vars" + "serialize_iterator_w_2_vars", + "serialize_iterator_type_info_1_tags", + "serialize_iterator_type_info_2_tags", + "serialize_iterator_type_info_1_component", + "serialize_iterator_type_info_2_components", + "serialize_iterator_type_info_1_struct", + "serialize_iterator_type_info_1_component_1_struct", + "serialize_iterator_type_info_2_structs" + ] + }, { + "id": "SerializeTypeInfoToJson", + "testcases": [ + "bool", + "byte", + "char", + "i8", + "i16", + "i32", + "i64", + "iptr", + "u8", + "u16", + "u32", + "u64", + "uptr", + "float", + "double", + "string", + "entity", + "enum", + "bitmask", + "struct", + "nested_struct" ] }, { "id": "MetaUtils", diff --git a/test/meta/src/SerializeToJson.c b/test/meta/src/SerializeToJson.c index a7e70651e4..19f8cfbf7d 100644 --- a/test/meta/src/SerializeToJson.c +++ b/test/meta/src/SerializeToJson.c @@ -1,6 +1,5 @@ #include - void SerializeToJson_struct_bool() { typedef struct { ecs_bool_t x; @@ -840,7 +839,7 @@ void SerializeToJson_serialize_entity_empty() { ecs_os_sprintf( str, "{\"path\":\"%u\", \"type\":[]}", (uint32_t)e); - char *json = ecs_entity_to_json(world, e); + char *json = ecs_entity_to_json(world, e, NULL); test_assert(json != NULL); test_str(json, str); ecs_os_free(json); @@ -854,7 +853,7 @@ void SerializeToJson_serialize_entity_w_name() { ecs_entity_t e = ecs_set_name(world, 0, "Foo"); - char *json = ecs_entity_to_json(world, e); + char *json = ecs_entity_to_json(world, e, NULL); test_assert(json != NULL); test_str(json, "{\"path\":\"Foo\", \"type\":[{\"pred\":\"Identifier\", \"obj\":\"Name\"}]}"); ecs_os_free(json); @@ -870,7 +869,7 @@ void SerializeToJson_serialize_entity_w_name_1_tag() { ecs_entity_t e = ecs_set_name(world, 0, "Foo"); ecs_add(world, e, Tag); - char *json = ecs_entity_to_json(world, e); + char *json = ecs_entity_to_json(world, e, NULL); test_assert(json != NULL); test_str(json, "{\"path\":\"Foo\", \"type\":[{\"pred\":\"Tag\"}, {\"pred\":\"Identifier\", \"obj\":\"Name\"}]}"); ecs_os_free(json); @@ -888,7 +887,7 @@ void SerializeToJson_serialize_entity_w_name_2_tags() { ecs_add(world, e, TagA); ecs_add(world, e, TagB); - char *json = ecs_entity_to_json(world, e); + char *json = ecs_entity_to_json(world, e, NULL); test_assert(json != NULL); test_str(json, "{\"path\":\"Foo\", \"type\":[{\"pred\":\"TagA\"}, {\"pred\":\"TagB\"}, {\"pred\":\"Identifier\", \"obj\":\"Name\"}]}"); @@ -906,7 +905,7 @@ void SerializeToJson_serialize_entity_w_name_1_pair() { ecs_entity_t e = ecs_set_name(world, 0, "Foo"); ecs_add_pair(world, e, Rel, Obj); - char *json = ecs_entity_to_json(world, e); + char *json = ecs_entity_to_json(world, e, NULL); test_assert(json != NULL); test_str(json, "{\"path\":\"Foo\", \"type\":[{\"pred\":\"Identifier\", \"obj\":\"Name\"}, {\"pred\":\"Rel\", \"obj\":\"Obj\"}]}"); @@ -928,7 +927,7 @@ void SerializeToJson_serialize_entity_w_base() { ecs_add_pair(world, e, EcsIsA, base); ecs_add(world, e, TagB); - char *json = ecs_entity_to_json(world, e); + char *json = ecs_entity_to_json(world, e, NULL); test_assert(json != NULL); test_str(json, "{" "\"path\":\"Foo\", " @@ -958,7 +957,7 @@ void SerializeToJson_serialize_entity_w_base_override() { ecs_add(world, e, TagA); ecs_add(world, e, TagB); - char *json = ecs_entity_to_json(world, e); + char *json = ecs_entity_to_json(world, e, NULL); test_assert(json != NULL); test_str(json, "{" "\"path\":\"Foo\", " @@ -988,7 +987,7 @@ void SerializeToJson_serialize_entity_w_1_component() { ecs_entity_t e = ecs_set_name(world, 0, "Foo"); ecs_set(world, e, Position, {10, 20}); - char *json = ecs_entity_to_json(world, e); + char *json = ecs_entity_to_json(world, e, NULL); test_assert(json != NULL); test_str(json, "{" "\"path\":\"Foo\", " @@ -1023,7 +1022,7 @@ void SerializeToJson_serialize_entity_w_2_components() { ecs_set(world, e, Position, {10, 20}); ecs_set(world, e, Mass, {1234}); - char *json = ecs_entity_to_json(world, e); + char *json = ecs_entity_to_json(world, e, NULL); test_assert(json != NULL); test_str(json, "{" "\"path\":\"Foo\", " @@ -1044,7 +1043,7 @@ void SerializeToJson_serialize_entity_w_primitive_component() { ecs_entity_t e = ecs_set_name(world, 0, "Foo"); ecs_set(world, e, ecs_i32_t, {10}); - char *json = ecs_entity_to_json(world, e); + char *json = ecs_entity_to_json(world, e, NULL); test_assert(json != NULL); test_str(json, "{" "\"path\":\"Foo\", " @@ -1076,7 +1075,7 @@ void SerializeToJson_serialize_entity_w_enum_component() { ecs_entity_t e = ecs_set_name(world, 0, "Foo"); ecs_set(world, e, Color, {1}); - char *json = ecs_entity_to_json(world, e); + char *json = ecs_entity_to_json(world, e, NULL); test_assert(json != NULL); test_str(json, "{" "\"path\":\"Foo\", " @@ -1119,7 +1118,7 @@ void SerializeToJson_serialize_entity_w_struct_and_enum_component() { ecs_set(world, e, Color, {1}); ecs_set(world, e, Position, {10, 20}); - char *json = ecs_entity_to_json(world, e); + char *json = ecs_entity_to_json(world, e, NULL); test_assert(json != NULL); test_str(json, "{" "\"path\":\"Foo\", " @@ -1153,12 +1152,46 @@ void SerializeToJson_serialize_entity_w_invalid_enum_component() { ecs_entity_t e = ecs_set_name(world, 0, "Foo"); ecs_set(world, e, Color, {100}); - char *json = ecs_entity_to_json(world, e); + char *json = ecs_entity_to_json(world, e, NULL); test_assert(json == NULL); ecs_fini(world); } +void SerializeToJson_serialize_entity_w_type_info() { + ecs_world_t *world = ecs_init(); + + ecs_entity_t ecs_id(Position) = ecs_struct_init(world, &(ecs_struct_desc_t) { + .entity.name = "Position", + .members = { + {"x", ecs_id(ecs_i32_t)}, + {"y", ecs_id(ecs_i32_t)} + } + }); + + ecs_entity_t e = ecs_set_name(world, 0, "Foo"); + ecs_set(world, e, Position, {10, 20}); + + ecs_entity_to_json_desc_t desc = ECS_ENTITY_TO_JSON_INIT; + desc.serialize_type_info = true; + char *json = ecs_entity_to_json(world, e, &desc); + test_assert(json != NULL); + test_str(json, "{" + "\"path\":\"Foo\", " + "\"type\":[" + "{" + "\"pred\":\"Position\", " + "\"value\":{\"x\":10, \"y\":20}, " + "\"type_info\":{\"x\":[\"int\"], \"y\":[\"int\"]}" + "}, " + "{\"pred\":\"Identifier\", \"obj\":\"Name\"}" + "]}"); + + ecs_os_free(json); + + ecs_fini(world); +} + void SerializeToJson_serialize_iterator_1_comps_empty() { ecs_world_t *world = ecs_init(); @@ -1656,3 +1689,340 @@ void SerializeToJson_serialize_iterator_w_2_vars() { ecs_fini(world); } + +void SerializeToJson_serialize_iterator_type_info_1_tags() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, TagA); + + ecs_entity_t e1 = ecs_set_name(world, 0, "Foo"); + ecs_entity_t e2 = ecs_set_name(world, 0, "Bar"); + + ecs_add(world, e1, TagA); + ecs_add(world, e2, TagA); + + ecs_query_t *q = ecs_query_new(world, "TagA"); + ecs_iter_t it = ecs_query_iter(world, q); + + ecs_iter_to_json_desc_t desc = ECS_ITER_TO_JSON_INIT; + desc.serialize_type_info = true; + char *json = ecs_iter_to_json(world, &it, &desc); + + test_str(json, + "{" + "\"ids\":[\"TagA\"], " + "\"type_info\":{\"TagA\":0}, " + "\"results\":[{" + "\"ids\":[\"TagA\"], " + "\"subjects\":[0], " + "\"is_set\":[true], " + "\"entities\":[" + "\"Foo\", \"Bar\"" + "], " + "\"values\":[0]" + "}]" + "}"); + + ecs_os_free(json); + + ecs_fini(world); +} + +void SerializeToJson_serialize_iterator_type_info_2_tags() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, TagA); + ECS_TAG(world, TagB); + + ecs_entity_t e1 = ecs_set_name(world, 0, "Foo"); + ecs_entity_t e2 = ecs_set_name(world, 0, "Bar"); + + ecs_add(world, e1, TagA); + ecs_add(world, e1, TagB); + ecs_add(world, e2, TagA); + ecs_add(world, e2, TagB); + + ecs_query_t *q = ecs_query_new(world, "TagA, TagB"); + ecs_iter_t it = ecs_query_iter(world, q); + + ecs_iter_to_json_desc_t desc = ECS_ITER_TO_JSON_INIT; + desc.serialize_type_info = true; + char *json = ecs_iter_to_json(world, &it, &desc); + + test_str(json, + "{" + "\"ids\":[\"TagA\", \"TagB\"], " + "\"type_info\":{\"TagA\":0, \"TagB\":0}, " + "\"results\":[{" + "\"ids\":[\"TagA\", \"TagB\"], " + "\"subjects\":[0, 0], " + "\"is_set\":[true, true], " + "\"entities\":[" + "\"Foo\", \"Bar\"" + "], " + "\"values\":[0, 0]" + "}]" + "}"); + + ecs_os_free(json); + + ecs_fini(world); +} + +void SerializeToJson_serialize_iterator_type_info_1_component() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_entity_t e1 = ecs_set_name(world, 0, "Foo"); + ecs_entity_t e2 = ecs_set_name(world, 0, "Bar"); + + ecs_add(world, e1, Position); + ecs_add(world, e2, Position); + + ecs_query_t *q = ecs_query_new(world, "Position"); + ecs_iter_t it = ecs_query_iter(world, q); + + ecs_iter_to_json_desc_t desc = ECS_ITER_TO_JSON_INIT; + desc.serialize_type_info = true; + char *json = ecs_iter_to_json(world, &it, &desc); + + test_str(json, + "{" + "\"ids\":[\"Position\"], " + "\"type_info\":{\"Position\":0}, " + "\"results\":[{" + "\"ids\":[\"Position\"], " + "\"subjects\":[0], " + "\"is_set\":[true], " + "\"entities\":[" + "\"Foo\", \"Bar\"" + "], " + "\"values\":[0]" + "}]" + "}"); + + ecs_os_free(json); + + ecs_fini(world); +} + +void SerializeToJson_serialize_iterator_type_info_2_components() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ecs_entity_t e1 = ecs_set_name(world, 0, "Foo"); + ecs_entity_t e2 = ecs_set_name(world, 0, "Bar"); + + ecs_add(world, e1, Position); + ecs_add(world, e2, Position); + + ecs_add(world, e1, Velocity); + ecs_add(world, e2, Velocity); + + ecs_query_t *q = ecs_query_new(world, "Position, Velocity"); + ecs_iter_t it = ecs_query_iter(world, q); + + ecs_iter_to_json_desc_t desc = ECS_ITER_TO_JSON_INIT; + desc.serialize_type_info = true; + char *json = ecs_iter_to_json(world, &it, &desc); + + test_str(json, + "{" + "\"ids\":[\"Position\", \"Velocity\"], " + "\"type_info\":{\"Position\":0, \"Velocity\":0}, " + "\"results\":[{" + "\"ids\":[\"Position\", \"Velocity\"], " + "\"subjects\":[0, 0], " + "\"is_set\":[true, true], " + "\"entities\":[" + "\"Foo\", \"Bar\"" + "], " + "\"values\":[0, 0]" + "}]" + "}"); + + ecs_os_free(json); + + ecs_fini(world); +} + +void SerializeToJson_serialize_iterator_type_info_1_struct() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_entity_t t = ecs_struct_init(world, &(ecs_struct_desc_t) { + .entity.name = "Position", + .members = { + {"x", ecs_id(ecs_i32_t)}, + {"y", ecs_id(ecs_i32_t)} + } + }); + + test_assert(t == ecs_id(Position)); + + ecs_entity_t e1 = ecs_set_name(world, 0, "Foo"); + ecs_entity_t e2 = ecs_set_name(world, 0, "Bar"); + + ecs_add(world, e1, Position); + ecs_add(world, e2, Position); + + ecs_query_t *q = ecs_query_new(world, "Position"); + ecs_iter_t it = ecs_query_iter(world, q); + + ecs_iter_to_json_desc_t desc = ECS_ITER_TO_JSON_INIT; + desc.serialize_type_info = true; + char *json = ecs_iter_to_json(world, &it, &desc); + + test_str(json, + "{" + "\"ids\":[\"Position\"], " + "\"type_info\":{\"Position\":{\"x\":[\"int\"], \"y\":[\"int\"]}}, " + "\"results\":[{" + "\"ids\":[\"Position\"], " + "\"subjects\":[0], " + "\"is_set\":[true], " + "\"entities\":[" + "\"Foo\", \"Bar\"" + "], " + "\"values\":[[" + "{\"x\":0, \"y\":0}, " + "{\"x\":0, \"y\":0}" + "]]" + "}]" + "}"); + + ecs_os_free(json); + + ecs_fini(world); +} + +void SerializeToJson_serialize_iterator_type_info_1_component_1_struct() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ecs_entity_t t = ecs_struct_init(world, &(ecs_struct_desc_t) { + .entity.name = "Position", + .members = { + {"x", ecs_id(ecs_i32_t)}, + {"y", ecs_id(ecs_i32_t)} + } + }); + + test_assert(t == ecs_id(Position)); + + ecs_entity_t e1 = ecs_set_name(world, 0, "Foo"); + ecs_entity_t e2 = ecs_set_name(world, 0, "Bar"); + + ecs_add(world, e1, Position); + ecs_add(world, e2, Position); + + ecs_add(world, e1, Velocity); + ecs_add(world, e2, Velocity); + + ecs_query_t *q = ecs_query_new(world, "Position, Velocity"); + ecs_iter_t it = ecs_query_iter(world, q); + + ecs_iter_to_json_desc_t desc = ECS_ITER_TO_JSON_INIT; + desc.serialize_type_info = true; + char *json = ecs_iter_to_json(world, &it, &desc); + + test_str(json, + "{" + "\"ids\":[\"Position\", \"Velocity\"], " + "\"type_info\":{" + "\"Position\":{\"x\":[\"int\"], \"y\":[\"int\"]}, " + "\"Velocity\":0" + "}, " + "\"results\":[{" + "\"ids\":[\"Position\", \"Velocity\"], " + "\"subjects\":[0, 0], " + "\"is_set\":[true, true], " + "\"entities\":[" + "\"Foo\", \"Bar\"" + "], " + "\"values\":[[" + "{\"x\":0, \"y\":0}, " + "{\"x\":0, \"y\":0}" + "], 0]" + "}]" + "}"); + + ecs_os_free(json); + + ecs_fini(world); +} + +void SerializeToJson_serialize_iterator_type_info_2_structs() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ecs_entity_t t = ecs_struct_init(world, &(ecs_struct_desc_t) { + .entity.name = "Position", + .members = { + {"x", ecs_id(ecs_i32_t)}, + {"y", ecs_id(ecs_i32_t)} + } + }); + test_assert(t == ecs_id(Position)); + + t = ecs_struct_init(world, &(ecs_struct_desc_t) { + .entity.name = "Velocity", + .members = { + {"x", ecs_id(ecs_i32_t)}, + {"y", ecs_id(ecs_i32_t)} + } + }); + test_assert(t == ecs_id(Velocity)); + + ecs_entity_t e1 = ecs_set_name(world, 0, "Foo"); + ecs_entity_t e2 = ecs_set_name(world, 0, "Bar"); + + ecs_add(world, e1, Position); + ecs_add(world, e2, Position); + + ecs_add(world, e1, Velocity); + ecs_add(world, e2, Velocity); + + ecs_query_t *q = ecs_query_new(world, "Position, Velocity"); + ecs_iter_t it = ecs_query_iter(world, q); + + ecs_iter_to_json_desc_t desc = ECS_ITER_TO_JSON_INIT; + desc.serialize_type_info = true; + char *json = ecs_iter_to_json(world, &it, &desc); + + test_str(json, + "{" + "\"ids\":[\"Position\", \"Velocity\"], " + "\"type_info\":{" + "\"Position\":{\"x\":[\"int\"], \"y\":[\"int\"]}, " + "\"Velocity\":{\"x\":[\"int\"], \"y\":[\"int\"]}" + "}, " + "\"results\":[{" + "\"ids\":[\"Position\", \"Velocity\"], " + "\"subjects\":[0, 0], " + "\"is_set\":[true, true], " + "\"entities\":[" + "\"Foo\", \"Bar\"" + "], " + "\"values\":[[" + "{\"x\":0, \"y\":0}, " + "{\"x\":0, \"y\":0}" + "], [" + "{\"x\":0, \"y\":0}, " + "{\"x\":0, \"y\":0}" + "]]" + "}]" + "}"); + + ecs_os_free(json); + + ecs_fini(world); +} diff --git a/test/meta/src/SerializeTypeInfoToJson.c b/test/meta/src/SerializeTypeInfoToJson.c new file mode 100644 index 0000000000..31b81b8e90 --- /dev/null +++ b/test/meta/src/SerializeTypeInfoToJson.c @@ -0,0 +1,265 @@ +#include + +void SerializeTypeInfoToJson_bool() { + ecs_world_t *world = ecs_init(); + + char *str = ecs_type_info_to_json(world, ecs_id(ecs_bool_t)); + test_assert(str != NULL); + test_str(str, "[\"bool\"]"); + ecs_os_free(str); + + ecs_fini(world); +} + +void SerializeTypeInfoToJson_byte() { + ecs_world_t *world = ecs_init(); + + char *str = ecs_type_info_to_json(world, ecs_id(ecs_byte_t)); + test_assert(str != NULL); + test_str(str, "[\"byte\"]"); + ecs_os_free(str); + + ecs_fini(world); +} + +void SerializeTypeInfoToJson_char() { + ecs_world_t *world = ecs_init(); + + char *str = ecs_type_info_to_json(world, ecs_id(ecs_char_t)); + test_assert(str != NULL); + test_str(str, "[\"text\"]"); + ecs_os_free(str); + + ecs_fini(world); +} + +void SerializeTypeInfoToJson_i8() { + ecs_world_t *world = ecs_init(); + + char *str = ecs_type_info_to_json(world, ecs_id(ecs_i8_t)); + test_assert(str != NULL); + test_str(str, "[\"int\"]"); + ecs_os_free(str); + + ecs_fini(world); +} + +void SerializeTypeInfoToJson_i16() { + ecs_world_t *world = ecs_init(); + + char *str = ecs_type_info_to_json(world, ecs_id(ecs_i16_t)); + test_assert(str != NULL); + test_str(str, "[\"int\"]"); + ecs_os_free(str); + + ecs_fini(world); +} + +void SerializeTypeInfoToJson_i32() { + ecs_world_t *world = ecs_init(); + + char *str = ecs_type_info_to_json(world, ecs_id(ecs_i32_t)); + test_assert(str != NULL); + test_str(str, "[\"int\"]"); + ecs_os_free(str); + + ecs_fini(world); +} + +void SerializeTypeInfoToJson_i64() { + ecs_world_t *world = ecs_init(); + + char *str = ecs_type_info_to_json(world, ecs_id(ecs_i64_t)); + test_assert(str != NULL); + test_str(str, "[\"int\"]"); + ecs_os_free(str); + + ecs_fini(world); +} + +void SerializeTypeInfoToJson_iptr() { + ecs_world_t *world = ecs_init(); + + char *str = ecs_type_info_to_json(world, ecs_id(ecs_iptr_t)); + test_assert(str != NULL); + test_str(str, "[\"int\"]"); + ecs_os_free(str); + + ecs_fini(world); +} + +void SerializeTypeInfoToJson_u8() { + ecs_world_t *world = ecs_init(); + + char *str = ecs_type_info_to_json(world, ecs_id(ecs_u8_t)); + test_assert(str != NULL); + test_str(str, "[\"int\"]"); + ecs_os_free(str); + + ecs_fini(world); +} + +void SerializeTypeInfoToJson_u16() { + ecs_world_t *world = ecs_init(); + + char *str = ecs_type_info_to_json(world, ecs_id(ecs_u16_t)); + test_assert(str != NULL); + test_str(str, "[\"int\"]"); + ecs_os_free(str); + + ecs_fini(world); +} + +void SerializeTypeInfoToJson_u32() { + ecs_world_t *world = ecs_init(); + + char *str = ecs_type_info_to_json(world, ecs_id(ecs_u32_t)); + test_assert(str != NULL); + test_str(str, "[\"int\"]"); + ecs_os_free(str); + + ecs_fini(world); +} + +void SerializeTypeInfoToJson_u64() { + ecs_world_t *world = ecs_init(); + + char *str = ecs_type_info_to_json(world, ecs_id(ecs_u64_t)); + test_assert(str != NULL); + test_str(str, "[\"int\"]"); + ecs_os_free(str); + + ecs_fini(world); +} + +void SerializeTypeInfoToJson_uptr() { + ecs_world_t *world = ecs_init(); + + char *str = ecs_type_info_to_json(world, ecs_id(ecs_uptr_t)); + test_assert(str != NULL); + test_str(str, "[\"int\"]"); + ecs_os_free(str); + + ecs_fini(world); +} + +void SerializeTypeInfoToJson_float() { + ecs_world_t *world = ecs_init(); + + char *str = ecs_type_info_to_json(world, ecs_id(ecs_f32_t)); + test_assert(str != NULL); + test_str(str, "[\"float\"]"); + ecs_os_free(str); + + ecs_fini(world); +} + +void SerializeTypeInfoToJson_double() { + ecs_world_t *world = ecs_init(); + + char *str = ecs_type_info_to_json(world, ecs_id(ecs_f64_t)); + test_assert(str != NULL); + test_str(str, "[\"float\"]"); + ecs_os_free(str); + + ecs_fini(world); +} + +void SerializeTypeInfoToJson_string() { + ecs_world_t *world = ecs_init(); + + char *str = ecs_type_info_to_json(world, ecs_id(ecs_string_t)); + test_assert(str != NULL); + test_str(str, "[\"text\"]"); + ecs_os_free(str); + + ecs_fini(world); +} + +void SerializeTypeInfoToJson_entity() { + ecs_world_t *world = ecs_init(); + + char *str = ecs_type_info_to_json(world, ecs_id(ecs_entity_t)); + test_assert(str != NULL); + test_str(str, "[\"entity\"]"); + ecs_os_free(str); + + ecs_fini(world); +} + +void SerializeTypeInfoToJson_enum() { + ecs_world_t *world = ecs_init(); + + ecs_entity_t e = ecs_enum_init(world, &(ecs_enum_desc_t) { + .constants = { + {"Red"}, {"Blue"}, {"Green"} + } + }); + + char *str = ecs_type_info_to_json(world, e); + test_assert(str != NULL); + test_str(str, "[\"enum\", \"Red\", \"Blue\", \"Green\"]"); + ecs_os_free(str); + + ecs_fini(world); +} + +void SerializeTypeInfoToJson_bitmask() { + ecs_world_t *world = ecs_init(); + + ecs_entity_t b = ecs_bitmask_init(world, &(ecs_bitmask_desc_t) { + .constants = { + {"Red"}, {"Blue"}, {"Green"} + } + }); + + char *str = ecs_type_info_to_json(world, b); + test_assert(str != NULL); + test_str(str, "[\"bitmask\", \"Red\", \"Blue\", \"Green\"]"); + ecs_os_free(str); + + ecs_fini(world); +} + +void SerializeTypeInfoToJson_struct() { + ecs_world_t *world = ecs_init(); + + ecs_entity_t t = ecs_struct_init(world, &(ecs_struct_desc_t) { + .members = { + {"x", ecs_id(ecs_f32_t)}, + {"y", ecs_id(ecs_f32_t)} + } + }); + + char *str = ecs_type_info_to_json(world, t); + test_assert(str != NULL); + test_str(str, "{\"x\":[\"float\"], \"y\":[\"float\"]}"); + ecs_os_free(str); + + ecs_fini(world); +} + +void SerializeTypeInfoToJson_nested_struct() { + ecs_world_t *world = ecs_init(); + + ecs_entity_t t = ecs_struct_init(world, &(ecs_struct_desc_t) { + .members = { + {"x", ecs_id(ecs_f32_t)}, + {"y", ecs_id(ecs_f32_t)} + } + }); + + ecs_entity_t l = ecs_struct_init(world, &(ecs_struct_desc_t) { + .members = { + {"start", t}, + {"stop", t} + } + }); + + char *str = ecs_type_info_to_json(world, l); + test_assert(str != NULL); + test_str(str, "{\"start\":{\"x\":[\"float\"], \"y\":[\"float\"]}, \"stop\":{\"x\":[\"float\"], \"y\":[\"float\"]}}"); + ecs_os_free(str); + + ecs_fini(world); +} diff --git a/test/meta/src/main.c b/test/meta/src/main.c index a185d7a5f0..cd49aa56fb 100644 --- a/test/meta/src/main.c +++ b/test/meta/src/main.c @@ -429,6 +429,7 @@ void SerializeToJson_serialize_entity_w_primitive_component(void); void SerializeToJson_serialize_entity_w_enum_component(void); void SerializeToJson_serialize_entity_w_struct_and_enum_component(void); void SerializeToJson_serialize_entity_w_invalid_enum_component(void); +void SerializeToJson_serialize_entity_w_type_info(void); void SerializeToJson_serialize_iterator_1_comps_empty(void); void SerializeToJson_serialize_iterator_1_comps_2_ents_same_table(void); void SerializeToJson_serialize_iterator_1_tag_2_ents_same_table(void); @@ -439,6 +440,36 @@ void SerializeToJson_serialize_iterator_2_comps_1_owned_2_ents(void); void SerializeToJson_serialize_iterator_w_pair_wildcard(void); void SerializeToJson_serialize_iterator_w_var(void); void SerializeToJson_serialize_iterator_w_2_vars(void); +void SerializeToJson_serialize_iterator_type_info_1_tags(void); +void SerializeToJson_serialize_iterator_type_info_2_tags(void); +void SerializeToJson_serialize_iterator_type_info_1_component(void); +void SerializeToJson_serialize_iterator_type_info_2_components(void); +void SerializeToJson_serialize_iterator_type_info_1_struct(void); +void SerializeToJson_serialize_iterator_type_info_1_component_1_struct(void); +void SerializeToJson_serialize_iterator_type_info_2_structs(void); + +// Testsuite 'SerializeTypeInfoToJson' +void SerializeTypeInfoToJson_bool(void); +void SerializeTypeInfoToJson_byte(void); +void SerializeTypeInfoToJson_char(void); +void SerializeTypeInfoToJson_i8(void); +void SerializeTypeInfoToJson_i16(void); +void SerializeTypeInfoToJson_i32(void); +void SerializeTypeInfoToJson_i64(void); +void SerializeTypeInfoToJson_iptr(void); +void SerializeTypeInfoToJson_u8(void); +void SerializeTypeInfoToJson_u16(void); +void SerializeTypeInfoToJson_u32(void); +void SerializeTypeInfoToJson_u64(void); +void SerializeTypeInfoToJson_uptr(void); +void SerializeTypeInfoToJson_float(void); +void SerializeTypeInfoToJson_double(void); +void SerializeTypeInfoToJson_string(void); +void SerializeTypeInfoToJson_entity(void); +void SerializeTypeInfoToJson_enum(void); +void SerializeTypeInfoToJson_bitmask(void); +void SerializeTypeInfoToJson_struct(void); +void SerializeTypeInfoToJson_nested_struct(void); // Testsuite 'MetaUtils' void MetaUtils_struct_w_2_i32(void); @@ -2076,6 +2107,10 @@ bake_test_case SerializeToJson_testcases[] = { "serialize_entity_w_invalid_enum_component", SerializeToJson_serialize_entity_w_invalid_enum_component }, + { + "serialize_entity_w_type_info", + SerializeToJson_serialize_entity_w_type_info + }, { "serialize_iterator_1_comps_empty", SerializeToJson_serialize_iterator_1_comps_empty @@ -2115,6 +2150,121 @@ bake_test_case SerializeToJson_testcases[] = { { "serialize_iterator_w_2_vars", SerializeToJson_serialize_iterator_w_2_vars + }, + { + "serialize_iterator_type_info_1_tags", + SerializeToJson_serialize_iterator_type_info_1_tags + }, + { + "serialize_iterator_type_info_2_tags", + SerializeToJson_serialize_iterator_type_info_2_tags + }, + { + "serialize_iterator_type_info_1_component", + SerializeToJson_serialize_iterator_type_info_1_component + }, + { + "serialize_iterator_type_info_2_components", + SerializeToJson_serialize_iterator_type_info_2_components + }, + { + "serialize_iterator_type_info_1_struct", + SerializeToJson_serialize_iterator_type_info_1_struct + }, + { + "serialize_iterator_type_info_1_component_1_struct", + SerializeToJson_serialize_iterator_type_info_1_component_1_struct + }, + { + "serialize_iterator_type_info_2_structs", + SerializeToJson_serialize_iterator_type_info_2_structs + } +}; + +bake_test_case SerializeTypeInfoToJson_testcases[] = { + { + "bool", + SerializeTypeInfoToJson_bool + }, + { + "byte", + SerializeTypeInfoToJson_byte + }, + { + "char", + SerializeTypeInfoToJson_char + }, + { + "i8", + SerializeTypeInfoToJson_i8 + }, + { + "i16", + SerializeTypeInfoToJson_i16 + }, + { + "i32", + SerializeTypeInfoToJson_i32 + }, + { + "i64", + SerializeTypeInfoToJson_i64 + }, + { + "iptr", + SerializeTypeInfoToJson_iptr + }, + { + "u8", + SerializeTypeInfoToJson_u8 + }, + { + "u16", + SerializeTypeInfoToJson_u16 + }, + { + "u32", + SerializeTypeInfoToJson_u32 + }, + { + "u64", + SerializeTypeInfoToJson_u64 + }, + { + "uptr", + SerializeTypeInfoToJson_uptr + }, + { + "float", + SerializeTypeInfoToJson_float + }, + { + "double", + SerializeTypeInfoToJson_double + }, + { + "string", + SerializeTypeInfoToJson_string + }, + { + "entity", + SerializeTypeInfoToJson_entity + }, + { + "enum", + SerializeTypeInfoToJson_enum + }, + { + "bitmask", + SerializeTypeInfoToJson_bitmask + }, + { + "struct", + SerializeTypeInfoToJson_struct + }, + { + "nested_struct", + SerializeTypeInfoToJson_nested_struct } }; @@ -2262,9 +2412,16 @@ static bake_test_suite suites[] = { "SerializeToJson", NULL, NULL, - 51, + 59, SerializeToJson_testcases }, + { + "SerializeTypeInfoToJson", + NULL, + NULL, + 21, + SerializeTypeInfoToJson_testcases + }, { "MetaUtils", NULL, @@ -2276,5 +2433,5 @@ static bake_test_suite suites[] = { int main(int argc, char *argv[]) { ut_init(argv[0]); - return bake_test_run("meta", argc, argv, suites, 14); + return bake_test_run("meta", argc, argv, suites, 15); }