From d408c6ea1b2f7eba1e24b295942421773cf9a951 Mon Sep 17 00:00:00 2001 From: Sander Mertens Date: Mon, 29 Aug 2022 10:02:26 +0200 Subject: [PATCH] #795 fix issue with name lookups after setting name to NULL --- flecs.c | 74217 ++++++++++++++++++++-------------------- src/entity.c | 15 +- test/api/project.json | 1 + test/api/src/Lookup.c | 17 + test/api/src/main.c | 7 +- 5 files changed, 37147 insertions(+), 37110 deletions(-) diff --git a/flecs.c b/flecs.c index 62ec7b87bb..fee9c71fd6 100644 --- a/flecs.c +++ b/flecs.c @@ -2279,8655 +2279,8156 @@ void _assert_func( #endif -/* Marker object used to differentiate a component vs. a tag edge */ -static ecs_table_diff_t ecs_table_edge_is_component; - -/* Id sequence (type) utilities */ - +/* Table sanity check to detect storage issues. Only enabled in SANITIZE mode as + * this can severly slow down many ECS operations. */ +#ifdef FLECS_SANITIZE static -uint64_t flecs_type_hash(const void *ptr) { - const ecs_type_t *type = ptr; - ecs_id_t *ids = type->array; - int32_t count = type->count; - return flecs_hash(ids, count * ECS_SIZEOF(ecs_id_t)); -} +void check_table_sanity(ecs_table_t *table) { + int32_t size = ecs_storage_size(&table->data.entities); + int32_t count = ecs_storage_count(&table->data.entities); + + ecs_assert(size == ecs_storage_size(&table->data.records), + ECS_INTERNAL_ERROR, NULL); + ecs_assert(count == ecs_storage_count(&table->data.records), + ECS_INTERNAL_ERROR, NULL); -static -int flecs_type_compare(const void *ptr_1, const void *ptr_2) { - const ecs_type_t *type_1 = ptr_1; - const ecs_type_t *type_2 = ptr_2; + int32_t i; + int32_t sw_offset = table->sw_offset; + int32_t sw_count = table->sw_count; + int32_t bs_offset = table->bs_offset; + int32_t bs_count = table->bs_count; + int32_t type_count = table->type.count; + ecs_id_t *ids = table->type.array; - int32_t count_1 = type_1->count; - int32_t count_2 = type_2->count; + ecs_assert((sw_count + sw_offset) <= type_count, ECS_INTERNAL_ERROR, NULL); + ecs_assert((bs_count + bs_offset) <= type_count, ECS_INTERNAL_ERROR, NULL); - if (count_1 != count_2) { - return (count_1 > count_2) - (count_1 < count_2); - } + ecs_table_t *storage_table = table->storage_table; + if (storage_table) { + ecs_assert(table->storage_count == storage_table->type.count, + ECS_INTERNAL_ERROR, NULL); + ecs_assert(table->storage_ids == storage_table->type.array, + ECS_INTERNAL_ERROR, NULL); - const ecs_id_t *ids_1 = type_1->array; - const ecs_id_t *ids_2 = type_2->array; - int result = 0; - - int32_t i; - for (i = 0; !result && (i < count_1); i ++) { - ecs_id_t id_1 = ids_1[i]; - ecs_id_t id_2 = ids_2[i]; - result = (id_1 > id_2) - (id_1 < id_2); - } + int32_t storage_count = table->storage_count; + ecs_assert(type_count >= storage_count, ECS_INTERNAL_ERROR, NULL); - return result; -} + int32_t *storage_map = table->storage_map; + ecs_assert(storage_map != NULL, ECS_INTERNAL_ERROR, NULL); -void flecs_table_hashmap_init(ecs_hashmap_t *hm) { - flecs_hashmap_init(hm, ecs_type_t, ecs_table_t*, - flecs_type_hash, flecs_type_compare); -} + ecs_id_t *storage_ids = table->storage_ids; + for (i = 0; i < type_count; i ++) { + if (storage_map[i] != -1) { + ecs_assert(ids[i] == storage_ids[storage_map[i]], + ECS_INTERNAL_ERROR, NULL); + } + } -/* Find location where to insert id into type */ -static -int flecs_type_find_insert( - const ecs_type_t *type, - int32_t offset, - ecs_id_t to_add) -{ - ecs_id_t *array = type->array; - int32_t i, count = type->count; + ecs_assert(table->data.columns != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(table->type_info != NULL, ECS_INTERNAL_ERROR, NULL); - for (i = offset; i < count; i ++) { - ecs_id_t id = array[i]; - if (id == to_add) { - return -1; - } - if (id > to_add) { - return i; + for (i = 0; i < storage_count; i ++) { + ecs_column_t *column = &table->data.columns[i]; + ecs_assert(size == column->size, ECS_INTERNAL_ERROR, NULL); + ecs_assert(count == column->count, ECS_INTERNAL_ERROR, NULL); + int32_t storage_map_id = storage_map[i + type_count]; + ecs_assert(storage_map_id >= 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(ids[storage_map_id] == storage_ids[i], + ECS_INTERNAL_ERROR, NULL); } + } else { + ecs_assert(table->storage_count == 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(table->storage_ids == NULL, ECS_INTERNAL_ERROR, NULL); } - return i; -} - -/* Find location of id in type */ -static -int flecs_type_find( - const ecs_type_t *type, - ecs_id_t id) -{ - ecs_id_t *array = type->array; - int32_t i, count = type->count; - for (i = 0; i < count; i ++) { - ecs_id_t cur = array[i]; - if (ecs_id_match(cur, id)) { - return i; - } - if (cur > id) { - return -1; + if (sw_count) { + ecs_assert(table->data.sw_columns != NULL, + ECS_INTERNAL_ERROR, NULL); + for (i = 0; i < sw_count; i ++) { + ecs_switch_t *sw = &table->data.sw_columns[i]; + ecs_assert(ecs_vector_count(sw->values) == count, + ECS_INTERNAL_ERROR, NULL); + ecs_assert(ECS_PAIR_FIRST(ids[i + sw_offset]) == EcsUnion, + ECS_INTERNAL_ERROR, NULL); } } - return -1; -} - -/* Count number of matching ids */ -static -int flecs_type_count_matches( - const ecs_type_t *type, - ecs_id_t wildcard, - int32_t offset) -{ - ecs_id_t *array = type->array; - int32_t i = offset, count = type->count; - - for (; i < count; i ++) { - ecs_id_t cur = array[i]; - if (!ecs_id_match(cur, wildcard)) { - break; + if (bs_count) { + ecs_assert(table->data.bs_columns != NULL, + ECS_INTERNAL_ERROR, NULL); + for (i = 0; i < bs_count; i ++) { + ecs_bitset_t *bs = &table->data.bs_columns[i]; + ecs_assert(flecs_bitset_count(bs) == count, + ECS_INTERNAL_ERROR, NULL); + ecs_assert(ECS_HAS_ID_FLAG(ids[i + bs_offset], TOGGLE), + ECS_INTERNAL_ERROR, NULL); } } - - return i - offset; } +#else +#define check_table_sanity(table) +#endif -/* Create type from source type with id */ static -int flecs_type_new_with( - ecs_type_t *dst, - const ecs_type_t *src, - ecs_id_t with) +void flecs_table_init_storage_map( + ecs_table_t *table) { - ecs_id_t *src_array = src->array; - int32_t at = flecs_type_find_insert(src, 0, with); - if (at == -1) { - return -1; + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + if (!table->storage_table) { + return; } - int32_t dst_count = src->count + 1; - ecs_id_t *dst_array = ecs_os_malloc_n(ecs_id_t, dst_count); - dst->count = dst_count; - dst->array = dst_array; - - if (at) { - ecs_os_memcpy_n(dst_array, src_array, ecs_id_t, at); - } + ecs_type_t type = table->type; + ecs_id_t *ids = type.array; + int32_t t, ids_count = type.count; + ecs_id_t *storage_ids = table->storage_ids; + int32_t s, storage_ids_count = table->storage_count; - int32_t remain = src->count - at; - if (remain) { - ecs_os_memcpy_n(&dst_array[at + 1], &src_array[at], ecs_id_t, remain); + if (!ids_count) { + table->storage_map = NULL; + return; } - dst_array[at] = with; + table->storage_map = ecs_os_malloc_n( + int32_t, ids_count + storage_ids_count); - return 0; -} + int32_t *t2s = table->storage_map; + int32_t *s2t = &table->storage_map[ids_count]; -/* Create type from source type without ids matching wildcard */ -static -int flecs_type_new_filtered( - ecs_type_t *dst, - const ecs_type_t *src, - ecs_id_t wildcard, - int32_t at) -{ - *dst = flecs_type_copy(src); - ecs_id_t *dst_array = dst->array; - ecs_id_t *src_array = src->array; - if (at) { - ecs_os_memcpy_n(dst_array, src_array, ecs_id_t, at); - } + 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]; - int32_t i = at + 1, w = at, count = src->count; - for (; i < count; i ++) { - ecs_id_t id = src_array[i]; - if (!ecs_id_match(id, wildcard)) { - dst_array[w] = id; - w ++; + if (id == storage_id) { + t2s[t] = s; + s2t[s] = t; + } else { + t2s[t] = -1; } - } - dst->count = w; - if (w != count) { - dst->array = ecs_os_realloc_n(dst->array, ecs_id_t, w); + /* 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 0; + /* Storage ids is always a subset of ids, so all should be iterated */ + ecs_assert(s == storage_ids_count, ECS_INTERNAL_ERROR, NULL); + + /* Initialize remainder of type -> storage_type map */ + for (; (t < ids_count); t ++) { + t2s[t] = -1; + } } -/* Create type from source type without id */ static -int flecs_type_new_without( - ecs_type_t *dst, - const ecs_type_t *src, - ecs_id_t without) +ecs_flags32_t flecs_type_info_flags( + const ecs_type_info_t *ti) { - ecs_id_t *src_array = src->array; - int32_t count = 1, at = flecs_type_find(src, without); - if (at == -1) { - return -1; - } + ecs_flags32_t flags = 0; - int32_t src_count = src->count; - if (src_count == 1) { - dst->array = NULL; - dst->count = 0; - return 0; + if (ti->hooks.ctor) { + flags |= EcsTableHasCtors; } - - if (ecs_id_is_wildcard(without)) { - if (ECS_IS_PAIR(without)) { - ecs_entity_t r = ECS_PAIR_FIRST(without); - ecs_entity_t o = ECS_PAIR_SECOND(without); - if (r == EcsWildcard && o != EcsWildcard) { - return flecs_type_new_filtered(dst, src, without, at); - } - } - count += flecs_type_count_matches(src, without, at + 1); + if (ti->hooks.on_add) { + flags |= EcsTableHasCtors; } - - int32_t dst_count = src_count - count; - dst->count = dst_count; - if (!dst_count) { - dst->array = NULL; - return 0; + if (ti->hooks.dtor) { + flags |= EcsTableHasDtors; } - - ecs_id_t *dst_array = ecs_os_malloc_n(ecs_id_t, dst_count); - dst->array = dst_array; - - if (at) { - ecs_os_memcpy_n(dst_array, src_array, ecs_id_t, at); + if (ti->hooks.on_remove) { + flags |= EcsTableHasDtors; } - - int32_t remain = dst_count - at; - if (remain) { - ecs_os_memcpy_n( - &dst_array[at], &src_array[at + count], ecs_id_t, remain); + if (ti->hooks.copy) { + flags |= EcsTableHasCopy; } + if (ti->hooks.move) { + flags |= EcsTableHasMove; + } - return 0; -} - -/* Copy type */ -ecs_type_t flecs_type_copy( - const ecs_type_t *src) -{ - int32_t src_count = src->count; - return (ecs_type_t) { - .array = ecs_os_memdup_n(src->array, ecs_id_t, src_count), - .count = src_count - }; -} - -/* Free type */ -void flecs_type_free( - ecs_type_t *type) -{ - ecs_os_free(type->array); + return flags; } -/* Add to type */ static -void flecs_type_add( - ecs_type_t *type, - ecs_id_t add) +void flecs_table_init_type_info( + ecs_table_t *table) { - ecs_type_t new_type; - int res = flecs_type_new_with(&new_type, type, add); - if (res != -1) { - flecs_type_free(type); - type->array = new_type.array; - type->count = new_type.count; - } -} - -/* Graph edge utilities */ + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(table->storage_table == table, ECS_INTERNAL_ERROR, NULL); + ecs_assert(table->type_info == NULL, ECS_INTERNAL_ERROR, NULL); -static -void table_diff_free( - ecs_table_diff_t *diff) -{ - 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); - ecs_os_free(diff); -} + ecs_table_record_t *records = table->records; + int32_t i, count = table->type.count; + table->type_info = ecs_os_calloc_n(ecs_type_info_t*, count); -static -ecs_graph_edge_t* graph_edge_new( - ecs_world_t *world) -{ - ecs_graph_edge_t *result = (ecs_graph_edge_t*)world->store.first_free; - if (result) { - world->store.first_free = result->hdr.next; - ecs_os_zeromem(result); - } else { - result = ecs_os_calloc_t(ecs_graph_edge_t); + for (i = 0; i < count; i ++) { + ecs_table_record_t *tr = &records[i]; + ecs_id_record_t *idr = (ecs_id_record_t*)tr->hdr.cache; + + /* All ids in the storage table must be components with type info */ + const ecs_type_info_t *ti = idr->type_info; + ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); + table->flags |= flecs_type_info_flags(ti); + table->type_info[i] = (ecs_type_info_t*)ti; } - return result; } static -void graph_edge_free( +void flecs_table_init_storage_table( ecs_world_t *world, - ecs_graph_edge_t *edge) + ecs_table_t *table) { - if (world->flags & EcsWorldFini) { - ecs_os_free(edge); - } else { - edge->hdr.next = world->store.first_free; - world->store.first_free = &edge->hdr; + if (table->storage_table) { + return; } -} -static -ecs_graph_edge_t* ensure_hi_edge( - ecs_world_t *world, - ecs_graph_edges_t *edges, - ecs_id_t id) -{ - ecs_map_init_if(&edges->hi, ecs_graph_edge_t*, 1); + ecs_type_t type = table->type; + int32_t i, count = type.count; + ecs_id_t *ids = type.array; + ecs_table_record_t *records = table->records; - ecs_graph_edge_t **ep = ecs_map_ensure(&edges->hi, ecs_graph_edge_t*, id); - ecs_graph_edge_t *edge = ep[0]; - if (edge) { - return edge; + ecs_id_t array[ECS_ID_CACHE_SIZE]; + ecs_type_t storage_ids = { .array = array }; + if (count > ECS_ID_CACHE_SIZE) { + storage_ids.array = ecs_os_malloc_n(ecs_id_t, count); } - if (id < ECS_HI_COMPONENT_ID) { - edge = &edges->lo[id]; - } else { - edge = graph_edge_new(world); + for (i = 0; i < count; i ++) { + ecs_table_record_t *tr = &records[i]; + ecs_id_record_t *idr = (ecs_id_record_t*)tr->hdr.cache; + ecs_id_t id = ids[i]; + + if (idr->type_info != NULL) { + storage_ids.array[storage_ids.count ++] = id; + } } - ep[0] = edge; - return edge; -} + if (storage_ids.count && storage_ids.count != count) { + ecs_table_t *storage_table = flecs_table_find_or_create(world, + &storage_ids); + table->storage_table = storage_table; + table->storage_count = flecs_ito(uint16_t, storage_ids.count); + table->storage_ids = storage_table->type.array; + table->type_info = storage_table->type_info; + table->flags |= storage_table->flags; + storage_table->refcount ++; + } else if (storage_ids.count) { + table->storage_table = table; + table->storage_count = flecs_ito(uint16_t, count); + table->storage_ids = type.array; + flecs_table_init_type_info(table); + } -static -ecs_graph_edge_t* ensure_edge( - ecs_world_t *world, - ecs_graph_edges_t *edges, - ecs_id_t id) -{ - ecs_graph_edge_t *edge; - - if (id < ECS_HI_COMPONENT_ID) { - if (!edges->lo) { - edges->lo = ecs_os_calloc_n(ecs_graph_edge_t, ECS_HI_COMPONENT_ID); - } - edge = &edges->lo[id]; - } else { - ecs_map_init_if(&edges->hi, ecs_graph_edge_t*, 1); - edge = ensure_hi_edge(world, edges, id); + if (storage_ids.array != array) { + ecs_os_free(storage_ids.array); } - return edge; + if (!table->storage_map) { + flecs_table_init_storage_map(table); + } } -static -void disconnect_edge( - ecs_world_t *world, - ecs_id_t id, - ecs_graph_edge_t *edge) +void flecs_table_init_data( + ecs_table_t *table) { - ecs_assert(edge != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(edge->id == id, ECS_INTERNAL_ERROR, NULL); - (void)id; + int32_t sw_count = table->sw_count; + int32_t bs_count = table->bs_count; - /* Remove backref from destination table */ - ecs_graph_edge_hdr_t *next = edge->hdr.next; - ecs_graph_edge_hdr_t *prev = edge->hdr.prev; + ecs_data_t *storage = &table->data; + int32_t i, count = table->storage_count; - if (next) { - next->prev = prev; + /* Root tables don't have columns */ + if (!count && !sw_count && !bs_count) { + storage->columns = NULL; } - if (prev) { - prev->next = next; + + if (count) { + storage->columns = ecs_os_calloc_n(ecs_column_t, count); } - /* Remove data associated with edge */ - ecs_table_diff_t *diff = edge->diff; - if (diff && diff != &ecs_table_edge_is_component) { - table_diff_free(diff); + if (sw_count) { + storage->sw_columns = ecs_os_calloc_n(ecs_switch_t, sw_count); + for (i = 0; i < sw_count; i ++) { + flecs_switch_init(&storage->sw_columns[i], 0); + } } - /* If edge id is low, clear it from fast lookup array */ - if (id < ECS_HI_COMPONENT_ID) { - ecs_os_memset_t(edge, 0, ecs_graph_edge_t); - } else { - graph_edge_free(world, edge); + if (bs_count) { + storage->bs_columns = ecs_os_calloc_n(ecs_bitset_t, bs_count); + for (i = 0; i < bs_count; i ++) { + flecs_bitset_init(&storage->bs_columns[i]); + } } } static -void remove_edge( +void flecs_table_init_flags( ecs_world_t *world, - ecs_graph_edges_t *edges, - ecs_id_t id, - ecs_graph_edge_t *edge) + ecs_table_t *table) { - ecs_assert(edges != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(ecs_map_is_initialized(&edges->hi), ECS_INTERNAL_ERROR, NULL); - disconnect_edge(world, id, edge); - ecs_map_remove(&edges->hi, id); -} + ecs_id_t *ids = table->type.array; + int32_t count = table->type.count; -static -void init_edges( - ecs_graph_edges_t *edges) -{ - edges->lo = NULL; - ecs_os_zeromem(&edges->hi); -} + /* Iterate components to initialize table flags */ + int32_t i; + for (i = 0; i < count; i ++) { + ecs_id_t id = ids[i]; -static -void init_node( - ecs_graph_node_t *node) -{ - init_edges(&node->add); - init_edges(&node->remove); -} + /* 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. */ -bool flecs_table_records_update_empty( - ecs_table_t *table) -{ - bool result = false; - bool is_empty = ecs_table_count(table) == 0; + if (id <= EcsLastInternalComponentId) { + table->flags |= EcsTableHasBuiltins; + } - int32_t i, count = table->record_count; - for (i = 0; i < count; i ++) { - ecs_table_record_t *tr = &table->records[i]; - ecs_table_cache_t *cache = tr->hdr.cache; - result |= ecs_table_cache_set_empty(cache, table, is_empty); - } + if (id == EcsModule) { + table->flags |= EcsTableHasBuiltins; + table->flags |= EcsTableHasModule; + } else if (id == EcsPrefab) { + table->flags |= EcsTableIsPrefab; + } else if (id == EcsDisabled) { + table->flags |= EcsTableIsDisabled; + } else { + if (ECS_IS_PAIR(id)) { + ecs_entity_t r = ECS_PAIR_FIRST(id); - return result; + table->flags |= EcsTableHasPairs; + + if (r == EcsIsA) { + table->flags |= EcsTableHasIsA; + } else if (r == EcsChildOf) { + table->flags |= EcsTableHasChildOf; + ecs_entity_t obj = ecs_pair_second(world, id); + ecs_assert(obj != 0, ECS_INTERNAL_ERROR, NULL); + + if (obj == EcsFlecs || obj == EcsFlecsCore || + ecs_has_id(world, obj, EcsModule)) + { + /* If table contains entities that are inside one of the + * builtin modules, it contains builtin entities */ + table->flags |= EcsTableHasBuiltins; + table->flags |= EcsTableHasModule; + } + } else if (r == EcsUnion) { + table->flags |= EcsTableHasUnion; + + if (!table->sw_count) { + table->sw_offset = flecs_ito(int16_t, i); + } + table->sw_count ++; + } else if (r == ecs_id(EcsPoly)) { + table->flags |= EcsTableHasBuiltins; + } + } else { + if (ECS_HAS_ID_FLAG(id, TOGGLE)) { + table->flags |= EcsTableHasToggle; + + if (!table->bs_count) { + table->bs_offset = flecs_ito(int16_t, i); + } + table->bs_count ++; + } + if (ECS_HAS_ID_FLAG(id, OVERRIDE)) { + table->flags |= EcsTableHasOverrides; + } + } + } + } } static -void init_table( +void flecs_table_append_to_records( ecs_world_t *world, ecs_table_t *table, - ecs_table_t *prev) + ecs_vector_t **records, + ecs_id_t id, + int32_t column) { - table->type_info = NULL; - table->flags = 0; - table->dirty_state = NULL; - table->lock = 0; - table->refcount = 1; - table->generation = 0; + /* To avoid a quadratic search, use the O(1) lookup that the index + * already provides. */ + ecs_id_record_t *idr = flecs_id_record_ensure(world, id); + ecs_table_record_t *tr = (ecs_table_record_t*)flecs_id_record_get_table( + idr, table); + if (!tr) { + tr = ecs_vector_add(records, ecs_table_record_t); + tr->column = column; + tr->count = 1; - init_node(&table->node); + ecs_table_cache_insert(&idr->cache, table, &tr->hdr); + } else { + tr->count ++; + } - flecs_table_init(world, table, prev); + ecs_assert(tr->hdr.cache != NULL, ECS_INTERNAL_ERROR, NULL); } -static -ecs_table_t *create_table( +void flecs_table_init( ecs_world_t *world, - ecs_type_t *type, - flecs_hashmap_result_t table_elem, - ecs_table_t *prev) + ecs_table_t *table, + ecs_table_t *from) { - 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); - result->type = *type; + flecs_table_init_flags(world, table); - if (ecs_should_log_2()) { - char *expr = ecs_type_str(world, &result->type); - ecs_dbg_2( - "#[green]table#[normal] [%s] #[green]created#[reset] with id %d", - expr, result->id); - ecs_os_free(expr); + int32_t dst_i = 0, dst_count = table->type.count; + int32_t src_i = 0, src_count = 0; + ecs_id_t *dst_ids = table->type.array; + ecs_id_t *src_ids = NULL; + ecs_table_record_t *tr = NULL, *src_tr = NULL; + if (from) { + src_count = from->type.count; + src_ids = from->type.array; + src_tr = from->records; } - ecs_log_push_2(); + /* We don't know in advance how large the records array will be, so use + * cached vector. This eliminates unnecessary allocations, and/or expensive + * iterations to determine how many records we need. */ + ecs_vector_t *records = world->store.records; + ecs_vector_clear(records); + ecs_id_record_t *idr; - /* Store table in table hashmap */ - *(ecs_table_t**)table_elem.value = result; + int32_t last_id = -1; /* Track last regular (non-pair) id */ + int32_t first_pair = -1; /* Track the first pair in the table */ + int32_t first_role = -1; /* Track first id with role */ - /* Set keyvalue to one that has the same lifecycle as the table */ - *(ecs_type_t*)table_elem.key = result->type; + /* Scan to find boundaries of regular ids, pairs and roles */ + for (dst_i = 0; dst_i < dst_count; dst_i ++) { + ecs_id_t dst_id = dst_ids[dst_i]; + if (first_pair == -1 && ECS_IS_PAIR(dst_id)) { + first_pair = dst_i; + } + if ((dst_id & ECS_COMPONENT_MASK) == dst_id) { + last_id = dst_i; + } else if (first_role == -1 && !ECS_IS_PAIR(dst_id)) { + first_role = dst_i; + } + } - init_table(world, result, prev); + /* The easy part: initialize a record for every id in the type */ + for (dst_i = 0; (dst_i < dst_count) && (src_i < src_count); ) { + ecs_id_t dst_id = dst_ids[dst_i]; + ecs_id_t src_id = src_ids[src_i]; - flecs_notify_queries(world, &(ecs_query_event_t) { - .kind = EcsQueryTableMatch, - .table = result - }); + idr = NULL; - /* Update counters */ - world->info.table_count ++; - world->info.table_record_count += result->record_count; - world->info.table_storage_count += result->storage_count; - world->info.empty_table_count ++; - world->info.table_create_total ++; - - if (!result->storage_count) { - world->info.tag_table_count ++; - } else { - world->info.trivial_table_count += !(result->flags & EcsTableIsComplex); - } + if (dst_id == src_id) { + idr = (ecs_id_record_t*)src_tr[src_i].hdr.cache; + } else if (dst_id < src_id) { + idr = flecs_id_record_ensure(world, dst_id); + } + if (idr) { + tr = ecs_vector_add(&records, ecs_table_record_t); + tr->hdr.cache = (ecs_table_cache_t*)idr; + tr->column = dst_i; + tr->count = 1; + } - ecs_log_pop_2(); + dst_i += dst_id <= src_id; + src_i += dst_id >= src_id; + } - return result; -} + /* Add remaining ids that the "from" table didn't have */ + for (; (dst_i < dst_count); dst_i ++) { + ecs_id_t dst_id = dst_ids[dst_i]; + tr = ecs_vector_add(&records, ecs_table_record_t); + idr = flecs_id_record_ensure(world, dst_id); + tr->hdr.cache = (ecs_table_cache_t*)idr; + ecs_assert(tr->hdr.cache != NULL, ECS_INTERNAL_ERROR, NULL); + tr->column = dst_i; + tr->count = 1; + } -static -ecs_table_t* find_or_create( - ecs_world_t *world, - ecs_type_t *type, - bool own_type, - ecs_table_t *prev) -{ - ecs_poly_assert(world, ecs_world_t); + /* We're going to insert records from the vector into the index that + * will get patched up later. To ensure the record pointers don't get + * invalidated we need to grow the vector so that it won't realloc as + * we're adding the next set of records */ + if (first_role != -1 || first_pair != -1) { + int32_t start = first_role; + if (first_pair != -1 && (start != -1 || first_pair < start)) { + start = first_pair; + } - int32_t id_count = type->count; - if (!id_count) { - return &world->store.root; + /* Total number of records can never be higher than + * - number of regular (non-pair) ids + + * - three records for pairs: (R,T), (R,*), (*,T) + * - one wildcard (*), one any (_) and one pair wildcard (*,*) record + * - one record for (ChildOf, 0) + */ + int32_t flag_id_count = dst_count - start; + int32_t record_count = start + 3 * flag_id_count + 3 + 1; + ecs_vector_set_min_size(&records, ecs_table_record_t, record_count); } - ecs_table_t *table; - flecs_hashmap_result_t elem = flecs_hashmap_ensure( - &world->store.table_map, type, ecs_table_t*); - if ((table = *(ecs_table_t**)elem.value)) { - if (own_type) { - flecs_type_free(type); + /* Add records for ids with roles (used by cleanup logic) */ + if (first_role != -1) { + for (dst_i = first_role; dst_i < dst_count; dst_i ++) { + ecs_id_t id = dst_ids[dst_i]; + if (!ECS_IS_PAIR(id)) { + ecs_entity_t first = 0; + ecs_entity_t second = 0; + if (ECS_HAS_ID_FLAG(id, PAIR)) { + first = ECS_PAIR_FIRST(id); + second = ECS_PAIR_SECOND(id); + } else { + first = id & ECS_COMPONENT_MASK; + } + if (first) { + flecs_table_append_to_records(world, table, &records, + ecs_pair(EcsFlag, first), dst_i); + } + if (second) { + flecs_table_append_to_records(world, table, &records, + ecs_pair(EcsFlag, second), dst_i); + } + } } - return table; } - /* If we get here, table needs to be created which is only allowed when the - * application is not currently in progress */ - ecs_assert(!(world->flags & EcsWorldReadonly), ECS_INTERNAL_ERROR, NULL); - - /* If we get here, the table has not been found, so create it. */ - if (own_type) { - return create_table(world, type, elem, prev); - } + int32_t last_pair = -1; + bool has_childof = table->flags & EcsTableHasChildOf; + if (first_pair != -1) { + /* Add a (Relationship, *) record for each relationship. */ + ecs_entity_t r = 0; + for (dst_i = first_pair; dst_i < dst_count; dst_i ++) { + ecs_id_t dst_id = dst_ids[dst_i]; + if (!ECS_IS_PAIR(dst_id)) { + break; /* no more pairs */ + } + if (r != ECS_PAIR_FIRST(dst_id)) { /* New relationship, new record */ + tr = ecs_vector_get(records, ecs_table_record_t, dst_i); + idr = ((ecs_id_record_t*)tr->hdr.cache)->parent; /* (R, *) */ + ecs_assert(idr != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_type_t copy = flecs_type_copy(type); - return create_table(world, ©, elem, prev); -} + tr = ecs_vector_add(&records, ecs_table_record_t); + tr->hdr.cache = (ecs_table_cache_t*)idr; + tr->column = dst_i; + tr->count = 0; + r = ECS_PAIR_FIRST(dst_id); + } -static -void ids_append( - ecs_type_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; -} + ecs_assert(tr != NULL, ECS_INTERNAL_ERROR, NULL); + tr->count ++; + } -static -void diff_insert_isa( - ecs_world_t *world, - ecs_table_t *table, - ecs_table_diff_t *base_diff, - ecs_type_t *append_to, - ecs_type_t *append_from, - ecs_id_t add) -{ - ecs_entity_t base = ecs_pair_second(world, add); - ecs_table_t *base_table = ecs_get_table(world, base); - if (!base_table) { - return; - } + last_pair = dst_i; - ecs_type_t base_type = base_table->type; - ecs_table_t *table_wo_base = base_table; + /* Add a (*, Target) record for each relationship target. Type + * ids are sorted relationship-first, so we can't simply do a single linear + * scan to find all occurrences for a target. */ + for (dst_i = first_pair; dst_i < last_pair; dst_i ++) { + ecs_id_t dst_id = dst_ids[dst_i]; + ecs_id_t tgt_id = ecs_pair(EcsWildcard, ECS_PAIR_SECOND(dst_id)); - /* If the table does not have a component from the base, it should - * emit an OnSet event */ - ecs_id_t *ids = base_type.array; - int32_t j, i, count = base_type.count; - for (i = 0; i < count; i ++) { - ecs_id_t id = ids[i]; + flecs_table_append_to_records( + world, table, &records, tgt_id, dst_i); + } + } - if (ECS_HAS_RELATION(id, EcsIsA)) { - /* The base has an IsA relationship. 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); + /* Lastly, add records for all-wildcard ids */ + if (last_id >= 0) { + tr = ecs_vector_add(&records, ecs_table_record_t); + tr->hdr.cache = (ecs_table_cache_t*)world->idr_wildcard; + tr->column = 0; + tr->count = last_id + 1; + } + if (last_pair - first_pair) { + tr = ecs_vector_add(&records, ecs_table_record_t); + tr->hdr.cache = (ecs_table_cache_t*)world->idr_wildcard_wildcard; + tr->column = first_pair; + tr->count = last_pair - first_pair; + } + if (dst_count) { + tr = ecs_vector_add(&records, ecs_table_record_t); + tr->hdr.cache = (ecs_table_cache_t*)world->idr_any; + tr->column = 0; + tr->count = 1; + } + if (dst_count && !has_childof) { + tr = ecs_vector_add(&records, ecs_table_record_t); + tr->hdr.cache = (ecs_table_cache_t*)world->idr_childof_0; + tr->column = 0; + tr->count = 1; + } - /* 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_search(world, table, base_id, NULL) == -1) { - ids_append(append_to, base_id); - } - } + /* Now that all records have been added, copy them to array */ + int32_t i, dst_record_count = ecs_vector_count(records); + ecs_table_record_t *dst_tr = ecs_os_memdup_n( ecs_vector_first(records, + ecs_table_record_t), ecs_table_record_t, dst_record_count); + table->record_count = flecs_ito(uint16_t, dst_record_count); + table->records = dst_tr; - continue; - } + /* Register & patch up records */ + for (i = 0; i < dst_record_count; i ++) { + tr = &dst_tr[i]; + idr = (ecs_id_record_t*)dst_tr[i].hdr.cache; + ecs_assert(idr != NULL, ECS_INTERNAL_ERROR, NULL); - /* Identifiers are not inherited */ - if (ECS_HAS_RELATION(id, ecs_id(EcsIdentifier))) { - continue; + if (ecs_table_cache_get(&idr->cache, table)) { + /* If this is a target wildcard record it has already been + * registered, but the record is now at a different location in + * memory. Patch up the linked list with the new address */ + ecs_table_cache_replace(&idr->cache, table, &tr->hdr); + } else { + /* Other records are not registered yet */ + ecs_assert(idr != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_table_cache_insert(&idr->cache, table, &tr->hdr); } - if (!ecs_get_typeid(world, id)) { - continue; - } + /* Claim id record so it stays alive as long as the table exists */ + flecs_id_record_claim(world, idr); - if (ecs_search(world, table, id, NULL) == -1) { - ids_append(append_to, id); - } + /* Initialize event flags */ + table->flags |= idr->flags & EcsIdEventMask; } + + world->store.records = records; + + flecs_table_init_storage_table(world, table); + flecs_table_init_data(table); } static -void diff_insert_added_isa( +void flecs_table_records_unregister( ecs_world_t *world, - ecs_table_t *table, - ecs_table_diff_t *diff, - ecs_id_t id) + ecs_table_t *table) { - ecs_table_diff_t base_diff; - diff_insert_isa(world, table, &base_diff, &diff->on_set, - &base_diff.un_set, id); + int32_t i, count = table->record_count; + for (i = 0; i < count; i ++) { + ecs_table_record_t *tr = &table->records[i]; + ecs_table_cache_t *cache = tr->hdr.cache; + ecs_id_t id = ((ecs_id_record_t*)cache)->id; + + ecs_assert(tr->hdr.cache == cache, ECS_INTERNAL_ERROR, NULL); + ecs_assert(tr->hdr.table == table, ECS_INTERNAL_ERROR, NULL); + ecs_assert(flecs_id_record_get(world, id) == (ecs_id_record_t*)cache, + ECS_INTERNAL_ERROR, NULL); + (void)id; + + ecs_table_cache_remove(cache, table, &tr->hdr); + flecs_id_record_release(world, (ecs_id_record_t*)cache); + } + + ecs_os_free(table->records); } static -void diff_insert_removed_isa( - ecs_world_t *world, - ecs_table_t *table, - ecs_table_diff_t *diff, - ecs_id_t id) +void notify_trigger( + ecs_world_t *world, + ecs_table_t *table, + ecs_entity_t event) { - ecs_table_diff_t base_diff; - diff_insert_isa(world, table, &base_diff, &diff->un_set, - &base_diff.un_set, id); + (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; + } } static -void diff_insert_added( +void run_on_remove( ecs_world_t *world, ecs_table_t *table, - ecs_table_diff_t *diff, - ecs_id_t id) + ecs_data_t *data) { - diff->added.array[diff->added.count ++] = id; - - if (ECS_HAS_RELATION(id, EcsIsA)) { - diff_insert_added_isa(world, table, diff, id); + int32_t count = data->entities.count; + if (count) { + ecs_table_diff_t diff = { + .removed = table->type, + .un_set = table->type + }; + + flecs_notify_on_remove(world, table, NULL, 0, count, &diff); } } +/* -- Private functions -- */ + static -void diff_insert_removed( +void on_component_callback( ecs_world_t *world, ecs_table_t *table, - ecs_table_diff_t *diff, - ecs_id_t id) + ecs_iter_action_t callback, + ecs_entity_t event, + ecs_column_t *column, + ecs_entity_t *entities, + ecs_id_t id, + int32_t row, + int32_t count, + ecs_type_info_t *ti) { - diff->removed.array[diff->removed.count ++] = id; + ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_iter_t it = { .field_count = 1 }; + it.entities = entities; - if (ECS_HAS_RELATION(id, EcsIsA)) { - /* Removing an IsA relationship 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; - } + ecs_size_t size = ti->size; + void *ptr = ecs_storage_get(column, size, row); - if (table->flags & EcsTableHasIsA) { - if (!ecs_get_typeid(world, id)) { - /* Do nothing if id is not a component */ - return; - } + flecs_iter_init(&it, flecs_iter_cache_all); + it.world = world; + it.real_world = world; + it.table = table; + it.ptrs[0] = ptr; + it.sizes[0] = size; + it.ids[0] = id; + it.event = event; + it.event_id = id; + it.ctx = ti->hooks.ctx; + it.binding_ctx = ti->hooks.binding_ctx; + it.count = count; + flecs_iter_validate(&it); + callback(&it); +} - /* 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_search_relation(world, table, 0, id, EcsIsA, - EcsUp, 0, 0, 0) != -1) - { - ids_append(&diff->on_set, id); - return; - } - } +static +void ctor_component( + ecs_type_info_t *ti, + ecs_column_t *column, + int32_t row, + int32_t count) +{ + ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); - if (ecs_get_typeid(world, id) != 0) { - ids_append(&diff->un_set, id); + ecs_xtor_t ctor = ti->hooks.ctor; + if (ctor) { + void *ptr = ecs_storage_get(column, ti->size, row); + ctor(ptr, count, ti); } } static -void compute_table_diff( +void add_component( ecs_world_t *world, - ecs_table_t *node, - ecs_table_t *next, - ecs_graph_edge_t *edge, - ecs_id_t id) + ecs_table_t *table, + ecs_type_info_t *ti, + ecs_column_t *column, + ecs_entity_t *entities, + ecs_id_t id, + int32_t row, + int32_t count, + bool construct) { - if (node == next) { - return; - } + ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); - if (ECS_IS_PAIR(id)) { - ecs_id_record_t *idr = flecs_id_record_get(world, ecs_pair( - ECS_PAIR_FIRST(id), EcsWildcard)); - if (idr->flags & EcsIdUnion) { - id = ecs_pair(EcsUnion, ECS_PAIR_FIRST(id)); - } + if (construct) { + ctor_component(ti, column, row, count); } - ecs_type_t node_type = node->type; - ecs_type_t next_type = next->type; + ecs_iter_action_t on_add = ti->hooks.on_add; + if (on_add) { + on_component_callback(world, table, on_add, EcsOnAdd, column, + entities, id, row, count, ti); + } +} - ecs_id_t *ids_node = node_type.array; - ecs_id_t *ids_next = next_type.array; - int32_t i_node = 0, node_count = node_type.count; - int32_t i_next = 0, next_count = next_type.count; - int32_t added_count = 0; - int32_t removed_count = 0; - bool trivial_edge = !ECS_HAS_RELATION(id, EcsIsA); +static +void dtor_component( + ecs_type_info_t *ti, + ecs_column_t *column, + int32_t row, + int32_t count) +{ + ecs_assert(ti != NULL, 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]; + ecs_xtor_t dtor = ti->hooks.dtor; + if (dtor) { + void *ptr = ecs_storage_get(column, ti->size, row); + dtor(ptr, count, ti); + } +} - bool added = id_next < id_node; - bool removed = id_node < id_next; +static +void remove_component( + ecs_world_t *world, + ecs_table_t *table, + ecs_type_info_t *ti, + ecs_column_t *column, + ecs_entity_t *entities, + ecs_id_t id, + int32_t row, + int32_t count) +{ + ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); - trivial_edge &= !added || id_next == id; - trivial_edge &= !removed || id_node == id; + ecs_iter_action_t on_remove = ti->hooks.on_remove; + if (on_remove) { + on_component_callback(world, table, on_remove, EcsOnRemove, column, + entities, id, row, count, ti); + } + + dtor_component(ti, column, row, count); +} - added_count += added; - removed_count += removed; +static +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) +{ + /* Can't delete and not update the entity index */ + ecs_assert(!is_delete || update_entity_index, ECS_INTERNAL_ERROR, NULL); - i_node += id_node <= id_next; - i_next += id_next <= id_node; - } + ecs_id_t *ids = table->storage_ids; + int32_t ids_count = table->storage_count; + ecs_record_t **records = data->records.array; + ecs_entity_t *entities = data->entities.array; + int32_t i, c, end = row + count; - added_count += next_count - i_next; - removed_count += node_count - i_node; + (void)records; - trivial_edge &= (added_count + removed_count) <= 1 && - !ecs_id_is_wildcard(id); + /* If table has components with destructors, iterate component columns */ + if (table->flags & EcsTableHasDtors) { + /* Throw up a lock just to be sure */ + table->lock = true; - if (trivial_edge && removed_count && (node->flags & EcsTableHasIsA)) { - /* If a single component was removed from a table with an IsA, - * relationship it could reexpose an inherited component. If this is - * the case, don't treat it as a trivial edge. */ - if (ecs_search_relation(world, next, 0, id, EcsIsA, EcsUp, 0, 0, 0) != -1) { - trivial_edge = false; + /* Run on_remove callbacks first before destructing components */ + for (c = 0; c < ids_count; c++) { + ecs_column_t *column = &data->columns[c]; + ecs_type_info_t *ti = table->type_info[c]; + ecs_iter_action_t on_remove = ti->hooks.on_remove; + if (on_remove) { + on_component_callback(world, table, on_remove, EcsOnRemove, + column, &entities[row], ids[c], row, count, ti); + } } - } - if (trivial_edge) { - /* If edge is trivial there's no need to create a diff element for it. - * Store whether the id is a tag or not, so that we can still tell - * whether an UnSet handler should be called or not. */ - if (node->storage_table != next->storage_table) { - edge->diff = &ecs_table_edge_is_component; + /* Destruct components */ + for (c = 0; c < ids_count; c++) { + dtor_component(table->type_info[c], &data->columns[c], row, count); } - return; - } - - ecs_table_diff_t *diff = ecs_os_calloc_t(ecs_table_diff_t); - edge->diff = diff; - if (added_count) { - diff->added.array = ecs_os_malloc_n(ecs_id_t, added_count); - diff->added.count = 0; - } - if (removed_count) { - diff->removed.array = ecs_os_malloc_n(ecs_id_t, removed_count); - diff->removed.count = 0; - } - 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]; + /* Iterate entities first, then components. This ensures that only one + * entity is invalidated at a time, which ensures that destructors can + * safely access other entities. */ + for (i = row; i < end; i ++) { + /* Update entity index after invoking destructors so that entity can + * be safely used in destructor callbacks. */ + if (update_entity_index) { + ecs_entity_t e = entities[i]; + ecs_assert(!e || ecs_is_valid(world, e), + ECS_INTERNAL_ERROR, NULL); + ecs_assert(!e || records[i] == flecs_entities_get(world, e), + ECS_INTERNAL_ERROR, NULL); + ecs_assert(!e || records[i]->table == table, + ECS_INTERNAL_ERROR, 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 (is_delete) { + flecs_entities_remove(world, e); + ecs_assert(ecs_is_valid(world, e) == false, + ECS_INTERNAL_ERROR, NULL); + } else { + // If this is not a delete, clear the entity index record + records[i]->table = NULL; + records[i]->row = 0; + } + } else { + /* This should only happen in rare cases, such as when the data + * cleaned up is not part of the world (like with snapshots) */ + } } - i_node += id_node <= id_next; - i_next += id_next <= id_node; - } - - for (; i_next < next_count; i_next ++) { - diff_insert_added(world, node, diff, ids_next[i_next]); - } - for (; i_node < node_count; i_node ++) { - diff_insert_removed(world, next, diff, ids_node[i_node]); - } - - ecs_assert(diff->added.count == added_count, ECS_INTERNAL_ERROR, NULL); - ecs_assert(diff->removed.count == removed_count, ECS_INTERNAL_ERROR, NULL); -} + table->lock = false; -static -void flecs_add_overrides_for_base( - ecs_world_t *world, - ecs_type_t *dst_type, - ecs_id_t pair) -{ - ecs_entity_t base = ecs_pair_second(world, pair); - ecs_assert(base != 0, ECS_INTERNAL_ERROR, NULL); - ecs_table_t *base_table = ecs_get_table(world, base); - if (!base_table) { - return; - } - - ecs_id_t *ids = base_table->type.array; + /* 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] == flecs_entities_get(world, e), + ECS_INTERNAL_ERROR, NULL); + ecs_assert(!e || records[i]->table == table, + ECS_INTERNAL_ERROR, NULL); - ecs_flags32_t flags = base_table->flags; - if (flags & EcsTableHasOverrides) { - int32_t i, count = base_table->type.count; - for (i = 0; i < count; i ++) { - ecs_id_t id = ids[i]; - if (ECS_HAS_ID_FLAG(id, OVERRIDE)) { - flecs_type_add(dst_type, id & ~ECS_OVERRIDE); + flecs_entities_remove(world, e); + ecs_assert(!ecs_is_valid(world, e), ECS_INTERNAL_ERROR, NULL); + } + } else { + for (i = row; i < end; i ++) { + ecs_entity_t e = entities[i]; + ecs_assert(!e || ecs_is_valid(world, e), ECS_INTERNAL_ERROR, NULL); + ecs_assert(!e || records[i] == flecs_entities_get(world, e), + ECS_INTERNAL_ERROR, NULL); + ecs_assert(!e || records[i]->table == table, + ECS_INTERNAL_ERROR, NULL); + records[i]->table = NULL; + records[i]->row = records[i]->row & ECS_ROW_FLAGS_MASK; + (void)e; } - } - } - - if (flags & EcsTableHasIsA) { - const ecs_table_record_t *tr = flecs_id_record_get_table( - world->idr_isa_wildcard, base_table); - ecs_assert(tr != NULL, ECS_INTERNAL_ERROR, NULL); - int32_t i = tr->column, end = i + tr->count; - for (; i != end; i ++) { - flecs_add_overrides_for_base(world, dst_type, ids[i]); - } + } } } static -void flecs_add_with_property( +void fini_data( ecs_world_t *world, - ecs_id_record_t *idr_with_wildcard, - ecs_type_t *dst_type, - ecs_entity_t r, - ecs_entity_t o) + ecs_table_t *table, + ecs_data_t *data, + bool do_on_remove, + bool update_entity_index, + bool is_delete, + bool deactivate) { - r = ecs_get_alive(world, r); + ecs_assert(!table->lock, ECS_LOCKED_STORAGE, NULL); - /* Check if component/relationship has With pairs, which contain ids - * that need to be added to the table. */ - ecs_table_t *table = ecs_get_table(world, r); - if (!table) { + if (!data) { return; } - - const ecs_table_record_t *tr = flecs_id_record_get_table( - idr_with_wildcard, table); - if (tr) { - int32_t i = tr->column, end = i + tr->count; - ecs_id_t *ids = table->type.array; - - for (; i < end; i ++) { - ecs_id_t id = ids[i]; - ecs_assert(ECS_PAIR_FIRST(id) == EcsWith, ECS_INTERNAL_ERROR, NULL); - ecs_id_t ra = ECS_PAIR_SECOND(id); - ecs_id_t a = ra; - if (o) { - a = ecs_pair(ra, o); - } - flecs_type_add(dst_type, a); + ecs_flags32_t flags = table->flags; - flecs_add_with_property(world, idr_with_wildcard, dst_type, ra, o); - } + if (do_on_remove && (flags & EcsTableHasOnRemove)) { + run_on_remove(world, table, data); } -} + int32_t count = flecs_table_data_count(data); + if (count) { + dtor_all_components(world, table, data, 0, count, + update_entity_index, is_delete); + } -static -ecs_table_t* flecs_find_table_with( - ecs_world_t *world, - ecs_table_t *node, - ecs_id_t with) -{ - ecs_ensure_id(world, with); - - ecs_id_record_t *idr = NULL; - ecs_entity_t r = 0, o = 0; + /* Sanity check */ + ecs_assert(data->records.count == + data->entities.count, ECS_INTERNAL_ERROR, NULL); - if (ECS_IS_PAIR(with)) { - r = ECS_PAIR_FIRST(with); - o = ECS_PAIR_SECOND(with); - idr = flecs_id_record_ensure(world, ecs_pair(r, EcsWildcard)); - if (idr->flags & EcsIdUnion) { - ecs_type_t dst_type; - ecs_id_t union_id = ecs_pair(EcsUnion, r); - int res = flecs_type_new_with(&dst_type, &node->type, union_id); - if (res == -1) { - return node; - } + ecs_column_t *columns = data->columns; + if (columns) { + int32_t c, column_count = table->storage_count; + for (c = 0; c < column_count; c ++) { + /* Sanity check */ + ecs_assert(columns[c].count == data->entities.count, + ECS_INTERNAL_ERROR, NULL); - return find_or_create(world, &dst_type, true, node); - } else if (idr->flags & EcsIdExclusive) { - /* Relationship is exclusive, check if table already has it */ - const ecs_table_record_t *tr = flecs_id_record_get_table(idr, node); - if (tr) { - /* Table already has an instance of the relationship, create - * a new id sequence with the existing id replaced */ - ecs_type_t dst_type = flecs_type_copy(&node->type); - dst_type.array[tr->column] = with; - return find_or_create(world, &dst_type, true, node); - } + ecs_storage_fini(&columns[c]); } - } else { - idr = flecs_id_record_ensure(world, with); - r = with; + ecs_os_free(columns); + data->columns = NULL; } - /* Create sequence with new id */ - ecs_type_t dst_type; - int res = flecs_type_new_with(&dst_type, &node->type, with); - if (res == -1) { - return node; /* Current table already has id */ + ecs_switch_t *sw_columns = data->sw_columns; + if (sw_columns) { + int32_t c, column_count = table->sw_count; + for (c = 0; c < column_count; c ++) { + flecs_switch_fini(&sw_columns[c]); + } + ecs_os_free(sw_columns); + data->sw_columns = NULL; } - if (r == EcsIsA) { - /* If adding a prefab, check if prefab has overrides */ - flecs_add_overrides_for_base(world, &dst_type, with); + ecs_bitset_t *bs_columns = data->bs_columns; + if (bs_columns) { + int32_t c, column_count = table->bs_count; + for (c = 0; c < column_count; c ++) { + flecs_bitset_fini(&bs_columns[c]); + } + ecs_os_free(bs_columns); + data->bs_columns = NULL; } - if (idr->flags & EcsIdWith) { - ecs_id_record_t *idr_with_wildcard = flecs_id_record_get(world, - ecs_pair(EcsWith, EcsWildcard)); - /* If id has With property, add targets to type */ - flecs_add_with_property(world, idr_with_wildcard, &dst_type, r, o); + ecs_storage_fini(&data->entities); + ecs_storage_fini(&data->records); + + if (deactivate && count) { + flecs_table_set_empty(world, table); } - return find_or_create(world, &dst_type, true, node); + table->observed_count = 0; } -static -ecs_table_t* flecs_find_table_without( +/* Cleanup, no OnRemove, don't update entity index, don't deactivate table */ +void flecs_table_clear_data( ecs_world_t *world, - ecs_table_t *node, - ecs_id_t without) + ecs_table_t *table, + ecs_data_t *data) { - if (ECS_IS_PAIR(without)) { - ecs_entity_t r = 0; - ecs_id_record_t *idr = NULL; - r = ECS_PAIR_FIRST(without); - idr = flecs_id_record_get(world, ecs_pair(r, EcsWildcard)); - if (idr && idr->flags & EcsIdUnion) { - without = ecs_pair(EcsUnion, r); - } - } - - /* Create sequence with new id */ - ecs_type_t dst_type; - int res = flecs_type_new_without(&dst_type, &node->type, without); - if (res == -1) { - return node; /* Current table does not have id */ - } - - return find_or_create(world, &dst_type, true, node); + fini_data(world, table, data, false, false, false, false); } -static -void init_edge( - ecs_table_t *table, - ecs_graph_edge_t *edge, - ecs_id_t id, - ecs_table_t *to) +/* Cleanup, no OnRemove, clear entity index, deactivate table */ +void flecs_table_clear_entities_silent( + ecs_world_t *world, + ecs_table_t *table) { - ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(edge != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(edge->id == 0, ECS_INTERNAL_ERROR, NULL); - ecs_assert(edge->hdr.next == NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(edge->hdr.prev == NULL, ECS_INTERNAL_ERROR, NULL); - - edge->from = table; - edge->to = to; - edge->id = id; + fini_data(world, table, &table->data, false, true, false, true); } -static -void flecs_init_edge_for_add( +/* Cleanup, run OnRemove, clear entity index, deactivate table */ +void flecs_table_clear_entities( ecs_world_t *world, - ecs_table_t *table, - ecs_graph_edge_t *edge, - ecs_id_t id, - ecs_table_t *to) + ecs_table_t *table) { - init_edge(table, edge, id, to); + fini_data(world, table, &table->data, true, true, false, true); +} - ensure_hi_edge(world, &table->node.add, id); - - if (table != to) { - /* Add edges are appended to refs.next */ - ecs_graph_edge_hdr_t *to_refs = &to->node.refs; - ecs_graph_edge_hdr_t *next = to_refs->next; - - to_refs->next = &edge->hdr; - edge->hdr.prev = to_refs; - - edge->hdr.next = next; - if (next) { - next->prev = &edge->hdr; - } - - compute_table_diff(world, table, to, edge, id); - } -} - -static -void flecs_init_edge_for_remove( +/* Cleanup, run OnRemove, delete from entity index, deactivate table */ +void flecs_table_delete_entities( ecs_world_t *world, - ecs_table_t *table, - ecs_graph_edge_t *edge, - ecs_id_t id, - ecs_table_t *to) + ecs_table_t *table) { - init_edge(table, edge, id, to); - - ensure_hi_edge(world, &table->node.remove, id); - - if (table != to) { - /* Remove edges are appended to refs.prev */ - ecs_graph_edge_hdr_t *to_refs = &to->node.refs; - ecs_graph_edge_hdr_t *prev = to_refs->prev; - - to_refs->prev = &edge->hdr; - edge->hdr.next = to_refs; - - edge->hdr.prev = prev; - if (prev) { - prev->next = &edge->hdr; - } - - compute_table_diff(world, table, to, edge, id); - } + fini_data(world, table, &table->data, true, true, true, true); } -static -ecs_table_t* flecs_create_edge_for_remove( +/* 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 *node, - ecs_graph_edge_t *edge, - ecs_id_t id) + ecs_table_t *table) { - ecs_table_t *to = flecs_find_table_without(world, node, id); - - flecs_init_edge_for_remove(world, node, edge, id, to); - - return to; + (void)world; + run_on_remove(world, table, &table->data); } -static -ecs_table_t* flecs_create_edge_for_add( +/* Free table resources. */ +void flecs_table_free( ecs_world_t *world, - ecs_table_t *node, - ecs_graph_edge_t *edge, - ecs_id_t id) -{ - ecs_table_t *to = flecs_find_table_with(world, node, id); - - flecs_init_edge_for_add(world, node, edge, id, to); - - return to; -} - -static -void populate_diff( - ecs_graph_edge_t *edge, - ecs_id_t *add_ptr, - ecs_id_t *remove_ptr, - ecs_table_diff_t *out) + ecs_table_t *table) { - if (out) { - ecs_table_diff_t *diff = edge->diff; + bool is_root = table == &world->store.root; + ecs_assert(!table->lock, ECS_LOCKED_STORAGE, NULL); + ecs_assert(is_root || table->id != 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(is_root || flecs_sparse_is_alive(&world->store.tables, table->id), + ECS_INTERNAL_ERROR, NULL); + (void)world; - if (diff && diff != &ecs_table_edge_is_component) { - *out = *diff; - } else { - out->on_set.count = 0; + ecs_assert(table->refcount == 0, ECS_INTERNAL_ERROR, NULL); - if (add_ptr) { - out->added.array = add_ptr; - out->added.count = 1; - } else { - out->added.count = 0; - } + if (!is_root) { + flecs_notify_queries( + world, &(ecs_query_event_t){ + .kind = EcsQueryTableUnmatch, + .table = table + }); + } - if (remove_ptr) { - out->removed.array = remove_ptr; - out->removed.count = 1; - if (diff == &ecs_table_edge_is_component) { - 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 (ecs_should_log_2()) { + char *expr = ecs_type_str(world, &table->type); + ecs_dbg_2( + "#[green]table#[normal] [%s] #[red]deleted#[reset] with id %d", + expr, table->id); + ecs_os_free(expr); + ecs_log_push_2(); } -} -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); + world->info.empty_table_count -= (ecs_table_count(table) == 0); - node = node ? node : &world->store.root; + /* Cleanup data, no OnRemove, delete from entity index, don't deactivate */ + fini_data(world, table, &table->data, false, true, true, false); - /* 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); + flecs_table_clear_edges(world, table); - ecs_id_t id = id_ptr[0]; - ecs_graph_edge_t *edge = ensure_edge(world, &node->node.remove, id); - ecs_table_t *to = edge->to; + if (!is_root) { + ecs_type_t ids = { + .array = table->type.array, + .count = table->type.count + }; - if (!to) { - to = flecs_create_edge_for_remove(world, node, edge, id); - ecs_assert(to != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(edge->to != NULL, ECS_INTERNAL_ERROR, NULL); + flecs_hashmap_remove(&world->store.table_map, &ids, ecs_table_t*); } - populate_diff(edge, NULL, id_ptr, diff); - - return to; -error: - return NULL; -} - -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); + ecs_os_free(table->dirty_state); + ecs_os_free(table->storage_map); - node = node ? node : &world->store.root; + flecs_table_records_unregister(world, table); - /* 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); + ecs_table_t *storage_table = table->storage_table; + if (storage_table == table) { + if (table->type_info) { + ecs_os_free(table->type_info); + } + } else if (storage_table) { + flecs_table_release(world, storage_table); + } - ecs_id_t id = id_ptr[0]; - ecs_graph_edge_t *edge = ensure_edge(world, &node->node.add, id); - ecs_table_t *to = edge->to; + /* Update counters */ + world->info.table_count --; + world->info.table_record_count -= table->record_count; + world->info.table_storage_count -= table->storage_count; + world->info.table_delete_total ++; - if (!to) { - to = flecs_create_edge_for_add(world, node, edge, id); - ecs_assert(to != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(edge->to != NULL, ECS_INTERNAL_ERROR, NULL); + if (!table->storage_count) { + world->info.tag_table_count --; + } else { + world->info.trivial_table_count -= !(table->flags & EcsTableIsComplex); } - populate_diff(edge, id_ptr, NULL, diff); + if (!(world->flags & EcsWorldFini)) { + ecs_assert(!is_root, ECS_INTERNAL_ERROR, NULL); + flecs_table_free_type(table); + flecs_sparse_remove(&world->store.tables, table->id); + } - return to; -error: - return NULL; + ecs_log_pop_2(); } -ecs_table_t* flecs_table_find_or_create( +void flecs_table_claim( ecs_world_t *world, - ecs_type_t *type) -{ - ecs_poly_assert(world, ecs_world_t); - return find_or_create(world, type, false, NULL); -} - -void flecs_init_root_table( - ecs_world_t *world) + ecs_table_t *table) { ecs_poly_assert(world, ecs_world_t); - - world->store.root.type = (ecs_type_t){0}; - - init_table(world, &world->store.root, NULL); - - /* 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; + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(table->refcount > 0, ECS_INTERNAL_ERROR, NULL); + table->refcount ++; + (void)world; } -void flecs_table_clear_edges( +bool flecs_table_release( ecs_world_t *world, ecs_table_t *table) { - (void)world; ecs_poly_assert(world, ecs_world_t); + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(table->refcount > 0, ECS_INTERNAL_ERROR, NULL); - ecs_log_push_1(); - - ecs_map_iter_t it; - ecs_graph_node_t *table_node = &table->node; - ecs_graph_edges_t *node_add = &table_node->add; - ecs_graph_edges_t *node_remove = &table_node->remove; - ecs_map_t *add_hi = &node_add->hi; - ecs_map_t *remove_hi = &node_remove->hi; - ecs_graph_edge_hdr_t *node_refs = &table_node->refs; - ecs_graph_edge_t *edge; - uint64_t key; - - /* Cleanup outgoing edges */ - it = ecs_map_iter(add_hi); - while ((edge = ecs_map_next_ptr(&it, ecs_graph_edge_t*, &key))) { - disconnect_edge(world, key, edge); - } - - it = ecs_map_iter(remove_hi); - while ((edge = ecs_map_next_ptr(&it, ecs_graph_edge_t*, &key))) { - disconnect_edge(world, key, edge); - } - - /* Cleanup incoming add edges */ - ecs_graph_edge_hdr_t *next, *cur = node_refs->next; - if (cur) { - do { - edge = (ecs_graph_edge_t*)cur; - ecs_assert(edge->to == table, ECS_INTERNAL_ERROR, NULL); - ecs_assert(edge->from != NULL, ECS_INTERNAL_ERROR, NULL); - next = cur->next; - remove_edge(world, &edge->from->node.add, edge->id, edge); - } while ((cur = next)); - } - - /* Cleanup incoming remove edges */ - cur = node_refs->prev; - if (cur) { - do { - edge = (ecs_graph_edge_t*)cur; - ecs_assert(edge->to == table, ECS_INTERNAL_ERROR, NULL); - ecs_assert(edge->from != NULL, ECS_INTERNAL_ERROR, NULL); - next = cur->prev; - remove_edge(world, &edge->from->node.remove, edge->id, edge); - } while ((cur = next)); + if (--table->refcount == 0) { + flecs_table_free(world, table); + return true; } - - ecs_os_free(node_add->lo); - ecs_os_free(node_remove->lo); - ecs_map_fini(add_hi); - ecs_map_fini(remove_hi); - table_node->add.lo = NULL; - table_node->remove.lo = NULL; - - ecs_log_pop_1(); + + return false; } -/* 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) +/* 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) { - return flecs_table_traverse_add(world, table, &id, NULL); + ecs_os_free(table->type.array); } -ecs_table_t* ecs_table_remove_id( +/* Reset a table to its initial state. */ +void flecs_table_reset( ecs_world_t *world, - ecs_table_t *table, - ecs_id_t id) -{ - return flecs_table_traverse_remove(world, table, &id, NULL); -} - - -static -ecs_id_record_elem_t* id_record_elem( - ecs_id_record_t *head, - ecs_id_record_elem_t *list, - ecs_id_record_t *idr) + ecs_table_t *table) { - return ECS_OFFSET(idr, (uintptr_t)list - (uintptr_t)head); + ecs_assert(!table->lock, ECS_LOCKED_STORAGE, NULL); + flecs_table_clear_edges(world, table); } static -void id_record_elem_insert( - ecs_id_record_t *head, - ecs_id_record_t *idr, - ecs_id_record_elem_t *elem) +void mark_table_dirty( + ecs_world_t *world, + ecs_table_t *table, + int32_t index) { - ecs_id_record_elem_t *head_elem = id_record_elem(idr, elem, head); - ecs_id_record_t *cur = head_elem->next; - elem->next = cur; - elem->prev = head; - if (cur) { - ecs_id_record_elem_t *cur_elem = id_record_elem(idr, elem, cur); - cur_elem->prev = idr; + (void)world; + if (table->dirty_state) { + table->dirty_state[index] ++; } - head_elem->next = idr; } -static -void id_record_elem_remove( - ecs_id_record_t *idr, - ecs_id_record_elem_t *elem) +void flecs_table_mark_dirty( + ecs_world_t *world, + ecs_table_t *table, + ecs_entity_t component) { - ecs_id_record_t *prev = elem->prev; - ecs_id_record_t *next = elem->next; - ecs_assert(prev != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(!table->lock, ECS_LOCKED_STORAGE, NULL); + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_id_record_elem_t *prev_elem = id_record_elem(idr, elem, prev); - prev_elem->next = next; - if (next) { - ecs_id_record_elem_t *next_elem = id_record_elem(idr, elem, next); - next_elem->prev = prev; + if (table->dirty_state) { + int32_t index = ecs_search(world, table->storage_table, component, 0); + ecs_assert(index != -1, ECS_INTERNAL_ERROR, NULL); + table->dirty_state[index + 1] ++; } } static -void insert_id_elem( - ecs_world_t *world, - ecs_id_record_t *idr, - ecs_id_t wildcard, - ecs_id_record_t *widr) +void move_switch_columns( + ecs_table_t *dst_table, + int32_t dst_index, + ecs_table_t *src_table, + int32_t src_index, + int32_t count, + bool clear) { - ecs_assert(ecs_id_is_wildcard(wildcard), ECS_INTERNAL_ERROR, NULL); - if (!widr) { - widr = flecs_id_record_ensure(world, wildcard); + int32_t i_old = 0, src_column_count = src_table->sw_count; + int32_t i_new = 0, dst_column_count = dst_table->sw_count; + + if (!src_column_count && !dst_column_count) { + return; } - ecs_assert(widr != NULL, ECS_INTERNAL_ERROR, NULL); - if (ECS_PAIR_SECOND(wildcard) == EcsWildcard) { - ecs_assert(ECS_PAIR_FIRST(wildcard) != EcsWildcard, - ECS_INTERNAL_ERROR, NULL); - id_record_elem_insert(widr, idr, &idr->first); - } else { - ecs_assert(ECS_PAIR_FIRST(wildcard) == EcsWildcard, - ECS_INTERNAL_ERROR, NULL); - id_record_elem_insert(widr, idr, &idr->second); + ecs_switch_t *src_columns = src_table->data.sw_columns; + ecs_switch_t *dst_columns = dst_table->data.sw_columns; - if (idr->flags & EcsIdAcyclic) { - id_record_elem_insert(widr, idr, &idr->acyclic); - } - } -} + ecs_type_t dst_type = dst_table->type; + ecs_type_t src_type = src_table->type; -static -void remove_id_elem( - ecs_id_record_t *idr, - ecs_id_t wildcard) -{ - ecs_assert(ecs_id_is_wildcard(wildcard), ECS_INTERNAL_ERROR, NULL); + int32_t offset_new = dst_table->sw_offset; + int32_t offset_old = src_table->sw_offset; - if (ECS_PAIR_SECOND(wildcard) == EcsWildcard) { - ecs_assert(ECS_PAIR_FIRST(wildcard) != EcsWildcard, - ECS_INTERNAL_ERROR, NULL); - id_record_elem_remove(idr, &idr->first); - } else { - ecs_assert(ECS_PAIR_FIRST(wildcard) == EcsWildcard, - ECS_INTERNAL_ERROR, NULL); - id_record_elem_remove(idr, &idr->second); + ecs_id_t *dst_ids = dst_type.array; + ecs_id_t *src_ids = src_type.array; - if (idr->flags & EcsIdAcyclic) { - id_record_elem_remove(idr, &idr->acyclic); + for (; (i_new < dst_column_count) && (i_old < src_column_count);) { + ecs_entity_t dst_id = dst_ids[i_new + offset_new]; + ecs_entity_t src_id = src_ids[i_old + offset_old]; + + if (dst_id == src_id) { + ecs_switch_t *src_switch = &src_columns[i_old]; + ecs_switch_t *dst_switch = &dst_columns[i_new]; + + flecs_switch_ensure(dst_switch, dst_index + count); + + int i; + for (i = 0; i < count; i ++) { + uint64_t value = flecs_switch_get(src_switch, src_index + i); + flecs_switch_set(dst_switch, dst_index + i, value); + } + + if (clear) { + ecs_assert(count == flecs_switch_count(src_switch), + ECS_INTERNAL_ERROR, NULL); + flecs_switch_clear(src_switch); + } + } else if (dst_id > src_id) { + ecs_switch_t *src_switch = &src_columns[i_old]; + flecs_switch_clear(src_switch); } + + i_new += dst_id <= src_id; + i_old += dst_id >= src_id; } -} -static -ecs_id_t flecs_id_record_id( - ecs_id_t id) -{ - id = ecs_strip_generation(id); - if (ECS_IS_PAIR(id)) { - ecs_entity_t r = ECS_PAIR_FIRST(id); - ecs_entity_t o = ECS_PAIR_SECOND(id); - if (r == EcsAny) { - r = EcsWildcard; - } - if (o == EcsAny) { - o = EcsWildcard; + /* Clear remaining columns */ + if (clear) { + for (; (i_old < src_column_count); i_old ++) { + ecs_switch_t *src_switch = &src_columns[i_old]; + ecs_assert(count == flecs_switch_count(src_switch), + ECS_INTERNAL_ERROR, NULL); + flecs_switch_clear(src_switch); } - id = ecs_pair(r, o); } - return id; } static -ecs_id_record_t* flecs_id_record_new( - ecs_world_t *world, - ecs_id_t id) +void move_bitset_columns( + ecs_table_t *dst_table, + int32_t dst_index, + ecs_table_t *src_table, + int32_t src_index, + int32_t count, + bool clear) { - ecs_id_record_t *idr = ecs_os_calloc_t(ecs_id_record_t); - ecs_table_cache_init(&idr->cache); + int32_t i_old = 0, src_column_count = src_table->bs_count; + int32_t i_new = 0, dst_column_count = dst_table->bs_count; - idr->id = id; - idr->refcount = 1; + if (!src_column_count && !dst_column_count) { + return; + } - bool is_wildcard = ecs_id_is_wildcard(id); + ecs_bitset_t *src_columns = src_table->data.bs_columns; + ecs_bitset_t *dst_columns = dst_table->data.bs_columns; - ecs_entity_t rel = 0, obj = 0, role = id & ECS_ID_FLAGS_MASK; - if (ECS_HAS_ID_FLAG(id, PAIR)) { - rel = ecs_pair_first(world, id); - ecs_assert(rel != 0, ECS_INTERNAL_ERROR, NULL); + ecs_type_t dst_type = dst_table->type; + ecs_type_t src_type = src_table->type; - /* Relationship object can be 0, as tables without a ChildOf relationship are - * added to the (ChildOf, 0) id record */ - obj = ECS_PAIR_SECOND(id); - if (obj) { - obj = ecs_get_alive(world, obj); - ecs_assert(obj != 0, ECS_INTERNAL_ERROR, NULL); - } + int32_t offset_new = dst_table->bs_offset; + int32_t offset_old = src_table->bs_offset; - /* Check constraints */ - if (obj && !ecs_id_is_wildcard(obj)) { - ecs_entity_t oneof = flecs_get_oneof(world, rel); - ecs_check( !oneof || ecs_has_pair(world, obj, EcsChildOf, oneof), - ECS_CONSTRAINT_VIOLATED, NULL); - (void)oneof; - } + ecs_id_t *dst_ids = dst_type.array; + ecs_id_t *src_ids = src_type.array; - if (!is_wildcard && ECS_IS_PAIR(id) && (rel != EcsFlag)) { - /* Inherit flags from (relationship, *) record */ - ecs_id_record_t *idr_r = flecs_id_record_ensure( - world, ecs_pair(rel, EcsWildcard)); - idr->parent = idr_r; - idr->flags = idr_r->flags; + for (; (i_new < dst_column_count) && (i_old < src_column_count);) { + ecs_id_t dst_id = dst_ids[i_new + offset_new]; + ecs_id_t src_id = src_ids[i_old + offset_old]; - /* If pair is not a wildcard, append it to wildcard lists. These - * allow for quickly enumerating all relationships for an object, or all - * objecs for a relationship. */ - insert_id_elem(world, idr, ecs_pair(rel, EcsWildcard), idr_r); - insert_id_elem(world, idr, ecs_pair(EcsWildcard, obj), NULL); + if (dst_id == src_id) { + ecs_bitset_t *src_bs = &src_columns[i_old]; + ecs_bitset_t *dst_bs = &dst_columns[i_new]; - if (rel == EcsUnion) { - idr->flags |= EcsIdUnion; + flecs_bitset_ensure(dst_bs, dst_index + count); + + int i; + for (i = 0; i < count; i ++) { + uint64_t value = flecs_bitset_get(src_bs, src_index + i); + flecs_bitset_set(dst_bs, dst_index + i, value); } - } - } else { - rel = id & ECS_COMPONENT_MASK; - rel = ecs_get_alive(world, rel); - ecs_assert(rel != 0, ECS_INTERNAL_ERROR, NULL); - } - /* Initialize type info if id is not a tag */ - if (!is_wildcard && (!role || ECS_IS_PAIR(id))) { - if (!(idr->flags & EcsIdTag)) { - const ecs_type_info_t *ti = flecs_type_info_get(world, rel); - if (!ti && obj) { - ti = flecs_type_info_get(world, obj); + if (clear) { + ecs_assert(count == flecs_bitset_count(src_bs), + ECS_INTERNAL_ERROR, NULL); + flecs_bitset_fini(src_bs); } - idr->type_info = ti; + } else if (dst_id > src_id) { + ecs_bitset_t *src_bs = &src_columns[i_old]; + flecs_bitset_fini(src_bs); } - } - /* Mark entities that are used as component/pair ids. When a tracked - * entity is deleted, cleanup policies are applied so that the store - * won't contain any tables with deleted ids. */ + i_new += dst_id <= src_id; + i_old += dst_id >= src_id; + } - /* Flag for OnDelete policies */ - flecs_add_flag(world, rel, EcsEntityObservedId); - if (obj) { - /* Flag for OnDeleteTarget policies */ - flecs_add_flag(world, obj, EcsEntityObservedTarget); - if (ecs_has_id(world, rel, EcsAcyclic)) { - /* Flag used to determine if object should be traversed when - * propagating events or with super/subset queries */ - flecs_add_flag(world, obj, EcsEntityObservedAcyclic); + /* Clear remaining columns */ + if (clear) { + for (; (i_old < src_column_count); i_old ++) { + ecs_bitset_t *src_bs = &src_columns[i_old]; + ecs_assert(count == flecs_bitset_count(src_bs), + ECS_INTERNAL_ERROR, NULL); + flecs_bitset_fini(src_bs); } } +} - /* Flags for events */ - if (flecs_check_observers_for_event(world, id, EcsOnAdd)) { - idr->flags |= EcsIdHasOnAdd; - } - if (flecs_check_observers_for_event(world, id, EcsOnRemove)) { - idr->flags |= EcsIdHasOnRemove; - } - if (flecs_check_observers_for_event(world, id, EcsOnSet)) { - idr->flags |= EcsIdHasOnSet; - } - if (flecs_check_observers_for_event(world, id, EcsUnSet)) { - idr->flags |= EcsIdHasUnSet; - } +static +void* grow_column( + ecs_column_t *column, + ecs_type_info_t *ti, + int32_t to_add, + int32_t dst_size, + bool construct) +{ + ecs_assert(column != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); - if (ecs_should_log_1()) { - char *id_str = ecs_id_str(world, id); - ecs_dbg_1("#[green]id#[normal] %s #[green]created", id_str); - ecs_os_free(id_str); - } + int32_t size = ti->size; + int32_t count = column->count; + int32_t src_size = column->size; + int32_t dst_count = count + to_add; + bool can_realloc = dst_size != src_size; + void *result = NULL; - /* Update counters */ - world->info.id_create_total ++; + ecs_assert(dst_size >= dst_count, ECS_INTERNAL_ERROR, NULL); - if (!is_wildcard) { - world->info.id_count ++; + /* If the array could possibly realloc and the component has a move action + * defined, move old elements manually */ + ecs_move_t move_ctor; + if (count && can_realloc && (move_ctor = ti->hooks.ctor_move_dtor)) { + ecs_xtor_t ctor = ti->hooks.ctor; + ecs_assert(ctor != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(move_ctor != NULL, ECS_INTERNAL_ERROR, NULL); - if (idr->type_info) { - world->info.component_id_count ++; - } else { - world->info.tag_id_count ++; - } + /* Create vector */ + ecs_column_t dst; + ecs_storage_init(&dst, size, dst_size); + dst.count = dst_count; - if (ECS_IS_PAIR(id)) { - world->info.pair_id_count ++; + void *src_buffer = column->array; + void *dst_buffer = dst.array; + + /* Move (and construct) existing elements to new vector */ + move_ctor(dst_buffer, src_buffer, count, ti); + + if (construct) { + /* Construct new element(s) */ + result = ECS_ELEM(dst_buffer, size, count); + ctor(result, to_add, ti); } + + /* Free old vector */ + ecs_storage_fini(column); + + *column = dst; } else { - world->info.wildcard_id_count ++; + /* If array won't realloc or has no move, simply add new elements */ + if (can_realloc) { + ecs_storage_set_size(column, size, dst_size); + } + + result = ecs_storage_grow(column, size, to_add); + + ecs_xtor_t ctor; + if (construct && (ctor = ti->hooks.ctor)) { + /* If new elements need to be constructed and component has a + * constructor, construct */ + ctor(result, to_add, ti); + } } - return idr; -error: - return NULL; -} + ecs_assert(column->size == dst_size, ECS_INTERNAL_ERROR, NULL); -static -void flecs_id_record_assert_empty( - ecs_id_record_t *idr) -{ - (void)idr; - ecs_assert(flecs_table_cache_count(&idr->cache) == 0, - ECS_INTERNAL_ERROR, NULL); - ecs_assert(flecs_table_cache_empty_count(&idr->cache) == 0, - ECS_INTERNAL_ERROR, NULL); + return result; } static -void flecs_id_record_free( +int32_t grow_data( ecs_world_t *world, - ecs_id_record_t *idr) + ecs_table_t *table, + ecs_data_t *data, + int32_t to_add, + int32_t size, + const ecs_entity_t *ids) { - ecs_poly_assert(world, ecs_world_t); - ecs_assert(idr != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_id_t id = idr->id; + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(data != NULL, ECS_INTERNAL_ERROR, NULL); - flecs_id_record_assert_empty(idr); + int32_t cur_count = flecs_table_data_count(data); + int32_t column_count = table->storage_count; + int32_t sw_count = table->sw_count; + int32_t bs_count = table->bs_count; + ecs_column_t *columns = data->columns; + ecs_switch_t *sw_columns = data->sw_columns; + ecs_bitset_t *bs_columns = data->bs_columns; - if (ECS_IS_PAIR(id)) { - ecs_entity_t rel = ecs_pair_first(world, id); - ecs_entity_t obj = ECS_PAIR_SECOND(id); - if (!ecs_id_is_wildcard(id)) { - if (ECS_PAIR_FIRST(id) != EcsFlag) { - /* If id is not a wildcard, remove it from the wildcard lists */ - remove_id_elem(idr, ecs_pair(rel, EcsWildcard)); - remove_id_elem(idr, ecs_pair(EcsWildcard, obj)); - } - } else { - ecs_log_push_2(); + /* Add record to record ptr array */ + ecs_storage_set_size_t(&data->records, ecs_record_t*, size); + ecs_record_t **r = ecs_storage_last_t(&data->records, ecs_record_t*) + 1; + data->records.count += to_add; + if (data->records.size > size) { + size = data->records.size; + } - /* If id is a wildcard, it means that all id records that match the - * wildcard are also empty, so release them */ - if (ECS_PAIR_FIRST(id) == EcsWildcard) { - /* Iterate (*, Target) list */ - ecs_id_record_t *cur, *next = idr->second.next; - while ((cur = next)) { - flecs_id_record_assert_empty(cur); - next = cur->second.next; - flecs_id_record_release(world, cur); - } - } else { - /* Iterate (Relationship, *) list */ - ecs_assert(ECS_PAIR_SECOND(id) == EcsWildcard, - ECS_INTERNAL_ERROR, NULL); - ecs_id_record_t *cur, *next = idr->first.next; - while ((cur = next)) { - flecs_id_record_assert_empty(cur); - next = cur->first.next; - flecs_id_record_release(world, cur); - } - } + /* Add entity to column with entity ids */ + ecs_storage_set_size_t(&data->entities, ecs_entity_t, size); + ecs_entity_t *e = ecs_storage_last_t(&data->entities, ecs_entity_t) + 1; + data->entities.count += to_add; + ecs_assert(data->entities.size == size, ECS_INTERNAL_ERROR, NULL); - ecs_log_pop_2(); + /* 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); - /* Update counters */ - world->info.id_delete_total ++; - - if (!ecs_id_is_wildcard(id)) { - world->info.id_count --; + /* Add elements to each column array */ + ecs_type_info_t **type_info = table->type_info; + for (i = 0; i < column_count; i ++) { + ecs_column_t *column = &columns[i]; + ecs_type_info_t *ti = type_info[i]; + grow_column(column, ti, to_add, size, true); + ecs_assert(columns[i].size == size, ECS_INTERNAL_ERROR, NULL); + } - if (ECS_IS_PAIR(id)) { - world->info.pair_id_count --; - } + /* Add elements to each switch column */ + for (i = 0; i < sw_count; i ++) { + ecs_switch_t *sw = &sw_columns[i]; + flecs_switch_addn(sw, to_add); + } - if (idr->type_info) { - world->info.component_id_count --; - } else { - world->info.tag_id_count --; - } - } else { - world->info.wildcard_id_count --; + /* Add elements to each bitset column */ + for (i = 0; i < bs_count; i ++) { + ecs_bitset_t *bs = &bs_columns[i]; + flecs_bitset_addn(bs, to_add); } - /* Unregister the id record from the world */ - ecs_map_remove(&world->id_index, flecs_id_record_id(id)); + /* If the table is monitored indicate that there has been a change */ + mark_table_dirty(world, table, 0); - /* Free resources */ - ecs_table_cache_fini(&idr->cache); - flecs_name_index_free(idr->name_index); - ecs_os_free(idr); + if (!(world->flags & EcsWorldReadonly) && !cur_count) { + flecs_table_set_empty(world, table); + } - if (ecs_should_log_1()) { - char *id_str = ecs_id_str(world, id); - ecs_dbg_1("#[green]id#[normal] %s #[red]deleted", id_str); - ecs_os_free(id_str); + /* Return index of first added entity */ + return cur_count; +} + +static +void fast_append( + ecs_type_info_t **type_info, + ecs_column_t *columns, + int32_t count) +{ + /* Add elements to each column array */ + int32_t i; + for (i = 0; i < count; i ++) { + ecs_type_info_t *ti = type_info[i]; + ecs_column_t *column = &columns[i]; + ecs_storage_append(column, ti->size); } } -ecs_id_record_t* flecs_id_record_ensure( +int32_t flecs_table_append( ecs_world_t *world, - ecs_id_t id) + ecs_table_t *table, + ecs_entity_t entity, + ecs_record_t *record, + bool construct, + bool on_add) { - ecs_poly_assert(world, ecs_world_t); + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(!table->lock, ECS_LOCKED_STORAGE, NULL); - ecs_id_record_t **idr_ptr = ecs_map_ensure(&world->id_index, - ecs_id_record_t*, ecs_strip_generation(id)); - ecs_id_record_t *idr = idr_ptr[0]; - if (!idr) { - idr = flecs_id_record_new(world, id); - idr_ptr = ecs_map_get(&world->id_index, - ecs_id_record_t*, flecs_id_record_id(id)); - ecs_assert(idr_ptr != NULL, ECS_INTERNAL_ERROR, NULL); - idr_ptr[0] = idr; + check_table_sanity(table); + + /* Get count & size before growing entities array. This tells us whether the + * arrays will realloc */ + ecs_data_t *data = &table->data; + int32_t count = data->entities.count; + int32_t column_count = table->storage_count; + ecs_column_t *columns = table->data.columns; + + /* Grow buffer with entity ids, set new element to new entity */ + ecs_entity_t *e = ecs_storage_append_t(&data->entities, ecs_entity_t); + ecs_assert(e != NULL, ECS_INTERNAL_ERROR, NULL); + *e = entity; + + /* Add record ptr to array with record ptrs */ + ecs_record_t **r = ecs_storage_append_t(&data->records, 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(world, table, 0); + ecs_assert(count >= 0, ECS_INTERNAL_ERROR, NULL); + + ecs_type_info_t **type_info = table->type_info; + + /* Fast path: no switch columns, no lifecycle actions */ + if (!(table->flags & EcsTableIsComplex)) { + fast_append(type_info, columns, column_count); + if (!count) { + flecs_table_set_empty(world, table); /* See below */ + } + return count; } - return idr; -} + int32_t sw_count = table->sw_count; + int32_t bs_count = table->bs_count; + ecs_switch_t *sw_columns = data->sw_columns; + ecs_bitset_t *bs_columns = data->bs_columns; + ecs_entity_t *entities = data->entities.array; -ecs_id_record_t* flecs_id_record_get( - const ecs_world_t *world, - ecs_id_t id) -{ - ecs_poly_assert(world, ecs_world_t); - return ecs_map_get_ptr(&world->id_index, ecs_id_record_t*, - flecs_id_record_id(id)); -} + /* Reobtain size to ensure that the columns have the same size as the + * entities and record vectors. This keeps reasoning about when allocations + * occur easier. */ + int32_t size = data->entities.size; -ecs_id_record_t* flecs_query_id_record_get( - const ecs_world_t *world, - ecs_id_t id) -{ - ecs_id_record_t *idr = flecs_id_record_get(world, id); - if (!idr) { - if (ECS_IS_PAIR(id)) { - idr = flecs_id_record_get(world, - ecs_pair(EcsUnion, ECS_PAIR_FIRST(id))); + /* Grow component arrays with 1 element */ + int32_t i; + for (i = 0; i < column_count; i ++) { + ecs_column_t *column = &columns[i]; + ecs_type_info_t *ti = type_info[i]; + grow_column(column, ti, 1, size, construct); + + ecs_iter_action_t on_add_hook; + if (on_add && (on_add_hook = ti->hooks.on_add)) { + on_component_callback(world, table, on_add_hook, EcsOnAdd, column, + &entities[count], table->storage_ids[i], count, 1, ti); } - return idr; + + ecs_assert(columns[i].size == + data->entities.size, ECS_INTERNAL_ERROR, NULL); + ecs_assert(columns[i].count == + data->entities.count, ECS_INTERNAL_ERROR, NULL); } - if (ECS_IS_PAIR(id) && - ECS_PAIR_SECOND(id) == EcsWildcard && - (idr->flags & EcsIdUnion)) - { - idr = flecs_id_record_get(world, - ecs_pair(EcsUnion, ECS_PAIR_FIRST(id))); + + /* Add element to each switch column */ + for (i = 0; i < sw_count; i ++) { + ecs_assert(sw_columns != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_switch_t *sw = &sw_columns[i]; + flecs_switch_add(sw); } - return idr; + /* Add element to each bitset column */ + for (i = 0; i < bs_count; i ++) { + ecs_assert(bs_columns != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_bitset_t *bs = &bs_columns[i]; + flecs_bitset_addn(bs, 1); + } + + /* If this is the first entity in this table, signal queries so that the + * table moves from an inactive table to an active table. */ + if (!count) { + flecs_table_set_empty(world, table); + } + + check_table_sanity(table); + + return count; } -void flecs_id_record_claim( - ecs_world_t *world, - ecs_id_record_t *idr) +static +void fast_delete_last( + ecs_column_t *columns, + int32_t column_count) { - (void)world; - idr->refcount ++; + int i; + for (i = 0; i < column_count; i ++) { + ecs_storage_remove_last(&columns[i]); + } } -int32_t flecs_id_record_release( - ecs_world_t *world, - ecs_id_record_t *idr) +static +void fast_delete( + ecs_type_info_t **type_info, + ecs_column_t *columns, + int32_t column_count, + int32_t index) { - int32_t rc = -- idr->refcount; - ecs_assert(rc >= 0, ECS_INTERNAL_ERROR, NULL); - - if (!rc) { - flecs_id_record_free(world, idr); + int i; + for (i = 0; i < column_count; i ++) { + ecs_type_info_t *ti = type_info[i]; + ecs_column_t *column = &columns[i]; + ecs_storage_remove(column, ti->size, index); } - - return rc; } -void flecs_id_record_release_tables( +void flecs_table_delete( ecs_world_t *world, - ecs_id_record_t *idr) + ecs_table_t *table, + int32_t index, + bool destruct) { - /* Cache should not contain tables that aren't empty */ - ecs_assert(flecs_table_cache_count(&idr->cache) == 0, - ECS_INTERNAL_ERROR, NULL); + ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(!table->lock, ECS_LOCKED_STORAGE, NULL); - ecs_table_cache_iter_t it; - if (flecs_table_cache_empty_iter(&idr->cache, &it)) { - ecs_table_record_t *tr; - while ((tr = flecs_table_cache_next(&it, ecs_table_record_t))) { - /* Tables can hold claims on each other, so releasing a table can - * cause the next element to get invalidated. Claim the next table - * so that we can safely iterate. */ - ecs_table_t *next = NULL; - if (it.next) { - next = it.next->table; - flecs_table_claim(world, next); - } + check_table_sanity(table); - /* Release current table */ - flecs_table_release(world, tr->hdr.table); + ecs_data_t *data = &table->data; + int32_t count = data->entities.count; - /* Check if the only thing keeping the next table alive is our - * claim. If so, move to the next record before releasing */ - if (next) { - if (next->refcount == 1) { - it.next = it.next->next; - } + ecs_assert(count > 0, ECS_INTERNAL_ERROR, NULL); + count --; + ecs_assert(index <= count, ECS_INTERNAL_ERROR, NULL); - flecs_table_release(world, next); - } - } - } -} + /* Move last entity id to index */ + ecs_entity_t *entities = data->entities.array; + ecs_entity_t entity_to_move = entities[count]; + ecs_entity_t entity_to_delete = entities[index]; + entities[index] = entity_to_move; + ecs_storage_remove_last(&data->entities); -bool flecs_id_record_set_type_info( - ecs_world_t *world, - ecs_id_record_t *idr, - const ecs_type_info_t *ti) -{ - if (!ecs_id_is_wildcard(idr->id)) { - if (ti) { - if (!idr->type_info) { - world->info.tag_id_count --; - world->info.component_id_count ++; - } - } else { - if (idr->type_info) { - world->info.tag_id_count ++; - world->info.component_id_count --; - } - } - } + /* Move last record ptr to index */ + ecs_assert(count < data->records.count, ECS_INTERNAL_ERROR, NULL); - bool changed = idr->type_info != ti; - idr->type_info = ti; - return changed; -} + ecs_record_t **records = data->records.array; + ecs_record_t *record_to_move = records[count]; + records[index] = record_to_move; + ecs_storage_remove_last(&data->records); -ecs_hashmap_t* flecs_id_name_index_ensure( - ecs_world_t *world, - ecs_id_t id) -{ - ecs_poly_assert(world, ecs_world_t); + /* 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_id_record_t *idr = flecs_id_record_get(world, id); - ecs_assert(idr != NULL, ECS_INTERNAL_ERROR, NULL); + /* If the table is monitored indicate that there has been a change */ + mark_table_dirty(world, table, 0); - ecs_hashmap_t *map = idr->name_index; - if (!map) { - map = idr->name_index = flecs_name_index_new(); + /* If table is empty, deactivate it */ + if (!count) { + flecs_table_set_empty(world, table); } - return map; -} + /* Destruct component data */ + ecs_type_info_t **type_info = table->type_info; + ecs_column_t *columns = data->columns; + int32_t column_count = table->storage_count; + int32_t i; -ecs_hashmap_t* flecs_id_name_index_get( - const ecs_world_t *world, - ecs_id_t id) -{ - ecs_poly_assert(world, ecs_world_t); + /* 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(type_info, columns, column_count, index); + } - ecs_id_record_t *idr = flecs_id_record_get(world, id); - if (!idr) { - return NULL; + check_table_sanity(table); + return; } - return idr->name_index; -} + ecs_id_t *ids = table->storage_ids; -ecs_table_record_t* flecs_table_record_get( - const ecs_world_t *world, - const ecs_table_t *table, - ecs_id_t id) -{ - ecs_poly_assert(world, ecs_world_t); + /* Last element, destruct & remove */ + if (index == count) { + /* If table has component destructors, invoke */ + if (destruct && (table->flags & EcsTableHasDtors)) { + for (i = 0; i < column_count; i ++) { + remove_component(world, table, type_info[i], &columns[i], + &entity_to_delete, ids[i], index, 1); + } + } - ecs_id_record_t* idr = flecs_id_record_get(world, id); - if (!idr) { - return NULL; - } + fast_delete_last(columns, column_count); - return (ecs_table_record_t*)ecs_table_cache_get(&idr->cache, table); -} + /* Not last element, move last element to deleted element & destruct */ + } else { + /* If table has component destructors, invoke */ + if (destruct && (table->flags & (EcsTableHasDtors | EcsTableHasMove))) { + for (i = 0; i < column_count; i ++) { + ecs_column_t *column = &columns[i]; + ecs_type_info_t *ti = type_info[i]; + ecs_size_t size = ti->size; + void *dst = ecs_storage_get(column, size, index); + void *src = ecs_storage_last(column, size); + + ecs_iter_action_t on_remove = ti->hooks.on_remove; + if (on_remove) { + on_component_callback(world, table, on_remove, EcsOnRemove, + column, &entity_to_delete, ids[i], index, 1, ti); + } -const ecs_table_record_t* flecs_id_record_get_table( - const ecs_id_record_t *idr, - const ecs_table_t *table) -{ - if (!idr) { - return NULL; + ecs_move_t move_dtor = ti->hooks.move_dtor; + if (move_dtor) { + move_dtor(dst, src, 1, ti); + } else { + ecs_os_memcpy(dst, src, size); + } + + ecs_storage_remove_last(column); + } + } else { + fast_delete(type_info, columns, column_count, index); + } } - return (ecs_table_record_t*)ecs_table_cache_get(&idr->cache, table); -} -void flecs_fini_id_records( - 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_ptr(&it, ecs_id_record_t*, NULL))) { - flecs_id_record_release(world, idr); + /* Remove elements from switch columns */ + ecs_switch_t *sw_columns = data->sw_columns; + int32_t sw_count = table->sw_count; + for (i = 0; i < sw_count; i ++) { + flecs_switch_remove(&sw_columns[i], index); } - ecs_assert(ecs_map_count(&world->id_index) == 0, ECS_INTERNAL_ERROR, NULL); + /* Remove elements from bitset columns */ + ecs_bitset_t *bs_columns = data->bs_columns; + int32_t bs_count = table->bs_count; + for (i = 0; i < bs_count; i ++) { + flecs_bitset_remove(&bs_columns[i], index); + } - ecs_map_fini(&world->id_index); - flecs_sparse_free(world->pending_tables); - flecs_sparse_free(world->pending_buffer); + check_table_sanity(table); } -#include - static -void flecs_notify_on_add( - ecs_world_t *world, - ecs_table_t *table, - ecs_table_t *other_table, - int32_t row, - int32_t count, - ecs_table_diff_t *diff, - bool run_on_set); +void fast_move( + ecs_table_t *dst_table, + int32_t dst_index, + ecs_table_t *src_table, + int32_t src_index) +{ + int32_t i_new = 0, dst_column_count = dst_table->storage_count; + int32_t i_old = 0, src_column_count = src_table->storage_count; + ecs_id_t *dst_ids = dst_table->storage_ids; + ecs_id_t *src_ids = src_table->storage_ids; -static -const ecs_entity_t* new_w_data( - ecs_world_t *world, - ecs_table_t *table, - const ecs_entity_t *entities, - ecs_type_t *component_ids, - int32_t count, - void **c_info, - bool move, - int32_t *row_out, - ecs_table_diff_t *diff); + ecs_column_t *src_columns = src_table->data.columns; + ecs_column_t *dst_columns = dst_table->data.columns; -static -void* get_component_w_index( - ecs_table_t *table, - int32_t column_index, - int32_t row) -{ - ecs_check(column_index < table->storage_count, ECS_NOT_A_COMPONENT, NULL); - ecs_type_info_t *ti = table->type_info[column_index]; - ecs_column_t *column = &table->data.columns[column_index]; - return ecs_storage_get(column, ti->size, row);; -error: - return NULL; -} + ecs_type_info_t **dst_type_info = dst_table->type_info; -static -void* get_component( - const ecs_world_t *world, - ecs_table_t *table, - int32_t row, - ecs_id_t id) -{ - ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(id != 0, ECS_INVALID_PARAMETER, NULL); + for (; (i_new < dst_column_count) && (i_old < src_column_count);) { + ecs_id_t dst_id = dst_ids[i_new]; + ecs_id_t src_id = src_ids[i_old]; - if (!table->storage_table) { - ecs_check(ecs_search(world, table, id, 0) == -1, - ECS_NOT_A_COMPONENT, NULL); - return NULL; - } + if (dst_id == src_id) { + ecs_column_t *dst_column = &dst_columns[i_new]; + ecs_column_t *src_column = &src_columns[i_old]; + ecs_type_info_t *ti = dst_type_info[i_new]; + int32_t size = ti->size; + void *dst = ecs_storage_get(dst_column, size, dst_index); + void *src = ecs_storage_get(src_column, size, src_index); + ecs_os_memcpy(dst, src, size); + } - ecs_table_record_t *tr = flecs_table_record_get( - world, table->storage_table, id); - if (!tr) { - ecs_check(ecs_search(world, table, id, 0) == -1, - ECS_NOT_A_COMPONENT, NULL); - return NULL; + i_new += dst_id <= src_id; + i_old += dst_id >= src_id; } - - return get_component_w_index(table, tr->column, row); -error: - return NULL; } -static -void* get_base_component( - const ecs_world_t *world, - ecs_table_t *table, - ecs_id_t id, - ecs_id_record_t *table_index, - int32_t recur_depth) +void flecs_table_move( + ecs_world_t *world, + ecs_entity_t dst_entity, + ecs_entity_t src_entity, + ecs_table_t *dst_table, + int32_t dst_index, + ecs_table_t *src_table, + int32_t src_index, + bool construct) { - /* Cycle detected in IsA relationship */ - ecs_check(recur_depth < ECS_MAX_RECURSION, ECS_INVALID_PARAMETER, NULL); + ecs_assert(dst_table != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(src_table != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(!dst_table->lock, ECS_LOCKED_STORAGE, NULL); + ecs_assert(!src_table->lock, ECS_LOCKED_STORAGE, NULL); - /* Table (and thus entity) does not have component, look for base */ - if (!(table->flags & EcsTableHasIsA)) { - return NULL; - } + ecs_assert(src_index >= 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(dst_index >= 0, ECS_INTERNAL_ERROR, NULL); - /* Exclude Name */ - if (id == ecs_pair(ecs_id(EcsIdentifier), EcsName)) { - return NULL; + check_table_sanity(dst_table); + check_table_sanity(src_table); + + if (!((dst_table->flags | src_table->flags) & EcsTableIsComplex)) { + fast_move(dst_table, dst_index, src_table, src_index); + check_table_sanity(dst_table); + check_table_sanity(src_table); + return; } - /* Table should always be in the table index for (IsA, *), otherwise the - * HasBase flag should not have been set */ - const ecs_table_record_t *tr_isa = flecs_id_record_get_table( - world->idr_isa_wildcard, table); - ecs_check(tr_isa != NULL, ECS_INTERNAL_ERROR, NULL); + move_switch_columns(dst_table, dst_index, src_table, src_index, 1, false); + move_bitset_columns(dst_table, dst_index, src_table, src_index, 1, false); - ecs_type_t type = table->type; - ecs_id_t *ids = type.array; - int32_t i = tr_isa->column, end = tr_isa->count + tr_isa->column; - void *ptr = NULL; + bool same_entity = dst_entity == src_entity; - do { - ecs_id_t pair = ids[i ++]; - ecs_entity_t base = ecs_pair_second(world, pair); + ecs_type_info_t **dst_type_info = dst_table->type_info; + ecs_type_info_t **src_type_info = src_table->type_info; - ecs_record_t *r = flecs_entities_get(world, base); - if (!r) { - continue; - } + int32_t i_new = 0, dst_column_count = dst_table->storage_count; + int32_t i_old = 0, src_column_count = src_table->storage_count; + ecs_id_t *dst_ids = dst_table->storage_ids; + ecs_id_t *src_ids = src_table->storage_ids; - table = r->table; - if (!table) { - continue; - } + ecs_column_t *src_columns = src_table->data.columns; + ecs_column_t *dst_columns = dst_table->data.columns; - const ecs_table_record_t *tr = NULL; + for (; (i_new < dst_column_count) && (i_old < src_column_count);) { + ecs_id_t dst_id = dst_ids[i_new]; + ecs_id_t src_id = src_ids[i_old]; - ecs_table_t *storage_table = table->storage_table; - if (storage_table) { - tr = flecs_id_record_get_table(table_index, storage_table); - } else { - ecs_check(!ecs_owns_id(world, base, id), - ECS_NOT_A_COMPONENT, NULL); - } + if (dst_id == src_id) { + ecs_column_t *dst_column = &dst_columns[i_new]; + ecs_column_t *src_column = &src_columns[i_old]; + ecs_type_info_t *ti = dst_type_info[i_new]; + int32_t size = ti->size; - if (!tr) { - ptr = get_base_component(world, table, id, table_index, - recur_depth + 1); + ecs_assert(size != 0, ECS_INTERNAL_ERROR, NULL); + void *dst = ecs_storage_get(dst_column, size, dst_index); + void *src = ecs_storage_get(src_column, size, src_index); + + if (same_entity) { + ecs_move_t move = ti->hooks.ctor_move_dtor; + if (move) { + /* ctor + move + dtor */ + move(dst, src, 1, ti); + } else { + ecs_os_memcpy(dst, src, size); + } + } else { + ecs_copy_t copy = ti->hooks.copy_ctor; + if (copy) { + copy(dst, src, 1, ti); + } else { + ecs_os_memcpy(dst, src, size); + } + } } else { - int32_t row = ECS_RECORD_TO_ROW(r->row); - ptr = get_component_w_index(table, tr->column, row); + if (dst_id < src_id) { + add_component(world, dst_table, dst_type_info[i_new], + &dst_columns[i_new], &dst_entity, dst_id, + dst_index, 1, construct); + } else { + remove_component(world, src_table, src_type_info[i_old], + &src_columns[i_old], &src_entity, src_id, + src_index, 1); + } } - } while (!ptr && (i < end)); - return ptr; -error: - return NULL; + i_new += dst_id <= src_id; + i_old += dst_id >= src_id; + } + + for (; (i_new < dst_column_count); i_new ++) { + add_component(world, dst_table, dst_type_info[i_new], + &dst_columns[i_new], &dst_entity, dst_ids[i_new], dst_index, 1, + construct); + } + + for (; (i_old < src_column_count); i_old ++) { + remove_component(world, src_table, src_type_info[i_old], + &src_columns[i_old], &src_entity, src_ids[i_old], + src_index, 1); + } + + check_table_sanity(dst_table); + check_table_sanity(src_table); } -static -const ecs_type_info_t *get_c_info( +int32_t flecs_table_appendn( ecs_world_t *world, - ecs_entity_t component) + ecs_table_t *table, + ecs_data_t *data, + int32_t to_add, + const ecs_entity_t *ids) { - ecs_entity_t real_id = ecs_get_typeid(world, component); - if (real_id) { - return flecs_type_info_get(world, real_id); - } else { - return NULL; - } + ecs_assert(!table->lock, ECS_LOCKED_STORAGE, NULL); + + check_table_sanity(table); + + int32_t cur_count = flecs_table_data_count(data); + int32_t result = grow_data( + world, table, data, to_add, cur_count + to_add, ids); + check_table_sanity(table); + return result; } -/* Utilities for creating a diff struct on the fly between two arbitrary tables. - * This is temporary code that will eventually be replaced by a cache that - * stores the diff between two archetypes. */ +void flecs_table_set_size( + ecs_world_t *world, + ecs_table_t *table, + ecs_data_t *data, + int32_t size) +{ + ecs_assert(table != NULL, ECS_LOCKED_STORAGE, NULL); + ecs_assert(!table->lock, ECS_LOCKED_STORAGE, NULL); -typedef struct { - ecs_type_t type; - ecs_size_t size; -} ecs_type_buffer_t; + check_table_sanity(table); -typedef struct { - ecs_type_buffer_t added; - ecs_type_buffer_t removed; - ecs_type_buffer_t on_set; - ecs_type_buffer_t un_set; -} ecs_diff_buffer_t; + int32_t cur_count = flecs_table_data_count(data); -static -void ids_merge( - ecs_type_buffer_t *ids, - ecs_type_t *add) -{ - if (!add || !add->count) { - return; + if (cur_count < size) { + grow_data(world, table, data, 0, size, NULL); + check_table_sanity(table); } - - int32_t new_count = ids->type.count + add->count; - if (new_count > ids->size) { - ids->size = flecs_next_pow_of_2(new_count); - ecs_id_t *arr = ecs_os_malloc_n(ecs_id_t, ids->size); - ecs_os_memcpy_n(arr, ids->type.array, ecs_id_t, ids->type.count); +} - if (ids->type.count > ECS_ID_CACHE_SIZE) { - ecs_os_free(ids->type.array); - } - - ids->type.array = arr; +bool flecs_table_shrink( + ecs_world_t *world, + ecs_table_t *table) +{ + ecs_assert(table != NULL, ECS_LOCKED_STORAGE, NULL); + ecs_assert(!table->lock, ECS_LOCKED_STORAGE, NULL); + (void)world; + + check_table_sanity(table); + + ecs_data_t *data = &table->data; + bool has_payload = data->entities.array != NULL; + ecs_storage_reclaim_t(&data->entities, ecs_entity_t); + ecs_storage_reclaim_t(&data->records, ecs_record_t*); + + int32_t i, count = table->storage_count; + ecs_type_info_t **type_info = table->type_info; + for (i = 0; i < count; i ++) { + ecs_column_t *column = &data->columns[i]; + ecs_type_info_t *ti = type_info[i]; + ecs_storage_reclaim(column, ti->size); } - ecs_os_memcpy_n(&ids->type.array[ids->type.count], - add->array, ecs_id_t, add->count); - ids->type.count += add->count; + return has_payload; } -#define ECS_DIFF_INIT {\ - .added = { .type = { .count = 0, .array = (ecs_id_t[ECS_ID_CACHE_SIZE]){0}}, .size = ECS_ID_CACHE_SIZE },\ - .removed = { .type = { .count = 0, .array = (ecs_id_t[ECS_ID_CACHE_SIZE]){0}}, .size = ECS_ID_CACHE_SIZE },\ - .on_set = { .type = { .count = 0, .array = (ecs_id_t[ECS_ID_CACHE_SIZE]){0}}, .size = ECS_ID_CACHE_SIZE },\ - .un_set = { .type = { .count = 0, .array = (ecs_id_t[ECS_ID_CACHE_SIZE]){0}}, .size = ECS_ID_CACHE_SIZE },\ +int32_t flecs_table_data_count( + const ecs_data_t *data) +{ + return data ? data->entities.count : 0; } static -void diff_append( - ecs_diff_buffer_t *dst, - ecs_table_diff_t *src) -{ - ids_merge(&dst->added, &src->added); - ids_merge(&dst->removed, &src->removed); - ids_merge(&dst->on_set, &src->on_set); - ids_merge(&dst->un_set, &src->un_set); -} - -static -void diff_free( - ecs_diff_buffer_t *diff) +void swap_switch_columns( + ecs_table_t *table, + ecs_data_t *data, + int32_t row_1, + int32_t row_2) { - if (diff->added.type.count > ECS_ID_CACHE_SIZE) { - ecs_os_free(diff->added.type.array); - } - if (diff->removed.type.count > ECS_ID_CACHE_SIZE) { - ecs_os_free(diff->removed.type.array); - } - if (diff->on_set.type.count > ECS_ID_CACHE_SIZE) { - ecs_os_free(diff->on_set.type.array); - } - if (diff->un_set.type.count > ECS_ID_CACHE_SIZE) { - ecs_os_free(diff->un_set.type.array); + int32_t i = 0, column_count = table->sw_count; + if (!column_count) { + return; } -} - -static -ecs_table_diff_t diff_to_table_diff( - ecs_diff_buffer_t *diff) -{ - return (ecs_table_diff_t){ - .added = diff->added.type, - .removed = diff->removed.type, - .on_set = diff->on_set.type, - .un_set = diff->un_set.type - }; -} -static -ecs_table_t* table_append( - ecs_world_t *world, - ecs_table_t *table, - ecs_id_t id, - ecs_diff_buffer_t *diff) -{ - ecs_table_diff_t temp_diff; - table = flecs_table_traverse_add(world, table, &id, &temp_diff); - ecs_check(table != NULL, ECS_INVALID_PARAMETER, NULL); - diff_append(diff, &temp_diff); - return table; -error: - return NULL; -} + ecs_switch_t *columns = data->sw_columns; -static -void flecs_notify( - ecs_world_t *world, - ecs_table_t *table, - ecs_table_t *other_table, - int32_t row, - int32_t count, - ecs_entity_t event, - ecs_type_t *ids, - ecs_entity_t relationship) -{ - flecs_emit(world, world, &(ecs_event_desc_t){ - .event = event, - .ids = ids, - .table = table, - .other_table = other_table, - .offset = row, - .count = count, - .observable = world, - .relationship = relationship - }); + for (i = 0; i < column_count; i ++) { + ecs_switch_t *sw = &columns[i]; + flecs_switch_swap(sw, row_1, row_2); + } } static -void flecs_instantiate( - ecs_world_t *world, - ecs_entity_t base, +void swap_bitset_columns( ecs_table_t *table, - int32_t row, - int32_t count); - -static -void flecs_instantiate_slot( - ecs_world_t *world, - ecs_entity_t base, - ecs_entity_t instance, - ecs_entity_t slot_of, - ecs_entity_t slot, - ecs_entity_t child) + ecs_data_t *data, + int32_t row_1, + int32_t row_2) { - if (base == slot_of) { - /* Instance inherits from slot_of, add slot to instance */ - ecs_add_pair(world, instance, slot, child); - } else { - /* Slot is registered for other prefab, travel hierarchy - * upwards to find instance that inherits from slot_of */ - ecs_entity_t parent = instance; - int32_t depth = 0; - do { - if (ecs_has_pair(world, parent, EcsIsA, slot_of)) { - const char *name = ecs_get_name(world, slot); - if (name == NULL) { - char *slot_of_str = ecs_get_fullpath(world, slot_of); - ecs_throw(ECS_INVALID_OPERATION, "prefab '%s' has unnamed " - "slot (slots must be named)", slot_of_str); - ecs_os_free(slot_of_str); - return; - } - - /* The 'slot' variable is currently pointing to a child (or - * grandchild) of the current base. Find the original slot by - * looking it up under the prefab it was registered. */ - if (depth == 0) { - /* If the current instance is an instance of slot_of, just - * lookup the slot by name, which is faster than having to - * create a relative path. */ - slot = ecs_lookup_child(world, slot_of, name); - } else { - /* If the slot is more than one level away from the slot_of - * parent, use a relative path to find the slot */ - char *path = ecs_get_path_w_sep(world, parent, child, ".", - NULL); - slot = ecs_lookup_path_w_sep(world, slot_of, path, ".", - NULL, false); - ecs_os_free(path); - } - - if (slot == 0) { - char *slot_of_str = ecs_get_fullpath(world, slot_of); - char *slot_str = ecs_get_fullpath(world, slot); - ecs_throw(ECS_INVALID_OPERATION, - "'%s' is not in hierarchy for slot '%s'", - slot_of_str, slot_str); - ecs_os_free(slot_of_str); - ecs_os_free(slot_str); - } + int32_t i = 0, column_count = table->bs_count; + if (!column_count) { + return; + } - ecs_add_pair(world, parent, slot, child); - break; - } + ecs_bitset_t *columns = data->bs_columns; - depth ++; - } while ((parent = ecs_get_target(world, parent, EcsChildOf, 0))); - - if (parent == 0) { - char *slot_of_str = ecs_get_fullpath(world, slot_of); - char *slot_str = ecs_get_fullpath(world, slot); - ecs_throw(ECS_INVALID_OPERATION, - "'%s' is not in hierarchy for slot '%s'", - slot_of_str, slot_str); - ecs_os_free(slot_of_str); - ecs_os_free(slot_str); - } + for (i = 0; i < column_count; i ++) { + ecs_bitset_t *bs = &columns[i]; + flecs_bitset_swap(bs, row_1, row_2); } - -error: - return; } -static -void flecs_instantiate_children( +void flecs_table_swap( ecs_world_t *world, - ecs_entity_t base, ecs_table_t *table, - int32_t row, - int32_t count, - ecs_table_t *child_table) -{ - if (!ecs_table_count(child_table)) { - return; - } - - ecs_type_t type = child_table->type; - ecs_data_t *child_data = &child_table->data; + int32_t row_1, + int32_t row_2) +{ + (void)world; - ecs_entity_t slot_of = 0; - ecs_entity_t *ids = type.array; - int32_t type_count = type.count; + ecs_assert(!table->lock, ECS_LOCKED_STORAGE, NULL); + ecs_assert(row_1 >= 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(row_2 >= 0, ECS_INTERNAL_ERROR, NULL); - /* Instantiate child table for each instance */ + check_table_sanity(table); + + if (row_1 == row_2) { + return; + } - /* Create component array for creating the table */ - ecs_type_t components = { - .array = ecs_os_alloca_n(ecs_entity_t, type_count + 1) - }; + /* If the table is monitored indicate that there has been a change */ + mark_table_dirty(world, table, 0); - void **component_data = ecs_os_alloca_n(void*, type_count + 1); + ecs_entity_t *entities = table->data.entities.array; + ecs_entity_t e1 = entities[row_1]; + ecs_entity_t e2 = entities[row_2]; - /* Copy in component identifiers. Find the base index in the component - * array, since we'll need this to replace the base with the instance id */ - int j, i, childof_base_index = -1, pos = 0; - for (i = 0; i < type_count; i ++) { - ecs_id_t id = ids[i]; + ecs_record_t **records = table->data.records.array; + ecs_record_t *record_ptr_1 = records[row_1]; + ecs_record_t *record_ptr_2 = records[row_2]; - /* If id has DontInherit flag don't inherit it, except for the name - * and ChildOf pairs. The name is preserved so applications can lookup - * the instantiated children by name. The ChildOf pair is replaced later - * with the instance parent. */ - if ((id != ecs_pair(ecs_id(EcsIdentifier), EcsName)) && - ECS_PAIR_FIRST(id) != EcsChildOf) - { - if (id == EcsUnion) { - /* This should eventually be handled by the DontInherit property - * but right now there is no way to selectively apply it to - * EcsUnion itself: it would also apply to (Union, *) pairs, - * which would make all union relationships uninheritable. - * - * The reason this is explicitly skipped is so that slot - * instances don't all end up with the Union property. */ - continue; - } - ecs_table_record_t *tr = &child_table->records[i]; - ecs_id_record_t *idr = (ecs_id_record_t*)tr->hdr.cache; - if (idr->flags & EcsIdDontInherit) { - continue; - } - } + ecs_assert(record_ptr_1 != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(record_ptr_2 != NULL, ECS_INTERNAL_ERROR, NULL); - /* If child is a slot, keep track of which parent to add it to, but - * don't add slot relationship to child of instance. If this is a child - * of a prefab, keep the SlotOf relationship intact. */ - if (!(table->flags & EcsTableIsPrefab)) { - if (ECS_IS_PAIR(id) && ECS_PAIR_FIRST(id) == EcsSlotOf) { - ecs_assert(slot_of == 0, ECS_INTERNAL_ERROR, NULL); - slot_of = ecs_pair_second(world, id); - continue; - } - } + /* 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); - /* Keep track of the element that creates the ChildOf relationship with - * the prefab parent. We need to replace this element to make sure the - * created children point to the instance and not the prefab */ - if (ECS_HAS_RELATION(id, EcsChildOf) && (ECS_PAIR_SECOND(id) == base)) { - childof_base_index = pos; - } + /* 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); + records[row_1] = record_ptr_2; + records[row_2] = record_ptr_1; - int32_t storage_index = ecs_table_type_to_storage_index(child_table, i); - if (storage_index != -1) { - ecs_column_t *column = &child_data->columns[storage_index]; - component_data[pos] = ecs_storage_first(column); - } else { - component_data[pos] = NULL; - } + swap_switch_columns(table, &table->data, row_1, row_2); + swap_bitset_columns(table, &table->data, row_1, row_2); - components.array[pos] = id; - pos ++; + ecs_column_t *columns = table->data.columns; + if (!columns) { + check_table_sanity(table); + return; } - /* Table must contain children of base */ - ecs_assert(childof_base_index != -1, ECS_INTERNAL_ERROR, NULL); + ecs_type_info_t **type_info = table->type_info; - /* If children are added to a prefab, make sure they are prefabs too */ - if (table->flags & EcsTableIsPrefab) { - components.array[pos] = EcsPrefab; - component_data[pos] = NULL; - pos ++; + /* Find the maximum size of column elements + * and allocate a temporary buffer for swapping */ + int32_t i, temp_buffer_size = ECS_SIZEOF(uint64_t), column_count = table->storage_count; + for (i = 0; i < column_count; i++) { + ecs_type_info_t* ti = type_info[i]; + temp_buffer_size = ECS_MAX(temp_buffer_size, ti->size); } - components.count = pos; + void* tmp = ecs_os_alloca(temp_buffer_size); - /* Instantiate the prefab child table for each new instance */ - ecs_entity_t *instances = ecs_storage_first(&table->data.entities); - int32_t child_count = ecs_storage_count(&child_data->entities); - bool has_union = child_table->flags & EcsTableHasUnion; + /* Swap columns */ + for (i = 0; i < column_count; i ++) { + ecs_type_info_t *ti = type_info[i]; + int32_t size = ti->size; + ecs_assert(size != 0, ECS_INTERNAL_ERROR, NULL); - for (i = row; i < count + row; i ++) { - ecs_entity_t instance = instances[i]; - ecs_diff_buffer_t diff = ECS_DIFF_INIT; - ecs_table_t *i_table = NULL; - - /* Replace ChildOf element in the component array with instance id */ - components.array[childof_base_index] = ecs_pair(EcsChildOf, instance); + void *ptr = columns[i].array; - /* Find or create table */ - for (j = 0; j < components.count; j ++) { - i_table = table_append(world, i_table, components.array[j], &diff); - } + void *el_1 = ECS_ELEM(ptr, size, row_1); + void *el_2 = ECS_ELEM(ptr, size, row_2); - ecs_assert(i_table != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(i_table->type.count == components.count, - ECS_INTERNAL_ERROR, NULL); + ecs_os_memcpy(tmp, el_1, size); + ecs_os_memcpy(el_1, el_2, size); + ecs_os_memcpy(el_2, tmp, size); + } - /* The instance is trying to instantiate from a base that is also - * its parent. This would cause the hierarchy to instantiate itself - * which would cause infinite recursion. */ - ecs_entity_t *children = ecs_storage_first(&child_data->entities); + check_table_sanity(table); +} -#ifdef FLECS_DEBUG - for (j = 0; j < child_count; j ++) { - ecs_entity_t child = children[j]; - ecs_check(child != instance, ECS_INVALID_PARAMETER, NULL); - } -#else - /* Bit of boilerplate to ensure that we don't get warnings about the - * error label not being used. */ - ecs_check(true, ECS_INVALID_OPERATION, NULL); -#endif +static +void flecs_merge_column( + ecs_column_t *dst, + ecs_column_t *src, + int32_t size, + ecs_type_info_t *ti) +{ + int32_t dst_count = dst->count; - /* Create children */ - int32_t child_row; - ecs_table_diff_t table_diff = diff_to_table_diff(&diff); - const ecs_entity_t *i_children = new_w_data(world, i_table, NULL, - &components, child_count, component_data, false, &child_row, - &table_diff); - diff_free(&diff); + if (!dst_count) { + ecs_storage_fini(dst); + *dst = *src; + src->array = NULL; + src->count = 0; + src->size = 0; + + /* If the new table is not empty, copy the contents from the + * src into the dst. */ + } else { + int32_t src_count = src->count; + ecs_storage_set_count(dst, size, dst_count + src_count); - /* If children have union relationships, initialize */ - if (has_union) { - int32_t u, u_count = child_table->sw_count; - for (u = 0; u < u_count; u ++) { - ecs_switch_t *src_sw = &child_table->data.sw_columns[i]; - ecs_switch_t *dst_sw = &i_table->data.sw_columns[i]; - ecs_vector_t *v_src_values = flecs_switch_values(src_sw); - ecs_vector_t *v_dst_values = flecs_switch_values(dst_sw); - uint64_t *src_values = ecs_vector_first(v_src_values, uint64_t); - uint64_t *dst_values = ecs_vector_first(v_dst_values, uint64_t); - for (j = 0; j < child_count; j ++) { - dst_values[j] = src_values[j]; - } - } + /* Construct new values */ + if (ti) { + ctor_component(ti, dst, dst_count, src_count); } - - /* If children are slots, add slot relationships to parent */ - if (slot_of) { - for (j = 0; j < child_count; j ++) { - ecs_entity_t child = children[j]; - ecs_entity_t i_child = i_children[j]; - flecs_instantiate_slot(world, base, instance, slot_of, - child, i_child); - } + + void *dst_ptr = ECS_ELEM(dst->array, size, dst_count); + void *src_ptr = src->array; + + /* Move values into column */ + ecs_move_t move = NULL; + if (ti) { + move = ti->hooks.move; } - - /* If prefab child table has children itself, recursively instantiate */ - for (j = 0; j < child_count; j ++) { - ecs_entity_t child = children[j]; - flecs_instantiate(world, child, i_table, child_row + j, 1); + if (move) { + move(dst_ptr, src_ptr, src_count, ti); + } else { + ecs_os_memcpy(dst_ptr, src_ptr, size * src_count); } - } -error: - return; + + ecs_storage_fini(src); + } } static -void flecs_instantiate( +void flecs_merge_table_data( ecs_world_t *world, - ecs_entity_t base, - ecs_table_t *table, - int32_t row, - int32_t count) + ecs_table_t *dst_table, + ecs_table_t *src_table, + int32_t src_count, + int32_t dst_count, + ecs_data_t *src_data, + ecs_data_t *dst_data) { - ecs_table_t *base_table = ecs_get_table(world, ecs_get_alive(world, base)); - if (!base_table || !(base_table->flags & EcsTableIsPrefab)) { - /* Don't instantiate children from base entities that aren't prefabs */ - return; - } + int32_t i_new = 0, dst_column_count = dst_table->storage_count; + int32_t i_old = 0, src_column_count = src_table->storage_count; + ecs_id_t *dst_ids = dst_table->storage_ids; + ecs_id_t *src_ids = src_table->storage_ids; - ecs_run_aperiodic(world, EcsAperiodicEmptyTables); - - ecs_id_record_t *idr = flecs_id_record_get(world, ecs_childof(base)); - ecs_table_cache_iter_t it; - if (idr && flecs_table_cache_iter((ecs_table_cache_t*)idr, &it)) { - const ecs_table_record_t *tr; - while ((tr = flecs_table_cache_next(&it, ecs_table_record_t))) { - flecs_instantiate_children( - world, base, table, row, count, tr->hdr.table); - } - } -} + ecs_type_info_t **dst_type_info = dst_table->type_info; + ecs_type_info_t **src_type_info = src_table->type_info; -static -bool override_component( - ecs_world_t *world, - ecs_entity_t component, - ecs_type_t type, - ecs_table_t *table, - ecs_table_t *other_table, - const ecs_type_info_t *ti, - ecs_column_t *column, - int32_t row, - int32_t count, - bool notify_on_set); + ecs_column_t *src = src_data->columns; + ecs_column_t *dst = dst_data->columns; -static -bool override_from_base( - ecs_world_t *world, - ecs_entity_t base, - ecs_entity_t component, - ecs_table_t *table, - ecs_table_t *other_table, - const ecs_type_info_t *ti, - ecs_column_t *column, - int32_t row, - int32_t count, - bool notify_on_set) -{ - ecs_assert(component != 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(!dst_column_count || dst, ECS_INTERNAL_ERROR, NULL); - ecs_record_t *r = flecs_entities_get(world, base); - ecs_table_t *base_table; - if (!r || !(base_table = r->table)) { - return false; + if (!src_count) { + return; } - void *base_ptr = get_component( - world, base_table, ECS_RECORD_TO_ROW(r->row), component); - if (base_ptr) { - int32_t index, data_size = ti->size; - void *data_ptr = ecs_storage_get(column, data_size, row); + /* Merge entities */ + flecs_merge_column(&dst_data->entities, &src_data->entities, + ECS_SIZEOF(ecs_entity_t), NULL); + ecs_assert(dst_data->entities.count == src_count + dst_count, + ECS_INTERNAL_ERROR, NULL); - ecs_copy_t copy = ti->hooks.copy; - if (copy) { - for (index = 0; index < count; index ++) { - copy(data_ptr, base_ptr, 1, ti); - data_ptr = ECS_OFFSET(data_ptr, data_size); - } - } else { - for (index = 0; index < count; index ++) { - ecs_os_memcpy(data_ptr, base_ptr, data_size); - data_ptr = ECS_OFFSET(data_ptr, data_size); - } - } + /* Merge record pointers */ + flecs_merge_column(&dst_data->records, &src_data->records, + ECS_SIZEOF(ecs_record_t*), 0); - ecs_type_t ids = { - .array = (ecs_id_t[]){ component }, - .count = 1 - }; + for (; (i_new < dst_column_count) && (i_old < src_column_count); ) { + ecs_id_t dst_id = dst_ids[i_new]; + ecs_id_t src_id = src_ids[i_old]; + ecs_type_info_t *dst_ti = dst_type_info[i_new]; + int32_t size = dst_ti->size; + ecs_assert(size != 0, ECS_INTERNAL_ERROR, NULL); - if (notify_on_set) { - /* Check if the component was available for the previous table. If - * the override is caused by an add operation, it does not introduce - * a new component value, and the application should not be - * notified. - * - * If the override is the result if adding a IsA relationship - * with an entity that has components with the OVERRIDE flag, an - * event should be generated, since this represents a new component - * (and component value) for the entity. - * - * Note that this is an edge case, regular (self) triggers won't be - * notified because the event id is not the component but an IsA - * relationship. Superset triggers will not be invoked because the - * component is owned. */ - int32_t c = ecs_search_relation(world, other_table, 0, component, - EcsIsA, EcsUp, 0, 0, 0); - if (c == -1) { - flecs_notify( - world, table, other_table, row, count, EcsOnSet, &ids, 0); - } + if (dst_id == src_id) { + flecs_merge_column(&dst[i_new], &src[i_old], size, dst_ti); + mark_table_dirty(world, dst_table, i_new + 1); + + i_new ++; + i_old ++; + } else if (dst_id < src_id) { + /* New column, make sure vector is large enough. */ + ecs_column_t *column = &dst[i_new]; + ecs_storage_set_count(column, size, src_count + dst_count); + ctor_component(dst_ti, column, 0, src_count + dst_count); + i_new ++; + } else if (dst_id > src_id) { + /* Old column does not occur in new table, destruct */ + ecs_column_t *column = &src[i_old]; + dtor_component(src_type_info[i_old], column, 0, src_count); + ecs_storage_fini(column); + i_old ++; } + } - return true; - } else { - /* If component not found on base, check if base itself inherits */ - ecs_type_t base_type = base_table->type; - return override_component(world, component, base_type, table, - other_table, ti, column, row, count, notify_on_set); + move_switch_columns(dst_table, dst_count, src_table, 0, src_count, true); + move_bitset_columns(dst_table, dst_count, src_table, 0, src_count, true); + + /* Initialize remaining columns */ + for (; i_new < dst_column_count; i_new ++) { + ecs_column_t *column = &dst[i_new]; + ecs_type_info_t *ti = dst_type_info[i_new]; + int32_t size = ti->size; + ecs_assert(size != 0, ECS_INTERNAL_ERROR, NULL); + ecs_storage_set_count(column, size, src_count + dst_count); + ctor_component(ti, column, 0, src_count + dst_count); } + + /* Destruct remaining columns */ + for (; i_old < src_column_count; i_old ++) { + ecs_column_t *column = &src[i_old]; + dtor_component(src_type_info[i_old], column, 0, src_count); + ecs_storage_fini(column); + } + + /* Mark entity column as dirty */ + mark_table_dirty(world, dst_table, 0); } -static -bool override_component( +int32_t ecs_table_count( + const ecs_table_t *table) +{ + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + return flecs_table_data_count(&table->data); +} + +void flecs_table_merge( ecs_world_t *world, - ecs_entity_t component, - ecs_type_t type, - ecs_table_t *table, - ecs_table_t *other_table, - const ecs_type_info_t *ti, - ecs_column_t *column, - int32_t row, - int32_t count, - bool notify_on_set) + ecs_table_t *dst_table, + ecs_table_t *src_table, + ecs_data_t *dst_data, + ecs_data_t *src_data) { - ecs_entity_t *type_array = type.array; - int32_t i, type_count = type.count; + ecs_assert(src_table != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(!src_table->lock, ECS_LOCKED_STORAGE, NULL); - /* Walk prefabs */ - i = type_count - 1; - do { - ecs_entity_t e = type_array[i]; + check_table_sanity(dst_table); + check_table_sanity(src_table); + + bool move_data = false; + + /* If there is nothing to merge to, just clear the old table */ + if (!dst_table) { + flecs_table_clear_data(world, src_table, src_data); + check_table_sanity(src_table); + return; + } else { + ecs_assert(!dst_table->lock, ECS_LOCKED_STORAGE, NULL); + } - if (!(e & ECS_ID_FLAGS_MASK)) { - break; + /* If there is no data to merge, drop out */ + if (!src_data) { + return; + } + + if (!dst_data) { + dst_data = &dst_table->data; + if (dst_table == src_table) { + move_data = true; } + } - if (ECS_HAS_RELATION(e, EcsIsA)) { - if (override_from_base(world, ecs_pair_second(world, e), component, - table, other_table, ti, column, row, count, notify_on_set)) - { - return true; - } + ecs_entity_t *src_entities = src_data->entities.array; + int32_t src_count = src_data->entities.count; + int32_t dst_count = dst_data->entities.count; + ecs_record_t **src_records = src_data->records.array; + + /* First, update entity index so old entities point to new type */ + int32_t i; + for(i = 0; i < src_count; i ++) { + ecs_record_t *record; + if (dst_table != src_table) { + record = src_records[i]; + ecs_assert(record != NULL, ECS_INTERNAL_ERROR, NULL); + } else { + record = flecs_entities_ensure(world, src_entities[i]); } - } while (--i >= 0); - return false; + uint32_t flags = ECS_RECORD_TO_ROW_FLAGS(record->row); + record->row = ECS_ROW_TO_RECORD(dst_count + i, flags); + record->table = dst_table; + } + + /* Merge table columns */ + if (move_data) { + *dst_data = *src_data; + } else { + flecs_merge_table_data(world, dst_table, src_table, src_count, dst_count, + src_data, dst_data); + } + + if (src_count) { + if (!dst_count) { + flecs_table_set_empty(world, dst_table); + } + flecs_table_set_empty(world, src_table); + dst_table->observed_count += src_table->observed_count; + src_table->observed_count = 0; + } + + check_table_sanity(src_table); + check_table_sanity(dst_table); } -static -void components_override( +void flecs_table_replace_data( ecs_world_t *world, ecs_table_t *table, - ecs_table_t *other_table, - int32_t row, - int32_t count, - ecs_type_t *added, - bool notify_on_set) + ecs_data_t *data) { - ecs_column_t *columns = table->data.columns; - ecs_type_t type = table->type; - ecs_table_t *storage_table = table->storage_table; + int32_t prev_count = 0; + ecs_data_t *table_data = &table->data; + ecs_assert(!data || data != table_data, ECS_INTERNAL_ERROR, NULL); + ecs_assert(!table->lock, ECS_LOCKED_STORAGE, NULL); - int i; - for (i = 0; i < added->count; i ++) { - ecs_entity_t id = added->array[i]; + check_table_sanity(table); - if (ECS_HAS_RELATION(id, EcsIsA)) { - ecs_entity_t base = ECS_PAIR_SECOND(id); + prev_count = table_data->entities.count; + run_on_remove(world, table, table_data); + flecs_table_clear_data(world, table, table_data); - /* Cannot inherit from base if base is final */ - ecs_check(!ecs_has_id(world, ecs_get_alive(world, base), EcsFinal), - ECS_CONSTRAINT_VIOLATED, NULL); - ecs_check(base != 0, ECS_INVALID_PARAMETER, NULL); + if (data) { + table->data = *data; + } else { + flecs_table_init_data(table); + } - if (!world->stages[0].base) { - /* Setting base prevents instantiating the hierarchy multiple - * times. The instantiate function recursively iterates the - * hierarchy to instantiate children. While this is happening, - * new tables are created which end up calling this function, - * which would call instantiate multiple times for the same - * level in the hierarchy. */ - world->stages[0].base = base; - flecs_instantiate(world, base, table, row, count); - world->stages[0].base = 0; - } - } + int32_t count = ecs_table_count(table); - if (!storage_table) { - continue; - } + if (!prev_count && count) { + flecs_table_set_empty(world, table); + } else if (prev_count && !count) { + flecs_table_set_empty(world, table); + } - ecs_table_record_t *tr = flecs_table_record_get( - world, storage_table, id); - if (!tr) { - continue; - } + check_table_sanity(table); +} - ecs_id_record_t *idr = (ecs_id_record_t*)tr->hdr.cache; - if (idr->flags & EcsIdDontInherit) { - continue; +int32_t* flecs_table_get_dirty_state( + ecs_table_t *table) +{ + if (!table->dirty_state) { + int32_t column_count = table->storage_count; + table->dirty_state = ecs_os_malloc_n( int32_t, column_count + 1); + ecs_assert(table->dirty_state != NULL, ECS_INTERNAL_ERROR, NULL); + + for (int i = 0; i < column_count + 1; i ++) { + table->dirty_state[i] = 1; } + } + return table->dirty_state; +} - const ecs_type_info_t *ti = idr->type_info; - if (!ti->size) { - continue; - } +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); - ecs_column_t *column = &columns[tr->column]; - override_component(world, id, type, table, other_table, ti, - column, row, count, notify_on_set); - } -error: - return; + int32_t column_count = table->storage_count; + return ecs_os_memdup(dirty_state, (column_count + 1) * ECS_SIZEOF(int32_t)); } -static -void flecs_set_union( +void flecs_table_notify( ecs_world_t *world, ecs_table_t *table, - int32_t row, - int32_t count, - ecs_type_t *ids, - bool reset) + ecs_table_event_t *event) { - ecs_id_t *array = ids->array; - int32_t i, id_count = ids->count; - - for (i = 0; i < id_count; i ++) { - ecs_id_t id = array[i]; - - if (ECS_HAS_ID_FLAG(id, PAIR)) { - ecs_id_record_t *idr = flecs_id_record_get(world, - ecs_pair(EcsUnion, ECS_PAIR_FIRST(id))); - if (!idr) { - continue; - } + if (world->flags & EcsWorldFini) { + return; + } - const ecs_table_record_t *tr = flecs_id_record_get_table( - idr, table); - ecs_assert(tr != NULL, ECS_INTERNAL_ERROR, NULL); - int32_t column = tr->column - table->sw_offset; - ecs_switch_t *sw = &table->data.sw_columns[column]; - ecs_entity_t union_case = 0; - if (!reset) { - union_case = ECS_PAIR_SECOND(id); - } + switch(event->kind) { + case EcsTableTriggersForId: + notify_trigger(world, table, event->event); + break; + case EcsTableNoTriggersForId: + break; + } +} - int32_t r; - for (r = 0; r < count; r ++) { - flecs_switch_set(sw, row + r, union_case); - } +void ecs_table_lock( + ecs_world_t *world, + ecs_table_t *table) +{ + if (table) { + if (ecs_poly_is(world, ecs_world_t) && !(world->flags & EcsWorldReadonly)) { + table->lock ++; } } } -static -void flecs_add_remove_union( +void ecs_table_unlock( ecs_world_t *world, - ecs_table_t *table, - int32_t row, - int32_t count, - ecs_type_t *added, - ecs_type_t *removed) + ecs_table_t *table) { - if (added) { - flecs_set_union(world, table, row, count, added, false); + if (table) { + if (ecs_poly_is(world, ecs_world_t) && !(world->flags & EcsWorldReadonly)) { + table->lock --; + ecs_assert(table->lock >= 0, ECS_INVALID_OPERATION, NULL); + } } - if (removed) { - flecs_set_union(world, table, row, count, removed, true); - } } -static -ecs_record_t* new_entity( - ecs_world_t *world, - ecs_entity_t entity, - ecs_record_t *record, - ecs_table_t *new_table, - ecs_table_diff_t *diff, - bool construct, - bool notify_on_set) +bool ecs_table_has_module( + ecs_table_t *table) { - int32_t new_row; + return table->flags & EcsTableHasModule; +} - if (!record) { - record = flecs_entities_ensure(world, entity); +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; } - new_row = flecs_table_append(world, new_table, entity, record, - construct, true); + ecs_table_record_t *tr = flecs_table_record_get(world, storage_table, id); + if (tr) { + return &table->data.columns[tr->column]; + } - record->table = new_table; - record->row = ECS_ROW_TO_RECORD(new_row, record->row & ECS_ROW_FLAGS_MASK); + return NULL; +} - ecs_data_t *new_data = &new_table->data; - ecs_assert(ecs_storage_count(&new_data[0].entities) > new_row, - ECS_INTERNAL_ERROR, NULL); - (void)new_data; +const ecs_type_t* ecs_table_get_type( + const ecs_table_t *table) +{ + if (table) { + return &table->type; + } else { + return NULL; + } +} - ecs_flags32_t flags = new_table->flags; +ecs_table_t* ecs_table_get_storage_table( + const ecs_table_t *table) +{ + return table->storage_table; +} - if (flags & EcsTableHasAddActions) { - flecs_notify_on_add( - world, new_table, NULL, new_row, 1, diff, notify_on_set); +int32_t ecs_table_type_to_storage_index( + const ecs_table_t *table, + int32_t index) +{ + ecs_assert(index >= 0, ECS_INVALID_PARAMETER, NULL); + ecs_check(index < table->type.count, ECS_INVALID_PARAMETER, NULL); + int32_t *storage_map = table->storage_map; + if (storage_map) { + return storage_map[index]; } +error: + return -1; +} - return record; +int32_t ecs_table_storage_to_type_index( + const ecs_table_t *table, + int32_t index) +{ + ecs_check(index < table->storage_count, ECS_INVALID_PARAMETER, NULL); + ecs_check(table->storage_map != NULL, ECS_INVALID_PARAMETER, NULL); + int32_t offset = table->type.count; + return table->storage_map[offset + index]; +error: + return -1; } -static -void move_entity( - ecs_world_t *world, - ecs_entity_t entity, - ecs_record_t *record, - ecs_table_t *dst_table, - ecs_table_diff_t *diff, - bool construct, - bool notify_on_set) +int32_t flecs_table_column_to_union_index( + const ecs_table_t *table, + int32_t column) { - ecs_table_t *src_table = record->table; - int32_t src_row = ECS_RECORD_TO_ROW(record->row); - - ecs_assert(src_table != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(src_table != dst_table, ECS_INTERNAL_ERROR, NULL); - ecs_assert(src_row >= 0, ECS_INTERNAL_ERROR, NULL); - ecs_assert(ecs_storage_count(&src_table->data.entities) > src_row, - ECS_INTERNAL_ERROR, NULL); - ecs_check(ecs_is_alive(world, entity), ECS_INVALID_PARAMETER, NULL); - ecs_assert(record != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(record == flecs_entities_get(world, entity), ECS_INTERNAL_ERROR, NULL); + int32_t sw_count = table->sw_count; + if (sw_count) { + int32_t sw_offset = table->sw_offset; + if (column >= sw_offset && column < (sw_offset + sw_count)){ + return column - sw_offset; + } + } + return -1; +} - int32_t dst_row = flecs_table_append(world, dst_table, entity, - record, false, false); +ecs_record_t* ecs_record_find( + const ecs_world_t *world, + ecs_entity_t entity) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(entity != 0, ECS_INVALID_PARAMETER, NULL); - /* Copy entity & components from src_table to dst_table */ - if (src_table->type.count) { - flecs_notify_on_remove(world, src_table, dst_table, - ECS_RECORD_TO_ROW(src_row), 1, diff); + world = ecs_get_world(world); - flecs_table_move(world, entity, entity, dst_table, dst_row, - src_table, src_row, construct); + ecs_record_t *r = flecs_entities_get(world, entity); + if (r) { + return r; } +error: + return NULL; +} - /* Update entity index & delete old data after running remove actions */ - record->table = dst_table; - record->row = ECS_ROW_TO_RECORD(dst_row, record->row & ECS_ROW_FLAGS_MASK); - - flecs_table_delete(world, src_table, src_row, false); +void* ecs_table_get_column( + ecs_table_t *table, + int32_t index) +{ + ecs_check(table != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(index < table->type.count, ECS_INVALID_PARAMETER, NULL); - /* If components were added, invoke add actions */ - ecs_flags32_t dst_flags = dst_table->flags; - if (src_table != dst_table || diff->added.count) { - if (diff->added.count && (dst_flags & EcsTableHasAddActions)) { - flecs_notify_on_add(world, dst_table, src_table, dst_row, 1, diff, - notify_on_set); - } + int32_t storage_index = table->storage_map[index]; + if (storage_index == -1) { + return NULL; } + return table->data.columns[storage_index].array; error: - return; + return NULL; } -static -void delete_entity( - ecs_world_t *world, - ecs_record_t *record, - ecs_table_diff_t *diff) -{ - ecs_table_t *table = record->table; - int32_t row = ECS_RECORD_TO_ROW(record->row); +void* ecs_record_get_column( + const ecs_record_t *r, + int32_t column, + size_t c_size) +{ + (void)c_size; + ecs_table_t *table = r->table; - /* Invoke remove actions before deleting */ - if (table->flags & EcsTableHasRemoveActions) { - flecs_notify_on_remove(world, table, NULL, row, 1, diff); - } + ecs_check(column < table->storage_count, ECS_INVALID_PARAMETER, NULL); + ecs_type_info_t *ti = table->type_info[column]; + ecs_column_t *c = &table->data.columns[column]; + ecs_assert(c != NULL, ECS_INTERNAL_ERROR, NULL); - flecs_table_delete(world, table, row, true); + ecs_check(!flecs_utosize(c_size) || flecs_utosize(c_size) == ti->size, + ECS_INVALID_PARAMETER, NULL); + + return ecs_storage_get(c, ti->size, ECS_RECORD_TO_ROW(r->row)); +error: + return NULL; } -/* Updating component monitors is a relatively expensive operation that only - * happens for entities that are monitored. The approach balances the amount of - * processing between the operation on the entity vs the amount of work that - * needs to be done to rematch queries, as a simple brute force approach does - * not scale when there are many tables / queries. Therefore we need to do a bit - * of bookkeeping that is more intelligent than simply flipping a flag */ -static -void update_component_monitor_w_array( - ecs_world_t *world, - ecs_type_t *ids) +void ecs_table_swap_rows( + ecs_world_t* world, + ecs_table_t* table, + int32_t row_1, + int32_t row_2) { - if (!ids) { - return; - } - - int i; - for (i = 0; i < ids->count; i ++) { - ecs_entity_t id = ids->array[i]; - if (ECS_HAS_ID_FLAG(id, PAIR)) { - flecs_monitor_mark_dirty(world, - ecs_pair(ECS_PAIR_FIRST(id), EcsWildcard)); - } - - flecs_monitor_mark_dirty(world, id); - } + flecs_table_swap(world, table, row_1, row_2); } -static -void update_component_monitors( - ecs_world_t *world, - ecs_type_t *added, - ecs_type_t *removed) +int32_t flecs_table_observed_count( + const ecs_table_t *table) { - update_component_monitor_w_array(world, added); - update_component_monitor_w_array(world, removed); + return table->observed_count; } -static -void flecs_commit( - ecs_world_t *world, - ecs_entity_t entity, - ecs_record_t *record, - ecs_table_t *dst_table, - ecs_table_diff_t *diff, - bool construct, - bool notify_on_set) -{ - ecs_assert(!(world->flags & EcsWorldReadonly), ECS_INTERNAL_ERROR, NULL); - - ecs_table_t *src_table = NULL; - uint32_t row_flags = 0; - bool observed = false; - if (record) { - src_table = record->table; - row_flags = record->row & ECS_ROW_FLAGS_MASK; - observed = row_flags & EcsEntityObservedAcyclic; - } - if (src_table == dst_table) { - /* If source and destination table are the same no action is needed * - * However, if a component was added in the process of traversing a - * table, this suggests that a case switch could have occured. */ - if (((diff->added.count) || (diff->removed.count)) && - src_table && src_table->flags & EcsTableHasUnion) - { - flecs_add_remove_union(world, src_table, - ECS_RECORD_TO_ROW(record->row), 1, - &diff->added, &diff->removed); - } +static const char* mixin_kind_str[] = { + [EcsMixinBase] = "base (should never be requested by application)", + [EcsMixinWorld] = "world", + [EcsMixinEntity] = "entity", + [EcsMixinObservable] = "observable", + [EcsMixinIterable] = "iterable", + [EcsMixinDtor] = "dtor", + [EcsMixinMax] = "max (should never be requested by application)" +}; - return; +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) } +}; - if (src_table) { - ecs_assert(dst_table != NULL, ECS_INTERNAL_ERROR, NULL); - dst_table->observed_count += observed; - - if (dst_table->type.count) { - move_entity(world, entity, record, dst_table, diff, - construct, notify_on_set); - } else { - delete_entity(world, record, diff); - record->table = NULL; - } +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) + } +}; - src_table->observed_count -= observed; - } else { - dst_table->observed_count += observed; - if (dst_table->type.count) { - new_entity(world, entity, record, dst_table, diff, - construct, notify_on_set); - } +ecs_mixins_t ecs_query_t_mixins = { + .type_name = "ecs_query_t", + .elems = { + [EcsMixinWorld] = offsetof(ecs_query_t, world), + [EcsMixinEntity] = offsetof(ecs_query_t, entity), + [EcsMixinIterable] = offsetof(ecs_query_t, iterable), + [EcsMixinDtor] = offsetof(ecs_query_t, dtor) } +}; - /* If the entity is being watched, it is being monitored for changes and - * requires rematching systems when components are added or removed. This - * ensures that systems that rely on components from containers or prefabs - * update the matched tables when the application adds or removes a - * component from, for example, a container. */ - if (row_flags) { - update_component_monitors(world, &diff->added, &diff->removed); +ecs_mixins_t ecs_observer_t_mixins = { + .type_name = "ecs_observer_t", + .elems = { + [EcsMixinWorld] = offsetof(ecs_observer_t, world), + [EcsMixinEntity] = offsetof(ecs_observer_t, entity), + [EcsMixinDtor] = offsetof(ecs_observer_t, dtor) } +}; - if ((!src_table || !src_table->type.count) && world->range_check_enabled) { - ecs_check(!world->info.max_id || entity <= world->info.max_id, - ECS_OUT_OF_RANGE, 0); - ecs_check(entity >= world->info.min_id, - ECS_OUT_OF_RANGE, 0); - } -error: - return; -} +ecs_mixins_t ecs_filter_t_mixins = { + .type_name = "ecs_filter_t", + .elems = { + [EcsMixinIterable] = offsetof(ecs_filter_t, iterable) + } +}; static -void new( - ecs_world_t *world, - ecs_entity_t entity, - ecs_type_t *to_add) +void* get_mixin( + const ecs_poly_t *poly, + ecs_mixin_kind_t kind) { - int32_t i, count = to_add->count; - ecs_table_t *table = &world->store.root; + ecs_assert(poly != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(kind < EcsMixinMax, ECS_INVALID_PARAMETER, NULL); - ecs_diff_buffer_t diff = ECS_DIFF_INIT; - for (i = 0; i < count; i ++) { - table = table_append(world, table, to_add->array[i], &diff); - } - - ecs_table_diff_t table_diff = diff_to_table_diff(&diff); - new_entity(world, entity, NULL, table, &table_diff, true, true); - diff_free(&diff); -} + 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); -static -const ecs_entity_t* new_w_data( - ecs_world_t *world, - ecs_table_t *table, - const ecs_entity_t *entities, - ecs_type_t *component_ids, - int32_t count, - void **component_data, - bool is_move, - int32_t *row_out, - ecs_table_diff_t *diff) -{ - int32_t sparse_count = 0; - if (!entities) { - sparse_count = flecs_entities_count(world); - entities = flecs_sparse_new_ids(ecs_eis(world), count); + const ecs_mixins_t *mixins = hdr->mixins; + if (!mixins) { + /* Object has no mixins */ + goto not_found; } - if (!table) { - return entities; + 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; } - ecs_type_t type = table->type; - if (!type.count) { - return entities; - } + /* Object has mixin, return its address */ + return ECS_OFFSET(hdr, offset); - ecs_type_t component_array = { 0 }; - if (!component_ids) { - component_ids = &component_array; - component_array.array = type.array; - component_array.count = type.count; +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); + } } - - ecs_data_t *data = &table->data; - int32_t row = flecs_table_appendn(world, table, data, count, entities); - /* Update entity index. */ - int i; - ecs_record_t **records = ecs_storage_first(&data->records); - for (i = 0; i < count; i ++) { - records[row + i] = flecs_entities_set(world, entities[i], - &(ecs_record_t){ - .table = table, - .row = ECS_ROW_TO_RECORD(row + i, 0) - }); - } - - flecs_defer_begin(world, &world->stages[0]); +not_found: + /* Mixin wasn't found for poly */ + return NULL; +} - flecs_notify_on_add(world, table, NULL, row, count, diff, - component_data == 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(); + } - if (component_data) { - /* Set components that we're setting in the component mask so the init - * actions won't call OnSet triggers for them. This ensures we won't - * call OnSet triggers multiple times for the same component */ - int32_t c_i; - ecs_table_t *storage_table = table->storage_table; - for (c_i = 0; c_i < component_ids->count; c_i ++) { - void *src_ptr = component_data[c_i]; - if (!src_ptr) { - continue; - } + return ptr; +} - /* Find component in storage type */ - ecs_entity_t id = component_ids->array[c_i]; - const ecs_table_record_t *tr = flecs_table_record_get( - world, storage_table, id); - ecs_assert(tr != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(tr->count == 1, ECS_INTERNAL_ERROR, NULL); +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); - int32_t index = tr->column; - ecs_type_info_t *ti = table->type_info[index]; - ecs_column_t *column = &table->data.columns[index]; - int32_t size = ti->size; - ecs_assert(size != 0, ECS_INTERNAL_ERROR, NULL); - void *ptr = ecs_storage_get(column, size, row); + ecs_header_t *hdr = poly; + ecs_os_memset(poly, 0, size); - ecs_copy_t copy; - ecs_move_t move; - if (is_move && (move = ti->hooks.move)) { - move(ptr, src_ptr, count, ti); - } else if (!is_move && (copy = ti->hooks.copy)) { - copy(ptr, src_ptr, count, ti); - } else { - ecs_os_memcpy(ptr, src_ptr, size * count); - } - }; + hdr->magic = ECS_OBJECT_MAGIC; + hdr->type = type; + hdr->mixins = mixins; - flecs_notify_on_set(world, table, row, count, NULL, true); - flecs_notify_on_set(world, table, row, count, &diff->on_set, false); - } + return poly; +} - flecs_defer_end(world, &world->stages[0]); +void _ecs_poly_fini( + ecs_poly_t *poly, + int32_t type) +{ + ecs_assert(poly != NULL, ECS_INVALID_PARAMETER, NULL); + (void)type; - if (row_out) { - *row_out = row; - } + ecs_header_t *hdr = poly; - if (sparse_count) { - entities = flecs_sparse_ids(ecs_eis(world)); - return &entities[sparse_count]; - } else { - return entities; - } + /* 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 -void add_id_w_record( +EcsPoly* _ecs_poly_bind( ecs_world_t *world, ecs_entity_t entity, - ecs_record_t *record, - ecs_id_t id, - bool construct) + ecs_entity_t tag) { - ecs_table_diff_t diff; + /* Add tag to the entity for easy querying. This will make it possible to + * query for `Query` instead of `(Poly, Query) */ + if (!ecs_has_id(world, entity, tag)) { + ecs_add_id(world, entity, tag); + } - ecs_table_t *src_table = NULL; - if (record) { - src_table = record->table; + /* Never defer creation of a poly object */ + bool deferred = false; + if (ecs_is_deferred(world)) { + deferred = true; + ecs_defer_suspend(world); } - ecs_table_t *dst_table = flecs_table_traverse_add( - world, src_table, &id, &diff); + /* If this is a new poly, leave the actual creation up to the caller so they + * call tell the difference between a create or an update */ + EcsPoly *result = ecs_get_mut_pair(world, entity, EcsPoly, tag); - flecs_commit(world, entity, record, dst_table, &diff, construct, - false); /* notify_on_set = false, this function is only called from - * functions that are about to set the component. */ + if (deferred) { + ecs_defer_resume(world); + } + + return result; } -static -void add_id( +void _ecs_poly_modified( ecs_world_t *world, ecs_entity_t entity, - ecs_id_t id) + ecs_entity_t tag) { - ecs_stage_t *stage = flecs_stage_from_world(&world); - - if (flecs_defer_add(world, stage, entity, id)) { - return; - } - - ecs_record_t *r = flecs_entities_ensure(world, entity); - ecs_table_diff_t diff; - ecs_table_t *src_table = r->table; - ecs_table_t *dst_table = flecs_table_traverse_add( - world, src_table, &id, &diff); - - flecs_commit(world, entity, r, dst_table, &diff, true, true); - - flecs_defer_end(world, stage); + ecs_modified_pair(world, entity, ecs_id(EcsPoly), tag); } -static -void remove_id( - ecs_world_t *world, +const EcsPoly* _ecs_poly_bind_get( + const ecs_world_t *world, ecs_entity_t entity, - ecs_id_t id) + ecs_entity_t tag) { - ecs_stage_t *stage = flecs_stage_from_world(&world); + return ecs_get_pair(world, entity, EcsPoly, tag); +} - if (flecs_defer_remove(world, stage, entity, id)) { - return; +ecs_poly_t* _ecs_poly_get( + const ecs_world_t *world, + ecs_entity_t entity, + ecs_entity_t tag) +{ + const EcsPoly *p = _ecs_poly_bind_get(world, entity, tag); + if (p) { + return p->poly; } + return NULL; +} - ecs_record_t *r = flecs_entities_get(world, entity); - ecs_table_t *src_table = NULL; - if (!r || !(src_table = r->table)) { - goto done; /* Nothing to remove */ - } +#define assert_object(cond, file, line, type_name)\ + _ecs_assert((cond), ECS_INVALID_PARAMETER, #cond, file, line, type_name);\ + assert(cond) - ecs_table_diff_t diff; - ecs_table_t *dst_table = flecs_table_traverse_remove( - world, src_table, &id, &diff); +#ifndef FLECS_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, 0); + + const ecs_header_t *hdr = poly; + const char *type_name = hdr->mixins->type_name; + assert_object(hdr->magic == ECS_OBJECT_MAGIC, file, line, type_name); + assert_object(hdr->type == type, file, line, type_name); + return (ecs_poly_t*)poly; +} +#endif - flecs_commit(world, entity, r, dst_table, &diff, true, true); +bool _ecs_poly_is( + const ecs_poly_t *poly, + int32_t type) +{ + ecs_assert(poly != NULL, ECS_INVALID_PARAMETER, NULL); -done: - flecs_defer_end(world, stage); + const ecs_header_t *hdr = poly; + ecs_assert(hdr->magic == ECS_OBJECT_MAGIC, ECS_INVALID_PARAMETER, NULL); + return hdr->type == type; } -static -void *flecs_get_mut( - ecs_world_t *world, - ecs_entity_t entity, - ecs_entity_t id, - ecs_record_t *r) +ecs_iterable_t* ecs_get_iterable( + const ecs_poly_t *poly) { - void *dst = NULL; + return (ecs_iterable_t*)assert_mixin(poly, EcsMixinIterable); +} - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(id != 0, ECS_INVALID_PARAMETER, NULL); - ecs_check(r != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check((id & ECS_COMPONENT_MASK) == id || - ECS_HAS_ID_FLAG(id, PAIR), ECS_INVALID_PARAMETER, NULL); +ecs_observable_t* ecs_get_observable( + const ecs_poly_t *poly) +{ + return (ecs_observable_t*)assert_mixin(poly, EcsMixinObservable); +} - if (r->table) { - dst = get_component(world, r->table, ECS_RECORD_TO_ROW(r->row), id); - } +const ecs_world_t* ecs_get_world( + const ecs_poly_t *poly) +{ + return *(ecs_world_t**)assert_mixin(poly, EcsMixinWorld); +} - if (!dst) { - /* If entity didn't have component yet, add it */ - add_id_w_record(world, entity, r, id, true); +ecs_poly_dtor_t* ecs_get_dtor( + const ecs_poly_t *poly) +{ + return (ecs_poly_dtor_t*)assert_mixin(poly, EcsMixinDtor); +} - /* Flush commands so the pointer we're fetching is stable */ - ecs_defer_end(world); - ecs_defer_begin(world); - ecs_assert(r->table != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(r->table->storage_table != NULL, ECS_INTERNAL_ERROR, NULL); - dst = get_component(world, r->table, ECS_RECORD_TO_ROW(r->row), id); - - return dst; +void ecs_storage_init( + ecs_column_t *storage, + ecs_size_t size, + int32_t elem_count) +{ + ecs_assert(size != 0, ECS_INVALID_PARAMETER, NULL); + storage->array = NULL; + storage->count = 0; + if (elem_count) { + storage->array = ecs_os_malloc(size * elem_count); } - -error: - return dst; + storage->size = elem_count; } -/* -- Private functions -- */ -static -void flecs_notify_on_add( - ecs_world_t *world, - ecs_table_t *table, - ecs_table_t *other_table, - int32_t row, - int32_t count, - ecs_table_diff_t *diff, - bool run_on_set) +void ecs_storage_fini( + ecs_column_t *storage) { - ecs_assert(diff != NULL, ECS_INTERNAL_ERROR, NULL); - - if (diff->added.count) { - if (table->flags & EcsTableHasIsA) { - components_override(world, table, other_table, row, count, - &diff->added, run_on_set); - } - - if (table->flags & EcsTableHasUnion) { - flecs_add_remove_union(world, table, row, count, &diff->added, NULL); - } - - if (table->flags & EcsTableHasOnAdd) { - flecs_notify(world, table, other_table, row, count, EcsOnAdd, - &diff->added, 0); - } - } - - /* When a IsA relationship is added to an entity, that entity inherits the - * components from the base. Send OnSet notifications so that an application - * can respond to these new components. */ - if (run_on_set && diff->on_set.count) { - flecs_notify(world, table, other_table, row, count, EcsOnSet, &diff->on_set, - EcsIsA); - } + ecs_os_free(storage->array); + storage->array = NULL; + storage->count = 0; + storage->size = 0; } -void flecs_notify_on_remove( - ecs_world_t *world, - ecs_table_t *table, - ecs_table_t *other_table, - int32_t row, - int32_t count, - ecs_table_diff_t *diff) +int32_t ecs_storage_count( + ecs_column_t *storage) { - ecs_assert(diff != NULL, ECS_INTERNAL_ERROR, NULL); - - if (count) { - if (diff->un_set.count) { - flecs_notify(world, table, other_table, row, count, EcsUnSet, &diff->un_set, 0); - } + return storage->count; +} - if (table->flags & EcsTableHasOnRemove && diff->removed.count) { - flecs_notify(world, table, other_table, row, count, EcsOnRemove, - &diff->removed, 0); - } +int32_t ecs_storage_size( + ecs_column_t *storage) +{ + return storage->size; +} - if (table->flags & EcsTableHasIsA && diff->on_set.count) { - flecs_notify(world, table, other_table, row, count, EcsOnSet, - &diff->on_set, 0); - } - } +void* ecs_storage_get( + ecs_column_t *storage, + ecs_size_t size, + int32_t index) +{ + ecs_assert(index < storage->count, ECS_OUT_OF_RANGE, NULL); + return ECS_ELEM(storage->array, size, index); } -void flecs_notify_on_set( - ecs_world_t *world, - ecs_table_t *table, - int32_t row, - int32_t count, - ecs_type_t *ids, - bool owned) +void* ecs_storage_last( + ecs_column_t *storage, + ecs_size_t size) { - ecs_data_t *data = &table->data; + return ECS_ELEM(storage->array, size, storage->count - 1); +} - ecs_entity_t *entities = ecs_storage_get_t( - &data->entities, ecs_entity_t, row); - ecs_assert(entities != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert((row + count) <= ecs_storage_count(&data->entities), - ECS_INTERNAL_ERROR, NULL); +void* ecs_storage_first( + ecs_column_t *storage) +{ + return storage->array; +} - ecs_type_t local_ids; - if (!ids) { - local_ids.array = table->storage_ids; - local_ids.count = table->storage_count; - ids = &local_ids; +void* ecs_storage_append( + ecs_column_t *storage, + ecs_size_t size) +{ + int32_t count = storage->count; + if (storage->size == count) { + ecs_storage_set_size(storage, size, count + 1); } + storage->count = count + 1; + return ECS_ELEM(storage->array, size, count); +} - if (owned) { - ecs_table_t *storage_table = table->storage_table; - int i; - for (i = 0; i < ids->count; i ++) { - ecs_id_t id = ids->array[i]; - const ecs_table_record_t *tr = flecs_table_record_get(world, - storage_table, id); - ecs_assert(tr != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(tr->count == 1, ECS_INTERNAL_ERROR, NULL); - int32_t column = tr->column; - const ecs_type_info_t *ti = table->type_info[column]; - ecs_iter_action_t on_set = ti->hooks.on_set; - if (on_set) { - ecs_column_t *c = &table->data.columns[column]; - ecs_size_t size = ti->size; - void *ptr = ecs_storage_get(c, size, row); - ecs_assert(size != 0, ECS_INTERNAL_ERROR, NULL); - - ecs_iter_t it = { .field_count = 1}; - it.entities = entities; - - flecs_iter_init(&it, flecs_iter_cache_all); - it.world = world; - it.real_world = world; - it.table = table; - it.ptrs[0] = ptr; - it.sizes[0] = size; - it.ids[0] = id; - it.event = EcsOnSet; - it.event_id = id; - it.ctx = ti->hooks.ctx; - it.binding_ctx = ti->hooks.binding_ctx; - it.count = count; - flecs_iter_validate(&it); - on_set(&it); - } - } +void ecs_storage_remove( + ecs_column_t *storage, + ecs_size_t size, + int32_t index) +{ + ecs_assert(index < storage->count, ECS_OUT_OF_RANGE, NULL); + if (index == --storage->count) { + return; } - /* Run OnSet notifications */ - if (table->flags & EcsTableHasOnSet && ids->count) { - flecs_notify(world, table, NULL, row, count, EcsOnSet, ids, 0); - } + ecs_os_memcpy( + ECS_ELEM(storage->array, size, index), + ECS_ELEM(storage->array, size, storage->count), + size); } -uint32_t flecs_record_to_row( - uint32_t row, - bool *is_watched_out) +void ecs_storage_remove_last( + ecs_column_t *storage) { - *is_watched_out = (row & ECS_ROW_FLAGS_MASK) != 0; - return row & ECS_ROW_MASK; + storage->count --; } -uint32_t flecs_row_to_record( - uint32_t row, - bool is_watched) +ecs_column_t ecs_storage_copy( + ecs_column_t *storage, + ecs_size_t size) { - return row | (EcsEntityObserved * is_watched); + return (ecs_column_t) { + .count = storage->count, + .size = storage->size, + .array = ecs_os_memdup(storage->array, storage->size * size) + }; } -void flecs_add_flag( - ecs_world_t *world, - ecs_entity_t entity, - uint32_t flag) +void ecs_storage_reclaim( + ecs_column_t *storage, + ecs_size_t size) { - ecs_record_t *record = flecs_entities_get(world, entity); - if (!record) { - ecs_record_t new_record = {.row = flag, .table = NULL}; - flecs_entities_set(world, entity, &new_record); - } else { - if (flag == EcsEntityObservedAcyclic) { - if (!(record->row & flag)) { - ecs_table_t *table = record->table; - if (table) { - table->observed_count ++; - } - } + int32_t count = storage->count; + if (count < storage->size) { + if (count) { + storage->array = ecs_os_realloc(storage->array, size * count); + storage->size = count; + } else { + ecs_storage_fini(storage); } - record->row |= flag; } } - -/* -- Public functions -- */ - -bool ecs_commit( - ecs_world_t *world, - ecs_entity_t entity, - ecs_record_t *record, - ecs_table_t *table, - const ecs_type_t *added, - const ecs_type_t *removed) +void ecs_storage_set_size( + ecs_column_t *storage, + ecs_size_t size, + int32_t elem_count) { - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + if (storage->size != elem_count) { + if (elem_count < storage->count) { + elem_count = storage->count; + } - ecs_table_t *src_table = NULL; - if (!record) { - record = flecs_entities_get(world, entity); - src_table = record->table; + elem_count = flecs_next_pow_of_2(elem_count); + if (elem_count < 2) { + elem_count = 2; + } + if (elem_count != storage->size) { + storage->array = ecs_os_realloc(storage->array, size * elem_count); + storage->size = elem_count; + } } +} - ecs_table_diff_t diff; - ecs_os_zeromem(&diff); +void ecs_storage_set_count( + ecs_column_t *storage, + ecs_size_t size, + int32_t elem_count) +{ + if (storage->count != elem_count) { + if (storage->size < elem_count) { + ecs_storage_set_size(storage, size, elem_count); + } - if (added) { - diff.added = *added; - } - if (removed) { - diff.added = *removed; + storage->count = elem_count; } - - flecs_commit(world, entity, record, table, &diff, true, true); - - return src_table != table; -error: - return false; } -ecs_entity_t ecs_set_with( - ecs_world_t *world, - ecs_id_t id) +void* ecs_storage_grow( + ecs_column_t *storage, + ecs_size_t size, + int32_t elem_count) { - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_stage_t *stage = flecs_stage_from_world(&world); - ecs_id_t prev = stage->with; - stage->with = id; - return prev; -error: - return 0; + ecs_assert(elem_count > 0, ECS_INTERNAL_ERROR, NULL); + int32_t count = storage->count; + ecs_storage_set_count(storage, size, count + elem_count); + return ECS_ELEM(storage->array, size, count); } -ecs_id_t ecs_get_with( - const ecs_world_t *world) +#include + +static +void flecs_notify_on_add( + ecs_world_t *world, + ecs_table_t *table, + ecs_table_t *other_table, + 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_type_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(world != NULL, ECS_INVALID_PARAMETER, NULL); - const ecs_stage_t *stage = flecs_stage_from_readonly_world(world); - return stage->with; + ecs_check(column_index < table->storage_count, ECS_NOT_A_COMPONENT, NULL); + ecs_type_info_t *ti = table->type_info[column_index]; + ecs_column_t *column = &table->data.columns[column_index]; + return ecs_storage_get(column, ti->size, row);; error: - return 0; + return NULL; } -ecs_entity_t ecs_new_id( - ecs_world_t *world) +static +void* get_component( + const ecs_world_t *world, + ecs_table_t *table, + int32_t row, + ecs_id_t id) { - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - - const ecs_stage_t *stage = flecs_stage_from_readonly_world(world); - - /* It is possible that the world passed to this function is a stage, so - * make sure we have the actual world. Cast away const since this is one of - * the few functions that may modify the world while it is in readonly mode, - * since it is thread safe (uses atomic inc when in threading mode) */ - ecs_world_t *unsafe_world = (ecs_world_t*)ecs_get_world(world); - - ecs_entity_t entity; - if (stage->async || (unsafe_world->flags & EcsWorldMultiThreaded)) { - /* When using an async stage or world is in multithreading mode, make - * sure OS API has threading functions initialized */ - ecs_assert(ecs_os_has_threading(), ECS_INVALID_OPERATION, NULL); + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(id != 0, ECS_INVALID_PARAMETER, NULL); - /* Can't atomically increase number above max int */ - ecs_assert(unsafe_world->info.last_id < UINT_MAX, - ECS_INVALID_OPERATION, NULL); - entity = (ecs_entity_t)ecs_os_ainc( - (int32_t*)&unsafe_world->info.last_id); - } else { - entity = flecs_entities_recycle(unsafe_world); + if (!table->storage_table) { + ecs_check(ecs_search(world, table, id, 0) == -1, + ECS_NOT_A_COMPONENT, NULL); + return NULL; } - ecs_assert(!unsafe_world->info.max_id || - ecs_entity_t_lo(entity) <= unsafe_world->info.max_id, - ECS_OUT_OF_RANGE, NULL); + ecs_table_record_t *tr = flecs_table_record_get( + world, table->storage_table, id); + if (!tr) { + ecs_check(ecs_search(world, table, id, 0) == -1, + ECS_NOT_A_COMPONENT, NULL); + return NULL; + } - return entity; + return get_component_w_index(table, tr->column, row); error: - return 0; + return NULL; } -ecs_entity_t ecs_new_low_id( - ecs_world_t *world) +static +void* get_base_component( + const ecs_world_t *world, + ecs_table_t *table, + ecs_id_t id, + ecs_id_record_t *table_index, + int32_t recur_depth) { - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - - /* It is possible that the world passed to this function is a stage, so - * make sure we have the actual world. Cast away const since this is one of - * the few functions that may modify the world while it is in readonly mode, - * but only if single threaded. */ - ecs_world_t *unsafe_world = (ecs_world_t*)ecs_get_world(world); - if (unsafe_world->flags & EcsWorldReadonly) { - /* Can't issue new comp id while iterating when in multithreaded mode */ - ecs_check(ecs_get_stage_count(world) <= 1, - ECS_INVALID_WHILE_READONLY, NULL); - } + /* Cycle detected in IsA relationship */ + ecs_check(recur_depth < ECS_MAX_RECURSION, ECS_INVALID_PARAMETER, NULL); - ecs_entity_t id = 0; - if (unsafe_world->info.last_component_id < ECS_HI_COMPONENT_ID) { - do { - id = unsafe_world->info.last_component_id ++; - } while (ecs_exists(unsafe_world, id) && id <= ECS_HI_COMPONENT_ID); + /* Table (and thus entity) does not have component, look for base */ + if (!(table->flags & EcsTableHasIsA)) { + return NULL; } - if (!id || id >= ECS_HI_COMPONENT_ID) { - /* If the low component ids are depleted, return a regular entity id */ - id = ecs_new_id(unsafe_world); + /* Exclude Name */ + if (id == ecs_pair(ecs_id(EcsIdentifier), EcsName)) { + return NULL; } - return id; -error: - return 0; -} + /* Table should always be in the table index for (IsA, *), otherwise the + * HasBase flag should not have been set */ + const ecs_table_record_t *tr_isa = flecs_id_record_get_table( + world->idr_isa_wildcard, table); + ecs_check(tr_isa != NULL, ECS_INTERNAL_ERROR, NULL); -ecs_entity_t ecs_new_w_id( - ecs_world_t *world, - ecs_id_t id) -{ - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(!id || ecs_id_is_valid(world, id), ECS_INVALID_PARAMETER, NULL); + ecs_type_t type = table->type; + ecs_id_t *ids = type.array; + int32_t i = tr_isa->column, end = tr_isa->count + tr_isa->column; + void *ptr = NULL; - ecs_stage_t *stage = flecs_stage_from_world(&world); - ecs_entity_t entity = ecs_new_id(world); + do { + ecs_id_t pair = ids[i ++]; + ecs_entity_t base = ecs_pair_second(world, pair); - ecs_id_t ids[3]; - ecs_type_t to_add = { .array = ids, .count = 0 }; + ecs_record_t *r = flecs_entities_get(world, base); + if (!r) { + continue; + } - if (id) { - ids[to_add.count ++] = id; - } + table = r->table; + if (!table) { + continue; + } - ecs_id_t with = stage->with; - if (with) { - ids[to_add.count ++] = with; - } + const ecs_table_record_t *tr = NULL; - ecs_entity_t scope = stage->scope; - if (scope) { - if (!id || !ECS_HAS_RELATION(id, EcsChildOf)) { - ids[to_add.count ++] = ecs_pair(EcsChildOf, scope); - } - } - if (to_add.count) { - if (flecs_defer_new(world, stage, entity, to_add.array[0])) { - int i; - for (i = 1; i < to_add.count; i ++) { - flecs_defer_add(world, stage, entity, to_add.array[i]); - } - return entity; + ecs_table_t *storage_table = table->storage_table; + if (storage_table) { + tr = flecs_id_record_get_table(table_index, storage_table); + } else { + ecs_check(!ecs_owns_id(world, base, id), + ECS_NOT_A_COMPONENT, NULL); } - new(world, entity, &to_add); - } else { - if (flecs_defer_new(world, stage, entity, 0)) { - return entity; + if (!tr) { + ptr = get_base_component(world, table, id, table_index, + recur_depth + 1); + } else { + int32_t row = ECS_RECORD_TO_ROW(r->row); + ptr = get_component_w_index(table, tr->column, row); } + } while (!ptr && (i < end)); - flecs_entities_set(world, entity, &(ecs_record_t){ 0 }); - } - flecs_defer_end(world, stage); - - return entity; + return ptr; error: - return 0; + return NULL; } -#ifdef FLECS_PARSER - -/* Traverse table graph by either adding or removing identifiers parsed from the - * passed in expression. */ static -ecs_table_t *traverse_from_expr( +const ecs_type_info_t *get_c_info( ecs_world_t *world, - ecs_table_t *table, - const char *name, - const char *expr, - ecs_diff_buffer_t *diff, - bool replace_and, - bool *error) + ecs_entity_t component) { - const char *ptr = expr; - if (ptr) { - ecs_term_t term = {0}; - while (ptr[0] && (ptr = ecs_parse_term(world, name, expr, ptr, &term))){ - if (!ecs_term_is_initialized(&term)) { - break; - } + ecs_entity_t real_id = ecs_get_typeid(world, component); + if (real_id) { + return flecs_type_info_get(world, real_id); + } else { + return NULL; + } +} - if (!(term.first.flags & (EcsSelf|EcsUp))) { - term.first.flags = EcsSelf; - } - if (!(term.second.flags & (EcsSelf|EcsUp))) { - term.second.flags = EcsSelf; - } - if (!(term.src.flags & (EcsSelf|EcsUp))) { - term.src.flags = EcsSelf; - } +/* Utilities for creating a diff struct on the fly between two arbitrary tables. + * This is temporary code that will eventually be replaced by a cache that + * stores the diff between two archetypes. */ - if (ecs_term_finalize(world, &term)) { - ecs_term_fini(&term); - if (error) { - *error = true; - } - return NULL; - } +typedef struct { + ecs_type_t type; + ecs_size_t size; +} ecs_type_buffer_t; - if (!ecs_id_is_valid(world, term.id)) { - ecs_term_fini(&term); - ecs_parser_error(name, expr, (ptr - expr), - "invalid term for add expression"); - return NULL; - } +typedef struct { + ecs_type_buffer_t added; + ecs_type_buffer_t removed; + ecs_type_buffer_t on_set; + ecs_type_buffer_t un_set; +} ecs_diff_buffer_t; - if (term.oper == EcsAnd || !replace_and) { - /* Regular AND expression */ - table = table_append(world, table, term.id, diff); - } +static +void ids_merge( + ecs_type_buffer_t *ids, + ecs_type_t *add) +{ + if (!add || !add->count) { + return; + } + + int32_t new_count = ids->type.count + add->count; + if (new_count > ids->size) { + ids->size = flecs_next_pow_of_2(new_count); + ecs_id_t *arr = ecs_os_malloc_n(ecs_id_t, ids->size); + ecs_os_memcpy_n(arr, ids->type.array, ecs_id_t, ids->type.count); - ecs_term_fini(&term); + if (ids->type.count > ECS_ID_CACHE_SIZE) { + ecs_os_free(ids->type.array); } + + ids->type.array = arr; + } - if (!ptr) { - if (error) { - *error = true; - } - return NULL; - } + ecs_os_memcpy_n(&ids->type.array[ids->type.count], + add->array, ecs_id_t, add->count); + ids->type.count += add->count; +} + +#define ECS_DIFF_INIT {\ + .added = { .type = { .count = 0, .array = (ecs_id_t[ECS_ID_CACHE_SIZE]){0}}, .size = ECS_ID_CACHE_SIZE },\ + .removed = { .type = { .count = 0, .array = (ecs_id_t[ECS_ID_CACHE_SIZE]){0}}, .size = ECS_ID_CACHE_SIZE },\ + .on_set = { .type = { .count = 0, .array = (ecs_id_t[ECS_ID_CACHE_SIZE]){0}}, .size = ECS_ID_CACHE_SIZE },\ + .un_set = { .type = { .count = 0, .array = (ecs_id_t[ECS_ID_CACHE_SIZE]){0}}, .size = ECS_ID_CACHE_SIZE },\ +} + +static +void diff_append( + ecs_diff_buffer_t *dst, + ecs_table_diff_t *src) +{ + ids_merge(&dst->added, &src->added); + ids_merge(&dst->removed, &src->removed); + ids_merge(&dst->on_set, &src->on_set); + ids_merge(&dst->un_set, &src->un_set); +} + +static +void diff_free( + ecs_diff_buffer_t *diff) +{ + if (diff->added.type.count > ECS_ID_CACHE_SIZE) { + ecs_os_free(diff->added.type.array); + } + if (diff->removed.type.count > ECS_ID_CACHE_SIZE) { + ecs_os_free(diff->removed.type.array); + } + if (diff->on_set.type.count > ECS_ID_CACHE_SIZE) { + ecs_os_free(diff->on_set.type.array); + } + if (diff->un_set.type.count > ECS_ID_CACHE_SIZE) { + ecs_os_free(diff->un_set.type.array); } +} + +static +ecs_table_diff_t diff_to_table_diff( + ecs_diff_buffer_t *diff) +{ + return (ecs_table_diff_t){ + .added = diff->added.type, + .removed = diff->removed.type, + .on_set = diff->on_set.type, + .un_set = diff->un_set.type + }; +} +static +ecs_table_t* table_append( + ecs_world_t *world, + ecs_table_t *table, + ecs_id_t id, + ecs_diff_buffer_t *diff) +{ + ecs_table_diff_t temp_diff; + table = flecs_table_traverse_add(world, table, &id, &temp_diff); + ecs_check(table != NULL, ECS_INVALID_PARAMETER, NULL); + diff_append(diff, &temp_diff); return table; +error: + return NULL; } -/* Add/remove components based on the parsed expression. This operation is - * slower than traverse_from_expr, but safe to use from a deferred context. */ static -void defer_from_expr( +void flecs_notify( ecs_world_t *world, - ecs_entity_t entity, - const char *name, - const char *expr, - bool is_add, - bool replace_and) + ecs_table_t *table, + ecs_table_t *other_table, + int32_t row, + int32_t count, + ecs_entity_t event, + ecs_type_t *ids, + ecs_entity_t relationship) { - const char *ptr = expr; - if (ptr) { - ecs_term_t term = {0}; - while (ptr[0] && (ptr = ecs_parse_term(world, name, expr, ptr, &term))){ - if (!ecs_term_is_initialized(&term)) { - break; - } + flecs_emit(world, world, &(ecs_event_desc_t){ + .event = event, + .ids = ids, + .table = table, + .other_table = other_table, + .offset = row, + .count = count, + .observable = world, + .relationship = relationship + }); +} - if (ecs_term_finalize(world, &term)) { - return; - } +static +void flecs_instantiate( + ecs_world_t *world, + ecs_entity_t base, + ecs_table_t *table, + int32_t row, + int32_t count); - if (!ecs_id_is_valid(world, term.id)) { - ecs_term_fini(&term); - ecs_parser_error(name, expr, (ptr - expr), - "invalid term for add expression"); - return; - } +static +void flecs_instantiate_slot( + ecs_world_t *world, + ecs_entity_t base, + ecs_entity_t instance, + ecs_entity_t slot_of, + ecs_entity_t slot, + ecs_entity_t child) +{ + if (base == slot_of) { + /* Instance inherits from slot_of, add slot to instance */ + ecs_add_pair(world, instance, slot, child); + } else { + /* Slot is registered for other prefab, travel hierarchy + * upwards to find instance that inherits from slot_of */ + ecs_entity_t parent = instance; + int32_t depth = 0; + do { + if (ecs_has_pair(world, parent, EcsIsA, slot_of)) { + const char *name = ecs_get_name(world, slot); + if (name == NULL) { + char *slot_of_str = ecs_get_fullpath(world, slot_of); + ecs_throw(ECS_INVALID_OPERATION, "prefab '%s' has unnamed " + "slot (slots must be named)", slot_of_str); + ecs_os_free(slot_of_str); + return; + } - if (term.oper == EcsAnd || !replace_and) { - /* Regular AND expression */ - if (is_add) { - ecs_add_id(world, entity, term.id); + /* The 'slot' variable is currently pointing to a child (or + * grandchild) of the current base. Find the original slot by + * looking it up under the prefab it was registered. */ + if (depth == 0) { + /* If the current instance is an instance of slot_of, just + * lookup the slot by name, which is faster than having to + * create a relative path. */ + slot = ecs_lookup_child(world, slot_of, name); } else { - ecs_remove_id(world, entity, term.id); + /* If the slot is more than one level away from the slot_of + * parent, use a relative path to find the slot */ + char *path = ecs_get_path_w_sep(world, parent, child, ".", + NULL); + slot = ecs_lookup_path_w_sep(world, slot_of, path, ".", + NULL, false); + ecs_os_free(path); + } + + if (slot == 0) { + char *slot_of_str = ecs_get_fullpath(world, slot_of); + char *slot_str = ecs_get_fullpath(world, slot); + ecs_throw(ECS_INVALID_OPERATION, + "'%s' is not in hierarchy for slot '%s'", + slot_of_str, slot_str); + ecs_os_free(slot_of_str); + ecs_os_free(slot_str); } + + ecs_add_pair(world, parent, slot, child); + break; } - ecs_term_fini(&term); + depth ++; + } while ((parent = ecs_get_target(world, parent, EcsChildOf, 0))); + + if (parent == 0) { + char *slot_of_str = ecs_get_fullpath(world, slot_of); + char *slot_str = ecs_get_fullpath(world, slot); + ecs_throw(ECS_INVALID_OPERATION, + "'%s' is not in hierarchy for slot '%s'", + slot_of_str, slot_str); + ecs_os_free(slot_of_str); + ecs_os_free(slot_str); } } + +error: + return; } -#endif -/* If operation is not deferred, add components by finding the target - * table and moving the entity towards it. */ -static -int traverse_add( +static +void flecs_instantiate_children( ecs_world_t *world, - ecs_entity_t result, - const char *name, - const ecs_entity_desc_t *desc, - ecs_entity_t scope, - ecs_id_t with, - bool new_entity, - bool name_assigned) + ecs_entity_t base, + ecs_table_t *table, + int32_t row, + int32_t count, + ecs_table_t *child_table) { - const char *sep = desc->sep; - const char *root_sep = desc->root_sep; - - /* Find existing table */ - ecs_table_t *src_table = NULL, *table = NULL; - ecs_record_t *r = NULL; - if (!new_entity) { - r = flecs_entities_get(world, result); - if (r) { - table = r->table; - } + if (!ecs_table_count(child_table)) { + return; } - /* Find destination table */ - ecs_diff_buffer_t diff = ECS_DIFF_INIT; + ecs_type_t type = child_table->type; + ecs_data_t *child_data = &child_table->data; - /* If this is a new entity without a name, add the scope. If a name is - * provided, the scope will be added by the add_path_w_sep function */ - if (new_entity) { - if (new_entity && scope && !name && !name_assigned) { - table = table_append( - world, table, ecs_pair(EcsChildOf, scope), &diff); - } - if (with) { - table = table_append(world, table, with, &diff); - } - } + ecs_entity_t slot_of = 0; + ecs_entity_t *ids = type.array; + int32_t type_count = type.count; - /* If a name is provided but not yet assigned, add the Name component */ - if (name && !name_assigned) { - table = table_append(world, table, - ecs_pair(ecs_id(EcsIdentifier), EcsName), &diff); - } + /* Instantiate child table for each instance */ - /* Add components from the 'add' id array */ - int32_t i = 0; - ecs_id_t id; - const ecs_id_t *ids = desc->add; - while ((i < ECS_ID_CACHE_SIZE) && (id = ids[i ++])) { - bool should_add = true; - if (ECS_HAS_ID_FLAG(id, PAIR) && ECS_PAIR_FIRST(id) == EcsChildOf) { - scope = ECS_PAIR_SECOND(id); - if ((!desc->id && desc->name) || (name && !name_assigned)) { - /* If name is added to entity, pass scope to add_path instead - * of adding it to the table. The provided name may have nested - * elements, in which case the parent provided here is not the - * parent the entity will end up with. */ - should_add = false; + /* Create component array for creating the table */ + ecs_type_t components = { + .array = ecs_os_alloca_n(ecs_entity_t, type_count + 1) + }; + + void **component_data = ecs_os_alloca_n(void*, type_count + 1); + + /* Copy in component identifiers. Find the base index in the component + * array, since we'll need this to replace the base with the instance id */ + int j, i, childof_base_index = -1, pos = 0; + for (i = 0; i < type_count; i ++) { + ecs_id_t id = ids[i]; + + /* If id has DontInherit flag don't inherit it, except for the name + * and ChildOf pairs. The name is preserved so applications can lookup + * the instantiated children by name. The ChildOf pair is replaced later + * with the instance parent. */ + if ((id != ecs_pair(ecs_id(EcsIdentifier), EcsName)) && + ECS_PAIR_FIRST(id) != EcsChildOf) + { + if (id == EcsUnion) { + /* This should eventually be handled by the DontInherit property + * but right now there is no way to selectively apply it to + * EcsUnion itself: it would also apply to (Union, *) pairs, + * which would make all union relationships uninheritable. + * + * The reason this is explicitly skipped is so that slot + * instances don't all end up with the Union property. */ + continue; + } + ecs_table_record_t *tr = &child_table->records[i]; + ecs_id_record_t *idr = (ecs_id_record_t*)tr->hdr.cache; + if (idr->flags & EcsIdDontInherit) { + continue; } } - if (should_add) { - table = table_append(world, table, id, &diff); + + /* If child is a slot, keep track of which parent to add it to, but + * don't add slot relationship to child of instance. If this is a child + * of a prefab, keep the SlotOf relationship intact. */ + if (!(table->flags & EcsTableIsPrefab)) { + if (ECS_IS_PAIR(id) && ECS_PAIR_FIRST(id) == EcsSlotOf) { + ecs_assert(slot_of == 0, ECS_INTERNAL_ERROR, NULL); + slot_of = ecs_pair_second(world, id); + continue; + } } - } - /* Add components from the 'add_expr' expression */ - if (desc->add_expr && ecs_os_strcmp(desc->add_expr, "0")) { -#ifdef FLECS_PARSER - bool error = false; - table = traverse_from_expr( - world, table, name, desc->add_expr, &diff, true, &error); - if (error) { - return -1; + /* Keep track of the element that creates the ChildOf relationship with + * the prefab parent. We need to replace this element to make sure the + * created children point to the instance and not the prefab */ + if (ECS_HAS_RELATION(id, EcsChildOf) && (ECS_PAIR_SECOND(id) == base)) { + childof_base_index = pos; } -#else - ecs_abort(ECS_UNSUPPORTED, "parser addon is not available"); -#endif - } - /* Commit entity to destination table */ - if (src_table != table) { - ecs_defer_begin(world); - ecs_table_diff_t table_diff = diff_to_table_diff(&diff); - flecs_commit(world, result, r, table, &table_diff, true, true); - ecs_defer_end(world); - } + int32_t storage_index = ecs_table_type_to_storage_index(child_table, i); + if (storage_index != -1) { + ecs_column_t *column = &child_data->columns[storage_index]; + component_data[pos] = ecs_storage_first(column); + } else { + component_data[pos] = NULL; + } - /* Set name */ - if (name && !name_assigned) { - ecs_add_path_w_sep(world, result, scope, name, sep, root_sep); - ecs_assert(ecs_get_name(world, result) != NULL, - ECS_INTERNAL_ERROR, NULL); + components.array[pos] = id; + pos ++; } - if (desc->symbol && desc->symbol[0]) { - const char *sym = ecs_get_symbol(world, result); - if (sym) { - ecs_assert(!ecs_os_strcmp(desc->symbol, sym), - ECS_INCONSISTENT_NAME, desc->symbol); - } else { - ecs_set_symbol(world, result, desc->symbol); - } + /* Table must contain children of base */ + ecs_assert(childof_base_index != -1, ECS_INTERNAL_ERROR, NULL); + + /* If children are added to a prefab, make sure they are prefabs too */ + if (table->flags & EcsTableIsPrefab) { + components.array[pos] = EcsPrefab; + component_data[pos] = NULL; + pos ++; } - diff_free(&diff); + components.count = pos; - return 0; -} + /* Instantiate the prefab child table for each new instance */ + ecs_entity_t *instances = ecs_storage_first(&table->data.entities); + int32_t child_count = ecs_storage_count(&child_data->entities); + bool has_union = child_table->flags & EcsTableHasUnion; -/* When in deferred mode, we need to add/remove components one by one using - * the regular operations. */ -static -void deferred_add_remove( - ecs_world_t *world, - ecs_entity_t entity, - const char *name, - const ecs_entity_desc_t *desc, - ecs_entity_t scope, - ecs_id_t with, - bool new_entity, - bool name_assigned) -{ - const char *sep = desc->sep; - const char *root_sep = desc->root_sep; + for (i = row; i < count + row; i ++) { + ecs_entity_t instance = instances[i]; + ecs_diff_buffer_t diff = ECS_DIFF_INIT; + ecs_table_t *i_table = NULL; + + /* Replace ChildOf element in the component array with instance id */ + components.array[childof_base_index] = ecs_pair(EcsChildOf, instance); - /* If this is a new entity without a name, add the scope. If a name is - * provided, the scope will be added by the add_path_w_sep function */ - if (new_entity) { - if (new_entity && scope && !name && !name_assigned) { - ecs_add_id(world, entity, ecs_pair(EcsChildOf, scope)); + /* Find or create table */ + for (j = 0; j < components.count; j ++) { + i_table = table_append(world, i_table, components.array[j], &diff); } - if (with) { - ecs_add_id(world, entity, with); - } - } + ecs_assert(i_table != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(i_table->type.count == components.count, + ECS_INTERNAL_ERROR, NULL); - /* Add components from the 'add' id array */ - int32_t i = 0; - ecs_id_t id; - const ecs_id_t *ids = desc->add; - while ((i < ECS_ID_CACHE_SIZE) && (id = ids[i ++])) { - bool defer = true; - if (ECS_HAS_ID_FLAG(id, PAIR) && ECS_PAIR_FIRST(id) == EcsChildOf) { - scope = ECS_PAIR_SECOND(id); - if (!desc->id || (name && !name_assigned)) { - /* New named entities are created by temporarily going out of - * readonly mode to ensure no duplicates are created. */ - defer = false; - } - } - if (defer) { - ecs_add_id(world, entity, id); - } - } + /* The instance is trying to instantiate from a base that is also + * its parent. This would cause the hierarchy to instantiate itself + * which would cause infinite recursion. */ + ecs_entity_t *children = ecs_storage_first(&child_data->entities); - /* Add components from the 'add_expr' expression */ - if (desc->add_expr) { -#ifdef FLECS_PARSER - defer_from_expr(world, entity, name, desc->add_expr, true, true); +#ifdef FLECS_DEBUG + for (j = 0; j < child_count; j ++) { + ecs_entity_t child = children[j]; + ecs_check(child != instance, ECS_INVALID_PARAMETER, NULL); + } #else - ecs_abort(ECS_UNSUPPORTED, "parser addon is not available"); + /* Bit of boilerplate to ensure that we don't get warnings about the + * error label not being used. */ + ecs_check(true, ECS_INVALID_OPERATION, NULL); #endif - } - int32_t thread_count = ecs_get_stage_count(world); + /* Create children */ + int32_t child_row; + ecs_table_diff_t table_diff = diff_to_table_diff(&diff); + const ecs_entity_t *i_children = new_w_data(world, i_table, NULL, + &components, child_count, component_data, false, &child_row, + &table_diff); + diff_free(&diff); - /* Set name */ - if (name && !name_assigned) { - /* To prevent creating two entities with the same name, temporarily go - * out of readonly mode if it's safe to do so. */ - ecs_suspend_readonly_state_t state; - if (thread_count <= 1) { - /* When not running on multiple threads we can temporarily leave - * readonly mode which ensures that we don't accidentally create - * two entities with the same name. */ - ecs_world_t *real_world = flecs_suspend_readonly(world, &state); - ecs_add_path_w_sep(real_world, entity, scope, name, sep, root_sep); - flecs_resume_readonly(real_world, &state); - } else { - /* In multithreaded mode we can't leave readonly mode, which means - * there is a risk of creating two entities with the same name. - * Future improvements will be able to detect this. */ - ecs_add_path_w_sep(world, entity, scope, name, sep, root_sep); + /* If children have union relationships, initialize */ + if (has_union) { + int32_t u, u_count = child_table->sw_count; + for (u = 0; u < u_count; u ++) { + ecs_switch_t *src_sw = &child_table->data.sw_columns[i]; + ecs_switch_t *dst_sw = &i_table->data.sw_columns[i]; + ecs_vector_t *v_src_values = flecs_switch_values(src_sw); + ecs_vector_t *v_dst_values = flecs_switch_values(dst_sw); + uint64_t *src_values = ecs_vector_first(v_src_values, uint64_t); + uint64_t *dst_values = ecs_vector_first(v_dst_values, uint64_t); + for (j = 0; j < child_count; j ++) { + dst_values[j] = src_values[j]; + } + } } - } - /* Set symbol */ - if (desc->symbol) { - const char *sym = ecs_get_symbol(world, entity); - if (!sym || ecs_os_strcmp(sym, desc->symbol)) { - if (thread_count <= 1) { /* See above */ - ecs_suspend_readonly_state_t state; - ecs_world_t *real_world = flecs_suspend_readonly(world, &state); - ecs_set_symbol(world, entity, desc->symbol); - flecs_resume_readonly(real_world, &state); - } else { - ecs_set_symbol(world, entity, desc->symbol); + /* If children are slots, add slot relationships to parent */ + if (slot_of) { + for (j = 0; j < child_count; j ++) { + ecs_entity_t child = children[j]; + ecs_entity_t i_child = i_children[j]; + flecs_instantiate_slot(world, base, instance, slot_of, + child, i_child); } } - } + + /* If prefab child table has children itself, recursively instantiate */ + for (j = 0; j < child_count; j ++) { + ecs_entity_t child = children[j]; + flecs_instantiate(world, child, i_table, child_row + j, 1); + } + } +error: + return; } -ecs_entity_t ecs_entity_init( +static +void flecs_instantiate( ecs_world_t *world, - const ecs_entity_desc_t *desc) + ecs_entity_t base, + ecs_table_t *table, + int32_t row, + int32_t count) { - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(desc->_canary == 0, ECS_INVALID_PARAMETER, NULL); - - ecs_stage_t *stage = flecs_stage_from_world(&world); - ecs_entity_t scope = stage->scope; - ecs_id_t with = ecs_get_with(world); - - const char *name = desc->name; - const char *sep = desc->sep; - if (!sep) { - sep = "."; + ecs_table_t *base_table = ecs_get_table(world, ecs_get_alive(world, base)); + if (!base_table || !(base_table->flags & EcsTableIsPrefab)) { + /* Don't instantiate children from base entities that aren't prefabs */ + return; } - if (name && !name[0]) { - name = NULL; + ecs_run_aperiodic(world, EcsAperiodicEmptyTables); + + ecs_id_record_t *idr = flecs_id_record_get(world, ecs_childof(base)); + ecs_table_cache_iter_t it; + if (idr && flecs_table_cache_iter((ecs_table_cache_t*)idr, &it)) { + const ecs_table_record_t *tr; + while ((tr = flecs_table_cache_next(&it, ecs_table_record_t))) { + flecs_instantiate_children( + world, base, table, row, count, tr->hdr.table); + } } +} - const char *root_sep = desc->root_sep; - bool new_entity = false; - bool name_assigned = false; +static +bool override_component( + ecs_world_t *world, + ecs_entity_t component, + ecs_type_t type, + ecs_table_t *table, + ecs_table_t *other_table, + const ecs_type_info_t *ti, + ecs_column_t *column, + int32_t row, + int32_t count, + bool notify_on_set); - /* Remove optional prefix from name. Entity names can be derived from - * language identifiers, such as components (typenames) and systems - * function names). Because C does not have namespaces, such identifiers - * often encode the namespace as a prefix. - * To ensure interoperability between C and C++ (and potentially other - * languages with namespacing) the entity must be stored without this prefix - * and with the proper namespace, which is what the name_prefix is for */ - const char *prefix = world->info.name_prefix; - if (name && prefix) { - ecs_size_t len = ecs_os_strlen(prefix); - if (!ecs_os_strncmp(name, prefix, len) && - (isupper(name[len]) || name[len] == '_')) - { - if (name[len] == '_') { - name = name + len + 1; - } else { - name = name + len; - } - } +static +bool override_from_base( + ecs_world_t *world, + ecs_entity_t base, + ecs_entity_t component, + ecs_table_t *table, + ecs_table_t *other_table, + const ecs_type_info_t *ti, + ecs_column_t *column, + int32_t row, + int32_t count, + bool notify_on_set) +{ + ecs_assert(component != 0, ECS_INTERNAL_ERROR, NULL); + + ecs_record_t *r = flecs_entities_get(world, base); + ecs_table_t *base_table; + if (!r || !(base_table = r->table)) { + return false; } - /* Find or create entity */ - ecs_entity_t result = desc->id; - if (!result) { - if (name) { - /* If add array contains a ChildOf pair, use it as scope instead */ - const ecs_id_t *ids = desc->add; - ecs_id_t id; - int32_t i = 0; - while ((i < ECS_ID_CACHE_SIZE) && (id = ids[i ++])) { - if (ECS_HAS_ID_FLAG(id, PAIR) && - (ECS_PAIR_FIRST(id) == EcsChildOf)) - { - scope = ECS_PAIR_SECOND(id); - } - } + void *base_ptr = get_component( + world, base_table, ECS_RECORD_TO_ROW(r->row), component); + if (base_ptr) { + int32_t index, data_size = ti->size; + void *data_ptr = ecs_storage_get(column, data_size, row); - result = ecs_lookup_path_w_sep( - world, scope, name, sep, root_sep, false); - if (result) { - name_assigned = true; + ecs_copy_t copy = ti->hooks.copy; + if (copy) { + for (index = 0; index < count; index ++) { + copy(data_ptr, base_ptr, 1, ti); + data_ptr = ECS_OFFSET(data_ptr, data_size); } + } else { + for (index = 0; index < count; index ++) { + ecs_os_memcpy(data_ptr, base_ptr, data_size); + data_ptr = ECS_OFFSET(data_ptr, data_size); + } } - if (!result) { - if (desc->use_low_id) { - result = ecs_new_low_id(world); - } else { - result = ecs_new_id(world); - } - new_entity = true; - ecs_assert(ecs_get_type(world, result) == NULL, - ECS_INTERNAL_ERROR, NULL); - } - } else { - /* Make sure provided id is either alive or revivable */ - ecs_ensure(world, result); + ecs_type_t ids = { + .array = (ecs_id_t[]){ component }, + .count = 1 + }; - name_assigned = ecs_has_pair( - world, result, ecs_id(EcsIdentifier), EcsName); - if (name && name_assigned) { - /* If entity has name, verify that name matches. The name provided - * to the function could either have been relative to the current - * scope, or fully qualified. */ - char *path; - ecs_size_t root_sep_len = root_sep ? ecs_os_strlen(root_sep) : 0; - if (root_sep && !ecs_os_strncmp(name, root_sep, root_sep_len)) { - /* Fully qualified name was provided, so make sure to - * compare with fully qualified name */ - path = ecs_get_path_w_sep(world, 0, result, sep, root_sep); - } else { - /* Relative name was provided, so make sure to compare with - * relative name */ - path = ecs_get_path_w_sep(world, scope, result, sep, ""); - } - if (path) { - if (ecs_os_strcmp(path, name)) { - /* Mismatching name */ - ecs_err("existing entity '%s' is initialized with " - "conflicting name '%s'", path, name); - ecs_os_free(path); - return 0; - } - ecs_os_free(path); + if (notify_on_set) { + /* Check if the component was available for the previous table. If + * the override is caused by an add operation, it does not introduce + * a new component value, and the application should not be + * notified. + * + * If the override is the result if adding a IsA relationship + * with an entity that has components with the OVERRIDE flag, an + * event should be generated, since this represents a new component + * (and component value) for the entity. + * + * Note that this is an edge case, regular (self) triggers won't be + * notified because the event id is not the component but an IsA + * relationship. Superset triggers will not be invoked because the + * component is owned. */ + int32_t c = ecs_search_relation(world, other_table, 0, component, + EcsIsA, EcsUp, 0, 0, 0); + if (c == -1) { + flecs_notify( + world, table, other_table, row, count, EcsOnSet, &ids, 0); } } - } - - ecs_assert(name_assigned == ecs_has_pair( - world, result, ecs_id(EcsIdentifier), EcsName), - ECS_INTERNAL_ERROR, NULL); - if (stage->defer) { - deferred_add_remove((ecs_world_t*)stage, result, name, desc, - scope, with, new_entity, name_assigned); + return true; } else { - if (traverse_add(world, result, name, desc, - scope, with, new_entity, name_assigned)) - { - return 0; - } + /* If component not found on base, check if base itself inherits */ + ecs_type_t base_type = base_table->type; + return override_component(world, component, base_type, table, + other_table, ti, column, row, count, notify_on_set); } - - return result; -error: - return 0; } -const ecs_entity_t* ecs_bulk_init( +static +bool override_component( ecs_world_t *world, - const ecs_bulk_desc_t *desc) + ecs_entity_t component, + ecs_type_t type, + ecs_table_t *table, + ecs_table_t *other_table, + const ecs_type_info_t *ti, + ecs_column_t *column, + int32_t row, + int32_t count, + bool notify_on_set) { - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(desc->_canary == 0, ECS_INVALID_PARAMETER, NULL); + ecs_entity_t *type_array = type.array; + int32_t i, type_count = type.count; - const ecs_entity_t *entities = desc->entities; - int32_t count = desc->count; + /* Walk prefabs */ + i = type_count - 1; + do { + ecs_entity_t e = type_array[i]; - int32_t sparse_count = 0; - if (!entities) { - sparse_count = flecs_entities_count(world); - entities = flecs_sparse_new_ids(ecs_eis(world), count); - ecs_assert(entities != NULL, ECS_INTERNAL_ERROR, NULL); - } else { - int i; - for (i = 0; i < count; i ++) { - ecs_ensure(world, entities[i]); + if (!(e & ECS_ID_FLAGS_MASK)) { + break; } - } - ecs_type_t ids; - ecs_table_t *table = desc->table; - ecs_diff_buffer_t diff = ECS_DIFF_INIT; - if (!table) { - int32_t i = 0; - ecs_id_t id; - while ((id = desc->ids[i])) { - table = table_append(world, table, id, &diff); - i ++; + if (ECS_HAS_RELATION(e, EcsIsA)) { + if (override_from_base(world, ecs_pair_second(world, e), component, + table, other_table, ti, column, row, count, notify_on_set)) + { + return true; + } } + } while (--i >= 0); - ids.array = (ecs_id_t*)desc->ids; - ids.count = i; - } else { - diff.added.type.array = table->type.array; - diff.added.type.count = table->type.count; - ids = diff.added.type; - } - - ecs_table_diff_t table_diff = diff_to_table_diff(&diff); - new_w_data(world, table, entities, &ids, count, desc->data, true, NULL, - &table_diff); - - if (!sparse_count) { - return entities; - } else { - /* Refetch entity ids, in case the underlying array was reallocated */ - entities = flecs_sparse_ids(ecs_eis(world)); - return &entities[sparse_count]; - } -error: - return NULL; + return false; } -ecs_entity_t ecs_component_init( +static +void components_override( ecs_world_t *world, - const ecs_component_desc_t *desc) + ecs_table_t *table, + ecs_table_t *other_table, + int32_t row, + int32_t count, + ecs_type_t *added, + bool notify_on_set) { - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(desc->_canary == 0, ECS_INVALID_PARAMETER, NULL); + ecs_column_t *columns = table->data.columns; + ecs_type_t type = table->type; + ecs_table_t *storage_table = table->storage_table; - ecs_suspend_readonly_state_t readonly_state; - world = flecs_suspend_readonly(world, &readonly_state); + int i; + for (i = 0; i < added->count; i ++) { + ecs_entity_t id = added->array[i]; - ecs_entity_t result = desc->entity; - if (!result) { - result = ecs_new_low_id(world); - } + if (ECS_HAS_RELATION(id, EcsIsA)) { + ecs_entity_t base = ECS_PAIR_SECOND(id); - EcsComponent *ptr = ecs_get_mut(world, result, EcsComponent); - if (!ptr->size) { - ecs_assert(ptr->alignment == 0, ECS_INTERNAL_ERROR, NULL); - ptr->size = desc->type.size; - ptr->alignment = desc->type.alignment; - if (!ptr->size) { - ecs_trace("#[green]tag#[reset] %s created", - ecs_get_name(world, result)); - } else { - ecs_trace("#[green]component#[reset] %s created", - ecs_get_name(world, result)); + /* Cannot inherit from base if base is final */ + ecs_check(!ecs_has_id(world, ecs_get_alive(world, base), EcsFinal), + ECS_CONSTRAINT_VIOLATED, NULL); + ecs_check(base != 0, ECS_INVALID_PARAMETER, NULL); + + if (!world->stages[0].base) { + /* Setting base prevents instantiating the hierarchy multiple + * times. The instantiate function recursively iterates the + * hierarchy to instantiate children. While this is happening, + * new tables are created which end up calling this function, + * which would call instantiate multiple times for the same + * level in the hierarchy. */ + world->stages[0].base = base; + flecs_instantiate(world, base, table, row, count); + world->stages[0].base = 0; + } } - } else { - if (ptr->size != desc->type.size) { - char *path = ecs_get_fullpath(world, result); - ecs_abort(ECS_INVALID_COMPONENT_SIZE, path); - ecs_os_free(path); + + if (!storage_table) { + continue; } - if (ptr->alignment != desc->type.alignment) { - char *path = ecs_get_fullpath(world, result); - ecs_abort(ECS_INVALID_COMPONENT_ALIGNMENT, path); - ecs_os_free(path); + + ecs_table_record_t *tr = flecs_table_record_get( + world, storage_table, id); + if (!tr) { + continue; } - } - ecs_modified(world, result, EcsComponent); + ecs_id_record_t *idr = (ecs_id_record_t*)tr->hdr.cache; + if (idr->flags & EcsIdDontInherit) { + continue; + } - if (desc->type.size && - !ecs_id_in_use(world, result) && - !ecs_id_in_use(world, ecs_pair(result, EcsWildcard))) - { - ecs_set_hooks_id(world, result, &desc->type.hooks); - } + const ecs_type_info_t *ti = idr->type_info; + if (!ti->size) { + continue; + } - if (result >= world->info.last_component_id && result < ECS_HI_COMPONENT_ID) { - world->info.last_component_id = result + 1; + ecs_column_t *column = &columns[tr->column]; + override_component(world, id, type, table, other_table, ti, + column, row, count, notify_on_set); } - - /* Ensure components cannot be deleted */ - ecs_add_pair(world, result, EcsOnDelete, EcsPanic); - - flecs_resume_readonly(world, &readonly_state); - - ecs_assert(result != 0, ECS_INTERNAL_ERROR, NULL); - ecs_assert(ecs_has(world, result, EcsComponent), ECS_INTERNAL_ERROR, NULL); - - return result; error: - return 0; + return; } -const ecs_entity_t* ecs_bulk_new_w_id( +static +void flecs_set_union( ecs_world_t *world, - ecs_id_t id, - int32_t count) + ecs_table_t *table, + int32_t row, + int32_t count, + ecs_type_t *ids, + bool reset) { - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_stage_t *stage = flecs_stage_from_world(&world); + ecs_id_t *array = ids->array; + int32_t i, id_count = ids->count; - const ecs_entity_t *ids; - if (flecs_defer_bulk_new(world, stage, count, id, &ids)) { - return ids; - } + for (i = 0; i < id_count; i ++) { + ecs_id_t id = array[i]; - ecs_table_t *table = &world->store.root; - ecs_diff_buffer_t diff = ECS_DIFF_INIT; - - if (id) { - table = table_append(world, table, id, &diff); - } + if (ECS_HAS_ID_FLAG(id, PAIR)) { + ecs_id_record_t *idr = flecs_id_record_get(world, + ecs_pair(EcsUnion, ECS_PAIR_FIRST(id))); + if (!idr) { + continue; + } - ecs_table_diff_t td = diff_to_table_diff(&diff); - ids = new_w_data(world, table, NULL, NULL, count, NULL, false, NULL, &td); - flecs_defer_end(world, stage); + const ecs_table_record_t *tr = flecs_id_record_get_table( + idr, table); + ecs_assert(tr != NULL, ECS_INTERNAL_ERROR, NULL); + int32_t column = tr->column - table->sw_offset; + ecs_switch_t *sw = &table->data.sw_columns[column]; + ecs_entity_t union_case = 0; + if (!reset) { + union_case = ECS_PAIR_SECOND(id); + } - return ids; -error: - return NULL; + int32_t r; + for (r = 0; r < count; r ++) { + flecs_switch_set(sw, row + r, union_case); + } + } + } } -void ecs_clear( +static +void flecs_add_remove_union( ecs_world_t *world, - ecs_entity_t entity) + ecs_table_t *table, + int32_t row, + int32_t count, + ecs_type_t *added, + ecs_type_t *removed) { - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(ecs_is_valid(world, entity), ECS_INVALID_PARAMETER, NULL); + if (added) { + flecs_set_union(world, table, row, count, added, false); + } + if (removed) { + flecs_set_union(world, table, row, count, removed, true); + } +} - ecs_stage_t *stage = flecs_stage_from_world(&world); - if (flecs_defer_clear(world, stage, entity)) { - return; +static +ecs_record_t* new_entity( + ecs_world_t *world, + ecs_entity_t entity, + ecs_record_t *record, + ecs_table_t *new_table, + ecs_table_diff_t *diff, + bool construct, + bool notify_on_set) +{ + int32_t new_row; + + if (!record) { + record = flecs_entities_ensure(world, entity); } - ecs_record_t *r = flecs_entities_get(world, entity); - if (!r) { - return; /* Nothing to clear */ + new_row = flecs_table_append(world, new_table, entity, record, + construct, true); + + record->table = new_table; + record->row = ECS_ROW_TO_RECORD(new_row, record->row & ECS_ROW_FLAGS_MASK); + + ecs_data_t *new_data = &new_table->data; + ecs_assert(ecs_storage_count(&new_data[0].entities) > new_row, + ECS_INTERNAL_ERROR, NULL); + (void)new_data; + + ecs_flags32_t flags = new_table->flags; + + if (flags & EcsTableHasAddActions) { + flecs_notify_on_add( + world, new_table, NULL, new_row, 1, diff, notify_on_set); } - ecs_table_t *table = r->table; - if (table) { - if (r->row & EcsEntityObservedAcyclic) { - table->observed_count --; - } + return record; +} - ecs_table_diff_t diff = { - .removed = table->type, - .un_set = { table->storage_ids, table->storage_count } - }; +static +void move_entity( + ecs_world_t *world, + ecs_entity_t entity, + ecs_record_t *record, + ecs_table_t *dst_table, + ecs_table_diff_t *diff, + bool construct, + bool notify_on_set) +{ + ecs_table_t *src_table = record->table; + int32_t src_row = ECS_RECORD_TO_ROW(record->row); + + ecs_assert(src_table != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(src_table != dst_table, ECS_INTERNAL_ERROR, NULL); + ecs_assert(src_row >= 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(ecs_storage_count(&src_table->data.entities) > src_row, + ECS_INTERNAL_ERROR, NULL); + ecs_check(ecs_is_alive(world, entity), ECS_INVALID_PARAMETER, NULL); + ecs_assert(record != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(record == flecs_entities_get(world, entity), ECS_INTERNAL_ERROR, NULL); - delete_entity(world, r, &diff); - r->table = NULL; - } + int32_t dst_row = flecs_table_append(world, dst_table, entity, + record, false, false); + + /* Copy entity & components from src_table to dst_table */ + if (src_table->type.count) { + flecs_notify_on_remove(world, src_table, dst_table, + ECS_RECORD_TO_ROW(src_row), 1, diff); + + flecs_table_move(world, entity, entity, dst_table, dst_row, + src_table, src_row, construct); + } + + /* Update entity index & delete old data after running remove actions */ + record->table = dst_table; + record->row = ECS_ROW_TO_RECORD(dst_row, record->row & ECS_ROW_FLAGS_MASK); + + flecs_table_delete(world, src_table, src_row, false); + + /* If components were added, invoke add actions */ + ecs_flags32_t dst_flags = dst_table->flags; + if (src_table != dst_table || diff->added.count) { + if (diff->added.count && (dst_flags & EcsTableHasAddActions)) { + flecs_notify_on_add(world, dst_table, src_table, dst_row, 1, diff, + notify_on_set); + } + } - flecs_defer_end(world, stage); error: return; } static -void flecs_throw_invalid_delete( +void delete_entity( ecs_world_t *world, - ecs_id_t id) -{ - char *id_str = NULL; - if (!(world->flags & EcsWorldQuit)) { - id_str = ecs_id_str(world, id); - ecs_throw(ECS_CONSTRAINT_VIOLATED, id_str); + ecs_record_t *record, + ecs_table_diff_t *diff) +{ + ecs_table_t *table = record->table; + int32_t row = ECS_RECORD_TO_ROW(record->row); + + /* Invoke remove actions before deleting */ + if (table->flags & EcsTableHasRemoveActions) { + flecs_notify_on_remove(world, table, NULL, row, 1, diff); } -error: - ecs_os_free(id_str); + + flecs_table_delete(world, table, row, true); } +/* Updating component monitors is a relatively expensive operation that only + * happens for entities that are monitored. The approach balances the amount of + * processing between the operation on the entity vs the amount of work that + * needs to be done to rematch queries, as a simple brute force approach does + * not scale when there are many tables / queries. Therefore we need to do a bit + * of bookkeeping that is more intelligent than simply flipping a flag */ static -void flecs_marked_id_push( +void update_component_monitor_w_array( ecs_world_t *world, - ecs_id_record_t* idr, - ecs_entity_t action) + ecs_type_t *ids) { - ecs_marked_id_t *m = ecs_vector_add(&world->store.marked_ids, - ecs_marked_id_t); + if (!ids) { + return; + } - m->idr = idr; - m->id = idr->id; - m->action = action; + int i; + for (i = 0; i < ids->count; i ++) { + ecs_entity_t id = ids->array[i]; + if (ECS_HAS_ID_FLAG(id, PAIR)) { + flecs_monitor_mark_dirty(world, + ecs_pair(ECS_PAIR_FIRST(id), EcsWildcard)); + } - flecs_id_record_claim(world, idr); + flecs_monitor_mark_dirty(world, id); + } } static -void flecs_id_mark_for_delete( +void update_component_monitors( ecs_world_t *world, - ecs_id_record_t *idr, - ecs_entity_t action); + ecs_type_t *added, + ecs_type_t *removed) +{ + update_component_monitor_w_array(world, added); + update_component_monitor_w_array(world, removed); +} static -void flecs_targets_mark_for_delete( +void flecs_commit( ecs_world_t *world, - ecs_table_t *table) + ecs_entity_t entity, + ecs_record_t *record, + ecs_table_t *dst_table, + ecs_table_diff_t *diff, + bool construct, + bool notify_on_set) { - ecs_id_record_t *idr; - ecs_entity_t *entities = ecs_storage_first(&table->data.entities); - ecs_record_t **records = ecs_storage_first(&table->data.records); - int32_t i, count = ecs_storage_count(&table->data.entities); - for (i = 0; i < count; i ++) { - ecs_record_t *r = records[i]; - if (!r) { - continue; - } + ecs_assert(!(world->flags & EcsWorldReadonly), ECS_INTERNAL_ERROR, NULL); + + ecs_table_t *src_table = NULL; + uint32_t row_flags = 0; + bool observed = false; + if (record) { + src_table = record->table; + row_flags = record->row & ECS_ROW_FLAGS_MASK; + observed = row_flags & EcsEntityObservedAcyclic; + } - /* If entity is not used as id or as relationship target, there won't - * be any tables with a reference to it. */ - ecs_flags32_t flags = r->row & ECS_ROW_FLAGS_MASK; - if (!(flags & (EcsEntityObservedId|EcsEntityObservedTarget))) { - continue; + if (src_table == dst_table) { + /* If source and destination table are the same no action is needed * + * However, if a component was added in the process of traversing a + * table, this suggests that a case switch could have occured. */ + if (((diff->added.count) || (diff->removed.count)) && + src_table && src_table->flags & EcsTableHasUnion) + { + flecs_add_remove_union(world, src_table, + ECS_RECORD_TO_ROW(record->row), 1, + &diff->added, &diff->removed); } - ecs_entity_t e = entities[i]; - if (flags & EcsEntityObservedId) { - if ((idr = flecs_id_record_get(world, e))) { - flecs_id_mark_for_delete(world, idr, - ECS_ID_ON_DELETE(idr->flags)); - } - if ((idr = flecs_id_record_get(world, ecs_pair(e, EcsWildcard)))) { - flecs_id_mark_for_delete(world, idr, - ECS_ID_ON_DELETE(idr->flags)); - } + return; + } + + if (src_table) { + ecs_assert(dst_table != NULL, ECS_INTERNAL_ERROR, NULL); + dst_table->observed_count += observed; + + if (dst_table->type.count) { + move_entity(world, entity, record, dst_table, diff, + construct, notify_on_set); + } else { + delete_entity(world, record, diff); + record->table = NULL; } - if (flags & EcsEntityObservedTarget) { - if ((idr = flecs_id_record_get(world, ecs_pair(EcsWildcard, e)))) { - flecs_id_mark_for_delete(world, idr, - ECS_ID_ON_DELETE_OBJECT(idr->flags)); - } - if ((idr = flecs_id_record_get(world, ecs_pair(EcsFlag, e)))) { - flecs_id_mark_for_delete(world, idr, - ECS_ID_ON_DELETE_OBJECT(idr->flags)); - } + + src_table->observed_count -= observed; + } else { + dst_table->observed_count += observed; + if (dst_table->type.count) { + new_entity(world, entity, record, dst_table, diff, + construct, notify_on_set); } } -} -static -bool flecs_id_is_delete_target( - ecs_id_t id, - ecs_entity_t action) -{ - if (!action && ecs_id_is_pair(id) && ECS_PAIR_FIRST(id) == EcsWildcard) { - /* If no explicit delete action is provided, and the id we're deleting - * has the form (*, Target), use OnDeleteTarget action */ - return true; + /* If the entity is being watched, it is being monitored for changes and + * requires rematching systems when components are added or removed. This + * ensures that systems that rely on components from containers or prefabs + * update the matched tables when the application adds or removes a + * component from, for example, a container. */ + if (row_flags) { + update_component_monitors(world, &diff->added, &diff->removed); } - return false; -} -static -ecs_entity_t flecs_get_delete_action( - ecs_table_t *table, - ecs_table_record_t *tr, - ecs_entity_t action, - bool delete_target) -{ - ecs_entity_t result = action; - if (!result && delete_target) { - /* If action is not specified and we're deleting a relationship target, - * derive the action from the current record */ - ecs_table_record_t *trr = &table->records[tr->column]; - ecs_id_record_t *idrr = (ecs_id_record_t*)trr->hdr.cache; - result = ECS_ID_ON_DELETE_OBJECT(idrr->flags); - } - return result; + if ((!src_table || !src_table->type.count) && world->range_check_enabled) { + ecs_check(!world->info.max_id || entity <= world->info.max_id, + ECS_OUT_OF_RANGE, 0); + ecs_check(entity >= world->info.min_id, + ECS_OUT_OF_RANGE, 0); + } +error: + return; } static -void flecs_update_monitors_for_delete( +void new( ecs_world_t *world, - ecs_id_t id) + ecs_entity_t entity, + ecs_type_t *to_add) { - update_component_monitors(world, NULL, &(ecs_type_t){ - .array = (ecs_id_t[]){id}, - .count = 1 - }); + int32_t i, count = to_add->count; + ecs_table_t *table = &world->store.root; + + ecs_diff_buffer_t diff = ECS_DIFF_INIT; + for (i = 0; i < count; i ++) { + table = table_append(world, table, to_add->array[i], &diff); + } + + ecs_table_diff_t table_diff = diff_to_table_diff(&diff); + new_entity(world, entity, NULL, table, &table_diff, true, true); + diff_free(&diff); } static -void flecs_id_mark_for_delete( +const ecs_entity_t* new_w_data( ecs_world_t *world, - ecs_id_record_t *idr, - ecs_entity_t action) + ecs_table_t *table, + const ecs_entity_t *entities, + ecs_type_t *component_ids, + int32_t count, + void **component_data, + bool is_move, + int32_t *row_out, + ecs_table_diff_t *diff) { - if (idr->flags & EcsIdMarkedForDelete) { - return; + int32_t sparse_count = 0; + if (!entities) { + sparse_count = flecs_entities_count(world); + entities = flecs_sparse_new_ids(ecs_eis(world), count); } - idr->flags |= EcsIdMarkedForDelete; - flecs_marked_id_push(world, idr, action); + if (!table) { + return entities; + } - ecs_id_t id = idr->id; + ecs_type_t type = table->type; + if (!type.count) { + return entities; + } - bool delete_target = flecs_id_is_delete_target(id, action); + ecs_type_t component_array = { 0 }; + if (!component_ids) { + component_ids = &component_array; + component_array.array = type.array; + component_array.count = type.count; + } - /* Mark all tables with the id for delete */ - ecs_table_cache_iter_t it; - if (flecs_table_cache_iter(&idr->cache, &it)) { - ecs_table_record_t *tr; - while ((tr = flecs_table_cache_next(&it, ecs_table_record_t))) { - ecs_table_t *table = tr->hdr.table; - if (table->flags & EcsTableMarkedForDelete) { + ecs_data_t *data = &table->data; + int32_t row = flecs_table_appendn(world, table, data, count, entities); + + /* Update entity index. */ + int i; + ecs_record_t **records = ecs_storage_first(&data->records); + for (i = 0; i < count; i ++) { + records[row + i] = flecs_entities_set(world, entities[i], + &(ecs_record_t){ + .table = table, + .row = ECS_ROW_TO_RECORD(row + i, 0) + }); + } + + flecs_defer_begin(world, &world->stages[0]); + + flecs_notify_on_add(world, table, NULL, row, count, diff, + component_data == NULL); + + if (component_data) { + /* Set components that we're setting in the component mask so the init + * actions won't call OnSet triggers for them. This ensures we won't + * call OnSet triggers multiple times for the same component */ + int32_t c_i; + ecs_table_t *storage_table = table->storage_table; + for (c_i = 0; c_i < component_ids->count; c_i ++) { + void *src_ptr = component_data[c_i]; + if (!src_ptr) { continue; } - ecs_entity_t cur_action = flecs_get_delete_action(table, tr, action, - delete_target); + /* Find component in storage type */ + ecs_entity_t id = component_ids->array[c_i]; + const ecs_table_record_t *tr = flecs_table_record_get( + world, storage_table, id); + ecs_assert(tr != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(tr->count == 1, ECS_INTERNAL_ERROR, NULL); - /* If this is a Delete action, recursively mark ids & tables */ - if (cur_action == EcsDelete) { - table->flags |= EcsTableMarkedForDelete; - ecs_log_push_2(); - flecs_targets_mark_for_delete(world, table); - ecs_log_pop_2(); - } else if (cur_action == EcsPanic) { - flecs_throw_invalid_delete(world, id); - } - } - } + int32_t index = tr->column; + ecs_type_info_t *ti = table->type_info[index]; + ecs_column_t *column = &table->data.columns[index]; + int32_t size = ti->size; + ecs_assert(size != 0, ECS_INTERNAL_ERROR, NULL); + void *ptr = ecs_storage_get(column, size, row); - /* Same for empty tables */ - if (flecs_table_cache_empty_iter(&idr->cache, &it)) { - ecs_table_record_t *tr; - while ((tr = flecs_table_cache_next(&it, ecs_table_record_t))) { - tr->hdr.table->flags |= EcsTableMarkedForDelete; - } + ecs_copy_t copy; + ecs_move_t move; + if (is_move && (move = ti->hooks.move)) { + move(ptr, src_ptr, count, ti); + } else if (!is_move && (copy = ti->hooks.copy)) { + copy(ptr, src_ptr, count, ti); + } else { + ecs_os_memcpy(ptr, src_ptr, size * count); + } + }; + + flecs_notify_on_set(world, table, row, count, NULL, true); + flecs_notify_on_set(world, table, row, count, &diff->on_set, false); } - /* Signal query cache monitors */ - flecs_update_monitors_for_delete(world, id); + flecs_defer_end(world, &world->stages[0]); - /* If id is a wildcard pair, update cache monitors for non-wildcard ids */ - if (ecs_id_is_wildcard(id)) { - ecs_assert(ECS_HAS_ID_FLAG(id, PAIR), ECS_INTERNAL_ERROR, NULL); - ecs_id_record_t *cur = idr; - if (ECS_PAIR_SECOND(id) == EcsWildcard) { - while ((cur = cur->first.next)) { - flecs_update_monitors_for_delete(world, cur->id); - } - } else { - ecs_assert(ECS_PAIR_FIRST(id) == EcsWildcard, - ECS_INTERNAL_ERROR, NULL); - while ((cur = cur->second.next)) { - flecs_update_monitors_for_delete(world, cur->id); - } - } + if (row_out) { + *row_out = row; + } + + if (sparse_count) { + entities = flecs_sparse_ids(ecs_eis(world)); + return &entities[sparse_count]; + } else { + return entities; } } static -bool flecs_on_delete_mark( +void add_id_w_record( ecs_world_t *world, + ecs_entity_t entity, + ecs_record_t *record, ecs_id_t id, - ecs_entity_t action) + bool construct) { - ecs_id_record_t *idr = flecs_id_record_get(world, id); - if (!idr) { - /* If there's no id record, there's nothing to delete */ - return false; - } - - if (!action) { - /* If no explicit action is provided, derive it */ - if (!ecs_id_is_pair(id) || ECS_PAIR_SECOND(id) == EcsWildcard) { - /* Delete actions are determined by the component, or in the case - * of a pair by the relationship. */ - action = ECS_ID_ON_DELETE(idr->flags); - } - } + ecs_table_diff_t diff; - if (action == EcsPanic) { - /* This id is protected from deletion */ - flecs_throw_invalid_delete(world, id); - return false; + ecs_table_t *src_table = NULL; + if (record) { + src_table = record->table; } - flecs_id_mark_for_delete(world, idr, action); + ecs_table_t *dst_table = flecs_table_traverse_add( + world, src_table, &id, &diff); - return true; + flecs_commit(world, entity, record, dst_table, &diff, construct, + false); /* notify_on_set = false, this function is only called from + * functions that are about to set the component. */ } static -void flecs_remove_from_table( - ecs_world_t *world, - ecs_table_t *table) +void add_id( + ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t id) { - ecs_table_diff_t temp_diff; - ecs_diff_buffer_t diff = ECS_DIFF_INIT; - ecs_table_t *dst_table = table; + ecs_stage_t *stage = flecs_stage_from_world(&world); - /* To find the dst table, remove all ids that are marked for deletion */ - int32_t i, t, count = ecs_vector_count(world->store.marked_ids); - ecs_marked_id_t *ids = ecs_vector_first(world->store.marked_ids, - ecs_marked_id_t); - const ecs_table_record_t *tr; + if (flecs_defer_add(world, stage, entity, id)) { + return; + } - for (i = 0; i < count; i ++) { - const ecs_id_record_t *idr = ids[i].idr; + ecs_record_t *r = flecs_entities_ensure(world, entity); + ecs_table_diff_t diff; + ecs_table_t *src_table = r->table; + ecs_table_t *dst_table = flecs_table_traverse_add( + world, src_table, &id, &diff); - if (!(tr = flecs_id_record_get_table(idr, dst_table))) { - continue; - } + flecs_commit(world, entity, r, dst_table, &diff, true, true); - t = tr->column; + flecs_defer_end(world, stage); +} - do { - ecs_id_t id = dst_table->type.array[t]; - dst_table = flecs_table_traverse_remove( - world, dst_table, &id, &temp_diff); - diff_append(&diff, &temp_diff); - } while (dst_table->type.count && (t = ecs_search_offset( - world, dst_table, t, idr->id, NULL)) != -1); +static +void remove_id( + ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t id) +{ + ecs_stage_t *stage = flecs_stage_from_world(&world); + + if (flecs_defer_remove(world, stage, entity, id)) { + return; } - ecs_assert(dst_table != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_record_t *r = flecs_entities_get(world, entity); + ecs_table_t *src_table = NULL; + if (!r || !(src_table = r->table)) { + goto done; /* Nothing to remove */ + } - if (!dst_table->type.count) { - /* If this removes all components, clear table */ - ecs_dbg_3("#[red]clear#[reset] entities from table %u", - (uint32_t)table->id); - flecs_table_clear_entities(world, table); - } else { - /* Otherwise, merge table into dst_table */ - ecs_dbg_3("#[red]move#[reset] entities from table %u to %u", - (uint32_t)table->id, (uint32_t)dst_table->id); + ecs_table_diff_t diff; + ecs_table_t *dst_table = flecs_table_traverse_remove( + world, src_table, &id, &diff); - if (dst_table != table) { - if (diff.removed.type.count) { - ecs_log_push_3(); - ecs_table_diff_t td = diff_to_table_diff(&diff); - flecs_notify_on_remove(world, table, NULL, - 0, ecs_table_count(table), &td); - ecs_log_pop_3(); - } - flecs_table_merge(world, dst_table, table, - &dst_table->data, &table->data); - } - } + flecs_commit(world, entity, r, dst_table, &diff, true, true); - diff_free(&diff); +done: + flecs_defer_end(world, stage); } static -bool flecs_on_delete_clear_tables( - ecs_world_t *world) +void *flecs_get_mut( + ecs_world_t *world, + ecs_entity_t entity, + ecs_entity_t id, + ecs_record_t *r) { - int32_t i, last = ecs_vector_count(world->store.marked_ids), first = 0; - ecs_marked_id_t *ids = ecs_vector_first(world->store.marked_ids, - ecs_marked_id_t); + void *dst = NULL; - /* Iterate in reverse order so that DAGs get deleted bottom to top */ - do { - for (i = last - 1; i >= first; i --) { - ecs_id_record_t *idr = ids[i].idr; - ecs_entity_t action = ids[i].action; - - /* Empty all tables for id */ - ecs_table_cache_iter_t it; - if (flecs_table_cache_iter(&idr->cache, &it)) { - ecs_table_record_t *tr; - while ((tr = flecs_table_cache_next(&it, ecs_table_record_t))) { - ecs_table_t *table = tr->hdr.table; + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(id != 0, ECS_INVALID_PARAMETER, NULL); + ecs_check(r != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check((id & ECS_COMPONENT_MASK) == id || + ECS_HAS_ID_FLAG(id, PAIR), ECS_INVALID_PARAMETER, NULL); - if ((action == EcsRemove) || - !(table->flags & EcsTableMarkedForDelete)) - { - flecs_remove_from_table(world, table); - } else { - ecs_dbg_3( - "#[red]delete#[reset] entities from table %u", - (uint32_t)table->id); - flecs_table_delete_entities(world, table); - } - } - } + if (r->table) { + dst = get_component(world, r->table, ECS_RECORD_TO_ROW(r->row), id); + } - /* Run commands so children get notified before parent is deleted */ - ecs_defer_end(world); - ecs_defer_begin(world); + if (!dst) { + /* If entity didn't have component yet, add it */ + add_id_w_record(world, entity, r, id, true); - /* User code (from triggers) could have enqueued more ids to delete, - * reobtain the array in case it got reallocated */ - ids = ecs_vector_first(world->store.marked_ids, ecs_marked_id_t); - } + /* Flush commands so the pointer we're fetching is stable */ + ecs_defer_end(world); + ecs_defer_begin(world); - /* Check if new ids were marked since we started */ - int32_t new_last = ecs_vector_count(world->store.marked_ids); - if (new_last != last) { - /* Iterate remaining ids */ - ecs_assert(new_last > last, ECS_INTERNAL_ERROR, NULL); - first = last; - last = new_last; - } else { - break; - } - } while (true); + ecs_assert(r->table != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(r->table->storage_table != NULL, ECS_INTERNAL_ERROR, NULL); + dst = get_component(world, r->table, ECS_RECORD_TO_ROW(r->row), id); - return true; + return dst; + } + +error: + return dst; } +/* -- Private functions -- */ static -bool flecs_on_delete_clear_ids( - ecs_world_t *world) +void flecs_notify_on_add( + ecs_world_t *world, + ecs_table_t *table, + ecs_table_t *other_table, + int32_t row, + int32_t count, + ecs_table_diff_t *diff, + bool run_on_set) { - int32_t i, count = ecs_vector_count(world->store.marked_ids); - ecs_marked_id_t *ids = ecs_vector_first(world->store.marked_ids, - ecs_marked_id_t); + ecs_assert(diff != NULL, ECS_INTERNAL_ERROR, NULL); - for (i = 0; i < count; i ++) { - ecs_id_record_t *idr = ids[i].idr; + if (diff->added.count) { + if (table->flags & EcsTableHasIsA) { + components_override(world, table, other_table, row, count, + &diff->added, run_on_set); + } - flecs_id_record_release_tables(world, idr); + if (table->flags & EcsTableHasUnion) { + flecs_add_remove_union(world, table, row, count, &diff->added, NULL); + } - /* Release the claim taken by flecs_marked_id_push. This may delete the - * id record as all other claims may have been released. */ - if (flecs_id_record_release(world, idr)) { - /* If the id record is still alive, release the initial claim */ - flecs_id_record_release(world, idr); + if (table->flags & EcsTableHasOnAdd) { + flecs_notify(world, table, other_table, row, count, EcsOnAdd, + &diff->added, 0); } } - return true; + /* When a IsA relationship is added to an entity, that entity inherits the + * components from the base. Send OnSet notifications so that an application + * can respond to these new components. */ + if (run_on_set && diff->on_set.count) { + flecs_notify(world, table, other_table, row, count, EcsOnSet, &diff->on_set, + EcsIsA); + } } -static -void flecs_on_delete( +void flecs_notify_on_remove( ecs_world_t *world, - ecs_id_t id, - ecs_entity_t action) + ecs_table_t *table, + ecs_table_t *other_table, + int32_t row, + int32_t count, + ecs_table_diff_t *diff) { - /* Cleanup can happen recursively. If a cleanup action is already in - * progress, only append ids to the marked_ids. The topmost cleanup - * frame will handle the actual cleanup. */ - int32_t count = ecs_vector_count(world->store.marked_ids); + ecs_assert(diff != NULL, ECS_INTERNAL_ERROR, NULL); - /* Make sure we're evaluating a consistent list of non-empty tables */ - ecs_run_aperiodic(world, EcsAperiodicEmptyTables); + if (count) { + if (diff->un_set.count) { + flecs_notify(world, table, other_table, row, count, EcsUnSet, &diff->un_set, 0); + } - /* Collect all ids that need to be deleted */ - flecs_on_delete_mark(world, id, action); + if (table->flags & EcsTableHasOnRemove && diff->removed.count) { + flecs_notify(world, table, other_table, row, count, EcsOnRemove, + &diff->removed, 0); + } - /* Only perform cleanup if we're the first stack frame doing it */ - if (!count && ecs_vector_count(world->store.marked_ids)) { - ecs_dbg_2("#[red]delete#[reset]"); - ecs_log_push_2(); + if (table->flags & EcsTableHasIsA && diff->on_set.count) { + flecs_notify(world, table, other_table, row, count, EcsOnSet, + &diff->on_set, 0); + } + } +} - /* Empty tables with all the to be deleted ids */ - flecs_on_delete_clear_tables(world); +void flecs_notify_on_set( + ecs_world_t *world, + ecs_table_t *table, + int32_t row, + int32_t count, + ecs_type_t *ids, + bool owned) +{ + ecs_data_t *data = &table->data; - /* All marked tables are empty, ensure they're in the right list */ - ecs_run_aperiodic(world, EcsAperiodicEmptyTables); + ecs_entity_t *entities = ecs_storage_get_t( + &data->entities, ecs_entity_t, row); + ecs_assert(entities != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert((row + count) <= ecs_storage_count(&data->entities), + ECS_INTERNAL_ERROR, NULL); - /* Release remaining references to the ids */ - flecs_on_delete_clear_ids(world); + ecs_type_t local_ids; + if (!ids) { + local_ids.array = table->storage_ids; + local_ids.count = table->storage_count; + ids = &local_ids; + } - /* Verify deleted ids are no longer in use */ -#ifdef FLECS_DEBUG - ecs_marked_id_t *ids = ecs_vector_first(world->store.marked_ids, - ecs_marked_id_t); - int32_t i; - count = ecs_vector_count(world->store.marked_ids); - for (i = 0; i < count; i ++) { - ecs_assert(!ecs_id_in_use(world, ids[i].id), - ECS_INTERNAL_ERROR, NULL); - } -#endif - ecs_assert(!ecs_id_in_use(world, id), ECS_INTERNAL_ERROR, NULL); + if (owned) { + ecs_table_t *storage_table = table->storage_table; + int i; + for (i = 0; i < ids->count; i ++) { + ecs_id_t id = ids->array[i]; + const ecs_table_record_t *tr = flecs_table_record_get(world, + storage_table, id); + ecs_assert(tr != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(tr->count == 1, ECS_INTERNAL_ERROR, NULL); + int32_t column = tr->column; + const ecs_type_info_t *ti = table->type_info[column]; + ecs_iter_action_t on_set = ti->hooks.on_set; + if (on_set) { + ecs_column_t *c = &table->data.columns[column]; + ecs_size_t size = ti->size; + void *ptr = ecs_storage_get(c, size, row); + ecs_assert(size != 0, ECS_INTERNAL_ERROR, NULL); - /* Ids are deleted, clear stack */ - ecs_vector_clear(world->store.marked_ids); + ecs_iter_t it = { .field_count = 1}; + it.entities = entities; + + flecs_iter_init(&it, flecs_iter_cache_all); + it.world = world; + it.real_world = world; + it.table = table; + it.ptrs[0] = ptr; + it.sizes[0] = size; + it.ids[0] = id; + it.event = EcsOnSet; + it.event_id = id; + it.ctx = ti->hooks.ctx; + it.binding_ctx = ti->hooks.binding_ctx; + it.count = count; + flecs_iter_validate(&it); + on_set(&it); + } + } + } - ecs_log_pop_2(); + /* Run OnSet notifications */ + if (table->flags & EcsTableHasOnSet && ids->count) { + flecs_notify(world, table, NULL, row, count, EcsOnSet, ids, 0); } } -void ecs_delete_with( - ecs_world_t *world, - ecs_id_t id) +uint32_t flecs_record_to_row( + uint32_t row, + bool *is_watched_out) { - ecs_stage_t *stage = flecs_stage_from_world(&world); - if (flecs_defer_on_delete_action(world, stage, id, EcsDelete)) { - return; - } - - flecs_on_delete(world, id, EcsDelete); - flecs_defer_end(world, stage); + *is_watched_out = (row & ECS_ROW_FLAGS_MASK) != 0; + return row & ECS_ROW_MASK; } -void ecs_remove_all( - ecs_world_t *world, - ecs_id_t id) +uint32_t flecs_row_to_record( + uint32_t row, + bool is_watched) { - ecs_stage_t *stage = flecs_stage_from_world(&world); - if (flecs_defer_on_delete_action(world, stage, id, EcsRemove)) { - return; - } - - flecs_on_delete(world, id, EcsRemove); - flecs_defer_end(world, stage); + return row | (EcsEntityObserved * is_watched); } -void ecs_delete( +void flecs_add_flag( ecs_world_t *world, - ecs_entity_t entity) + ecs_entity_t entity, + uint32_t flag) { - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(entity != 0, ECS_INVALID_PARAMETER, NULL); - - ecs_stage_t *stage = flecs_stage_from_world(&world); - if (flecs_defer_delete(world, stage, entity)) { - return; - } - - ecs_record_t *r = flecs_entities_get(world, entity); - if (r) { - ecs_flags32_t row_flags = ECS_RECORD_TO_ROW_FLAGS(r->row); - ecs_table_t *table; - if (row_flags) { - if (row_flags & EcsEntityObservedAcyclic) { - table = r->table; + ecs_record_t *record = flecs_entities_get(world, entity); + if (!record) { + ecs_record_t new_record = {.row = flag, .table = NULL}; + flecs_entities_set(world, entity, &new_record); + } else { + if (flag == EcsEntityObservedAcyclic) { + if (!(record->row & flag)) { + ecs_table_t *table = record->table; if (table) { - table->observed_count --; + table->observed_count ++; } } - if (row_flags & EcsEntityObservedId) { - flecs_on_delete(world, entity, 0); - flecs_on_delete(world, ecs_pair(entity, EcsWildcard), 0); - } - if (row_flags & EcsEntityObservedTarget) { - flecs_on_delete(world, ecs_pair(EcsFlag, entity), 0); - flecs_on_delete(world, ecs_pair(EcsWildcard, entity), 0); - } - - /* Merge operations before deleting entity */ - ecs_defer_end(world); - ecs_defer_begin(world); - } - - table = r->table; - - /* If entity has components, remove them. Check if table is still alive, - * as delete actions could have deleted the table already. */ - if (table) { - ecs_table_diff_t diff = { - .removed = table->type, - .un_set = { table->storage_ids, table->storage_count } - }; - - delete_entity(world, r, &diff); - - r->row = 0; - r->table = NULL; } - - flecs_entities_remove(world, entity); + record->row |= flag; } - - flecs_defer_end(world, stage); -error: - return; } -void ecs_add_id( + +/* -- Public functions -- */ + +bool ecs_commit( ecs_world_t *world, ecs_entity_t entity, - ecs_id_t id) + ecs_record_t *record, + ecs_table_t *table, + const ecs_type_t *added, + const ecs_type_t *removed) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(ecs_is_valid(world, entity), ECS_INVALID_PARAMETER, NULL); - ecs_check(ecs_id_is_valid(world, id), ECS_INVALID_PARAMETER, NULL); - add_id(world, entity, id); + + ecs_table_t *src_table = NULL; + if (!record) { + record = flecs_entities_get(world, entity); + src_table = record->table; + } + + ecs_table_diff_t diff; + ecs_os_zeromem(&diff); + + if (added) { + diff.added = *added; + } + if (removed) { + diff.added = *removed; + } + + flecs_commit(world, entity, record, table, &diff, true, true); + + return src_table != table; error: - return; + return false; } -void ecs_remove_id( +ecs_entity_t ecs_set_with( ecs_world_t *world, - ecs_entity_t entity, ecs_id_t id) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(ecs_is_valid(world, entity), ECS_INVALID_PARAMETER, NULL); - ecs_check(ecs_id_is_valid(world, id) || ecs_id_is_wildcard(id), - ECS_INVALID_PARAMETER, NULL); - remove_id(world, entity, id); + ecs_stage_t *stage = flecs_stage_from_world(&world); + ecs_id_t prev = stage->with; + stage->with = id; + return prev; error: - return; + return 0; } -void ecs_override_id( - ecs_world_t *world, - ecs_entity_t entity, - ecs_id_t id) +ecs_id_t ecs_get_with( + const ecs_world_t *world) { - ecs_check(ecs_id_is_valid(world, id), ECS_INVALID_PARAMETER, NULL); - ecs_add_id(world, entity, ECS_OVERRIDE | id); + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + const ecs_stage_t *stage = flecs_stage_from_readonly_world(world); + return stage->with; error: - return; + return 0; } -ecs_entity_t ecs_clone( - ecs_world_t *world, - ecs_entity_t dst, - ecs_entity_t src, - bool copy_value) +ecs_entity_t ecs_new_id( + ecs_world_t *world) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(src != 0, ECS_INVALID_PARAMETER, NULL); - ecs_check(ecs_is_valid(world, src), ECS_INVALID_PARAMETER, NULL); - ecs_check(!dst || !ecs_get_table(world, dst), ECS_INVALID_PARAMETER, NULL); - ecs_stage_t *stage = flecs_stage_from_world(&world); - if (!dst) { - dst = ecs_new_id(world); - } + const ecs_stage_t *stage = flecs_stage_from_readonly_world(world); - if (flecs_defer_clone(world, stage, dst, src, copy_value)) { - return dst; - } + /* It is possible that the world passed to this function is a stage, so + * make sure we have the actual world. Cast away const since this is one of + * the few functions that may modify the world while it is in readonly mode, + * since it is thread safe (uses atomic inc when in threading mode) */ + ecs_world_t *unsafe_world = (ecs_world_t*)ecs_get_world(world); - ecs_record_t *src_r = flecs_entities_get(world, src); - ecs_table_t *src_table; - if (!src_r || !(src_table = src_r->table)) { - goto done; - } + ecs_entity_t entity; + if (stage->async || (unsafe_world->flags & EcsWorldMultiThreaded)) { + /* When using an async stage or world is in multithreading mode, make + * sure OS API has threading functions initialized */ + ecs_assert(ecs_os_has_threading(), ECS_INVALID_OPERATION, NULL); - ecs_type_t src_type = src_table->type; - ecs_table_diff_t diff = { .added = src_type }; - ecs_record_t *dst_r = new_entity(world, dst, NULL, src_table, &diff, true, true); - int32_t row = ECS_RECORD_TO_ROW(dst_r->row); - - if (copy_value) { - flecs_table_move(world, dst, src, src_table, - row, src_table, ECS_RECORD_TO_ROW(src_r->row), true); - - flecs_notify_on_set(world, src_table, row, 1, NULL, true); + /* Can't atomically increase number above max int */ + ecs_assert(unsafe_world->info.last_id < UINT_MAX, + ECS_INVALID_OPERATION, NULL); + entity = (ecs_entity_t)ecs_os_ainc( + (int32_t*)&unsafe_world->info.last_id); + } else { + entity = flecs_entities_recycle(unsafe_world); } -done: - flecs_defer_end(world, stage); - return dst; + ecs_assert(!unsafe_world->info.max_id || + ecs_entity_t_lo(entity) <= unsafe_world->info.max_id, + ECS_OUT_OF_RANGE, NULL); + + return entity; error: return 0; } -const void* ecs_get_id( - const ecs_world_t *world, - ecs_entity_t entity, - ecs_id_t id) +ecs_entity_t ecs_new_low_id( + ecs_world_t *world) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(ecs_is_valid(world, entity), ECS_INVALID_PARAMETER, NULL); - ecs_check(flecs_stage_from_readonly_world(world)->async == false, - ECS_INVALID_PARAMETER, NULL); - - world = ecs_get_world(world); - - ecs_record_t *r = flecs_entities_get(world, entity); - if (!r) { - return NULL; - } - - ecs_table_t *table = r->table; - if (!table) { - return NULL; - } - ecs_id_record_t *idr = flecs_id_record_get(world, id); - if (!idr) { - return NULL; + /* It is possible that the world passed to this function is a stage, so + * make sure we have the actual world. Cast away const since this is one of + * the few functions that may modify the world while it is in readonly mode, + * but only if single threaded. */ + ecs_world_t *unsafe_world = (ecs_world_t*)ecs_get_world(world); + if (unsafe_world->flags & EcsWorldReadonly) { + /* Can't issue new comp id while iterating when in multithreaded mode */ + ecs_check(ecs_get_stage_count(world) <= 1, + ECS_INVALID_WHILE_READONLY, NULL); } - const ecs_table_record_t *tr = NULL; - ecs_table_t *storage_table = table->storage_table; - if (storage_table) { - tr = flecs_id_record_get_table(idr, storage_table); - } else { - /* If the entity does not have a storage table (has no data) but it does - * have the id, the id must be a tag, and getting a tag is illegal. */ - ecs_check(!ecs_owns_id(world, entity, id), ECS_NOT_A_COMPONENT, NULL); + ecs_entity_t id = 0; + if (unsafe_world->info.last_component_id < ECS_HI_COMPONENT_ID) { + do { + id = unsafe_world->info.last_component_id ++; + } while (ecs_exists(unsafe_world, id) && id <= ECS_HI_COMPONENT_ID); } - if (!tr) { - return get_base_component(world, table, id, idr, 0); + if (!id || id >= ECS_HI_COMPONENT_ID) { + /* If the low component ids are depleted, return a regular entity id */ + id = ecs_new_id(unsafe_world); } - int32_t row = ECS_RECORD_TO_ROW(r->row); - return get_component_w_index(table, tr->column, row); -error: - return NULL; + return id; +error: + return 0; } -void* ecs_get_mut_id( +ecs_entity_t ecs_new_w_id( ecs_world_t *world, - ecs_entity_t entity, ecs_id_t id) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(ecs_is_valid(world, entity), ECS_INVALID_PARAMETER, NULL); - ecs_check(ecs_id_is_valid(world, id), ECS_INVALID_PARAMETER, NULL); - - ecs_stage_t *stage = flecs_stage_from_world(&world); - void *result; - - if (flecs_defer_set( - world, stage, EcsOpMut, entity, id, 0, NULL, &result, false)) - { - return result; - } - - ecs_record_t *r = flecs_entities_ensure(world, entity); - result = flecs_get_mut(world, entity, id, r); - ecs_check(result != NULL, ECS_INVALID_PARAMETER, NULL); - - flecs_defer_end(world, stage); + ecs_check(!id || ecs_id_is_valid(world, id), ECS_INVALID_PARAMETER, NULL); - return result; -error: - return NULL; -} + ecs_stage_t *stage = flecs_stage_from_world(&world); + ecs_entity_t entity = ecs_new_id(world); -static -ecs_record_t* flecs_access_begin( - ecs_world_t *stage, - ecs_entity_t entity, - bool write) -{ - ecs_check(ecs_os_has_threading(), ECS_MISSING_OS_API, NULL); + ecs_id_t ids[3]; + ecs_type_t to_add = { .array = ids, .count = 0 }; - const ecs_world_t *world = ecs_get_world(stage); - ecs_record_t *r = flecs_entities_get(world, entity); - if (!r) { - return NULL; + if (id) { + ids[to_add.count ++] = id; } - ecs_table_t *table; - if (!(table = r->table)) { - return NULL; + ecs_id_t with = stage->with; + if (with) { + ids[to_add.count ++] = with; } - int32_t count = ecs_os_ainc(&table->lock); - (void)count; - if (write) { - ecs_check(count == 1, ECS_ACCESS_VIOLATION, NULL); + ecs_entity_t scope = stage->scope; + if (scope) { + if (!id || !ECS_HAS_RELATION(id, EcsChildOf)) { + ids[to_add.count ++] = ecs_pair(EcsChildOf, scope); + } } + if (to_add.count) { + if (flecs_defer_new(world, stage, entity, to_add.array[0])) { + int i; + for (i = 1; i < to_add.count; i ++) { + flecs_defer_add(world, stage, entity, to_add.array[i]); + } + return entity; + } - return r; -error: - return NULL; -} + new(world, entity, &to_add); + } else { + if (flecs_defer_new(world, stage, entity, 0)) { + return entity; + } -static -void flecs_access_end( - const ecs_record_t *r, - bool write) -{ - ecs_check(ecs_os_has_threading(), ECS_MISSING_OS_API, NULL); - ecs_check(r != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(r->table != NULL, ECS_INVALID_PARAMETER, NULL); - int32_t count = ecs_os_adec(&r->table->lock); - (void)count; - if (write) { - ecs_check(count == 0, ECS_ACCESS_VIOLATION, NULL); + flecs_entities_set(world, entity, &(ecs_record_t){ 0 }); } - ecs_check(count >= 0, ECS_ACCESS_VIOLATION, NULL); + flecs_defer_end(world, stage); + return entity; error: - return; + return 0; } -ecs_record_t* ecs_write_begin( +#ifdef FLECS_PARSER + +/* Traverse table graph by either adding or removing identifiers parsed from the + * passed in expression. */ +static +ecs_table_t *traverse_from_expr( ecs_world_t *world, - ecs_entity_t entity) + ecs_table_t *table, + const char *name, + const char *expr, + ecs_diff_buffer_t *diff, + bool replace_and, + bool *error) { - return flecs_access_begin(world, entity, true); -} + const char *ptr = expr; + if (ptr) { + ecs_term_t term = {0}; + while (ptr[0] && (ptr = ecs_parse_term(world, name, expr, ptr, &term))){ + if (!ecs_term_is_initialized(&term)) { + break; + } -void ecs_write_end( - ecs_record_t *r) -{ - flecs_access_end(r, true); -} + if (!(term.first.flags & (EcsSelf|EcsUp))) { + term.first.flags = EcsSelf; + } + if (!(term.second.flags & (EcsSelf|EcsUp))) { + term.second.flags = EcsSelf; + } + if (!(term.src.flags & (EcsSelf|EcsUp))) { + term.src.flags = EcsSelf; + } -const ecs_record_t* ecs_read_begin( - ecs_world_t *world, - ecs_entity_t entity) -{ - return flecs_access_begin(world, entity, false); -} + if (ecs_term_finalize(world, &term)) { + ecs_term_fini(&term); + if (error) { + *error = true; + } + return NULL; + } -void ecs_read_end( - const ecs_record_t *r) -{ - flecs_access_end(r, false); -} + if (!ecs_id_is_valid(world, term.id)) { + ecs_term_fini(&term); + ecs_parser_error(name, expr, (ptr - expr), + "invalid term for add expression"); + return NULL; + } -const void* ecs_record_get_id( - ecs_world_t *stage, - const ecs_record_t *r, - ecs_id_t id) -{ - const ecs_world_t *world = ecs_get_world(stage); - return get_component(world, r->table, ECS_RECORD_TO_ROW(r->row), id); -} + if (term.oper == EcsAnd || !replace_and) { + /* Regular AND expression */ + table = table_append(world, table, term.id, diff); + } -void* ecs_record_get_mut_id( - ecs_world_t *stage, - ecs_record_t *r, - ecs_id_t id) -{ - const ecs_world_t *world = ecs_get_world(stage); - return get_component(world, r->table, ECS_RECORD_TO_ROW(r->row), id); + ecs_term_fini(&term); + } + + if (!ptr) { + if (error) { + *error = true; + } + return NULL; + } + } + + return table; } -ecs_ref_t ecs_ref_init_id( - const ecs_world_t *world, +/* Add/remove components based on the parsed expression. This operation is + * slower than traverse_from_expr, but safe to use from a deferred context. */ +static +void defer_from_expr( + ecs_world_t *world, ecs_entity_t entity, - ecs_id_t id) + const char *name, + const char *expr, + bool is_add, + bool replace_and) { - ecs_check(ecs_is_valid(world, entity), ECS_INVALID_PARAMETER, NULL); - ecs_check(ecs_id_is_valid(world, id), ECS_INVALID_PARAMETER, NULL); - - world = ecs_get_world(world); + const char *ptr = expr; + if (ptr) { + ecs_term_t term = {0}; + while (ptr[0] && (ptr = ecs_parse_term(world, name, expr, ptr, &term))){ + if (!ecs_term_is_initialized(&term)) { + break; + } - ecs_record_t *record = flecs_entities_get(world, entity); - ecs_check(record != NULL, ECS_INVALID_PARAMETER, - "cannot create ref for empty entity"); + if (ecs_term_finalize(world, &term)) { + return; + } - ecs_ref_t result = { - .entity = entity, - .id = id, - .record = record - }; + if (!ecs_id_is_valid(world, term.id)) { + ecs_term_fini(&term); + ecs_parser_error(name, expr, (ptr - expr), + "invalid term for add expression"); + return; + } - ecs_table_t *table = record->table; - if (table) { - result.tr = flecs_table_record_get(world, table->storage_table, id); - } + if (term.oper == EcsAnd || !replace_and) { + /* Regular AND expression */ + if (is_add) { + ecs_add_id(world, entity, term.id); + } else { + ecs_remove_id(world, entity, term.id); + } + } - return result; -error: - return (ecs_ref_t){0}; + ecs_term_fini(&term); + } + } } +#endif -void* ecs_ref_get_id( - const ecs_world_t *world, - ecs_ref_t *ref, - ecs_id_t id) +/* If operation is not deferred, add components by finding the target + * table and moving the entity towards it. */ +static +int traverse_add( + ecs_world_t *world, + ecs_entity_t result, + const char *name, + const ecs_entity_desc_t *desc, + ecs_entity_t scope, + ecs_id_t with, + bool new_entity, + bool name_assigned) { - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(ref != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(ref->entity != 0, ECS_INVALID_PARAMETER, NULL); - ecs_check(ref->id != 0, ECS_INVALID_PARAMETER, NULL); - ecs_check(ref->record != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(id == ref->id, ECS_INVALID_PARAMETER, NULL); + const char *sep = desc->sep; + const char *root_sep = desc->root_sep; - ecs_record_t *r = ref->record; - ecs_table_t *table = r->table; - if (!table) { - return NULL; + /* Find existing table */ + ecs_table_t *src_table = NULL, *table = NULL; + ecs_record_t *r = NULL; + if (!new_entity) { + r = flecs_entities_get(world, result); + if (r) { + table = r->table; + } } - int32_t row = ECS_RECORD_TO_ROW(r->row); - ecs_check(row < ecs_table_count(table), ECS_INTERNAL_ERROR, NULL); + /* Find destination table */ + ecs_diff_buffer_t diff = ECS_DIFF_INIT; - ecs_table_record_t *tr = ref->tr; - if (!tr || tr->hdr.table != table) { - tr = ref->tr = flecs_table_record_get(world, table->storage_table, id); - if (!tr) { - return NULL; + /* If this is a new entity without a name, add the scope. If a name is + * provided, the scope will be added by the add_path_w_sep function */ + if (new_entity) { + if (new_entity && scope && !name && !name_assigned) { + table = table_append( + world, table, ecs_pair(EcsChildOf, scope), &diff); + } + if (with) { + table = table_append(world, table, with, &diff); } } - return get_component_w_index(table, tr->column, row); -error: - return NULL; -} + /* If a name is provided but not yet assigned, add the Name component */ + if (name && !name_assigned) { + table = table_append(world, table, + ecs_pair(ecs_id(EcsIdentifier), EcsName), &diff); + } -void* ecs_emplace_id( - ecs_world_t *world, - ecs_entity_t entity, - ecs_id_t id) -{ - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(ecs_is_valid(world, entity), ECS_INVALID_PARAMETER, NULL); - ecs_check(ecs_id_is_valid(world, id), ECS_INVALID_PARAMETER, NULL); - ecs_check(!ecs_has_id(world, entity, id), ECS_INVALID_PARAMETER, NULL); + /* Add components from the 'add' id array */ + int32_t i = 0; + ecs_id_t id; + const ecs_id_t *ids = desc->add; + while ((i < ECS_ID_CACHE_SIZE) && (id = ids[i ++])) { + bool should_add = true; + if (ECS_HAS_ID_FLAG(id, PAIR) && ECS_PAIR_FIRST(id) == EcsChildOf) { + scope = ECS_PAIR_SECOND(id); + if ((!desc->id && desc->name) || (name && !name_assigned)) { + /* If name is added to entity, pass scope to add_path instead + * of adding it to the table. The provided name may have nested + * elements, in which case the parent provided here is not the + * parent the entity will end up with. */ + should_add = false; + } + } + if (should_add) { + table = table_append(world, table, id, &diff); + } + } - ecs_stage_t *stage = flecs_stage_from_world(&world); - void *result; + /* Add components from the 'add_expr' expression */ + if (desc->add_expr && ecs_os_strcmp(desc->add_expr, "0")) { +#ifdef FLECS_PARSER + bool error = false; + table = traverse_from_expr( + world, table, name, desc->add_expr, &diff, true, &error); + if (error) { + return -1; + } +#else + ecs_abort(ECS_UNSUPPORTED, "parser addon is not available"); +#endif + } - if (flecs_defer_set(world, stage, EcsOpEmplace, entity, id, 0, NULL, - &result, true)) - { - return result; + /* Commit entity to destination table */ + if (src_table != table) { + ecs_defer_begin(world); + ecs_table_diff_t table_diff = diff_to_table_diff(&diff); + flecs_commit(world, result, r, table, &table_diff, true, true); + ecs_defer_end(world); } - ecs_record_t *r = flecs_entities_ensure(world, entity); - add_id_w_record(world, entity, r, id, false /* Add without ctor */); + /* Set name */ + if (name && !name_assigned) { + ecs_add_path_w_sep(world, result, scope, name, sep, root_sep); + ecs_assert(ecs_get_name(world, result) != NULL, + ECS_INTERNAL_ERROR, NULL); + } - void *ptr = get_component(world, r->table, ECS_RECORD_TO_ROW(r->row), id); - ecs_check(ptr != NULL, ECS_INVALID_PARAMETER, NULL); + if (desc->symbol && desc->symbol[0]) { + const char *sym = ecs_get_symbol(world, result); + if (sym) { + ecs_assert(!ecs_os_strcmp(desc->symbol, sym), + ECS_INCONSISTENT_NAME, desc->symbol); + } else { + ecs_set_symbol(world, result, desc->symbol); + } + } - flecs_defer_end(world, stage); + diff_free(&diff); - return ptr; -error: - return NULL; + return 0; } -void ecs_modified_id( +/* When in deferred mode, we need to add/remove components one by one using + * the regular operations. */ +static +void deferred_add_remove( ecs_world_t *world, ecs_entity_t entity, - ecs_id_t id) + const char *name, + const ecs_entity_desc_t *desc, + ecs_entity_t scope, + ecs_id_t with, + bool new_entity, + bool name_assigned) { - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(ecs_is_valid(world, entity), ECS_INVALID_PARAMETER, NULL); - ecs_check(ecs_id_is_valid(world, id), ECS_INVALID_PARAMETER, NULL); + const char *sep = desc->sep; + const char *root_sep = desc->root_sep; - ecs_stage_t *stage = flecs_stage_from_world(&world); + /* If this is a new entity without a name, add the scope. If a name is + * provided, the scope will be added by the add_path_w_sep function */ + if (new_entity) { + if (new_entity && scope && !name && !name_assigned) { + ecs_add_id(world, entity, ecs_pair(EcsChildOf, scope)); + } - if (flecs_defer_modified(world, stage, entity, id)) { - return; + if (with) { + ecs_add_id(world, entity, with); + } } - /* If the entity does not have the component, calling ecs_modified is - * invalid. The assert needs to happen after the defer statement, as the - * entity may not have the component when this function is called while - * operations are being deferred. */ - ecs_check(ecs_has_id(world, entity, id), ECS_INVALID_PARAMETER, NULL); - - ecs_record_t *r = flecs_entities_get(world, entity); - ecs_table_t *table = r->table; - ecs_type_t ids = { .array = &id, .count = 1 }; - flecs_notify_on_set(world, table, ECS_RECORD_TO_ROW(r->row), 1, &ids, true); - - flecs_table_mark_dirty(world, table, id); - flecs_defer_end(world, stage); -error: - return; + /* Add components from the 'add' id array */ + int32_t i = 0; + ecs_id_t id; + const ecs_id_t *ids = desc->add; + while ((i < ECS_ID_CACHE_SIZE) && (id = ids[i ++])) { + bool defer = true; + if (ECS_HAS_ID_FLAG(id, PAIR) && ECS_PAIR_FIRST(id) == EcsChildOf) { + scope = ECS_PAIR_SECOND(id); + if (!desc->id || (name && !name_assigned)) { + /* New named entities are created by temporarily going out of + * readonly mode to ensure no duplicates are created. */ + defer = false; + } + } + if (defer) { + ecs_add_id(world, entity, id); + } + } + + /* Add components from the 'add_expr' expression */ + if (desc->add_expr) { +#ifdef FLECS_PARSER + defer_from_expr(world, entity, name, desc->add_expr, true, true); +#else + ecs_abort(ECS_UNSUPPORTED, "parser addon is not available"); +#endif + } + + int32_t thread_count = ecs_get_stage_count(world); + + /* Set name */ + if (name && !name_assigned) { + /* To prevent creating two entities with the same name, temporarily go + * out of readonly mode if it's safe to do so. */ + ecs_suspend_readonly_state_t state; + if (thread_count <= 1) { + /* When not running on multiple threads we can temporarily leave + * readonly mode which ensures that we don't accidentally create + * two entities with the same name. */ + ecs_world_t *real_world = flecs_suspend_readonly(world, &state); + ecs_add_path_w_sep(real_world, entity, scope, name, sep, root_sep); + flecs_resume_readonly(real_world, &state); + } else { + /* In multithreaded mode we can't leave readonly mode, which means + * there is a risk of creating two entities with the same name. + * Future improvements will be able to detect this. */ + ecs_add_path_w_sep(world, entity, scope, name, sep, root_sep); + } + } + + /* Set symbol */ + if (desc->symbol) { + const char *sym = ecs_get_symbol(world, entity); + if (!sym || ecs_os_strcmp(sym, desc->symbol)) { + if (thread_count <= 1) { /* See above */ + ecs_suspend_readonly_state_t state; + ecs_world_t *real_world = flecs_suspend_readonly(world, &state); + ecs_set_symbol(world, entity, desc->symbol); + flecs_resume_readonly(real_world, &state); + } else { + ecs_set_symbol(world, entity, desc->symbol); + } + } + } } -static -ecs_entity_t set_ptr_w_id( +ecs_entity_t ecs_entity_init( ecs_world_t *world, - ecs_entity_t entity, - ecs_id_t id, - size_t size, - void *ptr, - bool is_move, - bool flecs_notify) + const ecs_entity_desc_t *desc) { + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(desc->_canary == 0, ECS_INVALID_PARAMETER, NULL); + ecs_stage_t *stage = flecs_stage_from_world(&world); + ecs_entity_t scope = stage->scope; + ecs_id_t with = ecs_get_with(world); - if (!entity) { - entity = ecs_new_id(world); - ecs_entity_t scope = stage->scope; - if (scope) { - ecs_add_pair(world, entity, EcsChildOf, scope); - } + const char *name = desc->name; + const char *sep = desc->sep; + if (!sep) { + sep = "."; } - if (flecs_defer_set(world, stage, EcsOpSet, entity, id, - flecs_utosize(size), ptr, NULL, false)) - { - return entity; + if (name && !name[0]) { + name = NULL; } - ecs_record_t *r = flecs_entities_ensure(world, entity); - void *dst = flecs_get_mut(world, entity, id, r); - ecs_check(dst != NULL, ECS_INVALID_PARAMETER, NULL); + const char *root_sep = desc->root_sep; + bool new_entity = false; + bool name_assigned = false; - if (ptr) { - ecs_entity_t real_id = ecs_get_typeid(world, id); - const ecs_type_info_t *ti = get_c_info(world, real_id); - if (ti) { - if (is_move) { - ecs_move_t move = ti->hooks.move; - if (move) { - move(dst, ptr, 1, ti); - } else { - ecs_os_memcpy(dst, ptr, flecs_utosize(size)); - } + /* Remove optional prefix from name. Entity names can be derived from + * language identifiers, such as components (typenames) and systems + * function names). Because C does not have namespaces, such identifiers + * often encode the namespace as a prefix. + * To ensure interoperability between C and C++ (and potentially other + * languages with namespacing) the entity must be stored without this prefix + * and with the proper namespace, which is what the name_prefix is for */ + const char *prefix = world->info.name_prefix; + if (name && prefix) { + ecs_size_t len = ecs_os_strlen(prefix); + if (!ecs_os_strncmp(name, prefix, len) && + (isupper(name[len]) || name[len] == '_')) + { + if (name[len] == '_') { + name = name + len + 1; } else { - ecs_copy_t copy = ti->hooks.copy; - if (copy) { - copy(dst, ptr, 1, ti); - } else { - ecs_os_memcpy(dst, ptr, flecs_utosize(size)); - } + name = name + len; } - } else { - ecs_os_memcpy(dst, ptr, flecs_utosize(size)); } - } else { - ecs_os_memset(dst, 0, size); } - flecs_table_mark_dirty(world, r->table, id); + /* Find or create entity */ + ecs_entity_t result = desc->id; + if (!result) { + if (name) { + /* If add array contains a ChildOf pair, use it as scope instead */ + const ecs_id_t *ids = desc->add; + ecs_id_t id; + int32_t i = 0; + while ((i < ECS_ID_CACHE_SIZE) && (id = ids[i ++])) { + if (ECS_HAS_ID_FLAG(id, PAIR) && + (ECS_PAIR_FIRST(id) == EcsChildOf)) + { + scope = ECS_PAIR_SECOND(id); + } + } - if (flecs_notify) { - ecs_type_t ids = { .array = &id, .count = 1 }; - flecs_notify_on_set( - world, r->table, ECS_RECORD_TO_ROW(r->row), 1, &ids, true); - } + result = ecs_lookup_path_w_sep( + world, scope, name, sep, root_sep, false); + if (result) { + name_assigned = true; + } + } - flecs_defer_end(world, stage); + if (!result) { + if (desc->use_low_id) { + result = ecs_new_low_id(world); + } else { + result = ecs_new_id(world); + } + new_entity = true; + ecs_assert(ecs_get_type(world, result) == NULL, + ECS_INTERNAL_ERROR, NULL); + } + } else { + /* Make sure provided id is either alive or revivable */ + ecs_ensure(world, result); - return entity; -error: - return 0; -} + name_assigned = ecs_has_pair( + world, result, ecs_id(EcsIdentifier), EcsName); + if (name && name_assigned) { + /* If entity has name, verify that name matches. The name provided + * to the function could either have been relative to the current + * scope, or fully qualified. */ + char *path; + ecs_size_t root_sep_len = root_sep ? ecs_os_strlen(root_sep) : 0; + if (root_sep && !ecs_os_strncmp(name, root_sep, root_sep_len)) { + /* Fully qualified name was provided, so make sure to + * compare with fully qualified name */ + path = ecs_get_path_w_sep(world, 0, result, sep, root_sep); + } else { + /* Relative name was provided, so make sure to compare with + * relative name */ + path = ecs_get_path_w_sep(world, scope, result, sep, ""); + } + if (path) { + if (ecs_os_strcmp(path, name)) { + /* Mismatching name */ + ecs_err("existing entity '%s' is initialized with " + "conflicting name '%s'", path, name); + ecs_os_free(path); + return 0; + } + ecs_os_free(path); + } + } + } -ecs_entity_t ecs_set_id( - ecs_world_t *world, - ecs_entity_t entity, - ecs_id_t id, - size_t size, - const void *ptr) -{ - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(!entity || ecs_is_valid(world, entity), ECS_INVALID_PARAMETER, NULL); - ecs_check(ecs_id_is_valid(world, id), ECS_INVALID_PARAMETER, NULL); + ecs_assert(name_assigned == ecs_has_pair( + world, result, ecs_id(EcsIdentifier), EcsName), + ECS_INTERNAL_ERROR, NULL); - /* Safe to cast away const: function won't modify if move arg is false */ - return set_ptr_w_id( - world, entity, id, size, (void*)ptr, false, true); + if (stage->defer) { + deferred_add_remove((ecs_world_t*)stage, result, name, desc, + scope, with, new_entity, name_assigned); + } else { + if (traverse_add(world, result, name, desc, + scope, with, new_entity, name_assigned)) + { + return 0; + } + } + + return result; error: return 0; } -void ecs_enable_id( +const ecs_entity_t* ecs_bulk_init( ecs_world_t *world, - ecs_entity_t entity, - ecs_id_t id, - bool enable) + const ecs_bulk_desc_t *desc) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(ecs_is_valid(world, entity), ECS_INVALID_PARAMETER, NULL); - ecs_check(ecs_id_is_valid(world, id), ECS_INVALID_PARAMETER, NULL); + ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(desc->_canary == 0, ECS_INVALID_PARAMETER, NULL); - ecs_stage_t *stage = flecs_stage_from_world(&world); + const ecs_entity_t *entities = desc->entities; + int32_t count = desc->count; - if (flecs_defer_enable( - world, stage, entity, id, enable)) - { - return; + int32_t sparse_count = 0; + if (!entities) { + sparse_count = flecs_entities_count(world); + entities = flecs_sparse_new_ids(ecs_eis(world), count); + ecs_assert(entities != NULL, ECS_INTERNAL_ERROR, NULL); } else { - /* Operations invoked by enable/disable should not be deferred */ - stage->defer --; + int i; + for (i = 0; i < count; i ++) { + ecs_ensure(world, entities[i]); + } } - ecs_record_t *r = flecs_entities_ensure(world, entity); - ecs_entity_t bs_id = id | ECS_TOGGLE; - - ecs_table_t *table = r->table; - int32_t index = -1; - if (table) { - index = ecs_search(world, table, bs_id, 0); - } + ecs_type_t ids; + ecs_table_t *table = desc->table; + ecs_diff_buffer_t diff = ECS_DIFF_INIT; + if (!table) { + int32_t i = 0; + ecs_id_t id; + while ((id = desc->ids[i])) { + table = table_append(world, table, id, &diff); + i ++; + } - if (index == -1) { - ecs_add_id(world, entity, bs_id); - ecs_enable_id(world, entity, id, enable); - return; + ids.array = (ecs_id_t*)desc->ids; + ids.count = i; + } else { + diff.added.type.array = table->type.array; + diff.added.type.count = table->type.count; + ids = diff.added.type; } - index -= table->bs_offset; - ecs_assert(index >= 0, ECS_INTERNAL_ERROR, NULL); - - /* Data cannot be NULl, since entity is stored in the table */ - ecs_bitset_t *bs = &table->data.bs_columns[index]; - ecs_assert(bs != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_table_diff_t table_diff = diff_to_table_diff(&diff); + new_w_data(world, table, entities, &ids, count, desc->data, true, NULL, + &table_diff); - flecs_bitset_set(bs, ECS_RECORD_TO_ROW(r->row), enable); + if (!sparse_count) { + return entities; + } else { + /* Refetch entity ids, in case the underlying array was reallocated */ + entities = flecs_sparse_ids(ecs_eis(world)); + return &entities[sparse_count]; + } error: - return; + return NULL; } -bool ecs_is_enabled_id( - const ecs_world_t *world, - ecs_entity_t entity, - ecs_id_t id) +ecs_entity_t ecs_component_init( + ecs_world_t *world, + const ecs_component_desc_t *desc) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(ecs_is_valid(world, entity), ECS_INVALID_PARAMETER, NULL); - ecs_check(ecs_id_is_valid(world, id), ECS_INVALID_PARAMETER, NULL); + ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(desc->_canary == 0, ECS_INVALID_PARAMETER, NULL); - /* Make sure we're not working with a stage */ - world = ecs_get_world(world); + ecs_suspend_readonly_state_t readonly_state; + world = flecs_suspend_readonly(world, &readonly_state); - ecs_record_t *r = flecs_entities_get(world, entity); - ecs_table_t *table; - if (!r || !(table = r->table)) { - return false; + ecs_entity_t result = desc->entity; + if (!result) { + result = ecs_new_low_id(world); } - ecs_entity_t bs_id = id | ECS_TOGGLE; - int32_t index = ecs_search(world, table, bs_id, 0); - if (index == -1) { - /* If table does not have TOGGLE column for component, component is - * always enabled, if the entity has it */ - return ecs_has_id(world, entity, id); + EcsComponent *ptr = ecs_get_mut(world, result, EcsComponent); + if (!ptr->size) { + ecs_assert(ptr->alignment == 0, ECS_INTERNAL_ERROR, NULL); + ptr->size = desc->type.size; + ptr->alignment = desc->type.alignment; + if (!ptr->size) { + ecs_trace("#[green]tag#[reset] %s created", + ecs_get_name(world, result)); + } else { + ecs_trace("#[green]component#[reset] %s created", + ecs_get_name(world, result)); + } + } else { + if (ptr->size != desc->type.size) { + char *path = ecs_get_fullpath(world, result); + ecs_abort(ECS_INVALID_COMPONENT_SIZE, path); + ecs_os_free(path); + } + if (ptr->alignment != desc->type.alignment) { + char *path = ecs_get_fullpath(world, result); + ecs_abort(ECS_INVALID_COMPONENT_ALIGNMENT, path); + ecs_os_free(path); + } } - index -= table->bs_offset; - ecs_assert(index >= 0, ECS_INTERNAL_ERROR, NULL); - ecs_bitset_t *bs = &table->data.bs_columns[index]; + ecs_modified(world, result, EcsComponent); - return flecs_bitset_get(bs, ECS_RECORD_TO_ROW(r->row)); + if (desc->type.size && + !ecs_id_in_use(world, result) && + !ecs_id_in_use(world, ecs_pair(result, EcsWildcard))) + { + ecs_set_hooks_id(world, result, &desc->type.hooks); + } + + if (result >= world->info.last_component_id && result < ECS_HI_COMPONENT_ID) { + world->info.last_component_id = result + 1; + } + + /* Ensure components cannot be deleted */ + ecs_add_pair(world, result, EcsOnDelete, EcsPanic); + + flecs_resume_readonly(world, &readonly_state); + + ecs_assert(result != 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(ecs_has(world, result, EcsComponent), ECS_INTERNAL_ERROR, NULL); + + return result; error: - return false; + return 0; } -bool ecs_has_id( - const ecs_world_t *world, - ecs_entity_t entity, - ecs_id_t id) +const ecs_entity_t* ecs_bulk_new_w_id( + ecs_world_t *world, + ecs_id_t id, + int32_t count) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(ecs_is_valid(world, entity), ECS_INVALID_PARAMETER, NULL); - ecs_check(id != 0, ECS_INVALID_PARAMETER, NULL); - - /* Make sure we're not working with a stage */ - world = ecs_get_world(world); + ecs_stage_t *stage = flecs_stage_from_world(&world); - ecs_record_t *r = flecs_entities_get(world, entity); - ecs_table_t *table; - if (!r || !(table = r->table)) { - return false; + const ecs_entity_t *ids; + if (flecs_defer_bulk_new(world, stage, count, id, &ids)) { + return ids; } - ecs_table_record_t *tr = NULL; - int32_t column = ecs_search_relation( - world, table, 0, id, EcsIsA, 0, 0, 0, &tr); - if (column == -1) { - return false; + ecs_table_t *table = &world->store.root; + ecs_diff_buffer_t diff = ECS_DIFF_INIT; + + if (id) { + table = table_append(world, table, id, &diff); } - table = tr->hdr.table; - if ((table->flags & EcsTableHasUnion) && ECS_HAS_ID_FLAG(id, PAIR) && - ECS_PAIR_SECOND(id) != EcsWildcard) - { - if (ECS_PAIR_FIRST(table->type.array[column]) == EcsUnion) { - ecs_switch_t *sw = &table->data.sw_columns[ - column - table->sw_offset]; - int32_t row = ECS_RECORD_TO_ROW(r->row); - uint64_t value = flecs_switch_get(sw, row); - return value == ECS_PAIR_SECOND(id); - } - } - - return true; + ecs_table_diff_t td = diff_to_table_diff(&diff); + ids = new_w_data(world, table, NULL, NULL, count, NULL, false, NULL, &td); + flecs_defer_end(world, stage); + + return ids; error: - return false; + return NULL; } -ecs_entity_t ecs_get_target( - const ecs_world_t *world, - ecs_entity_t entity, - ecs_entity_t rel, - int32_t index) +void ecs_clear( + ecs_world_t *world, + ecs_entity_t entity) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(entity != 0, ECS_INVALID_PARAMETER, NULL); - ecs_check(rel != 0, ECS_INVALID_PARAMETER, NULL); + ecs_check(ecs_is_valid(world, entity), ECS_INVALID_PARAMETER, NULL); - world = ecs_get_world(world); + ecs_stage_t *stage = flecs_stage_from_world(&world); + if (flecs_defer_clear(world, stage, entity)) { + return; + } ecs_record_t *r = flecs_entities_get(world, entity); - ecs_table_t *table; - if (!r || !(table = r->table)) { - return 0; + if (!r) { + return; /* Nothing to clear */ } - ecs_id_t wc = ecs_pair(rel, EcsWildcard); - ecs_table_record_t *tr = flecs_table_record_get(world, table, wc); - if (!tr) { - if (table->flags & EcsTableHasUnion) { - wc = ecs_pair(EcsUnion, rel); - tr = flecs_table_record_get(world, table, wc); - if (tr) { - ecs_switch_t *sw = &table->data.sw_columns[ - tr->column - table->sw_offset]; - int32_t row = ECS_RECORD_TO_ROW(r->row); - return flecs_switch_get(sw, row); - - } + ecs_table_t *table = r->table; + if (table) { + if (r->row & EcsEntityObservedAcyclic) { + table->observed_count --; } - return 0; - } - if (index >= tr->count) { - return 0; - } + ecs_table_diff_t diff = { + .removed = table->type, + .un_set = { table->storage_ids, table->storage_count } + }; - ecs_id_t *ids = table->type.array; - return ecs_pair_second(world, ids[tr->column + index]); + delete_entity(world, r, &diff); + r->table = NULL; + } + + flecs_defer_end(world, stage); error: - return 0; + return; } -ecs_entity_t ecs_get_target_for_id( - const ecs_world_t *world, - ecs_entity_t entity, - ecs_entity_t rel, +static +void flecs_throw_invalid_delete( + ecs_world_t *world, ecs_id_t id) { - ecs_table_t *table = ecs_get_table(world, entity); - ecs_entity_t subject = 0; - - if (rel) { - int32_t column = ecs_search_relation( - world, table, 0, id, rel, 0, &subject, 0, 0); - if (column == -1) { - return 0; - } - } else { - ecs_id_t *ids = table->type.array; - int32_t i, count = table->type.count; - - for (i = 0; i < count; i ++) { - ecs_id_t ent = ids[i]; - if (ent & ECS_ID_FLAGS_MASK) { - /* Skip ids with pairs, roles since 0 was provided for rel */ - break; - } - - if (ecs_has_id(world, ent, id)) { - subject = ent; - break; - } - } - } - - if (subject == 0) { - return entity; - } else { - return subject; + char *id_str = NULL; + if (!(world->flags & EcsWorldQuit)) { + id_str = ecs_id_str(world, id); + ecs_throw(ECS_CONSTRAINT_VIOLATED, id_str); } +error: + ecs_os_free(id_str); } static -const char* get_identifier( - const ecs_world_t *world, - ecs_entity_t entity, - ecs_entity_t tag) +void flecs_marked_id_push( + ecs_world_t *world, + ecs_id_record_t* idr, + ecs_entity_t action) { - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(ecs_is_valid(world, entity), ECS_INVALID_PARAMETER, NULL); + ecs_marked_id_t *m = ecs_vector_add(&world->store.marked_ids, + ecs_marked_id_t); - const EcsIdentifier *ptr = ecs_get_pair( - world, entity, EcsIdentifier, tag); + m->idr = idr; + m->id = idr->id; + m->action = action; - if (ptr) { - return ptr->value; - } else { - return NULL; - } -error: - return NULL; + flecs_id_record_claim(world, idr); } -const char* ecs_get_name( - const ecs_world_t *world, - ecs_entity_t entity) -{ - return get_identifier(world, entity, EcsName); -} +static +void flecs_id_mark_for_delete( + ecs_world_t *world, + ecs_id_record_t *idr, + ecs_entity_t action); -const char* ecs_get_symbol( - const ecs_world_t *world, - ecs_entity_t entity) +static +void flecs_targets_mark_for_delete( + ecs_world_t *world, + ecs_table_t *table) { - return get_identifier(world, entity, EcsSymbol); + ecs_id_record_t *idr; + ecs_entity_t *entities = ecs_storage_first(&table->data.entities); + ecs_record_t **records = ecs_storage_first(&table->data.records); + int32_t i, count = ecs_storage_count(&table->data.entities); + for (i = 0; i < count; i ++) { + ecs_record_t *r = records[i]; + if (!r) { + continue; + } + + /* If entity is not used as id or as relationship target, there won't + * be any tables with a reference to it. */ + ecs_flags32_t flags = r->row & ECS_ROW_FLAGS_MASK; + if (!(flags & (EcsEntityObservedId|EcsEntityObservedTarget))) { + continue; + } + + ecs_entity_t e = entities[i]; + if (flags & EcsEntityObservedId) { + if ((idr = flecs_id_record_get(world, e))) { + flecs_id_mark_for_delete(world, idr, + ECS_ID_ON_DELETE(idr->flags)); + } + if ((idr = flecs_id_record_get(world, ecs_pair(e, EcsWildcard)))) { + flecs_id_mark_for_delete(world, idr, + ECS_ID_ON_DELETE(idr->flags)); + } + } + if (flags & EcsEntityObservedTarget) { + if ((idr = flecs_id_record_get(world, ecs_pair(EcsWildcard, e)))) { + flecs_id_mark_for_delete(world, idr, + ECS_ID_ON_DELETE_OBJECT(idr->flags)); + } + if ((idr = flecs_id_record_get(world, ecs_pair(EcsFlag, e)))) { + flecs_id_mark_for_delete(world, idr, + ECS_ID_ON_DELETE_OBJECT(idr->flags)); + } + } + } } static -ecs_entity_t set_identifier( - ecs_world_t *world, - ecs_entity_t entity, - ecs_entity_t tag, - const char *name) +bool flecs_id_is_delete_target( + ecs_id_t id, + ecs_entity_t action) { - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - if (!entity) { - entity = ecs_new_id(world); + if (!action && ecs_id_is_pair(id) && ECS_PAIR_FIRST(id) == EcsWildcard) { + /* If no explicit delete action is provided, and the id we're deleting + * has the form (*, Target), use OnDeleteTarget action */ + return true; } - - EcsIdentifier *ptr = ecs_get_mut_pair(world, entity, EcsIdentifier, tag); - ecs_assert(ptr != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_os_strset(&ptr->value, name); - ecs_modified_pair(world, entity, ecs_id(EcsIdentifier), tag); - - return entity; -error: - return 0; + return false; } - -ecs_entity_t ecs_set_name( - ecs_world_t *world, - ecs_entity_t entity, - const char *name) +static +ecs_entity_t flecs_get_delete_action( + ecs_table_t *table, + ecs_table_record_t *tr, + ecs_entity_t action, + bool delete_target) { - if (!entity) { - return ecs_entity_init(world, &(ecs_entity_desc_t){ - .name = name - }); + ecs_entity_t result = action; + if (!result && delete_target) { + /* If action is not specified and we're deleting a relationship target, + * derive the action from the current record */ + ecs_table_record_t *trr = &table->records[tr->column]; + ecs_id_record_t *idrr = (ecs_id_record_t*)trr->hdr.cache; + result = ECS_ID_ON_DELETE_OBJECT(idrr->flags); } - return set_identifier(world, entity, EcsName, name); + return result; } -ecs_entity_t ecs_set_symbol( +static +void flecs_update_monitors_for_delete( ecs_world_t *world, - ecs_entity_t entity, - const char *name) + ecs_id_t id) { - return set_identifier(world, entity, EcsSymbol, name); + update_component_monitors(world, NULL, &(ecs_type_t){ + .array = (ecs_id_t[]){id}, + .count = 1 + }); } -void ecs_set_alias( +static +void flecs_id_mark_for_delete( ecs_world_t *world, - ecs_entity_t entity, - const char *name) + ecs_id_record_t *idr, + ecs_entity_t action) { - set_identifier(world, entity, EcsAlias, name); -} + if (idr->flags & EcsIdMarkedForDelete) { + return; + } -ecs_id_t ecs_make_pair( - ecs_entity_t relationship, - ecs_entity_t target) -{ - return ecs_pair(relationship, target); -} + idr->flags |= EcsIdMarkedForDelete; + flecs_marked_id_push(world, idr, action); -bool ecs_is_valid( - const ecs_world_t *world, - ecs_entity_t entity) -{ - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_id_t id = idr->id; - /* 0 is not a valid entity id */ - if (!entity) { - return false; - } - - /* Make sure we're not working with a stage */ - world = ecs_get_world(world); - - /* Entity identifiers should not contain flag bits */ - if (entity & ECS_ID_FLAGS_MASK) { - return false; - } + bool delete_target = flecs_id_is_delete_target(id, action); - /* Entities should not contain data in dead zone bits */ - if (entity & ~0xFF00FFFFFFFFFFFF) { - return false; - } + /* Mark all tables with the id for delete */ + ecs_table_cache_iter_t it; + if (flecs_table_cache_iter(&idr->cache, &it)) { + ecs_table_record_t *tr; + while ((tr = flecs_table_cache_next(&it, ecs_table_record_t))) { + ecs_table_t *table = tr->hdr.table; + if (table->flags & EcsTableMarkedForDelete) { + continue; + } - if (entity & ECS_ID_FLAG_BIT) { - return ecs_entity_t_lo(entity) != 0; + ecs_entity_t cur_action = flecs_get_delete_action(table, tr, action, + delete_target); + + /* If this is a Delete action, recursively mark ids & tables */ + if (cur_action == EcsDelete) { + table->flags |= EcsTableMarkedForDelete; + ecs_log_push_2(); + flecs_targets_mark_for_delete(world, table); + ecs_log_pop_2(); + } else if (cur_action == EcsPanic) { + flecs_throw_invalid_delete(world, id); + } + } } - /* If entity doesn't exist in the world, the id is valid as long as the - * generation is 0. Using a non-existing id with a non-zero generation - * requires calling ecs_ensure first. */ - if (!ecs_exists(world, entity)) { - return ECS_GENERATION(entity) == 0; + /* Same for empty tables */ + if (flecs_table_cache_empty_iter(&idr->cache, &it)) { + ecs_table_record_t *tr; + while ((tr = flecs_table_cache_next(&it, ecs_table_record_t))) { + tr->hdr.table->flags |= EcsTableMarkedForDelete; + } } - /* If id exists, it must be alive (the generation count must match) */ - return ecs_is_alive(world, entity); -error: - return false; + /* Signal query cache monitors */ + flecs_update_monitors_for_delete(world, id); + + /* If id is a wildcard pair, update cache monitors for non-wildcard ids */ + if (ecs_id_is_wildcard(id)) { + ecs_assert(ECS_HAS_ID_FLAG(id, PAIR), ECS_INTERNAL_ERROR, NULL); + ecs_id_record_t *cur = idr; + if (ECS_PAIR_SECOND(id) == EcsWildcard) { + while ((cur = cur->first.next)) { + flecs_update_monitors_for_delete(world, cur->id); + } + } else { + ecs_assert(ECS_PAIR_FIRST(id) == EcsWildcard, + ECS_INTERNAL_ERROR, NULL); + while ((cur = cur->second.next)) { + flecs_update_monitors_for_delete(world, cur->id); + } + } + } } -ecs_id_t ecs_strip_generation( - ecs_entity_t e) +static +bool flecs_on_delete_mark( + ecs_world_t *world, + ecs_id_t id, + ecs_entity_t action) { - /* If this is not a pair, erase the generation bits */ - if (!(e & ECS_ID_FLAGS_MASK)) { - e &= ~ECS_GENERATION_MASK; + ecs_id_record_t *idr = flecs_id_record_get(world, id); + if (!idr) { + /* If there's no id record, there's nothing to delete */ + return false; } - return e; -} + if (!action) { + /* If no explicit action is provided, derive it */ + if (!ecs_id_is_pair(id) || ECS_PAIR_SECOND(id) == EcsWildcard) { + /* Delete actions are determined by the component, or in the case + * of a pair by the relationship. */ + action = ECS_ID_ON_DELETE(idr->flags); + } + } -bool ecs_is_alive( - const ecs_world_t *world, - ecs_entity_t entity) -{ - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(entity != 0, ECS_INVALID_PARAMETER, NULL); + if (action == EcsPanic) { + /* This id is protected from deletion */ + flecs_throw_invalid_delete(world, id); + return false; + } - world = ecs_get_world(world); + flecs_id_mark_for_delete(world, idr, action); - return flecs_entities_is_alive(world, entity); -error: - return false; + return true; } -ecs_entity_t ecs_get_alive( - const ecs_world_t *world, - ecs_entity_t entity) +static +void flecs_remove_from_table( + ecs_world_t *world, + ecs_table_t *table) { - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - - if (!entity) { - return 0; - } + ecs_table_diff_t temp_diff; + ecs_diff_buffer_t diff = ECS_DIFF_INIT; + ecs_table_t *dst_table = table; - if (ecs_is_alive(world, entity)) { - return entity; - } + /* To find the dst table, remove all ids that are marked for deletion */ + int32_t i, t, count = ecs_vector_count(world->store.marked_ids); + ecs_marked_id_t *ids = ecs_vector_first(world->store.marked_ids, + ecs_marked_id_t); + const ecs_table_record_t *tr; - /* Make sure id does not have generation. This guards against accidentally - * "upcasting" a not alive identifier to a alive one. */ - ecs_assert((uint32_t)entity == entity, ECS_INVALID_PARAMETER, NULL); + for (i = 0; i < count; i ++) { + const ecs_id_record_t *idr = ids[i].idr; - /* Make sure we're not working with a stage */ - world = ecs_get_world(world); + if (!(tr = flecs_id_record_get_table(idr, dst_table))) { + continue; + } - ecs_entity_t current = flecs_entities_get_current(world, entity); - if (!current || !ecs_is_alive(world, current)) { - return 0; + t = tr->column; + + do { + ecs_id_t id = dst_table->type.array[t]; + dst_table = flecs_table_traverse_remove( + world, dst_table, &id, &temp_diff); + diff_append(&diff, &temp_diff); + } while (dst_table->type.count && (t = ecs_search_offset( + world, dst_table, t, idr->id, NULL)) != -1); } - return current; -error: - return 0; -} + ecs_assert(dst_table != NULL, ECS_INTERNAL_ERROR, NULL); -void ecs_ensure( - ecs_world_t *world, - ecs_entity_t entity) -{ - ecs_poly_assert(world, ecs_world_t); /* Cannot be a stage */ - ecs_check(entity != 0, ECS_INVALID_PARAMETER, NULL); + if (!dst_table->type.count) { + /* If this removes all components, clear table */ + ecs_dbg_3("#[red]clear#[reset] entities from table %u", + (uint32_t)table->id); + flecs_table_clear_entities(world, table); + } else { + /* Otherwise, merge table into dst_table */ + ecs_dbg_3("#[red]move#[reset] entities from table %u to %u", + (uint32_t)table->id, (uint32_t)dst_table->id); - /* Check if a version of the provided id is alive */ - ecs_entity_t any = ecs_get_alive(world, ecs_strip_generation(entity)); - if (any == entity) { - /* If alive and equal to the argument, there's nothing left to do */ - return; + if (dst_table != table) { + if (diff.removed.type.count) { + ecs_log_push_3(); + ecs_table_diff_t td = diff_to_table_diff(&diff); + flecs_notify_on_remove(world, table, NULL, + 0, ecs_table_count(table), &td); + ecs_log_pop_3(); + } + flecs_table_merge(world, dst_table, table, + &dst_table->data, &table->data); + } } - /* If the id is currently alive but did not match the argument, fail */ - ecs_check(!any, ECS_INVALID_PARAMETER, NULL); - - /* Set generation if not alive. The sparse set checks if the provided - * id matches its own generation which is necessary for alive ids. This - * check would cause ecs_ensure to fail if the generation of the 'entity' - * argument doesn't match with its generation. - * - * While this could've been addressed in the sparse set, this is a rare - * scenario that can only be triggered by ecs_ensure. Implementing it here - * allows the sparse set to not do this check, which is more efficient. */ - flecs_entities_set_generation(world, entity); - - /* Ensure id exists. The underlying datastructure will verify that the - * generation count matches the provided one. */ - flecs_entities_ensure(world, entity); -error: - return; + diff_free(&diff); } -void ecs_ensure_id( - ecs_world_t *world, - ecs_id_t id) +static +bool flecs_on_delete_clear_tables( + ecs_world_t *world) { - if (ECS_HAS_ID_FLAG(id, PAIR)) { - ecs_entity_t r = ECS_PAIR_FIRST(id); - ecs_entity_t o = ECS_PAIR_SECOND(id); + int32_t i, last = ecs_vector_count(world->store.marked_ids), first = 0; + ecs_marked_id_t *ids = ecs_vector_first(world->store.marked_ids, + ecs_marked_id_t); - ecs_check(r != 0, ECS_INVALID_PARAMETER, NULL); - ecs_check(o != 0, ECS_INVALID_PARAMETER, NULL); + /* Iterate in reverse order so that DAGs get deleted bottom to top */ + do { + for (i = last - 1; i >= first; i --) { + ecs_id_record_t *idr = ids[i].idr; + ecs_entity_t action = ids[i].action; + + /* Empty all tables for id */ + ecs_table_cache_iter_t it; + if (flecs_table_cache_iter(&idr->cache, &it)) { + ecs_table_record_t *tr; + while ((tr = flecs_table_cache_next(&it, ecs_table_record_t))) { + ecs_table_t *table = tr->hdr.table; - if (ecs_get_alive(world, r) == 0) { - ecs_ensure(world, r); + if ((action == EcsRemove) || + !(table->flags & EcsTableMarkedForDelete)) + { + flecs_remove_from_table(world, table); + } else { + ecs_dbg_3( + "#[red]delete#[reset] entities from table %u", + (uint32_t)table->id); + flecs_table_delete_entities(world, table); + } + } + } + + /* Run commands so children get notified before parent is deleted */ + ecs_defer_end(world); + ecs_defer_begin(world); + + /* User code (from triggers) could have enqueued more ids to delete, + * reobtain the array in case it got reallocated */ + ids = ecs_vector_first(world->store.marked_ids, ecs_marked_id_t); } - if (ecs_get_alive(world, o) == 0) { - ecs_ensure(world, o); + + /* Check if new ids were marked since we started */ + int32_t new_last = ecs_vector_count(world->store.marked_ids); + if (new_last != last) { + /* Iterate remaining ids */ + ecs_assert(new_last > last, ECS_INTERNAL_ERROR, NULL); + first = last; + last = new_last; + } else { + break; } - } else { - ecs_ensure(world, id & ECS_COMPONENT_MASK); - } -error: - return; + } while (true); + + return true; } -bool ecs_exists( - const ecs_world_t *world, - ecs_entity_t entity) +static +bool flecs_on_delete_clear_ids( + ecs_world_t *world) { - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(entity != 0, ECS_INVALID_PARAMETER, NULL); + int32_t i, count = ecs_vector_count(world->store.marked_ids); + ecs_marked_id_t *ids = ecs_vector_first(world->store.marked_ids, + ecs_marked_id_t); - world = ecs_get_world(world); + for (i = 0; i < count; i ++) { + ecs_id_record_t *idr = ids[i].idr; - return flecs_entities_exists(world, entity); -error: - return false; + flecs_id_record_release_tables(world, idr); + + /* Release the claim taken by flecs_marked_id_push. This may delete the + * id record as all other claims may have been released. */ + if (flecs_id_record_release(world, idr)) { + /* If the id record is still alive, release the initial claim */ + flecs_id_record_release(world, idr); + } + } + + return true; } -ecs_table_t* ecs_get_table( - const ecs_world_t *world, - ecs_entity_t entity) +static +void flecs_on_delete( + ecs_world_t *world, + ecs_id_t id, + ecs_entity_t action) { - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(ecs_is_valid(world, entity), ECS_INVALID_PARAMETER, NULL); - - world = ecs_get_world(world); - - ecs_record_t *record = flecs_entities_get(world, entity); - ecs_table_t *table; - if (record && (table = record->table)) { - return table; - } -error: - return NULL; -} + /* Cleanup can happen recursively. If a cleanup action is already in + * progress, only append ids to the marked_ids. The topmost cleanup + * frame will handle the actual cleanup. */ + int32_t count = ecs_vector_count(world->store.marked_ids); -ecs_table_t* ecs_get_storage_table( - const ecs_world_t *world, - ecs_entity_t entity) -{ - ecs_table_t *table = ecs_get_table(world, entity); - if (table) { - return table->storage_table; - } + /* Make sure we're evaluating a consistent list of non-empty tables */ + ecs_run_aperiodic(world, EcsAperiodicEmptyTables); - return NULL; -} + /* Collect all ids that need to be deleted */ + flecs_on_delete_mark(world, id, action); -const ecs_type_t* ecs_get_type( - const ecs_world_t *world, - ecs_entity_t entity) -{ - ecs_table_t *table = ecs_get_table(world, entity); - if (table) { - return &table->type; - } + /* Only perform cleanup if we're the first stack frame doing it */ + if (!count && ecs_vector_count(world->store.marked_ids)) { + ecs_dbg_2("#[red]delete#[reset]"); + ecs_log_push_2(); - return NULL; -} + /* Empty tables with all the to be deleted ids */ + flecs_on_delete_clear_tables(world); -const ecs_type_info_t* ecs_get_type_info( - const ecs_world_t *world, - ecs_id_t id) -{ - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + /* All marked tables are empty, ensure they're in the right list */ + ecs_run_aperiodic(world, EcsAperiodicEmptyTables); - world = ecs_get_world(world); + /* Release remaining references to the ids */ + flecs_on_delete_clear_ids(world); - ecs_id_record_t *idr = flecs_id_record_get(world, id); - if (!idr && ECS_IS_PAIR(id)) { - idr = flecs_id_record_get(world, - ecs_pair(ECS_PAIR_FIRST(id), EcsWildcard)); - if (!idr || !idr->type_info) { - idr = NULL; - } - if (!idr) { - ecs_entity_t first = ecs_pair_first(world, id); - if (!first || !ecs_has_id(world, first, EcsTag)) { - idr = flecs_id_record_get(world, - ecs_pair(EcsWildcard, ECS_PAIR_SECOND(id))); - if (!idr || !idr->type_info) { - idr = NULL; - } - } + /* Verify deleted ids are no longer in use */ +#ifdef FLECS_DEBUG + ecs_marked_id_t *ids = ecs_vector_first(world->store.marked_ids, + ecs_marked_id_t); + int32_t i; + count = ecs_vector_count(world->store.marked_ids); + for (i = 0; i < count; i ++) { + ecs_assert(!ecs_id_in_use(world, ids[i].id), + ECS_INTERNAL_ERROR, NULL); } - } +#endif + ecs_assert(!ecs_id_in_use(world, id), ECS_INTERNAL_ERROR, NULL); - if (idr) { - return idr->type_info; - } else if (!(id & ECS_ID_FLAGS_MASK)) { - return flecs_sparse_get(world->type_info, ecs_type_info_t, id); + /* Ids are deleted, clear stack */ + ecs_vector_clear(world->store.marked_ids); + + ecs_log_pop_2(); } -error: - return NULL; } -ecs_entity_t ecs_get_typeid( - const ecs_world_t *world, +void ecs_delete_with( + ecs_world_t *world, ecs_id_t id) { - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - const ecs_type_info_t *ti = ecs_get_type_info(world, id); - if (ti) { - return ti->component; + ecs_stage_t *stage = flecs_stage_from_world(&world); + if (flecs_defer_on_delete_action(world, stage, id, EcsDelete)) { + return; } -error: - return 0; + + flecs_on_delete(world, id, EcsDelete); + flecs_defer_end(world, stage); } -ecs_entity_t ecs_id_is_tag( - const ecs_world_t *world, +void ecs_remove_all( + ecs_world_t *world, ecs_id_t id) { - if (ecs_id_is_wildcard(id)) { - /* If id is a wildcard, we can't tell if it's a tag or not, except - * when the relationship part of a pair has the Tag property */ - if (ECS_HAS_ID_FLAG(id, PAIR)) { - if (ECS_PAIR_FIRST(id) != EcsWildcard) { - ecs_entity_t rel = ecs_pair_first(world, id); - if (ecs_is_valid(world, rel)) { - if (ecs_has_id(world, rel, EcsTag)) { - return true; - } - } else { - /* During bootstrap it's possible that not all ids are valid - * yet. Using ecs_get_typeid will ensure correct values are - * returned for only those components initialized during - * bootstrap, while still asserting if another invalid id - * is provided. */ - if (ecs_get_typeid(world, id) == 0) { - return true; - } - } - } else { - /* If relationship is wildcard id is not guaranteed to be a tag */ - } - } - } else { - if (ecs_get_typeid(world, id) == 0) { - return true; - } + ecs_stage_t *stage = flecs_stage_from_world(&world); + if (flecs_defer_on_delete_action(world, stage, id, EcsRemove)) { + return; } - return false; + flecs_on_delete(world, id, EcsRemove); + flecs_defer_end(world, stage); } -int32_t ecs_count_id( - const ecs_world_t *world, - ecs_entity_t id) +void ecs_delete( + ecs_world_t *world, + ecs_entity_t entity) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(entity != 0, ECS_INVALID_PARAMETER, NULL); - if (!id) { - return 0; + ecs_stage_t *stage = flecs_stage_from_world(&world); + if (flecs_defer_delete(world, stage, entity)) { + return; } - int32_t count = 0; - ecs_iter_t it = ecs_term_iter(world, &(ecs_term_t) { - .id = id, - .src.flags = EcsSelf - }); + ecs_record_t *r = flecs_entities_get(world, entity); + if (r) { + ecs_flags32_t row_flags = ECS_RECORD_TO_ROW_FLAGS(r->row); + ecs_table_t *table; + if (row_flags) { + if (row_flags & EcsEntityObservedAcyclic) { + table = r->table; + if (table) { + table->observed_count --; + } + } + if (row_flags & EcsEntityObservedId) { + flecs_on_delete(world, entity, 0); + flecs_on_delete(world, ecs_pair(entity, EcsWildcard), 0); + } + if (row_flags & EcsEntityObservedTarget) { + flecs_on_delete(world, ecs_pair(EcsFlag, entity), 0); + flecs_on_delete(world, ecs_pair(EcsWildcard, entity), 0); + } - it.flags |= EcsIterIsFilter; - it.flags |= EcsIterEvalTables; + /* Merge operations before deleting entity */ + ecs_defer_end(world); + ecs_defer_begin(world); + } - while (ecs_term_next(&it)) { - count += it.count; - } + table = r->table; - return count; -error: - return 0; -} + /* If entity has components, remove them. Check if table is still alive, + * as delete actions could have deleted the table already. */ + if (table) { + ecs_table_diff_t diff = { + .removed = table->type, + .un_set = { table->storage_ids, table->storage_count } + }; -void ecs_enable( - ecs_world_t *world, - ecs_entity_t entity, - bool enabled) -{ - if (ecs_has_id(world, entity, EcsPrefab)) { - /* If entity is a type, enable/disable all entities in the type */ - const ecs_type_t *type = ecs_get_type(world, entity); - ecs_assert(type != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_id_t *ids = type->array; - int32_t i, count = type->count; - for (i = 0; i < count; i ++) { - ecs_id_t id = ids[i]; - if (ecs_id_get_flags(world, id) & EcsIdDontInherit) { - continue; - } - ecs_enable(world, id, enabled); - } - } else { - if (enabled) { - ecs_remove_id(world, entity, EcsDisabled); - } else { - ecs_add_id(world, entity, EcsDisabled); + delete_entity(world, r, &diff); + + r->row = 0; + r->table = NULL; } + + flecs_entities_remove(world, entity); } -} -bool ecs_defer_begin( - ecs_world_t *world) -{ - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_stage_t *stage = flecs_stage_from_world(&world); - return flecs_defer_begin(world, stage); + flecs_defer_end(world, stage); error: - return false; + return; } -bool ecs_defer_end( - ecs_world_t *world) +void ecs_add_id( + ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t id) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_stage_t *stage = flecs_stage_from_world(&world); - return flecs_defer_end(world, stage); + ecs_check(ecs_is_valid(world, entity), ECS_INVALID_PARAMETER, NULL); + ecs_check(ecs_id_is_valid(world, id), ECS_INVALID_PARAMETER, NULL); + add_id(world, entity, id); error: - return false; + return; } -void ecs_defer_suspend( - ecs_world_t *world) +void ecs_remove_id( + ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t id) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(ecs_is_deferred(world), ECS_INVALID_OPERATION, NULL); - ecs_stage_t *stage = flecs_stage_from_world(&world); - ecs_check(stage->defer_suspend == false, ECS_INVALID_OPERATION, NULL); - stage->defer_suspend = true; + ecs_check(ecs_is_valid(world, entity), ECS_INVALID_PARAMETER, NULL); + ecs_check(ecs_id_is_valid(world, id) || ecs_id_is_wildcard(id), + ECS_INVALID_PARAMETER, NULL); + remove_id(world, entity, id); error: return; } -void ecs_defer_resume( - ecs_world_t *world) +void ecs_override_id( + ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t id) { - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(ecs_is_deferred(world), ECS_INVALID_OPERATION, NULL); - ecs_stage_t *stage = flecs_stage_from_world(&world); - ecs_check(stage->defer_suspend == true, ECS_INVALID_OPERATION, NULL); - stage->defer_suspend = false; + ecs_check(ecs_id_is_valid(world, id), ECS_INVALID_PARAMETER, NULL); + ecs_add_id(world, entity, ECS_OVERRIDE | id); error: return; } -const char* ecs_id_flag_str( - ecs_entity_t entity) -{ - if (ECS_HAS_ID_FLAG(entity, PAIR)) { - return "PAIR"; - } else - if (ECS_HAS_ID_FLAG(entity, TOGGLE)) { - return "TOGGLE"; - } else - if (ECS_HAS_ID_FLAG(entity, OR)) { - return "OR"; - } else - if (ECS_HAS_ID_FLAG(entity, AND)) { - return "AND"; - } else - if (ECS_HAS_ID_FLAG(entity, NOT)) { - return "NOT"; - } else - if (ECS_HAS_ID_FLAG(entity, OVERRIDE)) { - return "OVERRIDE"; - } else { - return "UNKNOWN"; - } -} - -void ecs_id_str_buf( - const ecs_world_t *world, - ecs_id_t id, - ecs_strbuf_t *buf) +ecs_entity_t ecs_clone( + ecs_world_t *world, + ecs_entity_t dst, + ecs_entity_t src, + bool copy_value) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(src != 0, ECS_INVALID_PARAMETER, NULL); + ecs_check(ecs_is_valid(world, src), ECS_INVALID_PARAMETER, NULL); + ecs_check(!dst || !ecs_get_table(world, dst), ECS_INVALID_PARAMETER, NULL); - world = ecs_get_world(world); - - if (ECS_HAS_ID_FLAG(id, TOGGLE)) { - ecs_strbuf_appendstr(buf, ecs_id_flag_str(ECS_TOGGLE)); - ecs_strbuf_appendch(buf, '|'); - } - - if (ECS_HAS_ID_FLAG(id, OVERRIDE)) { - ecs_strbuf_appendstr(buf, ecs_id_flag_str(ECS_OVERRIDE)); - ecs_strbuf_appendch(buf, '|'); - } - - if (ECS_HAS_ID_FLAG(id, AND)) { - ecs_strbuf_appendstr(buf, ecs_id_flag_str(ECS_AND)); - ecs_strbuf_appendch(buf, '|'); + ecs_stage_t *stage = flecs_stage_from_world(&world); + if (!dst) { + dst = ecs_new_id(world); } - if (ECS_HAS_ID_FLAG(id, OR)) { - ecs_strbuf_appendstr(buf, ecs_id_flag_str(ECS_OR)); - ecs_strbuf_appendch(buf, '|'); + if (flecs_defer_clone(world, stage, dst, src, copy_value)) { + return dst; } - if (ECS_HAS_ID_FLAG(id, NOT)) { - ecs_strbuf_appendstr(buf, ecs_id_flag_str(ECS_NOT)); - ecs_strbuf_appendch(buf, '|'); + ecs_record_t *src_r = flecs_entities_get(world, src); + ecs_table_t *src_table; + if (!src_r || !(src_table = src_r->table)) { + goto done; } - if (ECS_HAS_ID_FLAG(id, PAIR)) { - ecs_entity_t rel = ECS_PAIR_FIRST(id); - ecs_entity_t obj = ECS_PAIR_SECOND(id); + ecs_type_t src_type = src_table->type; + ecs_table_diff_t diff = { .added = src_type }; + ecs_record_t *dst_r = new_entity(world, dst, NULL, src_table, &diff, true, true); + int32_t row = ECS_RECORD_TO_ROW(dst_r->row); - ecs_entity_t e; - if ((e = ecs_get_alive(world, rel))) { - rel = e; - } - if ((e = ecs_get_alive(world, obj))) { - obj = e; - } + if (copy_value) { + flecs_table_move(world, dst, src, src_table, + row, src_table, ECS_RECORD_TO_ROW(src_r->row), true); - ecs_strbuf_appendch(buf, '('); - ecs_get_path_w_sep_buf(world, 0, rel, NULL, NULL, buf); - ecs_strbuf_appendch(buf, ','); - ecs_get_path_w_sep_buf(world, 0, obj, NULL, NULL, buf); - ecs_strbuf_appendch(buf, ')'); - } else { - ecs_entity_t e = id & ECS_COMPONENT_MASK; - ecs_get_path_w_sep_buf(world, 0, e, NULL, NULL, buf); + flecs_notify_on_set(world, src_table, row, 1, NULL, true); } +done: + flecs_defer_end(world, stage); + return dst; error: - return; + return 0; } -char* ecs_id_str( +const void* ecs_get_id( const ecs_world_t *world, + ecs_entity_t entity, ecs_id_t id) { - ecs_strbuf_t buf = ECS_STRBUF_INIT; - ecs_id_str_buf(world, id, &buf); - return ecs_strbuf_get(&buf); -} - -static -void ecs_type_str_buf( - const ecs_world_t *world, - const ecs_type_t *type, - ecs_strbuf_t *buf) -{ - ecs_entity_t *ids = type->array; - int32_t i, count = type->count; - - for (i = 0; i < count; i ++) { - ecs_entity_t id = ids[i]; + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(ecs_is_valid(world, entity), ECS_INVALID_PARAMETER, NULL); + ecs_check(flecs_stage_from_readonly_world(world)->async == false, + ECS_INVALID_PARAMETER, NULL); - if (i) { - ecs_strbuf_appendch(buf, ','); - ecs_strbuf_appendch(buf, ' '); - } + world = ecs_get_world(world); - if (id == 1) { - ecs_strbuf_appendstr(buf, "Component"); - } else { - ecs_id_str_buf(world, id, buf); - } + ecs_record_t *r = flecs_entities_get(world, entity); + if (!r) { + return NULL; } -} -char* ecs_type_str( - const ecs_world_t *world, - const ecs_type_t *type) -{ - if (!type) { - return ecs_os_strdup(""); + ecs_table_t *table = r->table; + if (!table) { + return NULL; } - ecs_strbuf_t buf = ECS_STRBUF_INIT; - ecs_type_str_buf(world, type, &buf); - return ecs_strbuf_get(&buf); -} - -char* ecs_table_str( - const ecs_world_t *world, - const ecs_table_t *table) -{ - if (table) { - return ecs_type_str(world, &table->type); - } else { + ecs_id_record_t *idr = flecs_id_record_get(world, id); + if (!idr) { return NULL; } -} -char* ecs_entity_str( - const ecs_world_t *world, - ecs_entity_t entity) -{ - ecs_strbuf_t buf = ECS_STRBUF_INIT; + const ecs_table_record_t *tr = NULL; + ecs_table_t *storage_table = table->storage_table; + if (storage_table) { + tr = flecs_id_record_get_table(idr, storage_table); + } else { + /* If the entity does not have a storage table (has no data) but it does + * have the id, the id must be a tag, and getting a tag is illegal. */ + ecs_check(!ecs_owns_id(world, entity, id), ECS_NOT_A_COMPONENT, NULL); + } - ecs_get_path_w_sep_buf(world, 0, entity, 0, "", &buf); - - ecs_strbuf_appendstr(&buf, " ["); - const ecs_type_t *type = ecs_get_type(world, entity); - if (type) { - ecs_type_str_buf(world, type, &buf); + if (!tr) { + return get_base_component(world, table, id, idr, 0); } - ecs_strbuf_appendch(&buf, ']'); - return ecs_strbuf_get(&buf); + int32_t row = ECS_RECORD_TO_ROW(r->row); + return get_component_w_index(table, tr->column, row); +error: + return NULL; } -static -void flecs_flush_bulk_new( +void* ecs_get_mut_id( ecs_world_t *world, - ecs_defer_op_t *op) + ecs_entity_t entity, + ecs_id_t id) { - ecs_entity_t *entities = op->is._n.entities; + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(ecs_is_valid(world, entity), ECS_INVALID_PARAMETER, NULL); + ecs_check(ecs_id_is_valid(world, id), ECS_INVALID_PARAMETER, NULL); + + ecs_stage_t *stage = flecs_stage_from_world(&world); + void *result; - if (op->id) { - int i, count = op->is._n.count; - for (i = 0; i < count; i ++) { - add_id(world, entities[i], op->id); - } + if (flecs_defer_set( + world, stage, EcsOpMut, entity, id, 0, NULL, &result, false)) + { + return result; } - ecs_os_free(entities); + ecs_record_t *r = flecs_entities_ensure(world, entity); + result = flecs_get_mut(world, entity, id, r); + ecs_check(result != NULL, ECS_INVALID_PARAMETER, NULL); + + flecs_defer_end(world, stage); + + return result; +error: + return NULL; } static -void flecs_dtor_value( - ecs_world_t *world, - ecs_id_t id, - void *value, - int32_t count) +ecs_record_t* flecs_access_begin( + ecs_world_t *stage, + ecs_entity_t entity, + bool write) { - const ecs_type_info_t *ti = ecs_get_type_info(world, id); - ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_xtor_t dtor = ti->hooks.dtor; - if (dtor) { - ecs_size_t size = ti->size; - void *ptr; - int i; - for (i = 0, ptr = value; i < count; i ++, ptr = ECS_OFFSET(ptr, size)) { - dtor(ptr, 1, ti); - } + ecs_check(ecs_os_has_threading(), ECS_MISSING_OS_API, NULL); + + const ecs_world_t *world = ecs_get_world(stage); + ecs_record_t *r = flecs_entities_get(world, entity); + if (!r) { + return NULL; + } + + ecs_table_t *table; + if (!(table = r->table)) { + return NULL; + } + + int32_t count = ecs_os_ainc(&table->lock); + (void)count; + if (write) { + ecs_check(count == 1, ECS_ACCESS_VIOLATION, NULL); } + + return r; +error: + return NULL; } static -void flecs_discard_op( - ecs_world_t *world, - ecs_defer_op_t *op) +void flecs_access_end( + const ecs_record_t *r, + bool write) { - if (op->kind != EcsOpBulkNew) { - void *value = op->is._1.value; - if (value) { - flecs_dtor_value(world, op->id, value, 1); - flecs_stack_free(value, op->is._1.size); - } - } else { - ecs_os_free(op->is._n.entities); + ecs_check(ecs_os_has_threading(), ECS_MISSING_OS_API, NULL); + ecs_check(r != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(r->table != NULL, ECS_INVALID_PARAMETER, NULL); + int32_t count = ecs_os_adec(&r->table->lock); + (void)count; + if (write) { + ecs_check(count == 0, ECS_ACCESS_VIOLATION, NULL); } + ecs_check(count >= 0, ECS_ACCESS_VIOLATION, NULL); + +error: + return; } -static -bool flecs_is_entity_valid( +ecs_record_t* ecs_write_begin( ecs_world_t *world, - ecs_entity_t e) + ecs_entity_t entity) { - if (ecs_exists(world, e) && !ecs_is_alive(world, e)) { - return false; - } - return true; + return flecs_access_begin(world, entity, true); } -static -bool flecs_remove_invalid( +void ecs_write_end( + ecs_record_t *r) +{ + flecs_access_end(r, true); +} + +const ecs_record_t* ecs_read_begin( ecs_world_t *world, - ecs_id_t *id_out) + ecs_entity_t entity) { - ecs_id_t id = *id_out; + return flecs_access_begin(world, entity, false); +} - if (ECS_HAS_ID_FLAG(id, PAIR)) { - ecs_entity_t rel = ecs_pair_first(world, id); - if (!rel || !flecs_is_entity_valid(world, rel)) { - /* After relationship is deleted we can no longer see what its - * delete action was, so pretend this never happened */ - *id_out = 0; - return true; - } else { - ecs_entity_t obj = ecs_pair_second(world, id); - if (!obj || !flecs_is_entity_valid(world, obj)) { - /* Check the relationship's policy for deleted objects */ - ecs_id_record_t *idr = flecs_id_record_get(world, - ecs_pair(rel, EcsWildcard)); - if (idr) { - ecs_entity_t action = ECS_ID_ON_DELETE_OBJECT(idr->flags); - if (action == EcsDelete) { - /* Entity should be deleted, don't bother checking - * other ids */ - return false; - } else if (action == EcsPanic) { - /* If policy is throw this object should not have - * been deleted */ - flecs_throw_invalid_delete(world, id); - } else { - *id_out = 0; - return true; - } - } else { - *id_out = 0; - return true; - } - } - } - } else { - id &= ECS_COMPONENT_MASK; - if (!flecs_is_entity_valid(world, id)) { - /* After relationship is deleted we can no longer see what its - * delete action was, so pretend this never happened */ - *id_out = 0; - return true; - } +void ecs_read_end( + const ecs_record_t *r) +{ + flecs_access_end(r, false); +} + +const void* ecs_record_get_id( + ecs_world_t *stage, + const ecs_record_t *r, + ecs_id_t id) +{ + const ecs_world_t *world = ecs_get_world(stage); + return get_component(world, r->table, ECS_RECORD_TO_ROW(r->row), id); +} + +void* ecs_record_get_mut_id( + ecs_world_t *stage, + ecs_record_t *r, + ecs_id_t id) +{ + const ecs_world_t *world = ecs_get_world(stage); + return get_component(world, r->table, ECS_RECORD_TO_ROW(r->row), id); +} + +ecs_ref_t ecs_ref_init_id( + const ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t id) +{ + ecs_check(ecs_is_valid(world, entity), ECS_INVALID_PARAMETER, NULL); + ecs_check(ecs_id_is_valid(world, id), ECS_INVALID_PARAMETER, NULL); + + world = ecs_get_world(world); + + ecs_record_t *record = flecs_entities_get(world, entity); + ecs_check(record != NULL, ECS_INVALID_PARAMETER, + "cannot create ref for empty entity"); + + ecs_ref_t result = { + .entity = entity, + .id = id, + .record = record + }; + + ecs_table_t *table = record->table; + if (table) { + result.tr = flecs_table_record_get(world, table->storage_table, id); } - return true; + return result; +error: + return (ecs_ref_t){0}; } -/* Leave safe section. Run all deferred commands. */ -bool flecs_defer_end( - ecs_world_t *world, - ecs_stage_t *stage) +void* ecs_ref_get_id( + const ecs_world_t *world, + ecs_ref_t *ref, + ecs_id_t id) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(stage != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(ref != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(ref->entity != 0, ECS_INVALID_PARAMETER, NULL); + ecs_check(ref->id != 0, ECS_INVALID_PARAMETER, NULL); + ecs_check(ref->record != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(id == ref->id, ECS_INVALID_PARAMETER, NULL); - if (stage->defer_suspend) { - return false; + ecs_record_t *r = ref->record; + ecs_table_t *table = r->table; + if (!table) { + return NULL; } - if (!--stage->defer) { - /* Set to NULL. Processing deferred commands can cause additional - * commands to get enqueued (as result of reactive systems). Make sure - * that the original array is not reallocated, as this would complicate - * processing the queue. */ - ecs_vector_t *defer_queue = stage->defer_queue; - stage->defer_queue = NULL; + int32_t row = ECS_RECORD_TO_ROW(r->row); + ecs_check(row < ecs_table_count(table), ECS_INTERNAL_ERROR, NULL); - if (defer_queue) { - ecs_defer_op_t *ops = ecs_vector_first(defer_queue, ecs_defer_op_t); - int32_t i, count = ecs_vector_count(defer_queue); + ecs_table_record_t *tr = ref->tr; + if (!tr || tr->hdr.table != table) { + tr = ref->tr = flecs_table_record_get(world, table->storage_table, id); + if (!tr) { + return NULL; + } + } - ecs_stack_t stack = stage->defer_stack; - flecs_stack_init(&stage->defer_stack); + return get_component_w_index(table, tr->column, row); +error: + return NULL; +} - for (i = 0; i < count; i ++) { - ecs_defer_op_t *op = &ops[i]; - ecs_entity_t e = op->is._1.entity; - if (op->kind == EcsOpBulkNew) { - e = 0; - } +void* ecs_emplace_id( + ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t id) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(ecs_is_valid(world, entity), ECS_INVALID_PARAMETER, NULL); + ecs_check(ecs_id_is_valid(world, id), ECS_INVALID_PARAMETER, NULL); + ecs_check(!ecs_has_id(world, entity, id), ECS_INVALID_PARAMETER, NULL); - /* If entity is no longer alive, this could be because the queue - * contained both a delete and a subsequent add/remove/set which - * should be ignored. */ - if (e && !ecs_is_alive(world, e) && flecs_entities_exists(world, e)) { - ecs_assert(op->kind != EcsOpNew && op->kind != EcsOpClone, - ECS_INTERNAL_ERROR, NULL); - world->info.discard_count ++; - flecs_discard_op(world, op); - continue; - } + ecs_stage_t *stage = flecs_stage_from_world(&world); + void *result; - switch(op->kind) { - case EcsOpNew: - case EcsOpAdd: - ecs_assert(op->id != 0, ECS_INTERNAL_ERROR, NULL); - if (flecs_remove_invalid(world, &op->id)) { - if (op->id) { - world->info.add_count ++; - add_id(world, e, op->id); - } - } else { - ecs_delete(world, e); - } - break; - case EcsOpRemove: - remove_id(world, e, op->id); - break; - case EcsOpClone: - ecs_clone(world, e, op->id, op->is._1.clone_value); - break; - case EcsOpSet: - set_ptr_w_id(world, e, - op->id, flecs_itosize(op->is._1.size), - op->is._1.value, true, true); - break; - case EcsOpEmplace: - ecs_emplace_id(world, e, op->id); - set_ptr_w_id(world, e, - op->id, flecs_itosize(op->is._1.size), - op->is._1.value, true, false); - break; - case EcsOpMut: - set_ptr_w_id(world, e, - op->id, flecs_itosize(op->is._1.size), - op->is._1.value, true, false); - break; - case EcsOpModified: - if (ecs_has_id(world, e, op->id)) { - ecs_modified_id(world, e, op->id); - } - break; - case EcsOpDelete: { - ecs_delete(world, e); - break; - } - case EcsOpClear: - ecs_clear(world, e); - break; - case EcsOpOnDeleteAction: - flecs_on_delete(world, op->id, e); - break; - case EcsOpEnable: - ecs_enable_id(world, e, op->id, true); - break; - case EcsOpDisable: - ecs_enable_id(world, e, op->id, false); - break; - case EcsOpBulkNew: - flecs_flush_bulk_new(world, op); - continue; - } + if (flecs_defer_set(world, stage, EcsOpEmplace, entity, id, 0, NULL, + &result, true)) + { + return result; + } - if (op->is._1.value) { - flecs_stack_free(op->is._1.value, op->is._1.size); - } - } + ecs_record_t *r = flecs_entities_ensure(world, entity); + add_id_w_record(world, entity, r, id, false /* Add without ctor */); - if (stage->defer_queue) { - ecs_vector_free(stage->defer_queue); - } + void *ptr = get_component(world, r->table, ECS_RECORD_TO_ROW(r->row), id); + ecs_check(ptr != NULL, ECS_INVALID_PARAMETER, NULL); - /* Restore defer queue */ - ecs_vector_clear(defer_queue); - stage->defer_queue = defer_queue; + flecs_defer_end(world, stage); - /* Restore stack */ - flecs_stack_fini(&stage->defer_stack); - stage->defer_stack = stack; - flecs_stack_reset(&stage->defer_stack); - } + return ptr; +error: + return NULL; +} - return true; +void ecs_modified_id( + ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t id) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(ecs_is_valid(world, entity), ECS_INVALID_PARAMETER, NULL); + ecs_check(ecs_id_is_valid(world, id), ECS_INVALID_PARAMETER, NULL); + + ecs_stage_t *stage = flecs_stage_from_world(&world); + + if (flecs_defer_modified(world, stage, entity, id)) { + return; } + /* If the entity does not have the component, calling ecs_modified is + * invalid. The assert needs to happen after the defer statement, as the + * entity may not have the component when this function is called while + * operations are being deferred. */ + ecs_check(ecs_has_id(world, entity, id), ECS_INVALID_PARAMETER, NULL); + + ecs_record_t *r = flecs_entities_get(world, entity); + ecs_table_t *table = r->table; + ecs_type_t ids = { .array = &id, .count = 1 }; + flecs_notify_on_set(world, table, ECS_RECORD_TO_ROW(r->row), 1, &ids, true); + + flecs_table_mark_dirty(world, table, id); + flecs_defer_end(world, stage); error: - return false; + return; } -/* Delete operations from queue without executing them. */ -bool flecs_defer_purge( +static +ecs_entity_t set_ptr_w_id( ecs_world_t *world, - ecs_stage_t *stage) + ecs_entity_t entity, + ecs_id_t id, + size_t size, + void *ptr, + bool is_move, + bool flecs_notify) { - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(stage != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_stage_t *stage = flecs_stage_from_world(&world); - if (!--stage->defer) { - ecs_vector_t *defer_queue = stage->defer_queue; - stage->defer_queue = NULL; + if (!entity) { + entity = ecs_new_id(world); + ecs_entity_t scope = stage->scope; + if (scope) { + ecs_add_pair(world, entity, EcsChildOf, scope); + } + } - if (defer_queue) { - ecs_defer_op_t *ops = ecs_vector_first(defer_queue, ecs_defer_op_t); - int32_t i, count = ecs_vector_count(defer_queue); - for (i = 0; i < count; i ++) { - flecs_discard_op(world, &ops[i]); - } + if (flecs_defer_set(world, stage, EcsOpSet, entity, id, + flecs_utosize(size), ptr, NULL, false)) + { + return entity; + } - if (stage->defer_queue) { - ecs_vector_free(stage->defer_queue); - } + ecs_record_t *r = flecs_entities_ensure(world, entity); + void *dst = flecs_get_mut(world, entity, id, r); + ecs_check(dst != NULL, ECS_INVALID_PARAMETER, NULL); - /* Restore defer queue */ - ecs_vector_clear(defer_queue); - stage->defer_queue = defer_queue; - flecs_stack_reset(&stage->defer_stack); + if (ptr) { + ecs_entity_t real_id = ecs_get_typeid(world, id); + const ecs_type_info_t *ti = get_c_info(world, real_id); + if (ti) { + if (is_move) { + ecs_move_t move = ti->hooks.move; + if (move) { + move(dst, ptr, 1, ti); + } else { + ecs_os_memcpy(dst, ptr, flecs_utosize(size)); + } + } else { + ecs_copy_t copy = ti->hooks.copy; + if (copy) { + copy(dst, ptr, 1, ti); + } else { + ecs_os_memcpy(dst, ptr, flecs_utosize(size)); + } + } + } else { + ecs_os_memcpy(dst, ptr, flecs_utosize(size)); } + } else { + ecs_os_memset(dst, 0, size); + } - return true; + flecs_table_mark_dirty(world, r->table, id); + + if (flecs_notify) { + ecs_type_t ids = { .array = &id, .count = 1 }; + flecs_notify_on_set( + world, r->table, ECS_RECORD_TO_ROW(r->row), 1, &ids, true); } + flecs_defer_end(world, stage); + + return entity; error: - return false; + return 0; } +ecs_entity_t ecs_set_id( + ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t id, + size_t size, + const void *ptr) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(!entity || ecs_is_valid(world, entity), ECS_INVALID_PARAMETER, NULL); + ecs_check(ecs_id_is_valid(world, id), ECS_INVALID_PARAMETER, NULL); -#ifdef ECS_TARGET_GNU -#pragma GCC diagnostic ignored "-Wimplicit-fallthrough" -#endif + /* Safe to cast away const: function won't modify if move arg is false */ + return set_ptr_w_id( + world, entity, id, size, (void*)ptr, false, true); +error: + return 0; +} -/* 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(FLECS_NDEBUG) || defined(ADDRESS_SANITIZER) -#ifndef VALGRIND -#define VALGRIND -#endif -#endif +void ecs_enable_id( + ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t id, + bool enable) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(ecs_is_valid(world, entity), ECS_INVALID_PARAMETER, NULL); + ecs_check(ecs_id_is_valid(world, id), ECS_INVALID_PARAMETER, NULL); -/* -------------------------------------------------------------------------------- -lookup3.c, by Bob Jenkins, May 2006, Public Domain. - http://burtleburtle.net/bob/c/lookup3.c -------------------------------------------------------------------------------- -*/ + ecs_stage_t *stage = flecs_stage_from_world(&world); -#ifdef ECS_TARGET_WINDOWS -//FIXME -#else -#include /* attempt to define endianness */ -#endif -#ifdef ECS_TARGET_LINUX -# include /* attempt to define endianness */ -#endif + if (flecs_defer_enable( + world, stage, entity, id, enable)) + { + return; + } else { + /* Operations invoked by enable/disable should not be deferred */ + stage->defer --; + } -/* - * 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_record_t *r = flecs_entities_ensure(world, entity); + ecs_entity_t bs_id = id | ECS_TOGGLE; + + ecs_table_t *table = r->table; + int32_t index = -1; + if (table) { + index = ecs_search(world, table, bs_id, 0); + } -#define rot(x,k) (((x)<<(k)) | ((x)>>(32-(k)))) + if (index == -1) { + ecs_add_id(world, entity, bs_id); + ecs_enable_id(world, entity, id, enable); + return; + } -/* -------------------------------------------------------------------------------- -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; \ -} + index -= table->bs_offset; + ecs_assert(index >= 0, ECS_INTERNAL_ERROR, NULL); -/* -------------------------------------------------------------------------------- -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); \ -} + /* Data cannot be NULl, since entity is stored in the table */ + ecs_bitset_t *bs = &table->data.bs_columns[index]; + ecs_assert(bs != NULL, ECS_INTERNAL_ERROR, NULL); + flecs_bitset_set(bs, ECS_RECORD_TO_ROW(r->row), enable); +error: + return; +} -/* - * 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 */ +bool ecs_is_enabled_id( + const ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t id) { - uint32_t a,b,c; /* internal state */ - union { const void *ptr; size_t i; } u; /* needed for Mac Powerbook G4 */ - - /* Set up the internal state */ - a = b = c = 0xdeadbeef + ((uint32_t)length) + *pc; - c += *pb; + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(ecs_is_valid(world, entity), ECS_INVALID_PARAMETER, NULL); + ecs_check(ecs_id_is_valid(world, id), ECS_INVALID_PARAMETER, NULL); - 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; + /* Make sure we're not working with a stage */ + world = ecs_get_world(world); - /*------ 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; + ecs_record_t *r = flecs_entities_get(world, entity); + ecs_table_t *table; + if (!r || !(table = r->table)) { + return false; } - /*----------------------------- 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 - - 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 */ + ecs_entity_t bs_id = id | ECS_TOGGLE; + int32_t index = ecs_search(world, table, bs_id, 0); + if (index == -1) { + /* If table does not have TOGGLE column for component, component is + * always enabled, if the entity has it */ + return ecs_has_id(world, entity, id); } -#else /* make valgrind happy */ - - 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 */ - } + index -= table->bs_offset; + ecs_assert(index >= 0, ECS_INTERNAL_ERROR, NULL); + ecs_bitset_t *bs = &table->data.bs_columns[index]; -#endif /* !valgrind */ + return flecs_bitset_get(bs, ECS_RECORD_TO_ROW(r->row)); +error: + return false; +} - } 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 ecs_has_id( + const ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t id) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(ecs_is_valid(world, entity), ECS_INVALID_PARAMETER, NULL); + ecs_check(id != 0, ECS_INVALID_PARAMETER, NULL); - /*--------------- 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; - } + /* Make sure we're not working with a stage */ + world = ecs_get_world(world); - /*----------------------------- 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 */ + ecs_record_t *r = flecs_entities_get(world, entity); + ecs_table_t *table; + if (!r || !(table = r->table)) { + return false; } - } else { /* need to read the key one byte at a time */ - const uint8_t *k = (const uint8_t *)key; - - /*--------------- 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; + ecs_table_record_t *tr = NULL; + int32_t column = ecs_search_relation( + world, table, 0, id, EcsIsA, 0, 0, 0, &tr); + if (column == -1) { + return false; } - /*-------------------------------- last block: affect all 32 bits of (c) */ - switch(length) /* all the case statements fall through */ + table = tr->hdr.table; + if ((table->flags & EcsTableHasUnion) && ECS_HAS_ID_FLAG(id, PAIR) && + ECS_PAIR_SECOND(id) != EcsWildcard) { - 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 */ + if (ECS_PAIR_FIRST(table->type.array[column]) == EcsUnion) { + ecs_switch_t *sw = &table->data.sw_columns[ + column - table->sw_offset]; + int32_t row = ECS_RECORD_TO_ROW(r->row); + uint64_t value = flecs_switch_get(sw, row); + return value == ECS_PAIR_SECOND(id); + } } - } - - final(a,b,c); - *pc=c; *pb=b; + + return true; +error: + return false; } -uint64_t flecs_hash( - const void *data, - ecs_size_t length) +ecs_entity_t ecs_get_target( + const ecs_world_t *world, + ecs_entity_t entity, + ecs_entity_t rel, + int32_t index) { - uint32_t h_1 = 0; - uint32_t h_2 = 0; + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(entity != 0, ECS_INVALID_PARAMETER, NULL); + ecs_check(rel != 0, ECS_INVALID_PARAMETER, NULL); - hashlittle2( - data, - flecs_ito(size_t, length), - &h_1, - &h_2); + world = ecs_get_world(world); - return h_1 | ((uint64_t)h_2 << 32); -} + ecs_record_t *r = flecs_entities_get(world, entity); + ecs_table_t *table; + if (!r || !(table = r->table)) { + return 0; + } + ecs_id_t wc = ecs_pair(rel, EcsWildcard); + ecs_table_record_t *tr = flecs_table_record_get(world, table, wc); + if (!tr) { + if (table->flags & EcsTableHasUnion) { + wc = ecs_pair(EcsUnion, rel); + tr = flecs_table_record_get(world, table, wc); + if (tr) { + ecs_switch_t *sw = &table->data.sw_columns[ + tr->column - table->sw_offset]; + int32_t row = ECS_RECORD_TO_ROW(r->row); + return flecs_switch_get(sw, row); + + } + } + return 0; + } -/** The number of elements in a single chunk */ -#define CHUNK_COUNT (4096) + if (index >= tr->count) { + return 0; + } -/** Compute the chunk index from an id by stripping the first 12 bits */ -#define CHUNK(index) ((int32_t)((uint32_t)index >> 12)) + ecs_id_t *ids = table->type.array; + return ecs_pair_second(world, ids[tr->column + index]); +error: + return 0; +} -/** This computes the offset of an index inside a chunk */ -#define OFFSET(index) ((int32_t)index & 0xFFF) +ecs_entity_t ecs_get_target_for_id( + const ecs_world_t *world, + ecs_entity_t entity, + ecs_entity_t rel, + ecs_id_t id) +{ + ecs_table_t *table = ecs_get_table(world, entity); + ecs_entity_t subject = 0; -/* Utility to get a pointer to the payload */ -#define DATA(array, size, offset) (ECS_OFFSET(array, size * offset)) + if (rel) { + int32_t column = ecs_search_relation( + world, table, 0, id, rel, 0, &subject, 0, 0); + if (column == -1) { + return 0; + } + } else { + ecs_id_t *ids = table->type.array; + int32_t i, count = table->type.count; -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; + for (i = 0; i < count; i ++) { + ecs_id_t ent = ids[i]; + if (ent & ECS_ID_FLAGS_MASK) { + /* Skip ids with pairs, roles since 0 was provided for rel */ + break; + } -static -chunk_t* chunk_new( - ecs_sparse_t *sparse, - int32_t chunk_index) -{ - int32_t count = ecs_vector_count(sparse->chunks); - chunk_t *chunks; + if (ecs_has_id(world, ent, id)) { + subject = ent; + break; + } + } + } - 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)); + if (subject == 0) { + return entity; } else { - chunks = ecs_vector_first(sparse->chunks, chunk_t); + return subject; } +} - ecs_assert(chunks != NULL, ECS_INTERNAL_ERROR, NULL); - - chunk_t *result = &chunks[chunk_index]; - ecs_assert(result->sparse == NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(result->data == NULL, ECS_INTERNAL_ERROR, 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); +static +const char* get_identifier( + const ecs_world_t *world, + ecs_entity_t entity, + ecs_entity_t tag) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(ecs_is_valid(world, entity), ECS_INVALID_PARAMETER, NULL); - /* 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); + const EcsIdentifier *ptr = ecs_get_pair( + world, entity, EcsIdentifier, tag); - ecs_assert(result->sparse != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(result->data != NULL, ECS_INTERNAL_ERROR, NULL); + if (ptr) { + return ptr->value; + } else { + return NULL; + } +error: + return NULL; +} - return result; +const char* ecs_get_name( + const ecs_world_t *world, + ecs_entity_t entity) +{ + return get_identifier(world, entity, EcsName); } -static -void chunk_free( - chunk_t *chunk) +const char* ecs_get_symbol( + const ecs_world_t *world, + ecs_entity_t entity) { - ecs_os_free(chunk->sparse); - ecs_os_free(chunk->data); + return get_identifier(world, entity, EcsSymbol); } static -chunk_t* get_chunk( - const ecs_sparse_t *sparse, - int32_t chunk_index) +ecs_entity_t flecs_set_identifier( + ecs_world_t *world, + ecs_entity_t entity, + ecs_entity_t tag, + const char *name) { - if (!sparse->chunks) { - return NULL; - } - if (chunk_index >= ecs_vector_count(sparse->chunks)) { - return NULL; + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(entity != 0 || name != NULL, ECS_INVALID_PARAMETER, NULL); + + if (!entity) { + entity = ecs_new_id(world); } - /* 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; + if (!name) { + ecs_remove_pair(world, entity, ecs_id(EcsIdentifier), tag); + return entity; } - return result; + EcsIdentifier *ptr = ecs_get_mut_pair(world, entity, EcsIdentifier, tag); + ecs_assert(ptr != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_os_strset(&ptr->value, name); + ecs_modified_pair(world, entity, ecs_id(EcsIdentifier), tag); + + return entity; +error: + return 0; } -static -chunk_t* get_or_create_chunk( - ecs_sparse_t *sparse, - int32_t chunk_index) + +ecs_entity_t ecs_set_name( + ecs_world_t *world, + ecs_entity_t entity, + const char *name) { - chunk_t *chunk = get_chunk(sparse, chunk_index); - if (chunk) { - return chunk; + if (!entity) { + return ecs_entity_init(world, &(ecs_entity_desc_t){ + .name = name + }); } - - return chunk_new(sparse, chunk_index); + return flecs_set_identifier(world, entity, EcsName, name); } -static -void grow_dense( - ecs_sparse_t *sparse) +ecs_entity_t ecs_set_symbol( + ecs_world_t *world, + ecs_entity_t entity, + const char *name) { - ecs_vector_add(&sparse->dense, uint64_t); + return flecs_set_identifier(world, entity, EcsSymbol, name); } -static -uint64_t strip_generation( - uint64_t *index_out) +void ecs_set_alias( + ecs_world_t *world, + ecs_entity_t entity, + const char *name) { - 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; + flecs_set_identifier(world, entity, EcsAlias, name); } -static -void assign_index( - chunk_t * chunk, - uint64_t * dense_array, - uint64_t index, - int32_t dense) +ecs_id_t ecs_make_pair( + ecs_entity_t relationship, + ecs_entity_t target) { - /* 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; + return ecs_pair(relationship, target); } -static -uint64_t inc_gen( - uint64_t index) +bool ecs_is_valid( + const ecs_world_t *world, + ecs_entity_t entity) { - /* When an index is deleted, its generation is increased so that we can do - * liveliness checking while recycling ids */ - return ECS_GENERATION_INC(index); -} + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); -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]); -} + /* 0 is not a valid entity id */ + if (!entity) { + return false; + } + + /* Make sure we're not working with a stage */ + world = ecs_get_world(world); + + /* Entity identifiers should not contain flag bits */ + if (entity & ECS_ID_FLAGS_MASK) { + return false; + } -static -uint64_t get_id( - const ecs_sparse_t *sparse) -{ - return sparse->max_id[0]; -} + /* Entities should not contain data in dead zone bits */ + if (entity & ~0xFF00FFFFFFFFFFFF) { + return false; + } -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; + if (entity & ECS_ID_FLAG_BIT) { + return ecs_entity_t_lo(entity) != 0; + } + + /* If entity doesn't exist in the world, the id is valid as long as the + * generation is 0. Using a non-existing id with a non-zero generation + * requires calling ecs_ensure first. */ + if (!ecs_exists(world, entity)) { + return ECS_GENERATION(entity) == 0; + } + + /* If id exists, it must be alive (the generation count must match) */ + return ecs_is_alive(world, entity); +error: + return false; } -/* Pair dense id with new sparse id */ -static -uint64_t create_id( - ecs_sparse_t *sparse, - int32_t dense) +ecs_id_t ecs_strip_generation( + ecs_entity_t e) { - uint64_t index = inc_id(sparse); - grow_dense(sparse); + /* If this is not a pair, erase the generation bits */ + if (!(e & ECS_ID_FLAGS_MASK)) { + e &= ~ECS_GENERATION_MASK; + } - 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; + return e; } -/* Create new id */ -static -uint64_t new_index( - ecs_sparse_t *sparse) +bool ecs_is_alive( + const ecs_world_t *world, + ecs_entity_t entity) { - ecs_vector_t *dense = sparse->dense; - int32_t dense_count = ecs_vector_count(dense); - int32_t count = sparse->count ++; + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(entity != 0, ECS_INVALID_PARAMETER, NULL); - ecs_assert(count <= dense_count, ECS_INTERNAL_ERROR, NULL); + world = ecs_get_world(world); - 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); - } + return flecs_entities_is_alive(world, entity); +error: + return false; } -/* 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); +ecs_entity_t ecs_get_alive( + const ecs_world_t *world, + ecs_entity_t entity) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + + if (!entity) { + return 0; + } - chunk_t *chunk = get_chunk(sparse, CHUNK(index)); - if (!chunk) { - return NULL; + if (ecs_is_alive(world, entity)) { + return entity; } - int32_t offset = OFFSET(index); - int32_t dense = chunk->sparse[offset]; - bool in_use = dense && (dense < sparse->count); - if (!in_use) { - return NULL; + /* Make sure id does not have generation. This guards against accidentally + * "upcasting" a not alive identifier to a alive one. */ + ecs_assert((uint32_t)entity == entity, ECS_INVALID_PARAMETER, NULL); + + /* Make sure we're not working with a stage */ + world = ecs_get_world(world); + + ecs_entity_t current = flecs_entities_get_current(world, entity); + if (!current || !ecs_is_alive(world, current)) { + return 0; } - ecs_assert(dense == chunk->sparse[offset], ECS_INTERNAL_ERROR, NULL); - return DATA(chunk->data, sparse->size, offset); + return current; +error: + return 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) +void ecs_ensure( + ecs_world_t *world, + ecs_entity_t entity) { - chunk_t *chunk = get_chunk(sparse, CHUNK(index)); - if (!chunk) { - return NULL; - } + ecs_poly_assert(world, ecs_world_t); /* Cannot be a stage */ + ecs_check(entity != 0, ECS_INVALID_PARAMETER, NULL); - int32_t offset = OFFSET(index); - int32_t dense = chunk->sparse[offset]; - bool in_use = dense && (dense < sparse->count); - if (!in_use) { - return NULL; + /* Check if a version of the provided id is alive */ + ecs_entity_t any = ecs_get_alive(world, ecs_strip_generation(entity)); + if (any == entity) { + /* If alive and equal to the argument, there's nothing left to do */ + return; } - 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 the id is currently alive but did not match the argument, fail */ + ecs_check(!any, ECS_INVALID_PARAMETER, NULL); - if (cur_gen != gen) { - return NULL; - } + /* Set generation if not alive. The sparse set checks if the provided + * id matches its own generation which is necessary for alive ids. This + * check would cause ecs_ensure to fail if the generation of the 'entity' + * argument doesn't match with its generation. + * + * While this could've been addressed in the sparse set, this is a rare + * scenario that can only be triggered by ecs_ensure. Implementing it here + * allows the sparse set to not do this check, which is more efficient. */ + flecs_entities_set_generation(world, entity); - ecs_assert(dense == chunk->sparse[offset], ECS_INTERNAL_ERROR, NULL); - return DATA(chunk->data, sparse->size, offset); + /* Ensure id exists. The underlying datastructure will verify that the + * generation count matches the provided one. */ + flecs_entities_ensure(world, entity); +error: + return; } -/* 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) +void ecs_ensure_id( + ecs_world_t *world, + ecs_id_t id) { - 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; + if (ECS_HAS_ID_FLAG(id, PAIR)) { + ecs_entity_t r = ECS_PAIR_FIRST(id); + ecs_entity_t o = ECS_PAIR_SECOND(id); - return DATA(chunk->data, sparse->size, offset); + ecs_check(r != 0, ECS_INVALID_PARAMETER, NULL); + ecs_check(o != 0, ECS_INVALID_PARAMETER, NULL); + + if (ecs_get_alive(world, r) == 0) { + ecs_ensure(world, r); + } + if (ecs_get_alive(world, o) == 0) { + ecs_ensure(world, o); + } + } else { + ecs_ensure(world, id & ECS_COMPONENT_MASK); + } +error: + return; } -/* 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) +bool ecs_exists( + const ecs_world_t *world, + ecs_entity_t entity) { - 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]; + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(entity != 0, ECS_INVALID_PARAMETER, NULL); - 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); + world = ecs_get_world(world); + + return flecs_entities_exists(world, entity); +error: + return false; } -void _flecs_sparse_init( - ecs_sparse_t *result, - ecs_size_t size) +ecs_table_t* ecs_get_table( + const ecs_world_t *world, + ecs_entity_t entity) { - 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; + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(ecs_is_valid(world, entity), ECS_INVALID_PARAMETER, NULL); + + world = ecs_get_world(world); - result->count = 1; + ecs_record_t *record = flecs_entities_get(world, entity); + ecs_table_t *table; + if (record && (table = record->table)) { + return table; + } +error: + return NULL; } -ecs_sparse_t* _flecs_sparse_new( - ecs_size_t size) +ecs_table_t* ecs_get_storage_table( + const ecs_world_t *world, + ecs_entity_t entity) { - ecs_sparse_t *result = ecs_os_calloc_t(ecs_sparse_t); - - _flecs_sparse_init(result, size); + ecs_table_t *table = ecs_get_table(world, entity); + if (table) { + return table->storage_table; + } - return result; + return NULL; } -void flecs_sparse_set_id_source( - ecs_sparse_t * sparse, - uint64_t * id_source) +const ecs_type_t* ecs_get_type( + const ecs_world_t *world, + ecs_entity_t entity) { - ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); - sparse->max_id = id_source; + ecs_table_t *table = ecs_get_table(world, entity); + if (table) { + return &table->type; + } + + return NULL; } -void flecs_sparse_clear( - ecs_sparse_t *sparse) +const ecs_type_info_t* ecs_get_type_info( + const ecs_world_t *world, + ecs_id_t id) { - ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_vector_each(sparse->chunks, chunk_t, chunk, { - chunk_free(chunk); - }); + world = ecs_get_world(world); - ecs_vector_free(sparse->chunks); - ecs_vector_set_count(&sparse->dense, uint64_t, 1); + ecs_id_record_t *idr = flecs_id_record_get(world, id); + if (!idr && ECS_IS_PAIR(id)) { + idr = flecs_id_record_get(world, + ecs_pair(ECS_PAIR_FIRST(id), EcsWildcard)); + if (!idr || !idr->type_info) { + idr = NULL; + } + if (!idr) { + ecs_entity_t first = ecs_pair_first(world, id); + if (!first || !ecs_has_id(world, first, EcsTag)) { + idr = flecs_id_record_get(world, + ecs_pair(EcsWildcard, ECS_PAIR_SECOND(id))); + if (!idr || !idr->type_info) { + idr = NULL; + } + } + } + } - sparse->chunks = NULL; - sparse->count = 1; - sparse->max_id_local = 0; + if (idr) { + return idr->type_info; + } else if (!(id & ECS_ID_FLAGS_MASK)) { + return flecs_sparse_get(world->type_info, ecs_type_info_t, id); + } +error: + return NULL; } -void _flecs_sparse_fini( - ecs_sparse_t *sparse) +ecs_entity_t ecs_get_typeid( + const ecs_world_t *world, + ecs_id_t id) { - ecs_assert(sparse != NULL, ECS_INTERNAL_ERROR, NULL); - flecs_sparse_clear(sparse); - ecs_vector_free(sparse->dense); + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + const ecs_type_info_t *ti = ecs_get_type_info(world, id); + if (ti) { + return ti->component; + } +error: + return 0; } -void flecs_sparse_free( - ecs_sparse_t *sparse) +ecs_entity_t ecs_id_is_tag( + const ecs_world_t *world, + ecs_id_t id) { - if (sparse) { - _flecs_sparse_fini(sparse); - ecs_os_free(sparse); + if (ecs_id_is_wildcard(id)) { + /* If id is a wildcard, we can't tell if it's a tag or not, except + * when the relationship part of a pair has the Tag property */ + if (ECS_HAS_ID_FLAG(id, PAIR)) { + if (ECS_PAIR_FIRST(id) != EcsWildcard) { + ecs_entity_t rel = ecs_pair_first(world, id); + if (ecs_is_valid(world, rel)) { + if (ecs_has_id(world, rel, EcsTag)) { + return true; + } + } else { + /* During bootstrap it's possible that not all ids are valid + * yet. Using ecs_get_typeid will ensure correct values are + * returned for only those components initialized during + * bootstrap, while still asserting if another invalid id + * is provided. */ + if (ecs_get_typeid(world, id) == 0) { + return true; + } + } + } else { + /* If relationship is wildcard id is not guaranteed to be a tag */ + } + } + } else { + if (ecs_get_typeid(world, id) == 0) { + return true; + } } -} -uint64_t flecs_sparse_new_id( - ecs_sparse_t *sparse) -{ - ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); - return new_index(sparse); + return false; } -const uint64_t* flecs_sparse_new_ids( - ecs_sparse_t *sparse, - int32_t new_count) +int32_t ecs_count_id( + const ecs_world_t *world, + ecs_entity_t id) { - 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; + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - if (to_create > 0) { - flecs_sparse_set_size(sparse, dense_count + to_create); - for (i = 0; i < to_create; i ++) { - create_id(sparse, count + i); - } + if (!id) { + return 0; } - sparse->count += new_count; + int32_t count = 0; + ecs_iter_t it = ecs_term_iter(world, &(ecs_term_t) { + .id = id, + .src.flags = EcsSelf + }); - return ecs_vector_get(sparse->dense, uint64_t, count); -} + it.flags |= EcsIterIsFilter; + it.flags |= EcsIterEvalTables; -void* _flecs_sparse_add( - ecs_sparse_t *sparse, - ecs_size_t size) -{ - 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)); -} + while (ecs_term_next(&it)) { + count += it.count; + } -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]; + return count; +error: + return 0; } -void* _flecs_sparse_ensure( - ecs_sparse_t *sparse, - ecs_size_t size, - uint64_t index) +void ecs_enable( + ecs_world_t *world, + ecs_entity_t entity, + bool enabled) { - 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; - - 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]; - - 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); - - /* First unused element is now last used element */ - sparse->count ++; - } else { - /* Dense is already alive, nothing to be done */ + if (ecs_has_id(world, entity, EcsPrefab)) { + /* If entity is a type, enable/disable all entities in the type */ + const ecs_type_t *type = ecs_get_type(world, entity); + ecs_assert(type != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_id_t *ids = type->array; + int32_t i, count = type->count; + for (i = 0; i < count; i ++) { + ecs_id_t id = ids[i]; + if (ecs_id_get_flags(world, id) & EcsIdDontInherit) { + continue; + } + ecs_enable(world, id, enabled); } - - /* 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); - } - - 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); + if (enabled) { + ecs_remove_id(world, entity, EcsDisabled); + } else { + ecs_add_id(world, entity, EcsDisabled); } - - assign_index(chunk, dense_array, index, count); - dense_array[count] |= gen; } - - return DATA(chunk->data, sparse->size, offset); } -void* _flecs_sparse_set( - ecs_sparse_t * sparse, - ecs_size_t elem_size, - uint64_t index, - void* value) +bool ecs_defer_begin( + ecs_world_t *world) { - void *ptr = _flecs_sparse_ensure(sparse, elem_size, index); - ecs_os_memcpy(ptr, value, elem_size); - return ptr; + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_stage_t *stage = flecs_stage_from_world(&world); + return flecs_defer_begin(world, stage); +error: + return false; } -void* _flecs_sparse_remove_get( - ecs_sparse_t *sparse, - ecs_size_t size, - uint64_t index) +bool ecs_defer_end( + ecs_world_t *world) { - 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; - } + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_stage_t *stage = flecs_stage_from_world(&world); + return flecs_defer_end(world, stage); +error: + return false; } -void flecs_sparse_remove( - ecs_sparse_t *sparse, - uint64_t index) +void ecs_defer_suspend( + ecs_world_t *world) { - void *ptr = _flecs_sparse_remove_get(sparse, 0, index); - if (ptr) { - ecs_os_memset(ptr, 0, sparse->size); - } + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(ecs_is_deferred(world), ECS_INVALID_OPERATION, NULL); + ecs_stage_t *stage = flecs_stage_from_world(&world); + ecs_check(stage->defer_suspend == false, ECS_INVALID_OPERATION, NULL); + stage->defer_suspend = true; +error: + return; } -void flecs_sparse_set_generation( - ecs_sparse_t *sparse, - uint64_t index) +void ecs_defer_resume( + ecs_world_t *world) { - 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]; + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(ecs_is_deferred(world), ECS_INVALID_OPERATION, NULL); + ecs_stage_t *stage = flecs_stage_from_world(&world); + ecs_check(stage->defer_suspend == true, ECS_INVALID_OPERATION, NULL); + stage->defer_suspend = false; +error: + return; +} - if (dense) { - /* Increase generation */ - uint64_t *dense_array = ecs_vector_first(sparse->dense, uint64_t); - dense_array[dense] = index_w_gen; +const char* ecs_id_flag_str( + ecs_entity_t entity) +{ + if (ECS_HAS_ID_FLAG(entity, PAIR)) { + return "PAIR"; + } else + if (ECS_HAS_ID_FLAG(entity, TOGGLE)) { + return "TOGGLE"; + } else + if (ECS_HAS_ID_FLAG(entity, OR)) { + return "OR"; + } else + if (ECS_HAS_ID_FLAG(entity, AND)) { + return "AND"; + } else + if (ECS_HAS_ID_FLAG(entity, NOT)) { + return "NOT"; + } else + if (ECS_HAS_ID_FLAG(entity, OVERRIDE)) { + return "OVERRIDE"; } else { - /* Element is not paired and thus not alive, nothing to be done */ + return "UNKNOWN"; } } -bool flecs_sparse_exists( - const ecs_sparse_t *sparse, - uint64_t index) +void ecs_id_str_buf( + const ecs_world_t *world, + ecs_id_t id, + ecs_strbuf_t *buf) { - 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]; + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - return dense != 0; -} + world = ecs_get_world(world); -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; + if (ECS_HAS_ID_FLAG(id, TOGGLE)) { + ecs_strbuf_appendstr(buf, ecs_id_flag_str(ECS_TOGGLE)); + ecs_strbuf_appendch(buf, '|'); + } - dense_index ++; + if (ECS_HAS_ID_FLAG(id, OVERRIDE)) { + ecs_strbuf_appendstr(buf, ecs_id_flag_str(ECS_OVERRIDE)); + ecs_strbuf_appendch(buf, '|'); + } - uint64_t *dense_array = ecs_vector_first(sparse->dense, uint64_t); - return get_sparse(sparse, dense_index, dense_array[dense_index]); -} + if (ECS_HAS_ID_FLAG(id, AND)) { + ecs_strbuf_appendstr(buf, ecs_id_flag_str(ECS_AND)); + ecs_strbuf_appendch(buf, '|'); + } -bool flecs_sparse_is_alive( - const ecs_sparse_t *sparse, - uint64_t index) -{ - return try_sparse(sparse, index) != NULL; -} + if (ECS_HAS_ID_FLAG(id, OR)) { + ecs_strbuf_appendstr(buf, ecs_id_flag_str(ECS_OR)); + ecs_strbuf_appendch(buf, '|'); + } -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 (ECS_HAS_ID_FLAG(id, NOT)) { + ecs_strbuf_appendstr(buf, ecs_id_flag_str(ECS_NOT)); + ecs_strbuf_appendch(buf, '|'); } - int32_t offset = OFFSET(index); - int32_t dense = chunk->sparse[offset]; - uint64_t *dense_array = ecs_vector_first(sparse->dense, uint64_t); + if (ECS_HAS_ID_FLAG(id, PAIR)) { + ecs_entity_t rel = ECS_PAIR_FIRST(id); + ecs_entity_t obj = ECS_PAIR_SECOND(id); - /* If dense is 0 (tombstone) this will return 0 */ - return dense_array[dense]; -} + ecs_entity_t e; + if ((e = ecs_get_alive(world, rel))) { + rel = e; + } + if ((e = ecs_get_alive(world, obj))) { + obj = e; + } -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); + ecs_strbuf_appendch(buf, '('); + ecs_get_path_w_sep_buf(world, 0, rel, NULL, NULL, buf); + ecs_strbuf_appendch(buf, ','); + ecs_get_path_w_sep_buf(world, 0, obj, NULL, NULL, buf); + ecs_strbuf_appendch(buf, ')'); + } else { + ecs_entity_t e = id & ECS_COMPONENT_MASK; + ecs_get_path_w_sep_buf(world, 0, e, NULL, NULL, buf); + } + +error: + return; } -void* _flecs_sparse_get_any( - const ecs_sparse_t *sparse, - ecs_size_t size, - uint64_t index) +char* ecs_id_str( + const ecs_world_t *world, + ecs_id_t id) { - 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); + ecs_strbuf_t buf = ECS_STRBUF_INIT; + ecs_id_str_buf(world, id, &buf); + return ecs_strbuf_get(&buf); } -int32_t flecs_sparse_count( - const ecs_sparse_t *sparse) +static +void ecs_type_str_buf( + const ecs_world_t *world, + const ecs_type_t *type, + ecs_strbuf_t *buf) { - if (!sparse) { - return 0; - } + ecs_entity_t *ids = type->array; + int32_t i, count = type->count; - return sparse->count - 1; + 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); + } + } } -int32_t flecs_sparse_not_alive_count( - const ecs_sparse_t *sparse) +char* ecs_type_str( + const ecs_world_t *world, + const ecs_type_t *type) { - if (!sparse) { - return 0; + if (!type) { + return ecs_os_strdup(""); } - return ecs_vector_count(sparse->dense) - sparse->count; + ecs_strbuf_t buf = ECS_STRBUF_INIT; + ecs_type_str_buf(world, type, &buf); + return ecs_strbuf_get(&buf); } -int32_t flecs_sparse_size( - const ecs_sparse_t *sparse) +char* ecs_table_str( + const ecs_world_t *world, + const ecs_table_t *table) { - if (!sparse) { - return 0; + if (table) { + return ecs_type_str(world, &table->type); + } else { + return NULL; } - - return ecs_vector_count(sparse->dense) - 1; } -const uint64_t* flecs_sparse_ids( - const ecs_sparse_t *sparse) +char* ecs_entity_str( + const ecs_world_t *world, + ecs_entity_t entity) { - ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); - return &(ecs_vector_first(sparse->dense, uint64_t)[1]); -} + ecs_strbuf_t buf = ECS_STRBUF_INIT; -void flecs_sparse_set_size( - ecs_sparse_t *sparse, - int32_t elem_count) -{ - ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_vector_set_size(&sparse->dense, uint64_t, elem_count); + ecs_get_path_w_sep_buf(world, 0, entity, 0, "", &buf); + + ecs_strbuf_appendstr(&buf, " ["); + const ecs_type_t *type = ecs_get_type(world, entity); + if (type) { + ecs_type_str_buf(world, type, &buf); + } + ecs_strbuf_appendch(&buf, ']'); + + return ecs_strbuf_get(&buf); } static -void sparse_copy( - ecs_sparse_t * dst, - const ecs_sparse_t * src) +void flecs_flush_bulk_new( + ecs_world_t *world, + ecs_defer_op_t *op) { - 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_entity_t *entities = op->is._n.entities; - 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 (op->id) { + int i, count = op->is._n.count; + for (i = 0; i < count; i ++) { + add_id(world, entities[i], op->id); + } } - set_id(dst, get_id(src)); - - ecs_assert(src->count == dst->count, ECS_INTERNAL_ERROR, NULL); + ecs_os_free(entities); } -ecs_sparse_t* flecs_sparse_copy( - const ecs_sparse_t *src) +static +void flecs_dtor_value( + ecs_world_t *world, + ecs_id_t id, + void *value, + int32_t count) { - if (!src) { - return NULL; + const ecs_type_info_t *ti = ecs_get_type_info(world, id); + ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_xtor_t dtor = ti->hooks.dtor; + if (dtor) { + ecs_size_t size = ti->size; + void *ptr; + int i; + for (i = 0, ptr = value; i < count; i ++, ptr = ECS_OFFSET(ptr, size)) { + dtor(ptr, 1, ti); + } } - - ecs_sparse_t *dst = _flecs_sparse_new(src->size); - sparse_copy(dst, src); - - return dst; } -void flecs_sparse_restore( - ecs_sparse_t * dst, - const ecs_sparse_t * src) +static +void flecs_discard_op( + ecs_world_t *world, + ecs_defer_op_t *op) { - ecs_assert(dst != NULL, ECS_INVALID_PARAMETER, NULL); - dst->count = 1; - if (src) { - sparse_copy(dst, src); + if (op->kind != EcsOpBulkNew) { + void *value = op->is._1.value; + if (value) { + flecs_dtor_value(world, op->id, value, 1); + flecs_stack_free(value, op->is._1.size); + } + } else { + ecs_os_free(op->is._n.entities); } } -void flecs_sparse_memory( - ecs_sparse_t *sparse, - int32_t *allocd, - int32_t *used) +static +bool flecs_is_entity_valid( + ecs_world_t *world, + ecs_entity_t e) { - (void)sparse; - (void)allocd; - (void)used; + if (ecs_exists(world, e) && !ecs_is_alive(world, e)) { + return false; + } + return true; } -ecs_sparse_t* _ecs_sparse_new( - ecs_size_t elem_size) +static +bool flecs_remove_invalid( + ecs_world_t *world, + ecs_id_t *id_out) { - return _flecs_sparse_new(elem_size); -} + ecs_id_t id = *id_out; -void* _ecs_sparse_add( - ecs_sparse_t *sparse, - ecs_size_t elem_size) -{ - return _flecs_sparse_add(sparse, elem_size); -} + if (ECS_HAS_ID_FLAG(id, PAIR)) { + ecs_entity_t rel = ecs_pair_first(world, id); + if (!rel || !flecs_is_entity_valid(world, rel)) { + /* After relationship is deleted we can no longer see what its + * delete action was, so pretend this never happened */ + *id_out = 0; + return true; + } else { + ecs_entity_t obj = ecs_pair_second(world, id); + if (!obj || !flecs_is_entity_valid(world, obj)) { + /* Check the relationship's policy for deleted objects */ + ecs_id_record_t *idr = flecs_id_record_get(world, + ecs_pair(rel, EcsWildcard)); + if (idr) { + ecs_entity_t action = ECS_ID_ON_DELETE_OBJECT(idr->flags); + if (action == EcsDelete) { + /* Entity should be deleted, don't bother checking + * other ids */ + return false; + } else if (action == EcsPanic) { + /* If policy is throw this object should not have + * been deleted */ + flecs_throw_invalid_delete(world, id); + } else { + *id_out = 0; + return true; + } + } else { + *id_out = 0; + return true; + } + } + } + } else { + id &= ECS_COMPONENT_MASK; + if (!flecs_is_entity_valid(world, id)) { + /* After relationship is deleted we can no longer see what its + * delete action was, so pretend this never happened */ + *id_out = 0; + return true; + } + } -uint64_t ecs_sparse_last_id( - const ecs_sparse_t *sparse) -{ - return flecs_sparse_last_id(sparse); + return true; } -int32_t ecs_sparse_count( - const ecs_sparse_t *sparse) +/* Leave safe section. Run all deferred commands. */ +bool flecs_defer_end( + ecs_world_t *world, + ecs_stage_t *stage) { - return flecs_sparse_count(sparse); -} + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(stage != NULL, ECS_INVALID_PARAMETER, NULL); -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); -} + if (stage->defer_suspend) { + return false; + } -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); + if (!--stage->defer) { + /* Set to NULL. Processing deferred commands can cause additional + * commands to get enqueued (as result of reactive systems). Make sure + * that the original array is not reallocated, as this would complicate + * processing the queue. */ + ecs_vector_t *defer_queue = stage->defer_queue; + stage->defer_queue = NULL; + + if (defer_queue) { + ecs_defer_op_t *ops = ecs_vector_first(defer_queue, ecs_defer_op_t); + int32_t i, count = ecs_vector_count(defer_queue); + + ecs_stack_t stack = stage->defer_stack; + flecs_stack_init(&stage->defer_stack); + + for (i = 0; i < count; i ++) { + ecs_defer_op_t *op = &ops[i]; + ecs_entity_t e = op->is._1.entity; + if (op->kind == EcsOpBulkNew) { + e = 0; + } + + /* If entity is no longer alive, this could be because the queue + * contained both a delete and a subsequent add/remove/set which + * should be ignored. */ + if (e && !ecs_is_alive(world, e) && flecs_entities_exists(world, e)) { + ecs_assert(op->kind != EcsOpNew && op->kind != EcsOpClone, + ECS_INTERNAL_ERROR, NULL); + world->info.discard_count ++; + flecs_discard_op(world, op); + continue; + } + + switch(op->kind) { + case EcsOpNew: + case EcsOpAdd: + ecs_assert(op->id != 0, ECS_INTERNAL_ERROR, NULL); + if (flecs_remove_invalid(world, &op->id)) { + if (op->id) { + world->info.add_count ++; + add_id(world, e, op->id); + } + } else { + ecs_delete(world, e); + } + break; + case EcsOpRemove: + remove_id(world, e, op->id); + break; + case EcsOpClone: + ecs_clone(world, e, op->id, op->is._1.clone_value); + break; + case EcsOpSet: + set_ptr_w_id(world, e, + op->id, flecs_itosize(op->is._1.size), + op->is._1.value, true, true); + break; + case EcsOpEmplace: + ecs_emplace_id(world, e, op->id); + set_ptr_w_id(world, e, + op->id, flecs_itosize(op->is._1.size), + op->is._1.value, true, false); + break; + case EcsOpMut: + set_ptr_w_id(world, e, + op->id, flecs_itosize(op->is._1.size), + op->is._1.value, true, false); + break; + case EcsOpModified: + if (ecs_has_id(world, e, op->id)) { + ecs_modified_id(world, e, op->id); + } + break; + case EcsOpDelete: { + ecs_delete(world, e); + break; + } + case EcsOpClear: + ecs_clear(world, e); + break; + case EcsOpOnDeleteAction: + flecs_on_delete(world, op->id, e); + break; + case EcsOpEnable: + ecs_enable_id(world, e, op->id, true); + break; + case EcsOpDisable: + ecs_enable_id(world, e, op->id, false); + break; + case EcsOpBulkNew: + flecs_flush_bulk_new(world, op); + continue; + } + + if (op->is._1.value) { + flecs_stack_free(op->is._1.value, op->is._1.size); + } + } + + if (stage->defer_queue) { + ecs_vector_free(stage->defer_queue); + } + + /* Restore defer queue */ + ecs_vector_clear(defer_queue); + stage->defer_queue = defer_queue; + + /* Restore stack */ + flecs_stack_fini(&stage->defer_stack); + stage->defer_stack = stack; + flecs_stack_reset(&stage->defer_stack); + } + + return true; + } + +error: + return false; } -ecs_sparse_iter_t _flecs_sparse_iter( - ecs_sparse_t *sparse, - ecs_size_t elem_size) +/* Delete operations from queue without executing them. */ +bool flecs_defer_purge( + ecs_world_t *world, + ecs_stage_t *stage) { - 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_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(stage != NULL, ECS_INVALID_PARAMETER, NULL); + if (!--stage->defer) { + ecs_vector_t *defer_queue = stage->defer_queue; + stage->defer_queue = NULL; + + if (defer_queue) { + ecs_defer_op_t *ops = ecs_vector_first(defer_queue, ecs_defer_op_t); + int32_t i, count = ecs_vector_count(defer_queue); + for (i = 0; i < count; i ++) { + flecs_discard_op(world, &ops[i]); + } + + if (stage->defer_queue) { + ecs_vector_free(stage->defer_queue); + } + + /* Restore defer queue */ + ecs_vector_clear(defer_queue); + stage->defer_queue = defer_queue; + flecs_stack_reset(&stage->defer_stack); + } + + return true; + } + +error: + return false; +} -#define FLECS_STACK_PAGE_OFFSET ECS_ALIGN(ECS_SIZEOF(ecs_stack_page_t), 16) static -ecs_stack_page_t* flecs_stack_page_new(void) { - ecs_stack_page_t *result = ecs_os_malloc( - FLECS_STACK_PAGE_OFFSET + ECS_STACK_PAGE_SIZE); - result->data = ECS_OFFSET(result, FLECS_STACK_PAGE_OFFSET); - result->next = NULL; +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; } -void* flecs_stack_alloc( - ecs_stack_t *stack, - ecs_size_t size, - ecs_size_t align) +static +void merge_stages( + ecs_world_t *world, + bool force_merge) { - ecs_stack_page_t *page = stack->cur; - if (page == &stack->first && !page->data) { - page->data = ecs_os_malloc(ECS_STACK_PAGE_SIZE); + bool is_stage = ecs_poly_is(world, ecs_stage_t); + ecs_stage_t *stage = flecs_stage_from_world(&world); + + bool measure_frame_time = ECS_BIT_IS_SET(world->flags, + EcsWorldMeasureFrameTime); + + ecs_time_t t_start; + if (measure_frame_time) { + ecs_os_get_time(&t_start); } - ecs_size_t sp = ECS_ALIGN(page->sp, align); - ecs_size_t next_sp = sp + size; + ecs_dbg_3("#[magenta]merge"); - if (next_sp > ECS_STACK_PAGE_SIZE) { - if (size > ECS_STACK_PAGE_SIZE) { - return ecs_os_malloc(size); /* Too large for page */ + 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_assert(stage->defer == 1, ECS_INVALID_OPERATION, + "mismatching defer_begin/defer_end detected"); + ecs_defer_end((ecs_world_t*)stage); } - - if (page->next) { - page = page->next; - } else { - page = page->next = flecs_stack_page_new(); + } 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) { + flecs_defer_end(world, s); + } } - sp = 0; - next_sp = size; - stack->cur = page; } - page->sp = next_sp; + flecs_eval_component_monitors(world); - return ECS_OFFSET(page->data, sp); -} + if (measure_frame_time) { + world->info.merge_time_total += (float)ecs_time_measure(&t_start); + } -void flecs_stack_free( - void *ptr, - ecs_size_t size) -{ - if (size > ECS_STACK_PAGE_SIZE) { - ecs_os_free(ptr); + world->info.merge_count_total ++; + + /* If stage is asynchronous, deferring is always enabled */ + if (stage->async) { + ecs_defer_begin((ecs_world_t*)stage); } } -void flecs_stack_reset( - ecs_stack_t *stack) +static +void do_auto_merge( + ecs_world_t *world) { - stack->cur = &stack->first; - stack->first.sp = 0; + merge_stages(world, false); } -void flecs_stack_init( - ecs_stack_t *stack) +static +void do_manual_merge( + ecs_world_t *world) { - ecs_os_zeromem(stack); - stack->cur = &stack->first; - stack->first.data = NULL; + merge_stages(world, true); } -void flecs_stack_fini( - ecs_stack_t *stack) +bool flecs_defer_begin( + ecs_world_t *world, + ecs_stage_t *stage) { - ecs_stack_page_t *next, *cur = &stack->first; - do { - next = cur->next; - if (cur == &stack->first) { - ecs_os_free(cur->data); - } else { - ecs_os_free(cur); - } - } while ((cur = next)); + (void)world; + if (stage->defer_suspend) return false; + return (++ stage->defer) == 1; } -#include - -/** - * stm32tpl -- STM32 C++ Template Peripheral Library - * Visit https://github.com/antongus/stm32tpl for new versions - * - * Copyright (c) 2011-2020 Anton B. Gusev aka AHTOXA - */ - -#define MAX_PRECISION (10) -#define EXP_THRESHOLD (3) -#define INT64_MAX_F ((double)INT64_MAX) - -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 -char* strbuf_itoa( - char *buf, - int64_t v) +bool flecs_defer_op( + ecs_world_t *world, + ecs_stage_t *stage) { - char *ptr = buf; - char * p1; - char c; + (void)world; - if (!v) { - *ptr++ = '0'; - } else { - char *p = ptr; - while (v) { - *p++ = (char)('0' + v % 10); - v /= 10; - } + /* If deferring is suspended, do operation as usual */ + if (stage->defer_suspend) return false; - p1 = p; + if (stage->defer) { + /* If deferring is enabled, defer operation */ + return true; + } - while (p > ptr) { - c = *--p; - *p = *ptr; - *ptr++ = c; - } - ptr = p1; - } - return ptr; + /* Deferring is disabled, defer while operation is executed */ + stage->defer ++; + return false; } static -int ecs_strbuf_ftoa( - ecs_strbuf_t *out, - double f, - int precision, - char nan_delim) +bool flecs_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) { - char buf[64]; - char * ptr = buf; - char c; - int64_t intPart; - int64_t exp = 0; - - if (isnan(f)) { - if (nan_delim) { - ecs_strbuf_appendch(out, nan_delim); - ecs_strbuf_appendstr(out, "NaN"); - return ecs_strbuf_appendch(out, nan_delim); - } else { - return ecs_strbuf_appendstr(out, "NaN"); + if (flecs_defer_op(world, stage)) { + if (!id) { + return true; } - } - if (isinf(f)) { - if (nan_delim) { - ecs_strbuf_appendch(out, nan_delim); - ecs_strbuf_appendstr(out, "Inf"); - return ecs_strbuf_appendch(out, nan_delim); - } else { - return ecs_strbuf_appendstr(out, "Inf"); + + ecs_defer_op_t *op = new_defer_op(stage); + op->kind = op_kind; + op->id = id; + op->is._1.entity = entity; + + if (op_kind == EcsOpNew) { + world->info.new_count ++; + } else if (op_kind == EcsOpAdd) { + world->info.add_count ++; + } else if (op_kind == EcsOpRemove) { + world->info.remove_count ++; } + + return true; } + return false; +} - if (precision > MAX_PRECISION) { - precision = MAX_PRECISION; +bool flecs_defer_modified( + ecs_world_t *world, + ecs_stage_t *stage, + ecs_entity_t entity, + ecs_id_t id) +{ + if (flecs_defer_op(world, stage)) { + ecs_defer_op_t *op = new_defer_op(stage); + op->kind = EcsOpModified; + op->id = id; + op->is._1.entity = entity; + return true; } + return false; +} - if (f < 0) { - f = -f; - *ptr++ = '-'; - } - - 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; - } - - if (precision) { - f += rounders[precision]; +bool flecs_defer_clone( + ecs_world_t *world, + ecs_stage_t *stage, + ecs_entity_t entity, + ecs_entity_t src, + bool clone_value) +{ + if (flecs_defer_op(world, stage)) { + 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; } + return false; +} - /* Make sure that number can be represented as 64bit int, increase exp */ - while (f > INT64_MAX_F) { - f /= 1000 * 1000 * 1000; - exp += 9; +bool flecs_defer_delete( + ecs_world_t *world, + ecs_stage_t *stage, + ecs_entity_t entity) +{ + if (flecs_defer_op(world, stage)) { + ecs_defer_op_t *op = new_defer_op(stage); + op->kind = EcsOpDelete; + op->is._1.entity = entity; + world->info.delete_count ++; + return true; } + return false; +} - intPart = (int64_t)f; - f -= (double)intPart; - - ptr = strbuf_itoa(ptr, intPart); - - if (precision) { - *ptr++ = '.'; - while (precision--) { - f *= 10.0; - c = (char)f; - *ptr++ = (char)('0' + c); - f -= c; - } - } - *ptr = 0; - - /* Remove trailing 0s */ - while ((&ptr[-1] != buf) && (ptr[-1] == '0')) { - ptr[-1] = '\0'; - ptr --; - } - if (ptr != buf && ptr[-1] == '.') { - ptr[-1] = '\0'; - ptr --; +bool flecs_defer_clear( + ecs_world_t *world, + ecs_stage_t *stage, + ecs_entity_t entity) +{ + if (flecs_defer_op(world, stage)) { + ecs_defer_op_t *op = new_defer_op(stage); + op->kind = EcsOpClear; + op->is._1.entity = entity; + world->info.clear_count ++; + return true; } + return false; +} - /* If 0s before . exceed threshold, convert to exponent to save space - * without losing precision. */ - char *cur = ptr; - while ((&cur[-1] != buf) && (cur[-1] == '0')) { - cur --; +bool flecs_defer_on_delete_action( + ecs_world_t *world, + ecs_stage_t *stage, + ecs_id_t id, + ecs_entity_t action) +{ + if (flecs_defer_op(world, stage)) { + ecs_defer_op_t *op = new_defer_op(stage); + op->kind = EcsOpOnDeleteAction; + op->id = id; + op->is._1.entity = action; + world->info.clear_count ++; + return true; } + return false; +} - if (exp || ((ptr - cur) > EXP_THRESHOLD)) { - cur[0] = '\0'; - exp += (ptr - cur); - ptr = cur; +bool flecs_defer_enable( + ecs_world_t *world, + ecs_stage_t *stage, + ecs_entity_t entity, + ecs_id_t id, + bool enable) +{ + if (flecs_defer_op(world, stage)) { + ecs_defer_op_t *op = new_defer_op(stage); + op->kind = enable ? EcsOpEnable : EcsOpDisable; + op->is._1.entity = entity; + op->id = id; + return true; } + return false; +} - if (exp) { - char *p1 = &buf[1]; - if (nan_delim) { - ecs_os_memmove(buf + 1, buf, 1 + (ptr - buf)); - buf[0] = nan_delim; - p1 ++; - } - - /* Make sure that exp starts after first character */ - c = p1[0]; +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 (flecs_defer_op(world, stage)) { + ecs_entity_t *ids = ecs_os_malloc(count * ECS_SIZEOF(ecs_entity_t)); + world->info.bulk_new_count ++; - if (c) { - p1[0] = '.'; - do { - char t = (++p1)[0]; - p1[0] = c; - c = t; - exp ++; - } while (c); - ptr = p1 + 1; - } else { - ptr = p1; + /* Use ecs_new_id as this is thread safe */ + int i; + for (i = 0; i < count; i ++) { + ids[i] = ecs_new_id(world); } + *ids_out = ids; - ptr[0] = 'e'; - ptr = strbuf_itoa(ptr + 1, exp); - - if (nan_delim) { - ptr[0] = nan_delim; - ptr ++; - } + /* 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; - ptr[0] = '\0'; + return true; } - - return ecs_strbuf_appendstrn(out, buf, (int32_t)(ptr - buf)); + return false; } -/* Add an extra element to the buffer */ -static -void ecs_strbuf_grow( - ecs_strbuf_t *b) -{ - /* 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; +bool flecs_defer_new( + ecs_world_t *world, + ecs_stage_t *stage, + ecs_entity_t entity, + ecs_id_t id) +{ + return flecs_defer_add_remove(world, stage, EcsOpNew, entity, id); } -/* Add an extra dynamic element */ -static -void ecs_strbuf_grow_str( - ecs_strbuf_t *b, - char *str, - char *alloc_str, - int32_t size) -{ - /* 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; +bool flecs_defer_add( + ecs_world_t *world, + ecs_stage_t *stage, + ecs_entity_t entity, + ecs_id_t id) +{ + return flecs_defer_add_remove(world, stage, EcsOpAdd, entity, id); } -static -char* ecs_strbuf_ptr( - ecs_strbuf_t *b) +bool flecs_defer_remove( + ecs_world_t *world, + ecs_stage_t *stage, + ecs_entity_t entity, + ecs_id_t id) { - if (b->buf) { - return &b->buf[b->current->pos]; - } else { - return &b->current->buf[b->current->pos]; - } + return flecs_defer_add_remove(world, stage, EcsOpRemove, entity, id); } -/* Compute the amount of space left in the current element */ -static -int32_t ecs_strbuf_memLeftInCurrentElement( - ecs_strbuf_t *b) +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 emplace) { - if (b->current->buffer_embedded) { - return ECS_STRBUF_ELEMENT_SIZE - b->current->pos; - } else { - return 0; + if (flecs_defer_op(world, stage)) { + world->info.set_count ++; + + const ecs_type_info_t *ti = NULL; + { + ecs_id_record_t *idr = flecs_id_record_get(world, id); + if (!idr) { + /* If idr doesn't exist yet, create it but only if the + * application is not multithreaded. */ + if (stage->async || (world->flags & EcsWorldMultiThreaded)) { + ti = ecs_get_type_info(world, id); + ecs_assert(ti != NULL, ECS_INVALID_PARAMETER, NULL); + } else { + /* When not in multi threaded mode, it's safe to find or + * create the id record. */ + idr = flecs_id_record_ensure(world, id); + ecs_assert(idr != NULL, ECS_INTERNAL_ERROR, NULL); + + /* Get type_info from id record. We could have called + * ecs_get_type_info directly, but since this function can be + * expensive for pairs, creating the id record ensures we can + * find the type_info quickly for subsequent operations. */ + ti = idr->type_info; + } + } else { + ti = idr->type_info; + } + } + + /* If the id isn't associated with a type, we can't set anything */ + ecs_check(ti != NULL, ECS_INVALID_PARAMETER, NULL); + + /* Make sure the size of the value equals the type size */ + ecs_assert(!size || size == ti->size, ECS_INVALID_PARAMETER, NULL); + size = ti->size; + + ecs_stack_t *stack = &stage->defer_stack; + 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 = flecs_stack_alloc(stack, size, ti->alignment); + + if (!value) { + value = ecs_get_id(world, entity, id); + } + + if (value) { + ecs_copy_t copy; + if ((copy = ti->hooks.copy_ctor)) { + copy(op->is._1.value, value, 1, ti); + } else { + ecs_os_memcpy(op->is._1.value, value, size); + } + } else if (!emplace) { + ecs_xtor_t ctor; + if ((ctor = ti->hooks.ctor)) { + ctor(op->is._1.value, 1, ti); + } + } + + if (value_out) { + *value_out = op->is._1.value; + } + + return true; } +error: + return false; } -/* Compute the amount of space left */ -static -int32_t ecs_strbuf_memLeft( - ecs_strbuf_t *b) +void flecs_stage_merge_post_frame( + ecs_world_t *world, + ecs_stage_t *stage) { - if (b->max) { - return b->max - b->size - b->current->pos; - } else { - return INT_MAX; - } + /* Execute post frame actions */ + ecs_vector_each(stage->post_frame_actions, ecs_action_elem_t, action, { + action->action(world, action->ctx); + }); + + ecs_vector_free(stage->post_frame_actions); + stage->post_frame_actions = NULL; } -static -void ecs_strbuf_init( - ecs_strbuf_t *b) +void flecs_stage_init( + ecs_world_t *world, + ecs_stage_t *stage) { - /* 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_poly_assert(world, ecs_world_t); + ecs_poly_init(stage, ecs_stage_t); + + stage->world = world; + stage->thread_ctx = world; + stage->auto_merge = true; + stage->async = false; + + flecs_stack_init(&stage->defer_stack); } -/* Append a format string to a buffer */ -static -bool vappend( - ecs_strbuf_t *b, - const char* str, - va_list args) +void flecs_stage_deinit( + ecs_world_t *world, + ecs_stage_t *stage) { - bool result = true; - va_list arg_cpy; + (void)world; + ecs_poly_assert(world, ecs_world_t); + ecs_poly_assert(stage, ecs_stage_t); - if (!str) { - return result; - } + /* Make sure stage has no unmerged data */ + ecs_assert(ecs_vector_count(stage->defer_queue) == 0, + ECS_INTERNAL_ERROR, NULL); - ecs_strbuf_init(b); + ecs_poly_fini(stage, ecs_stage_t); - int32_t memLeftInElement = ecs_strbuf_memLeftInCurrentElement(b); - int32_t memLeft = ecs_strbuf_memLeft(b); + ecs_vector_free(stage->defer_queue); + flecs_stack_fini(&stage->defer_stack); +} - if (!memLeft) { - return false; - } +void ecs_set_stage_count( + ecs_world_t *world, + int32_t stage_count) +{ + ecs_poly_assert(world, ecs_world_t); - /* 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; + /* World must have at least one default stage */ + ecs_assert(stage_count >= 1 || (world->flags & EcsWorldFini), + ECS_INTERNAL_ERROR, NULL); - va_copy(arg_cpy, args); - memRequired = vsnprintf( - ecs_strbuf_ptr(b), (size_t)(max_copy + 1), str, args); + bool auto_merge = true; + ecs_entity_t *lookup_path = NULL; + ecs_entity_t scope = 0; + ecs_entity_t with = 0; + if (world->stage_count >= 1) { + auto_merge = world->stages[0].auto_merge; + lookup_path = world->stages[0].lookup_path; + scope = world->stages[0].scope; + with = world->stages[0].with; + } - ecs_assert(memRequired != -1, ECS_INTERNAL_ERROR, NULL); + int32_t i, count = world->stage_count; + if (count && count != stage_count) { + ecs_stage_t *stages = world->stages; - 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); + 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_stage_count 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]); + } - /* Copy entire string to new buffer */ - ecs_os_vsprintf(ecs_strbuf_ptr(b), str, arg_cpy); + ecs_os_free(world->stages); + } - /* 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. */ + if (stage_count) { + world->stages = ecs_os_malloc_n(ecs_stage_t, stage_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); + for (i = 0; i < stage_count; i ++) { + ecs_stage_t *stage = &world->stages[i]; + flecs_stage_init(world, stage); + stage->id = i; + + /* 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->stages = NULL; } - va_end(arg_cpy); + /* Regardless of whether the stage was just initialized or not, when the + * ecs_set_stage_count function is called, all stages inherit the auto_merge + * property from the world */ + for (i = 0; i < stage_count; i ++) { + world->stages[i].auto_merge = auto_merge; + world->stages[i].lookup_path = lookup_path; + world->stages[0].scope = scope; + world->stages[0].with = with; + } - return ecs_strbuf_memLeft(b) > 0; + world->stage_count = stage_count; +error: + return; } -static -bool appendstr( - ecs_strbuf_t *b, - const char* str, - int n) +int32_t ecs_get_stage_count( + const ecs_world_t *world) { - ecs_strbuf_init(b); + world = ecs_get_world(world); + return world->stage_count; +} - int32_t memLeftInElement = ecs_strbuf_memLeftInCurrentElement(b); - int32_t memLeft = ecs_strbuf_memLeft(b); - if (memLeft <= 0) { - return false; - } - - /* Never write more than what the buffer can store */ - if (n > memLeft) { - n = memLeft; - } - - 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); - - /* 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); +int32_t ecs_get_stage_id( + const ecs_world_t *world) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - /* 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); - } + if (ecs_poly_is(world, ecs_stage_t)) { + ecs_stage_t *stage = (ecs_stage_t*)world; - /* Update to number of characters copied to new buffer */ - b->current->pos += n; - } else { - /* String doesn't fit in a single element, strdup */ - char *remainder = ecs_os_strdup(str + memLeftInElement); - ecs_strbuf_grow_str(b, remainder, remainder, n); - } + /* Index 0 is reserved for main stage */ + return stage->id; + } else if (ecs_poly_is(world, ecs_world_t)) { + return 0; } else { - /* Buffer max has been reached */ - return false; + ecs_throw(ECS_INTERNAL_ERROR, NULL); } - - return ecs_strbuf_memLeft(b) > 0; +error: + return 0; } -static -bool appendch( - ecs_strbuf_t *b, - char ch) +ecs_world_t* ecs_get_stage( + const ecs_world_t *world, + int32_t stage_id) { - ecs_strbuf_init(b); + ecs_poly_assert(world, ecs_world_t); + ecs_check(world->stage_count > stage_id, ECS_INVALID_PARAMETER, NULL); + return (ecs_world_t*)&world->stages[stage_id]; +error: + return NULL; +} - int32_t memLeftInElement = ecs_strbuf_memLeftInCurrentElement(b); - int32_t memLeft = ecs_strbuf_memLeft(b); - if (memLeft <= 0) { - return false; - } +bool ecs_readonly_begin( + ecs_world_t *world) +{ + ecs_poly_assert(world, ecs_world_t); - 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 ++; - } + flecs_process_pending_tables(world); - return ecs_strbuf_memLeft(b) > 0; -} + ecs_dbg_3("#[bold]readonly"); + ecs_log_push_3(); -bool ecs_strbuf_vappend( - ecs_strbuf_t *b, - const char* fmt, - va_list args) -{ - ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_assert(fmt != NULL, ECS_INVALID_PARAMETER, NULL); - return vappend(b, fmt, args); -} + int32_t i, count = ecs_get_stage_count(world); + for (i = 0; i < count; i ++) { + ecs_stage_t *stage = &world->stages[i]; + stage->lookup_path = world->stages[0].lookup_path; + ecs_assert(stage->defer == 0, ECS_INVALID_OPERATION, + "deferred mode cannot be enabled when entering readonly mode"); + ecs_defer_begin((ecs_world_t*)stage); + } -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); + bool is_readonly = ECS_BIT_IS_SET(world->flags, EcsWorldReadonly); - va_list args; - va_start(args, fmt); - bool result = vappend(b, fmt, args); - va_end(args); + /* From this point on, the world is "locked" for mutations, and it is only + * allowed to enqueue commands from stages */ + ECS_BIT_SET(world->flags, EcsWorldReadonly); - return result; -} + /* If world has more than one stage, signal we might be running on multiple + * threads. This is a stricter version of readonly mode: while some + * mutations like implicit component registration are still allowed in plain + * readonly mode, no mutations are allowed when multithreaded. */ + if (count > 1) { + ECS_BIT_SET(world->flags, EcsWorldMultiThreaded); + } -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); + return is_readonly; } -bool ecs_strbuf_appendch( - ecs_strbuf_t *b, - char ch) +void ecs_readonly_end( + ecs_world_t *world) { - ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); - return appendch(b, ch); -} + ecs_poly_assert(world, ecs_world_t); + ecs_check(world->flags & EcsWorldReadonly, ECS_INVALID_OPERATION, NULL); -bool ecs_strbuf_appendflt( - ecs_strbuf_t *b, - double flt, - char nan_delim) -{ - ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); - return ecs_strbuf_ftoa(b, flt, 10, nan_delim); -} + /* After this it is safe again to mutate the world directly */ + ECS_BIT_CLEAR(world->flags, EcsWorldReadonly); + ECS_BIT_CLEAR(world->flags, EcsWorldMultiThreaded); -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; -} + ecs_log_pop_3(); -bool ecs_strbuf_appendstr_zerocpy_const( - ecs_strbuf_t *b, - const char* str) -{ - 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; + do_auto_merge(world); +error: + return; } -bool ecs_strbuf_appendstr( - ecs_strbuf_t *b, - const char* str) +void ecs_merge( + ecs_world_t *world) { - ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_assert(str != NULL, ECS_INVALID_PARAMETER, NULL); - return appendstr(b, str, ecs_os_strlen(str)); + 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; } -bool ecs_strbuf_mergebuff( - ecs_strbuf_t *dst_buffer, - ecs_strbuf_t *src_buffer) +void ecs_set_automerge( + ecs_world_t *world, + bool auto_merge) { - 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; - - /* Copy first element as it is inlined in the src buffer */ - ecs_strbuf_appendstrn(dst_buffer, e->buf, e->pos); + /* 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->stages[0].auto_merge = auto_merge; - while ((e = e->next)) { - dst_buffer->current->next = ecs_os_malloc(sizeof(ecs_strbuf_element)); - *dst_buffer->current->next = *e; - } + /* 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; } - *src_buffer = ECS_STRBUF_INIT; + /* 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; } - - return true; } -char* ecs_strbuf_get( - ecs_strbuf_t *b) +bool ecs_stage_is_readonly( + const ecs_world_t *stage) { - 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; + const ecs_world_t *world = ecs_get_world(stage); - 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)); + if (ecs_poly_is(stage, ecs_stage_t)) { + if (((ecs_stage_t*)stage)->async) { + return false; + } + } - result[len - 1] = '\0'; - b->length = len; + if (world->flags & EcsWorldReadonly) { + if (ecs_poly_is(stage, ecs_world_t)) { + return true; } } else { - result = NULL; + if (ecs_poly_is(stage, ecs_stage_t)) { + return true; + } } - b->elementCount = 0; - - b->content = result; - - return result; + return false; } -char *ecs_strbuf_get_small( - ecs_strbuf_t *b) +ecs_world_t* ecs_async_stage_new( + ecs_world_t *world) { - 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; -} + ecs_stage_t *stage = ecs_os_calloc(sizeof(ecs_stage_t)); + flecs_stage_init(world, stage); -void ecs_strbuf_reset( - ecs_strbuf_t *b) -{ - ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); + stage->id = -1; + stage->auto_merge = false; + stage->async = true; - 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)); - } + ecs_defer_begin((ecs_world_t*)stage); - *b = ECS_STRBUF_INIT; + return (ecs_world_t*)stage; } -void ecs_strbuf_list_push( - ecs_strbuf_t *b, - const char *list_open, - const char *separator) +void ecs_async_stage_free( + ecs_world_t *world) { - 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); - } + ecs_poly_assert(world, ecs_stage_t); + ecs_stage_t *stage = (ecs_stage_t*)world; + ecs_check(stage->async == true, ECS_INVALID_PARAMETER, NULL); + flecs_stage_deinit(stage->world, stage); + ecs_os_free(stage); +error: + return; } -void ecs_strbuf_list_pop( - ecs_strbuf_t *b, - const char *list_close) +bool ecs_stage_is_async( + ecs_world_t *stage) { - ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_assert(list_close != NULL, ECS_INVALID_PARAMETER, NULL); - - b->list_sp --; + if (!stage) { + return false; + } - if (list_close) { - ecs_strbuf_appendstr(b, list_close); + if (!ecs_poly_is(stage, ecs_stage_t)) { + return false; } + + return ((ecs_stage_t*)stage)->async; } -void ecs_strbuf_list_next( - ecs_strbuf_t *b) +bool ecs_is_deferred( + const ecs_world_t *world) { - ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); - - 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 ++; + 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; } -bool ecs_strbuf_list_append( - ecs_strbuf_t *b, - const char *fmt, - ...) + +struct ecs_vector_t { + int32_t count; + int32_t size; + +#ifndef FLECS_NDEBUG + int64_t elem_size; /* Used in debug mode to validate size */ +#endif +}; + +/** Resize the vector buffer */ +static +ecs_vector_t* resize( + ecs_vector_t *vector, + int16_t offset, + int32_t size) { - ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_assert(fmt != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_vector_t *result = ecs_os_realloc(vector, offset + size); + ecs_assert(result != NULL, ECS_OUT_OF_MEMORY, 0); + return result; +} - ecs_strbuf_list_next(b); +/* -- Public functions -- */ - va_list args; - va_start(args, fmt); - bool result = vappend(b, fmt, args); - va_end(args); +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); + result->count = 0; + result->size = elem_count; +#ifndef FLECS_NDEBUG + result->elem_size = elem_size; +#endif return result; } -bool ecs_strbuf_list_appendstr( - ecs_strbuf_t *b, - const char *str) +ecs_vector_t* _ecs_vector_from_array( + ecs_size_t elem_size, + int16_t offset, + int32_t elem_count, + void *array) { - ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_assert(str != NULL, ECS_INVALID_PARAMETER, NULL); + 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_strbuf_list_next(b); - return ecs_strbuf_appendstr(b, str); + ecs_os_memcpy(ECS_OFFSET(result, offset), array, elem_size * elem_count); + + result->count = elem_count; + result->size = elem_count; +#ifndef FLECS_NDEBUG + result->elem_size = elem_size; +#endif + return result; } -int32_t ecs_strbuf_written( - const ecs_strbuf_t *b) +void ecs_vector_free( + ecs_vector_t *vector) { - ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); - if (b->current) { - return b->size + b->current->pos; - } else { - return 0; - } + ecs_os_free(vector); } - -#ifdef FLECS_SANITIZE -static -void verify_nodes( - ecs_switch_header_t *hdr, - ecs_switch_node_t *nodes) +void ecs_vector_clear( + ecs_vector_t *vector) { - if (!hdr) { - return; - } - - int32_t prev = -1, elem = hdr->element, count = 0; - while (elem != -1) { - ecs_assert(prev == nodes[elem].prev, ECS_INTERNAL_ERROR, NULL); - prev = elem; - elem = nodes[elem].next; - count ++; + if (vector) { + vector->count = 0; } +} - ecs_assert(count == hdr->count, ECS_INTERNAL_ERROR, NULL); +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); } -#else -#define verify_nodes(hdr, nodes) -#endif -static -ecs_switch_header_t *get_header( - const ecs_switch_t *sw, - uint64_t value) +void ecs_vector_assert_size( + ecs_vector_t *vector, + ecs_size_t elem_size) { - if (value == 0) { - return NULL; + (void)elem_size; + + if (vector) { + ecs_dbg_assert(vector->elem_size == elem_size, ECS_INTERNAL_ERROR, NULL); } - - return ecs_map_get(&sw->headers, ecs_switch_header_t, value); -} - -static -ecs_switch_header_t *ensure_header( - ecs_switch_t *sw, - uint64_t value) -{ - if (value == 0) { - return NULL; - } - - ecs_switch_header_t *node = get_header(sw, value); - if (!node) { - node = ecs_map_ensure(&sw->headers, ecs_switch_header_t, value); - node->element = -1; - } - - return node; -} - -static -void remove_node( - ecs_switch_header_t *hdr, - ecs_switch_node_t *nodes, - ecs_switch_node_t *node, - int32_t element) -{ - ecs_assert(&nodes[element] == node, ECS_INTERNAL_ERROR, NULL); - - /* Update previous node/header */ - if (hdr->element == element) { - ecs_assert(node->prev == -1, ECS_INVALID_PARAMETER, NULL); - /* If this is the first node, update the header */ - hdr->element = node->next; - } else { - /* If this is not the first node, update the previous node to the - * removed node's next ptr */ - ecs_assert(node->prev != -1, ECS_INVALID_PARAMETER, NULL); - ecs_switch_node_t *prev_node = &nodes[node->prev]; - prev_node->next = node->next; - } - - /* Update next node */ - int32_t next = node->next; - if (next != -1) { - ecs_assert(next >= 0, ECS_INVALID_PARAMETER, NULL); - /* If this is not the last node, update the next node to point to the - * removed node's prev ptr */ - ecs_switch_node_t *next_node = &nodes[next]; - next_node->prev = node->prev; - } - - /* Decrease count of current header */ - hdr->count --; - ecs_assert(hdr->count >= 0, ECS_INTERNAL_ERROR, NULL); -} - -void flecs_switch_init( - ecs_switch_t *sw, - int32_t elements) -{ - ecs_map_init(&sw->headers, ecs_switch_header_t, 1); - sw->nodes = ecs_vector_new(ecs_switch_node_t, elements); - sw->values = ecs_vector_new(uint64_t, elements); - - ecs_switch_node_t *nodes = ecs_vector_first( - sw->nodes, ecs_switch_node_t); - uint64_t *values = ecs_vector_first( - sw->values, uint64_t); - - int i; - for (i = 0; i < elements; i ++) { - nodes[i].prev = -1; - nodes[i].next = -1; - values[i] = 0; - } -} - -ecs_switch_t* flecs_switch_new( - int32_t elements) -{ - ecs_switch_t *result = ecs_os_malloc(ECS_SIZEOF(ecs_switch_t)); - - flecs_switch_init(result, elements); - - return result; -} - -void flecs_switch_clear( - ecs_switch_t *sw) -{ - ecs_map_clear(&sw->headers); - ecs_vector_free(sw->nodes); - ecs_vector_free(sw->values); - sw->nodes = NULL; - sw->values = NULL; -} - -void flecs_switch_fini( - ecs_switch_t *sw) -{ - // ecs_os_free(sw->headers); - ecs_map_fini(&sw->headers); - ecs_vector_free(sw->nodes); - ecs_vector_free(sw->values); -} - -void flecs_switch_free( - ecs_switch_t *sw) -{ - flecs_switch_fini(sw); - ecs_os_free(sw); -} - -void flecs_switch_add( - ecs_switch_t *sw) -{ - ecs_switch_node_t *node = ecs_vector_add(&sw->nodes, ecs_switch_node_t); - uint64_t *value = ecs_vector_add(&sw->values, uint64_t); - node->prev = -1; - node->next = -1; - *value = 0; -} - -void flecs_switch_set_count( - ecs_switch_t *sw, - int32_t count) -{ - int32_t old_count = ecs_vector_count(sw->nodes); - if (old_count == count) { - return; - } - - ecs_vector_set_count(&sw->nodes, ecs_switch_node_t, count); - ecs_vector_set_count(&sw->values, uint64_t, count); - - ecs_switch_node_t *nodes = ecs_vector_first(sw->nodes, ecs_switch_node_t); - uint64_t *values = ecs_vector_first(sw->values, uint64_t); - - int32_t i; - for (i = old_count; i < count; i ++) { - ecs_switch_node_t *node = &nodes[i]; - node->prev = -1; - node->next = -1; - values[i] = 0; - } -} - -int32_t flecs_switch_count( - ecs_switch_t *sw) -{ - ecs_assert(ecs_vector_count(sw->values) == ecs_vector_count(sw->nodes), - ECS_INTERNAL_ERROR, NULL); - return ecs_vector_count(sw->values); -} - -void flecs_switch_ensure( - ecs_switch_t *sw, - int32_t count) -{ - int32_t old_count = ecs_vector_count(sw->nodes); - if (old_count >= count) { - return; - } - - flecs_switch_set_count(sw, count); -} - -void flecs_switch_addn( - ecs_switch_t *sw, - int32_t count) -{ - int32_t old_count = ecs_vector_count(sw->nodes); - flecs_switch_set_count(sw, old_count + count); -} - -void flecs_switch_set( - ecs_switch_t *sw, - int32_t element, - uint64_t value) -{ - ecs_assert(sw != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_assert(element < ecs_vector_count(sw->nodes), ECS_INVALID_PARAMETER, NULL); - ecs_assert(element < ecs_vector_count(sw->values), ECS_INVALID_PARAMETER, NULL); - ecs_assert(element >= 0, ECS_INVALID_PARAMETER, NULL); - - uint64_t *values = ecs_vector_first(sw->values, uint64_t); - uint64_t cur_value = values[element]; - - /* If the node is already assigned to the value, nothing to be done */ - if (cur_value == value) { - return; - } - - ecs_switch_node_t *nodes = ecs_vector_first(sw->nodes, ecs_switch_node_t); - ecs_switch_node_t *node = &nodes[element]; - - ecs_switch_header_t *dst_hdr = ensure_header(sw, value); - ecs_switch_header_t *cur_hdr = get_header(sw, cur_value); - - verify_nodes(cur_hdr, nodes); - verify_nodes(dst_hdr, nodes); - - /* If value is not 0, and dst_hdr is NULL, then this is not a valid value - * for this switch */ - ecs_assert(dst_hdr != NULL || !value, ECS_INVALID_PARAMETER, NULL); - - if (cur_hdr) { - remove_node(cur_hdr, nodes, node, element); - } - - /* Now update the node itself by adding it as the first node of dst */ - node->prev = -1; - values[element] = value; - - if (dst_hdr) { - node->next = dst_hdr->element; - - /* Also update the dst header */ - int32_t first = dst_hdr->element; - if (first != -1) { - ecs_assert(first >= 0, ECS_INTERNAL_ERROR, NULL); - ecs_switch_node_t *first_node = &nodes[first]; - first_node->prev = element; - } - - dst_hdr->element = element; - dst_hdr->count ++; - } -} - -void flecs_switch_remove( - ecs_switch_t *sw, - int32_t element) -{ - ecs_assert(sw != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_assert(element < ecs_vector_count(sw->nodes), ECS_INVALID_PARAMETER, NULL); - ecs_assert(element >= 0, ECS_INVALID_PARAMETER, NULL); - - uint64_t *values = ecs_vector_first(sw->values, uint64_t); - uint64_t value = values[element]; - ecs_switch_node_t *nodes = ecs_vector_first(sw->nodes, ecs_switch_node_t); - ecs_switch_node_t *node = &nodes[element]; - - /* If node is currently assigned to a case, remove it from the list */ - if (value != 0) { - ecs_switch_header_t *hdr = get_header(sw, value); - ecs_assert(hdr != NULL, ECS_INTERNAL_ERROR, NULL); - - verify_nodes(hdr, nodes); - remove_node(hdr, nodes, node, element); - } - - int32_t last_elem = ecs_vector_count(sw->nodes) - 1; - if (last_elem != element) { - ecs_switch_node_t *last = ecs_vector_last(sw->nodes, ecs_switch_node_t); - int32_t next = last->next, prev = last->prev; - if (next != -1) { - ecs_switch_node_t *n = &nodes[next]; - n->prev = element; - } - - if (prev != -1) { - ecs_switch_node_t *n = &nodes[prev]; - n->next = element; - } else { - ecs_switch_header_t *hdr = get_header(sw, values[last_elem]); - if (hdr && hdr->element != -1) { - ecs_assert(hdr->element == last_elem, - ECS_INTERNAL_ERROR, NULL); - hdr->element = element; - } - } - } - - /* Remove element from arrays */ - ecs_vector_remove(sw->nodes, ecs_switch_node_t, element); - ecs_vector_remove(sw->values, uint64_t, element); -} - -uint64_t flecs_switch_get( - const ecs_switch_t *sw, - int32_t element) -{ - ecs_assert(sw != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_assert(element < ecs_vector_count(sw->nodes), ECS_INVALID_PARAMETER, NULL); - ecs_assert(element < ecs_vector_count(sw->values), ECS_INVALID_PARAMETER, NULL); - ecs_assert(element >= 0, ECS_INVALID_PARAMETER, NULL); - - uint64_t *values = ecs_vector_first(sw->values, uint64_t); - return values[element]; -} - -ecs_vector_t* flecs_switch_values( - const ecs_switch_t *sw) -{ - return sw->values; -} - -int32_t flecs_switch_case_count( - const ecs_switch_t *sw, - uint64_t value) -{ - ecs_switch_header_t *hdr = get_header(sw, value); - if (!hdr) { - return 0; - } - - return hdr->count; -} - -void flecs_switch_swap( - ecs_switch_t *sw, - int32_t elem_1, - int32_t elem_2) -{ - uint64_t v1 = flecs_switch_get(sw, elem_1); - uint64_t v2 = flecs_switch_get(sw, elem_2); - - flecs_switch_set(sw, elem_2, v1); - flecs_switch_set(sw, elem_1, v2); -} - -int32_t flecs_switch_first( - const ecs_switch_t *sw, - uint64_t value) -{ - ecs_assert(sw != NULL, ECS_INVALID_PARAMETER, NULL); - - ecs_switch_header_t *hdr = get_header(sw, value); - if (!hdr) { - return -1; - } - - return hdr->element; -} - -int32_t flecs_switch_next( - const ecs_switch_t *sw, - int32_t element) -{ - ecs_assert(sw != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_assert(element < ecs_vector_count(sw->nodes), ECS_INVALID_PARAMETER, NULL); - ecs_assert(element >= 0, ECS_INVALID_PARAMETER, NULL); - - ecs_switch_node_t *nodes = ecs_vector_first( - sw->nodes, ecs_switch_node_t); - - return nodes[element].next; -} - - -struct ecs_vector_t { - int32_t count; - int32_t size; - -#ifndef FLECS_NDEBUG - int64_t elem_size; /* Used in debug mode to validate size */ -#endif -}; - -/** 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; -} - -/* -- Public functions -- */ - -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); - - result->count = 0; - result->size = elem_count; -#ifndef FLECS_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) -{ - 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_os_memcpy(ECS_OFFSET(result, offset), array, elem_size * elem_count); - - result->count = elem_count; - result->size = elem_count; -#ifndef FLECS_NDEBUG - result->elem_size = elem_size; -#endif - return result; -} - -void ecs_vector_free( - ecs_vector_t *vector) -{ - ecs_os_free(vector); -} - -void ecs_vector_clear( - ecs_vector_t *vector) -{ - if (vector) { - vector->count = 0; - } -} - -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); -} - -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); - } -} +} void* _ecs_vector_addn( ecs_vector_t **array_inout, @@ -11348,748 +10849,1217 @@ ecs_vector_t* _ecs_vector_copy( return dst; } -#include -/* 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.2f) -#define KEY_SIZE (ECS_SIZEOF(ecs_map_key_t)) -#define GET_ELEM(array, elem_size, index) \ - ECS_OFFSET(array, (elem_size) * (index)) +/** The number of elements in a single chunk */ +#define CHUNK_COUNT (4096) + +/** Compute the chunk index from an id by stripping the first 12 bits */ +#define CHUNK(index) ((int32_t)((uint32_t)index >> 12)) + +/** This computes the offset of an index inside a chunk */ +#define OFFSET(index) ((int32_t)index & 0xFFF) + +/* Utility to get a pointer to the payload */ +#define DATA(array, size, offset) (ECS_OFFSET(array, size * offset)) + +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; static -ecs_block_allocator_chunk_header_t *ecs_balloc_block( - ecs_block_allocator_t *allocator) +chunk_t* chunk_new( + ecs_sparse_t *sparse, + int32_t chunk_index) { - ecs_block_allocator_chunk_header_t *first_chunk = - ecs_os_malloc(allocator->block_size); - ecs_block_allocator_block_t *block = - ecs_os_malloc_t(ecs_block_allocator_block_t); + int32_t count = ecs_vector_count(sparse->chunks); + chunk_t *chunks; - block->memory = first_chunk; - if (!allocator->block_tail) { - ecs_assert(!allocator->block_head, ECS_INTERNAL_ERROR, 0); - block->next = NULL; - allocator->block_head = block; - allocator->block_tail = block; + 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 { - block->next = NULL; - allocator->block_tail->next = block; - allocator->block_tail = block; - } - - ecs_block_allocator_chunk_header_t *chunk = first_chunk; - int32_t i, end; - for (i = 0, end = allocator->chunks_per_block - 1; i < end; ++i) { - chunk->next = ECS_OFFSET(chunk, allocator->chunk_size); - chunk = chunk->next; + chunks = ecs_vector_first(sparse->chunks, chunk_t); } - chunk->next = NULL; - return first_chunk; -} + ecs_assert(chunks != NULL, ECS_INTERNAL_ERROR, NULL); -static -void *ecs_balloc( - ecs_block_allocator_t *allocator) -{ - if (!allocator->head) { - allocator->head = ecs_balloc_block(allocator); - } + chunk_t *result = &chunks[chunk_index]; + ecs_assert(result->sparse == NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(result->data == NULL, ECS_INTERNAL_ERROR, 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); + + /* 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_assert(result->sparse != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(result->data != NULL, ECS_INTERNAL_ERROR, NULL); - void *result = allocator->head; - allocator->head = allocator->head->next; return result; } -static -void ecs_bfree( - ecs_block_allocator_t *allocator, - void *memory) +static +void chunk_free( + chunk_t *chunk) { - ecs_block_allocator_chunk_header_t *chunk = memory; - chunk->next = allocator->head; - allocator->head = chunk; + ecs_os_free(chunk->sparse); + ecs_os_free(chunk->data); } static -uint8_t ecs_log2(uint32_t v) { - static const uint8_t log2table[32] = - {0, 9, 1, 10, 13, 21, 2, 29, 11, 14, 16, 18, 22, 25, 3, 30, - 8, 12, 20, 28, 15, 17, 24, 7, 19, 27, 23, 6, 26, 5, 4, 31}; +chunk_t* get_chunk( + const ecs_sparse_t *sparse, + int32_t chunk_index) +{ + if (!sparse->chunks) { + return NULL; + } + if (chunk_index >= ecs_vector_count(sparse->chunks)) { + return NULL; + } - v |= v >> 1; - v |= v >> 2; - v |= v >> 4; - v |= v >> 8; - v |= v >> 16; - return log2table[(uint32_t)(v * 0x07C4ACDDU) >> 27]; + /* 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; } -/* Get bucket count for number of elements */ static -int32_t get_bucket_count( - int32_t element_count) +chunk_t* get_or_create_chunk( + ecs_sparse_t *sparse, + int32_t chunk_index) { - return flecs_next_pow_of_2((int32_t)((float)element_count * LOAD_FACTOR)); + chunk_t *chunk = get_chunk(sparse, chunk_index); + if (chunk) { + return chunk; + } + + return chunk_new(sparse, chunk_index); } -/* Get bucket shift amount for a given bucket count */ static -uint8_t get_bucket_shift ( - int32_t bucket_count) +void grow_dense( + ecs_sparse_t *sparse) { - return (uint8_t)(64u - ecs_log2((uint32_t)bucket_count)); + ecs_vector_add(&sparse->dense, uint64_t); } -/* Get bucket index for provided map key */ static -int32_t get_bucket_index( - uint16_t bucket_shift, - ecs_map_key_t key) +uint64_t strip_generation( + uint64_t *index_out) { - ecs_assert(bucket_shift != 0, ECS_INTERNAL_ERROR, NULL); - return (int32_t)((11400714819323198485ull * key) >> bucket_shift); + 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; } -/* Get bucket for key */ static -ecs_bucket_t* get_bucket( - const ecs_map_t *map, - ecs_map_key_t key) +void assign_index( + chunk_t * chunk, + uint64_t * dense_array, + uint64_t index, + int32_t dense) { - ecs_assert(map->bucket_shift == get_bucket_shift(map->bucket_count), - ECS_INTERNAL_ERROR, NULL); - int32_t bucket_id = get_bucket_index(map->bucket_shift, key); - ecs_assert(bucket_id < map->bucket_count, ECS_INTERNAL_ERROR, NULL); - return &map->buckets[bucket_id]; + /* 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; } -/* Ensure that map has at least new_count buckets */ static -void ensure_buckets( - ecs_map_t *map, - int32_t new_count) +uint64_t inc_gen( + uint64_t index) { - int32_t bucket_count = map->bucket_count; - new_count = flecs_next_pow_of_2(new_count); - if (new_count < 2) { - new_count = 2; - } - - if (new_count && new_count > bucket_count) { - map->buckets = ecs_os_realloc_n(map->buckets, ecs_bucket_t, new_count); - map->buckets_end = ECS_ELEM_T(map->buckets, ecs_bucket_t, new_count); - - map->bucket_count = new_count; - map->bucket_shift = get_bucket_shift(new_count); - ecs_os_memset_n(ECS_ELEM_T(map->buckets, ecs_bucket_t, bucket_count), - 0, ecs_bucket_t, (new_count - bucket_count)); - } + /* When an index is deleted, its generation is increased so that we can do + * liveliness checking while recycling ids */ + return ECS_GENERATION_INC(index); } -/* Free contents of bucket */ static -void clear_bucket( - ecs_block_allocator_t *allocator, - ecs_bucket_entry_t *bucket) +uint64_t inc_id( + ecs_sparse_t *sparse) { - while(bucket) { - ecs_bucket_entry_t *next = bucket->next; - ecs_bfree(allocator, bucket); - bucket = next; - } + /* 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]); } -/* Clear all buckets */ static -void clear_buckets( - ecs_map_t *map) +uint64_t get_id( + const ecs_sparse_t *sparse) { - int32_t i, count = map->bucket_count; - for (i = 0; i < count; i ++) { - clear_bucket(&map->allocator, map->buckets[i].first); - } - ecs_os_free(map->buckets); - map->buckets = NULL; - map->bucket_count = 0; + return sparse->max_id[0]; } -/* Add element to bucket */ static -void* add_to_bucket( - ecs_block_allocator_t *allocator, - ecs_bucket_t *bucket, - ecs_size_t elem_size, - ecs_map_key_t key, - const void *payload) +void set_id( + ecs_sparse_t *sparse, + uint64_t value) { - ecs_bucket_entry_t *new_entry = ecs_balloc(allocator); - new_entry->key = key; - new_entry->next = bucket->first; - bucket->first = new_entry; - void *new_payload = ECS_OFFSET(&new_entry->key, ECS_SIZEOF(ecs_map_key_t)); - if (elem_size && payload) { - ecs_os_memcpy(new_payload, payload, elem_size); - } - return new_payload; + /* 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; } -/* Remove element from bucket */ +/* Pair dense id with new sparse id */ static -bool remove_from_bucket( - ecs_block_allocator_t *allocator, - ecs_bucket_t *bucket, - ecs_map_key_t key) +uint64_t create_id( + ecs_sparse_t *sparse, + int32_t dense) { - ecs_bucket_entry_t *entry; - for (entry = bucket->first; entry; entry = entry->next) { - if (entry->key == key) { - ecs_bucket_entry_t **next_holder = &bucket->first; - while(*next_holder != entry) { - next_holder = &(*next_holder)->next; - } - *next_holder = entry->next; - ecs_bfree(allocator, entry); - return true; - } - } + 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); - return false; + uint64_t *dense_array = ecs_vector_first(sparse->dense, uint64_t); + assign_index(chunk, dense_array, index, dense); + + return index; } -/* Get payload pointer for key from bucket */ +/* Create new id */ static -void* get_from_bucket( - ecs_bucket_t *bucket, - ecs_map_key_t key) +uint64_t new_index( + ecs_sparse_t *sparse) { - ecs_bucket_entry_t *entry; - for (entry = bucket->first; entry; entry = entry->next) { - if (entry->key == key) { - return ECS_OFFSET(&entry->key, ECS_SIZEOF(ecs_map_key_t)); - } + ecs_vector_t *dense = sparse->dense; + int32_t dense_count = ecs_vector_count(dense); + int32_t count = sparse->count ++; + + ecs_assert(count <= dense_count, 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); } - - return NULL; } -/* Grow number of buckets */ +/* Try obtaining a value from the sparse set, don't care about whether the + * provided index matches the current generation count. */ 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); - - int32_t old_count = map->bucket_count; - ecs_bucket_t *old_buckets = map->buckets; - - int32_t new_count = flecs_next_pow_of_2(bucket_count); - map->bucket_count = new_count; - map->bucket_shift = get_bucket_shift(new_count); - map->buckets = ecs_os_calloc_n(ecs_bucket_t, new_count); - map->buckets_end = ECS_ELEM_T(map->buckets, ecs_bucket_t, new_count); - - /* Remap old bucket entries to new buckets */ - int32_t index; - for (index = 0; index < old_count; ++index) { - ecs_bucket_entry_t* entry; - for (entry = old_buckets[index].first; entry;) { - ecs_bucket_entry_t* next = entry->next; - int32_t bucket_index = get_bucket_index( - map->bucket_shift, entry->key); - ecs_bucket_t *bucket = &map->buckets[bucket_index]; - entry->next = bucket->first; - bucket->first = entry; - entry = next; - } +void* try_sparse_any( + const ecs_sparse_t *sparse, + uint64_t index) +{ + strip_generation(&index); + + chunk_t *chunk = get_chunk(sparse, CHUNK(index)); + if (!chunk) { + return NULL; } - - ecs_os_free(old_buckets); + + int32_t offset = OFFSET(index); + int32_t dense = chunk->sparse[offset]; + bool in_use = dense && (dense < sparse->count); + if (!in_use) { + return NULL; + } + + ecs_assert(dense == chunk->sparse[offset], ECS_INTERNAL_ERROR, NULL); + return DATA(chunk->data, sparse->size, offset); } -bool ecs_map_is_initialized( - const ecs_map_t *result) +/* 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) { - return result != NULL && result->bucket_count != 0; + chunk_t *chunk = get_chunk(sparse, CHUNK(index)); + if (!chunk) { + return NULL; + } + + int32_t offset = OFFSET(index); + int32_t dense = chunk->sparse[offset]; + bool in_use = dense && (dense < sparse->count); + if (!in_use) { + return NULL; + } + + 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 (cur_gen != gen) { + return NULL; + } + + ecs_assert(dense == chunk->sparse[offset], ECS_INTERNAL_ERROR, NULL); + return DATA(chunk->data, sparse->size, offset); } -void _ecs_map_init( - ecs_map_t *result, - ecs_size_t elem_size, - int32_t element_count) +/* 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) { - ecs_assert(elem_size < INT16_MAX, ECS_INVALID_PARAMETER, NULL); - - result->count = 0; - result->elem_size = (int16_t)elem_size; + strip_generation(&index); + chunk_t *chunk = get_chunk(sparse, CHUNK(index)); + int32_t offset = OFFSET(index); - int32_t entry_size = elem_size + ECS_SIZEOF(ecs_bucket_entry_t); - int32_t balloc_min_chunk_size = ECS_MAX(entry_size, - ECS_SIZEOF(ecs_block_allocator_chunk_header_t)); - uint32_t alignment = 16; - uint32_t alignment_mask = alignment - 1u; - - /* Align balloc_min_chunk_size up to alignment. */ - result->allocator.chunk_size = (int32_t)(((uint32_t)balloc_min_chunk_size + - alignment_mask) & ~alignment_mask); - result->allocator.chunks_per_block = - ECS_MAX(4096 / result->allocator.chunk_size, 1); - result->allocator.block_size = result->allocator.chunks_per_block * - result->allocator.chunk_size; - result->allocator.head = NULL; - result->allocator.block_head = NULL; - result->allocator.block_tail = NULL; + ecs_assert(chunk != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(dense == chunk->sparse[offset], ECS_INTERNAL_ERROR, NULL); + (void)dense; - ensure_buckets(result, get_bucket_count(element_count)); + return DATA(chunk->data, sparse->size, offset); } -void _ecs_map_init_if( - ecs_map_t *result, - ecs_size_t elem_size, - int32_t element_count) +/* 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) { - if (ecs_map_is_initialized(result)) { - ecs_assert(elem_size == result->elem_size, ECS_INVALID_PARAMETER, NULL); - return; - } - _ecs_map_init(result, elem_size, element_count); + 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]; + + 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_map_t* _ecs_map_new( - ecs_size_t elem_size, - int32_t element_count) +void _flecs_sparse_init( + ecs_sparse_t *result, + ecs_size_t size) { - ecs_map_t *result = ecs_os_calloc_t(ecs_map_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_map_init(result, elem_size, element_count); + /* 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; - return result; + result->count = 1; } -void ecs_map_fini( - ecs_map_t *map) +ecs_sparse_t* _flecs_sparse_new( + ecs_size_t size) { - ecs_assert(map != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_block_allocator_block_t *block; - for (block = map->allocator.block_head; block; ){ - ecs_block_allocator_block_t *next = block->next; - ecs_os_free(block->memory); - ecs_os_free(block); - block = next; - } - map->allocator.block_head = NULL; - ecs_os_free(map->buckets); - map->buckets = NULL; - map->buckets_end = NULL; - map->bucket_count = 0; - ecs_assert(!ecs_map_is_initialized(map), ECS_INTERNAL_ERROR, NULL); + ecs_sparse_t *result = ecs_os_calloc_t(ecs_sparse_t); + + _flecs_sparse_init(result, size); + + return result; } -void ecs_map_free( - ecs_map_t *map) +void flecs_sparse_set_id_source( + ecs_sparse_t * sparse, + uint64_t * id_source) { - if (map) { - ecs_map_fini(map); - ecs_os_free(map); - } + ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); + sparse->max_id = id_source; } -void* _ecs_map_get( - const ecs_map_t *map, - ecs_size_t elem_size, - ecs_map_key_t key) +void flecs_sparse_clear( + ecs_sparse_t *sparse) { - (void)elem_size; - - if (!ecs_map_is_initialized(map)) { - return NULL; - } + ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_assert(elem_size == map->elem_size, ECS_INVALID_PARAMETER, NULL); + ecs_vector_each(sparse->chunks, chunk_t, chunk, { + chunk_free(chunk); + }); - ecs_bucket_t *bucket = get_bucket(map, key); + ecs_vector_free(sparse->chunks); + ecs_vector_set_count(&sparse->dense, uint64_t, 1); - return get_from_bucket(bucket, key); + sparse->chunks = NULL; + sparse->count = 1; + sparse->max_id_local = 0; } -void* _ecs_map_get_ptr( - const ecs_map_t *map, - ecs_map_key_t key) +void _flecs_sparse_fini( + ecs_sparse_t *sparse) { - void* ptr_ptr = _ecs_map_get(map, ECS_SIZEOF(void*), key); - - if (ptr_ptr) { - return *(void**)ptr_ptr; - } else { - return NULL; - } + ecs_assert(sparse != NULL, ECS_INTERNAL_ERROR, NULL); + flecs_sparse_clear(sparse); + ecs_vector_free(sparse->dense); } -bool ecs_map_has( - const ecs_map_t *map, - ecs_map_key_t key) +void flecs_sparse_free( + ecs_sparse_t *sparse) { - if (!ecs_map_is_initialized(map)) { - return false; + if (sparse) { + _flecs_sparse_fini(sparse); + ecs_os_free(sparse); } +} - ecs_bucket_t *bucket = get_bucket(map, key); - - return get_from_bucket(bucket, key) != NULL; +uint64_t flecs_sparse_new_id( + ecs_sparse_t *sparse) +{ + ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); + return new_index(sparse); } -void* _ecs_map_ensure( - ecs_map_t *map, - ecs_size_t elem_size, - ecs_map_key_t key) +const uint64_t* flecs_sparse_new_ids( + ecs_sparse_t *sparse, + int32_t new_count) { - void *result = _ecs_map_get(map, elem_size, key); - if (!result) { - result = _ecs_map_set(map, elem_size, key, NULL); - if (elem_size) { - ecs_assert(result != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_os_memset(result, 0, elem_size); + 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); + for (i = 0; i < to_create; i ++) { + create_id(sparse, count + i); } } - return result; + sparse->count += new_count; + + return ecs_vector_get(sparse->dense, uint64_t, count); } -void* _ecs_map_set( - ecs_map_t *map, - ecs_size_t elem_size, - ecs_map_key_t key, - const void *payload) +void* _flecs_sparse_add( + ecs_sparse_t *sparse, + ecs_size_t size) { - ecs_assert(map != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_assert(elem_size == map->elem_size, ECS_INVALID_PARAMETER, NULL); + 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_bucket_t *bucket = get_bucket(map, key); +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]; +} - void *elem = get_from_bucket(bucket, key); - if (!elem) { - void *added_data = add_to_bucket( - &map->allocator, 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; +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; - if (target_bucket_count > map_bucket_count) { - rehash(map, target_bucket_count); - bucket = get_bucket(map, key); - added_data = get_from_bucket(bucket, key); - ecs_assert(added_data != NULL, ECS_INVALID_PARAMETER, 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]; + + 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); + + /* First unused element is now last used element */ + sparse->count ++; + } else { + /* Dense is already alive, nothing to be done */ } - return added_data; + + /* 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 { - if (payload) { - ecs_os_memcpy(elem, payload, elem_size); - } - return elem; - } -} + /* Element is not paired yet. Must add a new element to dense array */ + grow_dense(sparse); -int32_t ecs_map_remove( - ecs_map_t *map, - ecs_map_key_t key) -{ - ecs_assert(map != NULL, ECS_INVALID_PARAMETER, NULL); + 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 ++; - ecs_bucket_t *bucket = get_bucket(map, key); - if (remove_from_bucket(&map->allocator, bucket, key)) { - return --map->count; - } - - return map->count; -} + /* If index is larger than max id, update max id */ + if (index >= get_id(sparse)) { + set_id(sparse, index); + } -int32_t ecs_map_count( - const ecs_map_t *map) -{ - return map ? map->count : 0; -} + 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); + } -int32_t ecs_map_bucket_count( - const ecs_map_t *map) -{ - return map ? map->bucket_count : 0; -} + assign_index(chunk, dense_array, index, count); + dense_array[count] |= gen; + } -void ecs_map_clear( - ecs_map_t *map) -{ - ecs_assert(map != NULL, ECS_INVALID_PARAMETER, NULL); - clear_buckets(map); - map->count = 0; - ensure_buckets(map, 2); + return DATA(chunk->data, sparse->size, offset); } -ecs_map_iter_t ecs_map_iter( - const ecs_map_t *map) +void* _flecs_sparse_set( + ecs_sparse_t * sparse, + ecs_size_t elem_size, + uint64_t index, + void* value) { - return (ecs_map_iter_t){ - .map = map, - .bucket = NULL, - .entry = NULL - }; + void *ptr = _flecs_sparse_ensure(sparse, elem_size, index); + ecs_os_memcpy(ptr, value, elem_size); + return ptr; } -void* _ecs_map_next( - ecs_map_iter_t *iter, - ecs_size_t elem_size, - ecs_map_key_t *key_out) +void* _flecs_sparse_remove_get( + ecs_sparse_t *sparse, + ecs_size_t size, + uint64_t index) { - (void)elem_size; - const ecs_map_t *map = iter->map; - if (!ecs_map_is_initialized(map)) { - return NULL; - } - if (iter->bucket == map->buckets_end) { - return NULL; - } + ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(!size || size == sparse->size, ECS_INVALID_PARAMETER, NULL); + (void)size; - ecs_assert(!elem_size || elem_size == map->elem_size, - ECS_INVALID_PARAMETER, 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]; - ecs_bucket_entry_t *entry = NULL; - - if (!iter->bucket) { - for (iter->bucket = map->buckets; - iter->bucket != map->buckets_end; - ++iter->bucket) - { - if (iter->bucket->first) { - entry = iter->bucket->first; - break; - } - } - if (iter->bucket == map->buckets_end) { + 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; } - } else if ((entry = iter->entry) == NULL) { - do { - ++iter->bucket; - if (iter->bucket == map->buckets_end) { - return NULL; - } - } while(!iter->bucket->first); - entry = iter->bucket->first; - } - - if (key_out) { - ecs_assert(entry != NULL, ECS_INTERNAL_ERROR, NULL); - *key_out = entry->key; - } - - iter->entry = entry->next; - return ECS_OFFSET(&entry->key, ECS_SIZEOF(ecs_map_key_t)); -} + /* 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; + } -void* _ecs_map_next_ptr( - ecs_map_iter_t *iter, - ecs_map_key_t *key_out) -{ - void *result = _ecs_map_next(iter, ECS_SIZEOF(void*), key_out); - if (result) { - return *(void**)result; + /* 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 ecs_map_grow( - ecs_map_t *map, - int32_t element_count) +void flecs_sparse_remove( + ecs_sparse_t *sparse, + uint64_t index) { - 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); + void *ptr = _flecs_sparse_remove_get(sparse, 0, index); + if (ptr) { + ecs_os_memset(ptr, 0, sparse->size); } } -ecs_map_t* ecs_map_copy( - ecs_map_t *map) +void flecs_sparse_set_generation( + ecs_sparse_t *sparse, + uint64_t index) { - if (!ecs_map_is_initialized(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_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]; - 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); + 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 */ } - - return result; } -void ecs_map_memory( - ecs_map_t *map, - int32_t *allocd, - int32_t *used) +bool flecs_sparse_exists( + const ecs_sparse_t *sparse, + uint64_t index) { - ecs_assert(map != NULL, ECS_INVALID_PARAMETER, NULL); - - if (used) { - *used = map->count * map->elem_size; + 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]; - // TODO: something something block allocator - if (allocd) { - *allocd += ECS_SIZEOF(ecs_map_t); - *allocd += ECS_SIZEOF(ecs_bucket_entry_t*) * map->bucket_count; - int32_t index; - int32_t entry_size = map->elem_size + ECS_SIZEOF(ecs_bucket_entry_t); - for (index = 0; index < map->bucket_count; ++index) { - ecs_bucket_entry_t *entry; - for (entry = map->buckets[index].first; entry; entry = entry->next){ - *allocd += entry_size; - } - } - } + 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 ++; -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); - } + uint64_t *dense_array = ecs_vector_first(sparse->dense, uint64_t); + return get_sparse(sparse, dense_index, dense_array[dense_index]); } -void flecs_bitset_init( - ecs_bitset_t* bs) +bool flecs_sparse_is_alive( + const ecs_sparse_t *sparse, + uint64_t index) { - bs->size = 0; - bs->count = 0; - bs->data = NULL; + return try_sparse(sparse, index) != NULL; } -void flecs_bitset_ensure( - ecs_bitset_t *bs, - int32_t count) +uint64_t flecs_sparse_get_alive( + const ecs_sparse_t *sparse, + uint64_t index) { - if (count > bs->count) { - bs->count = count; - ensure(bs, count); + chunk_t *chunk = get_chunk(sparse, CHUNK(index)); + if (!chunk) { + return 0; } + + 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_bitset_fini( - ecs_bitset_t *bs) +void* _flecs_sparse_get( + const ecs_sparse_t *sparse, + ecs_size_t size, + uint64_t index) { - ecs_os_free(bs->data); - bs->data = NULL; - bs->count = 0; + 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_bitset_addn( - ecs_bitset_t *bs, - int32_t count) +void* _flecs_sparse_get_any( + const ecs_sparse_t *sparse, + ecs_size_t size, + uint64_t index) { - int32_t elem = bs->count += count; - ensure(bs, elem); + 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); } -void flecs_bitset_set( - ecs_bitset_t *bs, - int32_t elem, - bool value) +int32_t flecs_sparse_count( + const ecs_sparse_t *sparse) { - ecs_check(elem < bs->count, ECS_INVALID_PARAMETER, NULL); - uint32_t hi = ((uint32_t)elem) >> 6; - uint32_t lo = ((uint32_t)elem) & 0x3F; - uint64_t v = bs->data[hi]; - bs->data[hi] = (v & ~((uint64_t)1 << lo)) | ((uint64_t)value << lo); -error: - return; + if (!sparse) { + return 0; + } + + return sparse->count - 1; } -bool flecs_bitset_get( - const ecs_bitset_t *bs, - int32_t elem) +int32_t flecs_sparse_not_alive_count( + const ecs_sparse_t *sparse) { - ecs_check(elem < bs->count, ECS_INVALID_PARAMETER, NULL); - return !!(bs->data[elem >> 6] & ((uint64_t)1 << ((uint64_t)elem & 0x3F))); -error: - return false; + if (!sparse) { + return 0; + } + + return ecs_vector_count(sparse->dense) - sparse->count; } -int32_t flecs_bitset_count( - const ecs_bitset_t *bs) +int32_t flecs_sparse_size( + const ecs_sparse_t *sparse) { - return bs->count; + if (!sparse) { + return 0; + } + + return ecs_vector_count(sparse->dense) - 1; } -void flecs_bitset_remove( - ecs_bitset_t *bs, - int32_t elem) +const uint64_t* flecs_sparse_ids( + const ecs_sparse_t *sparse) { - 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; + ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); + return &(ecs_vector_first(sparse->dense, uint64_t)[1]); } -void flecs_bitset_swap( - ecs_bitset_t *bs, - int32_t elem_a, - int32_t elem_b) +void flecs_sparse_set_size( + ecs_sparse_t *sparse, + int32_t elem_count) { - ecs_check(elem_a < bs->count, ECS_INVALID_PARAMETER, NULL); - ecs_check(elem_b < bs->count, ECS_INVALID_PARAMETER, NULL); - - 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_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_vector_set_size(&sparse->dense, uint64_t, elem_count); } - static -uint64_t name_index_hash( - const void *ptr) +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); + + 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); + } + + 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) +{ + if (!src) { + return NULL; + } + + ecs_sparse_t *dst = _flecs_sparse_new(src->size); + sparse_copy(dst, src); + + return dst; +} + +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 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) +{ + 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); +} + +uint64_t ecs_sparse_last_id( + const ecs_sparse_t *sparse) +{ + return flecs_sparse_last_id(sparse); +} + +int32_t ecs_sparse_count( + const ecs_sparse_t *sparse) +{ + return flecs_sparse_count(sparse); +} + +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); +} + +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_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; +} + + +#ifdef FLECS_SANITIZE +static +void verify_nodes( + ecs_switch_header_t *hdr, + ecs_switch_node_t *nodes) +{ + if (!hdr) { + return; + } + + int32_t prev = -1, elem = hdr->element, count = 0; + while (elem != -1) { + ecs_assert(prev == nodes[elem].prev, ECS_INTERNAL_ERROR, NULL); + prev = elem; + elem = nodes[elem].next; + count ++; + } + + ecs_assert(count == hdr->count, ECS_INTERNAL_ERROR, NULL); +} +#else +#define verify_nodes(hdr, nodes) +#endif + +static +ecs_switch_header_t *get_header( + const ecs_switch_t *sw, + uint64_t value) +{ + if (value == 0) { + return NULL; + } + + return ecs_map_get(&sw->headers, ecs_switch_header_t, value); +} + +static +ecs_switch_header_t *ensure_header( + ecs_switch_t *sw, + uint64_t value) +{ + if (value == 0) { + return NULL; + } + + ecs_switch_header_t *node = get_header(sw, value); + if (!node) { + node = ecs_map_ensure(&sw->headers, ecs_switch_header_t, value); + node->element = -1; + } + + return node; +} + +static +void remove_node( + ecs_switch_header_t *hdr, + ecs_switch_node_t *nodes, + ecs_switch_node_t *node, + int32_t element) +{ + ecs_assert(&nodes[element] == node, ECS_INTERNAL_ERROR, NULL); + + /* Update previous node/header */ + if (hdr->element == element) { + ecs_assert(node->prev == -1, ECS_INVALID_PARAMETER, NULL); + /* If this is the first node, update the header */ + hdr->element = node->next; + } else { + /* If this is not the first node, update the previous node to the + * removed node's next ptr */ + ecs_assert(node->prev != -1, ECS_INVALID_PARAMETER, NULL); + ecs_switch_node_t *prev_node = &nodes[node->prev]; + prev_node->next = node->next; + } + + /* Update next node */ + int32_t next = node->next; + if (next != -1) { + ecs_assert(next >= 0, ECS_INVALID_PARAMETER, NULL); + /* If this is not the last node, update the next node to point to the + * removed node's prev ptr */ + ecs_switch_node_t *next_node = &nodes[next]; + next_node->prev = node->prev; + } + + /* Decrease count of current header */ + hdr->count --; + ecs_assert(hdr->count >= 0, ECS_INTERNAL_ERROR, NULL); +} + +void flecs_switch_init( + ecs_switch_t *sw, + int32_t elements) +{ + ecs_map_init(&sw->headers, ecs_switch_header_t, 1); + sw->nodes = ecs_vector_new(ecs_switch_node_t, elements); + sw->values = ecs_vector_new(uint64_t, elements); + + ecs_switch_node_t *nodes = ecs_vector_first( + sw->nodes, ecs_switch_node_t); + uint64_t *values = ecs_vector_first( + sw->values, uint64_t); + + int i; + for (i = 0; i < elements; i ++) { + nodes[i].prev = -1; + nodes[i].next = -1; + values[i] = 0; + } +} + +ecs_switch_t* flecs_switch_new( + int32_t elements) +{ + ecs_switch_t *result = ecs_os_malloc(ECS_SIZEOF(ecs_switch_t)); + + flecs_switch_init(result, elements); + + return result; +} + +void flecs_switch_clear( + ecs_switch_t *sw) +{ + ecs_map_clear(&sw->headers); + ecs_vector_free(sw->nodes); + ecs_vector_free(sw->values); + sw->nodes = NULL; + sw->values = NULL; +} + +void flecs_switch_fini( + ecs_switch_t *sw) +{ + // ecs_os_free(sw->headers); + ecs_map_fini(&sw->headers); + ecs_vector_free(sw->nodes); + ecs_vector_free(sw->values); +} + +void flecs_switch_free( + ecs_switch_t *sw) +{ + flecs_switch_fini(sw); + ecs_os_free(sw); +} + +void flecs_switch_add( + ecs_switch_t *sw) +{ + ecs_switch_node_t *node = ecs_vector_add(&sw->nodes, ecs_switch_node_t); + uint64_t *value = ecs_vector_add(&sw->values, uint64_t); + node->prev = -1; + node->next = -1; + *value = 0; +} + +void flecs_switch_set_count( + ecs_switch_t *sw, + int32_t count) +{ + int32_t old_count = ecs_vector_count(sw->nodes); + if (old_count == count) { + return; + } + + ecs_vector_set_count(&sw->nodes, ecs_switch_node_t, count); + ecs_vector_set_count(&sw->values, uint64_t, count); + + ecs_switch_node_t *nodes = ecs_vector_first(sw->nodes, ecs_switch_node_t); + uint64_t *values = ecs_vector_first(sw->values, uint64_t); + + int32_t i; + for (i = old_count; i < count; i ++) { + ecs_switch_node_t *node = &nodes[i]; + node->prev = -1; + node->next = -1; + values[i] = 0; + } +} + +int32_t flecs_switch_count( + ecs_switch_t *sw) +{ + ecs_assert(ecs_vector_count(sw->values) == ecs_vector_count(sw->nodes), + ECS_INTERNAL_ERROR, NULL); + return ecs_vector_count(sw->values); +} + +void flecs_switch_ensure( + ecs_switch_t *sw, + int32_t count) +{ + int32_t old_count = ecs_vector_count(sw->nodes); + if (old_count >= count) { + return; + } + + flecs_switch_set_count(sw, count); +} + +void flecs_switch_addn( + ecs_switch_t *sw, + int32_t count) +{ + int32_t old_count = ecs_vector_count(sw->nodes); + flecs_switch_set_count(sw, old_count + count); +} + +void flecs_switch_set( + ecs_switch_t *sw, + int32_t element, + uint64_t value) +{ + ecs_assert(sw != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(element < ecs_vector_count(sw->nodes), ECS_INVALID_PARAMETER, NULL); + ecs_assert(element < ecs_vector_count(sw->values), ECS_INVALID_PARAMETER, NULL); + ecs_assert(element >= 0, ECS_INVALID_PARAMETER, NULL); + + uint64_t *values = ecs_vector_first(sw->values, uint64_t); + uint64_t cur_value = values[element]; + + /* If the node is already assigned to the value, nothing to be done */ + if (cur_value == value) { + return; + } + + ecs_switch_node_t *nodes = ecs_vector_first(sw->nodes, ecs_switch_node_t); + ecs_switch_node_t *node = &nodes[element]; + + ecs_switch_header_t *dst_hdr = ensure_header(sw, value); + ecs_switch_header_t *cur_hdr = get_header(sw, cur_value); + + verify_nodes(cur_hdr, nodes); + verify_nodes(dst_hdr, nodes); + + /* If value is not 0, and dst_hdr is NULL, then this is not a valid value + * for this switch */ + ecs_assert(dst_hdr != NULL || !value, ECS_INVALID_PARAMETER, NULL); + + if (cur_hdr) { + remove_node(cur_hdr, nodes, node, element); + } + + /* Now update the node itself by adding it as the first node of dst */ + node->prev = -1; + values[element] = value; + + if (dst_hdr) { + node->next = dst_hdr->element; + + /* Also update the dst header */ + int32_t first = dst_hdr->element; + if (first != -1) { + ecs_assert(first >= 0, ECS_INTERNAL_ERROR, NULL); + ecs_switch_node_t *first_node = &nodes[first]; + first_node->prev = element; + } + + dst_hdr->element = element; + dst_hdr->count ++; + } +} + +void flecs_switch_remove( + ecs_switch_t *sw, + int32_t element) +{ + ecs_assert(sw != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(element < ecs_vector_count(sw->nodes), ECS_INVALID_PARAMETER, NULL); + ecs_assert(element >= 0, ECS_INVALID_PARAMETER, NULL); + + uint64_t *values = ecs_vector_first(sw->values, uint64_t); + uint64_t value = values[element]; + ecs_switch_node_t *nodes = ecs_vector_first(sw->nodes, ecs_switch_node_t); + ecs_switch_node_t *node = &nodes[element]; + + /* If node is currently assigned to a case, remove it from the list */ + if (value != 0) { + ecs_switch_header_t *hdr = get_header(sw, value); + ecs_assert(hdr != NULL, ECS_INTERNAL_ERROR, NULL); + + verify_nodes(hdr, nodes); + remove_node(hdr, nodes, node, element); + } + + int32_t last_elem = ecs_vector_count(sw->nodes) - 1; + if (last_elem != element) { + ecs_switch_node_t *last = ecs_vector_last(sw->nodes, ecs_switch_node_t); + int32_t next = last->next, prev = last->prev; + if (next != -1) { + ecs_switch_node_t *n = &nodes[next]; + n->prev = element; + } + + if (prev != -1) { + ecs_switch_node_t *n = &nodes[prev]; + n->next = element; + } else { + ecs_switch_header_t *hdr = get_header(sw, values[last_elem]); + if (hdr && hdr->element != -1) { + ecs_assert(hdr->element == last_elem, + ECS_INTERNAL_ERROR, NULL); + hdr->element = element; + } + } + } + + /* Remove element from arrays */ + ecs_vector_remove(sw->nodes, ecs_switch_node_t, element); + ecs_vector_remove(sw->values, uint64_t, element); +} + +uint64_t flecs_switch_get( + const ecs_switch_t *sw, + int32_t element) +{ + ecs_assert(sw != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(element < ecs_vector_count(sw->nodes), ECS_INVALID_PARAMETER, NULL); + ecs_assert(element < ecs_vector_count(sw->values), ECS_INVALID_PARAMETER, NULL); + ecs_assert(element >= 0, ECS_INVALID_PARAMETER, NULL); + + uint64_t *values = ecs_vector_first(sw->values, uint64_t); + return values[element]; +} + +ecs_vector_t* flecs_switch_values( + const ecs_switch_t *sw) +{ + return sw->values; +} + +int32_t flecs_switch_case_count( + const ecs_switch_t *sw, + uint64_t value) +{ + ecs_switch_header_t *hdr = get_header(sw, value); + if (!hdr) { + return 0; + } + + return hdr->count; +} + +void flecs_switch_swap( + ecs_switch_t *sw, + int32_t elem_1, + int32_t elem_2) +{ + uint64_t v1 = flecs_switch_get(sw, elem_1); + uint64_t v2 = flecs_switch_get(sw, elem_2); + + flecs_switch_set(sw, elem_2, v1); + flecs_switch_set(sw, elem_1, v2); +} + +int32_t flecs_switch_first( + const ecs_switch_t *sw, + uint64_t value) +{ + ecs_assert(sw != NULL, ECS_INVALID_PARAMETER, NULL); + + ecs_switch_header_t *hdr = get_header(sw, value); + if (!hdr) { + return -1; + } + + return hdr->element; +} + +int32_t flecs_switch_next( + const ecs_switch_t *sw, + int32_t element) +{ + ecs_assert(sw != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(element < ecs_vector_count(sw->nodes), ECS_INVALID_PARAMETER, NULL); + ecs_assert(element >= 0, ECS_INVALID_PARAMETER, NULL); + + ecs_switch_node_t *nodes = ecs_vector_first( + sw->nodes, ecs_switch_node_t); + + return nodes[element].next; +} + + +static +uint64_t name_index_hash( + const void *ptr) { const ecs_hashed_string_t *str = ptr; ecs_assert(str->hash != 0, ECS_INTERNAL_ERROR, NULL); @@ -12295,240 +12265,339 @@ void flecs_name_index_ensure( } -static -int32_t find_key( - const ecs_hashmap_t *map, - ecs_vector_t *keys, - ecs_size_t key_size, - const void *key) -{ - int32_t i, count = ecs_vector_count(keys); - void *key_array = ecs_vector_first_t(keys, key_size, 8); - for (i = 0; i < count; i ++) { - void *key_ptr = ECS_OFFSET(key_array, key_size * i); - if (map->compare(key_ptr, key) == 0) { - return i; - } - } - return -1; -} - -void _flecs_hashmap_init( - ecs_hashmap_t *map, - ecs_size_t key_size, - ecs_size_t value_size, - ecs_hash_value_action_t hash, - ecs_compare_action_t compare) -{ - map->key_size = key_size; - map->value_size = value_size; - map->hash = hash; - map->compare = compare; - ecs_map_init(&map->impl, ecs_hm_bucket_t, 0); -} - -void flecs_hashmap_fini( - ecs_hashmap_t *map) -{ - ecs_map_iter_t it = ecs_map_iter(&map->impl); - ecs_hm_bucket_t *bucket; - while ((bucket = ecs_map_next(&it, ecs_hm_bucket_t, NULL))) { - ecs_vector_free(bucket->keys); - ecs_vector_free(bucket->values); - } +#ifdef ECS_TARGET_GNU +#pragma GCC diagnostic ignored "-Wimplicit-fallthrough" +#endif - ecs_map_fini(&map->impl); -} +/* 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(FLECS_NDEBUG) || defined(ADDRESS_SANITIZER) +#ifndef VALGRIND +#define VALGRIND +#endif +#endif -void flecs_hashmap_copy( - const ecs_hashmap_t *src, - ecs_hashmap_t *dst) -{ - if (dst != src) { - *dst = *src; - } - - ecs_map_t *impl = ecs_map_copy(&dst->impl); - dst->impl = *impl; - ecs_os_free(impl); +/* +------------------------------------------------------------------------------- +lookup3.c, by Bob Jenkins, May 2006, Public Domain. + http://burtleburtle.net/bob/c/lookup3.c +------------------------------------------------------------------------------- +*/ - ecs_map_iter_t it = ecs_map_iter(&dst->impl); - ecs_hm_bucket_t *bucket; - while ((bucket = ecs_map_next(&it, ecs_hm_bucket_t, NULL))) { - bucket->keys = ecs_vector_copy_t(bucket->keys, dst->key_size, 8); - bucket->values = ecs_vector_copy_t(bucket->values, dst->value_size, 8); - } -} +#ifdef ECS_TARGET_WINDOWS +//FIXME +#else +#include /* attempt to define endianness */ +#endif +#ifdef ECS_TARGET_LINUX +# include /* attempt to define endianness */ +#endif -void* _flecs_hashmap_get( - const ecs_hashmap_t *map, - ecs_size_t key_size, - const void *key, - ecs_size_t value_size) -{ - ecs_assert(map->key_size == key_size, ECS_INVALID_PARAMETER, NULL); - ecs_assert(map->value_size == value_size, ECS_INVALID_PARAMETER, NULL); +/* + * 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 - uint64_t hash = map->hash(key); - ecs_hm_bucket_t *bucket = ecs_map_get(&map->impl, ecs_hm_bucket_t, hash); - if (!bucket) { - return NULL; - } +#define rot(x,k) (((x)<<(k)) | ((x)>>(32-(k)))) - int32_t index = find_key(map, bucket->keys, key_size, key); - if (index == -1) { - return NULL; - } +/* +------------------------------------------------------------------------------- +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; \ +} - return ecs_vector_get_t(bucket->values, value_size, 8, index); +/* +------------------------------------------------------------------------------- +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); \ } -flecs_hashmap_result_t _flecs_hashmap_ensure( - ecs_hashmap_t *map, - ecs_size_t key_size, - const void *key, - ecs_size_t value_size) + +/* + * 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 */ { - ecs_assert(map->key_size == key_size, ECS_INVALID_PARAMETER, NULL); - ecs_assert(map->value_size == value_size, ECS_INVALID_PARAMETER, NULL); + uint32_t a,b,c; /* internal state */ + union { const void *ptr; size_t i; } u; /* needed for Mac Powerbook G4 */ - uint64_t hash = map->hash(key); - ecs_hm_bucket_t *bucket = ecs_map_ensure(&map->impl, ecs_hm_bucket_t, hash); - ecs_assert(bucket != NULL, ECS_INTERNAL_ERROR, NULL); + /* Set up the internal state */ + a = b = c = 0xdeadbeef + ((uint32_t)length) + *pc; + c += *pb; - void *value_ptr, *key_ptr; + 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; - ecs_vector_t *keys = bucket->keys; - if (!keys) { - bucket->keys = ecs_vector_new_t(key_size, 8, 1); - bucket->values = ecs_vector_new_t(value_size, 8, 1); - key_ptr = ecs_vector_add_t(&bucket->keys, key_size, 8); - ecs_os_memcpy(key_ptr, key, key_size); - value_ptr = ecs_vector_add_t(&bucket->values, value_size, 8); - ecs_os_memset(value_ptr, 0, value_size); - } else { - int32_t index = find_key(map, keys, key_size, key); - if (index == -1) { - key_ptr = ecs_vector_add_t(&bucket->keys, key_size, 8); - ecs_os_memcpy(key_ptr, key, key_size); - value_ptr = ecs_vector_add_t(&bucket->values, value_size, 8); - ecs_assert(value_ptr != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_os_memset(value_ptr, 0, value_size); - } else { - key_ptr = ecs_vector_get_t(bucket->keys, key_size, 8, index); - value_ptr = ecs_vector_get_t(bucket->values, value_size, 8, index); - } + /*------ 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; } - return (flecs_hashmap_result_t){ - .key = key_ptr, - .value = value_ptr, - .hash = hash - }; -} - -void _flecs_hashmap_set( - ecs_hashmap_t *map, - ecs_size_t key_size, - void *key, - ecs_size_t value_size, - const void *value) -{ - void *value_ptr = _flecs_hashmap_ensure(map, key_size, key, value_size).value; - ecs_assert(value_ptr != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_os_memcpy(value_ptr, value, value_size); -} + /*----------------------------- 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_hm_bucket_t* flecs_hashmap_get_bucket( - const ecs_hashmap_t *map, - uint64_t hash) -{ - ecs_assert(map != NULL, ECS_INTERNAL_ERROR, NULL); - return ecs_map_get(&map->impl, ecs_hm_bucket_t, hash); -} + 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 */ + } -void flecs_hm_bucket_remove( - ecs_hashmap_t *map, - ecs_hm_bucket_t *bucket, - uint64_t hash, - int32_t index) -{ - ecs_vector_remove_t(bucket->keys, map->key_size, 8, index); - ecs_vector_remove_t(bucket->values, map->value_size, 8, index); +#else /* make valgrind happy */ - if (!ecs_vector_count(bucket->keys)) { - ecs_vector_free(bucket->keys); - ecs_vector_free(bucket->values); - ecs_map_remove(&map->impl, hash); + 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 */ } -} -void _flecs_hashmap_remove_w_hash( - ecs_hashmap_t *map, - ecs_size_t key_size, - const void *key, - ecs_size_t value_size, - uint64_t hash) -{ - ecs_assert(map->key_size == key_size, ECS_INVALID_PARAMETER, NULL); - ecs_assert(map->value_size == value_size, ECS_INVALID_PARAMETER, NULL); - (void)value_size; +#endif /* !valgrind */ - ecs_hm_bucket_t *bucket = ecs_map_get(&map->impl, ecs_hm_bucket_t, hash); - if (!bucket) { - return; + } 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; + + /*--------------- 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; } - int32_t index = find_key(map, bucket->keys, key_size, key); - if (index == -1) { - return; + /*----------------------------- 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 */ } - flecs_hm_bucket_remove(map, bucket, hash, index); -} + } else { /* need to read the key one byte at a time */ + const uint8_t *k = (const uint8_t *)key; -void _flecs_hashmap_remove( - ecs_hashmap_t *map, - ecs_size_t key_size, - const void *key, - ecs_size_t value_size) -{ - ecs_assert(map->key_size == key_size, ECS_INVALID_PARAMETER, NULL); - ecs_assert(map->value_size == value_size, ECS_INVALID_PARAMETER, NULL); + /*--------------- 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; + } - uint64_t hash = map->hash(key); - _flecs_hashmap_remove_w_hash(map, key_size, key, value_size, hash); -} + /*-------------------------------- 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 */ + } + } -flecs_hashmap_iter_t flecs_hashmap_iter( - ecs_hashmap_t *map) -{ - return (flecs_hashmap_iter_t){ - .it = ecs_map_iter(&map->impl) - }; + final(a,b,c); + *pc=c; *pb=b; } -void* _flecs_hashmap_next( - flecs_hashmap_iter_t *it, - ecs_size_t key_size, - void *key_out, - ecs_size_t value_size) +uint64_t flecs_hash( + const void *data, + ecs_size_t length) { - int32_t index = ++ it->index; - ecs_hm_bucket_t *bucket = it->bucket; - while (!bucket || it->index >= ecs_vector_count(bucket->keys)) { - bucket = it->bucket = ecs_map_next(&it->it, ecs_hm_bucket_t, NULL); - if (!bucket) { - return NULL; - } - index = it->index = 0; - } + uint32_t h_1 = 0; + uint32_t h_2 = 0; - if (key_out) { - *(void**)key_out = ecs_vector_get_t(bucket->keys, key_size, 8, index); - } - - return ecs_vector_get_t(bucket->values, value_size, 8, index); + hashlittle2( + data, + flecs_ito(size_t, length), + &h_1, + &h_2); + + return h_1 | ((uint64_t)h_2 << 32); } @@ -12552,14615 +12621,13794 @@ void ecs_qsort( } -/* Id flags */ -const ecs_id_t ECS_PAIR = (1ull << 63); -const ecs_id_t ECS_OVERRIDE = (1ull << 62); -const ecs_id_t ECS_TOGGLE = (1ull << 61); -const ecs_id_t ECS_AND = (1ull << 60); -const ecs_id_t ECS_OR = (1ull << 59); -const ecs_id_t ECS_NOT = (1ull << 58); - -/** Builtin component ids */ -const ecs_entity_t ecs_id(EcsComponent) = 1; -const ecs_entity_t ecs_id(EcsIdentifier) = 2; -const ecs_entity_t ecs_id(EcsIterable) = 3; -const ecs_entity_t ecs_id(EcsPoly) = 4; - -const ecs_entity_t EcsQuery = 5; -const ecs_entity_t EcsObserver = 7; - -/* System module component ids */ -const ecs_entity_t EcsSystem = 10; -const ecs_entity_t ecs_id(EcsTickSource) = 11; -/** Timer module component ids */ -const ecs_entity_t ecs_id(EcsTimer) = 13; -const ecs_entity_t ecs_id(EcsRateFilter) = 14; - -/** 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; -const ecs_entity_t ecs_id(EcsUnit) = 24; -const ecs_entity_t ecs_id(EcsUnitPrefix) = 25; - -/* 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 EcsFlecsInternals = ECS_HI_COMPONENT_ID + 3; -const ecs_entity_t EcsModule = ECS_HI_COMPONENT_ID + 4; -const ecs_entity_t EcsPrivate = ECS_HI_COMPONENT_ID + 5; -const ecs_entity_t EcsPrefab = ECS_HI_COMPONENT_ID + 6; -const ecs_entity_t EcsDisabled = ECS_HI_COMPONENT_ID + 7; - -const ecs_entity_t EcsSlotOf = ECS_HI_COMPONENT_ID + 8; -const ecs_entity_t EcsFlag = ECS_HI_COMPONENT_ID + 9; - -/* Relationship properties */ -const ecs_entity_t EcsWildcard = ECS_HI_COMPONENT_ID + 10; -const ecs_entity_t EcsAny = ECS_HI_COMPONENT_ID + 11; -const ecs_entity_t EcsThis = ECS_HI_COMPONENT_ID + 12; -const ecs_entity_t EcsVariable = ECS_HI_COMPONENT_ID + 13; +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); + } +} -const ecs_entity_t EcsTransitive = ECS_HI_COMPONENT_ID + 14; -const ecs_entity_t EcsReflexive = ECS_HI_COMPONENT_ID + 15; -const ecs_entity_t EcsSymmetric = ECS_HI_COMPONENT_ID + 16; -const ecs_entity_t EcsFinal = ECS_HI_COMPONENT_ID + 17; -const ecs_entity_t EcsDontInherit = ECS_HI_COMPONENT_ID + 18; -const ecs_entity_t EcsTag = ECS_HI_COMPONENT_ID + 19; -const ecs_entity_t EcsUnion = ECS_HI_COMPONENT_ID + 20; -const ecs_entity_t EcsExclusive = ECS_HI_COMPONENT_ID + 21; -const ecs_entity_t EcsAcyclic = ECS_HI_COMPONENT_ID + 22; -const ecs_entity_t EcsWith = ECS_HI_COMPONENT_ID + 23; -const ecs_entity_t EcsOneOf = ECS_HI_COMPONENT_ID + 24; +void flecs_bitset_init( + ecs_bitset_t* bs) +{ + bs->size = 0; + bs->count = 0; + bs->data = NULL; +} -/* Builtin relationships */ -const ecs_entity_t EcsChildOf = ECS_HI_COMPONENT_ID + 25; -const ecs_entity_t EcsIsA = ECS_HI_COMPONENT_ID + 26; -const ecs_entity_t EcsDependsOn = ECS_HI_COMPONENT_ID + 27; +void flecs_bitset_ensure( + ecs_bitset_t *bs, + int32_t count) +{ + if (count > bs->count) { + bs->count = count; + ensure(bs, count); + } +} -/* Identifier tags */ -const ecs_entity_t EcsName = ECS_HI_COMPONENT_ID + 30; -const ecs_entity_t EcsSymbol = ECS_HI_COMPONENT_ID + 31; -const ecs_entity_t EcsAlias = ECS_HI_COMPONENT_ID + 32; +void flecs_bitset_fini( + ecs_bitset_t *bs) +{ + ecs_os_free(bs->data); + bs->data = NULL; + bs->count = 0; +} -/* Events */ -const ecs_entity_t EcsOnAdd = ECS_HI_COMPONENT_ID + 33; -const ecs_entity_t EcsOnRemove = ECS_HI_COMPONENT_ID + 34; -const ecs_entity_t EcsOnSet = ECS_HI_COMPONENT_ID + 35; -const ecs_entity_t EcsUnSet = ECS_HI_COMPONENT_ID + 36; -const ecs_entity_t EcsOnDelete = ECS_HI_COMPONENT_ID + 37; -const ecs_entity_t EcsOnCreateTable = ECS_HI_COMPONENT_ID + 38; -const ecs_entity_t EcsOnDeleteTable = ECS_HI_COMPONENT_ID + 39; -const ecs_entity_t EcsOnTableEmpty = ECS_HI_COMPONENT_ID + 40; -const ecs_entity_t EcsOnTableFill = ECS_HI_COMPONENT_ID + 41; -const ecs_entity_t EcsOnCreateTrigger = ECS_HI_COMPONENT_ID + 42; -const ecs_entity_t EcsOnDeleteTrigger = ECS_HI_COMPONENT_ID + 43; -const ecs_entity_t EcsOnDeleteObservable = ECS_HI_COMPONENT_ID + 44; -const ecs_entity_t EcsOnComponentHooks = ECS_HI_COMPONENT_ID + 45; -const ecs_entity_t EcsOnDeleteTarget = ECS_HI_COMPONENT_ID + 46; +void flecs_bitset_addn( + ecs_bitset_t *bs, + int32_t count) +{ + int32_t elem = bs->count += count; + ensure(bs, elem); +} -/* 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 EcsPanic = ECS_HI_COMPONENT_ID + 52; +void flecs_bitset_set( + ecs_bitset_t *bs, + int32_t elem, + bool value) +{ + ecs_check(elem < bs->count, ECS_INVALID_PARAMETER, NULL); + uint32_t hi = ((uint32_t)elem) >> 6; + uint32_t lo = ((uint32_t)elem) & 0x3F; + uint64_t v = bs->data[hi]; + bs->data[hi] = (v & ~((uint64_t)1 << lo)) | ((uint64_t)value << lo); +error: + return; +} -/* Misc */ -const ecs_entity_t EcsDefaultChildComponent = ECS_HI_COMPONENT_ID + 55; +bool flecs_bitset_get( + const ecs_bitset_t *bs, + int32_t elem) +{ + ecs_check(elem < bs->count, ECS_INVALID_PARAMETER, NULL); + return !!(bs->data[elem >> 6] & ((uint64_t)1 << ((uint64_t)elem & 0x3F))); +error: + return false; +} -/* Systems */ -const ecs_entity_t EcsMonitor = ECS_HI_COMPONENT_ID + 61; -const ecs_entity_t EcsEmpty = ECS_HI_COMPONENT_ID + 63; -const ecs_entity_t ecs_id(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; +int32_t flecs_bitset_count( + const ecs_bitset_t *bs) +{ + return bs->count; +} -const ecs_entity_t EcsPhase = ECS_HI_COMPONENT_ID + 75; +void flecs_bitset_remove( + ecs_bitset_t *bs, + int32_t elem) +{ + 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; +} -/* Meta primitive components (don't use low ids to save id space) */ -const ecs_entity_t ecs_id(ecs_bool_t) = ECS_HI_COMPONENT_ID + 80; -const ecs_entity_t ecs_id(ecs_char_t) = ECS_HI_COMPONENT_ID + 81; -const ecs_entity_t ecs_id(ecs_byte_t) = ECS_HI_COMPONENT_ID + 82; -const ecs_entity_t ecs_id(ecs_u8_t) = ECS_HI_COMPONENT_ID + 83; -const ecs_entity_t ecs_id(ecs_u16_t) = ECS_HI_COMPONENT_ID + 84; -const ecs_entity_t ecs_id(ecs_u32_t) = ECS_HI_COMPONENT_ID + 85; -const ecs_entity_t ecs_id(ecs_u64_t) = ECS_HI_COMPONENT_ID + 86; -const ecs_entity_t ecs_id(ecs_uptr_t) = ECS_HI_COMPONENT_ID + 87; -const ecs_entity_t ecs_id(ecs_i8_t) = ECS_HI_COMPONENT_ID + 88; -const ecs_entity_t ecs_id(ecs_i16_t) = ECS_HI_COMPONENT_ID + 89; -const ecs_entity_t ecs_id(ecs_i32_t) = ECS_HI_COMPONENT_ID + 90; -const ecs_entity_t ecs_id(ecs_i64_t) = ECS_HI_COMPONENT_ID + 91; -const ecs_entity_t ecs_id(ecs_iptr_t) = ECS_HI_COMPONENT_ID + 92; -const ecs_entity_t ecs_id(ecs_f32_t) = ECS_HI_COMPONENT_ID + 93; -const ecs_entity_t ecs_id(ecs_f64_t) = ECS_HI_COMPONENT_ID + 94; -const ecs_entity_t ecs_id(ecs_string_t) = ECS_HI_COMPONENT_ID + 95; -const ecs_entity_t ecs_id(ecs_entity_t) = ECS_HI_COMPONENT_ID + 96; -const ecs_entity_t EcsConstant = ECS_HI_COMPONENT_ID + 97; -const ecs_entity_t EcsQuantity = ECS_HI_COMPONENT_ID + 98; +void flecs_bitset_swap( + ecs_bitset_t *bs, + int32_t elem_a, + int32_t elem_b) +{ + ecs_check(elem_a < bs->count, ECS_INVALID_PARAMETER, NULL); + ecs_check(elem_b < bs->count, ECS_INVALID_PARAMETER, NULL); -/* 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; -const ecs_entity_t EcsDocColor = ECS_HI_COMPONENT_ID + 104; + 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; +} -/* REST module components */ -const ecs_entity_t ecs_id(EcsRest) = ECS_HI_COMPONENT_ID + 105; +#include -/* Default lookup path */ -static ecs_entity_t ecs_default_lookup_path[2] = { 0, 0 }; +/** + * stm32tpl -- STM32 C++ Template Peripheral Library + * Visit https://github.com/antongus/stm32tpl for new versions + * + * Copyright (c) 2011-2020 Anton B. Gusev aka AHTOXA + */ -/* -- Private functions -- */ +#define MAX_PRECISION (10) +#define EXP_THRESHOLD (3) +#define INT64_MAX_F ((double)INT64_MAX) -const ecs_stage_t* flecs_stage_from_readonly_world( - const ecs_world_t *world) +static const double rounders[MAX_PRECISION + 1] = { - ecs_assert(ecs_poly_is(world, ecs_world_t) || - ecs_poly_is(world, ecs_stage_t), - ECS_INTERNAL_ERROR, - NULL); - - if (ecs_poly_is(world, ecs_world_t)) { - return &world->stages[0]; - - } else if (ecs_poly_is(world, ecs_stage_t)) { - return (ecs_stage_t*)world; - } - - return NULL; -} + 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 +}; -ecs_stage_t *flecs_stage_from_world( - ecs_world_t **world_ptr) +static +char* strbuf_itoa( + char *buf, + int64_t v) { - ecs_world_t *world = *world_ptr; + char *ptr = buf; + char * p1; + char c; - ecs_assert(ecs_poly_is(world, ecs_world_t) || - ecs_poly_is(world, ecs_stage_t), - ECS_INTERNAL_ERROR, - NULL); + if (!v) { + *ptr++ = '0'; + } else { + char *p = ptr; + while (v) { + *p++ = (char)('0' + v % 10); + v /= 10; + } - if (ecs_poly_is(world, ecs_world_t)) { - ecs_assert(!(world->flags & EcsWorldReadonly) || (ecs_get_stage_count(world) <= 1), - ECS_INVALID_OPERATION, NULL); - return &world->stages[0]; + p1 = p; - } 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; + while (p > ptr) { + c = *--p; + *p = *ptr; + *ptr++ = c; + } + ptr = p1; + } + return ptr; } -ecs_world_t* flecs_suspend_readonly( - const ecs_world_t *stage_world, - ecs_suspend_readonly_state_t *state) +static +int ecs_strbuf_ftoa( + ecs_strbuf_t *out, + double f, + int precision, + char nan_delim) { - ecs_assert(stage_world != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(state != NULL, ECS_INTERNAL_ERROR, NULL); - - ecs_world_t *world = (ecs_world_t*)ecs_get_world(stage_world); - ecs_poly_assert(world, ecs_world_t); + char buf[64]; + char * ptr = buf; + char c; + int64_t intPart; + int64_t exp = 0; - bool is_readonly = ECS_BIT_IS_SET(world->flags, EcsWorldReadonly); - bool is_deferred = ecs_is_deferred(world); + if (isnan(f)) { + if (nan_delim) { + ecs_strbuf_appendch(out, nan_delim); + ecs_strbuf_appendstr(out, "NaN"); + return ecs_strbuf_appendch(out, nan_delim); + } else { + return ecs_strbuf_appendstr(out, "NaN"); + } + } + if (isinf(f)) { + if (nan_delim) { + ecs_strbuf_appendch(out, nan_delim); + ecs_strbuf_appendstr(out, "Inf"); + return ecs_strbuf_appendch(out, nan_delim); + } else { + return ecs_strbuf_appendstr(out, "Inf"); + } + } - if (!is_readonly && !is_deferred) { - state->is_readonly = false; - state->is_deferred = false; - return world; + if (precision > MAX_PRECISION) { + precision = MAX_PRECISION; } - ecs_dbg_3("suspending readonly mode"); + if (f < 0) { + f = -f; + *ptr++ = '-'; + } - /* Cannot suspend when running with multiple threads */ - ecs_assert(ecs_get_stage_count(world) <= 1, - ECS_INVALID_WHILE_READONLY, 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; + } - state->is_readonly = is_readonly; - state->is_deferred = is_deferred; + if (precision) { + f += rounders[precision]; + } - /* Silence readonly checks */ - world->flags &= ~EcsWorldReadonly; + /* Make sure that number can be represented as 64bit int, increase exp */ + while (f > INT64_MAX_F) { + f /= 1000 * 1000 * 1000; + exp += 9; + } - /* Hack around safety checks (this ought to look ugly) */ - ecs_world_t *temp_world = world; - ecs_stage_t *stage = flecs_stage_from_world(&temp_world); - state->defer_count = stage->defer; - state->defer_queue = stage->defer_queue; - state->defer_stack = stage->defer_stack; - flecs_stack_init(&stage->defer_stack); - state->scope = stage->scope; - state->with = stage->with; - stage->defer = 0; - stage->defer_queue = NULL; - - return world; -} + intPart = (int64_t)f; + f -= (double)intPart; -void flecs_resume_readonly( - ecs_world_t *world, - ecs_suspend_readonly_state_t *state) -{ - ecs_poly_assert(world, ecs_world_t); - ecs_assert(state != NULL, ECS_INTERNAL_ERROR, NULL); - - ecs_world_t *temp_world = world; - ecs_stage_t *stage = flecs_stage_from_world(&temp_world); + ptr = strbuf_itoa(ptr, intPart); - if (state->is_readonly || state->is_deferred) { - ecs_dbg_3("resuming readonly mode"); - - ecs_run_aperiodic(world, 0); + if (precision) { + *ptr++ = '.'; + while (precision--) { + f *= 10.0; + c = (char)f; + *ptr++ = (char)('0' + c); + f -= c; + } + } + *ptr = 0; - /* Restore readonly state / defer count */ - ECS_BIT_COND(world->flags, EcsWorldReadonly, state->is_readonly); - stage->defer = state->defer_count; - if (stage->defer_queue) { - ecs_assert(ecs_vector_count(stage->defer_queue) == 0, - ECS_INTERNAL_ERROR, NULL); - ecs_vector_free(stage->defer_queue); - } - stage->defer_queue = state->defer_queue; - flecs_stack_fini(&stage->defer_stack); - stage->defer_stack = state->defer_stack; - stage->scope = state->scope; - stage->with = state->with; + /* Remove trailing 0s */ + while ((&ptr[-1] != buf) && (ptr[-1] == '0')) { + ptr[-1] = '\0'; + ptr --; + } + if (ptr != buf && ptr[-1] == '.') { + ptr[-1] = '\0'; + ptr --; } -} -/* 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) -{ - ecs_poly_assert(world, ecs_world_t); + /* If 0s before . exceed threshold, convert to exponent to save space + * without losing precision. */ + char *cur = ptr; + while ((&cur[-1] != buf) && (cur[-1] == '0')) { + cur --; + } - if (!world->monitors.is_dirty) { - return; + if (exp || ((ptr - cur) > EXP_THRESHOLD)) { + cur[0] = '\0'; + exp += (ptr - cur); + ptr = cur; } - world->monitors.is_dirty = false; + if (exp) { + char *p1 = &buf[1]; + if (nan_delim) { + ecs_os_memmove(buf + 1, buf, 1 + (ptr - buf)); + buf[0] = nan_delim; + p1 ++; + } - ecs_map_iter_t it = ecs_map_iter(&world->monitors.monitors); - ecs_monitor_t *m; - while ((m = ecs_map_next(&it, ecs_monitor_t, NULL))) { - if (!m->is_dirty) { - continue; + /* Make sure that exp starts after first character */ + c = p1[0]; + + if (c) { + p1[0] = '.'; + do { + char t = (++p1)[0]; + p1[0] = c; + c = t; + exp ++; + } while (c); + ptr = p1 + 1; + } else { + ptr = p1; } - m->is_dirty = false; - ecs_vector_each(m->queries, ecs_query_t*, q_ptr, { - flecs_query_notify(world, *q_ptr, &(ecs_query_event_t) { - .kind = EcsQueryTableRematch - }); - }); + ptr[0] = 'e'; + ptr = strbuf_itoa(ptr + 1, exp); + + if (nan_delim) { + ptr[0] = nan_delim; + ptr ++; + } + + ptr[0] = '\0'; } + + return ecs_strbuf_appendstrn(out, buf, (int32_t)(ptr - buf)); } -void flecs_monitor_mark_dirty( - ecs_world_t *world, - ecs_entity_t id) +/* Add an extra element to the buffer */ +static +void ecs_strbuf_grow( + ecs_strbuf_t *b) { - ecs_map_t *monitors = &world->monitors.monitors; - - /* Only flag if there are actually monitors registered, so that we - * don't waste cycles evaluating monitors if there's no interest */ - if (ecs_map_is_initialized(monitors)) { - ecs_monitor_t *m = ecs_map_get(monitors, ecs_monitor_t, id); - if (m) { - m->is_dirty = true; - world->monitors.is_dirty = true; - } - } + /* 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; } -void flecs_monitor_register( - ecs_world_t *world, - ecs_entity_t id, - ecs_query_t *query) +/* Add an extra dynamic element */ +static +void ecs_strbuf_grow_str( + ecs_strbuf_t *b, + char *str, + char *alloc_str, + int32_t size) { - 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_map_t *monitors = &world->monitors.monitors; - - ecs_map_init_if(monitors, ecs_monitor_t, 1); - - ecs_monitor_t *m = ecs_map_ensure(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; + /* 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; } -void flecs_monitor_unregister( - ecs_world_t *world, - ecs_entity_t id, - ecs_query_t *query) +static +char* ecs_strbuf_ptr( + ecs_strbuf_t *b) { - 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_map_t *monitors = &world->monitors.monitors; - - if (!ecs_map_is_initialized(monitors)) { - return; - } - - ecs_monitor_t *m = ecs_map_get(monitors, ecs_monitor_t, id); - if (!m) { - return; - } - - int32_t i, count = ecs_vector_count(m->queries); - ecs_query_t **queries = ecs_vector_first(m->queries, ecs_query_t*); - for (i = 0; i < count; i ++) { - if (queries[i] == query) { - ecs_vector_remove(m->queries, ecs_query_t*, i); - count --; - break; - } + if (b->buf) { + return &b->buf[b->current->pos]; + } else { + return &b->current->buf[b->current->pos]; } +} - if (!count) { - ecs_vector_free(m->queries); - ecs_map_remove(monitors, id); +/* Compute the amount of space left in the current element */ +static +int32_t ecs_strbuf_memLeftInCurrentElement( + ecs_strbuf_t *b) +{ + if (b->current->buffer_embedded) { + return ECS_STRBUF_ELEMENT_SIZE - b->current->pos; + } else { + return 0; } +} - if (!ecs_map_count(monitors)) { - ecs_map_fini(monitors); +/* Compute the amount of space left */ +static +int32_t ecs_strbuf_memLeft( + ecs_strbuf_t *b) +{ + if (b->max) { + return b->max - b->size - b->current->pos; + } else { + return INT_MAX; } } static -void init_store( - ecs_world_t *world) +void ecs_strbuf_init( + ecs_strbuf_t *b) { - ecs_os_memset(&world->store, 0, ECS_SIZEOF(ecs_store_t)); - - /* Initialize entity index */ - flecs_sparse_init(&world->store.entity_index, ecs_record_t); - flecs_sparse_set_id_source(&world->store.entity_index, - &world->info.last_id); - - /* Initialize root table */ - flecs_sparse_init(&world->store.tables, ecs_table_t); - - /* Initialize table map */ - flecs_table_hashmap_init(&world->store.table_map); - - /* Initialize one root table per stage */ - flecs_init_root_table(world); + /* 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 -void clean_tables( - ecs_world_t *world) +bool vappend( + ecs_strbuf_t *b, + const char* str, + va_list args) { - int32_t i, count = flecs_sparse_count(&world->store.tables); - - /* Ensure that first table in sparse set has id 0. This is a dummy table - * that only exists so that there is no table with id 0 */ - ecs_table_t *first = flecs_sparse_get_dense(&world->store.tables, - ecs_table_t, 0); - ecs_assert(first->id == 0, ECS_INTERNAL_ERROR, NULL); - (void)first; + bool result = true; + va_list arg_cpy; - for (i = 1; i < count; i ++) { - ecs_table_t *t = flecs_sparse_get_dense(&world->store.tables, - ecs_table_t, i); - flecs_table_release(world, t); + if (!str) { + return result; } - /* Free table types separately so that if application destructors rely on - * a type it's still valid. */ - for (i = 1; i < count; i ++) { - ecs_table_t *t = flecs_sparse_get_dense(&world->store.tables, - ecs_table_t, i); - flecs_table_free_type(t); - } + ecs_strbuf_init(b); - /* Clear the root table */ - if (count) { - flecs_table_reset(world, &world->store.root); - } -} + int32_t memLeftInElement = ecs_strbuf_memLeftInCurrentElement(b); + int32_t memLeft = ecs_strbuf_memLeft(b); -static -void fini_roots(ecs_world_t *world) { - ecs_id_record_t *idr = flecs_id_record_get(world, ecs_pair(EcsChildOf, 0)); + if (!memLeft) { + return false; + } - ecs_run_aperiodic(world, EcsAperiodicEmptyTables); + /* 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; - ecs_table_cache_iter_t it; - bool has_roots = flecs_table_cache_iter(&idr->cache, &it); - ecs_assert(has_roots == true, ECS_INTERNAL_ERROR, NULL); - (void)has_roots; + va_copy(arg_cpy, args); + memRequired = vsnprintf( + ecs_strbuf_ptr(b), (size_t)(max_copy + 1), str, args); - /* Delete root entities that are not modules. This prioritizes deleting - * regular entities first, which reduces the chance of components getting - * destructed in random order because it got deleted before entities, - * thereby bypassing the OnDeleteTarget policy. */ - ecs_defer_begin(world); + ecs_assert(memRequired != -1, ECS_INTERNAL_ERROR, NULL); - const ecs_table_record_t *tr; - while ((tr = flecs_table_cache_next(&it, ecs_table_record_t))) { - ecs_table_t *table = tr->hdr.table; - if (table->flags & EcsTableHasBuiltins) { - continue; /* Filter out modules */ - } + 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); - int32_t i, count = table->data.entities.count; - ecs_entity_t *entities = table->data.entities.array; + /* Copy entire string to new buffer */ + ecs_os_vsprintf(ecs_strbuf_ptr(b), str, arg_cpy); - /* Count backwards so that we're always deleting the last entity in the - * table which reduces moving components around */ - for (i = count - 1; i >= 0; i --) { - ecs_record_t *r = flecs_entities_get(world, entities[i]); - ecs_flags32_t flags = ECS_RECORD_TO_ROW_FLAGS(r->row); - if (!(flags & EcsEntityObservedTarget)) { - continue; /* Filter out entities that aren't objects */ - } + /* 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_delete(world, entities[i]); + /* 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_defer_end(world); -} - -static -void fini_store(ecs_world_t *world) { - clean_tables(world); - flecs_sparse_fini(&world->store.tables); - flecs_table_release(world, &world->store.root); - flecs_sparse_clear(&world->store.entity_index); - flecs_hashmap_fini(&world->store.table_map); - ecs_vector_free(world->store.records); - ecs_vector_free(world->store.marked_ids); + va_end(arg_cpy); - ecs_graph_edge_hdr_t *cur, *next = world->store.first_free; - while ((cur = next)) { - next = cur->next; - ecs_os_free(cur); - } + return ecs_strbuf_memLeft(b) > 0; } -/* Implementation for iterable mixin */ static -bool world_iter_next( - ecs_iter_t *it) +bool appendstr( + ecs_strbuf_t *b, + const char* str, + int n) { - if (ECS_BIT_IS_SET(it->flags, EcsIterIsValid)) { - ECS_BIT_CLEAR(it->flags, EcsIterIsValid); + ecs_strbuf_init(b); + + int32_t memLeftInElement = ecs_strbuf_memLeftInCurrentElement(b); + int32_t memLeft = ecs_strbuf_memLeft(b); + if (memLeft <= 0) { return false; } - 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); - flecs_iter_validate(it); + /* Never write more than what the buffer can store */ + if (n > memLeft) { + n = memLeft; + } - return true; + 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); + + /* 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 { + /* String doesn't fit in a single element, strdup */ + char *remainder = ecs_os_strdup(str + memLeftInElement); + ecs_strbuf_grow_str(b, remainder, remainder, n); + } + } else { + /* Buffer max has been reached */ + return false; + } + + return ecs_strbuf_memLeft(b) > 0; } static -void world_iter_init( - const ecs_world_t *world, - const ecs_poly_t *poly, - ecs_iter_t *iter, - ecs_term_t *filter) +bool appendch( + ecs_strbuf_t *b, + char ch) { - ecs_poly_assert(poly, ecs_world_t); - (void)poly; + ecs_strbuf_init(b); - if (filter) { - iter[0] = ecs_term_iter(world, filter); + int32_t memLeftInElement = ecs_strbuf_memLeftInCurrentElement(b); + int32_t memLeft = ecs_strbuf_memLeft(b); + if (memLeft <= 0) { + return false; + } + + if (memLeftInElement) { + /* Element was large enough to fit string */ + ecs_strbuf_ptr(b)[0] = ch; + b->current->pos ++; } else { - iter[0] = (ecs_iter_t){ - .world = (ecs_world_t*)world, - .real_world = (ecs_world_t*)ecs_get_world(world), - .next = world_iter_next - }; + ecs_strbuf_grow(b); + ecs_strbuf_ptr(b)[0] = ch; + b->current->pos ++; } + + return ecs_strbuf_memLeft(b) > 0; } -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_MONITOR - ecs_trace("FLECS_MONITOR"); - #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_UNITS - ecs_trace("FLECS_UNITS"); - #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(); +bool ecs_strbuf_vappend( + ecs_strbuf_t *b, + const char* fmt, + va_list args) +{ + ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(fmt != NULL, ECS_INVALID_PARAMETER, NULL); + return vappend(b, fmt, args); } -/* -- Public functions -- */ +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); -ecs_world_t *ecs_mini(void) { -#ifdef FLECS_OS_API_IMPL - ecs_set_os_api_impl(); -#endif - ecs_os_init(); + va_list args; + va_start(args, fmt); + bool result = vappend(b, fmt, args); + va_end(args); - ecs_trace("#[bold]bootstrapping world"); - ecs_log_push(); + return result; +} - ecs_trace("tracing enabled, call ecs_log_set_level(-1) to disable"); +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); +} - if (!ecs_os_has_heap()) { - ecs_abort(ECS_MISSING_OS_API, NULL); - } +bool ecs_strbuf_appendch( + ecs_strbuf_t *b, + char ch) +{ + ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); + return appendch(b, ch); +} - if (!ecs_os_has_threading()) { - ecs_trace("threading unavailable, to use threads set OS API first (see examples)"); - } +bool ecs_strbuf_appendflt( + ecs_strbuf_t *b, + double flt, + char nan_delim) +{ + ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); + return ecs_strbuf_ftoa(b, flt, 10, nan_delim); +} - if (!ecs_os_has_time()) { - ecs_trace("time management not available"); - } +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; +} - log_addons(); - -#ifdef FLECS_SANITIZE - ecs_trace("sanitize build, rebuild without FLECS_SANITIZE for (much) " - "improved performance"); -#elif defined(FLECS_DEBUG) - ecs_trace("debug build, rebuild with NDEBUG or FLECS_NDEBUG for improved " - "performance"); -#else - ecs_trace("#[green]release#[reset] build"); -#endif +bool ecs_strbuf_appendstr_zerocpy_const( + ecs_strbuf_t *b, + const char* str) +{ + 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; +} -#ifdef __clang__ - ecs_trace("compiled with clang %s", __clang_version__); -#elif defined(__GNUC__) - ecs_trace("compiled with gcc %d.%d", __GNUC__, __GNUC_MINOR__); -#elif defined (_MSC_VER) - ecs_trace("compiled with msvc %d", _MSC_VER); -#endif +bool ecs_strbuf_appendstr( + ecs_strbuf_t *b, + const char* str) +{ + ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(str != NULL, ECS_INVALID_PARAMETER, NULL); + return appendstr(b, str, ecs_os_strlen(str)); +} - ecs_world_t *world = ecs_os_calloc_t(ecs_world_t); - ecs_assert(world != NULL, ECS_OUT_OF_MEMORY, NULL); - ecs_poly_init(world, ecs_world_t); +bool ecs_strbuf_mergebuff( + ecs_strbuf_t *dst_buffer, + ecs_strbuf_t *src_buffer) +{ + 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; - world->self = world; - world->type_info = flecs_sparse_new(ecs_type_info_t); - ecs_map_init(&world->id_index, ecs_id_record_t*, ECS_HI_COMPONENT_ID); - flecs_observable_init(&world->observable); - world->iterable.init = world_iter_init; - world->pending_tables = flecs_sparse_new(ecs_table_t*); - world->pending_buffer = flecs_sparse_new(ecs_table_t*); - flecs_name_index_init(&world->aliases); - flecs_name_index_init(&world->symbols); + /* Copy first element as it is inlined in the src buffer */ + ecs_strbuf_appendstrn(dst_buffer, e->buf, e->pos); - world->info.time_scale = 1.0; + while ((e = e->next)) { + dst_buffer->current->next = ecs_os_malloc(sizeof(ecs_strbuf_element)); + *dst_buffer->current->next = *e; + } + } - if (ecs_os_has_time()) { - ecs_os_get_time(&world->world_start_time); + *src_buffer = ECS_STRBUF_INIT; } - ecs_set_stage_count(world, 1); - ecs_default_lookup_path[0] = EcsFlecsCore; - ecs_set_lookup_path(world, ecs_default_lookup_path); - init_store(world); + return true; +} - flecs_bootstrap(world); +char* ecs_strbuf_get( + ecs_strbuf_t *b) +{ + ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_trace("world ready!"); - ecs_log_pop(); + 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; - return world; + ecs_strbuf_element *e = (ecs_strbuf_element*)&b->firstElement; + + result = ecs_os_malloc(len); + char* ptr = result; + + 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)); + + result[len - 1] = '\0'; + b->length = len; + } + } else { + result = NULL; + } + + b->elementCount = 0; + + b->content = result; + + return result; } -ecs_world_t *ecs_init(void) { - ecs_world_t *world = ecs_mini(); +char *ecs_strbuf_get_small( + ecs_strbuf_t *b) +{ + ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); -#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 -#ifdef FLECS_UNITS - ecs_trace("#[green]module#[reset] flecs.units is not automatically imported"); -#endif - ecs_trace("addons imported!"); - ecs_log_pop(); -#endif - return world; + 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; } -#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;\ - }\ - }\ - }\ +void ecs_strbuf_reset( + ecs_strbuf_t *b) +{ + ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); + + 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)); } -ecs_world_t* ecs_init_w_args( - int argc, - char *argv[]) + *b = ECS_STRBUF_INIT; +} + +void ecs_strbuf_list_push( + ecs_strbuf_t *b, + const char *list_open, + const char *separator) { - ecs_world_t *world = ecs_init(); + 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); - (void)argc; - (void) argv; + b->list_sp ++; + b->list_stack[b->list_sp].count = 0; + b->list_stack[b->list_sp].separator = separator; -#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}); + if (list_open) { + ecs_strbuf_appendstr(b, list_open); } -#endif +} - return world; +void ecs_strbuf_list_pop( + ecs_strbuf_t *b, + const char *list_close) +{ + ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(list_close != NULL, ECS_INVALID_PARAMETER, NULL); + + b->list_sp --; + + if (list_close) { + ecs_strbuf_appendstr(b, list_close); + } } -void ecs_quit( - ecs_world_t *world) +void ecs_strbuf_list_next( + ecs_strbuf_t *b) { - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - flecs_stage_from_world(&world); - world->flags |= EcsWorldQuit; -error: - return; + ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); + + 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 ++; } -bool ecs_should_quit( - const ecs_world_t *world) +bool ecs_strbuf_list_append( + ecs_strbuf_t *b, + const char *fmt, + ...) { - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - world = ecs_get_world(world); - return ECS_BIT_IS_SET(world->flags, EcsWorldQuit); -error: - return true; + ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(fmt != NULL, ECS_INVALID_PARAMETER, NULL); + + ecs_strbuf_list_next(b); + + va_list args; + va_start(args, fmt); + bool result = vappend(b, fmt, args); + va_end(args); + + return result; } -void flecs_notify_tables( - ecs_world_t *world, - ecs_id_t id, - ecs_table_event_t *event) +bool ecs_strbuf_list_appendstr( + ecs_strbuf_t *b, + const char *str) { - ecs_poly_assert(world, ecs_world_t); + ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(str != NULL, ECS_INVALID_PARAMETER, 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); - } + ecs_strbuf_list_next(b); + return ecs_strbuf_appendstr(b, str); +} - /* If id is specified, only broadcast to tables with id */ +int32_t ecs_strbuf_written( + const ecs_strbuf_t *b) +{ + ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); + if (b->current) { + return b->size + b->current->pos; } else { - ecs_id_record_t *idr = flecs_id_record_get(world, id); - if (!idr) { - return; - } + return 0; + } +} - ecs_table_cache_iter_t it; - const ecs_table_record_t *tr; +#include - flecs_table_cache_all_iter(&idr->cache, &it); - while ((tr = flecs_table_cache_next(&it, ecs_table_record_t))) { - flecs_table_notify(world, tr->hdr.table, event); - } +/* 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.2f) +#define KEY_SIZE (ECS_SIZEOF(ecs_map_key_t)) +#define GET_ELEM(array, elem_size, index) \ + ECS_OFFSET(array, (elem_size) * (index)) + +static +ecs_block_allocator_chunk_header_t *ecs_balloc_block( + ecs_block_allocator_t *allocator) +{ + ecs_block_allocator_chunk_header_t *first_chunk = + ecs_os_malloc(allocator->block_size); + ecs_block_allocator_block_t *block = + ecs_os_malloc_t(ecs_block_allocator_block_t); + + block->memory = first_chunk; + if (!allocator->block_tail) { + ecs_assert(!allocator->block_head, ECS_INTERNAL_ERROR, 0); + block->next = NULL; + allocator->block_head = block; + allocator->block_tail = block; + } else { + block->next = NULL; + allocator->block_tail->next = block; + allocator->block_tail = block; + } + + ecs_block_allocator_chunk_header_t *chunk = first_chunk; + int32_t i, end; + for (i = 0, end = allocator->chunks_per_block - 1; i < end; ++i) { + chunk->next = ECS_OFFSET(chunk, allocator->chunk_size); + chunk = chunk->next; } + + chunk->next = NULL; + return first_chunk; } -void ecs_default_ctor( - void *ptr, - int32_t count, - const ecs_type_info_t *ti) +static +void *ecs_balloc( + ecs_block_allocator_t *allocator) { - ecs_os_memset(ptr, 0, ti->size * count); + if (!allocator->head) { + allocator->head = ecs_balloc_block(allocator); + } + + void *result = allocator->head; + allocator->head = allocator->head->next; + return result; } -static -void default_copy_ctor(void *dst_ptr, const void *src_ptr, - int32_t count, const ecs_type_info_t *ti) +static +void ecs_bfree( + ecs_block_allocator_t *allocator, + void *memory) { - const ecs_type_hooks_t *cl = &ti->hooks; - cl->ctor(dst_ptr, count, ti); - cl->copy(dst_ptr, src_ptr, count, ti); + ecs_block_allocator_chunk_header_t *chunk = memory; + chunk->next = allocator->head; + allocator->head = chunk; } static -void default_move_ctor(void *dst_ptr, void *src_ptr, - int32_t count, const ecs_type_info_t *ti) -{ - const ecs_type_hooks_t *cl = &ti->hooks; - cl->ctor(dst_ptr, count, ti); - cl->move(dst_ptr, src_ptr, count, ti); +uint8_t ecs_log2(uint32_t v) { + static const uint8_t log2table[32] = + {0, 9, 1, 10, 13, 21, 2, 29, 11, 14, 16, 18, 22, 25, 3, 30, + 8, 12, 20, 28, 15, 17, 24, 7, 19, 27, 23, 6, 26, 5, 4, 31}; + + v |= v >> 1; + v |= v >> 2; + v |= v >> 4; + v |= v >> 8; + v |= v >> 16; + return log2table[(uint32_t)(v * 0x07C4ACDDU) >> 27]; } +/* Get bucket count for number of elements */ static -void default_ctor_w_move_w_dtor(void *dst_ptr, void *src_ptr, - int32_t count, const ecs_type_info_t *ti) +int32_t get_bucket_count( + int32_t element_count) { - const ecs_type_hooks_t *cl = &ti->hooks; - cl->ctor(dst_ptr, count, ti); - cl->move(dst_ptr, src_ptr, count, ti); - cl->dtor(src_ptr, count, ti); + return flecs_next_pow_of_2((int32_t)((float)element_count * LOAD_FACTOR)); } +/* Get bucket shift amount for a given bucket count */ static -void default_move_ctor_w_dtor(void *dst_ptr, void *src_ptr, - int32_t count, const ecs_type_info_t *ti) +uint8_t get_bucket_shift ( + int32_t bucket_count) { - const ecs_type_hooks_t *cl = &ti->hooks; - cl->move_ctor(dst_ptr, src_ptr, count, ti); - cl->dtor(src_ptr, count, ti); + return (uint8_t)(64u - ecs_log2((uint32_t)bucket_count)); } +/* Get bucket index for provided map key */ static -void default_move(void *dst_ptr, void *src_ptr, - int32_t count, const ecs_type_info_t *ti) +int32_t get_bucket_index( + uint16_t bucket_shift, + ecs_map_key_t key) { - const ecs_type_hooks_t *cl = &ti->hooks; - cl->move(dst_ptr, src_ptr, count, ti); + ecs_assert(bucket_shift != 0, ECS_INTERNAL_ERROR, NULL); + return (int32_t)((11400714819323198485ull * key) >> bucket_shift); } +/* Get bucket for key */ static -void default_dtor(void *dst_ptr, void *src_ptr, - int32_t count, const ecs_type_info_t *ti) +ecs_bucket_t* get_bucket( + const ecs_map_t *map, + ecs_map_key_t key) { - /* 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. */ - const ecs_type_hooks_t *cl = &ti->hooks; - cl->dtor(dst_ptr, count, ti); - ecs_os_memcpy(dst_ptr, src_ptr, flecs_uto(ecs_size_t, ti->size) * count); + ecs_assert(map->bucket_shift == get_bucket_shift(map->bucket_count), + ECS_INTERNAL_ERROR, NULL); + int32_t bucket_id = get_bucket_index(map->bucket_shift, key); + ecs_assert(bucket_id < map->bucket_count, ECS_INTERNAL_ERROR, NULL); + return &map->buckets[bucket_id]; } +/* Ensure that map has at least new_count buckets */ static -void default_move_w_dtor(void *dst_ptr, void *src_ptr, - int32_t count, const ecs_type_info_t *ti) +void ensure_buckets( + ecs_map_t *map, + int32_t new_count) { - /* 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. */ - const ecs_type_hooks_t *cl = &ti->hooks; - cl->move(dst_ptr, src_ptr, count, ti); - cl->dtor(src_ptr, count, ti); -} + int32_t bucket_count = map->bucket_count; + new_count = flecs_next_pow_of_2(new_count); + if (new_count < 2) { + new_count = 2; + } -void ecs_set_hooks_id( - ecs_world_t *world, - ecs_entity_t component, - const ecs_type_hooks_t *h) -{ - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + if (new_count && new_count > bucket_count) { + map->buckets = ecs_os_realloc_n(map->buckets, ecs_bucket_t, new_count); + map->buckets_end = ECS_ELEM_T(map->buckets, ecs_bucket_t, new_count); - flecs_stage_from_world(&world); + map->bucket_count = new_count; + map->bucket_shift = get_bucket_shift(new_count); + ecs_os_memset_n(ECS_ELEM_T(map->buckets, ecs_bucket_t, bucket_count), + 0, ecs_bucket_t, (new_count - bucket_count)); + } +} - /* Ensure that no tables have yet been created for the component */ - ecs_assert( ecs_id_in_use(world, component) == false, - ECS_ALREADY_IN_USE, ecs_get_name(world, component)); - ecs_assert( ecs_id_in_use(world, ecs_pair(component, EcsWildcard)) == false, - ECS_ALREADY_IN_USE, ecs_get_name(world, component)); +/* Free contents of bucket */ +static +void clear_bucket( + ecs_block_allocator_t *allocator, + ecs_bucket_entry_t *bucket) +{ + while(bucket) { + ecs_bucket_entry_t *next = bucket->next; + ecs_bfree(allocator, bucket); + bucket = next; + } +} - ecs_type_info_t *ti = flecs_type_info_ensure(world, component); - ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); +/* Clear all buckets */ +static +void clear_buckets( + ecs_map_t *map) +{ + int32_t i, count = map->bucket_count; + for (i = 0; i < count; i ++) { + clear_bucket(&map->allocator, map->buckets[i].first); + } + ecs_os_free(map->buckets); + map->buckets = NULL; + map->bucket_count = 0; +} - ecs_check(!ti->component || ti->component == component, - ECS_INCONSISTENT_COMPONENT_ACTION, NULL); - - if (!ti->size) { - 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); - - ti->size = component_ptr->size; - ti->alignment = component_ptr->alignment; - } - - if (h->ctor) ti->hooks.ctor = h->ctor; - if (h->dtor) ti->hooks.dtor = h->dtor; - if (h->copy) ti->hooks.copy = h->copy; - if (h->move) ti->hooks.move = h->move; - if (h->copy_ctor) ti->hooks.copy_ctor = h->copy_ctor; - if (h->move_ctor) ti->hooks.move_ctor = h->move_ctor; - if (h->ctor_move_dtor) ti->hooks.ctor_move_dtor = h->ctor_move_dtor; - if (h->move_dtor) ti->hooks.move_dtor = h->move_dtor; - - if (h->on_add) ti->hooks.on_add = h->on_add; - if (h->on_remove) ti->hooks.on_remove = h->on_remove; - if (h->on_set) ti->hooks.on_set = h->on_set; - - if (h->ctx) ti->hooks.ctx = h->ctx; - if (h->binding_ctx) ti->hooks.binding_ctx = h->binding_ctx; - if (h->ctx_free) ti->hooks.ctx_free = h->ctx_free; - if (h->binding_ctx_free) ti->hooks.binding_ctx_free = h->binding_ctx_free; - - /* 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 (!h->ctor && (h->dtor || h->copy || h->move)) { - ti->hooks.ctor = ecs_default_ctor; - } - - /* Set default copy ctor, move ctor and merge */ - if (h->copy && !h->copy_ctor) { - ti->hooks.copy_ctor = default_copy_ctor; - } - - if (h->move && !h->move_ctor) { - ti->hooks.move_ctor = default_move_ctor; - } - - if (!h->ctor_move_dtor) { - if (h->move) { - if (h->dtor) { - if (h->move_ctor) { - /* If an explicit move ctor has been set, use callback - * that uses the move ctor vs. using a ctor+move */ - ti->hooks.ctor_move_dtor = default_move_ctor_w_dtor; - } else { - /* If no explicit move_ctor has been set, use - * combination of ctor + move + dtor */ - ti->hooks.ctor_move_dtor = default_ctor_w_move_w_dtor; - } - } else { - /* If no dtor has been set, this is just a move ctor */ - ti->hooks.ctor_move_dtor = ti->hooks.move_ctor; - } - } +/* Add element to bucket */ +static +void* add_to_bucket( + ecs_block_allocator_t *allocator, + ecs_bucket_t *bucket, + ecs_size_t elem_size, + ecs_map_key_t key, + const void *payload) +{ + ecs_bucket_entry_t *new_entry = ecs_balloc(allocator); + new_entry->key = key; + new_entry->next = bucket->first; + bucket->first = new_entry; + void *new_payload = ECS_OFFSET(&new_entry->key, ECS_SIZEOF(ecs_map_key_t)); + if (elem_size && payload) { + ecs_os_memcpy(new_payload, payload, elem_size); } + return new_payload; +} - if (!h->move_dtor) { - if (h->move) { - if (h->dtor) { - ti->hooks.move_dtor = default_move_w_dtor; - } else { - ti->hooks.move_dtor = default_move; - } - } else { - if (h->dtor) { - ti->hooks.move_dtor = default_dtor; +/* Remove element from bucket */ +static +bool remove_from_bucket( + ecs_block_allocator_t *allocator, + ecs_bucket_t *bucket, + ecs_map_key_t key) +{ + ecs_bucket_entry_t *entry; + for (entry = bucket->first; entry; entry = entry->next) { + if (entry->key == key) { + ecs_bucket_entry_t **next_holder = &bucket->first; + while(*next_holder != entry) { + next_holder = &(*next_holder)->next; } + *next_holder = entry->next; + ecs_bfree(allocator, entry); + return true; } } - -error: - return; + + return false; } -const ecs_type_hooks_t* ecs_get_hooks_id( - ecs_world_t *world, - ecs_entity_t id) +/* Get payload pointer for key from bucket */ +static +void* get_from_bucket( + ecs_bucket_t *bucket, + ecs_map_key_t key) { - const ecs_type_info_t *ti = ecs_get_type_info(world, id); - if (ti) { - return &ti->hooks; + ecs_bucket_entry_t *entry; + for (entry = bucket->first; entry; entry = entry->next) { + if (entry->key == key) { + return ECS_OFFSET(&entry->key, ECS_SIZEOF(ecs_map_key_t)); + } } + return NULL; } -void ecs_atfini( - ecs_world_t *world, - ecs_fini_action_t action, - void *ctx) +/* Grow number of buckets */ +static +void rehash( + ecs_map_t *map, + int32_t bucket_count) { - 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(bucket_count != 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(bucket_count > map->bucket_count, ECS_INTERNAL_ERROR, NULL); + + int32_t old_count = map->bucket_count; + ecs_bucket_t *old_buckets = map->buckets; + + int32_t new_count = flecs_next_pow_of_2(bucket_count); + map->bucket_count = new_count; + map->bucket_shift = get_bucket_shift(new_count); + map->buckets = ecs_os_calloc_n(ecs_bucket_t, new_count); + map->buckets_end = ECS_ELEM_T(map->buckets, ecs_bucket_t, new_count); + + /* Remap old bucket entries to new buckets */ + int32_t index; + for (index = 0; index < old_count; ++index) { + ecs_bucket_entry_t* entry; + for (entry = old_buckets[index].first; entry;) { + ecs_bucket_entry_t* next = entry->next; + int32_t bucket_index = get_bucket_index( + map->bucket_shift, entry->key); + ecs_bucket_t *bucket = &map->buckets[bucket_index]; + entry->next = bucket->first; + bucket->first = entry; + entry = next; + } + } + + ecs_os_free(old_buckets); +} - elem->action = action; - elem->ctx = ctx; -error: - return; +bool ecs_map_is_initialized( + const ecs_map_t *result) +{ + return result != NULL && result->bucket_count != 0; } -void ecs_run_post_frame( - ecs_world_t *world, - ecs_fini_action_t action, - void *ctx) +void _ecs_map_init( + ecs_map_t *result, + ecs_size_t elem_size, + int32_t element_count) { - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(action != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(elem_size < INT16_MAX, ECS_INVALID_PARAMETER, NULL); + + result->count = 0; + result->elem_size = (int16_t)elem_size; - 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); + int32_t entry_size = elem_size + ECS_SIZEOF(ecs_bucket_entry_t); + int32_t balloc_min_chunk_size = ECS_MAX(entry_size, + ECS_SIZEOF(ecs_block_allocator_chunk_header_t)); + uint32_t alignment = 16; + uint32_t alignment_mask = alignment - 1u; - elem->action = action; - elem->ctx = ctx; -error: - return; + /* Align balloc_min_chunk_size up to alignment. */ + result->allocator.chunk_size = (int32_t)(((uint32_t)balloc_min_chunk_size + + alignment_mask) & ~alignment_mask); + result->allocator.chunks_per_block = + ECS_MAX(4096 / result->allocator.chunk_size, 1); + result->allocator.block_size = result->allocator.chunks_per_block * + result->allocator.chunk_size; + result->allocator.head = NULL; + result->allocator.block_head = NULL; + result->allocator.block_tail = NULL; + + ensure_buckets(result, get_bucket_count(element_count)); } -/* Unset data in tables */ -static -void fini_unset_tables( - ecs_world_t *world) +void _ecs_map_init_if( + ecs_map_t *result, + ecs_size_t elem_size, + int32_t element_count) { - 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); + if (ecs_map_is_initialized(result)) { + ecs_assert(elem_size == result->elem_size, ECS_INVALID_PARAMETER, NULL); + return; } + _ecs_map_init(result, elem_size, element_count); } -/* Invoke fini actions */ -static -void fini_actions( - ecs_world_t *world) +ecs_map_t* _ecs_map_new( + ecs_size_t elem_size, + int32_t element_count) { - ecs_vector_each(world->fini_actions, ecs_action_elem_t, elem, { - elem->action(world, elem->ctx); - }); + ecs_map_t *result = ecs_os_calloc_t(ecs_map_t); + ecs_assert(result != NULL, ECS_OUT_OF_MEMORY, NULL); - ecs_vector_free(world->fini_actions); + _ecs_map_init(result, elem_size, element_count); + + return result; } -/* Cleanup remaining type info elements */ -static -void fini_type_info( - ecs_world_t *world) +void ecs_map_fini( + ecs_map_t *map) { - int32_t i, count = flecs_sparse_count(world->type_info); - ecs_sparse_t *type_info = world->type_info; - for (i = 0; i < count; i ++) { - ecs_type_info_t *ti = flecs_sparse_get_dense(type_info, - ecs_type_info_t, i); - flecs_type_info_fini(ti); + ecs_assert(map != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_block_allocator_block_t *block; + for (block = map->allocator.block_head; block; ){ + ecs_block_allocator_block_t *next = block->next; + ecs_os_free(block->memory); + ecs_os_free(block); + block = next; } - flecs_sparse_free(world->type_info); + map->allocator.block_head = NULL; + ecs_os_free(map->buckets); + map->buckets = NULL; + map->buckets_end = NULL; + map->bucket_count = 0; + ecs_assert(!ecs_map_is_initialized(map), ECS_INTERNAL_ERROR, NULL); } -ecs_entity_t flecs_get_oneof( - const ecs_world_t *world, - ecs_entity_t e) +void ecs_map_free( + ecs_map_t *map) { - if (ecs_has_id(world, e, EcsOneOf)) { - return e; - } else { - return ecs_get_target(world, e, EcsOneOf, 0); + if (map) { + ecs_map_fini(map); + ecs_os_free(map); } } -/* The destroyer of worlds */ -int ecs_fini( - ecs_world_t *world) +void* _ecs_map_get( + const ecs_map_t *map, + ecs_size_t elem_size, + ecs_map_key_t key) { - ecs_poly_assert(world, ecs_world_t); - ecs_assert(!(world->flags & EcsWorldReadonly), ECS_INVALID_OPERATION, NULL); - ecs_assert(!(world->flags & EcsWorldFini), ECS_INVALID_OPERATION, NULL); - ecs_assert(world->stages[0].defer == 0, ECS_INVALID_OPERATION, - "call defer_end before destroying world"); + (void)elem_size; - ecs_trace("#[bold]shutting down world"); - ecs_log_push(); + if (!ecs_map_is_initialized(map)) { + return NULL; + } - world->flags |= EcsWorldQuit; + ecs_assert(elem_size == map->elem_size, ECS_INVALID_PARAMETER, NULL); - /* Delete root entities first using regular APIs. This ensures that cleanup - * policies get a chance to execute. */ - ecs_dbg_1("#[bold]cleanup root entities"); - ecs_log_push_1(); - fini_roots(world); - ecs_log_pop_1(); + ecs_bucket_t *bucket = get_bucket(map, key); - world->flags |= EcsWorldFini; + return get_from_bucket(bucket, key); +} - /* Run fini actions (simple callbacks ran when world is deleted) before - * destroying the storage */ - ecs_dbg_1("#[bold]run fini actions"); - ecs_log_push_1(); - fini_actions(world); - ecs_log_pop_1(); +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); - ecs_dbg_1("#[bold]cleanup remaining entities"); - ecs_log_push_1(); + if (ptr_ptr) { + return *(void**)ptr_ptr; + } else { + return NULL; + } +} - /* Operations invoked during UnSet/OnRemove/destructors are deferred and - * will be discarded after world cleanup */ - ecs_defer_begin(world); +bool ecs_map_has( + const ecs_map_t *map, + ecs_map_key_t key) +{ + if (!ecs_map_is_initialized(map)) { + return false; + } - /* Run UnSet/OnRemove actions for components while the store is still - * unmodified by cleanup. */ - fini_unset_tables(world); + ecs_bucket_t *bucket = get_bucket(map, key); - /* This will destroy all entities and components. After this point no more - * user code is executed. */ - fini_store(world); + return get_from_bucket(bucket, key) != NULL; +} - /* 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->stages[0]); - ecs_log_pop_1(); +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); + if (elem_size) { + ecs_assert(result != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_os_memset(result, 0, elem_size); + } + } - /* All queries are cleaned up, so monitors should've been cleaned up too */ - ecs_assert(!ecs_map_is_initialized(&world->monitors.monitors), - ECS_INTERNAL_ERROR, NULL); + return result; +} - ecs_dbg_1("#[bold]cleanup world datastructures"); - ecs_log_push_1(); - flecs_sparse_fini(&world->store.entity_index); - flecs_fini_id_records(world); - fini_type_info(world); - flecs_observable_fini(&world->observable); - flecs_name_index_fini(&world->aliases); - flecs_name_index_fini(&world->symbols); - ecs_set_stage_count(world, 0); - ecs_log_pop_1(); +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); - /* End of the world */ - ecs_poly_free(world, ecs_world_t); - ecs_os_fini(); + ecs_bucket_t *bucket = get_bucket(map, key); - ecs_trace("world destroyed, bye!"); - ecs_log_pop(); + void *elem = get_from_bucket(bucket, key); + if (!elem) { + void *added_data = add_to_bucket( + &map->allocator, 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; - return 0; + if (target_bucket_count > map_bucket_count) { + rehash(map, target_bucket_count); + bucket = get_bucket(map, key); + added_data = get_from_bucket(bucket, key); + ecs_assert(added_data != NULL, ECS_INVALID_PARAMETER, NULL); + } + return added_data; + } else { + if (payload) { + ecs_os_memcpy(elem, payload, elem_size); + } + return elem; + } } -bool ecs_is_fini( - const ecs_world_t *world) +int32_t ecs_map_remove( + ecs_map_t *map, + ecs_map_key_t key) { - ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); - world = ecs_get_world(world); - return ECS_BIT_IS_SET(world->flags, EcsWorldFini); -} + ecs_assert(map != NULL, ECS_INVALID_PARAMETER, NULL); -void ecs_dim( - ecs_world_t *world, - int32_t entity_count) -{ - ecs_poly_assert(world, ecs_world_t); - flecs_entities_set_size(world, entity_count + ECS_HI_COMPONENT_ID); + ecs_bucket_t *bucket = get_bucket(map, key); + if (remove_from_bucket(&map->allocator, bucket, key)) { + return --map->count; + } + + return map->count; } -void flecs_eval_component_monitors( - ecs_world_t *world) +int32_t ecs_map_count( + const ecs_map_t *map) { - ecs_poly_assert(world, ecs_world_t); - flecs_process_pending_tables(world); - eval_component_monitor(world); + return map ? map->count : 0; } -void ecs_measure_frame_time( - ecs_world_t *world, - bool enable) +int32_t ecs_map_bucket_count( + const ecs_map_t *map) { - ecs_poly_assert(world, ecs_world_t); - ecs_check(ecs_os_has_time(), ECS_MISSING_OS_API, NULL); - - if (world->info.target_fps == (ecs_ftime_t)0 || enable) { - ECS_BIT_COND(world->flags, EcsWorldMeasureFrameTime, enable); - } -error: - return; + return map ? map->bucket_count : 0; } -void ecs_measure_system_time( - ecs_world_t *world, - bool enable) +void ecs_map_clear( + ecs_map_t *map) { - ecs_poly_assert(world, ecs_world_t); - ecs_check(ecs_os_has_time(), ECS_MISSING_OS_API, NULL); - ECS_BIT_COND(world->flags, EcsWorldMeasureSystemTime, enable); -error: - return; + ecs_assert(map != NULL, ECS_INVALID_PARAMETER, NULL); + clear_buckets(map); + map->count = 0; + ensure_buckets(map, 2); } -void ecs_set_target_fps( - ecs_world_t *world, - ecs_ftime_t fps) +ecs_map_iter_t ecs_map_iter( + const ecs_map_t *map) { - ecs_poly_assert(world, ecs_world_t); - ecs_check(ecs_os_has_time(), ECS_MISSING_OS_API, NULL); - - ecs_measure_frame_time(world, true); - world->info.target_fps = fps; -error: - return; + return (ecs_map_iter_t){ + .map = map, + .bucket = NULL, + .entry = NULL + }; } -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; -} - -void ecs_set_context( - ecs_world_t *world, - void *context) +void* _ecs_map_next( + ecs_map_iter_t *iter, + ecs_size_t elem_size, + ecs_map_key_t *key_out) { - ecs_poly_assert(world, ecs_world_t); - world->context = context; -} + (void)elem_size; + const ecs_map_t *map = iter->map; + if (!ecs_map_is_initialized(map)) { + return NULL; + } + if (iter->bucket == map->buckets_end) { + return NULL; + } -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->info.last_id, + ecs_assert(!elem_size || elem_size == map->elem_size, ECS_INVALID_PARAMETER, NULL); - if (world->info.last_id < id_start) { - world->info.last_id = id_start - 1; + ecs_bucket_entry_t *entry = NULL; + + if (!iter->bucket) { + for (iter->bucket = map->buckets; + iter->bucket != map->buckets_end; + ++iter->bucket) + { + if (iter->bucket->first) { + entry = iter->bucket->first; + break; + } + } + if (iter->bucket == map->buckets_end) { + return NULL; + } + } else if ((entry = iter->entry) == NULL) { + do { + ++iter->bucket; + if (iter->bucket == map->buckets_end) { + return NULL; + } + } while(!iter->bucket->first); + entry = iter->bucket->first; + } + + if (key_out) { + ecs_assert(entry != NULL, ECS_INTERNAL_ERROR, NULL); + *key_out = entry->key; } - world->info.min_id = id_start; - world->info.max_id = id_end; -error: - return; -} + iter->entry = entry->next; -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; + return ECS_OFFSET(&entry->key, ECS_SIZEOF(ecs_map_key_t)); } -void ecs_set_entity_generation( - ecs_world_t *world, - ecs_entity_t entity_with_generation) +void* _ecs_map_next_ptr( + ecs_map_iter_t *iter, + ecs_map_key_t *key_out) { - flecs_sparse_set_generation( - &world->store.entity_index, entity_with_generation); + void *result = _ecs_map_next(iter, ECS_SIZEOF(void*), key_out); + if (result) { + return *(void**)result; + } else { + return NULL; + } } -const ecs_type_info_t* flecs_type_info_get( - const ecs_world_t *world, - ecs_entity_t component) +void ecs_map_grow( + ecs_map_t *map, + int32_t element_count) { - ecs_poly_assert(world, ecs_world_t); + 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); - ecs_assert(component != 0, ECS_INTERNAL_ERROR, NULL); - ecs_assert(!(component & ECS_ID_FLAGS_MASK), ECS_INTERNAL_ERROR, NULL); + if (bucket_count > map->bucket_count) { + rehash(map, bucket_count); + } +} - return flecs_sparse_get(world->type_info, ecs_type_info_t, component); +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); + } } -ecs_type_info_t* flecs_type_info_ensure( - ecs_world_t *world, - ecs_entity_t component) +ecs_map_t* ecs_map_copy( + ecs_map_t *map) { - ecs_poly_assert(world, ecs_world_t); - ecs_assert(component != 0, ECS_INTERNAL_ERROR, NULL); + if (!ecs_map_is_initialized(map)) { + return NULL; + } - const ecs_type_info_t *ti = flecs_type_info_get(world, component); - ecs_type_info_t *ti_mut = NULL; - if (!ti) { - ti_mut = flecs_sparse_ensure( - world->type_info, ecs_type_info_t, component); - ecs_assert(ti_mut != NULL, ECS_INTERNAL_ERROR, NULL); - ti_mut->component = component; - } else { - ti_mut = (ecs_type_info_t*)ti; + 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 ti_mut; + return result; } -bool flecs_type_info_init_id( - ecs_world_t *world, - ecs_entity_t component, - ecs_size_t size, - ecs_size_t alignment, - const ecs_type_hooks_t *li) +void ecs_map_memory( + ecs_map_t *map, + int32_t *allocd, + int32_t *used) { - bool changed = false; + ecs_assert(map != NULL, ECS_INVALID_PARAMETER, NULL); - flecs_entities_ensure(world, component); + if (used) { + *used = map->count * map->elem_size; + } - ecs_type_info_t *ti = NULL; - if (!size || !alignment) { - ecs_assert(size == 0 && alignment == 0, - ECS_INVALID_COMPONENT_SIZE, NULL); - ecs_assert(li == NULL, ECS_INCONSISTENT_COMPONENT_ACTION, NULL); - flecs_sparse_remove(world->type_info, component); - } else { - ti = flecs_type_info_ensure(world, component); - ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); - changed |= ti->size != size; - changed |= ti->alignment != alignment; - ti->size = size; - ti->alignment = alignment; - if (li) { - ecs_set_hooks_id(world, component, li); + // TODO: something something block allocator + if (allocd) { + *allocd += ECS_SIZEOF(ecs_map_t); + *allocd += ECS_SIZEOF(ecs_bucket_entry_t*) * map->bucket_count; + int32_t index; + int32_t entry_size = map->elem_size + ECS_SIZEOF(ecs_bucket_entry_t); + for (index = 0; index < map->bucket_count; ++index) { + ecs_bucket_entry_t *entry; + for (entry = map->buckets[index].first; entry; entry = entry->next){ + *allocd += entry_size; + } } } +} - /* Set type info for id record of component */ - ecs_id_record_t *idr = flecs_id_record_ensure(world, component); - changed |= flecs_id_record_set_type_info(world, idr, ti); - bool is_tag = idr->flags & EcsIdTag; - - /* All id records with component as relationship inherit type info */ - idr = flecs_id_record_ensure(world, ecs_pair(component, EcsWildcard)); - do { - if (is_tag) { - changed |= flecs_id_record_set_type_info(world, idr, NULL); - } else if (ti) { - changed |= flecs_id_record_set_type_info(world, idr, ti); - } else if ((idr->type_info != NULL) && - (idr->type_info->component == component)) - { - changed |= flecs_id_record_set_type_info(world, idr, NULL); - } - } while ((idr = idr->first.next)); - /* All non-tag id records with component as object inherit type info, - * if relationship doesn't have type info */ - idr = flecs_id_record_ensure(world, ecs_pair(EcsWildcard, component)); - do { - if (!(idr->flags & EcsIdTag) && !idr->type_info) { - changed |= flecs_id_record_set_type_info(world, idr, ti); +static +int32_t find_key( + const ecs_hashmap_t *map, + ecs_vector_t *keys, + ecs_size_t key_size, + const void *key) +{ + int32_t i, count = ecs_vector_count(keys); + void *key_array = ecs_vector_first_t(keys, key_size, 8); + for (i = 0; i < count; i ++) { + void *key_ptr = ECS_OFFSET(key_array, key_size * i); + if (map->compare(key_ptr, key) == 0) { + return i; } - } while ((idr = idr->first.next)); - - /* Type info of (*, component) should always point to component */ - ecs_assert(flecs_id_record_get(world, ecs_pair(EcsWildcard, component))-> - type_info == ti, ECS_INTERNAL_ERROR, NULL); + } + return -1; +} - return changed; +void _flecs_hashmap_init( + ecs_hashmap_t *map, + ecs_size_t key_size, + ecs_size_t value_size, + ecs_hash_value_action_t hash, + ecs_compare_action_t compare) +{ + map->key_size = key_size; + map->value_size = value_size; + map->hash = hash; + map->compare = compare; + ecs_map_init(&map->impl, ecs_hm_bucket_t, 0); } -void flecs_type_info_fini( - ecs_type_info_t *ti) +void flecs_hashmap_fini( + ecs_hashmap_t *map) { - if (ti->hooks.ctx_free) { - ti->hooks.ctx_free(ti->hooks.ctx); - } - if (ti->hooks.binding_ctx_free) { - ti->hooks.binding_ctx_free(ti->hooks.binding_ctx); + ecs_map_iter_t it = ecs_map_iter(&map->impl); + ecs_hm_bucket_t *bucket; + while ((bucket = ecs_map_next(&it, ecs_hm_bucket_t, NULL))) { + ecs_vector_free(bucket->keys); + ecs_vector_free(bucket->values); } + + ecs_map_fini(&map->impl); } -void flecs_type_info_free( - ecs_world_t *world, - ecs_entity_t component) +void flecs_hashmap_copy( + const ecs_hashmap_t *src, + ecs_hashmap_t *dst) { - if (world->flags & EcsWorldFini) { - /* If world is in the final teardown stages, cleanup policies are no - * longer applied and it can't be guaranteed that a component is not - * deleted before entities that use it. The remaining type info elements - * will be deleted after the store is finalized. */ - return; + if (dst != src) { + *dst = *src; } - ecs_type_info_t *ti = flecs_sparse_remove_get( - world->type_info, ecs_type_info_t, component); - if (ti) { - flecs_type_info_fini(ti); + + ecs_map_t *impl = ecs_map_copy(&dst->impl); + dst->impl = *impl; + ecs_os_free(impl); + + ecs_map_iter_t it = ecs_map_iter(&dst->impl); + ecs_hm_bucket_t *bucket; + while ((bucket = ecs_map_next(&it, ecs_hm_bucket_t, NULL))) { + bucket->keys = ecs_vector_copy_t(bucket->keys, dst->key_size, 8); + bucket->values = ecs_vector_copy_t(bucket->values, dst->value_size, 8); } } -static -ecs_ftime_t flecs_insert_sleep( - ecs_world_t *world, - ecs_time_t *stop) +void* _flecs_hashmap_get( + const ecs_hashmap_t *map, + ecs_size_t key_size, + const void *key, + ecs_size_t value_size) { - ecs_poly_assert(world, ecs_world_t); - - ecs_time_t start = *stop; - ecs_ftime_t delta_time = (ecs_ftime_t)ecs_time_measure(stop); + ecs_assert(map->key_size == key_size, ECS_INVALID_PARAMETER, NULL); + ecs_assert(map->value_size == value_size, ECS_INVALID_PARAMETER, NULL); - if (world->info.target_fps == (ecs_ftime_t)0.0) { - return delta_time; + uint64_t hash = map->hash(key); + ecs_hm_bucket_t *bucket = ecs_map_get(&map->impl, ecs_hm_bucket_t, hash); + if (!bucket) { + return NULL; } - ecs_ftime_t target_delta_time = - ((ecs_ftime_t)1.0 / (ecs_ftime_t)world->info.target_fps); - - /* Calculate the time we need to sleep by taking the measured delta from the - * previous frame, and subtracting it from target_delta_time. */ - ecs_ftime_t sleep = target_delta_time - delta_time; - - /* Pick a sleep interval that is 4 times smaller than the time one frame - * should take. */ - ecs_ftime_t sleep_time = sleep / (ecs_ftime_t)4.0; - - 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); - } - - ecs_time_t now = start; - delta_time = (ecs_ftime_t)ecs_time_measure(&now); - } while ((target_delta_time - delta_time) > - (sleep_time / (ecs_ftime_t)2.0)); + int32_t index = find_key(map, bucket->keys, key_size, key); + if (index == -1) { + return NULL; + } - return delta_time; + return ecs_vector_get_t(bucket->values, value_size, 8, index); } -static -ecs_ftime_t flecs_start_measure_frame( - ecs_world_t *world, - ecs_ftime_t user_delta_time) +flecs_hashmap_result_t _flecs_hashmap_ensure( + ecs_hashmap_t *map, + ecs_size_t key_size, + const void *key, + ecs_size_t value_size) { - ecs_poly_assert(world, ecs_world_t); - - ecs_ftime_t delta_time = 0; - - if ((world->flags & EcsWorldMeasureFrameTime) || (user_delta_time == 0)) { - ecs_time_t t = world->frame_start_time; - do { - if (world->frame_start_time.nanosec || world->frame_start_time.sec){ - delta_time = flecs_insert_sleep(world, &t); + ecs_assert(map->key_size == key_size, ECS_INVALID_PARAMETER, NULL); + ecs_assert(map->value_size == value_size, ECS_INVALID_PARAMETER, NULL); - ecs_time_measure(&t); - } else { - ecs_time_measure(&t); - if (world->info.target_fps != 0) { - delta_time = (ecs_ftime_t)1.0 / world->info.target_fps; - } else { - /* Best guess */ - delta_time = (ecs_ftime_t)1.0 / (ecs_ftime_t)60.0; - } - } - - /* Keep trying while delta_time is zero */ - } while (delta_time == 0); + uint64_t hash = map->hash(key); + ecs_hm_bucket_t *bucket = ecs_map_ensure(&map->impl, ecs_hm_bucket_t, hash); + ecs_assert(bucket != NULL, ECS_INTERNAL_ERROR, NULL); - world->frame_start_time = t; + void *value_ptr, *key_ptr; - /* Keep track of total time passed in world */ - world->info.world_time_total_raw += (ecs_ftime_t)delta_time; + ecs_vector_t *keys = bucket->keys; + if (!keys) { + bucket->keys = ecs_vector_new_t(key_size, 8, 1); + bucket->values = ecs_vector_new_t(value_size, 8, 1); + key_ptr = ecs_vector_add_t(&bucket->keys, key_size, 8); + ecs_os_memcpy(key_ptr, key, key_size); + value_ptr = ecs_vector_add_t(&bucket->values, value_size, 8); + ecs_os_memset(value_ptr, 0, value_size); + } else { + int32_t index = find_key(map, keys, key_size, key); + if (index == -1) { + key_ptr = ecs_vector_add_t(&bucket->keys, key_size, 8); + ecs_os_memcpy(key_ptr, key, key_size); + value_ptr = ecs_vector_add_t(&bucket->values, value_size, 8); + ecs_assert(value_ptr != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_os_memset(value_ptr, 0, value_size); + } else { + key_ptr = ecs_vector_get_t(bucket->keys, key_size, 8, index); + value_ptr = ecs_vector_get_t(bucket->values, value_size, 8, index); + } } - return (ecs_ftime_t)delta_time; + return (flecs_hashmap_result_t){ + .key = key_ptr, + .value = value_ptr, + .hash = hash + }; } -static -void flecs_stop_measure_frame( - ecs_world_t* world) +void _flecs_hashmap_set( + ecs_hashmap_t *map, + ecs_size_t key_size, + void *key, + ecs_size_t value_size, + const void *value) { - ecs_poly_assert(world, ecs_world_t); - - if (world->flags & EcsWorldMeasureFrameTime) { - ecs_time_t t = world->frame_start_time; - world->info.frame_time_total += (ecs_ftime_t)ecs_time_measure(&t); - } + void *value_ptr = _flecs_hashmap_ensure(map, key_size, key, value_size).value; + ecs_assert(value_ptr != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_os_memcpy(value_ptr, value, value_size); } -ecs_ftime_t ecs_frame_begin( - ecs_world_t *world, - ecs_ftime_t user_delta_time) +ecs_hm_bucket_t* flecs_hashmap_get_bucket( + const ecs_hashmap_t *map, + uint64_t hash) { - ecs_poly_assert(world, ecs_world_t); - ecs_check(!(world->flags & EcsWorldReadonly), ECS_INVALID_OPERATION, NULL); - ecs_check(user_delta_time != 0 || ecs_os_has_time(), - ECS_MISSING_OS_API, "get_time"); - - /* Start measuring total frame time */ - ecs_ftime_t delta_time = flecs_start_measure_frame(world, user_delta_time); - if (user_delta_time == 0) { - user_delta_time = delta_time; - } - - world->info.delta_time_raw = user_delta_time; - world->info.delta_time = user_delta_time * world->info.time_scale; - - /* Keep track of total scaled time passed in world */ - world->info.world_time_total += world->info.delta_time; - - ecs_run_aperiodic(world, 0); - - return world->info.delta_time; -error: - return (ecs_ftime_t)0; + ecs_assert(map != NULL, ECS_INTERNAL_ERROR, NULL); + return ecs_map_get(&map->impl, ecs_hm_bucket_t, hash); } -void ecs_frame_end( - ecs_world_t *world) +void flecs_hm_bucket_remove( + ecs_hashmap_t *map, + ecs_hm_bucket_t *bucket, + uint64_t hash, + int32_t index) { - ecs_poly_assert(world, ecs_world_t); - ecs_check(!(world->flags & EcsWorldReadonly), ECS_INVALID_OPERATION, NULL); + ecs_vector_remove_t(bucket->keys, map->key_size, 8, index); + ecs_vector_remove_t(bucket->values, map->value_size, 8, index); - world->info.frame_count_total ++; - - ecs_stage_t *stages = world->stages; - int32_t i, count = world->stage_count; - for (i = 0; i < count; i ++) { - flecs_stage_merge_post_frame(world, &stages[i]); + if (!ecs_vector_count(bucket->keys)) { + ecs_vector_free(bucket->keys); + ecs_vector_free(bucket->values); + ecs_map_remove(&map->impl, hash); } - - flecs_stop_measure_frame(world); -error: - return; } -const ecs_world_info_t* ecs_get_world_info( - const ecs_world_t *world) +void _flecs_hashmap_remove_w_hash( + ecs_hashmap_t *map, + ecs_size_t key_size, + const void *key, + ecs_size_t value_size, + uint64_t hash) { - world = ecs_get_world(world); - return &world->info; -} + ecs_assert(map->key_size == key_size, ECS_INVALID_PARAMETER, NULL); + ecs_assert(map->value_size == value_size, ECS_INVALID_PARAMETER, NULL); + (void)value_size; -void flecs_notify_queries( - ecs_world_t *world, - ecs_query_event_t *event) -{ - ecs_poly_assert(world, ecs_world_t); + ecs_hm_bucket_t *bucket = ecs_map_get(&map->impl, ecs_hm_bucket_t, hash); + if (!bucket) { + return; + } - ecs_id_record_t *idr = flecs_id_record_get(world, - ecs_pair(ecs_id(EcsPoly), EcsQuery)); - if (!idr) { + int32_t index = find_key(map, bucket->keys, key_size, key); + if (index == -1) { return; } - ecs_run_aperiodic(world, EcsAperiodicEmptyTables); - - ecs_table_cache_iter_t it; - const ecs_table_record_t *tr; - if (flecs_table_cache_iter(&idr->cache, &it)) { - while ((tr = flecs_table_cache_next(&it, ecs_table_record_t))) { - ecs_table_t *table = tr->hdr.table; - EcsPoly *queries = ecs_table_get_column(table, tr->column); - int32_t i, count = ecs_table_count(table); + flecs_hm_bucket_remove(map, bucket, hash, index); +} - for (i = 0; i < count; i ++) { - ecs_query_t *query = queries[i].poly; - if (query->flags & EcsQueryIsSubquery) { - continue; - } +void _flecs_hashmap_remove( + ecs_hashmap_t *map, + ecs_size_t key_size, + const void *key, + ecs_size_t value_size) +{ + ecs_assert(map->key_size == key_size, ECS_INVALID_PARAMETER, NULL); + ecs_assert(map->value_size == value_size, ECS_INVALID_PARAMETER, NULL); - ecs_poly_assert(query, ecs_query_t); - flecs_query_notify(world, query, event); - } - } - } + uint64_t hash = map->hash(key); + _flecs_hashmap_remove_w_hash(map, key_size, key, value_size, hash); } -void flecs_delete_table( - ecs_world_t *world, - ecs_table_t *table) +flecs_hashmap_iter_t flecs_hashmap_iter( + ecs_hashmap_t *map) { - ecs_poly_assert(world, ecs_world_t); - flecs_table_release(world, table); + return (flecs_hashmap_iter_t){ + .it = ecs_map_iter(&map->impl) + }; } -static -void flecs_process_empty_queries( - ecs_world_t *world) +void* _flecs_hashmap_next( + flecs_hashmap_iter_t *it, + ecs_size_t key_size, + void *key_out, + ecs_size_t value_size) { - ecs_poly_assert(world, ecs_world_t); + int32_t index = ++ it->index; + ecs_hm_bucket_t *bucket = it->bucket; + while (!bucket || it->index >= ecs_vector_count(bucket->keys)) { + bucket = it->bucket = ecs_map_next(&it->it, ecs_hm_bucket_t, NULL); + if (!bucket) { + return NULL; + } + index = it->index = 0; + } - ecs_id_record_t *idr = flecs_id_record_get(world, - ecs_pair(ecs_id(EcsPoly), EcsQuery)); - if (!idr) { - return; + if (key_out) { + *(void**)key_out = ecs_vector_get_t(bucket->keys, key_size, 8, index); } + + return ecs_vector_get_t(bucket->values, value_size, 8, index); +} - ecs_run_aperiodic(world, EcsAperiodicEmptyTables); - /* Make sure that we defer adding the inactive tags until after iterating - * the query */ - flecs_defer_begin(world, &world->stages[0]); +#define FLECS_STACK_PAGE_OFFSET ECS_ALIGN(ECS_SIZEOF(ecs_stack_page_t), 16) - ecs_table_cache_iter_t it; - const ecs_table_record_t *tr; - if (flecs_table_cache_iter(&idr->cache, &it)) { - while ((tr = flecs_table_cache_next(&it, ecs_table_record_t))) { - ecs_table_t *table = tr->hdr.table; - EcsPoly *queries = ecs_table_get_column(table, tr->column); - int32_t i, count = ecs_table_count(table); +static +ecs_stack_page_t* flecs_stack_page_new(void) { + ecs_stack_page_t *result = ecs_os_malloc( + FLECS_STACK_PAGE_OFFSET + ECS_STACK_PAGE_SIZE); + result->data = ECS_OFFSET(result, FLECS_STACK_PAGE_OFFSET); + result->next = NULL; + return result; +} - for (i = 0; i < count; i ++) { - ecs_query_t *query = queries[i].poly; - ecs_entity_t *entities = table->data.entities.array; - if (!ecs_query_table_count(query)) { - ecs_add_id(world, entities[i], EcsEmpty); - } - } +void* flecs_stack_alloc( + ecs_stack_t *stack, + ecs_size_t size, + ecs_size_t align) +{ + ecs_stack_page_t *page = stack->cur; + if (page == &stack->first && !page->data) { + page->data = ecs_os_malloc(ECS_STACK_PAGE_SIZE); + } + + ecs_size_t sp = ECS_ALIGN(page->sp, align); + ecs_size_t next_sp = sp + size; + + if (next_sp > ECS_STACK_PAGE_SIZE) { + if (size > ECS_STACK_PAGE_SIZE) { + return ecs_os_malloc(size); /* Too large for page */ + } + + if (page->next) { + page = page->next; + } else { + page = page->next = flecs_stack_page_new(); } + sp = 0; + next_sp = size; + stack->cur = page; } - flecs_defer_end(world, &world->stages[0]); + page->sp = next_sp; + + return ECS_OFFSET(page->data, sp); } -/** Walk over tables that had a state change which requires bookkeeping */ -void flecs_process_pending_tables( - const ecs_world_t *world_r) +void flecs_stack_free( + void *ptr, + ecs_size_t size) { - ecs_poly_assert(world_r, ecs_world_t); - - /* We can't update the administration while in readonly mode, but we can - * ensure that when this function is called there are no pending events. */ - if (world_r->flags & EcsWorldReadonly) { - ecs_assert(flecs_sparse_count(world_r->pending_tables) == 0, - ECS_INTERNAL_ERROR, NULL); - return; + if (size > ECS_STACK_PAGE_SIZE) { + ecs_os_free(ptr); } +} - /* Safe to cast, world is not readonly */ - ecs_world_t *world = (ecs_world_t*)world_r; - - /* If pending buffer is NULL there already is a stackframe that's iterating - * the table list. This can happen when an observer for a table event results - * in a mutation that causes another table to change state. A typical - * example of this is a system that becomes active/inactive as the result of - * a query (and as a result, its matched tables) becoming empty/non empty */ - if (!world->pending_buffer) { - return; - } +void flecs_stack_reset( + ecs_stack_t *stack) +{ + stack->cur = &stack->first; + stack->first.sp = 0; +} - /* Swap buffer. The logic could in theory have been implemented with a - * single sparse set, but that would've complicated (and slowed down) the - * iteration. Additionally, by using a double buffer approach we can still - * keep most of the original ordering of events intact, which is desirable - * as it means that the ordering of tables in the internal datastructures is - * more predictable. */ - int32_t i, count = flecs_sparse_count(world->pending_tables); - if (!count) { - return; - } +void flecs_stack_init( + ecs_stack_t *stack) +{ + ecs_os_zeromem(stack); + stack->cur = &stack->first; + stack->first.data = NULL; +} +void flecs_stack_fini( + ecs_stack_t *stack) +{ + ecs_stack_page_t *next, *cur = &stack->first; do { - ecs_sparse_t *pending_tables = world->pending_tables; - world->pending_tables = world->pending_buffer; - world->pending_buffer = NULL; + next = cur->next; + if (cur == &stack->first) { + ecs_os_free(cur->data); + } else { + ecs_os_free(cur); + } + } while ((cur = next)); +} - /* Make sure that any ECS operations that occur while delivering the - * events does not cause inconsistencies, like sending an Empty - * notification for a table that just became non-empty. */ - ecs_defer_begin(world); - for (i = 0; i < count; i ++) { - ecs_table_t *table = flecs_sparse_get_dense( - pending_tables, ecs_table_t*, i)[0]; - if (!table->id) { - /* Table is being deleted, ignore empty events */ - continue; +#ifdef FLECS_LOG + +#include + +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; + + for (ptr = msg; (ch = *ptr); ptr++) { + dontAppend = false; + + 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; } - /* For each id in the table, add it to the empty/non empty list - * based on its current state */ - if (flecs_table_records_update_empty(table)) { - /* Only emit an event when there was a change in the - * administration. It is possible that a table ended up in the - * pending_tables list by going from empty->non-empty, but then - * became empty again. By the time we run this code, no changes - * in the administration would actually be made. */ - int32_t table_count = ecs_table_count(table); - flecs_emit(world, world, &(ecs_event_desc_t){ - .event = table_count - ? EcsOnTableFill - : EcsOnTableEmpty - , - .table = table, - .ids = &table->type, - .observable = world, - .table_event = true - }); + 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; + } - world->info.empty_table_count += (table_count == 0) * 2 - 1; + if (isVar && !isalpha(ch) && !isdigit(ch) && ch != '_') { + if (enable_colors) ecs_strbuf_appendstr(buf, ECS_NORMAL); + isVar = false; + } + + if (!isStr && !isVar && ch == '$' && isalpha(ptr[1])) { + if (enable_colors) ecs_strbuf_appendstr(buf, ECS_CYAN); + isVar = true; } } - flecs_sparse_clear(pending_tables); - ecs_defer_end(world); + if (!isVar && !isStr && !isNum && ch == '#' && ptr[1] == '[') { + bool isColor = true; + overrideColor = true; - world->pending_buffer = pending_tables; - } while ((count = flecs_sparse_count(world->pending_tables))); + /* 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; + } + + if (isColor) { + ptr += 2; + while ((ch = *ptr) != ']') ptr ++; + dontAppend = true; + } + if (!autoColor) { + overrideColor = true; + } + } + + 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 (!dontAppend) { + ecs_strbuf_appendstrn(buf, ptr, 1); + } + + if (!overrideColor) { + if (((ch == '\'') || (ch == '"')) && !isStr) { + if (enable_colors) ecs_strbuf_appendstr(buf, ECS_NORMAL); + } + } + + prev = ch; + } + + if (isNum || isStr || isVar || overrideColor) { + if (enable_colors) ecs_strbuf_appendstr(buf, ECS_NORMAL); + } } -void flecs_table_set_empty( - ecs_world_t *world, - ecs_table_t *table) +void _ecs_logv( + int level, + const char *file, + int32_t line, + const char *fmt, + va_list args) { - ecs_poly_assert(world, ecs_world_t); - ecs_assert(!(world->flags & EcsWorldReadonly), ECS_INTERNAL_ERROR, NULL); - ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + (void)level; + (void)line; - if (ecs_table_count(table)) { - table->generation = 0; + ecs_strbuf_t msg_buf = ECS_STRBUF_INIT; + + if (level > ecs_os_api.log_level_) { + return; } - flecs_sparse_set_generation(world->pending_tables, (uint32_t)table->id); - flecs_sparse_ensure(world->pending_tables, ecs_table_t*, - (uint32_t)table->id)[0] = table; + /* 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.flags_ & EcsOsApiLogWithColors, &msg_buf); + ecs_os_free(msg_nocolor); + + char *msg = ecs_strbuf_get(&msg_buf); + if (msg) { + ecs_os_api.log_(level, file, line, msg); + ecs_os_free(msg); + } else { + ecs_os_api.log_(level, file, line, ""); + } } -bool ecs_id_in_use( - ecs_world_t *world, - ecs_id_t id) +void _ecs_log( + int level, + const char *file, + int32_t line, + const char *fmt, + ...) { - ecs_id_record_t *idr = flecs_id_record_get(world, id); - if (!idr) { - return false; - } - return (flecs_table_cache_count(&idr->cache) != 0) || - (flecs_table_cache_empty_count(&idr->cache) != 0); + va_list args; + va_start(args, fmt); + _ecs_logv(level, file, line, fmt, args); + va_end(args); } -void ecs_run_aperiodic( - ecs_world_t *world, - ecs_flags32_t flags) +void _ecs_log_push( + int32_t level) { - ecs_poly_assert(world, ecs_world_t); - - if (!flags || (flags & EcsAperiodicEmptyTables)) { - flecs_process_pending_tables(world); - } - if ((flags & EcsAperiodicEmptyQueries)) { - flecs_process_empty_queries(world); + if (level <= ecs_os_api.log_level_) { + ecs_os_api.log_indent_ ++; } - if (!flags || (flags & EcsAperiodicComponentMonitors)) { - flecs_eval_component_monitors(world); +} + +void _ecs_log_pop( + int32_t level) +{ + if (level <= ecs_os_api.log_level_) { + ecs_os_api.log_indent_ --; } } -int32_t ecs_delete_empty_tables( - ecs_world_t *world, - ecs_id_t id, - uint16_t clear_generation, - uint16_t delete_generation, - int32_t min_id_count, - double time_budget_seconds) +void _ecs_parser_errorv( + const char *name, + const char *expr, + int64_t column_arg, + const char *fmt, + va_list args) { - ecs_poly_assert(world, ecs_world_t); + int32_t column = flecs_itoi32(column_arg); - /* Make sure empty tables are in the empty table lists */ - ecs_run_aperiodic(world, EcsAperiodicEmptyTables); + if (ecs_os_api.log_level_ >= -2) { + ecs_strbuf_t msg_buf = ECS_STRBUF_INIT; - ecs_time_t start = {0}, cur = {0}; - int32_t delete_count = 0, clear_count = 0; - bool time_budget = false; + ecs_strbuf_vappend(&msg_buf, fmt, args); - ecs_time_measure(&start); - if (time_budget_seconds != 0) { - time_budget = true; - } + if (expr) { + ecs_strbuf_appendstr(&msg_buf, "\n"); - if (!id) { - id = EcsAny; /* Iterate all empty tables */ - } + /* 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 --; + } - ecs_id_record_t *idr = flecs_id_record_get(world, id); - ecs_table_cache_iter_t it; - if (idr && flecs_table_cache_empty_iter((ecs_table_cache_t*)idr, &it)) { - ecs_table_record_t *tr; - while ((tr = flecs_table_cache_next(&it, ecs_table_record_t))) { - if (time_budget) { - cur = start; - if (ecs_time_measure(&cur) > time_budget_seconds) { - goto done; + if (ptr == expr) { + /* ptr is already at start of line */ + } else { + column -= (int32_t)(ptr - expr + 1); + expr = ptr + 1; } } - ecs_table_t *table = tr->hdr.table; - ecs_assert(ecs_table_count(table) == 0, ECS_INTERNAL_ERROR, NULL); - if (table->refcount > 1) { - /* Don't delete claimed tables */ - 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); } - if (table->type.count < min_id_count) { - continue; - } + ecs_strbuf_appendstr(&msg_buf, "\n"); - uint16_t gen = ++ table->generation; - if (delete_generation && (gen > delete_generation)) { - if (flecs_table_release(world, table)) { - delete_count ++; - } - } else if (clear_generation && (gen > clear_generation)) { - if (flecs_table_shrink(world, table)) { - clear_count ++; - } + if (column != -1) { + ecs_strbuf_append(&msg_buf, "%*s^", column, ""); } } - } -done: - if (delete_count) { - ecs_dbg_1("#[red]deleted#[normal] %d empty tables in %.2fs", - delete_count, ecs_time_measure(&start)); - } - if (clear_count) { - ecs_dbg_1("#[red]cleared#[normal] %d empty tables in %.2fs", - clear_count, ecs_time_measure(&start)); + char *msg = ecs_strbuf_get(&msg_buf); + ecs_os_err(name, 0, msg); + ecs_os_free(msg); } - return delete_count; } -#include - -static -bool flecs_multi_observer_invoke(ecs_iter_t *it) { - ecs_observer_t *o = it->ctx; - ecs_world_t *world = it->real_world; - - if (o->last_event_id[0] == world->event_id) { - /* Already handled this event */ - return false; +void _ecs_parser_error( + const char *name, + const char *expr, + int64_t column, + const char *fmt, + ...) +{ + 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); } +} - o->last_event_id[0] = world->event_id; +void _ecs_abort( + int32_t err, + const char *file, + int32_t line, + const char *fmt, + ...) +{ + 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_os_api.log_last_error_ = err; +} - ecs_iter_t user_it = *it; - user_it.field_count = o->filter.field_count; - user_it.terms = o->filter.terms; - user_it.flags = 0; - ECS_BIT_COND(user_it.flags, EcsIterIsFilter, - ECS_BIT_IS_SET(o->filter.flags, EcsFilterIsFilter)); - user_it.ids = NULL; - user_it.columns = NULL; - user_it.sources = NULL; - user_it.sizes = NULL; - user_it.ptrs = NULL; - flecs_iter_init(&user_it, flecs_iter_cache_all); +bool _ecs_assert( + bool condition, + int32_t err, + const char *cond_str, + const char *file, + int32_t line, + const char *fmt, + ...) +{ + 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; + } - 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]; + return condition; +} - int32_t column = it->columns[0]; - if (term->oper == EcsNot) { - table = it->other_table; - prev_table = it->table; - } +void _ecs_deprecated( + const char *file, + int32_t line, + const char *msg) +{ + _ecs_err(file, line, "%s", msg); +} - if (!table) { - table = &world->store.root; +bool ecs_should_log(int32_t level) { +# if !defined(FLECS_LOG_3) + if (level == 3) { + return false; } - if (!prev_table) { - prev_table = &world->store.root; +# endif +# if !defined(FLECS_LOG_2) + if (level == 2) { + return false; } - - if (column < 0) { - column = -column; +# endif +# if !defined(FLECS_LOG_1) + if (level == 1) { + return false; } +# endif - user_it.columns[0] = 0; - user_it.columns[pivot_term] = column; - - if (flecs_filter_match_table(world, &o->filter, table, user_it.ids, - user_it.columns, user_it.sources, NULL, NULL, false, -1, - user_it.flags)) - { - /* Monitor observers only invoke when the filter matches for the first - * time with an entity */ - if (o->is_monitor) { - if (flecs_filter_match_table(world, &o->filter, prev_table, - NULL, NULL, NULL, NULL, NULL, true, -1, user_it.flags)) - { - goto done; - } - } + return level <= ecs_os_api.log_level_; +} - /* While filter matching needs to be reversed for a Not term, the - * component data must be fetched from the table we got notified for. - * Repeat the matching process for the non-matching table so we get the - * correct column ids and sources, which we need for populate_data */ - if (term->oper == EcsNot) { - flecs_filter_match_table(world, &o->filter, prev_table, user_it.ids, - user_it.columns, user_it.sources, NULL, NULL, false, -1, - user_it.flags | EcsFilterPopulate); - } +#define ECS_ERR_STR(code) case code: return &(#code[4]) - flecs_iter_populate_data(world, &user_it, it->table, it->offset, - it->count, user_it.ptrs, user_it.sizes); +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_NAME_IN_USE); + 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_MISSING_SYMBOL); + ECS_ERR_STR(ECS_ALREADY_IN_USE); + ECS_ERR_STR(ECS_CYCLE_DETECTED); + 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_READONLY); + 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_ACCESS_VIOLATION); + ECS_ERR_STR(ECS_COMPONENT_NOT_REGISTERED); + ECS_ERR_STR(ECS_INCONSISTENT_COMPONENT_ID); + 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); + ECS_ERR_STR(ECS_ID_IN_USE); + } - user_it.ids[pivot_term] = it->event_id; - user_it.system = o->entity; - user_it.term_index = pivot_term; - user_it.ctx = o->ctx; - user_it.binding_ctx = o->binding_ctx; - user_it.field_count = o->filter.field_count; - flecs_iter_validate(&user_it); + return "unknown error code"; +} - ecs_assert(o->callback != NULL, ECS_INVALID_PARAMETER, NULL); - o->callback(&user_it); +#else - ecs_iter_fini(&user_it); +/* Empty bodies for when logging is disabled */ - return true; - } +void _ecs_log( + int32_t level, + const char *file, + int32_t line, + const char *fmt, + ...) +{ + (void)level; + (void)file; + (void)line; + (void)fmt; +} -done: - ecs_iter_fini(&user_it); - return false; +void _ecs_parser_error( + const char *name, + const char *expr, + int64_t column, + const char *fmt, + ...) +{ + (void)name; + (void)expr; + (void)column; + (void)fmt; } -static -ecs_entity_t flecs_observer_get_actual_event( - ecs_observer_t *observer, - ecs_entity_t event) +void _ecs_parser_errorv( + const char *name, + const char *expr, + int64_t column, + const char *fmt, + va_list args) { - /* If operator is Not, reverse the event */ - if (observer->filter.terms[0].oper == EcsNot) { - if (event == EcsOnAdd) { - event = EcsOnRemove; - } else if (event == EcsOnRemove) { - event = EcsOnAdd; - } - } + (void)name; + (void)expr; + (void)column; + (void)fmt; + (void)args; +} - return event; +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; } -static -void flecs_unregister_event_observer( - ecs_event_record_t *evt, - ecs_id_t id) +bool _ecs_assert( + bool condition, + int32_t error_code, + const char *condition_str, + const char *file, + int32_t line, + const char *fmt, + ...) { - if (ecs_map_remove(&evt->event_ids, id) == 0) { - ecs_map_fini(&evt->event_ids); - } + (void)condition; + (void)error_code; + (void)condition_str; + (void)file; + (void)line; + (void)fmt; + return true; } -static -ecs_event_id_record_t* flecs_ensure_event_id_record( - ecs_map_t *map, - ecs_id_t id) +#endif + +int ecs_log_set_level( + int level) { - ecs_event_id_record_t **idt = ecs_map_ensure( - map, ecs_event_id_record_t*, id); - if (!idt[0]) { - idt[0] = ecs_os_calloc_t(ecs_event_id_record_t); - } + int prev = level; + ecs_os_api.log_level_ = level; + return prev; +} - return idt[0]; +bool ecs_log_enable_colors( + bool enabled) +{ + bool prev = ecs_os_api.flags_ & EcsOsApiLogWithColors; + ECS_BIT_COND(ecs_os_api.flags_, EcsOsApiLogWithColors, enabled); + return prev; } -static -ecs_flags32_t flecs_id_flag_for_event( - ecs_entity_t e) +bool ecs_log_enable_timestamp( + bool enabled) { - if (e == EcsOnAdd) { - return EcsIdHasOnAdd; - } - if (e == EcsOnRemove) { - return EcsIdHasOnRemove; - } - if (e == EcsOnSet) { - return EcsIdHasOnSet; - } - if (e == EcsUnSet) { - return EcsIdHasUnSet; - } - return 0; + bool prev = ecs_os_api.flags_ & EcsOsApiLogWithTimeStamp; + ECS_BIT_COND(ecs_os_api.flags_, EcsOsApiLogWithTimeStamp, enabled); + return prev; } -static -void flecs_inc_observer_count( - ecs_world_t *world, - ecs_entity_t event, - ecs_event_record_t *evt, - ecs_id_t id, - int32_t value) +bool ecs_log_enable_timedelta( + bool enabled) { - ecs_event_id_record_t *idt = flecs_ensure_event_id_record(&evt->event_ids, id); - ecs_assert(idt != NULL, ECS_INTERNAL_ERROR, NULL); - - int32_t result = idt->observer_count += value; - if (result == 1) { - /* Notify framework that there are observers 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 - }); + bool prev = ecs_os_api.flags_ & EcsOsApiLogWithTimeDelta; + ECS_BIT_COND(ecs_os_api.flags_, EcsOsApiLogWithTimeDelta, enabled); + return prev; +} - ecs_flags32_t flags = flecs_id_flag_for_event(event); - if (flags) { - ecs_id_record_t *idr = flecs_id_record_get(world, id); - if (idr) { - idr->flags |= flags; - } - } - } else if (result == 0) { - /* Ditto, but the reverse */ - flecs_notify_tables(world, id, &(ecs_table_event_t){ - .kind = EcsTableNoTriggersForId, - .event = event - }); +int ecs_log_last_error(void) +{ + int result = ecs_os_api.log_last_error_; + ecs_os_api.log_last_error_ = 0; + return result; +} - ecs_flags32_t flags = flecs_id_flag_for_event(event); - if (flags) { - ecs_id_record_t *idr = flecs_id_record_get(world, id); - if (idr) { - idr->flags &= ~flags; - } - } +#ifndef FLECS_SYSTEM_PRIVATE_H +#define FLECS_SYSTEM_PRIVATE_H - /* Remove admin for id for event */ - if (!ecs_map_is_initialized(&idt->observers) && - !ecs_map_is_initialized(&idt->set_observers)) - { - flecs_unregister_event_observer(evt, id); - ecs_os_free(idt); - } - } -} +#ifdef FLECS_SYSTEM -static -void flecs_register_observer_for_id( - ecs_world_t *world, - ecs_observable_t *observable, - ecs_observer_t *observer, - ecs_id_t id, - size_t offset) -{ - ecs_sparse_t *events = observable->events; - ecs_assert(events != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_id_t term_id = observer->register_id; - int i; - for (i = 0; i < observer->event_count; i ++) { - ecs_entity_t event = flecs_observer_get_actual_event( - observer, observer->events[i]); +#define ecs_system_t_magic (0x65637383) +#define ecs_system_t_tag EcsSystem - /* Get observers for event */ - ecs_event_record_t *evt = flecs_sparse_ensure( - events, ecs_event_record_t, event); - ecs_assert(evt != NULL, ECS_INTERNAL_ERROR, NULL); +extern ecs_mixins_t ecs_system_t_mixins; - ecs_map_init_if(&evt->event_ids, ecs_event_id_record_t*, 1); +typedef struct ecs_system_t { + ecs_header_t hdr; - /* Get observers for (component) id for event */ - ecs_event_id_record_t *idt = flecs_ensure_event_id_record( - &evt->event_ids, id); - ecs_assert(idt != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_run_action_t run; /* See ecs_system_desc_t */ + ecs_iter_action_t action; /* See ecs_system_desc_t */ - ecs_map_t *observers = ECS_OFFSET(idt, offset); - ecs_map_init_if(observers, ecs_observer_t*, 1); + ecs_query_t *query; /* System query */ + ecs_entity_t query_entity; /* Entity associated with query */ + ecs_entity_t tick_source; /* Tick source associated with system */ + + /* Schedule parameters */ + bool multi_threaded; + bool no_staging; - ecs_map_ensure(observers, ecs_observer_t*, - observer->entity)[0] = observer; + int32_t invoke_count; /* Number of times system is invoked */ + float time_spent; /* Time spent on running system */ + ecs_ftime_t time_passed; /* Time passed since last invocation */ + int32_t last_frame; /* Last frame for which the system was considered */ - flecs_inc_observer_count(world, event, evt, term_id, 1); - if (term_id != id) { - flecs_inc_observer_count(world, event, evt, id, 1); - } - } -} + void *ctx; /* Userdata for system */ + void *binding_ctx; /* Optional language binding context */ -static -void flecs_uni_observer_register( - ecs_world_t *world, - ecs_observable_t *observable, - ecs_observer_t *observer) -{ - ecs_term_t *term = &observer->filter.terms[0]; - ecs_id_t id = observer->register_id; + ecs_ctx_free_t ctx_free; + ecs_ctx_free_t binding_ctx_free; - if (term->src.flags & EcsSelf) { - if (ecs_term_match_this(term)) { - flecs_register_observer_for_id(world, observable, observer, id, - offsetof(ecs_event_id_record_t, observers)); - } else { - flecs_register_observer_for_id(world, observable, observer, id, - offsetof(ecs_event_id_record_t, entity_observers)); - } - } + /* Mixins */ + ecs_world_t *world; + ecs_entity_t entity; + ecs_poly_dtor_t dtor; +} ecs_system_t; - if (observer->filter.terms[0].src.flags & EcsUp) { - ecs_id_t pair = ecs_pair(term->src.trav, EcsWildcard); - flecs_register_observer_for_id(world, observable, observer, pair, - offsetof(ecs_event_id_record_t, set_observers)); - } -} +/* Invoked when system becomes active / inactive */ +void ecs_system_activate( + ecs_world_t *world, + ecs_entity_t system, + bool activate, + const ecs_system_t *system_data); -static -void flecs_unregister_observer_for_id( +/* Internal function to run a system */ +ecs_entity_t ecs_run_intern( ecs_world_t *world, - ecs_observable_t *observable, - ecs_observer_t *observer, - ecs_id_t id, - size_t offset) -{ - ecs_sparse_t *events = observable->events; - ecs_assert(events != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_id_t term_id = observer->register_id; + ecs_stage_t *stage, + ecs_entity_t system, + ecs_system_t *system_data, + int32_t stage_current, + int32_t stage_count, + ecs_ftime_t delta_time, + int32_t offset, + int32_t limit, + void *param); - int i; - for (i = 0; i < observer->event_count; i ++) { - ecs_entity_t event = flecs_observer_get_actual_event( - observer, observer->events[i]); +#endif - /* Get observers 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 observers for (component) id */ - ecs_event_id_record_t *idt = ecs_map_get_ptr( - &evt->event_ids, ecs_event_id_record_t*, id); - ecs_assert(idt != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_map_t *id_observers = ECS_OFFSET(idt, offset); +#ifdef FLECS_PIPELINE +#ifndef FLECS_PIPELINE_PRIVATE_H +#define FLECS_PIPELINE_PRIVATE_H - if (ecs_map_remove(id_observers, observer->entity) == 0) { - ecs_map_fini(id_observers); - } - flecs_inc_observer_count(world, event, evt, term_id, -1); +/** 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; - if (id != term_id) { - /* Id is different from term_id in case of a set observer. If they're - * the same, flecs_inc_observer_count could already have done cleanup */ - if (!ecs_map_is_initialized(&idt->observers) && - !ecs_map_is_initialized(&idt->set_observers) && - !idt->observer_count) - { - flecs_unregister_event_observer(evt, id); - } +typedef struct { + ecs_query_t *query; /* Pipeline query */ + + ecs_vector_t *ops; /* Pipeline schedule */ + int32_t match_count; /* Used to track of rebuild is necessary */ + int32_t rebuild_count; /* Number of pipeline rebuilds */ + ecs_entity_t last_system; /* Last system ran by pipeline */ - flecs_inc_observer_count(world, event, evt, id, -1); - } - } -} + ecs_id_record_t *idr_inactive; /* Cached record for quick inactive test */ -static -void flecs_unregister_observer( + ecs_iter_t *iters; /* Iterator for worker(s) */ + int32_t iter_count; + + /* Members for continuing pipeline iteration after pipeline rebuild */ + ecs_pipeline_op_t *cur_op; /* Current pipeline op */ + int32_t cur_i; /* Index in current result */ +} EcsPipeline; + +//////////////////////////////////////////////////////////////////////////////// +//// Pipeline API +//////////////////////////////////////////////////////////////////////////////// + +/** 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_observable_t *observable, - ecs_observer_t *observer) -{ - ecs_assert(observer != NULL, ECS_INTERNAL_ERROR, NULL); - if (!observer->filter.terms) { - ecs_assert(observer->filter.term_count == 0, ECS_INTERNAL_ERROR, NULL); - return; - } + ecs_entity_t pipeline, + bool start_of_frame); - ecs_term_t *term = &observer->filter.terms[0]; - ecs_id_t id = observer->register_id; +void ecs_pipeline_reset_iter( + ecs_world_t *world, + EcsPipeline *q); - if (term->src.flags & EcsSelf) { - if (ecs_term_match_this(term)) { - flecs_unregister_observer_for_id(world, observable, observer, id, - offsetof(ecs_event_id_record_t, observers)); - } else { - flecs_unregister_observer_for_id(world, observable, observer, id, - offsetof(ecs_event_id_record_t, entity_observers)); - } - } +//////////////////////////////////////////////////////////////////////////////// +//// Worker API +//////////////////////////////////////////////////////////////////////////////// - if (term->src.flags & EcsUp) { - ecs_id_t pair = ecs_pair(term->src.trav, EcsWildcard); - flecs_unregister_observer_for_id(world, observable, observer, pair, - offsetof(ecs_event_id_record_t, set_observers)); - } -} +void ecs_worker_begin( + ecs_world_t *world); -static -ecs_map_t* flecs_get_observers_for_event( - const ecs_observable_t *observable, - ecs_entity_t event) -{ - ecs_check(observable != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_assert(event != 0, ECS_INTERNAL_ERROR, NULL); +bool ecs_worker_sync( + ecs_world_t *world, + EcsPipeline *p); - ecs_sparse_t *events = observable->events; - ecs_assert(events != NULL, ECS_INTERNAL_ERROR, NULL); +void ecs_worker_end( + ecs_world_t *world); - const ecs_event_record_t *evt = flecs_sparse_get( - events, ecs_event_record_t, event); - - if (evt) { - return (ecs_map_t*)&evt->event_ids; - } +void ecs_workers_progress( + ecs_world_t *world, + ecs_entity_t pipeline, + ecs_ftime_t delta_time); -error: - return NULL; -} +#endif -static -ecs_event_id_record_t* flecs_get_observers_for_id( - const ecs_map_t *evt, - ecs_id_t id) -{ - return ecs_map_get_ptr(evt, ecs_event_id_record_t*, id); -} +/* Worker thread */ static -void flecs_init_observer_iter( - ecs_iter_t *it, - bool *iter_set) -{ - ecs_assert(it != NULL, ECS_INTERNAL_ERROR, NULL); - - if (*iter_set) { - return; - } +void* worker(void *arg) { + ecs_stage_t *stage = arg; + ecs_world_t *world = stage->world; - if (ECS_BIT_IS_SET(it->flags, EcsIterTableOnly)) { - it->ids = it->priv.cache.ids; - it->ids[0] = it->event_id; - return; - } + ecs_dbg_2("worker %d: start", stage->id); - flecs_iter_init(it, flecs_iter_cache_all); - flecs_iter_validate(it); + /* Start worker, increase counter so main thread knows how many + * workers are ready */ + ecs_os_mutex_lock(world->sync_mutex); + world->workers_running ++; - *iter_set = true; + if (!(world->flags & EcsWorldQuitWorkers)) { + ecs_os_cond_wait(world->worker_cond, world->sync_mutex); + } - it->ids[0] = it->event_id; + ecs_os_mutex_unlock(world->sync_mutex); - ecs_assert(it->table != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(!it->count || 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); + while (!(world->flags & EcsWorldQuitWorkers)) { + ecs_entity_t old_scope = ecs_set_scope((ecs_world_t*)stage, 0); - int32_t index = ecs_search_relation(it->real_world, it->table, 0, - it->event_id, EcsIsA, 0, it->sources, 0, 0); - - if (index == -1) { - it->columns[0] = 0; - } else if (it->sources[0]) { - it->columns[0] = -index - 1; - } else { - it->columns[0] = index + 1; + ecs_dbg_3("worker %d: run", stage->id); + + ecs_run_pipeline( + (ecs_world_t*)stage, + world->pipeline, + world->info.delta_time); + + ecs_set_scope((ecs_world_t*)stage, old_scope); } - ecs_term_t term = { - .id = it->event_id - }; + ecs_dbg_2("worker %d: finalizing", stage->id); - it->field_count = 1; - it->terms = &term; - flecs_iter_populate_data(it->real_world, it, it->table, it->offset, - it->count, it->ptrs, it->sizes); + ecs_os_mutex_lock(world->sync_mutex); + world->workers_running --; + ecs_os_mutex_unlock(world->sync_mutex); + + ecs_dbg_2("worker %d: stop", stage->id); + + return NULL; } +/* Start threads */ static -bool flecs_ignore_observer( +void start_workers( ecs_world_t *world, - ecs_observer_t *observer, - ecs_table_t *table) + int32_t threads) { - int32_t *last_event_id = observer->last_event_id; - if (last_event_id && last_event_id[0] == world->event_id) { - return true; - } + ecs_set_stage_count(world, threads); - if (!table) { - return false; - } - - ecs_filter_t *filter = &observer->filter; - if (!(filter->flags & EcsFilterMatchPrefab) && - (table->flags & EcsTableIsPrefab)) - { - return true; - } - if (!(filter->flags & EcsFilterMatchDisabled) && - (table->flags & EcsTableIsDisabled)) - { - return true; - } - - return false; -} + ecs_assert(ecs_get_stage_count(world) == threads, ECS_INTERNAL_ERROR, NULL); -bool ecs_observer_default_run_action(ecs_iter_t *it) { - ecs_observer_t *observer = it->ctx; - if (observer->is_multi) { - return flecs_multi_observer_invoke(it); - } else { - it->callback(it); - return true; + 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); + stage->thread = ecs_os_thread_new(worker, stage); + ecs_assert(stage->thread != 0, ECS_OPERATION_FAILED, NULL); } } -static -void flecs_default_multi_observer_run_callback(ecs_iter_t *it) { - flecs_multi_observer_invoke(it); -} - -static -void flecs_default_uni_observer_run_callback(ecs_iter_t *it) { - ecs_observer_t *observer = it->ctx; - it->ctx = observer->ctx; - it->callback = observer->callback; - it->callback(it); -} +/* Wait until all workers are running */ +static +void wait_for_workers( + ecs_world_t *world) +{ + int32_t stage_count = ecs_get_stage_count(world); + bool wait = true; -/* For convenience, so applications can (in theory) use a single run callback - * that uses ecs_iter_next to iterate results */ -static -bool flecs_default_observer_next_callback(ecs_iter_t *it) { - if (it->interrupted_by) { - return false; - } else { - /* Use interrupted_by to signal the next iteration must return false */ - it->interrupted_by = it->system; - return 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); } -/* Run action for children of multi observer */ +/* Synchronize workers */ static -void flecs_multi_observer_builtin_run(ecs_iter_t *it) { - ecs_observer_t *observer = it->ctx; - ecs_run_action_t run = observer->run; +void sync_worker( + ecs_world_t *world) +{ + int32_t stage_count = ecs_get_stage_count(world); - if (run) { - it->next = flecs_default_observer_next_callback; - it->callback = flecs_default_multi_observer_run_callback; - it->interrupted_by = 0; - run(it); - } else { - flecs_multi_observer_invoke(it); + /* 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); } -/* Run action for uni observer */ +/* Wait until all threads are waiting on sync point */ static -void flecs_uni_observer_builtin_run( - ecs_observer_t *observer, - ecs_iter_t *it) +void wait_for_sync( + ecs_world_t *world) { - ECS_BIT_COND(it->flags, EcsIterIsFilter, - observer->filter.terms[0].inout == EcsInOutNone); + int32_t stage_count = ecs_get_stage_count(world); - it->system = observer->entity; - it->ctx = observer->ctx; - it->binding_ctx = observer->binding_ctx; - it->term_index = observer->term_index; - it->terms = observer->filter.terms; + ecs_dbg_3("#[bold]pipeline: waiting for worker sync"); - void *ptrs = it->ptrs; - if (it->flags & EcsIterIsFilter) { - it->ptrs = NULL; + 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_entity_t event = it->event; - it->event = flecs_observer_get_actual_event(observer, event); + ecs_os_mutex_unlock(world->sync_mutex); - if (observer->run) { - it->next = flecs_default_observer_next_callback; - it->callback = flecs_default_uni_observer_run_callback; - it->ctx = observer; - observer->run(it); - } else { - observer->callback(it); - } + ecs_dbg_3("#[bold]pipeline: workers synced"); +} - it->event = event; - it->ptrs = ptrs; +/* Signal workers that they can start/resume work */ +static +void signal_workers( + ecs_world_t *world) +{ + ecs_dbg_3("#[bold]pipeline: signal workers"); + ecs_os_mutex_lock(world->sync_mutex); + ecs_os_cond_broadcast(world->worker_cond); + ecs_os_mutex_unlock(world->sync_mutex); } +/** Stop workers */ static -void flecs_notify_self_observers( - ecs_world_t *world, - ecs_iter_t *it, - const ecs_map_t *observers) +bool ecs_stop_threads( + ecs_world_t *world) { - ecs_assert(observers != NULL, ECS_INTERNAL_ERROR, NULL); + bool threads_active = false; - ecs_map_iter_t mit = ecs_map_iter(observers); - ecs_observer_t *observer; - while ((observer = ecs_map_next_ptr(&mit, ecs_observer_t*, NULL))) { - if (flecs_ignore_observer(world, observer, it->table)) { - continue; + /* Test if threads are created. Cannot use workers_running, since this is + * a potential race if threads haven't spun up yet. */ + ecs_stage_t *stages = world->stages; + int i, count = world->stage_count; + for (i = 0; i < count; i ++) { + ecs_stage_t *stage = &stages[i]; + if (stage->thread) { + threads_active = true; + break; } + stage->thread = 0; + }; - flecs_uni_observer_builtin_run(observer, it); + /* If no threads are active, just return */ + if (!threads_active) { + return false; } -} -static -void flecs_notify_entity_observers( - ecs_world_t *world, - ecs_iter_t *it, - const ecs_map_t *observers) -{ - ecs_assert(observers != NULL, ECS_INTERNAL_ERROR, NULL); + /* Make sure all threads are running, to ensure they catch the signal */ + wait_for_workers(world); - if (ECS_BIT_IS_SET(it->flags, EcsIterTableOnly)) { - return; + /* Signal threads should quit */ + world->flags |= EcsWorldQuitWorkers; + signal_workers(world); + + /* Join all threads with main */ + for (i = 0; i < count; i ++) { + ecs_os_thread_join(stages[i].thread); + stages[i].thread = 0; } - ecs_map_iter_t mit = ecs_map_iter(observers); - ecs_observer_t *observer; - int32_t offset = it->offset, count = it->count; - ecs_entity_t *entities = it->entities; - - ecs_entity_t dummy = 0; - it->entities = &dummy; + world->flags &= ~EcsWorldQuitWorkers; + ecs_assert(world->workers_running == 0, ECS_INTERNAL_ERROR, NULL); - while ((observer = ecs_map_next_ptr(&mit, ecs_observer_t*, NULL))) { - if (flecs_ignore_observer(world, observer, it->table)) { - continue; - } + /* Deinitialize stages */ + ecs_set_stage_count(world, 1); - int32_t i, entity_count = it->count; - for (i = 0; i < entity_count; i ++) { - if (entities[i] != observer->filter.terms[0].src.id) { - continue; - } + return true; +} - it->offset = i; - it->count = 1; - it->sources[0] = entities[i]; - flecs_uni_observer_builtin_run(observer, it); +/* -- Private functions -- */ + +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 EcsPipeline *pq = ecs_get(world, pipeline, EcsPipeline); + 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_readonly_begin(world); } } - - it->offset = offset; - it->count = count; - it->entities = entities; - it->sources[0] = 0; } -static -void flecs_notify_set_base_observers( +bool ecs_worker_sync( ecs_world_t *world, - ecs_iter_t *it, - const ecs_map_t *observers) + EcsPipeline *pq) { - ecs_assert(observers != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(pq != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(pq->cur_op != NULL, ECS_INTERNAL_ERROR, NULL); + bool rebuild = false; - ecs_entity_t event_id = it->event_id; - ecs_entity_t rel = ECS_PAIR_FIRST(event_id); - ecs_entity_t obj = ecs_pair_second(world, event_id); - if (!obj) { - /* Don't notify for deleted (or in progress of being deleted) object */ - return; + int32_t stage_count = ecs_get_stage_count(world); + ecs_assert(stage_count != 0, ECS_INTERNAL_ERROR, NULL); + int32_t build_count = world->info.pipeline_build_count_total; + + /* If there are no threads, merge in place */ + if (stage_count == 1) { + if (!pq->cur_op->no_staging) { + ecs_readonly_end(world); + } + + ecs_pipeline_update(world, world->pipeline, 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); } - ecs_record_t *obj_record = flecs_entities_get(world, obj); - if (!obj_record) { - return; + if (build_count != world->info.pipeline_build_count_total) { + rebuild = true; } - ecs_table_t *obj_table = obj_record->table; - if (!obj_table) { - return; + if (stage_count == 1) { + if (!pq->cur_op->no_staging) { + ecs_readonly_begin(world); + } } - ecs_map_iter_t mit = ecs_map_iter(observers); - ecs_observer_t *observer; - while ((observer = ecs_map_next_ptr(&mit, ecs_observer_t*, NULL))) { - if (flecs_ignore_observer(world, observer, it->table)) { - continue; - } - - ecs_term_t *term = &observer->filter.terms[0]; - ecs_id_t id = term->id; - int32_t column = ecs_search_relation(world, obj_table, 0, id, rel, - 0, it->sources, it->ids, 0); - - bool result = column != -1; - if (term->oper == EcsNot) { - result = !result; - } - if (!result) { - continue; - } + return rebuild; +} - if ((term->src.flags & EcsSelf) && flecs_table_record_get( - world, it->table, id) != NULL) - { - continue; - } +void ecs_worker_end( + ecs_world_t *world) +{ + flecs_stage_from_world(&world); - if (!ECS_BIT_IS_SET(it->flags, EcsIterTableOnly)) { - if (!it->sources[0]) { - it->sources[0] = obj; - } + int32_t stage_count = ecs_get_stage_count(world); + ecs_assert(stage_count != 0, ECS_INTERNAL_ERROR, NULL); - /* Populate pointer from object */ - int32_t s_column = ecs_table_type_to_storage_index( - obj_table, column); - if (s_column != -1) { - ecs_column_t *c = &obj_table->data.columns[s_column]; - int32_t row = ECS_RECORD_TO_ROW(obj_record->row); - ecs_type_info_t *ti = obj_table->type_info[s_column]; - void *ptr = ecs_storage_get(c, ti->size, row); - it->ptrs[0] = ptr; - it->sizes[0] = ti->size; - } + /* If there are no threads, merge in place */ + if (stage_count == 1) { + if (ecs_stage_is_readonly(world)) { + ecs_readonly_end(world); } - it->event_id = observer->filter.terms[0].id; - flecs_uni_observer_builtin_run(observer, it); + /* 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 flecs_notify_set_observers( +void ecs_workers_progress( ecs_world_t *world, - ecs_iter_t *it, - const ecs_map_t *observers) + ecs_entity_t pipeline, + ecs_ftime_t delta_time) { - ecs_assert(observers != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(it->count != 0, ECS_INTERNAL_ERROR, NULL); + ecs_poly_assert(world, ecs_world_t); + ecs_assert(!ecs_is_deferred(world), ECS_INVALID_OPERATION, NULL); + int32_t stage_count = ecs_get_stage_count(world); - if (ECS_BIT_IS_SET(it->flags, EcsIterTableOnly)) { - return; + ecs_time_t start = {0}; + if (world->flags & EcsWorldMeasureFrameTime) { + ecs_time_measure(&start); } - ecs_map_iter_t mit = ecs_map_iter(observers); - ecs_observer_t *observer; - while ((observer = ecs_map_next_ptr(&mit, ecs_observer_t*, NULL))) { - if (!ecs_id_match(it->event_id, observer->filter.terms[0].id)) { - continue; - } + EcsPipeline *pq = ecs_get_mut(world, pipeline, EcsPipeline); + ecs_assert(pq != NULL, ECS_INTERNAL_ERROR, NULL); - if (flecs_ignore_observer(world, observer, it->table)) { - continue; - } + if (stage_count != pq->iter_count) { + pq->iters = ecs_os_realloc_n(pq->iters, ecs_iter_t, stage_count); + pq->iter_count = stage_count; + } - ecs_entity_t src = it->entities[0]; - int32_t i, count = it->count; - ecs_filter_t *filter = &observer->filter; - ecs_term_t *term = &filter->terms[0]; - ecs_entity_t src_id = term->src.id; + ecs_pipeline_update(world, pipeline, true); + ecs_vector_t *ops = pq->ops; + ecs_pipeline_op_t *op = ecs_vector_first(ops, ecs_pipeline_op_t); + if (!op) { + return; + } - /* If observer is for a specific entity, make sure it is in the table - * being triggered for */ - if (!(ecs_term_match_this(term))) { - for (i = 0; i < count; i ++) { - if (it->entities[i] == src_id) { - break; - } - } + if (stage_count == 1) { + 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_op_t *op_last = ecs_vector_last(ops, ecs_pipeline_op_t); - if (i == count) { - continue; + /* Make sure workers are running and ready */ + wait_for_workers(world); + + /* Synchronize n times for each op in the pipeline */ + for (; op <= op_last; op ++) { + if (!op->no_staging) { + ecs_readonly_begin(world); } - /* If the entity matches, observer for no other entities */ - it->entities[0] = 0; - it->count = 1; - } + /* Signal workers that they should start running systems */ + world->workers_waiting = 0; + signal_workers(world); - if (flecs_term_match_table(world, term, it->table, it->ids, - it->columns, it->sources, NULL, true, it->flags)) - { - if (!it->sources[0]) { - /* Do not match owned components */ - continue; + /* Wait until all workers are waiting on sync point */ + wait_for_sync(world); + + /* Merge */ + if (!op->no_staging) { + ecs_readonly_end(world); } - /* Triggers for supersets can be instanced */ - bool instanced = filter->flags & EcsFilterIsInstanced; - bool is_filter = ECS_BIT_IS_SET(it->flags, EcsIterIsFilter); - if (it->count == 1 || instanced || is_filter || !it->sizes[0]) { - ECS_BIT_COND(it->flags, EcsIterIsInstanced, instanced); - flecs_uni_observer_builtin_run(observer, it); - ECS_BIT_CLEAR(it->flags, EcsIterIsInstanced); - } else { - ecs_entity_t *entities = it->entities; - it->count = 1; - for (i = 0; i < count; i ++) { - it->entities = &entities[i]; - flecs_uni_observer_builtin_run(observer, it); - } - it->entities = entities; + if (ecs_pipeline_update(world, pipeline, false)) { + ecs_assert(!ecs_is_deferred(world), ECS_INVALID_OPERATION, NULL); + pq = ecs_get_mut(world, pipeline, EcsPipeline); + /* Refetch, in case pipeline itself has moved */ + op = pq->cur_op - 1; + op_last = ecs_vector_last(pq->ops, ecs_pipeline_op_t); + ecs_assert(op <= op_last, ECS_INTERNAL_ERROR, NULL); } } - - it->entities[0] = src; - it->count = count; } + + if (world->flags & EcsWorldMeasureFrameTime) { + world->info.system_time_total += (float)ecs_time_measure(&start); + } } -static -void flecs_notify_observers_for_id( +/* -- Public functions -- */ + +void ecs_set_threads( ecs_world_t *world, - const ecs_map_t *evt, - ecs_id_t event_id, - ecs_iter_t *it, - bool *iter_set) + int32_t threads) { - const ecs_event_id_record_t *idt = flecs_get_observers_for_id(evt, event_id); - if (!idt) { - return; - } + ecs_assert(threads <= 1 || ecs_os_has_threading(), ECS_MISSING_OS_API, NULL); - if (ecs_map_is_initialized(&idt->observers)) { - flecs_init_observer_iter(it, iter_set); - flecs_notify_self_observers(world, it, &idt->observers); - } - if (ecs_map_is_initialized(&idt->entity_observers)) { - flecs_init_observer_iter(it, iter_set); - flecs_notify_entity_observers(world, it, &idt->entity_observers); - } - if (ecs_map_is_initialized(&idt->set_observers)) { - flecs_init_observer_iter(it, iter_set); - flecs_notify_set_base_observers(world, it, &idt->set_observers); + 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); + } } } +#endif + + +#ifdef FLECS_PIPELINE + +static ECS_DTOR(EcsPipeline, ptr, { + ecs_vector_free(ptr->ops); + ecs_os_free(ptr->iters); +}) + +typedef enum ComponentWriteState { + NotWritten = 0, + WriteToMain, + WriteToStage +} ComponentWriteState; + +typedef struct write_state_t { + ecs_map_t *components; + bool wildcard; +} write_state_t; + static -void flecs_notify_set_observers_for_id( - ecs_world_t *world, - const ecs_map_t *evt, - ecs_iter_t *it, - bool *iter_set, - ecs_id_t set_id) +int32_t get_write_state( + ecs_map_t *write_state, + ecs_entity_t component) { - const ecs_event_id_record_t *idt = flecs_get_observers_for_id(evt, set_id); - if (idt && ecs_map_is_initialized(&idt->set_observers)) { - flecs_init_observer_iter(it, iter_set); - flecs_notify_set_observers(world, it, &idt->set_observers); + int32_t *ptr = ecs_map_get(write_state, int32_t, component); + if (ptr) { + return *ptr; + } else { + return 0; } } static -void flecs_uni_observer_trigger_existing( - ecs_world_t *world, - ecs_observer_t *observer) +void set_write_state( + write_state_t *write_state, + ecs_entity_t component, + int32_t value) { - ecs_iter_action_t callback = observer->callback; + if (component == EcsWildcard) { + ecs_assert(value == WriteToStage, ECS_INTERNAL_ERROR, NULL); + write_state->wildcard = true; + } else { + ecs_map_set(write_state->components, component, &value); + } +} - /* If yield existing is enabled, observer for each thing that matches - * the event, if the event is iterable. */ - int i, count = observer->event_count; - for (i = 0; i < count; i ++) { - ecs_entity_t evt = observer->events[i]; - const EcsIterable *iterable = ecs_get(world, evt, EcsIterable); - if (!iterable) { - continue; - } +static +void reset_write_state( + write_state_t *write_state) +{ + ecs_map_clear(write_state->components); + write_state->wildcard = false; +} - ecs_iter_t it; - iterable->init(world, world, &it, &observer->filter.terms[0]); - it.system = observer->entity; - it.ctx = observer->ctx; - it.binding_ctx = observer->binding_ctx; - it.event = evt; +static +int32_t get_any_write_state( + write_state_t *write_state) +{ + if (write_state->wildcard) { + return WriteToStage; + } - ecs_iter_next_action_t next = it.next; - ecs_assert(next != NULL, ECS_INTERNAL_ERROR, NULL); - while (next(&it)) { - it.event_id = it.ids[0]; - callback(&it); + 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 flecs_multi_observer_yield_existing( - ecs_world_t *world, - ecs_observer_t *observer) +bool check_term_component( + ecs_term_t *term, + bool is_active, + ecs_entity_t component, + write_state_t *write_state) { - ecs_run_action_t run = observer->run; - if (!run) { - run = flecs_default_multi_observer_run_callback; - } + int32_t state = get_write_state(write_state->components, component); - ecs_run_aperiodic(world, EcsAperiodicEmptyTables); + ecs_term_id_t *src = &term->src; - int32_t pivot_term = ecs_filter_pivot_term(world, &observer->filter); - if (pivot_term < 0) { - return; - } + if ((src->flags & EcsSelf) && ecs_term_match_this(term) && term->oper != EcsNot) { + switch(term->inout) { + case EcsInOutNone: + /* 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); + } + }; + } else if (!src->id || term->oper == EcsNot) { + bool needs_merge = false; - /* If yield existing is enabled, invoke for each thing that matches - * the event, if the event is iterable. */ - int i, count = observer->event_count; - for (i = 0; i < count; i ++) { - ecs_entity_t evt = observer->events[i]; - const EcsIterable *iterable = ecs_get(world, evt, EcsIterable); - if (!iterable) { - continue; - } + 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; + }; - ecs_iter_t it; - iterable->init(world, world, &it, &observer->filter.terms[pivot_term]); - it.terms = observer->filter.terms; - it.field_count = 1; - it.term_index = pivot_term; - it.system = observer->entity; - it.ctx = observer; - it.binding_ctx = observer->binding_ctx; - it.event = evt; + switch(term->inout) { + case EcsInOutDefault: + if ((!(src->flags & EcsSelf) || (!ecs_term_match_this(term))) && (!ecs_term_match_0(term))) { + /* 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; + }; - ecs_iter_next_action_t next = it.next; - ecs_assert(next != NULL, ECS_INTERNAL_ERROR, NULL); - while (next(&it)) { - it.event_id = it.ids[0]; - run(&it); - world->event_id ++; + if (needs_merge) { + return true; } } + + return false; } -bool flecs_check_observers_for_event( - 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 = flecs_get_observers_for_event(observable, event); - if (!evt) { - return false; - } +static +bool check_term( + ecs_term_t *term, + bool is_active, + write_state_t *write_state) +{ + if (term->oper != EcsOr) { + return check_term_component( + term, is_active, term->id, write_state); + } - ecs_event_id_record_t *edr = flecs_get_observers_for_id(evt, id); - if (edr) { - return edr->observer_count != 0; - } else { - return false; - } + return false; } -void flecs_observers_notify( - ecs_iter_t *it, - ecs_observable_t *observable, - const ecs_type_t *ids, - ecs_entity_t event) +static +bool check_terms( + ecs_filter_t *filter, + bool is_active, + write_state_t *ws) { - 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; - ecs_world_t *world = it->real_world; + bool needs_merge = false; + ecs_term_t *terms = filter->terms; + int32_t t, term_count = filter->term_count; - for (e = 0; e < 2; e ++) { - event = events[e]; - const ecs_map_t *evt = flecs_get_observers_for_event(observable, event); - if (!evt) { - continue; + /* 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 (ecs_term_match_this(term)) { + needs_merge |= check_term(term, is_active, ws); } + } - it->event = event; + /* Now check staged terms */ + for (t = 0; t < term_count; t ++) { + ecs_term_t *term = &terms[t]; + if (!ecs_term_match_this(term)) { + needs_merge |= check_term(term, is_active, ws); + } + } - for (i = 0; i < ids_count; i ++) { - ecs_id_t id = ids_array[i]; - ecs_entity_t role = id & ECS_ID_FLAGS_MASK; - bool iter_set = false; + return needs_merge; +} - it->event_id = id; +static +bool flecs_pipeline_is_inactive( + const EcsPipeline *pq, + ecs_table_t *table) +{ + return flecs_id_record_get_table(pq->idr_inactive, table) != NULL; +} - flecs_notify_observers_for_id(world, evt, id, it, &iter_set); +static +EcsPoly* flecs_pipeline_term_system( + ecs_iter_t *it) +{ + int32_t index = ecs_search(it->world, it->table, ecs_poly_id(EcsSystem), 0); + ecs_assert(index != -1, ECS_INTERNAL_ERROR, NULL); + EcsPoly *poly = ecs_table_get_column(it->table, index); + ecs_assert(poly != NULL, ECS_INTERNAL_ERROR, NULL); + poly = &poly[it->offset]; + return poly; +} - if (ECS_HAS_ID_FLAG(role, PAIR)) { - ecs_entity_t r = ECS_PAIR_FIRST(id); - ecs_entity_t o = ECS_PAIR_SECOND(id); +static +bool flecs_pipeline_build( + ecs_world_t *world, + ecs_entity_t pipeline, + EcsPipeline *pq) +{ + (void)pipeline; - ecs_id_t tid = ecs_pair(r, EcsWildcard); - flecs_notify_observers_for_id(world, evt, tid, it, &iter_set); - - tid = ecs_pair(EcsWildcard, o); - flecs_notify_observers_for_id(world, evt, tid, it, &iter_set); - - tid = ecs_pair(EcsWildcard, EcsWildcard); - flecs_notify_observers_for_id(world, evt, tid, it, &iter_set); - } else { - flecs_notify_observers_for_id( - world, evt, EcsWildcard, it, &iter_set); - } - - flecs_notify_observers_for_id(world, evt, EcsAny, it, &iter_set); + ecs_query_iter(world, pq->query); - if (iter_set) { - ecs_iter_fini(it); - } - } + if (pq->match_count == pq->query->match_count) { + /* No need to rebuild the pipeline */ + return false; } -} -void flecs_set_observers_notify( - ecs_iter_t *it, - ecs_observable_t *observable, - const ecs_type_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; - ecs_world_t *world = it->real_world; + world->info.pipeline_build_count_total ++; + pq->rebuild_count ++; - for (e = 0; e < 2; e ++) { - event = events[e]; - const ecs_map_t *evt = flecs_get_observers_for_event(observable, event); - if (!evt) { - continue; - } + write_state_t ws = { + .components = ecs_map_new(int32_t, ECS_HI_COMPONENT_ID), + .wildcard = false + }; - it->event = event; + ecs_pipeline_op_t *op = NULL; + ecs_vector_t *ops = NULL; + ecs_query_t *query = pq->query; - for (i = 0; i < ids_count; i ++) { - ecs_id_t id = ids_array[i]; - bool iter_set = false; + if (pq->ops) { + ecs_vector_free(pq->ops); + } - it->event_id = id; + bool multi_threaded = false; + bool no_staging = false; + bool first = true; - flecs_notify_set_observers_for_id(world, evt, it, &iter_set, set_id); + /* Iterate systems in pipeline, add ops for running / merging */ + ecs_iter_t it = ecs_query_iter(world, query); + while (ecs_query_next(&it)) { + EcsPoly *poly = flecs_pipeline_term_system(&it); - if (iter_set) { - ecs_iter_fini(it); + int i; + for (i = 0; i < it.count; i ++) { + ecs_system_t *sys = ecs_poly(poly[i].poly, ecs_system_t); + ecs_query_t *q = sys->query; + if (!q) { + continue; } - } - } -} -static -int flecs_uni_observer_init( - ecs_world_t *world, - ecs_observer_t *observer, - const ecs_observer_desc_t *desc) -{ - ecs_term_t *term = &observer->filter.terms[0]; - observer->last_event_id = desc->last_event_id; - observer->register_id = flecs_from_public_id(world, term->id); - term->field_index = desc->term_index; + bool needs_merge = false; + bool is_active = !ecs_has_id( + world, it.entities[i], EcsEmpty); + needs_merge = check_terms(&q->filter, is_active, &ws); - if (ecs_id_is_tag(world, term->id)) { - /* If id is a tag, downgrade OnSet/UnSet to OnAdd/OnRemove. */ - int32_t e, count = observer->event_count; - for (e = 0; e < count; e ++) { - if (observer->events[e] == EcsOnSet) { - observer->events[e] = EcsOnAdd; - } else - if (observer->events[e] == EcsUnSet) { - observer->events[e] = EcsOnRemove; - } - } - } + if (is_active) { + if (first) { + multi_threaded = sys->multi_threaded; + no_staging = sys->no_staging; + first = false; + } - flecs_uni_observer_register(world, observer->observable, observer); + if (sys->multi_threaded != multi_threaded) { + needs_merge = true; + multi_threaded = sys->multi_threaded; + } + if (sys->no_staging != no_staging) { + needs_merge = true; + no_staging = sys->no_staging; + } + } - if (desc->yield_existing) { - flecs_uni_observer_trigger_existing(world, observer); - } + if (needs_merge) { + /* After merge all components will be merged, so reset state */ + reset_write_state(&ws); + op = NULL; - return 0; -} + /* 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); + } -static -int flecs_multi_observer_init( - ecs_world_t *world, - ecs_observer_t *observer, - const ecs_observer_desc_t *desc) -{ - /* Create last event id for filtering out the same event that arrives from - * more than one term */ - observer->last_event_id = ecs_os_calloc_t(int32_t); - - /* Mark observer as multi observer */ - observer->is_multi = true; + /* 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); + } - /* Create a child observer for each term in the filter */ - ecs_filter_t *filter = &observer->filter; - ecs_observer_desc_t child_desc = *desc; - child_desc.last_event_id = observer->last_event_id; - child_desc.run = NULL; - child_desc.callback = flecs_multi_observer_builtin_run; - child_desc.ctx = observer; - child_desc.filter.expr = NULL; - child_desc.filter.terms_buffer = NULL; - child_desc.filter.terms_buffer_count = 0; - child_desc.binding_ctx = NULL; - child_desc.binding_ctx_free = NULL; - child_desc.yield_existing = false; - ecs_os_zeromem(&child_desc.entity); - ecs_os_zeromem(&child_desc.filter.terms); - ecs_os_memcpy_n(child_desc.events, observer->events, - ecs_entity_t, observer->event_count); + if (!op) { + op = ecs_vector_add(&ops, ecs_pipeline_op_t); + op->count = 0; + op->multi_threaded = false; + op->no_staging = false; + } - int i, term_count = filter->term_count; - bool optional_only = filter->flags & EcsFilterMatchThis; - for (i = 0; i < term_count; i ++) { - if (filter->terms[i].oper != EcsOptional) { - if (ecs_term_match_this(&filter->terms[i])) { - optional_only = 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 ++; } } } - if (filter->flags & EcsFilterMatchPrefab) { - child_desc.filter.flags |= EcsFilterMatchPrefab; - } - if (filter->flags & EcsFilterMatchDisabled) { - child_desc.filter.flags |= EcsFilterMatchDisabled; - } + ecs_map_free(ws.components); - /* Create observers as children of observer */ - ecs_entity_t old_scope = ecs_set_scope(world, observer->entity); + /* 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; - for (i = 0; i < term_count; i ++) { - ecs_term_t *term = &child_desc.filter.terms[0]; - child_desc.term_index = filter->terms[i].field_index; - *term = filter->terms[i]; + if (!op) { + ecs_dbg("#[green]pipeline#[reset] is empty"); + return true; + } else { + ecs_assert(op != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_oper_kind_t oper = term->oper; - ecs_id_t id = term->id; + /* Add schedule to debug tracing */ + ecs_dbg("#[bold]pipeline rebuild"); + ecs_log_push_1(); - /* AndFrom & OrFrom terms insert multiple observers */ - if (oper == EcsAndFrom || oper == EcsOrFrom) { - const ecs_type_t *type = ecs_get_type(world, id); - int32_t ti, ti_count = type->count; - ecs_id_t *ti_ids = type->array; + ecs_dbg("#[green]schedule#[reset]: threading: %d, staging: %d:", + op->multi_threaded, !op->no_staging); + ecs_log_push_1(); - /* Correct operator will be applied when an event occurs, and - * the observer is evaluated on the observer source */ - term->oper = EcsAnd; - for (ti = 0; ti < ti_count; ti ++) { - ecs_id_t ti_id = ti_ids[ti]; - ecs_id_record_t *idr = flecs_id_record_get(world, ti_id); - if (idr->flags & EcsIdDontInherit) { - continue; + it = ecs_query_iter(world, pq->query); + while (ecs_query_next(&it)) { + if (flecs_pipeline_is_inactive(pq, it.table)) { + continue; + } + + EcsPoly *poly = flecs_pipeline_term_system(&it); + + for (i = 0; i < it.count; i ++) { + ecs_system_t *sys = ecs_poly(poly[i].poly, ecs_system_t); + if (ecs_should_log_1()) { + char *path = ecs_get_fullpath(world, it.entities[i]); + ecs_dbg("#[green]system#[reset] %s", path); + ecs_os_free(path); } - term->first.name = NULL; - term->first.id = ti_ids[ti]; - term->id = ti_ids[ti]; + ran_since_merge ++; + if (ran_since_merge == op[op_index].count) { + ecs_dbg("#[magenta]merge#[reset]"); + ecs_log_pop_1(); + 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_1(); + } - if (ecs_observer_init(world, &child_desc) == 0) { - goto error; + if (sys->last_frame == (world->info.frame_count_total + 1)) { + last_system = it.entities[i]; + + /* Can't break from loop yet. It's possible that previously + * inactive systems that ran before the last ran system are + * now active. */ } } - continue; - } - - /* If observer only contains optional terms, match everything */ - if (optional_only) { - term->id = EcsAny; - term->first.id = EcsAny; - term->src.id = EcsThis; - term->src.flags = EcsIsVariable; - term->second.id = 0; - } else if (term->oper == EcsOptional) { - continue; - } - if (ecs_observer_init(world, &child_desc) == 0) { - goto error; } - if (optional_only) { - break; - } + ecs_log_pop_1(); + ecs_log_pop_1(); } - ecs_set_scope(world, old_scope); - - if (desc->yield_existing) { - flecs_multi_observer_yield_existing(world, observer); - } + /* 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 0; -error: - return -1; + return true; } -ecs_entity_t ecs_observer_init( +void ecs_pipeline_reset_iter( ecs_world_t *world, - const ecs_observer_desc_t *desc) + EcsPipeline *pq) { - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(desc->_canary == 0, ECS_INVALID_PARAMETER, NULL); - ecs_check(!(world->flags & EcsWorldFini), ECS_INVALID_OPERATION, NULL); + ecs_pipeline_op_t *op = ecs_vector_first(pq->ops, ecs_pipeline_op_t); + int32_t i, ran_since_merge = 0, op_index = 0, iter_count = pq->iter_count; - ecs_entity_t entity = desc->entity; - if (!entity) { - entity = ecs_new(world, 0); + /* Free state of existing iterators */ + for (i = 0; i < iter_count; i ++) { + ecs_iter_fini(&pq->iters[i]); } - EcsPoly *poly = ecs_poly_bind(world, entity, ecs_observer_t); - if (!poly->poly) { - ecs_check(desc->callback != NULL || desc->run != NULL, - ECS_INVALID_OPERATION, NULL); - ecs_observer_t *observer = ecs_poly_new(ecs_observer_t); - ecs_assert(observer != NULL, ECS_INTERNAL_ERROR, NULL); - - observer->world = world; - observer->dtor = (ecs_poly_dtor_t)flecs_observer_fini; - observer->entity = entity; + 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. */ + pq->cur_i = -1; + return; + } - /* 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 = ecs_get_name(world, entity); - ecs_filter_t *filter = filter_desc.storage = &observer->filter; - *filter = ECS_FILTER_INIT; + /* Create new iterators */ + for (i = 0; i < iter_count; i ++) { + pq->iters[i] = ecs_query_iter(world, pq->query); + } - /* Parse filter */ - if (ecs_filter_init(world, &filter_desc) == NULL) { - flecs_observer_fini(observer); - return 0; + /* Move iterator to last ran system */ + ecs_iter_t *it = &pq->iters[0]; + while (ecs_query_next(it)) { + /* Progress worker iterators */ + for (i = 1; i < iter_count; i ++) { + bool hasnext = ecs_query_next(&pq->iters[i]); + ecs_assert_var(hasnext, ECS_INTERNAL_ERROR, NULL); + ecs_assert(it->table == pq->iters[i].table, + ECS_INTERNAL_ERROR, NULL); + ecs_assert(it->count == pq->iters[i].count, + ECS_INTERNAL_ERROR, NULL); } - /* Observer must have at least one term */ - ecs_check(observer->filter.term_count > 0, ECS_INVALID_PARAMETER, NULL); - - poly->poly = observer; - - ecs_observable_t *observable = desc->observable; - if (!observable) { - observable = ecs_get_observable(world); + if (flecs_pipeline_is_inactive(pq, it->table)) { + continue; } - observer->run = desc->run; - observer->callback = desc->callback; - 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->term_index = desc->term_index; - observer->observable = observable; - - /* Check if observer is monitor. Monitors are created as multi observers - * since they require pre/post checking of the filter to test if the - * entity is entering/leaving the monitor. */ - int i; - for (i = 0; i < ECS_OBSERVER_DESC_EVENT_COUNT_MAX; i ++) { - ecs_entity_t event = desc->events[i]; - if (!event) { - break; + for (i = 0; i < it->count; i ++) { + ran_since_merge ++; + if (ran_since_merge == op[op_index].count) { + ran_since_merge = 0; + op_index ++; } - 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; + if (it->entities[i] == pq->last_system) { + pq->cur_op = &op[op_index]; + pq->cur_i = i; + return; } - - observer->event_count ++; } + } - /* Observer must have at least one event */ - ecs_check(observer->event_count != 0, ECS_INVALID_PARAMETER, NULL); + ecs_abort(ECS_INTERNAL_ERROR, NULL); +} - bool multi = false; +bool ecs_pipeline_update( + ecs_world_t *world, + ecs_entity_t pipeline, + bool start_of_frame) +{ + ecs_poly_assert(world, ecs_world_t); + ecs_assert(!(world->flags & EcsWorldReadonly), ECS_INVALID_OPERATION, NULL); + ecs_assert(pipeline != 0, ECS_INTERNAL_ERROR, NULL); - if (filter->term_count == 1 && !desc->last_event_id) { - ecs_term_t *term = &filter->terms[0]; - /* If the filter has a single term but it is a *From operator, we - * need to create a multi observer */ - multi |= (term->oper == EcsAndFrom) || (term->oper == EcsOrFrom); - - /* An observer with only optional terms is a special case that is - * only handled by multi observers */ - multi |= term->oper == EcsOptional; - } + /* 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) { + ecs_run_aperiodic(world, 0); + } - if (filter->term_count == 1 && !observer->is_monitor && !multi) { - if (flecs_uni_observer_init(world, observer, desc)) { - goto error; - } - } else { - if (flecs_multi_observer_init(world, observer, desc)) { - goto error; - } - } + EcsPipeline *pq = ecs_get_mut(world, pipeline, EcsPipeline); + ecs_assert(pq != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(pq->query != NULL, ECS_INTERNAL_ERROR, NULL); - if (ecs_get_name(world, entity)) { - ecs_trace("#[green]observer#[reset] %s created", - ecs_get_name(world, entity)); + bool rebuilt = flecs_pipeline_build(world, pipeline, pq); + if (start_of_frame) { + /* Initialize iterators */ + int32_t i, count = pq->iter_count; + for (i = 0; i < count; i ++) { + pq->iters[i] = ecs_query_iter(world, pq->query); } + pq->cur_op = ecs_vector_first(pq->ops, ecs_pipeline_op_t); + } else if (rebuilt) { + /* If pipeline got updated and we were mid frame, reset iterators */ + ecs_pipeline_reset_iter(world, pq); + return true; } else { - /* If existing entity handle was provided, override existing params */ - if (desc->callback) { - ecs_poly(poly->poly, ecs_observer_t)->callback = desc->callback; - } - if (desc->ctx) { - ecs_poly(poly->poly, ecs_observer_t)->ctx = desc->ctx; - } - if (desc->binding_ctx) { - ecs_poly(poly->poly, ecs_observer_t)->binding_ctx = desc->binding_ctx; - } + pq->cur_op += 1; } - ecs_poly_modified(world, entity, ecs_observer_t); - - return entity; -error: - ecs_delete(world, entity); - return 0; + return false; } -void* ecs_get_observer_ctx( - const ecs_world_t *world, - ecs_entity_t observer) +void ecs_run_pipeline( + ecs_world_t *world, + ecs_entity_t pipeline, + ecs_ftime_t delta_time) { - const EcsPoly *o = ecs_poly_bind_get(world, observer, ecs_observer_t); - if (o) { - ecs_poly_assert(o->poly, ecs_observer_t); - return ((ecs_observer_t*)o->poly)->ctx; - } else { - return NULL; - } -} + ecs_assert(world != NULL, ECS_INVALID_OPERATION, NULL); -void* ecs_get_observer_binding_ctx( - const ecs_world_t *world, - ecs_entity_t observer) -{ - const EcsPoly *o = ecs_poly_bind_get(world, observer, ecs_observer_t); - if (o) { - ecs_poly_assert(o->poly, ecs_observer_t); - return ((ecs_observer_t*)o->poly)->binding_ctx; - } else { - return NULL; - } -} - -void flecs_observer_fini( - ecs_observer_t *observer) -{ - if (observer->is_multi) { - /* Child observers get deleted up by entity cleanup logic */ - ecs_os_free(observer->last_event_id); - } else { - if (observer->filter.term_count) { - flecs_unregister_observer( - observer->world, observer->observable, observer); - } else { - /* Observer creation failed while creating filter */ - } - } - - /* Cleanup filters */ - ecs_filter_fini(&observer->filter); - - /* Cleanup context */ - if (observer->ctx_free) { - observer->ctx_free(observer->ctx); - } - - if (observer->binding_ctx_free) { - observer->binding_ctx_free(observer->binding_ctx); + if (!pipeline) { + pipeline = world->pipeline; } - ecs_poly_free(observer, ecs_observer_t); -} + ecs_assert(pipeline != 0, ECS_INVALID_PARAMETER, NULL); + /* 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->flags & EcsWorldReadonly), + ECS_INVALID_OPERATION, NULL); -void flecs_observable_init( - ecs_observable_t *observable) -{ - observable->events = ecs_sparse_new(ecs_event_record_t); -} + /* Forward to worker_progress. This function handles staging, threading + * and synchronization across workers. */ + ecs_workers_progress(world, pipeline, delta_time); + return; -void flecs_observable_fini( - ecs_observable_t *observable) -{ - ecs_sparse_t *triggers = observable->events; - int32_t i, count = flecs_sparse_count(triggers); + /* 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); + } - 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); - (void)et; + ecs_stage_t *stage = flecs_stage_from_world(&world); - /* All triggers should've unregistered by now */ - ecs_assert(!ecs_map_is_initialized(&et->event_ids), - ECS_INTERNAL_ERROR, NULL); - } + EcsPipeline *pq = (EcsPipeline*)ecs_get(world, pipeline, EcsPipeline); + ecs_assert(pq != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(pq->query != NULL, ECS_INTERNAL_ERROR, NULL); - flecs_sparse_free(observable->events); -} + 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 notify_subset( - ecs_world_t *world, - ecs_iter_t *it, - ecs_observable_t *observable, - ecs_entity_t entity, - ecs_entity_t event, - const ecs_type_t *ids) -{ - ecs_id_t pair = ecs_pair(EcsWildcard, entity); - ecs_id_record_t *idr = flecs_id_record_get(world, pair); - if (!idr) { - return; - } + int32_t stage_index = ecs_get_stage_id(stage->thread_ctx); + int32_t stage_count = ecs_get_stage_count(world); - /* Iterate acyclic relationships */ - ecs_id_record_t *cur = idr; - while ((cur = cur->acyclic.next)) { - flecs_process_pending_tables(world); + ecs_worker_begin(stage->thread_ctx); - ecs_table_cache_iter_t idt; - if (!flecs_table_cache_iter(&cur->cache, &idt)) { - return; + ecs_iter_t *it = &pq->iters[stage_index]; + while (ecs_query_next(it)) { + if (flecs_pipeline_is_inactive(pq, it->table)) { + continue; /* Skip inactive systems */ } - ecs_entity_t rel = ECS_PAIR_FIRST(cur->id); - const ecs_table_record_t *tr; - while ((tr = flecs_table_cache_next(&idt, ecs_table_record_t))) { - ecs_table_t *table = tr->hdr.table; + EcsPoly *poly = flecs_pipeline_term_system(it); + int32_t i, count = it->count; + for(i = 0; i < count; i ++) { + ecs_entity_t e = it->entities[i]; + ecs_system_t *sys = ecs_poly(poly[i].poly, ecs_system_t); - int32_t e, entity_count = ecs_table_count(table); - it->table = table; - it->other_table = NULL; - it->offset = 0; - it->count = entity_count; + ecs_assert(sys->entity == e, ECS_INTERNAL_ERROR, NULL); - /* Treat as new event as this could invoke observers again for - * different tables. */ - world->event_id ++; + sys->last_frame = world->info.frame_count_total + 1; - flecs_set_observers_notify(it, observable, ids, event, - ecs_pair(rel, EcsWildcard)); + if (!stage_index || op->multi_threaded) { + ecs_stage_t *s = NULL; + if (!op->no_staging) { + s = stage; + } - if (!table->observed_count) { - continue; + ecs_run_intern(world, s, e, sys, stage_index, + stage_count, delta_time, 0, 0, NULL); } - ecs_entity_t *entities = ecs_storage_first(&table->data.entities); - ecs_record_t **records = ecs_storage_first(&table->data.records); + ran_since_merge ++; + world->info.systems_ran_frame ++; - for (e = 0; e < entity_count; e ++) { - uint32_t flags = ECS_RECORD_TO_ROW_FLAGS(records[e]->row); - if (flags & EcsEntityObservedAcyclic) { - /* Only notify for entities that are used in pairs with - * acyclic relationships */ - notify_subset(world, it, observable, entities[e], event, ids); + if (op != op_last && ran_since_merge == op->count) { + ran_since_merge = 0; + + /* 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. */ + bool rebuild = ecs_worker_sync(world, pq); + pq = (EcsPipeline*)ecs_get(world, pipeline, EcsPipeline); + if (rebuild) { + i = pq->cur_i; + count = it->count; } + + poly = flecs_pipeline_term_system(it); + op = pq->cur_op; + op_last = ecs_vector_last(pq->ops, ecs_pipeline_op_t); } } } + + ecs_worker_end(stage->thread_ctx); } -void flecs_emit( +bool ecs_progress( ecs_world_t *world, - ecs_world_t *stage, - ecs_event_desc_t *desc) + ecs_ftime_t user_delta_time) { - 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); - - const ecs_type_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_entity_t relationship = desc->relationship; - - if (!count) { - count = ecs_table_count(table) - row; - } - - ecs_iter_t it = { - .world = stage, - .real_world = world, - .table = table, - .field_count = 1, - .other_table = desc->other_table, - .offset = row, - .count = count, - .param = (void*)desc->param, - .flags = desc->table_event ? EcsIterTableOnly : 0 - }; + ecs_ftime_t delta_time = ecs_frame_begin(world, user_delta_time); + + ecs_dbg_3("#[bold]progress#[reset](dt = %.2f)", (double)delta_time); + ecs_log_push_3(); + ecs_run_pipeline(world, 0, delta_time); + ecs_log_pop_3(); - world->event_id ++; + ecs_frame_end(world); - ecs_observable_t *observable = ecs_get_observable(desc->observable); - ecs_check(observable != NULL, ECS_INVALID_PARAMETER, NULL); + return !ECS_BIT_IS_SET(world->flags, EcsWorldQuit); +} - if (!desc->relationship) { - flecs_observers_notify(&it, observable, ids, event); - } else { - flecs_set_observers_notify(&it, observable, ids, event, - ecs_pair(relationship, EcsWildcard)); - } +void ecs_set_time_scale( + ecs_world_t *world, + ecs_ftime_t scale) +{ + world->info.time_scale = scale; +} - if (count && !desc->table_event) { - if (!table->observed_count) { - return; - } +void ecs_reset_clock( + ecs_world_t *world) +{ + world->info.world_time_total = 0; + world->info.world_time_total_raw = 0; +} - ecs_record_t **recs = ecs_storage_get_t( - &table->data.records, ecs_record_t*, row); - for (i = 0; i < count; i ++) { - ecs_record_t *r = recs[i]; - if (!r) { - /* If the event is emitted after a bulk operation, it's possible - * that it hasn't been populated with entities yet. */ - continue; - } +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, EcsPipeline) != NULL, + ECS_INVALID_PARAMETER, "not a pipeline"); - uint32_t flags = ECS_RECORD_TO_ROW_FLAGS(recs[i]->row); - if (flags & EcsEntityObservedAcyclic) { - notify_subset(world, &it, observable, ecs_storage_first_t( - &table->data.entities, ecs_entity_t)[row + i], event, ids); - } - } - } - + world->pipeline = pipeline; error: return; } -void ecs_emit( - ecs_world_t *stage, - ecs_event_desc_t *desc) +ecs_entity_t ecs_get_pipeline( + const ecs_world_t *world) { - ecs_world_t *world = (ecs_world_t*)ecs_get_world(stage); - flecs_emit(world, stage, desc); + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + world = ecs_get_world(world); + return world->pipeline; +error: + return 0; } - -static -void flecs_query_compute_group_id( - ecs_query_t *query, - ecs_query_table_match_t *match) +ecs_entity_t ecs_pipeline_init( + ecs_world_t *world, + const ecs_pipeline_desc_t *desc) { - ecs_assert(match != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_poly_assert(world, ecs_world_t); + ecs_assert(desc != NULL, ECS_INVALID_PARAMETER, NULL); - if (query->group_by) { - ecs_table_t *table = match->table; - ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_entity_t result = desc->entity; + if (!result) { + result = ecs_new(world, 0); + } - match->group_id = query->group_by(query->world, table, - query->group_by_id, query->group_by_ctx); - } else { - match->group_id = 0; + ecs_query_desc_t qd = desc->query; + if (!qd.order_by) { + qd.order_by = flecs_entity_compare; + } + qd.entity = result; + + ecs_query_t *query = ecs_query_init(world, &qd); + if (!query) { + ecs_delete(world, result); + return 0; } + + ecs_assert(query->filter.terms[0].id == EcsSystem, + ECS_INVALID_PARAMETER, NULL); + + ecs_set(world, result, EcsPipeline, { + .query = query, + .match_count = -1, + .idr_inactive = flecs_id_record_ensure(world, EcsEmpty) + }); + + return result; } +/* -- Module implementation -- */ + static -ecs_query_table_list_t* flecs_query_get_group( - ecs_query_t *query, - uint64_t group_id) +void FlecsPipelineFini( + ecs_world_t *world, + void *ctx) { - return ecs_map_get(&query->groups, ecs_query_table_list_t, group_id); + (void)ctx; + if (ecs_get_stage_count(world)) { + ecs_set_threads(world, 0); + } + + ecs_assert(world->workers_running == 0, ECS_INTERNAL_ERROR, NULL); } +#define flecs_bootstrap_phase(world, phase, depends_on)\ + flecs_bootstrap_tag(world, phase);\ + _flecs_bootstrap_phase(world, phase, depends_on) static -ecs_query_table_list_t* flecs_query_ensure_group( - ecs_query_t *query, - uint64_t group_id) +void _flecs_bootstrap_phase( + ecs_world_t *world, + ecs_entity_t phase, + ecs_entity_t depends_on) { - return ecs_map_ensure(&query->groups, ecs_query_table_list_t, group_id); + ecs_add_id(world, phase, EcsPhase); + if (depends_on) { + ecs_add_pair(world, phase, EcsDependsOn, depends_on); + } } -/* Find the last node of the group after which this group should be inserted */ -static -ecs_query_table_node_t* flecs_query_find_group_insertion_node( - ecs_query_t *query, - uint64_t group_id) +void FlecsPipelineImport( + ecs_world_t *world) { - /* Grouping must be enabled */ - ecs_assert(query->group_by != NULL, ECS_INTERNAL_ERROR, NULL); + ECS_MODULE(world, FlecsPipeline); + ECS_IMPORT(world, FlecsSystem); - 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; + ecs_set_name_prefix(world, "Ecs"); - /* Find closest smaller group id */ - while ((list = ecs_map_next(&it, ecs_query_table_list_t, &id))) { - if (id >= group_id) { - continue; - } + flecs_bootstrap_component(world, EcsPipeline); + flecs_bootstrap_tag(world, EcsPhase); - if (!list->last) { - ecs_assert(list->first == NULL, ECS_INTERNAL_ERROR, NULL); - continue; - } + flecs_bootstrap_phase(world, EcsPreFrame, 0); + flecs_bootstrap_phase(world, EcsOnLoad, EcsPreFrame); + flecs_bootstrap_phase(world, EcsPostLoad, EcsOnLoad); + flecs_bootstrap_phase(world, EcsPreUpdate, EcsPostLoad); + flecs_bootstrap_phase(world, EcsOnUpdate, EcsPreUpdate); + flecs_bootstrap_phase(world, EcsOnValidate, EcsOnUpdate); + flecs_bootstrap_phase(world, EcsPostUpdate, EcsOnValidate); + flecs_bootstrap_phase(world, EcsPreStore, EcsPostUpdate); + flecs_bootstrap_phase(world, EcsOnStore, EcsPreStore); + flecs_bootstrap_phase(world, EcsPostFrame, EcsOnStore); - if (!closest_list || ((group_id - id) < (group_id - closest_id))) { - closest_id = id; - closest_list = list; + ecs_set_hooks(world, EcsPipeline, { + .ctor = ecs_default_ctor, + .dtor = ecs_dtor(EcsPipeline) + }); + + world->pipeline = ecs_pipeline_init(world, &(ecs_pipeline_desc_t){ + .entity = ecs_entity(world, { .name = "BuiltinPipeline" }), + .query = { + .filter.terms = { + { .id = EcsSystem }, + { .id = EcsPhase, .src.flags = EcsCascade, .src.trav = EcsDependsOn } + }, + .order_by = flecs_entity_compare } - } + }); - if (closest_list) { - return closest_list->last; - } else { - return NULL; /* Group should be first in query */ - } + /* Cleanup thread administration when world is destroyed */ + ecs_atfini(world, FlecsPipelineFini, NULL); } -/* Initialize group with first node */ -static -void flecs_query_create_group( - ecs_query_t *query, - ecs_query_table_node_t *node) -{ - ecs_query_table_match_t *match = node->match; - uint64_t group_id = match->group_id; +#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 = flecs_query_find_group_insertion_node( - query, group_id); - 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); +#ifdef FLECS_MONITOR - /* 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_COMPONENT_DECLARE(FlecsMonitor); +ECS_COMPONENT_DECLARE(EcsWorldStats); +ECS_COMPONENT_DECLARE(EcsPipelineStats); + +ecs_entity_t EcsPeriod1s = 0; +ecs_entity_t EcsPeriod1m = 0; +ecs_entity_t EcsPeriod1h = 0; +ecs_entity_t EcsPeriod1d = 0; +ecs_entity_t EcsPeriod1w = 0; + +static int32_t flecs_day_interval_count = 24; +static int32_t flecs_week_interval_count = 168; static -void flecs_query_remove_group( - ecs_query_t *query, - uint64_t group_id) -{ - ecs_map_remove(&query->groups, group_id); -} +ECS_COPY(EcsPipelineStats, dst, src, { + (void)dst; + (void)src; + ecs_abort(ECS_INVALID_OPERATION, "cannot copy pipeline stats component"); +}) -/* Find the list the node should be part of */ static -ecs_query_table_list_t* flecs_query_get_node_list( - ecs_query_t *query, - ecs_query_table_node_t *node) -{ - ecs_query_table_match_t *match = node->match; - if (query->group_by) { - return flecs_query_get_group(query, match->group_id); - } else { - return &query->list; - } -} +ECS_MOVE(EcsPipelineStats, dst, src, { + ecs_os_memcpy_t(dst, src, EcsPipelineStats); + ecs_os_zeromem(src); +}) -/* Find or create the list the node should be part of */ static -ecs_query_table_list_t* flecs_query_ensure_node_list( - ecs_query_t *query, - ecs_query_table_node_t *node) -{ - ecs_query_table_match_t *match = node->match; - if (query->group_by) { - return flecs_query_ensure_group(query, match->group_id); - } else { - return &query->list; - } -} +ECS_DTOR(EcsPipelineStats, ptr, { + ecs_pipeline_stats_fini(&ptr->stats); +}) -/* Remove node from list */ static -void flecs_query_remove_table_node( - ecs_query_t *query, - ecs_query_table_node_t *node) -{ - 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_query_table_list_t *list = flecs_query_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; - } - - 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 MonitorStats(ecs_iter_t *it) { + ecs_world_t *world = it->real_world; - ecs_assert(list->count > 0, ECS_INTERNAL_ERROR, NULL); - list->count --; + EcsStatsHeader *hdr = ecs_field_w_size(it, 0, 1); + ecs_id_t kind = ecs_pair_first(it->world, ecs_field_id(it, 1)); + void *stats = ECS_OFFSET_T(hdr, EcsStatsHeader); - if (query->group_by) { - ecs_query_table_match_t *match = node->match; - uint64_t group_id = match->group_id; + ecs_ftime_t elapsed = hdr->elapsed; + hdr->elapsed += it->delta_time; - /* 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; - } + int32_t t_last = (int32_t)(elapsed * 60); + int32_t t_next = (int32_t)(hdr->elapsed * 60); + int32_t i, dif = t_last - t_next; - ecs_assert(query->list.count > 0, ECS_INTERNAL_ERROR, NULL); - query->list.count --; + ecs_world_stats_t last_world = {0}; + ecs_pipeline_stats_t last_pipeline = {0}; + void *last = NULL; - /* 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; + if (!dif) { + /* Copy last value so we can pass it to reduce_last */ + if (kind == ecs_id(EcsWorldStats)) { + last = &last_world; + ecs_world_stats_copy_last(&last_world, stats); + } else if (kind == ecs_id(EcsPipelineStats)) { + last = &last_pipeline; + ecs_pipeline_stats_copy_last(&last_pipeline, stats); } + } - /* 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 */ - flecs_query_remove_group(query, group_id); - list = NULL; - } + if (kind == ecs_id(EcsWorldStats)) { + ecs_world_stats_get(world, stats); + } else if (kind == ecs_id(EcsPipelineStats)) { + ecs_pipeline_stats_get(world, ecs_get_pipeline(world), stats); } - if (list) { - if (list->first == node) { - list->first = next; + if (!dif) { + /* Still in same interval, combine with last measurement */ + hdr->reduce_count ++; + if (kind == ecs_id(EcsWorldStats)) { + ecs_world_stats_reduce_last(stats, last, hdr->reduce_count); + } else if (kind == ecs_id(EcsPipelineStats)) { + ecs_pipeline_stats_reduce_last(stats, last, hdr->reduce_count); } - if (list->last == node) { - list->last = prev; + } else if (dif > 1) { + /* More than 16ms has passed, backfill */ + for (i = 1; i < dif; i ++) { + if (kind == ecs_id(EcsWorldStats)) { + ecs_world_stats_repeat_last(stats); + } else if (kind == ecs_id(EcsPipelineStats)) { + ecs_world_stats_repeat_last(stats); + } } + hdr->reduce_count = 0; } - node->prev = NULL; - node->next = NULL; - - query->match_count ++; + if (last && kind == ecs_id(EcsPipelineStats)) { + ecs_pipeline_stats_fini(last); + } } -/* Add node to list */ static -void flecs_query_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); - - /* If this is the first match, activate system */ - if (!query->list.first && query->entity) { - ecs_remove_id(query->world, query->entity, EcsEmpty); - } +void ReduceStats(ecs_iter_t *it) { + void *dst = ecs_field_w_size(it, 0, 1); + void *src = ecs_field_w_size(it, 0, 2); - flecs_query_compute_group_id(query, node->match); + ecs_id_t kind = ecs_pair_first(it->world, ecs_field_id(it, 1)); - ecs_query_table_list_t *list = flecs_query_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); + dst = ECS_OFFSET_T(dst, EcsStatsHeader); + src = ECS_OFFSET_T(src, EcsStatsHeader); - ecs_query_table_node_t *last = list->last; - ecs_query_table_node_t *last_next = last->next; + if (kind == ecs_id(EcsWorldStats)) { + ecs_world_stats_reduce(dst, src); + } else if (kind == ecs_id(EcsPipelineStats)) { + ecs_pipeline_stats_reduce(dst, src); + } +} - node->prev = last; - node->next = last_next; - last->next = node; +static +void AggregateStats(ecs_iter_t *it) { + int32_t interval = *(int32_t*)it->ctx; - if (last_next) { - last_next->prev = node; - } + EcsStatsHeader *dst_hdr = ecs_field_w_size(it, 0, 1); + EcsStatsHeader *src_hdr = ecs_field_w_size(it, 0, 2); - list->last = node; + void *dst = ECS_OFFSET_T(dst_hdr, EcsStatsHeader); + void *src = ECS_OFFSET_T(src_hdr, EcsStatsHeader); - 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); + ecs_id_t kind = ecs_pair_first(it->world, ecs_field_id(it, 1)); - list->first = node; - list->last = node; + ecs_world_stats_t last_world = {0}; + ecs_pipeline_stats_t last_pipeline = {0}; + void *last = NULL; - if (query->group_by) { - /* Initialize group with its first node */ - flecs_query_create_group(query, node); + if (dst_hdr->reduce_count != 0) { + /* Copy last value so we can pass it to reduce_last */ + if (kind == ecs_id(EcsWorldStats)) { + last_world.t = 0; + ecs_world_stats_copy_last(&last_world, dst); + last = &last_world; + } else if (kind == ecs_id(EcsPipelineStats)) { + last_pipeline.t = 0; + ecs_pipeline_stats_copy_last(&last_pipeline, dst); + last = &last_pipeline; } } - if (query->group_by) { - query->list.count ++; + /* Reduce from minutes to the current day */ + if (kind == ecs_id(EcsWorldStats)) { + ecs_world_stats_reduce(dst, src); + } else if (kind == ecs_id(EcsPipelineStats)) { + ecs_pipeline_stats_reduce(dst, src); } - list->count ++; - query->match_count ++; + if (dst_hdr->reduce_count != 0) { + if (kind == ecs_id(EcsWorldStats)) { + ecs_world_stats_reduce_last(dst, last, dst_hdr->reduce_count); + } else if (kind == ecs_id(EcsPipelineStats)) { + ecs_pipeline_stats_reduce_last(dst, last, dst_hdr->reduce_count); + } + } - ecs_assert(node->prev != node, ECS_INTERNAL_ERROR, NULL); - ecs_assert(node->next != node, ECS_INTERNAL_ERROR, NULL); + /* A day has 60 24 minute intervals */ + dst_hdr->reduce_count ++; + if (dst_hdr->reduce_count >= interval) { + dst_hdr->reduce_count = 0; + } - 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); + if (last && kind == ecs_id(EcsPipelineStats)) { + ecs_pipeline_stats_fini(last); + } } static -ecs_query_table_match_t* flecs_query_cache_add( - ecs_query_table_t *elem) +void flecs_stats_monitor_import( + ecs_world_t *world, + ecs_id_t kind, + size_t size) { - ecs_query_table_match_t *result = ecs_os_calloc_t(ecs_query_table_match_t); - ecs_query_table_node_t *node = &result->node; + ecs_entity_t prev = ecs_set_scope(world, kind); - 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; - } + // Called each frame, collects 60 measurements per second + ecs_system_init(world, &(ecs_system_desc_t){ + .entity = ecs_entity(world, { .name = "Monitor1s", .add = {ecs_dependson(EcsPreFrame)} }), + .query.filter.terms = {{ + .id = ecs_pair(kind, EcsPeriod1s), + .src.id = EcsWorld + }}, + .callback = MonitorStats + }); - return result; -} + // Called each second, reduces into 60 measurements per minute + ecs_entity_t mw1m = ecs_system_init(world, &(ecs_system_desc_t){ + .entity = ecs_entity(world, { .name = "Monitor1m", .add = {ecs_dependson(EcsPreFrame)} }), + .query.filter.terms = {{ + .id = ecs_pair(kind, EcsPeriod1m), + .src.id = EcsWorld + }, { + .id = ecs_pair(kind, EcsPeriod1s), + .src.id = EcsWorld + }}, + .callback = ReduceStats, + .interval = 1.0 + }); -typedef struct { - ecs_table_t *table; - int32_t *dirty_state; - int32_t column; -} table_dirty_state_t; + // Called each minute, reduces into 60 measurements per hour + ecs_system_init(world, &(ecs_system_desc_t){ + .entity = ecs_entity(world, { .name = "Monitor1h", .add = {ecs_dependson(EcsPreFrame)} }), + .query.filter.terms = {{ + .id = ecs_pair(kind, EcsPeriod1h), + .src.id = EcsWorld + }, { + .id = ecs_pair(kind, EcsPeriod1m), + .src.id = EcsWorld + }}, + .callback = ReduceStats, + .rate = 60, + .tick_source = mw1m + }); -static -void flecs_query_get_dirty_state( - ecs_query_t *query, - ecs_query_table_match_t *match, - int32_t term, - table_dirty_state_t *out) -{ - ecs_world_t *world = query->world; - ecs_entity_t subject = match->sources[term]; - int32_t column; + // Called each minute, reduces into 60 measurements per day + ecs_system_init(world, &(ecs_system_desc_t){ + .entity = ecs_entity(world, { .name = "Monitor1d", .add = {ecs_dependson(EcsPreFrame)} }), + .query.filter.terms = {{ + .id = ecs_pair(kind, EcsPeriod1d), + .src.id = EcsWorld + }, { + .id = ecs_pair(kind, EcsPeriod1m), + .src.id = EcsWorld + }}, + .callback = AggregateStats, + .rate = 60, + .tick_source = mw1m, + .ctx = &flecs_day_interval_count + }); - if (!subject) { - out->table = match->table; - column = match->columns[term]; - if (column == -1) { - column = 0; - } - } else { - out->table = ecs_get_table(world, subject); - column = -match->columns[term]; - } + // Called each hour, reduces into 60 measurements per week + ecs_system_init(world, &(ecs_system_desc_t){ + .entity = ecs_entity(world, { .name = "Monitor1w", .add = {ecs_dependson(EcsPreFrame)} }), + .query.filter.terms = {{ + .id = ecs_pair(kind, EcsPeriod1w), + .src.id = EcsWorld + }, { + .id = ecs_pair(kind, EcsPeriod1h), + .src.id = EcsWorld + }}, + .callback = AggregateStats, + .rate = 60, + .tick_source = mw1m, + .ctx = &flecs_week_interval_count + }); - out->dirty_state = flecs_table_get_dirty_state(out->table); + ecs_set_scope(world, prev); - if (column) { - out->column = ecs_table_type_to_storage_index(out->table, column - 1); - } else { - out->column = -1; - } + ecs_set_id(world, EcsWorld, ecs_pair(kind, EcsPeriod1s), size, NULL); + ecs_set_id(world, EcsWorld, ecs_pair(kind, EcsPeriod1m), size, NULL); + ecs_set_id(world, EcsWorld, ecs_pair(kind, EcsPeriod1h), size, NULL); + ecs_set_id(world, EcsWorld, ecs_pair(kind, EcsPeriod1d), size, NULL); + ecs_set_id(world, EcsWorld, ecs_pair(kind, EcsPeriod1w), size, NULL); } -/* Get match monitor. Monitors are used to keep track of whether components - * matched by the query in a table have changed. */ static -bool flecs_query_get_match_monitor( - ecs_query_t *query, - ecs_query_table_match_t *match) +void flecs_world_monitor_import( + ecs_world_t *world) { - if (match->monitor) { - return false; - } + ECS_COMPONENT_DEFINE(world, EcsWorldStats); - int32_t *monitor = ecs_os_calloc_n(int32_t, query->filter.term_count + 1); + flecs_stats_monitor_import(world, ecs_id(EcsWorldStats), + sizeof(EcsWorldStats)); +} - /* Mark terms that don't need to be monitored. This saves time when reading - * and/or updating the monitor. */ - const ecs_filter_t *f = &query->filter; - int32_t i, t = -1, term_count = f->term_count; - table_dirty_state_t cur_dirty_state; +static +void flecs_pipeline_monitor_import( + ecs_world_t *world) +{ + ECS_COMPONENT_DEFINE(world, EcsPipelineStats); - for (i = 0; i < term_count; i ++) { - if (t == f->terms[i].field_index) { - if (monitor[t + 1] != -1) { - continue; - } - } + ecs_set_hooks(world, EcsPipelineStats, { + .ctor = ecs_default_ctor, + .copy = ecs_copy(EcsPipelineStats), + .move = ecs_move(EcsPipelineStats), + .dtor = ecs_dtor(EcsPipelineStats) + }); - t = f->terms[i].field_index; - monitor[t + 1] = -1; + flecs_stats_monitor_import(world, ecs_id(EcsPipelineStats), + sizeof(EcsPipelineStats)); +} - if (f->terms[i].inout != EcsIn && - f->terms[i].inout != EcsInOut && - f->terms[i].inout != EcsInOutDefault) { - continue; /* If term isn't read, don't monitor */ - } +void FlecsMonitorImport( + ecs_world_t *world) +{ + ECS_MODULE_DEFINE(world, FlecsMonitor); - int32_t column = match->columns[t]; - if (column == 0) { - continue; /* Don't track terms that aren't matched */ - } + ecs_set_name_prefix(world, "Ecs"); - flecs_query_get_dirty_state(query, match, t, &cur_dirty_state); - if (cur_dirty_state.column == -1) { - continue; /* Don't track terms that aren't stored */ - } + EcsPeriod1s = ecs_new_entity(world, "EcsPeriod1s"); + EcsPeriod1m = ecs_new_entity(world, "EcsPeriod1m"); + EcsPeriod1h = ecs_new_entity(world, "EcsPeriod1h"); + EcsPeriod1d = ecs_new_entity(world, "EcsPeriod1d"); + EcsPeriod1w = ecs_new_entity(world, "EcsPeriod1w"); - monitor[t + 1] = 0; + flecs_world_monitor_import(world); + flecs_pipeline_monitor_import(world); + + if (ecs_os_has_time()) { + ecs_measure_frame_time(world, true); + ecs_measure_system_time(world, true); } +} - match->monitor = monitor; +#endif - query->flags |= EcsQueryHasMonitor; - return true; -} +#ifdef FLECS_TIMER -/* Synchronize match monitor with table dirty state */ static -void flecs_query_sync_match_monitor( - ecs_query_t *query, - ecs_query_table_match_t *match) -{ - ecs_assert(match != NULL, ECS_INTERNAL_ERROR, NULL); - if (!match->monitor) { - if (query->flags & EcsQueryHasMonitor) { - flecs_query_get_match_monitor(query, match); - } else { - return; - } +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 *monitor = match->monitor; - ecs_table_t *table = match->table; - int32_t *dirty_state = flecs_table_get_dirty_state(table); - ecs_assert(dirty_state != NULL, ECS_INTERNAL_ERROR, NULL); - table_dirty_state_t cur; +static +void ProgressTimers(ecs_iter_t *it) { + EcsTimer *timer = ecs_field(it, EcsTimer, 1); + EcsTickSource *tick_source = ecs_field(it, EcsTickSource, 2); - monitor[0] = dirty_state[0]; /* Did table gain/lose entities */ + ecs_assert(timer != NULL, ECS_INTERNAL_ERROR, NULL); - int32_t i, term_count = query->filter.term_count; - for (i = 0; i < term_count; i ++) { - int32_t t = query->filter.terms[i].field_index; - if (monitor[t + 1] == -1) { + int i; + for (i = 0; i < it->count; i ++) { + tick_source[i].tick = false; + + if (!timer[i].active) { continue; } - - flecs_query_get_dirty_state(query, match, t, &cur); - ecs_assert(cur.column != -1, ECS_INTERNAL_ERROR, NULL); - monitor[t + 1] = cur.dirty_state[cur.column + 1]; - } -} -/* Check if single match term has changed */ -static -bool flecs_query_check_match_monitor_term( - ecs_query_t *query, - ecs_query_table_match_t *match, - int32_t term) -{ - ecs_assert(match != NULL, ECS_INTERNAL_ERROR, NULL); + const ecs_world_info_t *info = ecs_get_world_info(it->world); + ecs_ftime_t time_elapsed = timer[i].time + info->delta_time_raw; + ecs_ftime_t timeout = timer[i].timeout; + + if (time_elapsed >= timeout) { + ecs_ftime_t t = time_elapsed - timeout; + if (t > timeout) { + t = 0; + } - if (flecs_query_get_match_monitor(query, match)) { - return true; - } - - int32_t *monitor = match->monitor; - ecs_table_t *table = match->table; - int32_t *dirty_state = flecs_table_get_dirty_state(table); - ecs_assert(dirty_state != NULL, ECS_INTERNAL_ERROR, NULL); - table_dirty_state_t cur; + timer[i].time = t; /* Initialize with remainder */ + tick_source[i].tick = true; + tick_source[i].time_elapsed = time_elapsed; - int32_t state = monitor[term]; - if (state == -1) { - return false; + if (timer[i].single_shot) { + timer[i].active = false; + } + } else { + timer[i].time = time_elapsed; + } } +} - if (!term) { - return monitor[0] != dirty_state[0]; - } +static +void ProgressRateFilters(ecs_iter_t *it) { + EcsRateFilter *filter = ecs_field(it, EcsRateFilter, 1); + EcsTickSource *tick_dst = ecs_field(it, EcsTickSource, 2); - flecs_query_get_dirty_state(query, match, term - 1, &cur); - ecs_assert(cur.column != -1, ECS_INTERNAL_ERROR, NULL); + int i; + for (i = 0; i < it->count; i ++) { + ecs_entity_t src = filter[i].src; + bool inc = false; - return monitor[term] != cur.dirty_state[cur.column + 1]; -} + filter[i].time_elapsed += it->delta_time; -/* Check if any term for match has changed */ -static -bool flecs_query_check_match_monitor( - ecs_query_t *query, - ecs_query_table_match_t *match) -{ - ecs_assert(match != NULL, ECS_INTERNAL_ERROR, NULL); + 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; + } - if (flecs_query_get_match_monitor(query, match)) { - return 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; + } } +} - int32_t *monitor = match->monitor; - ecs_table_t *table = match->table; - int32_t *dirty_state = flecs_table_get_dirty_state(table); - ecs_assert(dirty_state != NULL, ECS_INTERNAL_ERROR, NULL); - table_dirty_state_t cur; +static +void ProgressTickSource(ecs_iter_t *it) { + EcsTickSource *tick_src = ecs_field(it, EcsTickSource, 1); - if (monitor[0] != dirty_state[0]) { - return true; + /* 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; } +} - ecs_filter_t *f = &query->filter; - int32_t i, term_count = f->term_count; - for (i = 0; i < term_count; i ++) { - ecs_term_t *term = &f->terms[i]; - int32_t t = term->field_index; - if (monitor[t + 1] == -1) { - continue; - } +ecs_entity_t ecs_set_timeout( + ecs_world_t *world, + ecs_entity_t timer, + ecs_ftime_t timeout) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - flecs_query_get_dirty_state(query, match, t, &cur); - ecs_assert(cur.column != -1, ECS_INTERNAL_ERROR, NULL); + timer = ecs_set(world, timer, EcsTimer, { + .timeout = timeout, + .single_shot = true, + .active = true + }); - if (monitor[t + 1] != cur.dirty_state[cur.column + 1]) { - return true; - } + ecs_system_t *system_data = ecs_poly_get(world, timer, ecs_system_t); + if (system_data) { + system_data->tick_source = timer; } - return false; +error: + return timer; } -/* Check if any term for matched table has changed */ -static -bool flecs_query_check_table_monitor( - ecs_query_t *query, - ecs_query_table_t *table, - int32_t term) +ecs_ftime_t ecs_get_timeout( + const ecs_world_t *world, + ecs_entity_t timer) { - ecs_query_table_node_t *cur, *end = table->last->node.next; + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(timer != 0, ECS_INVALID_PARAMETER, NULL); - for (cur = &table->first->node; cur != end; cur = cur->next) { - ecs_query_table_match_t *match = (ecs_query_table_match_t*)cur; - if (term == -1) { - if (flecs_query_check_match_monitor(query, match)) { - return true; - } - } else { - if (flecs_query_check_match_monitor_term(query, match, term)) { - return true; - } - } + const EcsTimer *value = ecs_get(world, timer, EcsTimer); + if (value) { + return value->timeout; } - - return false; +error: + return 0; } -static -bool flecs_query_check_query_monitor( - ecs_query_t *query) +ecs_entity_t ecs_set_interval( + ecs_world_t *world, + ecs_entity_t timer, + ecs_ftime_t interval) { - ecs_table_cache_iter_t it; - if (flecs_table_cache_iter(&query->cache, &it)) { - ecs_query_table_t *qt; - while ((qt = flecs_table_cache_next(&it, ecs_query_table_t))) { - if (flecs_query_check_table_monitor(query, qt, -1)) { - return true; - } - } - } + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - return false; + timer = ecs_set(world, timer, EcsTimer, { + .timeout = interval, + .active = true + }); + + ecs_system_t *system_data = ecs_poly_get(world, timer, ecs_system_t); + if (system_data) { + system_data->tick_source = timer; + } +error: + return timer; } -static -void flecs_query_init_query_monitors( - ecs_query_t *query) +ecs_ftime_t ecs_get_interval( + const ecs_world_t *world, + ecs_entity_t timer) { - ecs_query_table_node_t *cur = query->list.first; + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - /* Ensure each match has a monitor */ - for (; cur != NULL; cur = cur->next) { - ecs_query_table_match_t *match = (ecs_query_table_match_t*)cur; - flecs_query_get_match_monitor(query, match); + if (!timer) { + return 0; } + + const EcsTimer *value = ecs_get(world, timer, EcsTimer); + if (value) { + return value->timeout; + } +error: + return 0; } -/* The group by function for cascade computes the tree depth for the table type. - * This causes tables in the query cache to be ordered by depth, which ensures - * breadth-first iteration order. */ -static -uint64_t flecs_query_group_by_cascade( +void ecs_start_timer( ecs_world_t *world, - ecs_table_t *table, - ecs_id_t id, - void *ctx) + ecs_entity_t timer) { - (void)id; - ecs_term_t *term = ctx; - ecs_entity_t rel = term->src.trav; - int32_t depth = flecs_relation_depth(world, rel, table); - return flecs_ito(uint64_t, depth); + EcsTimer *ptr = ecs_get_mut(world, timer, EcsTimer); + ecs_check(ptr != NULL, ECS_INVALID_PARAMETER, NULL); + ptr->active = true; + ptr->time = 0; +error: + return; } -static -ecs_vector_t* flecs_query_add_ref( +void ecs_stop_timer( 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_size_t size) + ecs_entity_t timer) { - ecs_assert(entity != 0, ECS_INTERNAL_ERROR, NULL); + EcsTimer *ptr = ecs_get_mut(world, timer, EcsTimer); + ecs_check(ptr != NULL, ECS_INVALID_PARAMETER, NULL); + ptr->active = false; +error: + return; +} - ecs_ref_t *ref = ecs_vector_add(&references, ecs_ref_t); - ecs_term_id_t *src = &term->src; +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); - if (!(src->flags & EcsCascade)) { - ecs_assert(entity != 0, ECS_INTERNAL_ERROR, NULL); - } - - if (size) { - *ref = ecs_ref_init_id(world, entity, component); - } else { - *ref = (ecs_ref_t){ - .entity = entity, - .id = 0 - }; - } + filter = ecs_set(world, filter, EcsRateFilter, { + .rate = rate, + .src = source + }); - query->flags |= EcsQueryHasRefs; + ecs_system_t *system_data = ecs_poly_get(world, filter, ecs_system_t); + if (system_data) { + system_data->tick_source = filter; + } - return references; +error: + return filter; } -static -ecs_query_table_match_t* flecs_query_add_table_match( - ecs_query_t *query, - ecs_query_table_t *qt, - ecs_table_t *table) +void ecs_set_tick_source( + ecs_world_t *world, + ecs_entity_t system, + ecs_entity_t tick_source) { - ecs_filter_t *filter = &query->filter; - int32_t term_count = filter->term_count; - - /* Add match for table. One table can have more than one match, if - * the query contains wildcards. */ - ecs_query_table_match_t *qm = flecs_query_cache_add(qt); - qm->table = table; - qm->columns = ecs_os_malloc_n(int32_t, term_count); - qm->ids = ecs_os_malloc_n(ecs_id_t, term_count); - qm->sources = ecs_os_malloc_n(ecs_entity_t, term_count); - qm->sizes = ecs_os_malloc_n(ecs_size_t, term_count); + 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); - /* Insert match to iteration list if table is not empty */ - if (!table || ecs_table_count(table) != 0) { - flecs_query_insert_table_node(query, &qm->node); - } + ecs_system_t *system_data = ecs_poly_get(world, system, ecs_system_t); + ecs_check(system_data != NULL, ECS_INVALID_PARAMETER, NULL); - return qm; + system_data->tick_source = tick_source; +error: + return; } -static -void flecs_query_set_table_match( - ecs_world_t *world, - ecs_query_t *query, - ecs_query_table_match_t *qm, - ecs_table_t *table, - ecs_iter_t *it) +void FlecsTimerImport( + ecs_world_t *world) { - ecs_filter_t *filter = &query->filter; - int32_t i, term_count = filter->term_count; - int32_t field_count = filter->field_count; - ecs_term_t *terms = filter->terms; - - /* Reset resources in case this is an existing record */ - if (qm->sparse_columns) { - ecs_vector_free(qm->sparse_columns); - qm->sparse_columns = NULL; - } - if (qm->bitset_columns) { - ecs_vector_free(qm->bitset_columns); - qm->bitset_columns = NULL; - } - if (qm->references) { - ecs_os_free(qm->references); - qm->references = NULL; - } + ECS_MODULE(world, FlecsTimer); - ecs_os_memcpy_n(qm->columns, it->columns, int32_t, field_count); - ecs_os_memcpy_n(qm->ids, it->ids, ecs_id_t, field_count); - ecs_os_memcpy_n(qm->sources, it->sources, ecs_entity_t, field_count); - ecs_os_memcpy_n(qm->sizes, it->sizes, ecs_size_t, field_count); + ECS_IMPORT(world, FlecsPipeline); - /* Look for union & disabled terms */ - if (table) { - if (table->flags & EcsTableHasUnion) { - for (i = 0; i < term_count; i ++) { - if (ecs_term_match_0(&terms[i])) { - continue; - } + ecs_set_name_prefix(world, "Ecs"); - ecs_id_t id = terms[i].id; - if (ECS_HAS_ID_FLAG(id, PAIR) && ECS_PAIR_SECOND(id) == EcsWildcard) { - continue; - } - - int32_t actual_index = terms[i].field_index; - int32_t column = it->columns[actual_index]; - if (column <= 0) { - continue; - } + flecs_bootstrap_component(world, EcsTimer); + flecs_bootstrap_component(world, EcsRateFilter); - ecs_id_t table_id = table->type.array[column - 1]; - if (ECS_PAIR_FIRST(table_id) != EcsUnion) { - continue; - } + /* Add EcsTickSource to timers and rate filters */ + ecs_system_init(world, &(ecs_system_desc_t){ + .entity = ecs_entity(world, {.name = "AddTickSource", .add = { ecs_dependson(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 + }); - flecs_switch_term_t *sc = ecs_vector_add( - &qm->sparse_columns, flecs_switch_term_t); - sc->signature_column_index = actual_index; - sc->sw_case = ECS_PAIR_SECOND(id); - sc->sw_column = NULL; - qm->ids[actual_index] = id; - } - } - if (table->flags & EcsTableHasToggle) { - for (i = 0; i < term_count; i ++) { - if (ecs_term_match_0(&terms[i])) { - continue; - } + /* Timer handling */ + ecs_system_init(world, &(ecs_system_desc_t){ + .entity = ecs_entity(world, {.name = "ProgressTimers", .add = { ecs_dependson(EcsPreFrame)}}), + .query.filter.terms = { + { .id = ecs_id(EcsTimer) }, + { .id = ecs_id(EcsTickSource) } + }, + .callback = ProgressTimers + }); - int32_t actual_index = terms[i].field_index; - ecs_id_t id = it->ids[actual_index]; - ecs_id_t bs_id = ECS_TOGGLE | id; - int32_t bs_index = ecs_search(world, table, bs_id, 0); + /* Rate filter handling */ + ecs_system_init(world, &(ecs_system_desc_t){ + .entity = ecs_entity(world, {.name = "ProgressRateFilters", .add = { ecs_dependson(EcsPreFrame)}}), + .query.filter.terms = { + { .id = ecs_id(EcsRateFilter), .inout = EcsIn }, + { .id = ecs_id(EcsTickSource), .inout = EcsOut } + }, + .callback = ProgressRateFilters + }); - if (bs_index != -1) { - flecs_bitset_term_t *bc = ecs_vector_add( - &qm->bitset_columns, flecs_bitset_term_t); - bc->column_index = bs_index; - bc->bs_column = NULL; - } - } - } - } + /* TickSource without a timer or rate filter just increases each frame */ + ecs_system_init(world, &(ecs_system_desc_t){ + .entity = ecs_entity(world, { .name = "ProgressTickSource", .add = { ecs_dependson(EcsPreFrame)}}), + .query.filter.terms = { + { .id = ecs_id(EcsTickSource), .inout = EcsOut }, + { .id = ecs_id(EcsRateFilter), .oper = EcsNot }, + { .id = ecs_id(EcsTimer), .oper = EcsNot } + }, + .callback = ProgressTickSource + }); +} - /* Add references for substituted terms */ - ecs_vector_t *refs = NULL; - for (i = 0; i < term_count; i ++) { - ecs_term_t *term = &terms[i]; - if (!ecs_term_match_this(term)) { - /* non-This terms are set during iteration */ - continue; - } +#endif - int32_t actual_index = terms[i].field_index; - ecs_entity_t src = it->sources[actual_index]; - ecs_size_t size = 0; - if (it->sizes) { - size = it->sizes[actual_index]; - } - if (src) { - ecs_id_t id = it->ids[actual_index]; - ecs_assert(ecs_is_valid(world, src), ECS_INTERNAL_ERROR, NULL); +#include - if (id) { - refs = flecs_query_add_ref(world, query, refs, term, id, src, size); +/* Utilities for C++ API */ - /* Use column index to bind term and ref */ - if (qm->columns[actual_index] != 0) { - qm->columns[actual_index] = -ecs_vector_count(refs); - } - } - } - } - if (refs) { - int32_t count = ecs_vector_count(refs); - ecs_ref_t *ptr = ecs_vector_first(refs, ecs_ref_t); - qm->references = ecs_os_memdup_n(ptr, ecs_ref_t, count); - ecs_vector_free(refs); - } -} +#ifdef FLECS_CPP -/** Populate query cache with tables */ -static -void flecs_query_match_tables( - ecs_world_t *world, - ecs_query_t *query) -{ - ecs_table_t *table = NULL; - ecs_query_table_t *qt = NULL; +/* Convert compiler-specific typenames extracted from __PRETTY_FUNCTION__ to + * a uniform identifier */ - ecs_iter_t it = ecs_filter_iter(world, &query->filter); - ECS_BIT_SET(it.flags, EcsIterIsInstanced); - ECS_BIT_SET(it.flags, EcsIterIsFilter); - ECS_BIT_SET(it.flags, EcsIterEntityOptional); +#define ECS_CONST_PREFIX "const " +#define ECS_STRUCT_PREFIX "struct " +#define ECS_CLASS_PREFIX "class " +#define ECS_ENUM_PREFIX "enum " - while (ecs_filter_next(&it)) { - if ((table != it.table) || (!it.table && !qt)) { - /* New table matched, add record to cache */ - qt = ecs_os_calloc_t(ecs_query_table_t); - ecs_table_cache_insert(&query->cache, it.table, &qt->hdr); - table = it.table; - } +#define ECS_CONST_LEN (-1 + (ecs_size_t)sizeof(ECS_CONST_PREFIX)) +#define ECS_STRUCT_LEN (-1 + (ecs_size_t)sizeof(ECS_STRUCT_PREFIX)) +#define ECS_CLASS_LEN (-1 + (ecs_size_t)sizeof(ECS_CLASS_PREFIX)) +#define ECS_ENUM_LEN (-1 + (ecs_size_t)sizeof(ECS_ENUM_PREFIX)) - ecs_query_table_match_t *qm = flecs_query_add_table_match(query, qt, table); - flecs_query_set_table_match(world, query, qm, table, &it); +static +ecs_size_t ecs_cpp_strip_prefix( + char *typeName, + ecs_size_t len, + const char *prefix, + ecs_size_t prefix_len) +{ + if ((len > prefix_len) && !ecs_os_strncmp(typeName, prefix, prefix_len)) { + ecs_os_memmove(typeName, typeName + prefix_len, len - prefix_len); + typeName[len - prefix_len] = '\0'; + len -= prefix_len; } + return len; } -static -bool flecs_query_match_table( - ecs_world_t *world, - ecs_query_t *query, - ecs_table_t *table) +static +void ecs_cpp_trim_type_name( + char *typeName) { - if (!ecs_map_is_initialized(&query->cache.index)) { - return false; + ecs_size_t len = ecs_os_strlen(typeName); + + len = ecs_cpp_strip_prefix(typeName, len, ECS_CONST_PREFIX, ECS_CONST_LEN); + len = ecs_cpp_strip_prefix(typeName, len, ECS_STRUCT_PREFIX, ECS_STRUCT_LEN); + len = ecs_cpp_strip_prefix(typeName, len, ECS_CLASS_PREFIX, ECS_CLASS_LEN); + len = ecs_cpp_strip_prefix(typeName, len, ECS_ENUM_PREFIX, ECS_ENUM_LEN); + + while (typeName[len - 1] == ' ' || + typeName[len - 1] == '&' || + typeName[len - 1] == '*') + { + len --; + typeName[len] = '\0'; } - ecs_query_table_t *qt = NULL; - int var_id = ecs_filter_find_this_var(&query->filter); - if (var_id == -1) { - /* If query doesn't match with This term, it can't match with tables */ - return false; + /* Remove const at end of string */ + if (len > ECS_CONST_LEN) { + if (!ecs_os_strncmp(&typeName[len - ECS_CONST_LEN], " const", ECS_CONST_LEN)) { + typeName[len - ECS_CONST_LEN] = '\0'; + } + len -= ECS_CONST_LEN; } - ecs_iter_t it = ecs_filter_iter(world, &query->filter); - ECS_BIT_SET(it.flags, EcsIterIsInstanced); - ECS_BIT_SET(it.flags, EcsIterIsFilter); - ECS_BIT_SET(it.flags, EcsIterEntityOptional); - ecs_iter_set_var_as_table(&it, var_id, table); - - while (ecs_filter_next(&it)) { - ecs_assert(it.table == table, ECS_INTERNAL_ERROR, NULL); - if (qt == NULL) { - qt = ecs_os_calloc_t(ecs_query_table_t); - ecs_table_cache_insert(&query->cache, it.table, &qt->hdr); - table = it.table; + /* Check if there are any remaining "struct " strings, which can happen + * if this is a template type on msvc. */ + if (len > ECS_STRUCT_LEN) { + char *ptr = typeName; + while ((ptr = strstr(ptr + 1, ECS_STRUCT_PREFIX)) != 0) { + /* Make sure we're not matched with part of a longer identifier + * that contains 'struct' */ + if (ptr[-1] == '<' || ptr[-1] == ',' || isspace(ptr[-1])) { + ecs_os_memmove(ptr, ptr + ECS_STRUCT_LEN, + ecs_os_strlen(ptr + ECS_STRUCT_LEN) + 1); + len -= ECS_STRUCT_LEN; + } } - - ecs_query_table_match_t *qm = flecs_query_add_table_match(query, qt, table); - flecs_query_set_table_match(world, query, qm, table, &it); } - - return qt != NULL; } -ECS_SORT_TABLE_WITH_COMPARE(_, flecs_query_sort_table_generic, order_by, static) +char* ecs_cpp_get_type_name( + char *type_name, + const char *func_name, + size_t len) +{ + memcpy(type_name, func_name + ECS_FUNC_NAME_FRONT(const char*, type_name), len); + type_name[len] = '\0'; + ecs_cpp_trim_type_name(type_name); + return type_name; +} -static -void flecs_query_sort_table( - ecs_world_t *world, - ecs_table_t *table, - int32_t column_index, - ecs_order_by_action_t compare, - ecs_sort_table_action_t sort) +char* ecs_cpp_get_symbol_name( + char *symbol_name, + const char *type_name, + size_t len) { - ecs_data_t *data = &table->data; - if (!ecs_storage_count(&data->entities)) { - /* Nothing to sort */ - return; - } + // Symbol is same as name, but with '::' replaced with '.' + ecs_os_strcpy(symbol_name, type_name); - int32_t count = flecs_table_data_count(data); - if (count < 2) { - return; + char *ptr; + size_t i; + for (i = 0, ptr = symbol_name; i < len && *ptr; i ++, ptr ++) { + if (*ptr == ':') { + symbol_name[i] = '.'; + ptr ++; + } else { + symbol_name[i] = *ptr; + } } - ecs_entity_t *entities = ecs_storage_first(&data->entities); - - void *ptr = NULL; - int32_t size = 0; - if (column_index != -1) { - ecs_type_info_t *ti = table->type_info[column_index]; - ecs_column_t *column = &data->columns[column_index]; - size = ti->size; - ptr = ecs_storage_first(column); - } + symbol_name[i] = '\0'; - if (sort) { - sort(world, table, entities, ptr, size, 0, count - 1, compare); - } else { - flecs_query_sort_table_generic(world, table, entities, ptr, size, 0, count - 1, compare); - } + return symbol_name; } -/* Helper struct for building sorted table ranges */ -typedef struct sort_helper_t { - ecs_query_table_match_t *match; - ecs_entity_t *entities; - const void *ptr; - int32_t row; - int32_t elem_size; - int32_t count; - bool shared; -} sort_helper_t; - static -const void* ptr_from_helper( - sort_helper_t *helper) +const char* cpp_func_rchr( + const char *func_name, + ecs_size_t func_name_len, + char ch) { - ecs_assert(helper->row < helper->count, ECS_INTERNAL_ERROR, NULL); - ecs_assert(helper->elem_size >= 0, ECS_INTERNAL_ERROR, NULL); - ecs_assert(helper->row >= 0, ECS_INTERNAL_ERROR, NULL); - if (helper->shared) { - return helper->ptr; - } else { - return ECS_ELEM(helper->ptr, helper->elem_size, helper->row); + const char *r = strrchr(func_name, ch); + if ((r - func_name) >= (func_name_len - flecs_uto(ecs_size_t, ECS_FUNC_NAME_BACK))) { + return NULL; } + return r; } static -ecs_entity_t e_from_helper( - sort_helper_t *helper) +const char* cpp_func_max( + const char *a, + const char *b) { - if (helper->row < helper->count) { - return helper->entities[helper->row]; - } else { - return 0; - } + if (a > b) return a; + return b; } -static -void flecs_query_build_sorted_table_range( - ecs_query_t *query, - ecs_query_table_list_t *list) +char* ecs_cpp_get_constant_name( + char *constant_name, + const char *func_name, + size_t func_name_len) { - ecs_world_t *world = query->world; - ecs_entity_t id = query->order_by_component; - ecs_order_by_action_t compare = query->order_by; + ecs_size_t f_len = flecs_uto(ecs_size_t, func_name_len); + const char *start = cpp_func_rchr(func_name, f_len, ' '); + start = cpp_func_max(start, cpp_func_rchr(func_name, f_len, ')')); + start = cpp_func_max(start, cpp_func_rchr(func_name, f_len, ':')); + start = cpp_func_max(start, cpp_func_rchr(func_name, f_len, ',')); + ecs_assert(start != NULL, ECS_INVALID_PARAMETER, func_name); + start ++; - if (!list->count) { - return; - } + ecs_size_t len = flecs_uto(ecs_size_t, + (f_len - (start - func_name) - flecs_uto(ecs_size_t, ECS_FUNC_NAME_BACK))); + ecs_os_memcpy_n(constant_name, start, char, len); + constant_name[len] = '\0'; + return constant_name; +} - int to_sort = 0; +// Names returned from the name_helper class do not start with :: +// but are relative to the root. If the namespace of the type +// overlaps with the namespace of the current module, strip it from +// the implicit identifier. +// This allows for registration of component types that are not in the +// module namespace to still be registered under the module scope. +const char* ecs_cpp_trim_module( + ecs_world_t *world, + const char *type_name) +{ + ecs_entity_t scope = ecs_get_scope(world); + if (!scope) { + return type_name; + } - 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->data; + char *path = ecs_get_path_w_sep(world, 0, scope, "::", NULL); + if (path) { + const char *ptr = strrchr(type_name, ':'); + ecs_assert(ptr != type_name, ECS_INTERNAL_ERROR, NULL); + if (ptr) { + ptr --; + ecs_assert(ptr[0] == ':', ECS_INTERNAL_ERROR, NULL); + ecs_size_t name_path_len = (ecs_size_t)(ptr - type_name); + if (name_path_len <= ecs_os_strlen(path)) { + if (!ecs_os_strncmp(type_name, path, name_path_len)) { + type_name = &type_name[name_path_len + 2]; + } + } + } + } + ecs_os_free(path); - ecs_assert(ecs_table_count(table) != 0, ECS_INTERNAL_ERROR, NULL); + return type_name; +} - int32_t index = -1; - if (id) { - index = ecs_search(world, table->storage_table, id, 0); +// Validate registered component +void ecs_cpp_component_validate( + ecs_world_t *world, + ecs_entity_t id, + const char *name, + size_t size, + size_t alignment, + bool implicit_name) +{ + /* If entity has a name check if it matches */ + if (ecs_is_valid(world, id) && ecs_get_name(world, id) != NULL) { + if (!implicit_name && id >= EcsFirstUserComponentId) { +# ifndef FLECS_NDEBUG + char *path = ecs_get_path_w_sep( + world, 0, id, "::", NULL); + if (ecs_os_strcmp(path, name)) { + ecs_err( + "component '%s' already registered with name '%s'", + name, path); + ecs_abort(ECS_INCONSISTENT_NAME, NULL); + } + ecs_os_free(path); +# endif + } + } else { + /* Ensure that the entity id valid */ + if (!ecs_is_alive(world, id)) { + ecs_ensure(world, id); } - if (index != -1) { - ecs_type_info_t *ti = table->type_info[index]; - ecs_column_t *column = &data->columns[index]; - int32_t size = ti->size; - helper[to_sort].ptr = ecs_storage_first(column); - helper[to_sort].elem_size = size; - helper[to_sort].shared = false; - } else if (id) { - /* Find component in prefab */ - ecs_entity_t base = 0; - ecs_search_relation(world, table, 0, id, - EcsIsA, EcsUp, &base, 0, 0); + /* Register name with entity, so that when the entity is created the + * correct id will be resolved from the name. Only do this when the + * entity is empty. */ + ecs_add_path_w_sep(world, id, 0, name, "::", "::"); + } - /* 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); + /* If a component was already registered with this id but with a + * different size, the ecs_component_init function will fail. */ - const EcsComponent *cptr = ecs_get(world, id, EcsComponent); - ecs_assert(cptr != NULL, ECS_INTERNAL_ERROR, NULL); + /* We need to explicitly call ecs_component_init here again. Even though + * the component was already registered, it may have been registered + * with a different world. This ensures that the component is registered + * with the same id for the current world. + * If the component was registered already, nothing will change. */ + ecs_entity_t ent = ecs_component_init(world, &(ecs_component_desc_t){ + .entity = id, + .type.size = flecs_uto(int32_t, size), + .type.alignment = flecs_uto(int32_t, alignment) + }); + (void)ent; + ecs_assert(ent == id, ECS_INTERNAL_ERROR, NULL); +} - helper[to_sort].ptr = ecs_get_id(world, base, id); - 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; - } +ecs_entity_t ecs_cpp_component_register( + ecs_world_t *world, + ecs_entity_t id, + const char *name, + const char *symbol, + ecs_size_t size, + ecs_size_t alignment, + bool implicit_name) +{ + (void)size; + (void)alignment; - helper[to_sort].match = match; - helper[to_sort].entities = ecs_storage_first(&data->entities); - helper[to_sort].row = 0; - helper[to_sort].count = ecs_table_count(table); - to_sort ++; + /* If the component is not yet registered, ensure no other component + * or entity has been registered with this name. Ensure component is + * looked up from root. */ + bool existing = false; + ecs_entity_t prev_scope = ecs_set_scope(world, 0); + ecs_entity_t ent; + if (id) { + ent = id; + } else { + ent = ecs_lookup_path_w_sep(world, 0, name, "::", "::", false); + existing = ent != 0; } + ecs_set_scope(world, prev_scope); - ecs_assert(to_sort != 0, ECS_INTERNAL_ERROR, NULL); - - bool proceed; - do { - int32_t j, min = 0; - proceed = true; + /* If entity exists, compare symbol name to ensure that the component + * we are trying to register under this name is the same */ + if (ent) { + const EcsComponent *component = ecs_get(world, ent, EcsComponent); + if (component != NULL) { + const char *sym = ecs_get_symbol(world, ent); + ecs_assert(!existing || (sym != NULL), ECS_MISSING_SYMBOL, + ecs_get_name(world, ent)); + (void)existing; - ecs_entity_t e1; - while (!(e1 = e_from_helper(&helper[min]))) { - min ++; - if (min == to_sort) { - proceed = false; - break; + if (sym && ecs_os_strcmp(sym, symbol)) { + /* Application is trying to register a type with an entity that + * was already associated with another type. In most cases this + * is an error, with the exception of a scenario where the + * application is wrapping a C type with a C++ type. + * + * In this case the C++ type typically inherits from the C type, + * and adds convenience methods to the derived class without + * changing anything that would change the size or layout. + * + * To meet this condition, the new type must have the same size + * and alignment as the existing type, and the name of the type + * type must be equal to the registered name (not symbol). + * + * The latter ensures that it was the intent of the application + * to alias the type, vs. accidentally registering an unrelated + * type with the same size/alignment. */ + char *type_path = ecs_get_fullpath(world, ent); + if (ecs_os_strcmp(type_path, symbol) || + component->size != size || + component->alignment != alignment) + { + ecs_err( + "component with name '%s' is already registered for"\ + " type '%s' (trying to register for type '%s')", + name, sym, symbol); + ecs_abort(ECS_NAME_IN_USE, NULL); + } + ecs_os_free(type_path); } } - if (!proceed) { - break; - } + /* If no entity is found, lookup symbol to check if the component was + * registered under a different name. */ + } else if (!implicit_name) { + ent = ecs_lookup_symbol(world, symbol, false); + ecs_assert(ent == 0 || (ent == id), ECS_INCONSISTENT_COMPONENT_ID, symbol); + } - for (j = min + 1; j < to_sort; j++) { - ecs_entity_t e2 = e_from_helper(&helper[j]); - if (!e2) { - continue; - } + return ent; +} - const void *ptr1 = ptr_from_helper(&helper[min]); - const void *ptr2 = ptr_from_helper(&helper[j]); +ecs_entity_t ecs_cpp_component_register_explicit( + ecs_world_t *world, + ecs_entity_t s_id, + ecs_entity_t id, + const char *name, + const char *type_name, + const char *symbol, + size_t size, + size_t alignment, + bool is_component) +{ + char *existing_name = NULL; - if (compare(e1, ptr1, e2, ptr2) > 0) { - min = j; - e1 = e_from_helper(&helper[min]); - } + // If an explicit id is provided, it is possible that the symbol and + // name differ from the actual type, as the application may alias + // one type to another. + if (!id) { + if (!name) { + // If no name was provided first check if a type with the provided + // symbol was already registered. + id = ecs_lookup_symbol(world, symbol, false); + if (id) { + existing_name = ecs_get_path_w_sep(world, 0, id, "::", "::"); + name = existing_name; + } else { + // If type is not yet known, derive from type name + name = ecs_cpp_trim_module(world, type_name); + } } - - 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 ++; + } else { + // If an explicit id is provided but it has no name, inherit + // the name from the type. + if (!ecs_is_valid(world, id) || !ecs_get_name(world, id)) { + name = ecs_cpp_trim_module(world, type_name); } + } - cur_helper->row ++; - } while (proceed); + ecs_entity_t entity; + if (is_component || size != 0) { + entity = ecs_entity(world, { + .id = s_id, + .name = name, + .sep = "::", + .root_sep = "::", + .symbol = symbol, + .use_low_id = true + }); + ecs_assert(entity != 0, ECS_INVALID_OPERATION, NULL); - /* 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]; + entity = ecs_component_init(world, &(ecs_component_desc_t){ + .entity = entity, + .type.size = flecs_uto(int32_t, size), + .type.alignment = flecs_uto(int32_t, alignment) + }); + ecs_assert(entity != 0, ECS_INVALID_OPERATION, NULL); + } else { + entity = ecs_entity_init(world, &(ecs_entity_desc_t){ + .id = s_id, + .name = name, + .sep = "::", + .root_sep = "::", + .symbol = symbol, + .use_low_id = true + }); } - nodes[0].prev = NULL; - nodes[i - 1].next = NULL; + ecs_assert(entity != 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(!s_id || s_id == entity, ECS_INTERNAL_ERROR, NULL); + ecs_os_free(existing_name); - ecs_os_free(helper); + return entity; } -static -void flecs_query_build_sorted_tables( - ecs_query_t *query) +ecs_entity_t ecs_cpp_enum_constant_register( + ecs_world_t *world, + ecs_entity_t parent, + ecs_entity_t id, + const char *name, + int value) { - ecs_vector_clear(query->table_slices); - - 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_suspend_readonly_state_t readonly_state; + world = flecs_suspend_readonly(world, &readonly_state); - /* Sort tables in current group */ - flecs_query_build_sorted_table_range(query, list); - - /* Find next group to sort */ - cur = list->last->next; - } while (cur); + const char *parent_name = ecs_get_name(world, parent); + ecs_size_t parent_name_len = ecs_os_strlen(parent_name); + if (!ecs_os_strncmp(name, parent_name, parent_name_len)) { + name += parent_name_len; + if (name[0] == '_') { + name ++; } - } else { - flecs_query_build_sorted_table_range(query, &query->list); - } -} - -static -void flecs_query_sort_tables( - ecs_world_t *world, - ecs_query_t *query) -{ - ecs_order_by_action_t compare = query->order_by; - if (!compare) { - return; } - ecs_sort_table_action_t sort = query->sort_table; - - ecs_entity_t order_by_component = query->order_by_component; - int32_t i, order_by_term = -1; + ecs_entity_t prev = ecs_set_scope(world, parent); + id = ecs_entity_init(world, &(ecs_entity_desc_t){ + .id = id, + .name = name + }); + ecs_assert(id != 0, ECS_INVALID_OPERATION, name); + ecs_set_scope(world, prev); - /* Find term that iterates over component (must be at least one) */ - if (order_by_component) { - const ecs_filter_t *f = &query->filter; - int32_t term_count = f->term_count; - for (i = 0; i < term_count; i ++) { - ecs_term_t *term = &f->terms[i]; - if (!ecs_term_match_this(term)) { - continue; - } - - if (term->id == order_by_component) { - order_by_term = i; - break; - } - } - - ecs_assert(order_by_term != -1, ECS_INTERNAL_ERROR, NULL); - } - - /* Iterate over non-empty tables. Don't bother with empty tables as they - * have nothing to sort */ + #ifdef FLECS_DEBUG + const EcsComponent *cptr = ecs_get(world, parent, EcsComponent); + ecs_assert(cptr != NULL, ECS_INVALID_PARAMETER, "enum is not a component"); + ecs_assert(cptr->size == ECS_SIZEOF(int32_t), ECS_UNSUPPORTED, + "enum component must have 32bit size"); + #endif - bool tables_sorted = false; + ecs_set_id(world, id, parent, sizeof(int), &value); - ecs_table_cache_iter_t it; - ecs_query_table_t *qt; - flecs_table_cache_iter(&query->cache, &it); + flecs_resume_readonly(world, &readonly_state); - while ((qt = flecs_table_cache_next(&it, ecs_query_table_t))) { - ecs_table_t *table = qt->hdr.table; - bool dirty = false; + ecs_trace("#[green]constant#[reset] %s.%s created with value %d", + ecs_get_name(world, parent), name, value); - if (flecs_query_check_table_monitor(query, qt, 0)) { - dirty = true; - } + return id; +} - int32_t column = -1; - if (order_by_component) { - if (flecs_query_check_table_monitor(query, qt, order_by_term + 1)) { - dirty = true; - } +static int32_t flecs_reset_count = 0; - if (dirty) { - column = -1; +int32_t ecs_cpp_reset_count_get(void) { + return flecs_reset_count; +} - ecs_table_t *storage_table = table->storage_table; - if (storage_table) { - column = ecs_search(world, storage_table, - order_by_component, NULL); - } +int32_t ecs_cpp_reset_count_inc(void) { + return ++flecs_reset_count; +} - if (column == -1) { - /* Component is shared, no sorting is needed */ - dirty = false; - } - } - } +#endif - if (!dirty) { - continue; - } - /* Something has changed, sort the table. Prefers using flecs_query_sort_table when available */ - flecs_query_sort_table(world, table, column, compare, sort); - tables_sorted = true; - } +#ifdef FLECS_OS_API_IMPL +#ifdef ECS_TARGET_WINDOWS +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#include +#include - if (tables_sorted || query->match_count != query->prev_match_count) { - flecs_query_build_sorted_tables(query); - query->match_count ++; /* Increase version if tables changed */ - } +static +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; } static -bool flecs_query_has_refs( - ecs_query_t *query) +void* win_thread_join( + ecs_os_thread_t thr) { - ecs_term_t *terms = query->filter.terms; - int32_t i, count = query->filter.term_count; - for (i = 0; i < count; i ++) { - if (terms[i].src.flags & (EcsUp | EcsIsEntity)) { - return true; - } + HANDLE *thread = (HANDLE*)(uintptr_t)thr; + DWORD r = WaitForSingleObject(*thread, INFINITE); + if (r == WAIT_FAILED) { + ecs_err("win_thread_join: WaitForSingleObject failed"); } - - return false; + ecs_os_free(thread); + return NULL; } static -void flecs_query_for_each_component_monitor( - ecs_world_t *world, - ecs_query_t *query, - void(*callback)( - ecs_world_t* world, - ecs_id_t id, - ecs_query_t *query)) +int32_t win_ainc( + int32_t *count) { - ecs_term_t *terms = query->filter.terms; - int32_t i, count = query->filter.term_count; + return InterlockedIncrement(count); +} - for (i = 0; i < count; i++) { - ecs_term_t *term = &terms[i]; - ecs_term_id_t *src = &term->src; +static +int32_t win_adec( + int32_t *count) +{ + return InterlockedDecrement(count); +} - if (src->flags & EcsUp) { - callback(world, ecs_pair(src->trav, EcsWildcard), query); - if (src->trav != EcsIsA) { - callback(world, ecs_pair(EcsIsA, EcsWildcard), query); - } - callback(world, term->id, query); +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; +} - } else if (src->flags & EcsSelf && !ecs_term_match_this(term)) { - callback(world, term->id, query); - } - } +static +void win_mutex_free( + ecs_os_mutex_t m) +{ + CRITICAL_SECTION *mutex = (CRITICAL_SECTION*)(intptr_t)m; + DeleteCriticalSection(mutex); + ecs_os_free(mutex); } static -bool flecs_query_is_term_id_supported( - ecs_term_id_t *term_id) +void win_mutex_lock( + ecs_os_mutex_t m) { - if (!(term_id->flags & EcsIsVariable)) { - return true; - } - if (ecs_id_is_wildcard(term_id->id)) { - return true; - } - return false; + CRITICAL_SECTION *mutex = (CRITICAL_SECTION*)(intptr_t)m; + EnterCriticalSection(mutex); } static -void flecs_query_process_signature( - ecs_world_t *world, - ecs_query_t *query) +void win_mutex_unlock( + ecs_os_mutex_t m) { - ecs_term_t *terms = query->filter.terms; - int32_t i, count = query->filter.term_count; + CRITICAL_SECTION *mutex = (CRITICAL_SECTION*)(intptr_t)m; + LeaveCriticalSection(mutex); +} - for (i = 0; i < count; i ++) { - ecs_term_t *term = &terms[i]; - ecs_term_id_t *first = &term->first; - ecs_term_id_t *src = &term->src; - ecs_term_id_t *second = &term->second; - ecs_inout_kind_t inout = term->inout; +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; +} - bool is_src_ok = flecs_query_is_term_id_supported(src); - bool is_first_ok = flecs_query_is_term_id_supported(first); - bool is_second_ok = flecs_query_is_term_id_supported(second); +static +void win_cond_free( + ecs_os_cond_t c) +{ + (void)c; +} - (void)first; - (void)second; - (void)is_src_ok; - (void)is_first_ok; - (void)is_second_ok; +static +void win_cond_signal( + ecs_os_cond_t c) +{ + CONDITION_VARIABLE *cond = (CONDITION_VARIABLE*)(intptr_t)c; + WakeConditionVariable(cond); +} - /* Queries do not support named variables */ - ecs_check(is_src_ok || ecs_term_match_this(term), - ECS_UNSUPPORTED, NULL); - ecs_check(is_first_ok, ECS_UNSUPPORTED, NULL); - ecs_check(is_second_ok, ECS_UNSUPPORTED, NULL); +static +void win_cond_broadcast( + ecs_os_cond_t c) +{ + CONDITION_VARIABLE *cond = (CONDITION_VARIABLE*)(intptr_t)c; + WakeAllConditionVariable(cond); +} - if (inout != EcsIn) { - query->flags |= EcsQueryHasOutColumns; - } +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); +} - if (src->flags & EcsCascade) { - /* Query can only have one cascade column */ - ecs_assert(query->cascade_by == 0, ECS_INVALID_PARAMETER, NULL); - query->cascade_by = i + 1; - } +static bool win_time_initialized; +static double win_time_freq; +static LARGE_INTEGER win_time_start; - if ((src->flags & EcsTraverseFlags) == EcsSelf) { - if (src->flags & EcsIsEntity) { - flecs_add_flag(world, term->src.id, EcsEntityObserved); - } - } +static +void win_time_setup(void) { + if ( win_time_initialized) { + return; } + + win_time_initialized = true; - query->flags |= (ecs_flags32_t)(flecs_query_has_refs(query) * EcsQueryHasRefs); - - if (!(query->flags & EcsQueryIsSubquery)) { - flecs_query_for_each_component_monitor(world, query, flecs_monitor_register); - } -error: - return; + LARGE_INTEGER freq; + QueryPerformanceFrequency(&freq); + QueryPerformanceCounter(&win_time_start); + win_time_freq = (double)freq.QuadPart / 1000000000.0; } -/** When a table becomes empty remove it from the query list, or vice versa. */ static -void flecs_query_update_table( - ecs_query_t *query, - ecs_table_t *table, - bool empty) +void win_sleep( + int32_t sec, + int32_t nanosec) { - int32_t prev_count = ecs_query_table_count(query); - ecs_table_cache_set_empty(&query->cache, table, empty); - int32_t cur_count = ecs_query_table_count(query); - - if (prev_count != cur_count) { - ecs_query_table_t *qt = ecs_table_cache_get(&query->cache, table); - ecs_assert(qt != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_query_table_match_t *cur, *next; - - for (cur = qt->first; cur != NULL; cur = next) { - next = cur->next_match; - - if (empty) { - ecs_assert(ecs_table_count(table) == 0, - ECS_INTERNAL_ERROR, NULL); + HANDLE timer; + LARGE_INTEGER ft; - flecs_query_remove_table_node(query, &cur->node); - } else { - ecs_assert(ecs_table_count(table) != 0, - ECS_INTERNAL_ERROR, NULL); - flecs_query_insert_table_node(query, &cur->node); - } - } - } + ft.QuadPart = -((int64_t)sec * 10000000 + (int64_t)nanosec / 100); - ecs_assert(cur_count || query->list.first == NULL, - ECS_INTERNAL_ERROR, NULL); + timer = CreateWaitableTimer(NULL, TRUE, NULL); + SetWaitableTimer(timer, &ft, 0, NULL, NULL, 0); + WaitForSingleObject(timer, INFINITE); + CloseHandle(timer); } +static double win_time_freq; +static ULONG win_current_resolution; + static -void flecs_query_add_subquery( - ecs_world_t *world, - ecs_query_t *parent, - ecs_query_t *subquery) +void win_enable_high_timer_resolution(bool enable) { - ecs_query_t **elem = ecs_vector_add(&parent->subqueries, ecs_query_t*); - *elem = subquery; - - ecs_table_cache_t *cache = &parent->cache; - ecs_table_cache_iter_t it; - ecs_query_table_t *qt; - flecs_table_cache_all_iter(cache, &it); - while ((qt = flecs_table_cache_next(&it, ecs_query_table_t))) { - flecs_query_match_table(world, subquery, qt->hdr.table); + HMODULE hntdll = GetModuleHandle((LPCTSTR)"ntdll.dll"); + if (!hntdll) { + return; } -} -static -void flecs_query_notify_subqueries( - ecs_world_t *world, - ecs_query_t *query, - ecs_query_event_t *event) -{ - if (query->subqueries) { - ecs_query_t **queries = ecs_vector_first(query->subqueries, ecs_query_t*); - int32_t i, count = ecs_vector_count(query->subqueries); + LONG (__stdcall *pNtSetTimerResolution)( + ULONG desired, BOOLEAN set, ULONG * current); - ecs_query_event_t sub_event = *event; - sub_event.parent_query = query; + pNtSetTimerResolution = (LONG(__stdcall*)(ULONG, BOOLEAN, ULONG*)) + GetProcAddress(hntdll, "NtSetTimerResolution"); - for (i = 0; i < count; i ++) { - ecs_query_t *sub = queries[i]; - flecs_query_notify(world, sub, &sub_event); - } + if(!pNtSetTimerResolution) { + return; } -} -/* Remove table */ -static -void flecs_query_table_match_free( - ecs_query_t *query, - ecs_query_table_t *elem, - ecs_query_table_match_t *first) -{ - ecs_query_table_match_t *cur, *next; + ULONG current, resolution = 10000; /* 1 ms */ - for (cur = first; cur != NULL; cur = next) { - ecs_os_free(cur->columns); - ecs_os_free(cur->ids); - ecs_os_free(cur->sources); - ecs_os_free(cur->sizes); - ecs_os_free(cur->references); - ecs_os_free(cur->sparse_columns); - ecs_os_free(cur->bitset_columns); - ecs_os_free(cur->monitor); + if (!enable && win_current_resolution) { + pNtSetTimerResolution(win_current_resolution, 0, ¤t); + win_current_resolution = 0; + return; + } else if (!enable) { + return; + } - if (!elem->hdr.empty) { - flecs_query_remove_table_node(query, &cur->node); - } + if (resolution == win_current_resolution) { + return; + } - next = cur->next_match; + if (win_current_resolution) { + pNtSetTimerResolution(win_current_resolution, 0, ¤t); + } - ecs_os_free(cur); + if (pNtSetTimerResolution(resolution, 1, ¤t)) { + /* Try setting a lower resolution */ + resolution *= 2; + if(pNtSetTimerResolution(resolution, 1, ¤t)) return; } + + win_current_resolution = resolution; } static -void flecs_query_table_free( - ecs_query_t *query, - ecs_query_table_t *elem) -{ - flecs_query_table_match_free(query, elem, elem->first); - ecs_os_free(elem); +uint64_t win_time_now(void) { + uint64_t now; + + LARGE_INTEGER qpc_t; + QueryPerformanceCounter(&qpc_t); + now = (uint64_t)(qpc_t.QuadPart / win_time_freq); + + return now; } static -void flecs_query_unmatch_table( - ecs_query_t *query, - ecs_table_t *table) -{ - ecs_query_table_t *qt = ecs_table_cache_remove( - &query->cache, table, NULL); - if (qt) { - flecs_query_table_free(query, qt); +void win_fini(void) { + if (ecs_os_api.flags_ & EcsOsApiHighResolutionTimer) { + win_enable_high_timer_resolution(false); } } -/* Rematch system with tables after a change happened to a watched entity */ -static -void flecs_query_rematch_tables( - ecs_world_t *world, - ecs_query_t *query, - ecs_query_t *parent_query) -{ - ecs_iter_t it, parent_it; - ecs_table_t *table = NULL; - ecs_query_table_t *qt = NULL; - ecs_query_table_match_t *qm = NULL; +void ecs_set_os_api_impl(void) { + ecs_os_set_api_defaults(); - if (parent_query) { - parent_it = ecs_query_iter(world, parent_query); - it = ecs_filter_chain_iter(&parent_it, &query->filter); - } else { - it = ecs_filter_iter(world, &query->filter); - } + ecs_os_api_t api = ecs_os_api; - ECS_BIT_SET(it.flags, EcsIterIsInstanced); - ECS_BIT_SET(it.flags, EcsIterIsFilter); - ECS_BIT_SET(it.flags, EcsIterEntityOptional); + 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; + api.sleep_ = win_sleep; + api.now_ = win_time_now; + api.fini_ = win_fini; - int32_t rematch_count = ++ query->rematch_count; + win_time_setup(); - while (ecs_iter_next(&it)) { - if ((table != it.table) || (!it.table && !qt)) { - if (qm && qm->next_match) { - flecs_query_table_match_free(query, qt, qm->next_match); - qm->next_match = NULL; - } + if (ecs_os_api.flags_ & EcsOsApiHighResolutionTimer) { + win_enable_high_timer_resolution(true); + } - table = it.table; + ecs_os_set_api(&api); +} - qt = ecs_table_cache_get(&query->cache, table); - if (!qt) { - qt = ecs_os_calloc_t(ecs_query_table_t); - ecs_table_cache_insert(&query->cache, table, &qt->hdr); - } +#else +#include "pthread.h" - ecs_assert(qt->hdr.table == table, ECS_INTERNAL_ERROR, NULL); - qt->rematch_count = rematch_count; - qm = NULL; - } - if (!qm) { - qm = qt->first; - } else { - qm = qm->next_match; - } - if (!qm) { - qm = flecs_query_add_table_match(query, qt, table); - } +#if defined(__APPLE__) && defined(__MACH__) +#include +#elif defined(__EMSCRIPTEN__) +#include +#else +#include +#endif - flecs_query_set_table_match(world, query, qm, table, &it); +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 (table && ecs_table_count(table) && query->group_by) { - /* If grouping is enabled, make sure match is in the right place */ - flecs_query_remove_table_node(query, &qm->node); - flecs_query_insert_table_node(query, &qm->node); - } + if (pthread_create (thread, NULL, callback, arg) != 0) { + ecs_os_abort(); } - if (qm && qm->next_match) { - flecs_query_table_match_free(query, qt, qm->next_match); - qm->next_match = NULL; - } + return (ecs_os_thread_t)(uintptr_t)thread; +} - /* Iterate all tables in cache, remove ones that weren't just matched */ - ecs_table_cache_iter_t cache_it; - if (flecs_table_cache_all_iter(&query->cache, &cache_it)) { - while ((qt = flecs_table_cache_next(&cache_it, ecs_query_table_t))) { - if (qt->rematch_count != rematch_count) { - flecs_query_unmatch_table(query, qt->hdr.table); - } - } - } +static +void* posix_thread_join( + ecs_os_thread_t thread) +{ + void *arg; + pthread_t *thr = (pthread_t*)(uintptr_t)thread; + pthread_join(*thr, &arg); + ecs_os_free(thr); + return arg; } static -void flecs_query_remove_subquery( - ecs_query_t *parent, - ecs_query_t *sub) +int32_t posix_ainc( + int32_t *count) { - 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); + int value; +#ifdef __GNUC__ + value = __sync_add_and_fetch (count, 1); + return value; +#else + /* Unsupported */ + abort(); +#endif +} - int32_t i, count = ecs_vector_count(parent->subqueries); - ecs_query_t **sq = ecs_vector_first(parent->subqueries, ecs_query_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 +} - for (i = 0; i < count; i ++) { - if (sq[i] == sub) { - break; - } +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_vector_remove(parent->subqueries, ecs_query_t*, i); + return (ecs_os_mutex_t)(uintptr_t)mutex; } -/* -- Private API -- */ - -void flecs_query_notify( - ecs_world_t *world, - ecs_query_t *query, - ecs_query_event_t *event) +static +void posix_mutex_free( + ecs_os_mutex_t m) { - bool notify = true; - - switch(event->kind) { - case EcsQueryTableMatch: - /* Creation of new table */ - if (flecs_query_match_table(world, query, event->table)) { - if (query->subqueries) { - flecs_query_notify_subqueries(world, query, event); - } - } - notify = false; - break; - case EcsQueryTableUnmatch: - /* Deletion of table */ - flecs_query_unmatch_table(query, event->table); - break; - case EcsQueryTableRematch: - /* Rematch tables of query */ - flecs_query_rematch_tables(world, query, event->parent_query); - break; - case EcsQueryOrphan: - ecs_assert(query->flags & EcsQueryIsSubquery, ECS_INTERNAL_ERROR, NULL); - query->flags |= EcsQueryIsOrphaned; - query->parent = NULL; - break; - } + pthread_mutex_t *mutex = (pthread_mutex_t*)(intptr_t)m; + pthread_mutex_destroy(mutex); + ecs_os_free(mutex); +} - if (notify) { - flecs_query_notify_subqueries(world, query, event); +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(); } } static -void flecs_query_order_by( - ecs_world_t *world, - ecs_query_t *query, - ecs_entity_t order_by_component, - ecs_order_by_action_t order_by, - ecs_sort_table_action_t action) +void posix_mutex_unlock( + ecs_os_mutex_t m) { - ecs_check(query != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(!(query->flags & EcsQueryIsOrphaned), ECS_INVALID_PARAMETER, NULL); - - query->order_by_component = order_by_component; - query->order_by = order_by; - query->sort_table = action; - - ecs_vector_free(query->table_slices); - query->table_slices = NULL; - - flecs_query_sort_tables(world, query); - - if (!query->table_slices) { - flecs_query_build_sorted_tables(query); + pthread_mutex_t *mutex = (pthread_mutex_t*)(intptr_t)m; + if (pthread_mutex_unlock(mutex)) { + abort(); } -error: - return; } static -void flecs_query_group_by( - ecs_query_t *query, - ecs_entity_t sort_component, - ecs_group_by_action_t group_by) -{ - /* Cannot change grouping once a query has been created */ - ecs_check(query->group_by_id == 0, ECS_INVALID_OPERATION, NULL); - ecs_check(query->group_by == 0, ECS_INVALID_OPERATION, NULL); +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; +} - query->group_by_id = sort_component; - query->group_by = group_by; - ecs_map_init(&query->groups, ecs_query_table_list_t, 16); -error: - return; +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); } -/* Implementation for iterable mixin */ -static -void flecs_query_iter_init( - const ecs_world_t *world, - const ecs_poly_t *poly, - ecs_iter_t *iter, - ecs_term_t *filter) +static +void posix_cond_signal( + ecs_os_cond_t c) { - ecs_poly_assert(poly, ecs_query_t); + pthread_cond_t *cond = (pthread_cond_t*)(intptr_t)c; + if (pthread_cond_signal(cond)) { + abort(); + } +} - 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); +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(); } } -static -void flecs_query_on_event( - ecs_iter_t *it) +static +void posix_cond_wait( + ecs_os_cond_t c, + ecs_os_mutex_t m) { - /* Because this is the observer::run callback, checking if this is event is - * already handled is not done for us. */ - ecs_world_t *world = it->world; - ecs_observer_t *o = it->ctx; - if (o->last_event_id) { - if (o->last_event_id[0] == world->event_id) { - return; - } - o->last_event_id[0] = world->event_id; + 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_query_t *query = o->ctx; - ecs_table_t *table = it->table; - ecs_assert(query != NULL, ECS_INTERNAL_ERROR, NULL); +static bool posix_time_initialized; - /* The observer isn't doing the matching because the query can do it more - * efficiently by checking the table with the query cache. */ - if (ecs_table_cache_get(&query->cache, table) == NULL) { +#if defined(__APPLE__) && defined(__MACH__) +static mach_timebase_info_data_t posix_osx_timebase; +static uint64_t posix_time_start; +#else +static uint64_t posix_time_start; +#endif + +static +void posix_time_setup(void) { + if (posix_time_initialized) { return; } + + posix_time_initialized = true; - ecs_entity_t event = it->event; - if (event == EcsOnTableEmpty) { - flecs_query_update_table(query, table, true); - } else - if (event == EcsOnTableFill) { - flecs_query_update_table(query, table, false); - } + #if defined(__APPLE__) && defined(__MACH__) + mach_timebase_info(&posix_osx_timebase); + posix_time_start = mach_absolute_time(); + #else + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + posix_time_start = (uint64_t)ts.tv_sec*1000000000 + (uint64_t)ts.tv_nsec; + #endif } static -void flecs_query_table_cache_free( - ecs_query_t *query) +void posix_sleep( + int32_t sec, + int32_t nanosec) { - ecs_table_cache_iter_t it; - ecs_query_table_t *qt; + struct timespec sleepTime; + ecs_assert(sec >= 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(nanosec >= 0, ECS_INTERNAL_ERROR, NULL); - if (flecs_table_cache_all_iter(&query->cache, &it)) { - while ((qt = flecs_table_cache_next(&it, ecs_query_table_t))) { - flecs_query_table_free(query, qt); - } + sleepTime.tv_sec = sec; + sleepTime.tv_nsec = nanosec; + if (nanosleep(&sleepTime, NULL)) { + ecs_err("nanosleep failed"); } +} - ecs_table_cache_fini(&query->cache); +/* prevent 64-bit overflow when computing relative timestamp + see https://gist.github.com/jspohr/3dc4f00033d79ec5bdaf67bc46c813e3 +*/ +#if defined(ECS_TARGET_DARWIN) +static +int64_t posix_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 flecs_query_fini( - ecs_query_t *query) -{ - ecs_world_t *world = query->world; +uint64_t posix_time_now(void) { + ecs_assert(posix_time_initialized != 0, ECS_INTERNAL_ERROR, NULL); - if (query->group_by_ctx_free) { - if (query->group_by_ctx) { - query->group_by_ctx_free(query->group_by_ctx); - } - } + uint64_t now; - if ((query->flags & EcsQueryIsSubquery) && - !(query->flags & EcsQueryIsOrphaned)) - { - flecs_query_remove_subquery(query->parent, query); - } + #if defined(ECS_TARGET_DARWIN) + now = (uint64_t) posix_int64_muldiv( + (int64_t)mach_absolute_time(), + (int64_t)posix_osx_timebase.numer, + (int64_t)posix_osx_timebase.denom); + #elif defined(__EMSCRIPTEN__) + now = (long long)(emscripten_get_now() * 1000.0 * 1000); + #else + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + now = ((uint64_t)ts.tv_sec * 1000 * 1000 * 1000 + (uint64_t)ts.tv_nsec); + #endif - flecs_query_notify_subqueries(world, query, &(ecs_query_event_t){ - .kind = EcsQueryOrphan - }); + return now; +} - flecs_query_for_each_component_monitor(world, query, - flecs_monitor_unregister); - flecs_query_table_cache_free(query); +void ecs_set_os_api_impl(void) { + ecs_os_set_api_defaults(); - ecs_map_fini(&query->groups); + ecs_os_api_t api = ecs_os_api; - ecs_vector_free(query->subqueries); - ecs_vector_free(query->table_slices); - ecs_filter_fini(&query->filter); + 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; + api.sleep_ = posix_sleep; + api.now_ = posix_time_now; - ecs_poly_free(query, ecs_query_t); + posix_time_setup(); + + ecs_os_set_api(&api); } +#endif +#endif -/* -- Public API -- */ -ecs_query_t* ecs_query_init( - ecs_world_t *world, - const ecs_query_desc_t *desc) -{ - ecs_check(world != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(desc->_canary == 0, ECS_INVALID_PARAMETER, NULL); - ecs_check(!(world->flags & EcsWorldFini), ECS_INVALID_OPERATION, NULL); +#ifdef FLECS_PLECS - ecs_query_t *result = ecs_poly_new(ecs_query_t); - ecs_observer_desc_t observer_desc = { .filter = desc->filter }; - ecs_entity_t entity = desc->entity; +#include - observer_desc.filter.flags = EcsFilterMatchEmptyTables; - observer_desc.filter.storage = &result->filter; - result->filter = ECS_FILTER_INIT; +#define TOK_NEWLINE '\n' +#define TOK_WITH "with" +#define TOK_USING "using" - if (ecs_filter_init(world, &observer_desc.filter) == NULL) { - goto error; - } +#define STACK_MAX_SIZE (64) - if (result->filter.term_count) { - observer_desc.entity = entity; - observer_desc.run = flecs_query_on_event; - observer_desc.ctx = result; - observer_desc.events[0] = EcsOnTableEmpty; - observer_desc.events[1] = EcsOnTableFill; - observer_desc.filter.flags |= EcsFilterIsFilter; +typedef struct { + const char *name; + const char *code; - /* ecs_filter_init could have moved away resources from the terms array - * in the descriptor, so use the terms array from the filter. */ - observer_desc.filter.terms_buffer = result->filter.terms; - observer_desc.filter.terms_buffer_count = result->filter.term_count; - observer_desc.filter.expr = NULL; /* Already parsed */ + ecs_entity_t last_predicate; + ecs_entity_t last_subject; + ecs_entity_t last_object; - entity = ecs_observer_init(world, &observer_desc); - if (!entity) { - goto error; - } - } + ecs_id_t last_assign_id; + ecs_entity_t assign_to; - result->world = world; - result->iterable.init = flecs_query_iter_init; - result->dtor = (ecs_poly_dtor_t)flecs_query_fini; - result->prev_match_count = -1; + 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 (ecs_should_log_1()) { - char *filter_expr = ecs_filter_str(world, &result->filter); - ecs_dbg_1("#[green]query#[normal] [%s] created", - filter_expr ? filter_expr : ""); - ecs_os_free(filter_expr); - } + char *annot[STACK_MAX_SIZE]; + int32_t annot_count; - ecs_log_push_1(); + bool with_stmt; + bool scope_assign_stmt; + bool using_stmt; + bool assign_stmt; + bool isa_stmt; - flecs_query_process_signature(world, result); + int32_t errors; +} plecs_state_t; - /* Group before matching so we won't have to move tables around later */ - int32_t cascade_by = result->cascade_by; - if (cascade_by) { - flecs_query_group_by(result, result->filter.terms[cascade_by - 1].id, - flecs_query_group_by_cascade); - result->group_by_ctx = &result->filter.terms[cascade_by - 1]; - } +static +ecs_entity_t plecs_lookup( + const ecs_world_t *world, + const char *path, + plecs_state_t *state, + ecs_entity_t rel, + bool is_subject) +{ + ecs_entity_t e = 0; - 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); - flecs_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 (!is_subject) { + ecs_entity_t oneof = 0; + if (rel) { + if (ecs_has_id(world, rel, EcsOneOf)) { + oneof = rel; + } else { + oneof = ecs_get_target(world, rel, EcsOneOf, 0); + } + if (oneof) { + return ecs_lookup_path_w_sep( + world, oneof, path, NULL, NULL, false); + } + } + 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 (desc->parent != NULL) { - result->flags |= EcsQueryIsSubquery; + if (!e) { + e = ecs_lookup_path_w_sep(world, 0, path, NULL, NULL, !is_subject); } - /* If the query refers to itself, add the components that were queried for - * to the query itself. */ - if (entity) { - int32_t t, term_count = result->filter.term_count; - ecs_term_t *terms = result->filter.terms; + return e; +} - for (t = 0; t < term_count; t ++) { - ecs_term_t *term = &terms[t]; - if (term->src.id == entity) { - ecs_add_id(world, entity, term->id); - } - } +/* 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, 0, false); +} +#endif + +static +ecs_entity_t plecs_ensure_entity( + ecs_world_t *world, + plecs_state_t *state, + const char *path, + ecs_entity_t rel, + bool is_subject) +{ + if (!path) { + return 0; } - if (!entity) { - entity = ecs_new_id(world); - } - - EcsPoly *poly = ecs_poly_bind(world, entity, ecs_query_t); - poly->poly = result; - result->entity = entity; - - /* Ensure that while initially populating the query with tables, they are - * in the right empty/non-empty list. This ensures the query won't miss - * empty/non-empty events for tables that are currently out of sync, but - * change back to being in sync before processing pending events. */ - ecs_run_aperiodic(world, EcsAperiodicEmptyTables); + ecs_entity_t e = plecs_lookup(world, path, state, rel, is_subject); + if (!e) { + if (rel && flecs_get_oneof(world, rel)) { + /* If relationship has oneof and entity was not found, don't proceed + * with creating an entity as this can cause asserts later on */ + char *relstr = ecs_get_fullpath(world, rel); + ecs_parser_error(state->name, 0, 0, + "invalid identifier '%s' for relationship '%s'", path, relstr); + ecs_os_free(relstr); + return 0; + } - ecs_table_cache_init(&result->cache); + 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 (!desc->parent) { - flecs_query_match_tables(world, result); + e = ecs_add_path(world, e, 0, path); + ecs_assert(e != 0, ECS_INTERNAL_ERROR, NULL); } else { - flecs_query_add_subquery(world, desc->parent, result); - result->parent = desc->parent; - } + /* 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 (desc->order_by) { - flecs_query_order_by( - world, result, desc->order_by_component, desc->order_by, - desc->sort_table); + ecs_entity_t with = ecs_get_with(world); + if (with) { + ecs_add_id(world, e, with); + } + } } - if (!ecs_query_table_count(result) && result->filter.term_count) { - ecs_add_id(world, entity, EcsEmpty); - } + return e; +} - ecs_poly_modified(world, entity, ecs_query_t); +static +bool plecs_pred_is_subj( + ecs_term_t *term, + plecs_state_t *state) +{ + if (term->src.name != NULL) { + return false; + } + if (term->second.name != NULL) { + return false; + } + if (ecs_term_match_0(term)) { + 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; + } - ecs_log_pop_1(); + return true; +} - return result; -error: - if (result) { - ecs_filter_fini(&result->filter); - ecs_os_free(result); +/* Set masks aren't useful in plecs, so translate them back to entity names */ +static +const char* plecs_set_mask_to_name( + ecs_flags32_t flags) +{ + flags &= EcsTraverseFlags; + if (flags == EcsSelf) { + return "self"; + } else if (flags == EcsUp) { + return "up"; + } else if (flags == EcsDown) { + return "down"; + } else if (flags == EcsCascade || flags == (EcsUp|EcsCascade)) { + return "cascade"; + } else if (flags == EcsParent) { + return "parent"; } return NULL; } -void ecs_query_fini( - ecs_query_t *query) +static +char* plecs_trim_annot( + char *annot) { - ecs_poly_assert(query, ecs_query_t); - ecs_delete(query->world, query->entity); + annot = (char*)ecs_parse_whitespace(annot); + int32_t len = ecs_os_strlen(annot) - 1; + while (isspace(annot[len]) && (len > 0)) { + annot[len] = '\0'; + len --; + } + return annot; } -const ecs_filter_t* ecs_query_get_filter( - ecs_query_t *query) +static +void plecs_apply_annotations( + ecs_world_t *world, + ecs_entity_t subj, + plecs_state_t *state) { - ecs_poly_assert(query, ecs_query_t); - return &query->filter; + (void)world; + (void)subj; + (void)state; +#ifdef FLECS_DOC + int32_t i = 0, count = state->annot_count; + for (i = 0; i < count; i ++) { + char *annot = state->annot[i]; + if (!ecs_os_strncmp(annot, "@brief ", 7)) { + annot = plecs_trim_annot(annot + 7); + ecs_doc_set_brief(world, subj, annot); + } else if (!ecs_os_strncmp(annot, "@link ", 6)) { + annot = plecs_trim_annot(annot + 6); + ecs_doc_set_link(world, subj, annot); + } else if (!ecs_os_strncmp(annot, "@name ", 6)) { + annot = plecs_trim_annot(annot + 6); + ecs_doc_set_name(world, subj, annot); + } else if (!ecs_os_strncmp(annot, "@color ", 7)) { + annot = plecs_trim_annot(annot + 7); + ecs_doc_set_color(world, subj, annot); + } + } +#else + ecs_warn("cannot apply annotations, doc addon is missing"); +#endif } -ecs_iter_t ecs_query_iter( - const ecs_world_t *stage, - ecs_query_t *query) +static +int plecs_create_term( + ecs_world_t *world, + ecs_term_t *term, + const char *name, + const char *expr, + int64_t column, + plecs_state_t *state) { - ecs_poly_assert(query, ecs_query_t); - ecs_check(!(query->flags & EcsQueryIsOrphaned), - ECS_INVALID_PARAMETER, NULL); + state->last_subject = 0; + state->last_predicate = 0; + state->last_object = 0; + state->last_assign_id = 0; - ecs_world_t *world = query->world; - ecs_poly_assert(world, ecs_world_t); + const char *pred_name = term->first.name; + const char *subj_name = term->src.name; + const char *obj_name = term->second.name; - /* Process table events to ensure that the list of iterated tables doesn't - * contain empty tables. */ - flecs_process_pending_tables(world); + if (!subj_name) { + subj_name = plecs_set_mask_to_name(term->src.flags); + } + if (!obj_name) { + obj_name = plecs_set_mask_to_name(term->second.flags); + } - /* If query has order_by, apply sort */ - flecs_query_sort_tables(world, query); + if (!ecs_term_id_is_set(&term->first)) { + ecs_parser_error(name, expr, column, "missing predicate in expression"); + return -1; + } - /* If monitors changed, do query rematching */ - if (!(world->flags & EcsWorldReadonly) && query->flags & EcsQueryHasRefs) { - flecs_eval_component_monitors(world); + if (state->assign_stmt && !ecs_term_match_this(term)) { + ecs_parser_error(name, expr, column, + "invalid statement in assign statement"); + return -1; } - query->prev_match_count = query->match_count; + bool pred_as_subj = plecs_pred_is_subj(term, state); - /* Prepare iterator */ + ecs_entity_t pred = plecs_ensure_entity(world, state, pred_name, 0, pred_as_subj); + ecs_entity_t subj = plecs_ensure_entity(world, state, subj_name, pred, true); + ecs_entity_t obj = 0; - int32_t table_count; - if (query->table_slices) { - table_count = ecs_vector_count(query->table_slices); - } else { - table_count = ecs_query_table_count(query); + if (ecs_term_id_is_set(&term->second)) { + obj = plecs_ensure_entity(world, state, obj_name, pred, + state->assign_stmt == false); + if (!obj) { + return -1; + } } - ecs_query_iter_t it = { - .query = query, - .node = query->list.first, - .last = NULL - }; + if (state->assign_stmt || state->isa_stmt) { + subj = state->assign_to; + } - if (query->order_by && query->list.count) { - it.node = ecs_vector_first(query->table_slices, ecs_query_table_node_t); + if (state->isa_stmt && obj) { + ecs_parser_error(name, expr, column, + "invalid object in inheritance statement"); + return -1; } - ecs_flags32_t flags = 0; - ECS_BIT_COND(flags, EcsIterIsFilter, ECS_BIT_IS_SET(query->filter.flags, - EcsFilterIsFilter)); - ECS_BIT_COND(flags, EcsIterIsInstanced, ECS_BIT_IS_SET(query->filter.flags, - EcsFilterIsInstanced)); + if (state->using_stmt && (obj || subj)) { + ecs_parser_error(name, expr, column, + "invalid predicate/object in using statement"); + return -1; + } - ecs_iter_t result = { - .real_world = world, - .world = (ecs_world_t*)stage, - .terms = query->filter.terms, - .field_count = query->filter.field_count, - .table_count = table_count, - .flags = flags, - .priv.iter.query = it, - .next = ecs_query_next, - }; + if (state->isa_stmt) { + pred = ecs_pair(EcsIsA, pred); + } - ecs_filter_t *filter = &query->filter; - if (filter->flags & EcsFilterMatchOnlyThis) { - /* When the query only matches This terms, we can reuse the storage from - * the cache to populate the iterator */ - flecs_iter_init(&result, flecs_iter_cache_ptrs); + 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; + + pred_as_subj = false; } else { - /* Check if non-This terms (like singleton terms) still match */ - ecs_iter_t fit = flecs_filter_iter_w_flags( - world, &query->filter, EcsIterIgnoreThis); - if (!ecs_filter_next(&fit)) { - /* No match, so return nothing */ - ecs_iter_fini(&result); - goto noresults; + 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; } + } - /* Initialize iterator with private storage for ids, ptrs, sizes and - * columns so we have a place to store the non-This data */ - flecs_iter_init(&result, flecs_iter_cache_ptrs | flecs_iter_cache_ids | - flecs_iter_cache_columns | flecs_iter_cache_sizes); + /* 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; - /* Copy the data */ - int32_t term_count = filter->field_count; - if (term_count) { - if (result.ptrs) { - ecs_os_memcpy_n(result.ptrs, fit.ptrs, void*, term_count); + if (obj) { + id = ecs_pair(pred, obj); + } else { + id = pred; + } + + 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); + + state->using[state->using_frame ++] = pred; + state->using_frames[state->sp] = state->using_frame; + + /* 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]); } - ecs_os_memcpy_n(result.ids, fit.ids, ecs_id_t, term_count); - ecs_os_memcpy_n(result.sizes, fit.sizes, ecs_size_t, term_count); - ecs_os_memcpy_n(result.columns, fit.columns, int32_t, term_count); } + } - ecs_iter_fini(&fit); + /* 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); } - return result; -error: -noresults: - return (ecs_iter_t) { - .flags = EcsIterNoResults, - .next = ecs_query_next - }; -} + /* If annotations preceded the statement, append */ + if (state->annot_count) { + if (!subj) { + ecs_parser_error(name, expr, column, + "missing subject for annotations"); + return -1; + } -void ecs_query_set_group( - ecs_iter_t *it, - uint64_t group_id) -{ - ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(it->next == ecs_query_next, ECS_INVALID_PARAMETER, NULL); - ecs_check(!(it->flags & EcsIterIsValid), ECS_INVALID_PARAMETER, NULL); + plecs_apply_annotations(world, subj, state); + } - ecs_query_iter_t *qit = &it->priv.iter.query; - ecs_query_t *q = qit->query; - ecs_check(q != NULL, ECS_INVALID_PARAMETER, NULL); + return 0; +} - ecs_query_table_list_t *node = flecs_query_get_group(q, group_id); - if (!node) { - qit->node = NULL; - return; +static +const char* plecs_parse_inherit_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, + "cannot nest inheritance"); + return NULL; } - ecs_query_table_node_t *first = node->first; - if (first) { - qit->node = node->first; - qit->last = node->last->next; - } else { - qit->node = NULL; - qit->last = NULL; + if (!state->last_subject) { + ecs_parser_error(name, expr, ptr - expr, + "missing entity to assign inheritance to"); + return NULL; } -error: - return; + state->isa_stmt = true; + state->assign_to = state->last_subject; + + return ptr; } static -int find_smallest_column( - ecs_table_t *table, - ecs_query_table_match_t *table_data, - ecs_vector_t *sparse_columns) +const char* plecs_parse_assign_expr( + ecs_world_t *world, + const char *name, + const char *expr, + const char *ptr, + plecs_state_t *state) { - flecs_switch_term_t *sparse_column_array = - ecs_vector_first(sparse_columns, flecs_switch_term_t); - int32_t i, count = ecs_vector_count(sparse_columns); - int32_t min = INT_MAX, index = 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 ++) { - /* The array with sparse queries for the matched table */ - flecs_switch_term_t *sparse_column = &sparse_column_array[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; + } - /* Pointer to the switch column struct of the table */ - ecs_switch_t *sw = sparse_column->sw_column; +#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 the sparse column pointer hadn't been retrieved yet, do it now */ - if (!sw) { - /* Get the table column index from the signature column index */ - int32_t table_column_index = table_data->columns[ - sparse_column->signature_column_index]; + if (!assign_to) { + ecs_parser_error(name, expr, ptr - expr, + "missing entity to assign to"); + return NULL; + } - /* Translate the table column index to switch column index */ - table_column_index -= table->sw_offset; - ecs_assert(table_column_index >= 1, ECS_INTERNAL_ERROR, NULL); + 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; + } - /* Get the sparse column */ - ecs_data_t *data = &table->data; - sw = sparse_column->sw_column = - &data->sw_columns[table_column_index - 1]; - } + void *value_ptr = ecs_get_mut_id(world, assign_to, assign_id); - /* Find the smallest column */ - int32_t case_count = flecs_switch_case_count(sw, sparse_column->sw_case); - if (case_count < min) { - min = case_count; - index = i + 1; - } + 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 index; -} + ecs_modified_id(world, assign_to, assign_id); +#endif -typedef struct { - int32_t first; - int32_t count; -} query_iter_cursor_t; + return ptr; +} 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, - query_iter_cursor_t *cur, - bool filter) +const char* plecs_parse_assign_stmt( + ecs_world_t *world, + const char *name, + const char *expr, + const char *ptr, + plecs_state_t *state) { - bool first_iteration = false; - int32_t sparse_smallest; + (void)world; - if (!(sparse_smallest = iter->sparse_smallest)) { - sparse_smallest = iter->sparse_smallest = find_smallest_column( - table, matched_table, sparse_columns); - first_iteration = true; + 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; } - sparse_smallest -= 1; + if (state->assign_stmt) { + ecs_parser_error(name, expr, ptr - expr, + "invalid assign statement in assign statement"); + return NULL; + } - flecs_switch_term_t *columns = ecs_vector_first( - sparse_columns, flecs_switch_term_t); - flecs_switch_term_t *column = &columns[sparse_smallest]; - ecs_switch_t *sw, *sw_smallest = column->sw_column; - ecs_entity_t case_smallest = column->sw_case; + if (!state->scope_assign_stmt) { + state->assign_to = state->last_subject; + } - /* Find next entity to iterate in sparse column */ - int32_t first, sparse_first = iter->sparse_first; + state->assign_stmt = true; + + /* Assignment without a preceding component */ + if (ptr[0] == '{') { + ecs_entity_t type = 0; - 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; - } + if (state->scope_assign_stmt) { + ecs_assert(state->assign_to == ecs_get_scope(world), + ECS_INTERNAL_ERROR, NULL); } - } - - 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 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; } + } - column = &columns[i]; - sw = column->sw_column; + /* 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]; + } - if (flecs_switch_get(sw, first) != column->sw_case) { - first = flecs_switch_next(sw_smallest, first); - if (first == -1) { - goto done; - } + /* 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]; } } - } while (i != count); - cur->first = iter->sparse_first = first; - cur->count = 1; + if (!type) { + ecs_parser_error(name, expr, ptr - expr, + "missing type for assignment"); + return NULL; + } - 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; + state->last_assign_id = type; + } - return -1; + return ptr; } -#define BS_MAX ((uint64_t)0xFFFFFFFFFFFFFFFF) - static -int bitset_column_next( - ecs_table_t *table, - ecs_vector_t *bitset_columns, - ecs_query_iter_t *iter, - query_iter_cursor_t *cur) +const char* plecs_parse_using_stmt( + const char *name, + const char *expr, + const char *ptr, + plecs_state_t *state) { - /* 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) - }; - - int32_t i, count = ecs_vector_count(bitset_columns); - flecs_bitset_term_t *columns = ecs_vector_first( - bitset_columns, flecs_bitset_term_t); - int32_t bs_offset = table->bs_offset; - - int32_t first = iter->bitset_first; - int32_t last = 0; - - for (i = 0; i < count; i ++) { - flecs_bitset_term_t *column = &columns[i]; - ecs_bitset_t *bs = columns[i].bs_column; - - if (!bs) { - int32_t index = column->column_index; - ecs_assert((index - bs_offset >= 0), ECS_INTERNAL_ERROR, NULL); - bs = &table->data.bs_columns[index - bs_offset]; - columns[i].bs_column = bs; - } - - 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 (state->isa_stmt || state->assign_stmt) { + ecs_parser_error(name, expr, ptr - expr, + "invalid usage of using keyword"); + return NULL; + } - if (bs_block >= bs_block_count) { - goto done; - } + /* Add following expressions to using list */ + state->using_stmt = true; - uint64_t *data = bs->data; - int32_t bs_start = first & 0x3F; + return ptr + 5; +} - /* 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; - } +static +const char* plecs_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; + } - bs_start = 0; - remain = BS_MAX; /* Test the full block */ - v = data[bs_block]; - } + if (state->assign_stmt) { + ecs_parser_error(name, expr, ptr - expr, + "invalid with in assign_stmt"); + return NULL; + } - /* Step 2: find the first non-empty element in the block */ - while (!(v & bitmask[bs_start])) { - bs_start ++; + /* Add following expressions to with list */ + state->with_stmt = true; + return ptr + 5; +} - /* 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 ++; +static +const char* plecs_parse_scope_open( + ecs_world_t *world, + const char *name, + const char *expr, + const char *ptr, + plecs_state_t *state) +{ + state->isa_stmt = false; - if (bs_block_end == bs_block_count) { - break; - } + if (state->assign_stmt) { + ecs_parser_error(name, expr, ptr - expr, + "invalid scope in assign_stmt"); + return NULL; + } - v = data[bs_block_end]; - remain = BS_MAX; /* Test the full block */ - } + state->sp ++; - /* Step 4: find remainder of enabled elements in current block */ - if (bs_block_end != bs_block_count) { - while ((v & bitmask[bs_end])) { - bs_end ++; - } - } + ecs_entity_t scope = 0; + ecs_entity_t default_scope_type = 0; - /* Block was not 100% occupied, so bs_start must be smaller than 64 */ - ecs_assert(bs_end < 64, ECS_INTERNAL_ERROR, NULL); + if (!state->with_stmt) { + if (state->last_subject) { + scope = state->last_subject; + ecs_set_scope(world, state->last_subject); - /* 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; - } + /* Check if scope has a default child component */ + ecs_entity_t def_type_src = ecs_get_target_for_id(world, scope, + 0, ecs_pair(EcsDefaultChildComponent, EcsWildcard)); - /* 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; + if (def_type_src) { + default_scope_type = ecs_get_target( + world, def_type_src, EcsDefaultChildComponent, 0); } - - /* 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; + } 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); + } + ecs_set_scope(world, state->last_predicate); } } - 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; + 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]; } - - /* Keep track of last processed element for iteration */ - iter->bitset_first = last; - return 0; -done: - iter->sparse_smallest = 0; - iter->sparse_first = 0; - return -1; + state->using_frames[state->sp] = state->using_frame; + state->with_frames[state->sp] = state->with_frame; + state->with_stmt = false; + + return ptr; } static -void mark_columns_dirty( - ecs_query_t *query, - ecs_query_table_match_t *table_data) +const char* plecs_parse_scope_close( + ecs_world_t *world, + const char *name, + const char *expr, + const char *ptr, + plecs_state_t *state) { - ecs_table_t *table = table_data->table; + if (state->isa_stmt) { + ecs_parser_error(name, expr, ptr - expr, + "invalid '}' after inheritance statement"); + return NULL; + } - if (table && table->dirty_state) { - 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]; - int32_t ti = term->field_index; + if (state->assign_stmt) { + ecs_parser_error(name, expr, ptr - expr, + "unfinished assignment before }"); + return NULL; + } - if (term->inout == EcsIn || term->inout == EcsInOutNone) { - /* Don't mark readonly terms dirty */ - continue; - } + state->scope[state->sp] = 0; + state->default_scope_type[state->sp] = 0; + state->sp --; - if (table_data->sources[ti] != 0) { - /* Don't mark table dirty if term is not from the table */ - continue; - } + if (state->sp < 0) { + ecs_parser_error(name, expr, ptr - expr, "invalid } without a {"); + return NULL; + } - int32_t index = table_data->columns[ti]; - if (index <= 0) { - /* If term is not set, there's nothing to mark dirty */ - continue; - } + ecs_id_t id = state->scope[state->sp]; - /* 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 (!id || ECS_HAS_ID_FLAG(id, PAIR)) { + ecs_set_with(world, id); } -} - -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; + if (!id || !ECS_HAS_ID_FLAG(id, PAIR)) { + ecs_set_scope(world, id); } - return flecs_iter_next_instanced(it, ecs_query_next_instanced(it)); -error: - return false; + 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; } -bool ecs_query_next_instanced( - ecs_iter_t *it) +static +const char *plecs_parse_plecs_term( + ecs_world_t *world, + const char *name, + const char *expr, + const char *ptr, + plecs_state_t *state) { - ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(it->next == ecs_query_next, ECS_INVALID_PARAMETER, NULL); + ecs_term_t term = {0}; + ecs_entity_t scope = ecs_get_scope(world); - if (ECS_BIT_IS_SET(it->flags, EcsIterNoResults)) { - goto done; + /* 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; + + ptr = ecs_parse_term(world, name, expr, ptr, &term); + if (!ptr) { + return NULL; } - ECS_BIT_SET(it->flags, EcsIterIsValid); + if (!ecs_term_is_initialized(&term)) { + ecs_parser_error(name, expr, ptr - expr, "expected identifier"); + return NULL; /* No term found */ + } - ecs_query_iter_t *iter = &it->priv.iter.query; - ecs_query_t *query = iter->query; - ecs_world_t *world = query->world; - ecs_flags32_t flags = query->flags; - const ecs_filter_t *filter = &query->filter; - bool only_this = filter->flags & EcsFilterMatchOnlyThis; + /* 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.first.name, state, 0, false); + ecs_entity_t obj = plecs_lookup( + world, term.second.name, state, pred, false); + ecs_id_t id = 0; + if (pred && obj) { + id = ecs_pair(pred, obj); + } else if (pred) { + id = pred; + } - ecs_poly_assert(world, ecs_world_t); - (void)world; + if (id && (ecs_get_typeid(world, id) != 0)) { + scope_assignment = true; + } + } + } - query_iter_cursor_t cur; - ecs_query_table_node_t *node, *next, *prev, *last; - if ((prev = iter->prev)) { - /* Match has been iterated, update monitor for change tracking */ - if (flags & EcsQueryHasMonitor) { - flecs_query_sync_match_monitor(query, prev->match); - } - if (flags & EcsQueryHasOutColumns) { - mark_columns_dirty(query, prev->match); - } + bool prev = state->assign_stmt; + if (scope_assignment) { + state->assign_stmt = true; + state->assign_to = scope; + } + if (plecs_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; - iter->skip_count = 0; + ecs_term_fini(&term); - flecs_iter_validate(it); + return ptr; +} - last = iter->last; - for (node = iter->node; node != last; node = next) { - ecs_query_table_match_t *match = node->match; - ecs_table_t *table = match->table; - - next = node->next; - - if (table) { - cur.first = node->offset; - cur.count = node->count; - if (!cur.count) { - cur.count = ecs_table_count(table); - - /* List should never contain empty tables */ - ecs_assert(cur.count != 0, ECS_INTERNAL_ERROR, NULL); - } +static +const char* plecs_parse_annotation( + const char *name, + const char *expr, + const char *ptr, + plecs_state_t *state) +{ + do { + if(state->annot_count >= STACK_MAX_SIZE) { + ecs_parser_error(name, expr, ptr - expr, + "max number of annotations reached"); + return NULL; + } - ecs_vector_t *bitset_columns = match->bitset_columns; - ecs_vector_t *sparse_columns = match->sparse_columns; + char ch; + const char *start = ptr; + for (; (ch = *ptr) && ch != '\n'; ptr ++) { } - if (bitset_columns || sparse_columns) { - bool found = false; + int32_t len = (int32_t)(ptr - start); + char *annot = ecs_os_malloc_n(char, len + 1); + ecs_os_memcpy_n(annot, start, char, len); + annot[len] = '\0'; - do { - found = false; + state->annot[state->annot_count] = annot; + state->annot_count ++; - if (bitset_columns) { - if (bitset_column_next(table, bitset_columns, iter, - &cur) == -1) - { - /* No more enabled components for table */ - iter->bitset_first = 0; - break; - } else { - found = true; - next = node; - } - } + ptr = ecs_parse_fluff(ptr, NULL); + } while (ptr[0] == '@'); - 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 */ - iter->bitset_first = 0; - break; - } - } else { - found = true; - next = node; - iter->bitset_first = cur.first + cur.count; - } - } - } while (!found); + return ptr; +} - if (!found) { - continue; - } - } +static +void plecs_clear_annotations( + plecs_state_t *state) +{ + int32_t i, count = state->annot_count; + for (i = 0; i < count; i ++) { + ecs_os_free(state->annot[i]); + } + state->annot_count = 0; +} - it->group_id = match->group_id; - } else { - cur.count = 0; - cur.first = 0; - } +static +const char* plecs_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; - if (only_this) { - /* If query has only This terms, reuse cache storage */ - it->ids = match->ids; - it->columns = match->columns; - it->sizes = match->sizes; - } else { - /* If query has non-This terms make sure not to overwrite them */ - int32_t t, term_count = filter->term_count; - for (t = 0; t < term_count; t ++) { - ecs_term_t *term = &filter->terms[t]; - if (!ecs_term_match_this(term)) { - continue; - } + plecs_clear_annotations(state); - int32_t actual_index = term->field_index; - it->ids[actual_index] = match->ids[actual_index]; - it->columns[actual_index] = match->columns[actual_index]; - it->sizes[actual_index] = match->sizes[actual_index]; - } - } + ptr = ecs_parse_fluff(ptr, NULL); - it->sources = match->sources; - it->references = match->references; - it->instance_count = 0; + char ch = ptr[0]; - flecs_iter_populate_data(world, it, match->table, cur.first, cur.count, - it->ptrs, NULL); + if (!ch) { + goto done; + } else if (ch == '{') { + ptr = ecs_parse_fluff(ptr + 1, NULL); + goto scope_open; + } else if (ch == '}') { + ptr = ecs_parse_fluff(ptr + 1, NULL); + goto scope_close; + } else if (ch == '(') { + goto term_expr; + } else if (ch == '@') { + ptr = plecs_parse_annotation(name, expr, ptr, state); + if (!ptr) goto error; + goto term_expr; + } else if (!ecs_os_strncmp(ptr, TOK_USING " ", 5)) { + ptr = plecs_parse_using_stmt(name, expr, ptr, state); + if (!ptr) goto error; + goto term_expr; + } else if (!ecs_os_strncmp(ptr, TOK_WITH " ", 5)) { + ptr = plecs_parse_with_stmt(name, expr, ptr, state); + if (!ptr) goto error; + goto term_expr; + } else { + goto term_expr; + } - iter->node = next; - iter->prev = node; - goto yield; +term_expr: + if (!ptr[0]) { + goto done; } -done: -error: - ecs_iter_fini(it); - return false; - -yield: - return true; -} + if (!(ptr = plecs_parse_plecs_term(world, name, ptr, ptr, state))) { + goto error; + } -bool ecs_query_changed( - ecs_query_t *query, - const ecs_iter_t *it) -{ - if (it) { - ecs_check(it->next == ecs_query_next, ECS_INVALID_PARAMETER, NULL); - ecs_check(ECS_BIT_IS_SET(it->flags, EcsIterIsValid), - ECS_INVALID_PARAMETER, NULL); + ptr = ecs_parse_fluff(ptr, NULL); - ecs_query_table_match_t *qt = - (ecs_query_table_match_t*)it->priv.iter.query.prev; - ecs_assert(qt != NULL, ECS_INVALID_PARAMETER, NULL); + if (ptr[0] == '{' && !isspace(ptr[-1])) { + /* A '{' directly after an identifier (no whitespace) is a literal */ + goto assign_expr; + } - if (!query) { - query = it->priv.iter.query.query; - } else { - ecs_check(query == it->priv.iter.query.query, - ECS_INVALID_PARAMETER, NULL); + if (!state->using_stmt) { + if (ptr[0] == ':') { + ptr = ecs_parse_fluff(ptr + 1, NULL); + goto inherit_stmt; + } else if (ptr[0] == '=') { + ptr = ecs_parse_fluff(ptr + 1, NULL); + goto assign_stmt; + } else if (ptr[0] == ',') { + ptr = ecs_parse_fluff(ptr + 1, NULL); + goto term_expr; + } else if (ptr[0] == '{') { + state->assign_stmt = false; + ptr = ecs_parse_fluff(ptr + 1, NULL); + goto scope_open; } + } - ecs_check(query != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_poly_assert(query, ecs_query_t); - - flecs_process_pending_tables(it->real_world); + state->assign_stmt = false; + goto done; - return flecs_query_check_match_monitor(query, qt); - } +inherit_stmt: + ptr = plecs_parse_inherit_stmt(name, expr, ptr, state); + if (!ptr) goto error; - ecs_poly_assert(query, ecs_query_t); - ecs_check(!(query->flags & EcsQueryIsOrphaned), - ECS_INVALID_PARAMETER, NULL); + /* Expect base identifier */ + goto term_expr; - flecs_process_pending_tables(query->world); +assign_stmt: + ptr = plecs_parse_assign_stmt(world, name, expr, ptr, state); + if (!ptr) goto error; - if (!(query->flags & EcsQueryHasMonitor)) { - query->flags |= EcsQueryHasMonitor; - flecs_query_init_query_monitors(query); - return true; /* Monitors didn't exist yet */ - } + ptr = ecs_parse_fluff(ptr, NULL); - if (query->match_count != query->prev_match_count) { - return true; + /* Assignment without a preceding component */ + if (ptr[0] == '{') { + goto assign_expr; } - return flecs_query_check_query_monitor(query); -error: - return false; -} + /* Expect component identifiers */ + goto term_expr; -void ecs_query_skip( - ecs_iter_t *it) -{ - ecs_assert(it->next == ecs_query_next, ECS_INVALID_PARAMETER, NULL); - ecs_assert(ECS_BIT_IS_SET(it->flags, EcsIterIsValid), - ECS_INVALID_PARAMETER, NULL); +assign_expr: + ptr = plecs_parse_assign_expr(world, name, expr, ptr, state); + if (!ptr) goto error; - if (it->instance_count > it->count) { - it->priv.iter.query.skip_count ++; - if (it->priv.iter.query.skip_count == it->instance_count) { - /* For non-instanced queries, make sure all entities are skipped */ - it->priv.iter.query.prev = NULL; - } + ptr = ecs_parse_fluff(ptr, NULL); + if (ptr[0] == ',') { + ptr ++; + goto term_expr; + } else if (ptr[0] == '{') { + state->assign_stmt = false; + ptr ++; + goto scope_open; } else { - it->priv.iter.query.prev = NULL; + state->assign_stmt = false; + goto done; } -} -bool ecs_query_orphaned( - ecs_query_t *query) -{ - ecs_poly_assert(query, ecs_query_t); - return query->flags & EcsQueryIsOrphaned; -} +scope_open: + ptr = plecs_parse_scope_open(world, name, expr, ptr, state); + if (!ptr) goto error; + goto done; -char* ecs_query_str( - const ecs_query_t *query) -{ - return ecs_filter_str(query->world, &query->filter); -} +scope_close: + ptr = plecs_parse_scope_close(world, name, expr, ptr, state); + if (!ptr) goto error; + goto done; -int32_t ecs_query_table_count( - const ecs_query_t *query) -{ - ecs_run_aperiodic(query->world, EcsAperiodicEmptyTables); - return query->cache.tables.count; +done: + return ptr; +error: + return NULL; } -int32_t ecs_query_empty_table_count( - const ecs_query_t *query) +int ecs_plecs_from_str( + ecs_world_t *world, + const char *name, + const char *expr) { - ecs_run_aperiodic(query->world, EcsAperiodicEmptyTables); - return query->cache.empty_tables.count; -} + const char *ptr = expr; + ecs_term_t term = {0}; + plecs_state_t state = {0}; -int32_t ecs_query_entity_count( - const ecs_query_t *query) -{ - ecs_run_aperiodic(query->world, EcsAperiodicEmptyTables); - - int32_t result = 0; - ecs_table_cache_hdr_t *cur, *last = query->cache.tables.last; - if (!last) { + if (!expr) { return 0; } - for (cur = query->cache.tables.first; cur != NULL; cur = cur->next) { - result += ecs_table_count(cur->table); - } + state.scope[0] = 0; + ecs_entity_t prev_scope = ecs_set_scope(world, 0); + ecs_entity_t prev_with = ecs_set_with(world, 0); - return result; -} + do { + expr = ptr = plecs_parse_stmt(world, name, expr, ptr, &state); + if (!ptr) { + goto error; + } -ecs_entity_t ecs_query_entity( - const ecs_query_t *query) -{ - return query->entity; -} + if (!ptr[0]) { + break; /* End of expression */ + } + } while (true); -#include + ecs_set_scope(world, prev_scope); + ecs_set_with(world, prev_with); -/* Utility macros to enforce consistency when initializing iterator fields */ + plecs_clear_annotations(&state); -/* If term count is smaller than cache size, initialize with inline array, - * otherwise allocate. */ -#define INIT_CACHE(it, fields, f, count, cache_size)\ - if (!it->f && (fields & flecs_iter_cache_##f) && count) {\ - if (count <= cache_size) {\ - it->f = it->priv.cache.f;\ - it->priv.cache.used |= flecs_iter_cache_##f;\ - } else {\ - it->f = ecs_os_calloc(ECS_SIZEOF(*(it->f)) * count);\ - it->priv.cache.allocated |= flecs_iter_cache_##f;\ - }\ + if (state.sp != 0) { + ecs_parser_error(name, expr, 0, "missing end of scope"); + goto error; } -/* If array is using the cache, make sure that its address is correct in case - * the iterator got moved (typically happens when returned by a function) */ -#define VALIDATE_CACHE(it, f)\ - if (it->f) {\ - if (it->priv.cache.used & flecs_iter_cache_##f) {\ - it->f = it->priv.cache.f;\ - }\ + if (state.assign_stmt) { + ecs_parser_error(name, expr, 0, "unfinished assignment"); + goto error; } -/* If array is allocated, free it when finalizing the iterator */ -#define FINI_CACHE(it, f)\ - if (it->f) {\ - if (it->priv.cache.allocated & flecs_iter_cache_##f) {\ - ecs_os_free((void*)it->f);\ - }\ + if (state.errors) { + goto error; } -void flecs_iter_init( - ecs_iter_t *it, - ecs_flags8_t fields) + return 0; +error: + ecs_set_scope(world, state.scope[0]); + ecs_set_with(world, prev_with); + ecs_term_fini(&term); + return -1; +} + +int ecs_plecs_from_file( + ecs_world_t *world, + const char *filename) { - ecs_assert(!ECS_BIT_IS_SET(it->flags, EcsIterIsValid), - ECS_INTERNAL_ERROR, NULL); + FILE* file; + char* content = NULL; + int32_t bytes; + size_t size; - it->priv.cache.used = 0; - it->priv.cache.allocated = 0; + /* Open file for reading */ + ecs_os_fopen(&file, filename, "r"); + if (!file) { + ecs_err("%s (%s)", ecs_os_strerror(errno), filename); + goto error; + } - INIT_CACHE(it, fields, ids, it->field_count, ECS_TERM_CACHE_SIZE); - INIT_CACHE(it, fields, sources, it->field_count, ECS_TERM_CACHE_SIZE); - INIT_CACHE(it, fields, match_indices, it->field_count, ECS_TERM_CACHE_SIZE); - INIT_CACHE(it, fields, columns, it->field_count, ECS_TERM_CACHE_SIZE); - INIT_CACHE(it, fields, variables, it->variable_count, - ECS_VARIABLE_CACHE_SIZE); - INIT_CACHE(it, fields, sizes, it->field_count, ECS_TERM_CACHE_SIZE); + /* Determine file size */ + fseek(file, 0 , SEEK_END); + bytes = (int32_t)ftell(file); + if (bytes == -1) { + goto error; + } + rewind(file); - if (!ECS_BIT_IS_SET(it->flags, EcsIterIsFilter)) { - INIT_CACHE(it, fields, ptrs, it->field_count, ECS_TERM_CACHE_SIZE); + /* 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 { - it->ptrs = NULL; + content[size] = '\0'; } -} -static -void iter_validate_cache( - ecs_iter_t *it) -{ - /* Make sure pointers to cache are up to date in case iter has moved */ - VALIDATE_CACHE(it, ids); - VALIDATE_CACHE(it, sources); - VALIDATE_CACHE(it, match_indices); - VALIDATE_CACHE(it, columns); - VALIDATE_CACHE(it, variables); - VALIDATE_CACHE(it, sizes); - VALIDATE_CACHE(it, ptrs); -} + fclose(file); -void flecs_iter_validate( - ecs_iter_t *it) -{ - iter_validate_cache(it); - ECS_BIT_SET(it->flags, EcsIterIsValid); + int result = ecs_plecs_from_str(world, filename, content); + ecs_os_free(content); + return result; +error: + ecs_os_free(content); + return -1; } -void ecs_iter_fini( - ecs_iter_t *it) -{ - ECS_BIT_CLEAR(it->flags, EcsIterIsValid); - - if (it->fini) { - it->fini(it); - } - - FINI_CACHE(it, ids); - FINI_CACHE(it, columns); - FINI_CACHE(it, sources); - FINI_CACHE(it, sizes); - FINI_CACHE(it, ptrs); - FINI_CACHE(it, match_indices); - FINI_CACHE(it, variables); -} +#endif -static -ecs_size_t iter_get_size_for_id( - ecs_world_t *world, - ecs_id_t id) -{ - ecs_id_record_t *idr = flecs_id_record_get(world, id); - if (!idr) { - return 0; - } - if (idr->flags & EcsIdUnion) { - return ECS_SIZEOF(ecs_entity_t); - } +#ifdef FLECS_RULES - if (idr->type_info) { - return idr->type_info->size; - } +#define ECS_RULE_MAX_VAR_COUNT (32) - return 0; -} +#define RULE_PAIR_PREDICATE (1) +#define RULE_PAIR_OBJECT (2) -static -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) -{ - bool is_shared = false; - ecs_table_t *table; - void *data; - ecs_size_t size = 0; - int32_t row, u_index; - - if (!column) { - /* Term has no data. This includes terms that have Not operators. */ - goto no_data; - } - - if (!it->terms) { - goto no_data; - } - - /* Filter terms may match with data but don't return it */ - if (it->terms[t].inout == EcsInOutNone) { - if (size_out) { - size = iter_get_size_for_id(world, it->ids[t]); - } - goto no_data; - } - - if (column < 0) { - is_shared = true; - - /* Data is not from This */ - if (it->references) { - /* The reference array is used only for components matched on a - * table (vs. individual entities). Remaining components should be - * assigned outside of this function */ - if (ecs_term_match_this(&it->terms[t])) { +/* 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 id; + } first; + union { + int32_t reg; + ecs_entity_t id; + } second; + int32_t reg_mask; /* bit 1 = predicate, bit 2 = object */ - /* Iterator provides cached references for non-This terms */ - ecs_ref_t *ref = &it->references[-column - 1]; - if (ptr_out) { - if (ref->id) { - ptr_out[0] = (void*)ecs_ref_get_id(world, ref, ref->id); - } else { - ptr_out[0] = NULL; - } - } + bool transitive; /* Is predicate transitive */ + bool final; /* Is predicate final */ + bool reflexive; /* Is predicate reflexive */ + bool acyclic; /* Is predicate acyclic */ + bool second_0; +} ecs_rule_pair_t; - if (!ref->id) { - is_shared = false; - } +/* 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 */ - /* 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 is_shared; - } + bool wildcard; /* Does the filter contain wildcards */ + bool first_wildcard; /* Is predicate a wildcard */ + bool second_wildcard; /* Is object a wildcard */ + bool same_var; /* True if first & second are both the same variable */ - return true; - } else { - ecs_entity_t subj = it->sources[t]; - ecs_assert(subj != 0, ECS_INTERNAL_ERROR, NULL); + 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; - /* 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 = flecs_entities_get(world, subj); - ecs_assert(r != NULL && r->table != NULL, ECS_INTERNAL_ERROR, NULL); +/* 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; - row = ECS_RECORD_TO_ROW(r->row); - table = r->table; +/* 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 */ + EcsRuleInTable, /* Test if entity (subject) is in table (r_in) */ + EcsRuleEq, /* Test if entity in (subject) and (r_in) are equal */ + EcsRuleYield /* Yield result */ +} ecs_rule_op_kind_t; - ecs_id_t id = it->ids[t]; - ecs_table_t *s_table = table->storage_table; - ecs_table_record_t *tr; +/* 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 */ - if (!s_table || !(tr = flecs_table_record_get(world, s_table, id))){ - u_index = flecs_table_column_to_union_index(table, -column - 1); - if (u_index != -1) { - goto has_union; - } - goto no_data; - } + int32_t on_pass; /* Jump location when match succeeds */ + int32_t on_fail; /* Jump location when match fails */ + int32_t frame; /* Register frame */ - /* 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_type_info_t *ti = table->type_info[column]; - ecs_column_t *s = &table->data.columns[column]; - size = ti->size; - data = ecs_storage_first(s); - /* Fallthrough to has_data */ - } - } else { - /* Data is from This, use table from iterator */ - table = it->table; - if (!table) { - goto no_data; - } + int32_t term; /* Corresponding term index in signature */ + int32_t r_in; /* Optional In/Out registers */ + int32_t r_out; - row = it->offset; + 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 storage_column = ecs_table_type_to_storage_index( - table, column - 1); - if (storage_column == -1) { - u_index = flecs_table_column_to_union_index(table, column - 1); - if (u_index != -1) { - goto has_union; - } - goto no_data; - } +/* With context. Shared with select. */ +typedef struct ecs_rule_with_ctx_t { + ecs_id_record_t *idr; /* Currently evaluated table set */ + ecs_table_cache_iter_t it; + int32_t column; +} ecs_rule_with_ctx_t; - ecs_type_info_t *ti = table->type_info[storage_column]; - ecs_column_t *s = &table->data.columns[storage_column]; - size = ti->size; - data = ecs_storage_first(s); +/* 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 (!table || !ecs_table_count(table)) { - goto no_data; - } +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; - /* Fallthrough to has_data */ - } +/* Superset context */ +typedef struct ecs_rule_superset_frame_t { + ecs_table_t *table; + int32_t column; +} ecs_rule_superset_frame_t; -has_data: - if (ptr_out) ptr_out[0] = ECS_ELEM(data, size, row); - if (size_out) size_out[0] = size; - return is_shared; +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; -has_union: { - /* 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->data.sw_columns[u_index]; - data = ecs_vector_first(flecs_switch_values(sw), ecs_entity_t); - size = ECS_SIZEOF(ecs_entity_t); - goto has_data; - } +/* Each context */ +typedef struct ecs_rule_each_ctx_t { + int32_t row; /* Currently evaluated row in evaluated table */ +} ecs_rule_each_ctx_t; -no_data: - if (ptr_out) ptr_out[0] = NULL; - if (size_out) size_out[0] = size; - return false; -} +/* Jump context */ +typedef struct ecs_rule_setjmp_ctx_t { + int32_t label; /* Operation label to jump to */ +} ecs_rule_setjmp_ctx_t; -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) -{ - if (it->table) { - it->frame_offset += ecs_table_count(it->table); - } +/* 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; - it->table = table; - it->offset = offset; - it->count = count; +/* 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 other; /* Id to table variable (-1 if none exists) */ + 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 (table) { - if (!count) { - count = it->count = ecs_table_count(table); - } - if (count) { - it->entities = ecs_storage_get_t( - &table->data.entities, ecs_entity_t, offset); - } else { - it->entities = NULL; - } - } +/* Variable ids per term */ +typedef struct ecs_rule_term_vars_t { + int32_t first; + int32_t src; + int32_t second; +} ecs_rule_term_vars_t; - int t, field_count = it->field_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_filter_t filter; /* Filter of rule */ - if (ECS_BIT_IS_SET(it->flags, EcsIterIsFilter)) { - ECS_BIT_CLEAR(it->flags, EcsIterHasShared); + /* Variable array */ + ecs_rule_var_t vars[ECS_RULE_MAX_VAR_COUNT]; - if (!sizes) { - return; - } + /* Passed to iterator */ + char *var_names[ECS_RULE_MAX_VAR_COUNT]; - /* Fetch sizes, skip fetching data */ - for (t = 0; t < field_count; t ++) { - sizes[t] = iter_get_size_for_id(world, it->ids[t]); - } - return; - } + /* Variable ids used in terms */ + ecs_rule_term_vars_t term_vars[ECS_RULE_MAX_VAR_COUNT]; - bool has_shared = false; + /* Variable evaluation order */ + int32_t var_eval_order[ECS_RULE_MAX_VAR_COUNT]; - if (ptrs && sizes) { - for (t = 0; t < field_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 < field_count; t ++) { - ecs_assert(it->columns != NULL, ECS_INTERNAL_ERROR, NULL); + int32_t var_count; /* Number of variables in signature */ + int32_t subj_var_count; + int32_t frame_count; /* Number of register frames */ + int32_t operation_count; /* Number of operations in rule */ - int32_t column = it->columns[t]; - void **ptr = NULL; - if (ptrs) { - ptr = &ptrs[t]; - } - ecs_size_t *size = NULL; - if (sizes) { - size = &sizes[t]; - } + ecs_iterable_t iterable; /* Iterable mixin */ +}; - has_shared |= flecs_iter_populate_term_data(world, it, t, column, - ptr, size); - } +/* 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) } +}; - ECS_BIT_COND(it->flags, EcsIterHasShared, has_shared); +static +void rule_error( + const ecs_rule_t *rule, + const char *fmt, + ...) +{ + char *fstr = ecs_filter_str(rule->world, &rule->filter); + va_list valist; + va_start(valist, fmt); + ecs_parser_errorv(rule->filter.name, fstr, -1, fmt, valist); + va_end(valist); + ecs_os_free(fstr); } -bool flecs_iter_next_row( - ecs_iter_t *it) +static +bool subj_is_set( + ecs_term_t *term) { - ecs_assert(it != NULL, ECS_INTERNAL_ERROR, NULL); - - bool is_instanced = ECS_BIT_IS_SET(it->flags, EcsIterIsInstanced); - if (!is_instanced) { - int32_t instance_count = it->instance_count; - int32_t count = it->count; - int32_t offset = it->offset; - - if (instance_count > count && offset < (instance_count - 1)) { - ecs_assert(count == 1, ECS_INTERNAL_ERROR, NULL); - int t, field_count = it->field_count; - - for (t = 0; t < field_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; - } - } - - return false; + return ecs_term_id_is_set(&term->src); } -bool flecs_iter_next_instanced( - ecs_iter_t *it, - bool result) +static +bool obj_is_set( + ecs_term_t *term) { - it->instance_count = it->count; - bool is_instanced = ECS_BIT_IS_SET(it->flags, EcsIterIsInstanced); - bool has_shared = ECS_BIT_IS_SET(it->flags, EcsIterHasShared); - if (result && !is_instanced && it->count && has_shared) { - it->count = 1; - } - return result; + return ecs_term_id_is_set(&term->second) || ECS_HAS_ID_FLAG(term->id_flags, PAIR); } -/* --- Public API --- */ - -void* ecs_field_w_size( - const ecs_iter_t *it, - size_t size, - int32_t term) +static +ecs_rule_op_t* create_operation( + ecs_rule_t *rule) { - ecs_check(it->flags & EcsIterIsValid, ECS_INVALID_PARAMETER, NULL); - ecs_check(!size || ecs_field_size(it, term) == size || - (!ecs_field_size(it, term) && (!it->ptrs || !it->ptrs[term - 1])), - ECS_INVALID_PARAMETER, NULL); + int32_t cur = rule->operation_count ++; + rule->operations = ecs_os_realloc( + rule->operations, (cur + 1) * ECS_SIZEOF(ecs_rule_op_t)); - (void)size; + ecs_rule_op_t *result = &rule->operations[cur]; + ecs_os_memset_t(result, 0, ecs_rule_op_t); - if (!term) { - return it->entities; - } + return result; +} - if (!it->ptrs) { - return NULL; +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 = "."; } - return it->ptrs[term - 1]; -error: - return NULL; + return name; } -bool ecs_field_is_readonly( - const ecs_iter_t *it, - int32_t term_index) +static +ecs_rule_var_t* create_variable( + ecs_rule_t *rule, + ecs_rule_var_kind_t kind, + const char *name) { - ecs_check(it->flags & EcsIterIsValid, ECS_INVALID_PARAMETER, NULL); - ecs_check(term_index > 0, ECS_INVALID_PARAMETER, NULL); - - ecs_term_t *term = &it->terms[term_index - 1]; - ecs_check(term != NULL, ECS_INVALID_PARAMETER, NULL); + int32_t cur = ++ rule->var_count; - if (term->inout == EcsIn) { - return true; + name = get_var_name(name); + if (name && !ecs_os_strcmp(name, "*")) { + /* Wildcards are treated as anonymous variables */ + name = NULL; + } + + ecs_rule_var_t *var = &rule->vars[cur - 1]; + if (name) { + var->name = ecs_os_strdup(name); } else { - ecs_term_id_t *src = &term->src; + /* Anonymous register */ + char name_buff[32]; + ecs_os_sprintf(name_buff, "_%u", cur - 1); + var->name = ecs_os_strdup(name_buff); + } - if (term->inout == EcsInOutDefault) { - if (!(ecs_term_match_this(term))) { - return true; - } + var->kind = kind; - if (!(src->flags & EcsSelf)) { - return true; - } - } - } + /* 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; -error: - return false; + /* 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; + + return var; } -bool ecs_field_is_writeonly( - const ecs_iter_t *it, - int32_t term_index) +static +ecs_rule_var_t* create_anonymous_variable( + ecs_rule_t *rule, + ecs_rule_var_kind_t kind) { - ecs_check(it->flags & EcsIterIsValid, ECS_INVALID_PARAMETER, NULL); - ecs_check(term_index > 0, ECS_INVALID_PARAMETER, NULL); + return create_variable(rule, kind, NULL); +} - ecs_term_t *term = &it->terms[term_index - 1]; - ecs_check(term != NULL, ECS_INVALID_PARAMETER, NULL); +/* 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_assert(rule != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(name != NULL, ECS_INTERNAL_ERROR, NULL); + + name = get_var_name(name); + + const ecs_rule_var_t *variables = rule->vars; + int32_t i, count = rule->var_count; - if (term->inout == EcsOut) { - return true; + for (i = 0; i < count; i ++) { + const ecs_rule_var_t *variable = &variables[i]; + if (!ecs_os_strcmp(name, variable->name)) { + if (kind == EcsRuleVarKindUnknown || kind == variable->kind) { + return (ecs_rule_var_t*)variable; + } + } } -error: - return false; + return NULL; } -int32_t ecs_iter_find_column( - const ecs_iter_t *it, - ecs_entity_t component) +/* 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) { - ecs_check(it->flags & EcsIterIsValid, ECS_INVALID_PARAMETER, NULL); - ecs_check(it->table != NULL, ECS_INVALID_PARAMETER, NULL); - return ecs_search(it->real_world, it->table, component, 0); -error: - return -1; + 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; } -bool ecs_field_is_set( - const ecs_iter_t *it, - int32_t index) +static +const char *term_id_var_name( + ecs_term_id_t *term_id) { - ecs_check(it->flags & EcsIterIsValid, 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; + if (term_id->flags & EcsIsVariable) { + if (term_id->name) { + return term_id->name; + } else if (term_id->id == EcsThis) { + return "."; + } else if (term_id->id == EcsWildcard) { + return "*"; + } else if (term_id->id == EcsAny) { + return "_"; + } else if (term_id->id == EcsVariable) { + return "$"; } else { - return true; + ecs_check(term_id->name != NULL, ECS_INVALID_PARAMETER, NULL); } } - return true; error: - return false; + return NULL; } -bool ecs_field_is_self( - const ecs_iter_t *it, - int32_t index) +static +int ensure_term_id_variable( + ecs_rule_t *rule, + ecs_term_t *term, + ecs_term_id_t *term_id) { - ecs_assert(it != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_assert(index >= 0, ECS_INVALID_PARAMETER, NULL); - return it->sources == NULL || it->sources[index - 1] == 0; + if (term_id->flags & EcsIsVariable) { + if (term_id->id == EcsAny) { + /* Any variables aren't translated to rule variables since their + * result isn't stored. */ + return 0; + } + + const char *name = term_id_var_name(term_id); + + /* If this is a Not term, it should not introduce new variables. It may + * however create entity variables if there already was an existing + * table variable */ + if (term->oper == EcsNot) { + if (!find_variable(rule, EcsRuleVarKindUnknown, name)) { + rule_error(rule, "variable in '%s' only appears in Not term", + name); + return -1; + } + } + + ecs_rule_var_t *var = ensure_variable(rule, EcsRuleVarKindEntity, name); + ecs_os_strset(&term_id->name, var->name); + return 0; + } + + return 0; } -ecs_id_t ecs_field_id( - const ecs_iter_t *it, - int32_t index) +static +bool term_id_is_variable( + ecs_term_id_t *term_id) { - ecs_assert(it != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_assert(index >= 0, ECS_INVALID_PARAMETER, NULL); - return it->ids[index - 1]; + return term_id->flags & EcsIsVariable; } -ecs_entity_t ecs_field_src( - const ecs_iter_t *it, - int32_t index) +/* Get variable from a term identifier */ +static +ecs_rule_var_t* term_id_to_var( + ecs_rule_t *rule, + ecs_term_id_t *id) { - ecs_assert(it != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_assert(index >= 0, ECS_INVALID_PARAMETER, NULL); - if (it->sources) { - return it->sources[index - 1]; - } else { - return 0; + if (id->flags & EcsIsVariable) { + return find_variable(rule, EcsRuleVarKindUnknown, term_id_var_name(id)); } + return NULL; } -size_t ecs_field_size( - const ecs_iter_t *it, - int32_t index) +/* Get variable from a term predicate */ +static +ecs_rule_var_t* term_pred( + ecs_rule_t *rule, + ecs_term_t *term) { - ecs_assert(it != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_assert(index >= 0, ECS_INVALID_PARAMETER, NULL); + return term_id_to_var(rule, &term->first); +} - if (index == 0) { - return sizeof(ecs_entity_t); - } else { - return (size_t)it->sizes[index - 1]; - } +/* 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->src); } -void* ecs_iter_column_w_size( - const ecs_iter_t *it, - size_t size, - int32_t index) +/* Get variable from a term object */ +static +ecs_rule_var_t* term_obj( + ecs_rule_t *rule, + ecs_term_t *term) { - ecs_check(it->flags & EcsIterIsValid, 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) { + if (obj_is_set(term)) { + return term_id_to_var(rule, &term->second); + } else { return NULL; } - - ecs_type_info_t *ti = table->type_info[storage_index]; - ecs_check(!size || (ecs_size_t)size == ti->size, - ECS_INVALID_PARAMETER, NULL); - (void)ti; - - ecs_column_t *column = &table->data.columns[storage_index]; - return ecs_storage_get(column, flecs_uto(int32_t, size), it->offset); -error: - return NULL; } -size_t ecs_iter_column_size( - const ecs_iter_t *it, - int32_t index) +/* Return predicate variable from pair */ +static +ecs_rule_var_t* pair_pred( + ecs_rule_t *rule, + const ecs_rule_pair_t *pair) { - ecs_check(it->flags & EcsIterIsValid, 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; + if (pair->reg_mask & RULE_PAIR_PREDICATE) { + return &rule->vars[pair->first.reg]; + } else { + return NULL; } - - ecs_type_info_t *ti = table->type_info[storage_index]; - return flecs_ito(size_t, ti->size); -error: - return 0; } -char* ecs_iter_str( - const ecs_iter_t *it) +/* Return object variable from pair */ +static +ecs_rule_var_t* pair_obj( + ecs_rule_t *rule, + const ecs_rule_pair_t *pair) { - ecs_world_t *world = it->world; - ecs_strbuf_t buf = ECS_STRBUF_INIT; - int i; - - if (it->field_count) { - ecs_strbuf_list_push(&buf, "term: ", ","); - for (i = 0; i < it->field_count; i ++) { - ecs_id_t id = ecs_field_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->field_count; i ++) { - ecs_entity_t subj = ecs_field_src(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"); + if (pair->reg_mask & RULE_PAIR_OBJECT) { + return &rule->vars[pair->second.reg]; + } else { + return 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; - } - - ecs_var_t var = it->variables[i]; - if (!var.entity) { - /* Skip table variables */ - continue; - } - - if (!actual_count) { - ecs_strbuf_list_push(&buf, "vars: ", ","); - } - - char *str = ecs_get_fullpath(world, var.entity); - ecs_strbuf_list_append(&buf, "%s=%s", var_name, str); - ecs_os_free(str); +/* 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) +{ + return rule->frame_count ++; +} - actual_count ++; - } - if (actual_count) { - ecs_strbuf_list_pop(&buf, "\n"); - } +/* 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_var_t* get_register_frame( + const ecs_rule_iter_t *it, + int32_t frame) +{ + if (it->registers) { + return &it->registers[frame * it->rule->var_count]; + } else { + return NULL; } +} - 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); - } - } +/* 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_var_t* get_registers( + const ecs_rule_iter_t *it, + ecs_rule_op_t *op) +{ + return get_register_frame(it, op->frame); +} - return ecs_strbuf_get(&buf); +/* 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]; } -void ecs_iter_poly( - const ecs_world_t *world, - const ecs_poly_t *poly, - ecs_iter_t *iter_out, - ecs_term_t *filter) +static +int32_t* rule_get_columns( + ecs_rule_iter_t *it, + ecs_rule_op_t *op) { - ecs_iterable_t *iterable = ecs_get_iterable(poly); - iterable->init(world, poly, iter_out, filter); + return rule_get_columns_frame(it, op->frame); } -bool ecs_iter_next( - ecs_iter_t *iter) +static +void entity_reg_set( + const ecs_rule_t *rule, + ecs_var_t *regs, + int32_t r, + ecs_entity_t entity) { - ecs_check(iter != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(iter->next != NULL, ECS_INVALID_PARAMETER, NULL); - return iter->next(iter); + (void)rule; + ecs_assert(rule->vars[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 false; + return; } -int32_t ecs_iter_count( - ecs_iter_t *it) +static +ecs_entity_t entity_reg_get( + const ecs_rule_t *rule, + ecs_var_t *regs, + int32_t r) { - ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); - - ECS_BIT_SET(it->flags, EcsIterIsFilter); - ECS_BIT_SET(it->flags, EcsIterIsInstanced); - - int32_t count = 0; - while (ecs_iter_next(it)) { - count += it->count; + (void)rule; + ecs_entity_t e = regs[r].entity; + if (!e) { + return EcsWildcard; } - return count; + + ecs_check(ecs_is_valid(rule->world, e), ECS_INVALID_PARAMETER, NULL); + return e; error: return 0; } -bool ecs_iter_is_true( - ecs_iter_t *it) +static +void table_reg_set( + const ecs_rule_t *rule, + ecs_var_t *regs, + int32_t r, + ecs_table_t *table) { - ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); + (void)rule; + ecs_assert(rule->vars[r].kind == EcsRuleVarKindTable, + ECS_INTERNAL_ERROR, NULL); - ECS_BIT_SET(it->flags, EcsIterIsFilter); - ECS_BIT_SET(it->flags, EcsIterIsInstanced); + regs[r].range.table = table; + regs[r].range.offset = 0; + regs[r].range.count = 0; + regs[r].entity = 0; +} - bool result = ecs_iter_next(it); - if (result) { - ecs_iter_fini(it); - } - return result; -error: - return false; +static +ecs_table_range_t table_reg_get( + const ecs_rule_t *rule, + ecs_var_t *regs, + int32_t r) +{ + (void)rule; + ecs_assert(rule->vars[r].kind == EcsRuleVarKindTable, + ECS_INTERNAL_ERROR, NULL); + + return regs[r].range; } -ecs_entity_t ecs_iter_get_var( - ecs_iter_t *it, - int32_t var_id) +static +ecs_entity_t reg_get_entity( + const ecs_rule_t *rule, + ecs_rule_op_t *op, + ecs_var_t *regs, + int32_t r) { - ecs_check(var_id >= 0, ECS_INVALID_PARAMETER, NULL); - ecs_check(var_id < it->variable_count, ECS_INVALID_PARAMETER, NULL); - ecs_check(it->variables != NULL, ECS_INVALID_PARAMETER, NULL); + if (r == UINT8_MAX) { + ecs_assert(op->subject != 0, ECS_INTERNAL_ERROR, NULL); - ecs_var_t *var = &it->variables[var_id]; - ecs_entity_t e = var->entity; - if (!e) { - ecs_table_t *table = var->range.table; - if (table) { - if ((var->range.count == 1) || (ecs_table_count(table) == 1)) { - ecs_assert(ecs_table_count(table) > var->range.offset, - ECS_INTERNAL_ERROR, NULL); - e = ecs_storage_get_t(&table->data.entities, ecs_entity_t, - var->range.offset)[0]; - } - } - } else { - ecs_assert(ecs_is_valid(it->real_world, e), 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); + + return op->subject; } + if (rule->vars[r].kind == EcsRuleVarKindTable) { + int32_t offset = regs[r].range.offset; + + ecs_assert(regs[r].range.count == 1, ECS_INTERNAL_ERROR, NULL); + ecs_data_t *data = &table_reg_get(rule, regs, r).table->data; + ecs_assert(data != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_entity_t *entities = ecs_storage_first(&data->entities); + ecs_assert(entities != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(offset < ecs_storage_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->vars[r].kind == EcsRuleVarKindEntity) { + return entity_reg_get(rule, regs, r); + } + + /* Must return an entity */ + ecs_assert(false, ECS_INTERNAL_ERROR, NULL); - return e; error: return 0; } -ecs_table_t* ecs_iter_get_var_as_table( - ecs_iter_t *it, - int32_t var_id) +static +ecs_table_range_t table_from_entity( + const ecs_world_t *world, + ecs_entity_t entity) { - ecs_check(var_id >= 0, ECS_INVALID_PARAMETER, NULL); - ecs_check(var_id < it->variable_count, ECS_INVALID_PARAMETER, NULL); - ecs_check(it->variables != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(entity != 0, ECS_INTERNAL_ERROR, NULL); - ecs_var_t *var = &it->variables[var_id]; - ecs_table_t *table = var->range.table; - if (!table) { - /* If table is not set, try to get table from entity */ - ecs_entity_t e = var->entity; - if (e) { - ecs_record_t *r = flecs_entities_get(it->real_world, e); - if (r) { - table = r->table; - if (ecs_table_count(table) != 1) { - /* If table contains more than the entity, make sure not to - * return a partial table. */ - return NULL; - } - } - } - } - - if (table) { - if (var->range.offset) { - /* Don't return whole table if only partial table is matched */ - return NULL; - } - - if (!var->range.count || ecs_table_count(table) == var->range.count) { - /* Return table if count matches */ - return table; - } + entity = ecs_get_alive(world, entity); + + ecs_table_range_t slice = {0}; + ecs_record_t *record = flecs_entities_get(world, entity); + if (record) { + slice.table = record->table; + slice.offset = ECS_RECORD_TO_ROW(record->row); + slice.count = 1; } -error: - return NULL; + return slice; } -ecs_table_range_t ecs_iter_get_var_as_range( - ecs_iter_t *it, - int32_t var_id) +static +ecs_table_range_t reg_get_range( + const ecs_rule_t *rule, + ecs_rule_op_t *op, + ecs_var_t *regs, + int32_t r) { - ecs_check(var_id >= 0, ECS_INVALID_PARAMETER, NULL); - ecs_check(var_id < it->variable_count, ECS_INVALID_PARAMETER, NULL); - ecs_check(it->variables != NULL, ECS_INVALID_PARAMETER, NULL); - - ecs_table_range_t result = { 0 }; - - ecs_var_t *var = &it->variables[var_id]; - ecs_table_t *table = var->range.table; - if (!table) { - ecs_entity_t e = var->entity; - if (e) { - ecs_record_t *r = flecs_entities_get(it->real_world, e); - if (r) { - result.table = r->table; - result.offset = ECS_RECORD_TO_ROW(r->row); - result.count = 1; - } - } - } else { - result.table = table; - result.offset = var->range.offset; - result.count = var->range.count; - if (!result.count) { - result.count = ecs_table_count(table); - } + if (r == UINT8_MAX) { + ecs_check(ecs_is_valid(rule->world, op->subject), + ECS_INVALID_PARAMETER, NULL); + return table_from_entity(rule->world, op->subject); } - - return result; + if (rule->vars[r].kind == EcsRuleVarKindTable) { + return table_reg_get(rule, regs, r); + } + if (rule->vars[r].kind == EcsRuleVarKindEntity) { + return table_from_entity(rule->world, entity_reg_get(rule, regs, r)); + } error: return (ecs_table_range_t){0}; } -void ecs_iter_set_var( - ecs_iter_t *it, - int32_t var_id, +static +void reg_set_entity( + const ecs_rule_t *rule, + ecs_var_t *regs, + int32_t r, ecs_entity_t entity) { - ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(var_id >= 0, ECS_INVALID_PARAMETER, NULL); - ecs_check(var_id < ECS_VARIABLE_COUNT_MAX, ECS_INVALID_PARAMETER, NULL); - ecs_check(var_id < it->variable_count, ECS_INVALID_PARAMETER, NULL); - ecs_check(entity != 0, ECS_INVALID_PARAMETER, NULL); - /* Can't set variable while iterating */ - ecs_check(!(it->flags & EcsIterIsValid), ECS_INVALID_PARAMETER, NULL); - ecs_check(it->variables != NULL, ECS_INTERNAL_ERROR, NULL); - - iter_validate_cache(it); - - ecs_var_t *var = &it->variables[var_id]; - var->entity = entity; - - ecs_record_t *r = flecs_entities_get(it->real_world, entity); - if (r) { - var->range.table = r->table; - var->range.offset = ECS_RECORD_TO_ROW(r->row); - var->range.count = 1; + if (rule->vars[r].kind == EcsRuleVarKindTable) { + ecs_world_t *world = rule->world; + ecs_check(ecs_is_valid(world, entity), ECS_INVALID_PARAMETER, NULL); + regs[r].range = table_from_entity(world, entity); + regs[r].entity = entity; } else { - var->range.table = NULL; - var->range.offset = 0; - var->range.count = 0; + entity_reg_set(rule, regs, r, entity); } - - it->constrained_vars |= flecs_ito(uint64_t, 1 << var_id); - error: return; } -void ecs_iter_set_var_as_table( - ecs_iter_t *it, - int32_t var_id, - const ecs_table_t *table) -{ - ecs_table_range_t range = { .table = (ecs_table_t*)table }; - ecs_iter_set_var_as_range(it, var_id, &range); -} - -void ecs_iter_set_var_as_range( - ecs_iter_t *it, - int32_t var_id, +static +void reg_set_range( + const ecs_rule_t *rule, + ecs_var_t *regs, + int32_t r, const ecs_table_range_t *range) { - ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(var_id >= 0, ECS_INVALID_PARAMETER, NULL); - ecs_check(var_id < ECS_VARIABLE_COUNT_MAX, ECS_INVALID_PARAMETER, NULL); - ecs_check(var_id < it->variable_count, ECS_INVALID_PARAMETER, NULL); - ecs_check(range != 0, ECS_INVALID_PARAMETER, NULL); - ecs_check(range->table != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(!range->offset || range->offset < ecs_table_count(range->table), - ECS_INVALID_PARAMETER, NULL); - ecs_check((range->offset + range->count) <= ecs_table_count(range->table), - ECS_INVALID_PARAMETER, NULL); - - /* Can't set variable while iterating */ - ecs_check(!(it->flags & EcsIterIsValid), ECS_INVALID_OPERATION, NULL); - - iter_validate_cache(it); - - ecs_var_t *var = &it->variables[var_id]; - var->range = *range; - - if (range->count == 1) { - ecs_table_t *table = range->table; - var->entity = ecs_storage_get_t( - &table->data.entities, ecs_entity_t, range->offset)[0]; + if (rule->vars[r].kind == EcsRuleVarKindEntity) { + ecs_check(range->count == 1, ECS_INTERNAL_ERROR, NULL); + regs[r].range = *range; + regs[r].entity = ecs_storage_get_t(&range->table->data.entities, + ecs_entity_t, range->offset)[0]; } else { - var->entity = 0; + regs[r].range = *range; + regs[r].entity = 0; } - - it->constrained_vars |= flecs_uto(uint64_t, 1 << var_id); - error: return; } -bool ecs_iter_var_is_constrained( - ecs_iter_t *it, - int32_t var_id) +/* 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) { - return (it->constrained_vars & (flecs_uto(uint64_t, 1 << var_id))) != 0; -} + ecs_rule_pair_t result = {0}; -ecs_iter_t ecs_page_iter( - const ecs_iter_t *it, - int32_t offset, - int32_t limit) -{ - ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(it->next != NULL, ECS_INVALID_PARAMETER, NULL); + /* Terms must always have at least one argument (the subject) */ + ecs_assert(subj_is_set(term), ECS_INTERNAL_ERROR, NULL); - ecs_iter_t result = *it; - result.priv.iter.page = (ecs_page_iter_t){ - .offset = offset, - .limit = limit, - .remaining = limit - }; - result.next = ecs_page_next; - result.chain_it = (ecs_iter_t*)it; + /* 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->first.flags & EcsIsVariable) { + if (term->first.id != EcsAny) { + /* Always lookup var as an entity, as pairs never refer to tables */ + const ecs_rule_var_t *var = find_variable( + rule, EcsRuleVarKindEntity, term_id_var_name(&term->first)); - return result; -error: - return (ecs_iter_t){ 0 }; -} + /* Variables should have been declared */ + ecs_assert(var != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(var->kind == EcsRuleVarKindEntity, + ECS_INTERNAL_ERROR, NULL); + result.first.reg = var->id; -static -void offset_iter( - ecs_iter_t *it, - int32_t offset) -{ - it->entities = &it->entities[offset]; + /* Set flag so the operation can see the predicate is a variable */ + result.reg_mask |= RULE_PAIR_PREDICATE; + result.final = true; + } else { + result.first.id = EcsWildcard; + result.final = true; + } + } else { + /* If the predicate is not a variable, simply store its id. */ + ecs_entity_t pred_id = term->first.id; + result.first.id = pred_id; - int32_t t, field_count = it->field_count; - for (t = 0; t < field_count; t ++) { - void *ptrs = it->ptrs[t]; - if (!ptrs) { - continue; + /* 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 (it->sources[t]) { - continue; + if (ecs_has_id(rule->world, pred_id, EcsFinal)) { + result.final = true; } - it->ptrs[t] = ECS_OFFSET(ptrs, offset * it->sizes[t]); + if (ecs_has_id(rule->world, pred_id, EcsReflexive)) { + result.reflexive = true; + } + + if (ecs_has_id(rule->world, pred_id, EcsAcyclic)) { + result.acyclic = true; + } } -} -static -bool ecs_page_next_instanced( - ecs_iter_t *it) -{ - ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(it->chain_it != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(it->next == ecs_page_next, ECS_INVALID_PARAMETER, NULL); + /* The pair doesn't do anything with the subject (sources 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_iter_t *chain_it = it->chain_it; - bool instanced = ECS_BIT_IS_SET(it->flags, EcsIterIsInstanced); + /* 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); - do { - if (!ecs_iter_next(chain_it)) { - goto depleted; + /* Same as above, if the object is a variable, store it and flag it */ + if (term->second.flags & EcsIsVariable) { + if (term->second.id != EcsAny) { + const ecs_rule_var_t *var = find_variable( + rule, EcsRuleVarKindEntity, term_id_var_name(&term->second)); + + /* Variables should have been declared */ + ecs_assert(var != NULL, ECS_INTERNAL_ERROR, + term_id_var_name(&term->second)); + ecs_assert(var->kind == EcsRuleVarKindEntity, ECS_INTERNAL_ERROR, + term_id_var_name(&term->second)); + + result.second.reg = var->id; + result.reg_mask |= RULE_PAIR_OBJECT; + } else { + result.second.id = EcsWildcard; } + } else { + /* If the object is not a variable, simply store its id */ + result.second.id = term->second.id; + if (!result.second.id) { + result.second_0 = true; + } + } - ecs_page_iter_t *iter = &it->priv.iter.page; - - /* Copy everything up to the private iterator data */ - ecs_os_memcpy(it, chain_it, offsetof(ecs_iter_t, priv)); + return result; +} - /* Keep instancing setting from original iterator */ - ECS_BIT_COND(it->flags, EcsIterIsInstanced, instanced); +/* 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) +{ + ecs_entity_t first = pair.first.id; + ecs_entity_t second = pair.second.id; + ecs_rule_filter_t result = { + .lo_var = -1, + .hi_var = -1 + }; - if (!chain_it->table) { - goto yield; /* Task query */ - } + /* 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_var_t *regs = get_register_frame(it, op->frame - 1); - int32_t offset = iter->offset; - int32_t limit = iter->limit; - if (!(offset || limit)) { - if (it->count) { - goto yield; - } else { - goto depleted; - } + if (pair.reg_mask & RULE_PAIR_OBJECT) { + second = entity_reg_get(it->rule, regs, pair.second.reg); + second = ecs_entity_t_lo(second); /* Filters don't have generations */ + + if (second == EcsWildcard) { + result.wildcard = true; + result.second_wildcard = true; + result.lo_var = pair.second.reg; } + } - int32_t count = it->count; - int32_t remaining = iter->remaining; + if (pair.reg_mask & RULE_PAIR_PREDICATE) { + first = entity_reg_get(it->rule, regs, pair.first.reg); + first = ecs_entity_t_lo(first); /* Filters don't have generations */ - if (offset) { - if (offset > count) { - /* No entities to iterate in current table */ - iter->offset -= count; - it->count = 0; - continue; - } else { - it->offset += offset; - count = it->count -= offset; - iter->offset = 0; - offset_iter(it, offset); + if (first == EcsWildcard) { + if (result.wildcard) { + result.same_var = pair.first.reg == pair.second.reg; } - } - if (remaining) { - if (remaining > count) { - iter->remaining -= count; + result.wildcard = true; + result.first_wildcard = true; + + if (second) { + result.hi_var = pair.first.reg; } else { - it->count = remaining; - iter->remaining = 0; + result.lo_var = pair.first.reg; } - } else if (limit) { - /* Limit hit: no more entities left to iterate */ - goto done; } - } while (it->count == 0); + } -yield: - if (!ECS_BIT_IS_SET(it->flags, EcsIterIsInstanced)) { - it->offset = 0; + if (!second && !pair.second_0) { + result.mask = first; + } else { + result.mask = ecs_pair(first, second); } - return true; -done: - /* Cleanup iterator resources if it wasn't yet depleted */ - ecs_iter_fini(chain_it); -depleted: -error: - return false; + return result; } -bool ecs_page_next( - ecs_iter_t *it) +/* 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) { - ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(it->next == ecs_page_next, ECS_INVALID_PARAMETER, NULL); - ecs_check(it->chain_it != NULL, ECS_INVALID_PARAMETER, NULL); + const ecs_rule_t *rule = it->rule; + const ecs_rule_var_t *vars = rule->vars; + (void)vars; - ECS_BIT_SET(it->chain_it->flags, EcsIterIsInstanced); + ecs_var_t *regs = get_registers(it, op); + ecs_assert(column < type.count, ECS_INTERNAL_ERROR, NULL); + ecs_entity_t *elem = &type.array[column]; - if (flecs_iter_next_row(it)) { - return true; - } + int32_t obj_var = filter->lo_var; + int32_t pred_var = filter->hi_var; - return flecs_iter_next_instanced(it, ecs_page_next_instanced(it)); -error: - return false; -} + if (obj_var != -1) { + ecs_assert(vars[obj_var].kind == EcsRuleVarKindEntity, + ECS_INTERNAL_ERROR, NULL); -ecs_iter_t ecs_worker_iter( - const ecs_iter_t *it, - int32_t index, - int32_t count) -{ - ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(it->next != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(count > 0, ECS_INVALID_PARAMETER, NULL); - ecs_check(index >= 0, ECS_INVALID_PARAMETER, NULL); - ecs_check(index < count, ECS_INVALID_PARAMETER, NULL); + entity_reg_set(rule, regs, obj_var, + ecs_get_alive(rule->world, ECS_PAIR_SECOND(*elem))); + } - return (ecs_iter_t){ - .real_world = it->real_world, - .world = it->world, - .priv.iter.worker = { - .index = index, - .count = count - }, - .next = ecs_worker_next, - .chain_it = (ecs_iter_t*)it, - .flags = it->flags & EcsIterIsInstanced - }; + if (pred_var != -1) { + ecs_assert(vars[pred_var].kind == EcsRuleVarKindEntity, + ECS_INTERNAL_ERROR, NULL); -error: - return (ecs_iter_t){ 0 }; + entity_reg_set(rule, regs, pred_var, + ecs_get_alive(rule->world, + ECS_PAIR_FIRST(*elem))); + } } +/* Returns whether variable is a subject */ static -bool ecs_worker_next_instanced( - ecs_iter_t *it) +bool is_subject( + ecs_rule_t *rule, + ecs_rule_var_t *var) { - ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(it->chain_it != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(it->next == ecs_worker_next, ECS_INVALID_PARAMETER, NULL); - - bool instanced = ECS_BIT_IS_SET(it->flags, EcsIterIsInstanced); + ecs_assert(rule != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_iter_t *chain_it = it->chain_it; - ecs_worker_iter_t *iter = &it->priv.iter.worker; - int32_t res_count = iter->count, res_index = iter->index; - int32_t per_worker, instances_per_worker, first; + if (!var) { + return false; + } - do { - if (!ecs_iter_next(chain_it)) { - return false; - } + if (var->id < rule->subj_var_count) { + return true; + } - /* Copy everything up to the private iterator data */ - ecs_os_memcpy(it, chain_it, offsetof(ecs_iter_t, priv)); + return false; +} - /* Keep instancing setting from original iterator */ - ECS_BIT_COND(it->flags, EcsIterIsInstanced, instanced); +static +bool skip_term(ecs_term_t *term) { + if (ecs_term_match_0(term)) { + return true; + } + return false; +} - int32_t count = it->count; - int32_t instance_count = it->instance_count; - per_worker = count / res_count; - instances_per_worker = instance_count / res_count; - first = per_worker * res_index; - count -= per_worker * res_count; +static +int32_t get_variable_depth( + ecs_rule_t *rule, + ecs_rule_var_t *var, + ecs_rule_var_t *root, + int recur); - if (count) { - if (res_index < count) { - per_worker ++; - first += res_index; - } else { - first += count; - } +static +int32_t crawl_variable( + ecs_rule_t *rule, + ecs_rule_var_t *var, + ecs_rule_var_t *root, + int recur) +{ + ecs_term_t *terms = rule->filter.terms; + int32_t i, count = rule->filter.term_count; + + for (i = 0; i < count; i ++) { + ecs_term_t *term = &terms[i]; + if (skip_term(term)) { + continue; } - if (!per_worker && it->table == NULL) { - if (res_index == 0) { - return true; - } else { - return false; - } + ecs_rule_var_t + *first = term_pred(rule, term), + *src = term_subj(rule, term), + *second = term_obj(rule, term); + + /* Variable must at least appear once in term */ + if (var != first && var != src && var != second) { + continue; } - } while (!per_worker); - it->instance_count = instances_per_worker; - it->frame_offset += first; + if (first && first != var && !first->marked) { + get_variable_depth(rule, first, root, recur + 1); + } - offset_iter(it, it->offset + first); - it->count = per_worker; + if (src && src != var && !src->marked) { + get_variable_depth(rule, src, root, recur + 1); + } - if (ECS_BIT_IS_SET(it->flags, EcsIterIsInstanced)) { - it->offset += first; - } else { - it->offset = 0; + if (second && second != var && !second->marked) { + get_variable_depth(rule, second, root, recur + 1); + } } - return true; -error: - return false; + return 0; } -bool ecs_worker_next( - ecs_iter_t *it) +static +int32_t get_depth_from_var( + ecs_rule_t *rule, + ecs_rule_var_t *var, + ecs_rule_var_t *root, + int recur) { - ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(it->next == ecs_worker_next, ECS_INVALID_PARAMETER, NULL); - ecs_check(it->chain_it != NULL, ECS_INVALID_PARAMETER, NULL); - - ECS_BIT_SET(it->chain_it->flags, EcsIterIsInstanced); - - if (flecs_iter_next_row(it)) { - return true; + /* 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; } - return flecs_iter_next_instanced(it, ecs_worker_next_instanced(it)); -error: - return false; + /* 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; + } } +static +int32_t get_depth_from_term( + ecs_rule_t *rule, + ecs_rule_var_t *cur, + ecs_rule_var_t *first, + ecs_rule_var_t *second, + ecs_rule_var_t *root, + int recur) +{ + int32_t result = UINT8_MAX; -/* -- Identifier Component -- */ -static ECS_DTOR(EcsIdentifier, ptr, { - ecs_os_strset(&ptr->value, NULL); -}) + /* If neither of the other parts of the terms are variables, this + * variable is guaranteed to have no dependencies. */ + if (!first && !second) { + 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 (first && cur != first) { + int32_t depth = get_depth_from_var(rule, first, root, recur); + if (depth == UINT8_MAX) { + return UINT8_MAX; + } -static ECS_COPY(EcsIdentifier, dst, src, { - ecs_os_strset(&dst->value, src->value); - dst->hash = src->hash; - dst->length = src->length; - dst->index_hash = src->index_hash; - dst->index = src->index; -}) + /* If the found depth is lower than the depth found, overwrite it */ + if (depth < result) { + result = depth; + } + } -static ECS_MOVE(EcsIdentifier, dst, src, { - ecs_os_strset(&dst->value, NULL); - dst->value = src->value; - dst->hash = src->hash; - dst->length = src->length; - dst->index_hash = src->index_hash; - dst->index = src->index; + /* Same for second */ + if (second && cur != second) { + int32_t depth = get_depth_from_var(rule, second, root, recur); + if (depth == UINT8_MAX) { + return UINT8_MAX; + } - src->value = NULL; - src->hash = 0; - src->index_hash = 0; - src->index = 0; - src->length = 0; -}) + if (depth < result) { + result = depth; + } + } + } -static -void ecs_on_set(EcsIdentifier)(ecs_iter_t *it) { - EcsIdentifier *ptr = ecs_field(it, EcsIdentifier, 1); - - ecs_world_t *world = it->real_world; - ecs_entity_t evt = it->event; - ecs_id_t evt_id = it->event_id; - ecs_entity_t kind = ECS_PAIR_SECOND(evt_id); /* Name, Symbol, Alias */ - ecs_id_t pair = ecs_childof(0); - ecs_hashmap_t *index = NULL; + return result; +} - if (kind == EcsSymbol) { - index = &world->symbols; - } else if (kind == EcsAlias) { - index = &world->aliases; - } else if (kind == EcsName) { - ecs_assert(it->table != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_search(world, it->table, ecs_childof(EcsWildcard), &pair); - ecs_assert(pair != 0, ECS_INTERNAL_ERROR, NULL); +/* Find the depth of the dependency tree from the variable to the root */ +static +int32_t get_variable_depth( + ecs_rule_t *rule, + ecs_rule_var_t *var, + ecs_rule_var_t *root, + int recur) +{ + var->marked = true; - if (evt == EcsOnSet) { - index = flecs_id_name_index_ensure(world, pair); - } else { - index = flecs_id_name_index_get(world, pair); - } - } + /* 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; - int i, count = it->count; + int32_t i, count = rule->filter.term_count; + int32_t result = UINT8_MAX; for (i = 0; i < count; i ++) { - EcsIdentifier *cur = &ptr[i]; - uint64_t hash; - ecs_size_t len; - const char *name = cur->value; + ecs_term_t *term = &terms[i]; + if (skip_term(term)) { + continue; + } - if (cur->index && cur->index != index) { - /* If index doesn't match up, the value must have been copied from - * another entity, so reset index & cached index hash */ - cur->index = NULL; - cur->index_hash = 0; + ecs_rule_var_t + *first = term_pred(rule, term), + *src = term_subj(rule, term), + *second = term_obj(rule, term); + + if (src != var) { + continue; } - if (cur->value && (evt == EcsOnSet)) { - len = cur->length = ecs_os_strlen(name); - hash = cur->hash = flecs_hash(name, len); - } else { - len = cur->length = 0; - hash = cur->hash = 0; - cur->index = NULL; + if (!is_subject(rule, first)) { + first = NULL; } - if (index) { - uint64_t index_hash = cur->index_hash; - ecs_entity_t e = it->entities[i]; + if (!is_subject(rule, second)) { + second = NULL; + } - if (hash != index_hash) { - if (index_hash) { - flecs_name_index_remove(index, e, index_hash); - } - if (hash) { - flecs_name_index_ensure(index, e, name, len, hash); - cur->index_hash = hash; - cur->index = index; - } - } else { - /* Name didn't change, but the string could have been - * reallocated. Make sure name index points to correct string */ - flecs_name_index_update_name(index, e, hash, name); - } + int32_t depth = get_depth_from_term(rule, var, first, second, root, recur); + if (depth < result) { + result = depth; } } -} - - -/* -- Poly component -- */ -static ECS_COPY(EcsPoly, dst, src, { - (void)dst; - (void)src; - ecs_abort(ECS_INVALID_OPERATION, "poly component cannot be copied"); -}) - -static ECS_MOVE(EcsPoly, dst, src, { - if (dst->poly && (dst->poly != src->poly)) { - ecs_poly_dtor_t *dtor = ecs_get_dtor(dst->poly); - ecs_assert(dtor != NULL, ECS_INTERNAL_ERROR, NULL); - dtor[0](dst->poly); + if (result == UINT8_MAX) { + result = 0; } - dst->poly = src->poly; - src->poly = NULL; -}) + var->depth = result; -static ECS_DTOR(EcsPoly, ptr, { - if (ptr->poly) { - ecs_poly_dtor_t *dtor = ecs_get_dtor(ptr->poly); - ecs_assert(dtor != NULL, ECS_INTERNAL_ERROR, NULL); - dtor[0](ptr->poly); - } -}) + /* Dependencies are calculated from subject to (first, second). If there were + * sources 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_rule_var_t + *src = term_subj(rule, term), + *first = term_pred(rule, term), + *second = term_obj(rule, term); -/* -- Builtin triggers -- */ + /* Only evaluate first & second 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 (src != var) { + continue; + } -static -void assert_relation_unused( - ecs_world_t *world, - ecs_entity_t rel, - ecs_entity_t property) -{ - if (world->flags & EcsWorldFini) { - return; - } + crawl_variable(rule, src, root, recur); - ecs_vector_t *marked_ids = world->store.marked_ids; - int32_t i, count = ecs_vector_count(marked_ids); - for (i = 0; i < count; i ++) { - ecs_marked_id_t *mid = ecs_vector_get(marked_ids, ecs_marked_id_t, i); - if (mid->id == ecs_pair(rel, EcsWildcard)) { - /* If id is being cleaned up, no need to throw error as tables will - * be cleaned up */ - return; + if (first && first != var) { + crawl_variable(rule, first, root, recur); } - } - - if (ecs_id_in_use(world, ecs_pair(rel, EcsWildcard))) { - char *r_str = ecs_get_fullpath(world, rel); - char *p_str = ecs_get_fullpath(world, property); - ecs_throw(ECS_ID_IN_USE, - "cannot change property '%s' for relationship '%s': already in use", - p_str, r_str); - - ecs_os_free(r_str); - ecs_os_free(p_str); + if (second && second != var) { + crawl_variable(rule, second, root, recur); + } } -error: - return; + return var->depth; } +/* Compare function used for qsort. It ensures that variables are first ordered + * by depth, followed by how often they occur. */ static -bool set_id_flag( - ecs_id_record_t *idr, - ecs_flags32_t flag) +int compare_variable( + const void* ptr1, + const void *ptr2) { - if (!(idr->flags & flag)) { - idr->flags |= flag; - return true; + const ecs_rule_var_t *v1 = ptr1; + const ecs_rule_var_t *v2 = ptr2; + + if (v1->kind < v2->kind) { + return -1; + } else if (v1->kind > v2->kind) { + return 1; } - return false; -} -static -bool unset_id_flag( - ecs_id_record_t *idr, - ecs_flags32_t flag) -{ - if ((idr->flags & flag)) { - idr->flags &= ~flag; - return true; + if (v1->depth < v2->depth) { + return -1; + } else if (v1->depth > v2->depth) { + return 1; } - return false; + + if (v1->occurs < v2->occurs) { + return 1; + } else { + return -1; + } + + return (v1->id < v2->id) - (v1->id > v2->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 register_id_flag_for_relation( - ecs_iter_t *it, - ecs_entity_t prop, - ecs_flags32_t flag, - ecs_flags32_t not_flag, - ecs_flags32_t entity_flag) +int ensure_all_variables( + ecs_rule_t *rule) { - ecs_world_t *world = it->world; - ecs_entity_t event = it->event; + ecs_term_t *terms = rule->filter.terms; + int32_t i, count = rule->filter.term_count; - int i, count = it->count; for (i = 0; i < count; i ++) { - ecs_entity_t e = it->entities[i]; - bool changed = false; + ecs_term_t *term = &terms[i]; + if (skip_term(term)) { + continue; + } - if (event == EcsOnAdd) { - ecs_id_record_t *idr = flecs_id_record_ensure(world, e); - changed |= set_id_flag(idr, flag); - idr = flecs_id_record_ensure(world, ecs_pair(e, EcsWildcard)); - do { - changed |= set_id_flag(idr, flag); - } while ((idr = idr->first.next)); - if (entity_flag) flecs_add_flag(world, e, entity_flag); - } else if (event == EcsOnRemove) { - ecs_id_record_t *idr = flecs_id_record_get(world, e); - if (idr) changed |= unset_id_flag(idr, not_flag); - idr = flecs_id_record_get(world, ecs_pair(e, EcsWildcard)); - if (idr) { - do { - changed |= unset_id_flag(idr, not_flag); - } while ((idr = idr->first.next)); + /* If predicate is a variable, make sure it has been registered */ + if (term->first.flags & EcsIsVariable) { + if (ensure_term_id_variable(rule, term, &term->first) != 0) { + return -1; } } - if (changed) { - assert_relation_unused(world, e, prop); + /* 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 (!ecs_term_match_this(term)) { + if (ensure_term_id_variable(rule, term, &term->src) != 0) { + return -1; + } } - } -} -static -void register_final(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 (flecs_id_record_get(world, ecs_pair(EcsIsA, e)) != NULL) { - char *e_str = ecs_get_fullpath(world, e); - ecs_throw(ECS_ID_IN_USE, - "cannot change property 'Final' for '%s': already inherited from", - e_str); - ecs_os_free(e_str); - error: - continue; + /* If object is a variable, make sure it has been registered */ + if (obj_is_set(term) && (term->second.flags & EcsIsVariable)) { + if (ensure_term_id_variable(rule, term, &term->second) != 0) { + return -1; + } } } -} -static -void register_on_delete(ecs_iter_t *it) { - ecs_id_t id = ecs_field_id(it, 1); - register_id_flag_for_relation(it, EcsOnDelete, - ECS_ID_ON_DELETE_FLAG(ECS_PAIR_SECOND(id)), - EcsIdOnDeleteMask, - EcsEntityObservedId); + return 0; } +/* Scan for variables, put them in optimal dependency order. */ static -void register_on_delete_object(ecs_iter_t *it) { - ecs_id_t id = ecs_field_id(it, 1); - register_id_flag_for_relation(it, EcsOnDeleteTarget, - ECS_ID_ON_DELETE_OBJECT_FLAG(ECS_PAIR_SECOND(id)), - EcsIdOnDeleteObjectMask, - EcsEntityObservedId); -} +int scan_variables( + ecs_rule_t *rule) +{ + /* Objects found in rule. One will be elected root */ + int32_t subject_count = 0; -static -void register_acyclic(ecs_iter_t *it) { - register_id_flag_for_relation(it, EcsAcyclic, EcsIdAcyclic, - EcsIdAcyclic, 0); -} + /* If this (.) is found, it always takes precedence in root election */ + int32_t this_var = UINT8_MAX; -static -void register_tag(ecs_iter_t *it) { - register_id_flag_for_relation(it, EcsTag, EcsIdTag, ~EcsIdTag, 0); + /* 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; - /* Ensure that all id records for tag have type info set to NULL */ - ecs_world_t *world = it->real_world; - int i, count = it->count; - for (i = 0; i < count; i ++) { - ecs_entity_t e = it->entities[i]; + /* Step 1: find all possible roots */ + ecs_term_t *terms = rule->filter.terms; + int32_t i, term_count = rule->filter.term_count; - if (it->event == EcsOnAdd) { - ecs_id_record_t *idr = flecs_id_record_get(world, - ecs_pair(e, EcsWildcard)); - ecs_assert(idr != NULL, ECS_INTERNAL_ERROR, NULL); - do { - if (idr->type_info != NULL) { - assert_relation_unused(world, e, EcsTag); - } - idr->type_info = NULL; - } while ((idr = idr->first.next)); - } - } -} + for (i = 0; i < term_count; i ++) { + ecs_term_t *term = &terms[i]; -static -void register_exclusive(ecs_iter_t *it) { - register_id_flag_for_relation(it, EcsExclusive, EcsIdExclusive, - EcsIdExclusive, 0); -} + /* Evaluate the subject. The predicate and object are not evaluated, + * since they never can be elected as root. */ + if (term_id_is_variable(&term->src)) { + const char *subj_name = term_id_var_name(&term->src); + + ecs_rule_var_t *src = find_variable( + rule, EcsRuleVarKindTable, subj_name); + if (!src) { + src = create_variable(rule, EcsRuleVarKindTable, subj_name); + if (subject_count >= ECS_RULE_MAX_VAR_COUNT) { + rule_error(rule, "too many variables in rule"); + goto error; + } -static -void register_dont_inherit(ecs_iter_t *it) { - register_id_flag_for_relation(it, EcsDontInherit, - EcsIdDontInherit, EcsIdDontInherit, 0); -} + /* Make sure that variable name in term array matches with the + * rule name. */ + if (term->src.id != EcsThis && term->src.id != EcsAny) { + ecs_os_strset(&term->src.name, src->name); + term->src.id = 0; + } + } -static -void register_with(ecs_iter_t *it) { - register_id_flag_for_relation(it, EcsWith, EcsIdWith, 0, 0); -} + if (++ src->occurs > max_occur) { + max_occur = src->occurs; + max_occur_var = src->id; + } + } + } -static -void register_union(ecs_iter_t *it) { - register_id_flag_for_relation(it, EcsUnion, EcsIdUnion, 0, 0); -} + rule->subj_var_count = rule->var_count; -static -void register_slot_of(ecs_iter_t *it) { - int i, count = it->count; - for (i = 0; i < count; i ++) { - ecs_add_id(it->world, it->entities[i], EcsUnion); + if (ensure_all_variables(rule) != 0) { + goto error; } -} -static -void on_symmetric_add_remove(ecs_iter_t *it) { - ecs_entity_t pair = ecs_field_id(it, 1); + /* Variables in a term with a literal subject have depth 0 */ + for (i = 0; i < term_count; i ++) { + ecs_term_t *term = &terms[i]; - if (!ECS_HAS_ID_FLAG(pair, PAIR)) { - /* If relationship was not added as a pair, there's nothing to do */ - return; - } + if (!(term->src.flags & EcsIsVariable)) { + ecs_rule_var_t + *first = term_pred(rule, term), + *second = term_obj(rule, term); - ecs_entity_t rel = ECS_PAIR_FIRST(pair); - ecs_entity_t obj = ECS_PAIR_SECOND(pair); - ecs_entity_t event = it->event; - - int i, count = it->count; - for (i = 0; i < count; i ++) { - ecs_entity_t subj = it->entities[i]; - if (event == EcsOnAdd) { - if (!ecs_has_id(it->real_world, obj, ecs_pair(rel, subj))) { - ecs_add_pair(it->world, obj, rel, subj); + if (first) { + first->depth = 0; } - } else { - if (ecs_has_id(it->real_world, obj, ecs_pair(rel, subj))) { - ecs_remove_pair(it->world, obj, rel, subj); + if (second) { + second->depth = 0; } } } -} -static -void register_symmetric(ecs_iter_t *it) { - ecs_world_t *world = it->real_world; + /* 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; + } + } - int i, count = it->count; - for (i = 0; i < count; i ++) { - ecs_entity_t r = it->entities[i]; - assert_relation_unused(world, r, EcsSymmetric); + ecs_rule_var_t *root = &rule->vars[root_var]; + root->depth = get_variable_depth(rule, root, root, 0); - /* Create observer that adds the reverse relationship when R(X, Y) is - * added, or remove the reverse relationship when R(X, Y) is removed. */ - ecs_observer_init(world, &(ecs_observer_desc_t){ - .entity = ecs_entity(world, {.add = {ecs_childof(EcsFlecsInternals)}}), - .filter.terms[0] = { .id = ecs_pair(r, EcsWildcard) }, - .callback = on_symmetric_add_remove, - .events = {EcsOnAdd, EcsOnRemove} - }); - } -} + /* Verify that there are no unconstrained variables. Unconstrained variables + * are variables that are unreachable from the root. */ + for (i = 0; i < rule->subj_var_count; i ++) { + if (rule->vars[i].depth == UINT8_MAX) { + rule_error(rule, "unconstrained variable '%s'", + rule->vars[i].name); + goto error; + } + } -static -void on_component(ecs_iter_t *it) { - ecs_world_t *world = it->world; - EcsComponent *c = ecs_field(it, EcsComponent, 1); + /* 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; + } - int i, count = it->count; - for (i = 0; i < count; i ++) { - ecs_entity_t e = it->entities[i]; - if (it->event == EcsOnSet) { - if (flecs_type_info_init_id( - world, e, c[i].size, c[i].alignment, NULL)) - { - assert_relation_unused(world, e, ecs_id(EcsComponent)); - } - } else if (it->event == EcsOnRemove) { - flecs_type_info_free(world, e); + ecs_rule_var_t + *first = term_pred(rule, term), + *second = term_obj(rule, term); + + if (!first && term_id_is_variable(&term->first) && + term->first.id != EcsAny) + { + rule_error(rule, "missing predicate variable '%s'", + term_id_var_name(&term->first)); + goto error; } + if (!second && term_id_is_variable(&term->second) && + term->second.id != EcsAny) + { + rule_error(rule, "missing object variable '%s'", + term_id_var_name(&term->second)); + goto error; + } + } + + /* 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. */ + int32_t var_count = rule->var_count; + ecs_rule_var_t vars[ECS_RULE_MAX_VAR_COUNT]; + ecs_os_memcpy_n(vars, rule->vars, ecs_rule_var_t, var_count); + ecs_qsort_t(&vars, var_count, ecs_rule_var_t, compare_variable); + for (i = 0; i < var_count; i ++) { + rule->var_eval_order[i] = vars[i].id; } + +done: + return 0; +error: + return -1; } +/* Get entity variable from table variable */ static -void ensure_module_tag(ecs_iter_t *it) { - ecs_world_t *world = it->world; +ecs_rule_var_t* to_entity( + ecs_rule_t *rule, + ecs_rule_var_t *var) +{ + if (!var) { + return NULL; + } - int i, count = it->count; - for (i = 0; i < count; i ++) { - ecs_entity_t e = it->entities[i]; - ecs_entity_t parent = ecs_get_target(world, e, EcsChildOf, 0); - if (parent) { - ecs_add_id(world, parent, EcsModule); - } + ecs_rule_var_t *evar = NULL; + if (var->kind == EcsRuleVarKindTable) { + evar = find_variable(rule, EcsRuleVarKindEntity, var->name); + } else { + evar = var; } -} -/* -- Triggers for keeping hashed ids in sync -- */ + 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 -void on_parent_change(ecs_iter_t *it) { - ecs_world_t *world = it->world; - ecs_table_t *other_table = it->other_table, *table = it->table; - - int32_t col = ecs_search(it->real_world, table, - ecs_pair(ecs_id(EcsIdentifier), EcsName), 0); - bool has_name = col != -1; - bool other_has_name = ecs_search(it->real_world, other_table, - ecs_pair(ecs_id(EcsIdentifier), EcsName), 0) != -1; - - if (!has_name && !other_has_name) { - /* If tables don't have names, index does not need to be updated */ - return; +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_id_t to_pair = it->event_id; - ecs_id_t from_pair = ecs_childof(0); + ecs_rule_var_t *tvar, *evar = to_entity(rule, var); + if (!evar) { + return var; + } - /* Find the other ChildOf relationship */ - ecs_search(it->real_world, other_table, - ecs_pair(EcsChildOf, EcsWildcard), &from_pair); + if (var->kind == EcsRuleVarKindTable) { + tvar = var; + } else { + tvar = find_variable(rule, EcsRuleVarKindTable, var->name); + } - bool to_has_name = has_name, from_has_name = other_has_name; - if (it->event == EcsOnRemove) { - if (from_pair != ecs_childof(0)) { - /* Because ChildOf is an exclusive relationship, events always come - * in OnAdd/OnRemove pairs (add for the new, remove for the old - * parent). We only need one of those events, so filter out the - * OnRemove events except for the case where a parent is removed and - * not replaced with another parent. */ - return; - } + /* If variable is used as predicate or object, it should have been + * registered as an entity. */ + ecs_assert(evar != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_id_t temp = from_pair; - from_pair = to_pair; - to_pair = temp; + /* 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; - to_has_name = other_has_name; - from_has_name = has_name; - } + /* Entity will either be written or has been written */ + written[evar->id] = true; - /* Get the table column with names */ - const EcsIdentifier *names = ecs_iter_column(it, EcsIdentifier, col); + push_frame(rule); - ecs_hashmap_t *from_index = 0; - if (from_has_name) { - from_index = flecs_id_name_index_get(world, from_pair); - } - ecs_hashmap_t *to_index = NULL; - if (to_has_name) { - to_index = flecs_id_name_index_ensure(world, to_pair); + return evar; + } else { + return tvar; + } + } + } else if (evar && written[evar->id]) { + return evar; } - int32_t i, count = it->count; - for (i = 0; i < count; i ++) { - ecs_entity_t e = it->entities[i]; - const EcsIdentifier *name = &names[i]; + return var; +} - uint64_t index_hash = name->index_hash; - if (from_index && index_hash) { - flecs_name_index_remove(from_index, e, index_hash); - } - const char *name_str = name->value; - if (to_index && name_str) { - ecs_assert(name->hash != 0, ECS_INTERNAL_ERROR, NULL); - flecs_name_index_ensure( - to_index, e, name_str, name->length, name->hash); - } - } +/* 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); +} -/* -- Iterable mixins -- */ +/* Ensure that an entity variable is written before using it */ 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) +ecs_rule_var_t* ensure_entity_written( + ecs_rule_t *rule, + ecs_rule_var_t *var, + bool *written) { - ecs_iter_poly(world, poly, it, filter); - it->event_id = filter->id; -} + if (!var) { + return NULL; + } -/* -- Bootstrapping -- */ + /* Ensure we're working with the most specific version of src we can get */ + ecs_rule_var_t *evar = ensure_most_specific_var(rule, var, written); -#define bootstrap_component(world, table, name)\ - _bootstrap_component(world, table, ecs_id(name), #name, sizeof(name),\ - ECS_ALIGNOF(name)) + /* 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 -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_rule_op_t* insert_operation( + ecs_rule_t *rule, + int32_t term_index, + bool *written) { - ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_rule_pair_t pair = {0}; - ecs_column_t *columns = table->data.columns; - ecs_assert(columns != NULL, 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]; - ecs_record_t *record = flecs_entities_ensure(world, entity); - record->table = table; + pair = term_to_pair(rule, term); - int32_t index = flecs_table_append(world, table, entity, record, false, false); - record->row = ECS_ROW_TO_RECORD(index, 0); + /* 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 *first = &rule->vars[pair.first.reg]; + first = get_most_specific_var(rule, first, written); + pair.first.reg = first->id; + } - EcsComponent *component = ecs_storage_first(&columns[0]); - component[index].size = size; - component[index].alignment = alignment; + if (pair.reg_mask & RULE_PAIR_OBJECT) { + ecs_rule_var_t *second = &rule->vars[pair.second.reg]; + second = get_most_specific_var(rule, second, written); + pair.second.reg = second->id; + } + } else { + /* Not all operations have a filter (like Each) */ + } - const char *name = &symbol[3]; /* Strip 'Ecs' */ - ecs_size_t symbol_length = ecs_os_strlen(symbol); - ecs_size_t name_length = symbol_length - 3; + 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; - EcsIdentifier *name_col = ecs_storage_first(&columns[1]); - name_col[index].value = ecs_os_strdup(name); - name_col[index].length = name_length; - name_col[index].hash = flecs_hash(name, name_length); - name_col[index].index_hash = 0; - name_col[index].index = NULL; + /* Store corresponding signature term so we can correlate and + * store the table columns with signature columns. */ + op->term = term_index; - EcsIdentifier *symbol_col = ecs_storage_first(&columns[2]); - symbol_col[index].value = ecs_os_strdup(symbol); - symbol_col[index].length = symbol_length; - symbol_col[index].hash = flecs_hash(symbol, symbol_length); - symbol_col[index].index_hash = 0; - symbol_col[index].index = NULL; + return op; } -/** 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 */ +/* Insert first operation, which is always Input. This creates an entry in + * the register stack for the initial state. */ static -ecs_table_t* bootstrap_component_table( - ecs_world_t *world) +void insert_input( + ecs_rule_t *rule) { - /* Before creating table, manually set flags for ChildOf/Identifier, as this - * can no longer be done after they are in use. */ - ecs_id_record_t *idr = flecs_id_record_ensure(world, EcsChildOf); - idr->flags |= EcsIdOnDeleteObjectDelete | EcsIdDontInherit | - EcsIdAcyclic | EcsIdTag; - idr = flecs_id_record_ensure(world, ecs_pair(EcsChildOf, EcsWildcard)); - idr->flags |= EcsIdOnDeleteObjectDelete | EcsIdDontInherit | - EcsIdAcyclic | EcsIdTag | EcsIdExclusive; - - idr = flecs_id_record_ensure( - world, ecs_pair(ecs_id(EcsIdentifier), EcsWildcard)); - idr->flags |= EcsIdDontInherit; - - world->idr_childof_0 = flecs_id_record_ensure(world, - ecs_pair(EcsChildOf, 0)); - - ecs_id_t ids[] = { - ecs_id(EcsComponent), - EcsFinal, - ecs_pair(ecs_id(EcsIdentifier), EcsName), - ecs_pair(ecs_id(EcsIdentifier), EcsSymbol), - ecs_pair(EcsChildOf, EcsFlecsCore), - ecs_pair(EcsOnDelete, EcsPanic) - }; - - ecs_type_t array = { - .array = ids, - .count = 6 - }; + ecs_rule_op_t *op = create_operation(rule); + op->kind = EcsRuleInput; - ecs_table_t *result = flecs_table_find_or_create(world, &array); - ecs_data_t *data = &result->data; + /* The first time Input is evaluated it goes to the next/first operation */ + op->on_pass = 1; - /* Preallocate enough memory for initial components */ - ecs_storage_init_t(&data->entities, ecs_entity_t, EcsFirstUserComponentId); - ecs_storage_init_t(&data->records, ecs_record_t, EcsFirstUserComponentId); + /* When Input is evaluated with redo = true it will return false, which will + * finish the program as op becomes -1. */ + op->on_fail = -1; - ecs_storage_init_t(&data->columns[0], EcsComponent, EcsFirstUserComponentId); - ecs_storage_init_t(&data->columns[1], EcsIdentifier, EcsFirstUserComponentId); - ecs_storage_init_t(&data->columns[2], EcsIdentifier, EcsFirstUserComponentId); - - return result; + push_frame(rule); } +/* Insert last operation, which is always Yield. When the program hits Yield, + * data is returned to the application. */ static -void bootstrap_entity( - ecs_world_t *world, - ecs_entity_t id, - const char *name, - ecs_entity_t parent) +void insert_yield( + ecs_rule_t *rule) { - char symbol[256]; - ecs_os_strcpy(symbol, "flecs.core."); - ecs_os_strcat(symbol, name); - - ecs_add_pair(world, id, EcsChildOf, parent); - ecs_set_name(world, id, name); - ecs_set_symbol(world, id, symbol); + 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(ecs_get_name(world, id) != 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, "."); + } - if (!parent || parent == EcsFlecsCore) { - ecs_assert(ecs_lookup_fullpath(world, name) == id, - ECS_INTERNAL_ERROR, NULL); + /* 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; } + + op->frame = push_frame(rule); } -void flecs_bootstrap( - ecs_world_t *world) +/* Return superset/subset including the root */ +static +void insert_reflexive_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 reflexive) { - ecs_log_push(); + ecs_assert(out != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_set_name_prefix(world, "Ecs"); + ecs_rule_var_t *first = pair_pred(rule, &pair); + ecs_rule_var_t *second = pair_obj(rule, &pair); - /* Ensure builtin ids are alive */ - ecs_ensure(world, ecs_id(EcsComponent)); - ecs_ensure(world, EcsFinal); - ecs_ensure(world, ecs_id(EcsIdentifier)); - ecs_ensure(world, EcsName); - ecs_ensure(world, EcsSymbol); - ecs_ensure(world, EcsAlias); - ecs_ensure(world, EcsChildOf); - ecs_ensure(world, EcsFlecsCore); - ecs_ensure(world, EcsOnDelete); - ecs_ensure(world, EcsPanic); - ecs_ensure(world, EcsFlag); - ecs_ensure(world, EcsWildcard); - ecs_ensure(world, EcsAny); - ecs_ensure(world, EcsTag); + 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; - /* Bootstrap builtin components */ - flecs_type_info_init(world, EcsComponent, { - .ctor = ecs_default_ctor, - .on_set = on_component, - .on_remove = on_component - }); + /* Insert 4 operations at once, so we don't have to worry about how + * the instruction array reallocs. If operation is not reflexive, we only + * need to insert the set operation. */ + if (reflexive) { + insert_operation(rule, -1, written); + insert_operation(rule, -1, written); + insert_operation(rule, -1, written); + } - flecs_type_info_init(world, EcsIdentifier, { - .ctor = ecs_default_ctor, - .dtor = ecs_dtor(EcsIdentifier), - .copy = ecs_copy(EcsIdentifier), - .move = ecs_move(EcsIdentifier), - .on_set = ecs_on_set(EcsIdentifier), - .on_remove = ecs_on_set(EcsIdentifier) - }); + ecs_rule_op_t *op = insert_operation(rule, -1, written); + ecs_rule_op_t *setjmp = &rule->operations[setjmp_lbl]; + ecs_rule_op_t *store = &rule->operations[store_lbl]; + ecs_rule_op_t *set = &rule->operations[set_lbl]; + ecs_rule_op_t *jump = op; - flecs_type_info_init(world, EcsPoly, { - .ctor = ecs_default_ctor, - .copy = ecs_copy(EcsPoly), - .move = ecs_move(EcsPoly), - .dtor = ecs_dtor(EcsPoly) - }); + if (!reflexive) { + set_lbl = setjmp_lbl; + set = op; + setjmp = NULL; + store = NULL; + jump = NULL; + next_op = set_lbl + 1; + prev_op = set_lbl - 1; + } - flecs_type_info_init(world, EcsIterable, { 0 }); + /* The SetJmp operation stores a conditional jump label that either + * points to the Store or *Set operation */ + if (reflexive) { + setjmp->kind = EcsRuleSetJmp; + setjmp->on_pass = store_lbl; + setjmp->on_fail = set_lbl; + } - /* Cache often used id records on world */ - world->idr_wildcard = flecs_id_record_ensure(world, EcsWildcard); - world->idr_wildcard_wildcard = flecs_id_record_ensure(world, - ecs_pair(EcsWildcard, EcsWildcard)); - world->idr_any = flecs_id_record_ensure(world, EcsAny); + /* 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 (reflexive) { + 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; - /* Create table for initial components */ - ecs_table_t *table = bootstrap_component_table(world); - assert(table != NULL); + if (!first) { + store->filter.first = pair.first; + } else { + store->filter.first.reg = first->id; + store->filter.reg_mask |= RULE_PAIR_PREDICATE; + } - bootstrap_component(world, table, EcsIdentifier); - bootstrap_component(world, table, EcsComponent); - bootstrap_component(world, table, EcsIterable); - bootstrap_component(world, table, EcsPoly); + /* If the object of the filter is not a variable, store literal */ + if (!second) { + store->r_in = UINT8_MAX; + store->subject = ecs_get_alive(rule->world, pair.second.id); + store->filter.second = pair.second; + } else { + store->r_in = second->id; + store->filter.second.reg = second->id; + store->filter.reg_mask |= RULE_PAIR_OBJECT; + } + } - world->info.last_component_id = EcsFirstUserComponentId; - world->info.last_id = EcsFirstUserEntityId; - world->info.min_id = 0; - world->info.max_id = 0; + /* 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; - /* 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 }); - - ecs_observer_init(world, &(ecs_observer_desc_t){ - .entity = ecs_entity(world, {.add = { ecs_childof(EcsFlecsInternals)}}), - .filter.terms[0] = { .id = EcsTag, .src.flags = EcsSelf }, - .events = {EcsOnAdd, EcsOnRemove}, - .callback = register_tag, - .yield_existing = true - }); + /* Predicate can be a variable if it's non-final */ + if (!first) { + set->filter.first = pair.first; + } else { + set->filter.first.reg = first->id; + set->filter.reg_mask |= RULE_PAIR_PREDICATE; + } - /* Populate core module */ - ecs_set_scope(world, EcsFlecsCore); + if (!second) { + set->filter.second = pair.second; + } else { + set->filter.second.reg = second->id; + set->filter.reg_mask |= RULE_PAIR_OBJECT; + } - flecs_bootstrap_tag(world, EcsName); - flecs_bootstrap_tag(world, EcsSymbol); - flecs_bootstrap_tag(world, EcsAlias); + if (reflexive) { + /* 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; + } - flecs_bootstrap_tag(world, EcsQuery); - flecs_bootstrap_tag(world, EcsObserver); + written[out->id] = true; +} - flecs_bootstrap_tag(world, EcsModule); - flecs_bootstrap_tag(world, EcsPrivate); - flecs_bootstrap_tag(world, EcsPrefab); - flecs_bootstrap_tag(world, EcsSlotOf); - flecs_bootstrap_tag(world, EcsDisabled); - flecs_bootstrap_tag(world, EcsEmpty); +static +ecs_rule_var_t* store_reflexive_set( + ecs_rule_t *rule, + ecs_rule_op_kind_t op_kind, + ecs_rule_pair_t *pair, + bool *written, + bool reflexive, + bool as_entity) +{ + /* Ensure we're using the most specific version of second */ + ecs_rule_var_t *second = pair_obj(rule, pair); + if (second) { + pair->second.reg = second->id; + } - /* Initialize builtin modules */ - ecs_set_name(world, EcsFlecs, "flecs"); - ecs_add_id(world, EcsFlecs, EcsModule); - ecs_add_pair(world, EcsFlecs, EcsOnDelete, EcsPanic); + ecs_rule_var_kind_t var_kind = EcsRuleVarKindTable; - ecs_add_pair(world, EcsFlecsCore, EcsChildOf, EcsFlecs); - ecs_set_name(world, EcsFlecsCore, "core"); - ecs_add_id(world, EcsFlecsCore, EcsModule); + /* Create anonymous variable for storing the set */ + ecs_rule_var_t *av = create_anonymous_variable(rule, var_kind); + int32_t ave_id = 0, av_id = av->id; - ecs_add_pair(world, EcsFlecsInternals, EcsChildOf, EcsFlecsCore); - ecs_set_name(world, EcsFlecsInternals, "internals"); - ecs_add_id(world, EcsFlecsInternals, EcsModule); + /* 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 && as_entity) { + create_variable(rule, EcsRuleVarKindEntity, av->name); + av = &rule->vars[av_id]; + ave_id = av_id + 1; + } - /* Initialize builtin entities */ - bootstrap_entity(world, EcsWorld, "World", EcsFlecsCore); - bootstrap_entity(world, EcsWildcard, "*", EcsFlecsCore); - bootstrap_entity(world, EcsAny, "_", EcsFlecsCore); - bootstrap_entity(world, EcsThis, "This", EcsFlecsCore); - bootstrap_entity(world, EcsVariable, "$", EcsFlecsCore); - bootstrap_entity(world, EcsFlag, "Flag", EcsFlecsCore); + /* Generate the operations */ + insert_reflexive_set(rule, op_kind, av, *pair, -1, written, reflexive); - /* Component/relationship properties */ - flecs_bootstrap_tag(world, EcsTransitive); - flecs_bootstrap_tag(world, EcsReflexive); - flecs_bootstrap_tag(world, EcsSymmetric); - flecs_bootstrap_tag(world, EcsFinal); - flecs_bootstrap_tag(world, EcsDontInherit); - flecs_bootstrap_tag(world, EcsTag); - flecs_bootstrap_tag(world, EcsUnion); - flecs_bootstrap_tag(world, EcsExclusive); - flecs_bootstrap_tag(world, EcsAcyclic); - flecs_bootstrap_tag(world, EcsWith); - flecs_bootstrap_tag(world, EcsOneOf); + /* Make sure to return entity variable, and that it is populated */ + if (as_entity) { + return ensure_entity_written(rule, &rule->vars[ave_id], written); + } else { + return &rule->vars[av_id]; + } +} - flecs_bootstrap_tag(world, EcsOnDelete); - flecs_bootstrap_tag(world, EcsOnDeleteTarget); - flecs_bootstrap_tag(world, EcsRemove); - flecs_bootstrap_tag(world, EcsDelete); - flecs_bootstrap_tag(world, EcsPanic); +static +bool is_known( + ecs_rule_var_t *var, + bool *written) +{ + if (!var) { + return true; + } else { + return written[var->id]; + } +} - flecs_bootstrap_tag(world, EcsDefaultChildComponent); +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) || pair->first.id == EcsWildcard) { + return false; + } - /* Builtin relationships */ - flecs_bootstrap_tag(world, EcsIsA); - flecs_bootstrap_tag(world, EcsChildOf); - flecs_bootstrap_tag(world, EcsDependsOn); + ecs_rule_var_t *obj_var = pair_obj(rule, pair); + if (!is_known(obj_var, written) || pair->second.id == EcsWildcard) { + 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, EcsOnTableFill, "OnTableFilled", EcsFlecsCore); + return true; +} - /* Tag relationships (relationships that should never have data) */ - ecs_add_id(world, EcsIsA, EcsTag); - ecs_add_id(world, EcsChildOf, EcsTag); - ecs_add_id(world, EcsSlotOf, EcsTag); - ecs_add_id(world, EcsDependsOn, EcsTag); - ecs_add_id(world, EcsDefaultChildComponent, EcsTag); - ecs_add_id(world, EcsUnion, EcsTag); - ecs_add_id(world, EcsFlag, EcsTag); +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->src.id; - /* Exclusive properties */ - ecs_add_id(world, EcsChildOf, EcsExclusive); - ecs_add_id(world, EcsOnDelete, EcsExclusive); - ecs_add_id(world, EcsOnDeleteTarget, EcsExclusive); - ecs_add_id(world, EcsDefaultChildComponent, EcsExclusive); + /* 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; + } +} - /* Sync properties of ChildOf and Identifier with bootstrapped flags */ - ecs_add_pair(world, EcsChildOf, EcsOnDeleteTarget, EcsDelete); - ecs_add_id(world, EcsChildOf, EcsAcyclic); - ecs_add_id(world, EcsChildOf, EcsDontInherit); - ecs_add_id(world, ecs_id(EcsIdentifier), EcsDontInherit); +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; - /* The (IsA, *) id record is used often in searches, so cache it */ - world->idr_isa_wildcard = flecs_id_record_ensure(world, - ecs_pair(EcsIsA, EcsWildcard)); + op->has_out = true; + if (!var) { + op->r_out = UINT8_MAX; + op->subject = term->src.id; - /* Create triggers in internals scope */ - ecs_set_scope(world, EcsFlecsInternals); + /* Invalid entities should have been caught during parsing */ + ecs_assert(ecs_is_valid(rule->world, op->subject), + ECS_INTERNAL_ERROR, NULL); + } else { + op->r_out = var->id; + } +} - /* Term used to also match prefabs */ - ecs_term_t match_prefab = { - .id = EcsPrefab, - .oper = EcsOptional, - .src.flags = EcsSelf - }; +static +void insert_select_or_with( + ecs_rule_t *rule, + int32_t c, + ecs_term_t *term, + ecs_rule_var_t *src, + ecs_rule_pair_t *pair, + bool *written) +{ + ecs_rule_op_t *op; + bool eval_subject_supersets = false; - ecs_observer_init(world, &(ecs_observer_desc_t){ - .filter.terms = { - { .id = ecs_pair(EcsChildOf, EcsWildcard), .src.flags = EcsSelf }, - match_prefab - }, - .events = { EcsOnAdd, EcsOnRemove }, - .yield_existing = true, - .callback = on_parent_change - }); + /* Find any entity and/or table variables for subject */ + ecs_rule_var_t *tvar = NULL, *evar = to_entity(rule, src), *var = evar; + if (src && src->kind == EcsRuleVarKindTable) { + tvar = src; + if (!evar) { + var = tvar; + } + } - ecs_observer_init(world, &(ecs_observer_desc_t){ - .filter.terms = {{ .id = EcsFinal, .src.flags = EcsSelf }, match_prefab }, - .events = {EcsOnAdd}, - .callback = register_final - }); + int32_t lbl_start = rule->operation_count; + ecs_rule_pair_t filter; + if (pair) { + filter = *pair; + } else { + filter = term_to_pair(rule, term); + } - ecs_observer_init(world, &(ecs_observer_desc_t){ - .filter.terms = { - { .id = ecs_pair(EcsOnDelete, EcsWildcard), .src.flags = EcsSelf }, - match_prefab - }, - .events = {EcsOnAdd, EcsOnRemove}, - .callback = register_on_delete - }); + /* Only insert implicit IsA if filter isn't already an IsA */ + if ((!filter.transitive || filter.first.id != EcsIsA) && term->oper != EcsNot) { + if (!var) { + ecs_rule_pair_t isa_pair = { + .first.id = EcsIsA, + .second.id = term->src.id + }; - ecs_observer_init(world, &(ecs_observer_desc_t){ - .filter.terms = { - { .id = ecs_pair(EcsOnDeleteTarget, EcsWildcard), .src.flags = EcsSelf }, - match_prefab - }, - .events = {EcsOnAdd, EcsOnRemove}, - .callback = register_on_delete_object - }); + evar = src = store_reflexive_set(rule, EcsRuleSuperSet, &isa_pair, + written, true, true); + tvar = NULL; + eval_subject_supersets = true; - ecs_observer_init(world, &(ecs_observer_desc_t){ - .filter.terms = { - { .id = EcsAcyclic, .src.flags = EcsSelf }, - match_prefab - }, - .events = {EcsOnAdd, EcsOnRemove}, - .callback = register_acyclic - }); + } else if (ecs_id_is_wildcard(term->id) && + ECS_PAIR_FIRST(term->id) != EcsThis && + ECS_PAIR_SECOND(term->id) != EcsThis) + { + ecs_assert(src != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_observer_init(world, &(ecs_observer_desc_t){ - .filter.terms = {{ .id = EcsExclusive, .src.flags = EcsSelf }, match_prefab }, - .events = {EcsOnAdd}, - .callback = register_exclusive - }); + op = insert_operation(rule, -1, written); - ecs_observer_init(world, &(ecs_observer_desc_t){ - .filter.terms = {{ .id = EcsSymmetric, .src.flags = EcsSelf }, match_prefab }, - .events = {EcsOnAdd}, - .callback = register_symmetric - }); + if (!is_known(src, written)) { + op->kind = EcsRuleSelect; + set_output_to_subj(rule, op, term, src); + written[src->id] = true; + } else { + op->kind = EcsRuleWith; + set_input_to_subj(rule, op, term, src); + } - ecs_observer_init(world, &(ecs_observer_desc_t){ - .filter.terms = {{ .id = EcsDontInherit, .src.flags = EcsSelf }, match_prefab }, - .events = {EcsOnAdd}, - .callback = register_dont_inherit - }); + ecs_rule_pair_t isa_pair = { + .first.id = EcsIsA, + .second.reg = src->id, + .reg_mask = RULE_PAIR_OBJECT + }; - ecs_observer_init(world, &(ecs_observer_desc_t){ - .filter.terms = { - { .id = ecs_pair(EcsWith, EcsWildcard), .src.flags = EcsSelf }, - match_prefab - }, - .events = {EcsOnAdd}, - .callback = register_with - }); + op->filter = filter; + if (op->filter.reg_mask & RULE_PAIR_PREDICATE) { + op->filter.first.id = EcsWildcard; + } + if (op->filter.reg_mask & RULE_PAIR_OBJECT) { + op->filter.second.id = EcsWildcard; + } + op->filter.reg_mask = 0; - ecs_observer_init(world, &(ecs_observer_desc_t){ - .filter.terms = {{ .id = EcsUnion, .src.flags = EcsSelf }, match_prefab }, - .events = {EcsOnAdd}, - .callback = register_union - }); + push_frame(rule); - /* Entities used as slot are marked as exclusive to ensure a slot can always - * only point to a single entity. */ - ecs_observer_init(world, &(ecs_observer_desc_t){ - .filter.terms = { - { .id = ecs_pair(EcsSlotOf, EcsWildcard), .src.flags = EcsSelf }, - match_prefab - }, - .events = {EcsOnAdd}, - .callback = register_slot_of - }); + tvar = src = store_reflexive_set(rule, EcsRuleSuperSet, &isa_pair, + written, true, false); - /* Define observer to make sure that adding a module to a child entity also - * adds it to the parent. */ - ecs_observer_init(world, &(ecs_observer_desc_t){ - .filter.terms = {{ .id = EcsModule, .src.flags = EcsSelf }, match_prefab}, - .events = {EcsOnAdd}, - .callback = ensure_module_tag - }); + evar = NULL; + } + } - /* Set scope back to flecs core */ - ecs_set_scope(world, EcsFlecsCore); + /* If no pair is provided, create operation from specified term */ + if (!pair) { + op = insert_operation(rule, c, written); - /* Acyclic components */ - ecs_add_id(world, EcsIsA, EcsAcyclic); - ecs_add_id(world, EcsDependsOn, EcsAcyclic); - ecs_add_id(world, EcsWith, EcsAcyclic); + /* 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; - /* DontInherit components */ - ecs_add_id(world, EcsDisabled, EcsDontInherit); - ecs_add_id(world, EcsPrefab, EcsDontInherit); + /* Assign the term id, so that the operation will still be correctly + * associated with the correct expression term. */ + op->term = c; + } - /* Transitive relationships are always Acyclic */ - ecs_add_pair(world, EcsTransitive, EcsWith, EcsAcyclic); + /* 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, src); - /* Transitive relationships */ - ecs_add_id(world, EcsIsA, EcsTransitive); - ecs_add_id(world, EcsIsA, EcsReflexive); + /* 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, src); - /* Exclusive properties */ - ecs_add_id(world, EcsSlotOf, EcsExclusive); - ecs_add_id(world, EcsOneOf, EcsExclusive); - - /* Run bootstrap functions for other parts of the code */ - flecs_bootstrap_hierarchy(world); + /* If subject is neither table nor entitiy, with operates on literal */ + } else if (!tvar && !evar) { + op->kind = EcsRuleWith; + set_input_to_subj(rule, op, term, src); - ecs_set_scope(world, 0); + /* If subject is table or entity but not known, use select */ + } else { + ecs_assert(src != NULL, ECS_INTERNAL_ERROR, NULL); + op->kind = EcsRuleSelect; + set_output_to_subj(rule, op, term, src); + written[src->id] = true; + } - ecs_set_name_prefix(world, NULL); + /* 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); - ecs_log_pop(); -} + /* 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 (op->filter.reg_mask & RULE_PAIR_PREDICATE) { + written[op->filter.first.reg] = true; + } + if (op->filter.reg_mask & RULE_PAIR_OBJECT) { + written[op->filter.second.reg] = true; + } +} static -void table_cache_list_remove( - ecs_table_cache_t *cache, - ecs_table_cache_hdr_t *elem) +void prepare_predicate( + ecs_rule_t *rule, + ecs_rule_pair_t *pair, + int32_t term, + bool *written) { - ecs_table_cache_hdr_t *next = elem->next; - ecs_table_cache_hdr_t *prev = elem->prev; + /* 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 = { + .first.id = EcsIsA, + .second.id = pair->first.id + }; - if (next) { - next->prev = prev; - } - if (prev) { - prev->next = next; - } + ecs_rule_var_t *first = store_reflexive_set(rule, EcsRuleSubSet, + &isa_pair, written, true, true); - cache->empty_tables.count -= !!elem->empty; - cache->tables.count -= !elem->empty; + pair->first.reg = first->id; + pair->reg_mask |= RULE_PAIR_PREDICATE; - if (cache->empty_tables.first == elem) { - cache->empty_tables.first = next; - } else if (cache->tables.first == elem) { - cache->tables.first = next; - } - if (cache->empty_tables.last == elem) { - cache->empty_tables.last = prev; - } - if (cache->tables.last == elem) { - cache->tables.last = prev; + if (term != -1) { + rule->term_vars[term].first = first->id; + } } } static -void table_cache_list_insert( - ecs_table_cache_t *cache, - ecs_table_cache_hdr_t *elem) +void insert_term_2( + ecs_rule_t *rule, + ecs_term_t *term, + ecs_rule_pair_t *filter, + int32_t c, + bool *written) { - ecs_table_cache_hdr_t *last; - if (elem->empty) { - last = cache->empty_tables.last; - cache->empty_tables.last = elem; - if ((++ cache->empty_tables.count) == 1) { - cache->empty_tables.first = elem; - } - } else { - last = cache->tables.last; - cache->tables.last = elem; - if ((++ cache->tables.count) == 1) { - cache->tables.first = elem; - } + int32_t subj_id = -1, obj_id = -1; + ecs_rule_var_t *src = term_subj(rule, term); + if ((src = get_most_specific_var(rule, src, written))) { + subj_id = src->id; } - elem->next = NULL; - elem->prev = last; - - if (last) { - last->next = elem; + ecs_rule_var_t *second = term_obj(rule, term); + if ((second = get_most_specific_var(rule, second, written))) { + obj_id = second->id; } -} -void ecs_table_cache_init( - ecs_table_cache_t *cache) -{ - ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_map_init(&cache->index, ecs_table_cache_hdr_t*, 4); -} + bool subj_known = is_known(src, written); + bool same_obj_subj = false; + if (src && second) { + same_obj_subj = !ecs_os_strcmp(src->name, second->name); + } -void ecs_table_cache_fini( - ecs_table_cache_t *cache) -{ - ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_map_fini(&cache->index); -} + if (!filter->transitive) { + insert_select_or_with(rule, c, term, src, filter, written); + if (src) src = &rule->vars[subj_id]; + if (second) second = &rule->vars[obj_id]; -bool ecs_table_cache_is_empty( - const ecs_table_cache_t *cache) -{ - return ecs_map_count(&cache->index) == 0; -} + } else if (filter->transitive) { + if (subj_known) { + if (is_known(second, written)) { + if (filter->second.id != EcsWildcard) { + ecs_rule_var_t *obj_subsets = store_reflexive_set( + rule, EcsRuleSubSet, filter, written, true, true); -void ecs_table_cache_insert( - ecs_table_cache_t *cache, - const ecs_table_t *table, - ecs_table_cache_hdr_t *result) -{ - ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(ecs_table_cache_get(cache, table) == NULL, - ECS_INTERNAL_ERROR, NULL); - ecs_assert(result != NULL, ECS_INTERNAL_ERROR, NULL); + if (src) { + src = &rule->vars[subj_id]; + } - bool empty; - if (!table) { - empty = false; - } else { - empty = ecs_table_count(table) == 0; - } + rule->term_vars[c].second = obj_subsets->id; - result->cache = cache; - result->table = (ecs_table_t*)table; - result->empty = empty; + ecs_rule_pair_t pair = *filter; + pair.second.reg = obj_subsets->id; + pair.reg_mask |= RULE_PAIR_OBJECT; - table_cache_list_insert(cache, result); + insert_select_or_with(rule, c, term, src, &pair, written); + } else { + insert_select_or_with(rule, c, term, src, filter, written); + } + } else { + ecs_assert(second != NULL, ECS_INTERNAL_ERROR, NULL); - if (table) { - ecs_map_set_ptr(&cache->index, table->id, result); - } + /* If subject is literal, find supersets for subject */ + if (src == NULL || src->kind == EcsRuleVarKindEntity) { + second = to_entity(rule, second); - ecs_assert(empty || cache->tables.first != NULL, - ECS_INTERNAL_ERROR, NULL); - ecs_assert(!empty || cache->empty_tables.first != NULL, - ECS_INTERNAL_ERROR, NULL); -} + ecs_rule_pair_t set_pair = *filter; + set_pair.reg_mask &= RULE_PAIR_PREDICATE; -void ecs_table_cache_replace( - ecs_table_cache_t *cache, - const ecs_table_t *table, - ecs_table_cache_hdr_t *elem) -{ - ecs_table_cache_hdr_t **oldptr = ecs_map_get(&cache->index, - ecs_table_cache_hdr_t*, table->id); - ecs_assert(oldptr != NULL, ECS_INTERNAL_ERROR, NULL); + if (src) { + set_pair.second.reg = src->id; + set_pair.reg_mask |= RULE_PAIR_OBJECT; + } else { + set_pair.second.id = term->src.id; + } - ecs_table_cache_hdr_t *old = *oldptr; - ecs_assert(old != NULL, ECS_INTERNAL_ERROR, NULL); + insert_reflexive_set(rule, EcsRuleSuperSet, second, set_pair, + c, written, filter->reflexive); - ecs_table_cache_hdr_t *prev = old->prev, *next = old->next; - if (prev) { - ecs_assert(prev->next == old, ECS_INTERNAL_ERROR, NULL); - prev->next = elem; - } - if (next) { - ecs_assert(next->prev == old, ECS_INTERNAL_ERROR, NULL); - next->prev = elem; - } + /* 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); - if (cache->empty_tables.first == old) { - cache->empty_tables.first = elem; - } - if (cache->empty_tables.last == old) { - cache->empty_tables.last = elem; - } - if (cache->tables.first == old) { - cache->tables.first = elem; - } - if (cache->tables.last == old) { - cache->tables.last = elem; - } + src = &rule->vars[subj_id]; + second = &rule->vars[obj_id]; + second = to_entity(rule, second); - *oldptr = elem; - elem->prev = prev; - elem->next = next; -} + ecs_rule_pair_t set_pair = *filter; + set_pair.second.reg = av->id; + set_pair.reg_mask |= RULE_PAIR_OBJECT; -void* ecs_table_cache_get( - const ecs_table_cache_t *cache, - const ecs_table_t *table) -{ - ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); - if (table) { - return ecs_map_get_ptr(&cache->index, ecs_table_cache_hdr_t*, table->id); - } else { - ecs_table_cache_hdr_t *elem = cache->tables.first; - ecs_assert(!elem || elem->table == NULL, ECS_INTERNAL_ERROR, NULL); - return elem; - } -} + /* Insert with to find initial object for relationship */ + insert_select_or_with( + rule, c, term, src, &set_pair, written); -void* ecs_table_cache_remove( - ecs_table_cache_t *cache, - const ecs_table_t *table, - ecs_table_cache_hdr_t *elem) -{ - ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + push_frame(rule); - if (!ecs_map_is_initialized(&cache->index)) { - return NULL; - } + /* Find supersets for returned initial object. Make sure + * this is always reflexive since it needs to return the + * object from the pair that the entity has itself. */ + insert_reflexive_set(rule, EcsRuleSuperSet, second, set_pair, + c, written, true); + } + } - if (!elem) { - elem = ecs_map_get_ptr( - &cache->index, ecs_table_cache_hdr_t*, table->id); - if (!elem) { - return false; - } - } + /* src is not known */ + } else { + ecs_assert(src != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(elem != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(elem->cache == cache, ECS_INTERNAL_ERROR, NULL); - ecs_assert(elem->table == table, ECS_INTERNAL_ERROR, NULL); + if (is_known(second, written)) { + ecs_rule_pair_t set_pair = *filter; + set_pair.reg_mask &= RULE_PAIR_PREDICATE; /* clear object mask */ - table_cache_list_remove(cache, elem); + if (second) { + set_pair.second.reg = second->id; + set_pair.reg_mask |= RULE_PAIR_OBJECT; + } else { + set_pair.second.id = term->second.id; + } - ecs_map_remove(&cache->index, table->id); + if (second) { + rule->term_vars[c].second = second->id; + } else { + ecs_rule_var_t *av = create_anonymous_variable(rule, + EcsRuleVarKindEntity); + rule->term_vars[c].second = av->id; + written[av->id] = true; + } - return elem; -} + insert_reflexive_set(rule, EcsRuleSubSet, src, set_pair, c, + written, filter->reflexive); + } else if (src == second) { + insert_select_or_with(rule, c, term, src, filter, written); + } else { + ecs_assert(second != NULL, ECS_INTERNAL_ERROR, NULL); -bool ecs_table_cache_set_empty( - ecs_table_cache_t *cache, - const ecs_table_t *table, - bool empty) -{ - ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_rule_var_t *av = NULL; + if (!filter->reflexive) { + av = create_anonymous_variable(rule, EcsRuleVarKindEntity); + } - ecs_table_cache_hdr_t *elem = ecs_map_get_ptr( - &cache->index, ecs_table_cache_hdr_t*, table->id); - if (!elem) { - return false; - } + src = &rule->vars[subj_id]; + second = &rule->vars[obj_id]; + second = to_entity(rule, second); - if (elem->empty == empty) { - return false; - } + /* Insert instruction to find all sources and objects */ + ecs_rule_op_t *op = insert_operation(rule, -1, written); + op->kind = EcsRuleSelect; + set_output_to_subj(rule, op, term, src); + op->filter.first = filter->first; - table_cache_list_remove(cache, elem); - elem->empty = empty; - table_cache_list_insert(cache, elem); + if (filter->reflexive) { + op->filter.second.id = EcsWildcard; + op->filter.reg_mask = filter->reg_mask & RULE_PAIR_PREDICATE; + } else { + op->filter.second.reg = av->id; + op->filter.reg_mask = filter->reg_mask | RULE_PAIR_OBJECT; + written[av->id] = true; + } - return true; -} + written[src->id] = true; -bool flecs_table_cache_iter( - ecs_table_cache_t *cache, - ecs_table_cache_iter_t *out) -{ - ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(out != NULL, ECS_INTERNAL_ERROR, NULL); - out->next = cache->tables.first; - out->next_list = NULL; - out->cur = NULL; - return out->next != NULL; -} + /* Create new frame for operations that create reflexive set */ + push_frame(rule); -bool flecs_table_cache_empty_iter( - ecs_table_cache_t *cache, - ecs_table_cache_iter_t *out) -{ - ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(out != NULL, ECS_INTERNAL_ERROR, NULL); - out->next = cache->empty_tables.first; - out->next_list = NULL; - out->cur = NULL; - return out->next != NULL; -} + /* Insert superset instruction to find all supersets */ + if (filter->reflexive) { + src = ensure_most_specific_var(rule, src, written); + ecs_assert(src->kind == EcsRuleVarKindEntity, + ECS_INTERNAL_ERROR, NULL); + ecs_assert(written[src->id] == true, + ECS_INTERNAL_ERROR, NULL); -bool flecs_table_cache_all_iter( - ecs_table_cache_t *cache, - ecs_table_cache_iter_t *out) -{ - ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(out != NULL, ECS_INTERNAL_ERROR, NULL); - out->next = cache->empty_tables.first; - out->next_list = cache->tables.first; - out->cur = NULL; - return out->next != NULL || out->next_list != NULL; -} + ecs_rule_pair_t super_filter = {0}; + super_filter.first = filter->first; + super_filter.second.reg = src->id; + super_filter.reg_mask = filter->reg_mask | RULE_PAIR_OBJECT; -ecs_table_cache_hdr_t* _flecs_table_cache_next( - ecs_table_cache_iter_t *it) -{ - ecs_table_cache_hdr_t *next = it->next; - if (!next) { - next = it->next_list; - it->next_list = NULL; - if (!next) { - return false; + insert_reflexive_set(rule, EcsRuleSuperSet, second, + super_filter, c, written, true); + } else { + insert_reflexive_set(rule, EcsRuleSuperSet, second, + op->filter, c, written, true); + } + } } } - it->cur = next; - it->next = next->next; - return next; -} - - -/* Table sanity check to detect storage issues. Only enabled in SANITIZE mode as - * this can severly slow down many ECS operations. */ -#ifdef FLECS_SANITIZE -static -void check_table_sanity(ecs_table_t *table) { - int32_t size = ecs_storage_size(&table->data.entities); - int32_t count = ecs_storage_count(&table->data.entities); - - ecs_assert(size == ecs_storage_size(&table->data.records), - ECS_INTERNAL_ERROR, NULL); - ecs_assert(count == ecs_storage_count(&table->data.records), - ECS_INTERNAL_ERROR, NULL); - - int32_t i; - int32_t sw_offset = table->sw_offset; - int32_t sw_count = table->sw_count; - int32_t bs_offset = table->bs_offset; - int32_t bs_count = table->bs_count; - int32_t type_count = table->type.count; - ecs_id_t *ids = table->type.array; - - ecs_assert((sw_count + sw_offset) <= type_count, ECS_INTERNAL_ERROR, NULL); - ecs_assert((bs_count + bs_offset) <= type_count, ECS_INTERNAL_ERROR, NULL); - - ecs_table_t *storage_table = table->storage_table; - if (storage_table) { - ecs_assert(table->storage_count == storage_table->type.count, - ECS_INTERNAL_ERROR, NULL); - ecs_assert(table->storage_ids == storage_table->type.array, + if (same_obj_subj) { + /* Can't have relationship with same variables that is acyclic and not + * reflexive, this should've been caught earlier. */ + ecs_assert(!filter->acyclic || filter->reflexive, ECS_INTERNAL_ERROR, NULL); - int32_t storage_count = table->storage_count; - ecs_assert(type_count >= storage_count, ECS_INTERNAL_ERROR, NULL); - - int32_t *storage_map = table->storage_map; - ecs_assert(storage_map != NULL, ECS_INTERNAL_ERROR, NULL); - - ecs_id_t *storage_ids = table->storage_ids; - for (i = 0; i < type_count; i ++) { - if (storage_map[i] != -1) { - ecs_assert(ids[i] == storage_ids[storage_map[i]], - ECS_INTERNAL_ERROR, NULL); - } - } - - ecs_assert(table->data.columns != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(table->type_info != NULL, ECS_INTERNAL_ERROR, NULL); + /* If relationship is reflexive and entity has an instance of R, no checks + * are needed because R(X, X) is always true. */ + if (!filter->reflexive) { + push_frame(rule); - for (i = 0; i < storage_count; i ++) { - ecs_column_t *column = &table->data.columns[i]; - ecs_assert(size == column->size, ECS_INTERNAL_ERROR, NULL); - ecs_assert(count == column->count, ECS_INTERNAL_ERROR, NULL); - int32_t storage_map_id = storage_map[i + type_count]; - ecs_assert(storage_map_id >= 0, ECS_INTERNAL_ERROR, NULL); - ecs_assert(ids[storage_map_id] == storage_ids[i], + /* Insert check if the (R, X) pair that was found matches with one + * of the entities in the table with the pair. */ + ecs_rule_op_t *op = insert_operation(rule, -1, written); + second = get_most_specific_var(rule, second, written); + ecs_assert(second->kind == EcsRuleVarKindEntity, ECS_INTERNAL_ERROR, NULL); - } - } else { - ecs_assert(table->storage_count == 0, ECS_INTERNAL_ERROR, NULL); - ecs_assert(table->storage_ids == NULL, ECS_INTERNAL_ERROR, NULL); - } + ecs_assert(written[src->id] == true, ECS_INTERNAL_ERROR, NULL); + ecs_assert(written[second->id] == true, ECS_INTERNAL_ERROR, NULL); + + set_input_to_subj(rule, op, term, src); + op->filter.second.reg = second->id; + op->filter.reg_mask = RULE_PAIR_OBJECT; - if (sw_count) { - ecs_assert(table->data.sw_columns != NULL, - ECS_INTERNAL_ERROR, NULL); - for (i = 0; i < sw_count; i ++) { - ecs_switch_t *sw = &table->data.sw_columns[i]; - ecs_assert(ecs_vector_count(sw->values) == count, - ECS_INTERNAL_ERROR, NULL); - ecs_assert(ECS_PAIR_FIRST(ids[i + sw_offset]) == EcsUnion, - ECS_INTERNAL_ERROR, NULL); + if (src->kind == EcsRuleVarKindTable) { + op->kind = EcsRuleInTable; + } else { + op->kind = EcsRuleEq; + } } } +} - if (bs_count) { - ecs_assert(table->data.bs_columns != NULL, - ECS_INTERNAL_ERROR, NULL); - for (i = 0; i < bs_count; i ++) { - ecs_bitset_t *bs = &table->data.bs_columns[i]; - ecs_assert(flecs_bitset_count(bs) == count, - ECS_INTERNAL_ERROR, NULL); - ecs_assert(ECS_HAS_ID_FLAG(ids[i + bs_offset], TOGGLE), - ECS_INTERNAL_ERROR, NULL); - } - } +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 *src = term_subj(rule, term); + src = get_most_specific_var(rule, src, written); + insert_select_or_with(rule, c, term, src, filter, written); } -#else -#define check_table_sanity(table) -#endif static -void flecs_table_init_storage_map( - ecs_table_t *table) +void insert_term( + ecs_rule_t *rule, + ecs_term_t *term, + int32_t c, + bool *written) { - ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); - if (!table->storage_table) { - return; + bool obj_set = obj_is_set(term); + + ensure_most_specific_var(rule, term_pred(rule, term), written); + if (obj_set) { + ensure_most_specific_var(rule, term_obj(rule, term), written); } - ecs_type_t type = table->type; - ecs_id_t *ids = type.array; - int32_t t, ids_count = type.count; - ecs_id_t *storage_ids = table->storage_ids; - int32_t s, storage_ids_count = table->storage_count; + /* 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; + } - if (!ids_count) { - table->storage_map = NULL; - return; + ecs_rule_pair_t filter = term_to_pair(rule, term); + prepare_predicate(rule, &filter, c, written); + + 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); } - table->storage_map = ecs_os_malloc_n( - int32_t, ids_count + storage_ids_count); + /* 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; - int32_t *t2s = table->storage_map; - int32_t *s2t = &table->storage_map[ids_count]; + not_post->on_pass = prev - 1; + not_post->on_fail = prev - 1; + not_pre = &rule->operations[prev]; + not_pre->on_fail = rule->operation_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 (term->oper == EcsOptional) { + /* Insert Not 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 (id == storage_id) { - t2s[t] = s; - s2t[s] = t; - } else { - t2s[t] = -1; + /* 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; + } } - /* 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); + 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; } - /* Storage ids is always a subset of ids, so all should be iterated */ - ecs_assert(s == storage_ids_count, ECS_INTERNAL_ERROR, NULL); - - /* Initialize remainder of type -> storage_type map */ - for (; (t < ids_count); t ++) { - t2s[t] = -1; - } + push_frame(rule); } +/* Create program from operations that will execute the query */ static -ecs_flags32_t flecs_type_info_flags( - const ecs_type_info_t *ti) +void compile_program( + ecs_rule_t *rule) { - ecs_flags32_t flags = 0; + /* Trace which variables have been written while inserting instructions. + * This determines which instruction needs to be inserted */ + bool written[ECS_RULE_MAX_VAR_COUNT] = { false }; - if (ti->hooks.ctor) { - flags |= EcsTableHasCtors; - } - if (ti->hooks.on_add) { - flags |= EcsTableHasCtors; - } - if (ti->hooks.dtor) { - flags |= EcsTableHasDtors; - } - if (ti->hooks.on_remove) { - flags |= EcsTableHasDtors; - } - if (ti->hooks.copy) { - flags |= EcsTableHasCopy; - } - if (ti->hooks.move) { - flags |= EcsTableHasMove; - } + ecs_term_t *terms = rule->filter.terms; + int32_t v, c, term_count = rule->filter.term_count; + ecs_rule_op_t *op; - return flags; -} + /* Insert input, which is always the first instruction */ + insert_input(rule); -static -void flecs_table_init_type_info( - ecs_table_t *table) -{ - ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(table->storage_table == table, ECS_INTERNAL_ERROR, NULL); - ecs_assert(table->type_info == NULL, ECS_INTERNAL_ERROR, NULL); + /* 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; + } - ecs_table_record_t *records = table->records; - int32_t i, count = table->type.count; - table->type_info = ecs_os_calloc_n(ecs_type_info_t*, count); + if (term->oper == EcsOptional || term->oper == EcsNot) { + continue; + } - for (i = 0; i < count; i ++) { - ecs_table_record_t *tr = &records[i]; - ecs_id_record_t *idr = (ecs_id_record_t*)tr->hdr.cache; - - /* All ids in the storage table must be components with type info */ - const ecs_type_info_t *ti = idr->type_info; - ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); - table->flags |= flecs_type_info_flags(ti); - table->type_info[i] = (ecs_type_info_t*)ti; - } -} + ecs_rule_var_t* src = term_subj(rule, term); + if (src) { + continue; + } -static -void flecs_table_init_storage_table( - ecs_world_t *world, - ecs_table_t *table) -{ - if (table->storage_table) { - return; + insert_term(rule, term, c, written); } - ecs_type_t type = table->type; - int32_t i, count = type.count; - ecs_id_t *ids = type.array; - ecs_table_record_t *records = table->records; + /* Insert variables based on dependency order */ + for (v = 0; v < rule->subj_var_count; v ++) { + int32_t var_id = rule->var_eval_order[v]; + ecs_rule_var_t *var = &rule->vars[var_id]; - ecs_id_t array[ECS_ID_CACHE_SIZE]; - ecs_type_t storage_ids = { .array = array }; - if (count > ECS_ID_CACHE_SIZE) { - storage_ids.array = ecs_os_malloc_n(ecs_id_t, count); - } + ecs_assert(var->kind == EcsRuleVarKindTable, ECS_INTERNAL_ERROR, NULL); - for (i = 0; i < count; i ++) { - ecs_table_record_t *tr = &records[i]; - ecs_id_record_t *idr = (ecs_id_record_t*)tr->hdr.cache; - ecs_id_t id = ids[i]; + for (c = 0; c < term_count; c ++) { + ecs_term_t *term = &terms[c]; + if (skip_term(term)) { + continue; + } - if (idr->type_info != NULL) { - storage_ids.array[storage_ids.count ++] = id; + if (term->oper == EcsOptional || term->oper == EcsNot) { + continue; + } + + /* Only process columns for which variable is subject */ + ecs_rule_var_t* src = term_subj(rule, term); + if (src != var) { + continue; + } + + insert_term(rule, term, c, written); + + var = &rule->vars[var_id]; } } - if (storage_ids.count && storage_ids.count != count) { - ecs_table_t *storage_table = flecs_table_find_or_create(world, - &storage_ids); - table->storage_table = storage_table; - table->storage_count = flecs_ito(uint16_t, storage_ids.count); - table->storage_ids = storage_table->type.array; - table->type_info = storage_table->type_info; - table->flags |= storage_table->flags; - storage_table->refcount ++; - } else if (storage_ids.count) { - table->storage_table = table; - table->storage_count = flecs_ito(uint16_t, count); - table->storage_ids = type.array; - flecs_table_init_type_info(table); + /* Insert terms with Not operators */ + for (c = 0; c < term_count; c ++) { + ecs_term_t *term = &terms[c]; + if (term->oper != EcsNot) { + continue; + } + + insert_term(rule, term, c, written); } - if (storage_ids.array != array) { - ecs_os_free(storage_ids.array); + /* 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); } - if (!table->storage_map) { - flecs_table_init_storage_map(table); + /* Verify all subject variables have been written. Source variables are of + * the table type, and a select/subset should have been inserted for each */ + for (v = 0; v < rule->subj_var_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->vars[v].name); + ecs_assert(var != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(written[var->id], ECS_INTERNAL_ERROR, var->name); + (void)var; + } } -} -void flecs_table_init_data( - ecs_table_t *table) -{ - int32_t sw_count = table->sw_count; - int32_t bs_count = table->bs_count; + /* 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->var_count; v ++) { + if (!written[v]) { + ecs_rule_var_t *var = &rule->vars[v]; + ecs_assert(var->kind == EcsRuleVarKindEntity, + ECS_INTERNAL_ERROR, NULL); - ecs_data_t *storage = &table->data; - int32_t i, count = table->storage_count; + 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); - /* Root tables don't have columns */ - if (!count && !sw_count && !bs_count) { - storage->columns = NULL; - } + /* 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); + } + } - if (count) { - storage->columns = ecs_os_calloc_n(ecs_column_t, count); - } + /* Insert yield, which is always the last operation */ + insert_yield(rule); +} - if (sw_count) { - storage->sw_columns = ecs_os_calloc_n(ecs_switch_t, sw_count); - for (i = 0; i < sw_count; i ++) { - flecs_switch_init(&storage->sw_columns[i], 0); +static +void create_variable_name_array( + ecs_rule_t *rule) +{ + if (rule->var_count) { + int i; + for (i = 0; i < rule->var_count; i ++) { + ecs_rule_var_t *var = &rule->vars[i]; + + if (var->kind != EcsRuleVarKindEntity) { + /* Table variables are hidden for applications. */ + rule->var_names[var->id] = NULL; + } else { + rule->var_names[var->id] = var->name; + } } } +} - if (bs_count) { - storage->bs_columns = ecs_os_calloc_n(ecs_bitset_t, bs_count); - for (i = 0; i < bs_count; i ++) { - flecs_bitset_init(&storage->bs_columns[i]); +static +void create_variable_cross_references( + ecs_rule_t *rule) +{ + if (rule->var_count) { + int i; + for (i = 0; i < rule->var_count; i ++) { + ecs_rule_var_t *var = &rule->vars[i]; + if (var->kind == EcsRuleVarKindEntity) { + ecs_rule_var_t *tvar = find_variable( + rule, EcsRuleVarKindTable, var->name); + if (tvar) { + var->other = tvar->id; + } else { + var->other = -1; + } + } else { + ecs_rule_var_t *evar = find_variable( + rule, EcsRuleVarKindEntity, var->name); + if (evar) { + var->other = evar->id; + } else { + var->other = -1; + } + } } } } +/* Implementation for iterable mixin */ static -void flecs_table_init_flags( - ecs_world_t *world, - ecs_table_t *table) +void rule_iter_init( + const ecs_world_t *world, + const ecs_poly_t *poly, + ecs_iter_t *iter, + ecs_term_t *filter) { - ecs_id_t *ids = table->type.array; - int32_t count = table->type.count; + ecs_poly_assert(poly, ecs_rule_t); - /* Iterate components to initialize table flags */ - int32_t i; - for (i = 0; i < count; i ++) { - ecs_id_t id = ids[i]; + 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); + } +} - /* 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. */ - - if (id <= EcsLastInternalComponentId) { - table->flags |= EcsTableHasBuiltins; - } - - if (id == EcsModule) { - table->flags |= EcsTableHasBuiltins; - table->flags |= EcsTableHasModule; - } else if (id == EcsPrefab) { - table->flags |= EcsTableIsPrefab; - } else if (id == EcsDisabled) { - table->flags |= EcsTableIsDisabled; +static +int32_t find_term_var_id( + ecs_rule_t *rule, + ecs_term_id_t *term_id) +{ + if (term_id_is_variable(term_id)) { + const char *var_name = term_id_var_name(term_id); + ecs_rule_var_t *var = find_variable( + rule, EcsRuleVarKindEntity, var_name); + if (var) { + return var->id; } else { - if (ECS_IS_PAIR(id)) { - ecs_entity_t r = ECS_PAIR_FIRST(id); - - table->flags |= EcsTableHasPairs; - - if (r == EcsIsA) { - table->flags |= EcsTableHasIsA; - } else if (r == EcsChildOf) { - table->flags |= EcsTableHasChildOf; - ecs_entity_t obj = ecs_pair_second(world, id); - ecs_assert(obj != 0, ECS_INTERNAL_ERROR, NULL); - - if (obj == EcsFlecs || obj == EcsFlecsCore || - ecs_has_id(world, obj, EcsModule)) - { - /* If table contains entities that are inside one of the - * builtin modules, it contains builtin entities */ - table->flags |= EcsTableHasBuiltins; - table->flags |= EcsTableHasModule; - } - } else if (r == EcsUnion) { - table->flags |= EcsTableHasUnion; - - if (!table->sw_count) { - table->sw_offset = flecs_ito(int16_t, i); - } - table->sw_count ++; - } else if (r == ecs_id(EcsPoly)) { - table->flags |= EcsTableHasBuiltins; - } - } else { - if (ECS_HAS_ID_FLAG(id, TOGGLE)) { - table->flags |= EcsTableHasToggle; - - if (!table->bs_count) { - table->bs_offset = flecs_ito(int16_t, i); - } - table->bs_count ++; - } - if (ECS_HAS_ID_FLAG(id, OVERRIDE)) { - table->flags |= EcsTableHasOverrides; + /* If this is Any look for table variable. Since Any is only + * required to return a single result, there is no need to + * insert an each instruction for a matching table. */ + if (term_id->id == EcsAny) { + var = find_variable( + rule, EcsRuleVarKindTable, var_name); + if (var) { + return var->id; } } - } - } -} - -static -void flecs_table_append_to_records( - ecs_world_t *world, - ecs_table_t *table, - ecs_vector_t **records, - ecs_id_t id, - int32_t column) -{ - /* To avoid a quadratic search, use the O(1) lookup that the index - * already provides. */ - ecs_id_record_t *idr = flecs_id_record_ensure(world, id); - ecs_table_record_t *tr = (ecs_table_record_t*)flecs_id_record_get_table( - idr, table); - if (!tr) { - tr = ecs_vector_add(records, ecs_table_record_t); - tr->column = column; - tr->count = 1; - - ecs_table_cache_insert(&idr->cache, table, &tr->hdr); - } else { - tr->count ++; + } } - - ecs_assert(tr->hdr.cache != NULL, ECS_INTERNAL_ERROR, NULL); + + return -1; } -void flecs_table_init( +ecs_rule_t* ecs_rule_init( ecs_world_t *world, - ecs_table_t *table, - ecs_table_t *from) + const ecs_filter_desc_t *const_desc) { - flecs_table_init_flags(world, table); + ecs_rule_t *result = ecs_poly_new(ecs_rule_t); - int32_t dst_i = 0, dst_count = table->type.count; - int32_t src_i = 0, src_count = 0; - ecs_id_t *dst_ids = table->type.array; - ecs_id_t *src_ids = NULL; - ecs_table_record_t *tr = NULL, *src_tr = NULL; - if (from) { - src_count = from->type.count; - src_ids = from->type.array; - src_tr = from->records; + /* Initialize the query */ + ecs_filter_desc_t desc = *const_desc; + desc.storage = &result->filter; /* Use storage of rule */ + result->filter = ECS_FILTER_INIT; + if (ecs_filter_init(world, &desc) == NULL) { + goto error; } - /* We don't know in advance how large the records array will be, so use - * cached vector. This eliminates unnecessary allocations, and/or expensive - * iterations to determine how many records we need. */ - ecs_vector_t *records = world->store.records; - ecs_vector_clear(records); - ecs_id_record_t *idr; - - int32_t last_id = -1; /* Track last regular (non-pair) id */ - int32_t first_pair = -1; /* Track the first pair in the table */ - int32_t first_role = -1; /* Track first id with role */ + result->world = world; - /* Scan to find boundaries of regular ids, pairs and roles */ - for (dst_i = 0; dst_i < dst_count; dst_i ++) { - ecs_id_t dst_id = dst_ids[dst_i]; - if (first_pair == -1 && ECS_IS_PAIR(dst_id)) { - first_pair = dst_i; - } - if ((dst_id & ECS_COMPONENT_MASK) == dst_id) { - last_id = dst_i; - } else if (first_role == -1 && !ECS_IS_PAIR(dst_id)) { - first_role = dst_i; - } + /* Rule has no terms */ + if (!result->filter.term_count) { + rule_error(result, "rule has no terms"); + goto error; } - /* The easy part: initialize a record for every id in the type */ - for (dst_i = 0; (dst_i < dst_count) && (src_i < src_count); ) { - ecs_id_t dst_id = dst_ids[dst_i]; - ecs_id_t src_id = src_ids[src_i]; - - idr = NULL; + ecs_term_t *terms = result->filter.terms; + int32_t i, term_count = result->filter.term_count; - if (dst_id == src_id) { - idr = (ecs_id_record_t*)src_tr[src_i].hdr.cache; - } else if (dst_id < src_id) { - idr = flecs_id_record_ensure(world, dst_id); - } - if (idr) { - tr = ecs_vector_add(&records, ecs_table_record_t); - tr->hdr.cache = (ecs_table_cache_t*)idr; - tr->column = dst_i; - tr->count = 1; + /* 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; } - - dst_i += dst_id <= src_id; - src_i += dst_id >= src_id; } - - /* Add remaining ids that the "from" table didn't have */ - for (; (dst_i < dst_count); dst_i ++) { - ecs_id_t dst_id = dst_ids[dst_i]; - tr = ecs_vector_add(&records, ecs_table_record_t); - idr = flecs_id_record_ensure(world, dst_id); - tr->hdr.cache = (ecs_table_cache_t*)idr; - ecs_assert(tr->hdr.cache != NULL, ECS_INTERNAL_ERROR, NULL); - tr->column = dst_i; - tr->count = 1; - } - - /* We're going to insert records from the vector into the index that - * will get patched up later. To ensure the record pointers don't get - * invalidated we need to grow the vector so that it won't realloc as - * we're adding the next set of records */ - if (first_role != -1 || first_pair != -1) { - int32_t start = first_role; - if (first_pair != -1 && (start != -1 || first_pair < start)) { - start = first_pair; - } - - /* Total number of records can never be higher than - * - number of regular (non-pair) ids + - * - three records for pairs: (R,T), (R,*), (*,T) - * - one wildcard (*), one any (_) and one pair wildcard (*,*) record - * - one record for (ChildOf, 0) - */ - int32_t flag_id_count = dst_count - start; - int32_t record_count = start + 3 * flag_id_count + 3 + 1; - ecs_vector_set_min_size(&records, ecs_table_record_t, record_count); + if (i == term_count) { + rule_error(result, "rule cannot only have terms with Not operator"); + goto error; } - /* Add records for ids with roles (used by cleanup logic) */ - if (first_role != -1) { - for (dst_i = first_role; dst_i < dst_count; dst_i ++) { - ecs_id_t id = dst_ids[dst_i]; - if (!ECS_IS_PAIR(id)) { - ecs_entity_t first = 0; - ecs_entity_t second = 0; - if (ECS_HAS_ID_FLAG(id, PAIR)) { - first = ECS_PAIR_FIRST(id); - second = ECS_PAIR_SECOND(id); - } else { - first = id & ECS_COMPONENT_MASK; - } - if (first) { - flecs_table_append_to_records(world, table, &records, - ecs_pair(EcsFlag, first), dst_i); - } - if (second) { - flecs_table_append_to_records(world, table, &records, - ecs_pair(EcsFlag, second), dst_i); - } + /* Translate terms with a Not operator and Wildcard to Any, as we don't need + * implicit variables for those */ + for (i = 0; i < term_count; i ++) { + ecs_term_t *term = &terms[i]; + if (term->oper == EcsNot) { + if (term->first.id == EcsWildcard) { + term->first.id = EcsAny; } - } - } - - int32_t last_pair = -1; - bool has_childof = table->flags & EcsTableHasChildOf; - if (first_pair != -1) { - /* Add a (Relationship, *) record for each relationship. */ - ecs_entity_t r = 0; - for (dst_i = first_pair; dst_i < dst_count; dst_i ++) { - ecs_id_t dst_id = dst_ids[dst_i]; - if (!ECS_IS_PAIR(dst_id)) { - break; /* no more pairs */ + if (term->src.id == EcsWildcard) { + term->src.id = EcsAny; } - if (r != ECS_PAIR_FIRST(dst_id)) { /* New relationship, new record */ - tr = ecs_vector_get(records, ecs_table_record_t, dst_i); - idr = ((ecs_id_record_t*)tr->hdr.cache)->parent; /* (R, *) */ - ecs_assert(idr != NULL, ECS_INTERNAL_ERROR, NULL); - - tr = ecs_vector_add(&records, ecs_table_record_t); - tr->hdr.cache = (ecs_table_cache_t*)idr; - tr->column = dst_i; - tr->count = 0; - r = ECS_PAIR_FIRST(dst_id); + if (term->second.id == EcsWildcard) { + term->second.id = EcsAny; } - - ecs_assert(tr != NULL, ECS_INTERNAL_ERROR, NULL); - tr->count ++; - } - - last_pair = dst_i; - - /* Add a (*, Target) record for each relationship target. Type - * ids are sorted relationship-first, so we can't simply do a single linear - * scan to find all occurrences for a target. */ - for (dst_i = first_pair; dst_i < last_pair; dst_i ++) { - ecs_id_t dst_id = dst_ids[dst_i]; - ecs_id_t tgt_id = ecs_pair(EcsWildcard, ECS_PAIR_SECOND(dst_id)); - - flecs_table_append_to_records( - world, table, &records, tgt_id, dst_i); } } - /* Lastly, add records for all-wildcard ids */ - if (last_id >= 0) { - tr = ecs_vector_add(&records, ecs_table_record_t); - tr->hdr.cache = (ecs_table_cache_t*)world->idr_wildcard; - tr->column = 0; - tr->count = last_id + 1; - } - if (last_pair - first_pair) { - tr = ecs_vector_add(&records, ecs_table_record_t); - tr->hdr.cache = (ecs_table_cache_t*)world->idr_wildcard_wildcard; - tr->column = first_pair; - tr->count = last_pair - first_pair; - } - if (dst_count) { - tr = ecs_vector_add(&records, ecs_table_record_t); - tr->hdr.cache = (ecs_table_cache_t*)world->idr_any; - tr->column = 0; - tr->count = 1; - } - if (dst_count && !has_childof) { - tr = ecs_vector_add(&records, ecs_table_record_t); - tr->hdr.cache = (ecs_table_cache_t*)world->idr_childof_0; - tr->column = 0; - tr->count = 1; + /* Find all variables & resolve dependencies */ + if (scan_variables(result) != 0) { + goto error; } - /* Now that all records have been added, copy them to array */ - int32_t i, dst_record_count = ecs_vector_count(records); - ecs_table_record_t *dst_tr = ecs_os_memdup_n( ecs_vector_first(records, - ecs_table_record_t), ecs_table_record_t, dst_record_count); - table->record_count = flecs_ito(uint16_t, dst_record_count); - table->records = dst_tr; - - /* Register & patch up records */ - for (i = 0; i < dst_record_count; i ++) { - tr = &dst_tr[i]; - idr = (ecs_id_record_t*)dst_tr[i].hdr.cache; - ecs_assert(idr != NULL, ECS_INTERNAL_ERROR, NULL); + /* Create lookup array for subject variables */ + for (i = 0; i < term_count; i ++) { + ecs_term_t *term = &terms[i]; + ecs_rule_term_vars_t *vars = &result->term_vars[i]; + vars->first = find_term_var_id(result, &term->first); + vars->src = find_term_var_id(result, &term->src); + vars->second = find_term_var_id(result, &term->second); + } - if (ecs_table_cache_get(&idr->cache, table)) { - /* If this is a target wildcard record it has already been - * registered, but the record is now at a different location in - * memory. Patch up the linked list with the new address */ - ecs_table_cache_replace(&idr->cache, table, &tr->hdr); - } else { - /* Other records are not registered yet */ - ecs_assert(idr != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_table_cache_insert(&idr->cache, table, &tr->hdr); - } + /* Generate the opcode array */ + compile_program(result); - /* Claim id record so it stays alive as long as the table exists */ - flecs_id_record_claim(world, idr); + /* 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); - /* Initialize event flags */ - table->flags |= idr->flags & EcsIdEventMask; - } + /* Create cross-references between variables so it's easy to go from entity + * to table variable and vice versa */ + create_variable_cross_references(result); - world->store.records = records; + result->iterable.init = rule_iter_init; - flecs_table_init_storage_table(world, table); - flecs_table_init_data(table); + return result; +error: + ecs_rule_fini(result); + return NULL; } -static -void flecs_table_records_unregister( - ecs_world_t *world, - ecs_table_t *table) +void ecs_rule_fini( + ecs_rule_t *rule) { - int32_t i, count = table->record_count; - for (i = 0; i < count; i ++) { - ecs_table_record_t *tr = &table->records[i]; - ecs_table_cache_t *cache = tr->hdr.cache; - ecs_id_t id = ((ecs_id_record_t*)cache)->id; + int32_t i; + for (i = 0; i < rule->var_count; i ++) { + ecs_os_free(rule->vars[i].name); + } - ecs_assert(tr->hdr.cache == cache, ECS_INTERNAL_ERROR, NULL); - ecs_assert(tr->hdr.table == table, ECS_INTERNAL_ERROR, NULL); - ecs_assert(flecs_id_record_get(world, id) == (ecs_id_record_t*)cache, - ECS_INTERNAL_ERROR, NULL); - (void)id; + ecs_filter_fini(&rule->filter); - ecs_table_cache_remove(cache, table, &tr->hdr); - flecs_id_record_release(world, (ecs_id_record_t*)cache); - } - - ecs_os_free(table->records); + ecs_os_free(rule->operations); + ecs_os_free(rule); } -static -void notify_trigger( - ecs_world_t *world, - ecs_table_t *table, - ecs_entity_t event) +const ecs_filter_t* ecs_rule_get_filter( + const ecs_rule_t *rule) { - (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 &rule->filter; } +/* Quick convenience function to get a variable from an id */ static -void run_on_remove( - ecs_world_t *world, - ecs_table_t *table, - ecs_data_t *data) +ecs_rule_var_t* get_variable( + const ecs_rule_t *rule, + int32_t var_id) { - int32_t count = data->entities.count; - if (count) { - ecs_table_diff_t diff = { - .removed = table->type, - .un_set = table->type - }; - - flecs_notify_on_remove(world, table, NULL, 0, count, &diff); + if (var_id == UINT8_MAX) { + return NULL; } -} -/* -- Private functions -- */ + return (ecs_rule_var_t*)&rule->vars[var_id]; +} -static -void on_component_callback( - ecs_world_t *world, - ecs_table_t *table, - ecs_iter_action_t callback, - ecs_entity_t event, - ecs_column_t *column, - ecs_entity_t *entities, - ecs_id_t id, - int32_t row, - int32_t count, - ecs_type_info_t *ti) +/* 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_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_iter_t it = { .field_count = 1 }; - it.entities = entities; + 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]; - ecs_size_t size = ti->size; - void *ptr = ecs_storage_get(column, size, row); + 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 first = pair.first.id; + ecs_entity_t second = pair.second.id; + const char *pred_name = NULL, *obj_name = NULL; + char *pred_name_alloc = NULL, *obj_name_alloc = NULL; - flecs_iter_init(&it, flecs_iter_cache_all); - it.world = world; - it.real_world = world; - it.table = table; - it.ptrs[0] = ptr; - it.sizes[0] = size; - it.ids[0] = id; - it.event = event; - it.event_id = id; - it.ctx = ti->hooks.ctx; - it.binding_ctx = ti->hooks.binding_ctx; - it.count = count; - flecs_iter_validate(&it); - callback(&it); -} + if (pair.reg_mask & RULE_PAIR_PREDICATE) { + ecs_assert(rule->vars != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_rule_var_t *type_var = &rule->vars[pair.first.reg]; + pred_name = type_var->name; + } else if (first) { + pred_name_alloc = ecs_get_fullpath(world, ecs_get_alive(world, first)); + pred_name = pred_name_alloc; + } -static -void ctor_component( - ecs_type_info_t *ti, - ecs_column_t *column, - int32_t row, - int32_t count) -{ - ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); + if (pair.reg_mask & RULE_PAIR_OBJECT) { + ecs_assert(rule->vars != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_rule_var_t *obj_var = &rule->vars[pair.second.reg]; + obj_name = obj_var->name; + } else if (second) { + obj_name_alloc = ecs_get_fullpath(world, ecs_get_alive(world, second)); + obj_name = obj_name_alloc; + } else if (pair.second_0) { + obj_name = "0"; + } - ecs_xtor_t ctor = ti->hooks.ctor; - if (ctor) { - void *ptr = ecs_storage_get(column, ti->size, row); - ctor(ptr, count, ti); - } -} + ecs_strbuf_append(&buf, "%2d: [S:%2d, P:%2d, F:%2d, T:%2d] ", i, + op->frame, op->on_pass, op->on_fail, op->term); -static -void add_component( - ecs_world_t *world, - ecs_table_t *table, - ecs_type_info_t *ti, - ecs_column_t *column, - ecs_entity_t *entities, - ecs_id_t id, - int32_t row, - int32_t count, - bool construct) -{ - ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); + bool has_filter = false; - if (construct) { - ctor_component(ti, column, row, count); - } + 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 EcsRuleInTable: + ecs_strbuf_append(&buf, "intable "); + has_filter = true; + break; + case EcsRuleEq: + ecs_strbuf_append(&buf, "eq "); + has_filter = true; + break; + case EcsRuleYield: + ecs_strbuf_append(&buf, "yield "); + break; + default: + continue; + } - ecs_iter_action_t on_add = ti->hooks.on_add; - if (on_add) { - on_component_callback(world, table, on_add, EcsOnAdd, column, - entities, id, row, count, ti); + 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 (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); + } + 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); + } + } + + if (has_filter) { + if (!pred_name) { + pred_name = "-"; + } + if (!obj_name && !pair.second_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); } + + return ecs_strbuf_get(&buf); +error: + return NULL; } -static -void dtor_component( - ecs_type_info_t *ti, - ecs_column_t *column, - int32_t row, - int32_t count) +/* Public function that returns number of variables. This enables an application + * to iterate the variables and obtain their values. */ +int32_t ecs_rule_var_count( + const ecs_rule_t *rule) { - ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(rule != NULL, ECS_INTERNAL_ERROR, NULL); + return rule->var_count; +} - ecs_xtor_t dtor = ti->hooks.dtor; - if (dtor) { - void *ptr = ecs_storage_get(column, ti->size, row); - dtor(ptr, count, ti); +/* Public function to find a variable by name */ +int32_t ecs_rule_find_var( + 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; } } -static -void remove_component( - ecs_world_t *world, - ecs_table_t *table, - ecs_type_info_t *ti, - ecs_column_t *column, - ecs_entity_t *entities, - ecs_id_t id, - int32_t row, - int32_t count) +/* Public function to get the name of a variable. */ +const char* ecs_rule_var_name( + const ecs_rule_t *rule, + int32_t var_id) { - ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); + return rule->vars[var_id].name; +} - ecs_iter_action_t on_remove = ti->hooks.on_remove; - if (on_remove) { - on_component_callback(world, table, on_remove, EcsOnRemove, column, - entities, id, row, count, ti); - } - - dtor_component(ti, column, row, count); +/* Public function to get the type of a variable. */ +bool ecs_rule_var_is_entity( + const ecs_rule_t *rule, + int32_t var_id) +{ + return rule->vars[var_id].kind == EcsRuleVarKindEntity; } static -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) +void ecs_rule_iter_free( + ecs_iter_t *iter) { - /* Can't delete and not update the entity index */ - ecs_assert(!is_delete || update_entity_index, ECS_INTERNAL_ERROR, NULL); + ecs_rule_iter_t *it = &iter->priv.iter.rule; + ecs_os_free(it->registers); + ecs_os_free(it->columns); + ecs_os_free(it->op_ctx); + iter->columns = NULL; + it->registers = NULL; + it->columns = NULL; + it->op_ctx = NULL; +} - ecs_id_t *ids = table->storage_ids; - int32_t ids_count = table->storage_count; - ecs_record_t **records = data->records.array; - ecs_entity_t *entities = data->entities.array; - int32_t i, c, end = row + 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); - (void)records; + ecs_iter_t result = {0}; + int i; - /* If table has components with destructors, iterate component columns */ - if (table->flags & EcsTableHasDtors) { - /* Throw up a lock just to be sure */ - table->lock = true; + result.world = (ecs_world_t*)world; + result.real_world = (ecs_world_t*)ecs_get_world(rule->world); - /* Run on_remove callbacks first before destructing components */ - for (c = 0; c < ids_count; c++) { - ecs_column_t *column = &data->columns[c]; - ecs_type_info_t *ti = table->type_info[c]; - ecs_iter_action_t on_remove = ti->hooks.on_remove; - if (on_remove) { - on_component_callback(world, table, on_remove, EcsOnRemove, - column, &entities[row], ids[c], row, count, ti); - } - } + flecs_process_pending_tables(result.real_world); - /* Destruct components */ - for (c = 0; c < ids_count; c++) { - dtor_component(table->type_info[c], &data->columns[c], row, count); - } + ecs_rule_iter_t *it = &result.priv.iter.rule; + it->rule = rule; - /* Iterate entities first, then components. This ensures that only one - * entity is invalidated at a time, which ensures that destructors can - * safely access other entities. */ - for (i = row; i < end; i ++) { - /* Update entity index after invoking destructors so that entity can - * be safely used in destructor callbacks. */ - if (update_entity_index) { - ecs_entity_t e = entities[i]; - ecs_assert(!e || ecs_is_valid(world, e), - ECS_INTERNAL_ERROR, NULL); - ecs_assert(!e || records[i] == flecs_entities_get(world, e), - ECS_INTERNAL_ERROR, NULL); - ecs_assert(!e || records[i]->table == table, - ECS_INTERNAL_ERROR, NULL); + if (rule->operation_count) { + if (rule->var_count) { + it->registers = ecs_os_malloc_n(ecs_var_t, + rule->operation_count * rule->var_count); + } + + it->op_ctx = ecs_os_calloc_n(ecs_rule_op_ctx_t, rule->operation_count); - if (is_delete) { - flecs_entities_remove(world, e); - ecs_assert(ecs_is_valid(world, e) == false, - ECS_INTERNAL_ERROR, NULL); - } else { - // If this is not a delete, clear the entity index record - records[i]->table = NULL; - records[i]->row = 0; - } - } else { - /* This should only happen in rare cases, such as when the data - * cleaned up is not part of the world (like with snapshots) */ - } + if (rule->filter.term_count) { + it->columns = ecs_os_malloc_n(int32_t, + rule->operation_count * rule->filter.term_count); } - table->lock = false; + for (i = 0; i < rule->filter.term_count; i ++) { + it->columns[i] = -1; + } + } - /* 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] == flecs_entities_get(world, e), - ECS_INTERNAL_ERROR, NULL); - ecs_assert(!e || records[i]->table == table, - ECS_INTERNAL_ERROR, NULL); + it->op = 0; - flecs_entities_remove(world, e); - ecs_assert(!ecs_is_valid(world, e), ECS_INTERNAL_ERROR, NULL); - } + for (i = 0; i < rule->var_count; i ++) { + if (rule->vars[i].kind == EcsRuleVarKindEntity) { + entity_reg_set(rule, it->registers, i, EcsWildcard); } 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] == flecs_entities_get(world, e), - ECS_INTERNAL_ERROR, NULL); - ecs_assert(!e || records[i]->table == table, - ECS_INTERNAL_ERROR, NULL); - records[i]->table = NULL; - records[i]->row = records[i]->row & ECS_ROW_FLAGS_MASK; - (void)e; - } - } + table_reg_set(rule, it->registers, i, NULL); + } } + + result.variable_names = (char**)rule->var_names; + result.variable_count = rule->var_count; + result.field_count = rule->filter.term_count; + result.terms = rule->filter.terms; + result.next = ecs_rule_next; + result.fini = ecs_rule_iter_free; + ECS_BIT_COND(result.flags, EcsIterIsFilter, + ECS_BIT_IS_SET(rule->filter.flags, EcsFilterIsFilter)); + + flecs_iter_init(&result, + flecs_iter_cache_ids | + /* flecs_iter_cache_columns | provided by rule iterator */ + flecs_iter_cache_sources | + flecs_iter_cache_sizes | + flecs_iter_cache_ptrs | + /* flecs_iter_cache_match_indices | not necessary for iteration */ + flecs_iter_cache_variables); + + result.columns = it->columns; /* prevent alloc */ + + return result; } +/* 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 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) +int32_t find_next_same_var( + ecs_type_t type, + int32_t column, + ecs_id_t pattern) { - ecs_assert(!table->lock, ECS_LOCKED_STORAGE, NULL); + /* 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 second are the same */ + ecs_id_t *ids = type.array; + int32_t i, count = type.count; + for (i = column + 1; i < count; i ++) { + ecs_id_t id = ids[i]; + if (!ECS_HAS_ID_FLAG(id, PAIR)) { + /* If id is not a pair, this will definitely not match, and we + * will find no further matches. */ + return -1; + } - if (!data) { - return; + if (ECS_PAIR_FIRST(id) == ECS_PAIR_SECOND(id)) { + /* Found a match! */ + return i; + } } - ecs_flags32_t flags = table->flags; + /* No pairs found with same rel/second */ + return -1; +} - if (do_on_remove && (flags & EcsTableHasOnRemove)) { - run_on_remove(world, table, data); +static +int32_t find_next_column( + const ecs_world_t *world, + const ecs_table_t *table, + int32_t column, + ecs_rule_filter_t *filter) +{ + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_entity_t pattern = filter->mask; + ecs_type_t type = table->type; + + if (column == -1) { + ecs_table_record_t *tr = flecs_table_record_get(world, table, pattern); + if (!tr) { + return -1; + } + column = tr->column; + } else { + column = ecs_search_offset(world, table, column + 1, filter->mask, 0); + if (column == -1) { + return -1; + } } - int32_t count = flecs_table_data_count(data); - if (count) { - dtor_all_components(world, table, data, 0, count, - update_entity_index, is_delete); + if (filter->same_var) { + column = find_next_same_var(type, column - 1, filter->mask); } - /* Sanity check */ - ecs_assert(data->records.count == - data->entities.count, ECS_INTERNAL_ERROR, NULL); + return column; +} - ecs_column_t *columns = data->columns; - if (columns) { - int32_t c, column_count = table->storage_count; - for (c = 0; c < column_count; c ++) { - /* Sanity check */ - ecs_assert(columns[c].count == data->entities.count, - ECS_INTERNAL_ERROR, NULL); +/* 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_table_cache_iter_t *it = &op_ctx->it; + ecs_table_t *table = NULL; + int32_t column = -1; - ecs_storage_fini(&columns[c]); - } - ecs_os_free(columns); - data->columns = NULL; - } + const ecs_table_record_t *tr; + while ((column == -1) && (tr = flecs_table_cache_next(it, ecs_table_record_t))) { + table = tr->hdr.table; - ecs_switch_t *sw_columns = data->sw_columns; - if (sw_columns) { - int32_t c, column_count = table->sw_count; - for (c = 0; c < column_count; c ++) { - flecs_switch_fini(&sw_columns[c]); - } - ecs_os_free(sw_columns); - data->sw_columns = NULL; - } + /* Should only iterate non-empty tables */ + ecs_assert(ecs_table_count(table) != 0, ECS_INTERNAL_ERROR, NULL); - ecs_bitset_t *bs_columns = data->bs_columns; - if (bs_columns) { - int32_t c, column_count = table->bs_count; - for (c = 0; c < column_count; c ++) { - flecs_bitset_fini(&bs_columns[c]); + column = tr->column; + if (filter->same_var) { + column = find_next_same_var(table->type, column - 1, filter->mask); } - ecs_os_free(bs_columns); - data->bs_columns = NULL; } - ecs_storage_fini(&data->entities); - ecs_storage_fini(&data->records); - - if (deactivate && count) { - flecs_table_set_empty(world, table); + if (column == -1) { + table = NULL; } - table->observed_count = 0; + return (ecs_table_record_t){.hdr.table = table, .column = column}; } -/* 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); -} -/* Cleanup, no OnRemove, clear entity index, deactivate table */ -void flecs_table_clear_entities_silent( +static +ecs_id_record_t* find_tables( ecs_world_t *world, - ecs_table_t *table) + ecs_id_t id) { - fini_data(world, table, &table->data, false, true, false, true); + ecs_id_record_t *idr = flecs_id_record_get(world, id); + if (!idr || !flecs_table_cache_count(&idr->cache)) { + /* Skip ids that don't have (non-empty) tables */ + return NULL; + } + return idr; } -/* Cleanup, run OnRemove, clear entity index, deactivate table */ -void flecs_table_clear_entities( - ecs_world_t *world, - ecs_table_t *table) +static +ecs_id_t rule_get_column( + ecs_type_t type, + int32_t column) { - fini_data(world, table, &table->data, true, true, false, true); + ecs_assert(column < type.count, ECS_INTERNAL_ERROR, NULL); + ecs_id_t *comp = &type.array[column]; + return *comp; } -/* Cleanup, run OnRemove, delete from entity index, deactivate table */ -void flecs_table_delete_entities( - ecs_world_t *world, - ecs_table_t *table) +static +void set_source( + ecs_iter_t *it, + ecs_rule_op_t *op, + ecs_var_t *regs, + int32_t r) { - fini_data(world, table, &table->data, true, true, true, true); -} + if (op->term == -1) { + /* If operation is not associated with a term, don't set anything */ + return; + } -/* 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->data); + ecs_assert(op->term >= 0, ECS_INTERNAL_ERROR, NULL); + + const ecs_rule_t *rule = it->priv.iter.rule.rule; + if ((r != UINT8_MAX) && rule->vars[r].kind == EcsRuleVarKindEntity) { + it->sources[op->term] = reg_get_entity(rule, op, regs, r); + } else { + it->sources[op->term] = 0; + } } -/* Free table resources. */ -void flecs_table_free( - ecs_world_t *world, - ecs_table_t *table) +static +void set_term_vars( + const ecs_rule_t *rule, + ecs_var_t *regs, + int32_t term, + ecs_id_t id) { - bool is_root = table == &world->store.root; - ecs_assert(!table->lock, ECS_LOCKED_STORAGE, NULL); - ecs_assert(is_root || table->id != 0, ECS_INTERNAL_ERROR, NULL); - ecs_assert(is_root || flecs_sparse_is_alive(&world->store.tables, table->id), - ECS_INTERNAL_ERROR, NULL); - (void)world; + if (term != -1) { + ecs_world_t *world = rule->world; + const ecs_rule_term_vars_t *vars = &rule->term_vars[term]; + if (vars->first != -1) { + regs[vars->first].entity = ecs_pair_first(world, id); + ecs_assert(ecs_is_valid(world, regs[vars->first].entity), + ECS_INTERNAL_ERROR, NULL); + } + if (vars->second != -1) { + regs[vars->second].entity = ecs_pair_second(world, id); + ecs_assert(ecs_is_valid(world, regs[vars->second].entity), + ECS_INTERNAL_ERROR, NULL); + } + } +} - ecs_assert(table->refcount == 0, ECS_INTERNAL_ERROR, NULL); +/* 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; - if (!is_root) { - flecs_notify_queries( - world, &(ecs_query_event_t){ - .kind = EcsQueryTableUnmatch, - .table = 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 (ecs_should_log_2()) { - char *expr = ecs_type_str(world, &table->type); - ecs_dbg_2( - "#[green]table#[normal] [%s] #[red]deleted#[reset] with id %d", - expr, table->id); - ecs_os_free(expr); - ecs_log_push_2(); - } +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->priv.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_var_t *regs = get_registers(iter, op); - world->info.empty_table_count -= (ecs_table_count(table) == 0); + /* Get register indices for output */ + int32_t sp; + int32_t r = op->r_out; - /* Cleanup data, no OnRemove, delete from entity index, don't deactivate */ - fini_data(world, table, &table->data, false, true, true, false); + /* Register cannot be a literal, since we need to store things in it */ + ecs_assert(r != UINT8_MAX, ECS_INTERNAL_ERROR, NULL); - flecs_table_clear_edges(world, table); + /* Get queried for id, fill out potential variables */ + ecs_rule_pair_t pair = op->filter; - if (!is_root) { - ecs_type_t ids = { - .array = table->type.array, - .count = table->type.count - }; + ecs_rule_filter_t filter = pair_to_filter(iter, op, pair); + ecs_entity_t rel = ECS_PAIR_FIRST(filter.mask); + ecs_rule_filter_t super_filter = { + .mask = ecs_pair(rel, EcsWildcard) + }; + ecs_table_t *table = NULL; - flecs_hashmap_remove(&world->store.table_map, &ids, ecs_table_t*); + /* Check if input register is constrained */ + ecs_entity_t result = iter->registers[r].entity; + bool output_is_input = ecs_iter_var_is_constrained(it, r); + if (output_is_input && !redo) { + ecs_assert(regs[r].entity == iter->registers[r].entity, + ECS_INTERNAL_ERROR, NULL); } - ecs_os_free(table->dirty_state); - ecs_os_free(table->storage_map); - - flecs_table_records_unregister(world, table); + if (!redo) { + op_ctx->stack = op_ctx->storage; + sp = op_ctx->sp = 0; + frame = &op_ctx->stack[sp]; - ecs_table_t *storage_table = table->storage_table; - if (storage_table == table) { - if (table->type_info) { - ecs_os_free(table->type_info); + /* Get table of object for which to get supersets */ + ecs_entity_t second = ECS_PAIR_SECOND(filter.mask); + if (second == EcsWildcard) { + ecs_assert(pair.reg_mask & RULE_PAIR_OBJECT, + ECS_INTERNAL_ERROR, NULL); + table = regs[pair.second.reg].range.table; + } else { + table = table_from_entity(world, second).table; } - } else if (storage_table) { - flecs_table_release(world, storage_table); - } - /* Update counters */ - world->info.table_count --; - world->info.table_record_count -= table->record_count; - world->info.table_storage_count -= table->storage_count; - world->info.table_delete_total ++; + int32_t column; - if (!table->storage_count) { - world->info.tag_table_count --; - } else { - world->info.trivial_table_count -= !(table->flags & EcsTableIsComplex); - } + /* If output variable is already set, check if it matches */ + if (output_is_input) { + ecs_id_t id = ecs_pair(rel, result); + ecs_entity_t src = 0; + column = ecs_search_relation(world, table, 0, id, rel, + 0, &src, 0, 0); + if (column != -1) { + if (src != 0) { + table = ecs_get_table(world, src); + } + } + } else { + column = find_next_column(world, table, -1, &super_filter); + } - if (!(world->flags & EcsWorldFini)) { - ecs_assert(!is_root, ECS_INTERNAL_ERROR, NULL); - flecs_table_free_type(table); - flecs_sparse_remove(&world->store.tables, table->id); - } + /* If no matching column was found, there are no supersets */ + if (column == -1) { + return false; + } - ecs_log_pop_2(); -} + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); -void flecs_table_claim( - ecs_world_t *world, - ecs_table_t *table) -{ - ecs_poly_assert(world, ecs_world_t); - ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(table->refcount > 0, ECS_INTERNAL_ERROR, NULL); - table->refcount ++; - (void)world; -} + ecs_id_t col_id = rule_get_column(table->type, column); + ecs_assert(ECS_HAS_ID_FLAG(col_id, PAIR), ECS_INTERNAL_ERROR, NULL); + ecs_entity_t col_obj = ecs_pair_second(world, col_id); -bool flecs_table_release( - ecs_world_t *world, - ecs_table_t *table) -{ - ecs_poly_assert(world, ecs_world_t); - ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(table->refcount > 0, ECS_INTERNAL_ERROR, NULL); + reg_set_entity(rule, regs, r, col_obj); + + frame->table = table; + frame->column = column; - if (--table->refcount == 0) { - flecs_table_free(world, table); return true; + } else if (output_is_input) { + return false; } - - return false; -} -/* 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_os_free(table->type.array); -} + sp = op_ctx->sp; + frame = &op_ctx->stack[sp]; + table = frame->table; + int32_t column = frame->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); -} + ecs_id_t col_id = rule_get_column(table->type, column); + ecs_entity_t col_obj = ecs_pair_second(world, col_id); + ecs_table_t *next_table = table_from_entity(world, col_obj).table; -static -void mark_table_dirty( - ecs_world_t *world, - ecs_table_t *table, - int32_t index) -{ - (void)world; - if (table->dirty_state) { - table->dirty_state[index] ++; + if (next_table) { + sp ++; + frame = &op_ctx->stack[sp]; + frame->table = next_table; + frame->column = -1; } -} -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); + do { + frame = &op_ctx->stack[sp]; + table = frame->table; + column = frame->column; - if (table->dirty_state) { - int32_t index = ecs_search(world, table->storage_table, component, 0); - ecs_assert(index != -1, ECS_INTERNAL_ERROR, NULL); - table->dirty_state[index + 1] ++; - } + column = find_next_column(world, table, column, &super_filter); + if (column != -1) { + op_ctx->sp = sp; + frame->column = column; + col_id = rule_get_column(table->type, column); + col_obj = ecs_pair_second(world, col_id); + reg_set_entity(rule, regs, r, col_obj); + return true; + } + + sp --; + } while (sp >= 0); + + return false; } static -void move_switch_columns( - ecs_table_t *dst_table, - int32_t dst_index, - ecs_table_t *src_table, - int32_t src_index, - int32_t count, - bool clear) +bool eval_subset( + ecs_iter_t *it, + ecs_rule_op_t *op, + int32_t op_index, + bool redo) { - int32_t i_old = 0, src_column_count = src_table->sw_count; - int32_t i_new = 0, dst_column_count = dst_table->sw_count; - - if (!src_column_count && !dst_column_count) { - return; - } + ecs_rule_iter_t *iter = &it->priv.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_var_t *regs = get_registers(iter, op); - ecs_switch_t *src_columns = src_table->data.sw_columns; - ecs_switch_t *dst_columns = dst_table->data.sw_columns; + /* 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_type_t dst_type = dst_table->type; - ecs_type_t src_type = src_table->type; + /* 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; - int32_t offset_new = dst_table->sw_offset; - int32_t offset_old = src_table->sw_offset; + 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_id_t *dst_ids = dst_type.array; - ecs_id_t *src_ids = src_type.array; + flecs_table_cache_iter(&idr->cache, &frame->with_ctx.it); + table_record = find_next_table(&filter, &frame->with_ctx); + + /* If first table set has no non-empty table, yield nothing */ + if (!table_record.hdr.table) { + return false; + } - for (; (i_new < dst_column_count) && (i_old < src_column_count);) { - ecs_entity_t dst_id = dst_ids[i_new + offset_new]; - ecs_entity_t src_id = src_ids[i_old + offset_old]; + frame->row = 0; + frame->column = table_record.column; + table_reg_set(rule, regs, r, (frame->table = table_record.hdr.table)); + goto yield; + } - if (dst_id == src_id) { - ecs_switch_t *src_switch = &src_columns[i_old]; - ecs_switch_t *dst_switch = &dst_columns[i_new]; + do { + sp = op_ctx->sp; + frame = &op_ctx->stack[sp]; + table = frame->table; + row = frame->row; - flecs_switch_ensure(dst_switch, dst_index + count); + /* 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); - int i; - for (i = 0; i < count; i ++) { - uint64_t value = flecs_switch_get(src_switch, src_index + i); - flecs_switch_set(dst_switch, dst_index + i, value); - } + if (table_record.hdr.table) { + table = frame->table = table_record.hdr.table; + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + frame->row = 0; + frame->column = table_record.column; + table_reg_set(rule, regs, r, table); + goto yield; + } 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 (clear) { - ecs_assert(count == flecs_switch_count(src_switch), - ECS_INTERNAL_ERROR, NULL); - flecs_switch_clear(src_switch); + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(idr != NULL, ECS_INTERNAL_ERROR, NULL); } - } else if (dst_id > src_id) { - ecs_switch_t *src_switch = &src_columns[i_old]; - flecs_switch_clear(src_switch); - } - - i_new += dst_id <= src_id; - i_old += dst_id >= src_id; - } - - /* Clear remaining columns */ - if (clear) { - for (; (i_old < src_column_count); i_old ++) { - ecs_switch_t *src_switch = &src_columns[i_old]; - ecs_assert(count == flecs_switch_count(src_switch), - ECS_INTERNAL_ERROR, NULL); - flecs_switch_clear(src_switch); } - } -} - -static -void move_bitset_columns( - ecs_table_t *dst_table, - int32_t dst_index, - ecs_table_t *src_table, - int32_t src_index, - int32_t count, - bool clear) -{ - int32_t i_old = 0, src_column_count = src_table->bs_count; - int32_t i_new = 0, dst_column_count = dst_table->bs_count; - - if (!src_column_count && !dst_column_count) { - return; - } - ecs_bitset_t *src_columns = src_table->data.bs_columns; - ecs_bitset_t *dst_columns = dst_table->data.bs_columns; + int32_t row_count = ecs_table_count(table); - ecs_type_t dst_type = dst_table->type; - ecs_type_t src_type = src_table->type; + /* Table must have at least row elements */ + ecs_assert(row_count > row, ECS_INTERNAL_ERROR, NULL); - int32_t offset_new = dst_table->bs_offset; - int32_t offset_old = src_table->bs_offset; + ecs_entity_t *entities = ecs_storage_first(&table->data.entities); + ecs_assert(entities != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_id_t *dst_ids = dst_type.array; - ecs_id_t *src_ids = src_type.array; + /* The entity used to find the next table set */ + do { + ecs_entity_t e = entities[row]; - for (; (i_new < dst_column_count) && (i_old < src_column_count);) { - ecs_id_t dst_id = dst_ids[i_new + offset_new]; - ecs_id_t src_id = src_ids[i_old + offset_old]; + /* 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.second.id = e; + filter = pair_to_filter(iter, op, pair); - if (dst_id == src_id) { - ecs_bitset_t *src_bs = &src_columns[i_old]; - ecs_bitset_t *dst_bs = &dst_columns[i_new]; + /* Find table set for expression */ + table = NULL; + idr = find_tables(world, filter.mask); - flecs_bitset_ensure(dst_bs, dst_index + count); + /* 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; + flecs_table_cache_iter(&idr->cache, &new_frame->with_ctx.it); + table_record = find_next_table(&filter, &new_frame->with_ctx); - int i; - for (i = 0; i < count; i ++) { - uint64_t value = flecs_bitset_get(src_bs, src_index + i); - flecs_bitset_set(dst_bs, dst_index + i, value); + /* If set contains non-empty table, push it to stack */ + if (table_record.hdr.table) { + table = table_record.hdr.table; + op_ctx->sp ++; + new_frame->table = table; + new_frame->row = 0; + new_frame->column = table_record.column; + frame = new_frame; + } } - if (clear) { - ecs_assert(count == flecs_bitset_count(src_bs), - ECS_INTERNAL_ERROR, NULL); - flecs_bitset_fini(src_bs); + /* If no table was found for the current entity, advance row */ + if (!table) { + row = ++ frame->row; } - } else if (dst_id > src_id) { - ecs_bitset_t *src_bs = &src_columns[i_old]; - flecs_bitset_fini(src_bs); - } + } while (!table && row < row_count); + } while (!table); - i_new += dst_id <= src_id; - i_old += dst_id >= src_id; - } + table_reg_set(rule, regs, r, table); - /* Clear remaining columns */ - if (clear) { - for (; (i_old < src_column_count); i_old ++) { - ecs_bitset_t *src_bs = &src_columns[i_old]; - ecs_assert(count == flecs_bitset_count(src_bs), - ECS_INTERNAL_ERROR, NULL); - flecs_bitset_fini(src_bs); - } - } +yield: + set_term_vars(rule, regs, op->term, frame->table->type.array[frame->column]); + + return true; } +/* Select operation. The select operation finds and iterates a table set that + * corresponds to its pair expression. */ static -void* grow_column( - ecs_column_t *column, - ecs_type_info_t *ti, - int32_t to_add, - int32_t dst_size, - bool construct) -{ - ecs_assert(column != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); +bool eval_select( + ecs_iter_t *it, + ecs_rule_op_t *op, + int32_t op_index, + bool redo) +{ + ecs_rule_iter_t *iter = &it->priv.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_var_t *regs = get_registers(iter, op); - int32_t size = ti->size; - int32_t count = column->count; - int32_t src_size = column->size; - int32_t dst_count = count + to_add; - bool can_realloc = dst_size != src_size; - void *result = NULL; + /* Get register indices for output */ + int32_t r = op->r_out; + ecs_assert(r != UINT8_MAX, ECS_INTERNAL_ERROR, NULL); - ecs_assert(dst_size >= dst_count, ECS_INTERNAL_ERROR, 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); - /* If the array could possibly realloc and the component has a move action - * defined, move old elements manually */ - ecs_move_t move_ctor; - if (count && can_realloc && (move_ctor = ti->hooks.ctor_move_dtor)) { - ecs_xtor_t ctor = ti->hooks.ctor; - ecs_assert(ctor != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(move_ctor != NULL, ECS_INTERNAL_ERROR, NULL); + int32_t column = -1; + ecs_table_t *table = NULL; + ecs_id_record_t *idr; - /* Create vector */ - ecs_column_t dst; - ecs_storage_init(&dst, size, dst_size); - dst.count = dst_count; + if (!redo && op->term != -1) { + columns[op->term] = -1; + } - void *src_buffer = column->array; - void *dst_buffer = dst.array; + /* 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 */ - /* Move (and construct) existing elements to new vector */ - move_ctor(dst_buffer, src_buffer, count, ti); + idr = op_ctx->idr = find_tables(world, pattern); + } - if (construct) { - /* Construct new element(s) */ - result = ECS_ELEM(dst_buffer, size, count); - ctor(result, to_add, ti); - } + /* If no table set was found for queried for entity, there are no results */ + if (!idr) { + return false; + } - /* Free old vector */ - ecs_storage_fini(column); + /* If the input register is not NULL, this is a variable that's been set by + * the application. */ + table = iter->registers[r].range.table; + bool output_is_input = table != NULL; - *column = dst; - } else { - /* If array won't realloc or has no move, simply add new elements */ - if (can_realloc) { - ecs_storage_set_size(column, size, dst_size); - } + if (output_is_input && !redo) { + ecs_assert(regs[r].range.table == iter->registers[r].range.table, + ECS_INTERNAL_ERROR, NULL); - result = ecs_storage_grow(column, size, to_add); + table = iter->registers[r].range.table; - ecs_xtor_t ctor; - if (construct && (ctor = ti->hooks.ctor)) { - /* If new elements need to be constructed and component has a - * constructor, construct */ - ctor(result, to_add, ti); + /* Check if table can be found in the id record. If not, the provided + * table does not match with the query. */ + ecs_table_record_t *tr = ecs_table_cache_get(&idr->cache, table); + if (!tr) { + return false; } - } - - ecs_assert(column->size == dst_size, ECS_INTERNAL_ERROR, NULL); - return result; -} + column = op_ctx->column = tr->column; + } -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); + /* If this is not a redo, start at the beginning */ + if (!redo) { + if (!table) { + flecs_table_cache_iter(&idr->cache, &op_ctx->it); - int32_t cur_count = flecs_table_data_count(data); - int32_t column_count = table->storage_count; - int32_t sw_count = table->sw_count; - int32_t bs_count = table->bs_count; - ecs_column_t *columns = data->columns; - ecs_switch_t *sw_columns = data->sw_columns; - ecs_bitset_t *bs_columns = data->bs_columns; + /* 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.hdr.table) { + return false; + } - /* Add record to record ptr array */ - ecs_storage_set_size_t(&data->records, ecs_record_t*, size); - ecs_record_t **r = ecs_storage_last_t(&data->records, ecs_record_t*) + 1; - data->records.count += to_add; - if (data->records.size > size) { - size = data->records.size; - } + table = table_record.hdr.table; - /* Add entity to column with entity ids */ - ecs_storage_set_size_t(&data->entities, ecs_entity_t, size); - ecs_entity_t *e = ecs_storage_last_t(&data->entities, ecs_entity_t) + 1; - data->entities.count += to_add; - ecs_assert(data->entities.size == size, ECS_INTERNAL_ERROR, NULL); + /* Set current column to first occurrence of queried for entity */ + column = op_ctx->column = table_record.column; - /* Initialize entity ids and record ptrs */ - int32_t i; - if (ids) { - for (i = 0; i < to_add; i ++) { - e[i] = ids[i]; + /* Store table in register */ + table_reg_set(rule, regs, r, table); } + + /* If this is a redo, progress to the next match */ } 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); + /* 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).table; + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); - /* Add elements to each column array */ - ecs_type_info_t **type_info = table->type_info; - for (i = 0; i < column_count; i ++) { - ecs_column_t *column = &columns[i]; - ecs_type_info_t *ti = type_info[i]; - grow_column(column, ti, to_add, size, true); - ecs_assert(columns[i].size == size, ECS_INTERNAL_ERROR, NULL); - } + column = op_ctx->column; + column = find_next_column(world, table, column, &filter); + op_ctx->column = column; + } - /* Add elements to each switch column */ - for (i = 0; i < sw_count; i ++) { - ecs_switch_t *sw = &sw_columns[i]; - flecs_switch_addn(sw, to_add); - } + /* If no next match was found for this table, move to next table */ + if (column == -1) { + if (output_is_input) { + return false; + } - /* Add elements to each bitset column */ - for (i = 0; i < bs_count; i ++) { - ecs_bitset_t *bs = &bs_columns[i]; - flecs_bitset_addn(bs, to_add); + table_record = find_next_table(&filter, op_ctx); + if (!table_record.hdr.table) { + return false; + } + + /* Assign new table to table register */ + table_reg_set(rule, regs, r, (table = table_record.hdr.table)); + + /* Assign first matching column */ + column = op_ctx->column = table_record.column; + } } - /* If the table is monitored indicate that there has been a change */ - mark_table_dirty(world, table, 0); + /* 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 (!(world->flags & EcsWorldReadonly) && !cur_count) { - flecs_table_set_empty(world, table); + if (op->term != -1) { + columns[op->term] = column; } - /* Return index of first added entity */ - return cur_count; + /* If this is a wildcard query, fill out the variable registers */ + if (filter.wildcard) { + reify_variables(iter, op, &filter, table->type, column); + } + + return true; } +/* With operation. The With operation always comes after either the Select or + * another With operation, and applies additional filters to the table. */ static -void fast_append( - ecs_type_info_t **type_info, - ecs_column_t *columns, - int32_t count) +bool eval_with( + ecs_iter_t *it, + ecs_rule_op_t *op, + int32_t op_index, + bool redo) { - /* Add elements to each column array */ - int32_t i; - for (i = 0; i < count; i ++) { - ecs_type_info_t *ti = type_info[i]; - ecs_column_t *column = &columns[i]; - ecs_storage_append(column, ti->size); + ecs_rule_iter_t *iter = &it->priv.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_var_t *regs = get_registers(iter, op); + + /* Get register indices for input */ + int32_t r = op->r_in; + + /* 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 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; } -} -int32_t flecs_table_append( - ecs_world_t *world, - ecs_table_t *table, - ecs_entity_t entity, - ecs_record_t *record, - bool construct, - bool on_add) -{ - ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(!table->lock, ECS_LOCKED_STORAGE, NULL); + int32_t column = -1; + ecs_table_t *table = NULL; + ecs_id_record_t *idr; - check_table_sanity(table); + if (op->term != -1) { + columns[op->term] = -1; + } - /* Get count & size before growing entities array. This tells us whether the - * arrays will realloc */ - ecs_data_t *data = &table->data; - int32_t count = data->entities.count; - int32_t column_count = table->storage_count; - ecs_column_t *columns = table->data.columns; + /* If this is a redo, we already looked up the table set */ + if (redo) { + idr = op_ctx->idr; - /* Grow buffer with entity ids, set new element to new entity */ - ecs_entity_t *e = ecs_storage_append_t(&data->entities, ecs_entity_t); - ecs_assert(e != NULL, ECS_INTERNAL_ERROR, NULL); - *e = entity; + /* 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 { + /* Predicates can be reflexive, 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_reflexive + * instruction to limit branches for non-transitive queries (and to keep + * code more readable). + */ + if (pair.transitive && pair.reflexive) { + ecs_entity_t src = 0, second = 0; + + if (r == UINT8_MAX) { + src = op->subject; + } else { + const ecs_rule_var_t *v_subj = &rule->vars[r]; - /* Add record ptr to array with record ptrs */ - ecs_record_t **r = ecs_storage_append_t(&data->records, 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(world, table, 0); - ecs_assert(count >= 0, ECS_INTERNAL_ERROR, NULL); + if (v_subj->kind == EcsRuleVarKindEntity) { + src = entity_reg_get(rule, regs, r); - ecs_type_info_t **type_info = table->type_info; + /* This is the input for the op, so should always be set */ + ecs_assert(src != 0, ECS_INTERNAL_ERROR, NULL); + } + } - /* Fast path: no switch columns, no lifecycle actions */ - if (!(table->flags & EcsTableIsComplex)) { - fast_append(type_info, columns, column_count); - if (!count) { - flecs_table_set_empty(world, table); /* See below */ + /* If src is set, it means that it is an entity. Try to also + * resolve the object. */ + if (src) { + /* If the object is not a wildcard, it has been reified. Get the + * value from either the register or as a literal */ + if (!filter.second_wildcard) { + second = ECS_PAIR_SECOND(filter.mask); + if (ecs_strip_generation(src) == second) { + return true; + } + } + } } - return count; - } - int32_t sw_count = table->sw_count; - int32_t bs_count = table->bs_count; - ecs_switch_t *sw_columns = data->sw_columns; - ecs_bitset_t *bs_columns = data->bs_columns; - ecs_entity_t *entities = data->entities.array; + /* 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); + } - /* Reobtain size to ensure that the columns have the same size as the - * entities and record vectors. This keeps reasoning about when allocations - * occur easier. */ - int32_t size = data->entities.size; + /* 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; + } - /* Grow component arrays with 1 element */ - int32_t i; - for (i = 0; i < column_count; i ++) { - ecs_column_t *column = &columns[i]; - ecs_type_info_t *ti = type_info[i]; - grow_column(column, ti, 1, size, construct); + table = reg_get_range(rule, op, regs, r).table; + if (!table) { + return false; + } - ecs_iter_action_t on_add_hook; - if (on_add && (on_add_hook = ti->hooks.on_add)) { - on_component_callback(world, table, on_add_hook, EcsOnAdd, column, - &entities[count], table->storage_ids[i], count, 1, ti); + /* If this is not a redo, start at the beginning */ + if (!redo) { + column = op_ctx->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, op_ctx->column, &filter); + op_ctx->column = column; + } - ecs_assert(columns[i].size == - data->entities.size, ECS_INTERNAL_ERROR, NULL); - ecs_assert(columns[i].count == - data->entities.count, 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_count; i ++) { - ecs_assert(sw_columns != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_switch_t *sw = &sw_columns[i]; - flecs_switch_add(sw); + if (op->term != -1) { + columns[op->term] = column; } - /* Add element to each bitset column */ - for (i = 0; i < bs_count; i ++) { - ecs_assert(bs_columns != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_bitset_t *bs = &bs_columns[i]; - flecs_bitset_addn(bs, 1); - } + /* If 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 this is the first entity in this table, signal queries so that the - * table moves from an inactive table to an active table. */ - if (!count) { - flecs_table_set_empty(world, table); + /* If this is a wildcard query, fill out the variable registers */ + if (filter.wildcard) { + reify_variables(iter, op, &filter, table->type, column); } - check_table_sanity(table); + set_source(it, op, regs, r); - return count; + return true; } +/* 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 -void fast_delete_last( - ecs_column_t *columns, - int32_t column_count) +bool eval_each( + ecs_iter_t *it, + ecs_rule_op_t *op, + int32_t op_index, + bool redo) { - int i; - for (i = 0; i < column_count; i ++) { - ecs_storage_remove_last(&columns[i]); - } -} + ecs_rule_iter_t *iter = &it->priv.iter.rule; + ecs_rule_each_ctx_t *op_ctx = &iter->op_ctx[op_index].is.each; + ecs_var_t *regs = get_registers(iter, op); + int32_t r_in = op->r_in; + int32_t r_out = op->r_out; + ecs_entity_t e; -static -void fast_delete( - ecs_type_info_t **type_info, - ecs_column_t *columns, - int32_t column_count, - int32_t index) -{ - int i; - for (i = 0; i < column_count; i ++) { - ecs_type_info_t *ti = type_info[i]; - ecs_column_t *column = &columns[i]; - ecs_storage_remove(column, ti->size, index); - } -} + /* Make sure in/out registers are of the correct kind */ + ecs_assert(iter->rule->vars[r_in].kind == EcsRuleVarKindTable, + ECS_INTERNAL_ERROR, NULL); + ecs_assert(iter->rule->vars[r_out].kind == EcsRuleVarKindEntity, + ECS_INTERNAL_ERROR, NULL); -void flecs_table_delete( - ecs_world_t *world, - ecs_table_t *table, - int32_t index, - bool destruct) -{ - ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(!table->lock, ECS_LOCKED_STORAGE, NULL); + /* Get table, make sure that it contains data. The select operation should + * ensure that empty tables are never forwarded. */ + ecs_table_range_t slice = table_reg_get(iter->rule, regs, r_in); + ecs_table_t *table = slice.table; + if (table) { + int32_t row, count = slice.count; + int32_t offset = slice.offset; - check_table_sanity(table); + if (!count) { + count = ecs_table_count(table); + ecs_assert(count != 0, ECS_INTERNAL_ERROR, NULL); + } else { + count += offset; + } - ecs_data_t *data = &table->data; - int32_t count = data->entities.count; + ecs_entity_t *entities = ecs_storage_first(&table->data.entities); + ecs_assert(entities != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(count > 0, ECS_INTERNAL_ERROR, NULL); - count --; - ecs_assert(index <= count, 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; + } - /* Move last entity id to index */ - ecs_entity_t *entities = data->entities.array; - ecs_entity_t entity_to_move = entities[count]; - ecs_entity_t entity_to_delete = entities[index]; - entities[index] = entity_to_move; - ecs_storage_remove_last(&data->entities); - - /* Move last record ptr to index */ - ecs_assert(count < data->records.count, ECS_INTERNAL_ERROR, NULL); - - ecs_record_t **records = data->records.array; - ecs_record_t *record_to_move = records[count]; - records[index] = record_to_move; - ecs_storage_remove_last(&data->records); - - /* 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); - } - } - - /* If the table is monitored indicate that there has been a change */ - mark_table_dirty(world, table, 0); - - /* If table is empty, deactivate it */ - if (!count) { - flecs_table_set_empty(world, table); - } - - /* Destruct component data */ - ecs_type_info_t **type_info = table->type_info; - ecs_column_t *columns = data->columns; - int32_t column_count = table->storage_count; - int32_t i; - - /* If this is a table without lifecycle callbacks or special columns, take - * fast path that just remove an element from the array(s) */ - if (!(table->flags & EcsTableIsComplex)) { - if (index == count) { - fast_delete_last(columns, column_count); - } else { - fast_delete(type_info, columns, column_count, index); + /* If row exceeds number of entities in table, return false */ + if (row >= count) { + return false; } - check_table_sanity(table); - return; - } - - ecs_id_t *ids = table->storage_ids; - - /* Last element, destruct & remove */ - if (index == count) { - /* If table has component destructors, invoke */ - if (destruct && (table->flags & EcsTableHasDtors)) { - for (i = 0; i < column_count; i ++) { - remove_component(world, table, type_info[i], &columns[i], - &entity_to_delete, ids[i], index, 1); + /* Skip builtin entities that could confuse operations */ + e = entities[row]; + while (e == EcsWildcard || e == EcsThis || e == EcsAny) { + row ++; + if (row == count) { + return false; } + e = entities[row]; } - - fast_delete_last(columns, column_count); - - /* Not last element, move last element to deleted element & destruct */ } else { - /* If table has component destructors, invoke */ - if (destruct && (table->flags & (EcsTableHasDtors | EcsTableHasMove))) { - for (i = 0; i < column_count; i ++) { - ecs_column_t *column = &columns[i]; - ecs_type_info_t *ti = type_info[i]; - ecs_size_t size = ti->size; - void *dst = ecs_storage_get(column, size, index); - void *src = ecs_storage_last(column, size); - - ecs_iter_action_t on_remove = ti->hooks.on_remove; - if (on_remove) { - on_component_callback(world, table, on_remove, EcsOnRemove, - column, &entity_to_delete, ids[i], index, 1, ti); - } - - ecs_move_t move_dtor = ti->hooks.move_dtor; - if (move_dtor) { - move_dtor(dst, src, 1, ti); - } else { - ecs_os_memcpy(dst, src, size); - } - - ecs_storage_remove_last(column); - } + if (!redo) { + e = entity_reg_get(iter->rule, regs, r_in); } else { - fast_delete(type_info, columns, column_count, index); + return false; } } - /* Remove elements from switch columns */ - ecs_switch_t *sw_columns = data->sw_columns; - int32_t sw_count = table->sw_count; - for (i = 0; i < sw_count; i ++) { - flecs_switch_remove(&sw_columns[i], index); - } - - /* Remove elements from bitset columns */ - ecs_bitset_t *bs_columns = data->bs_columns; - int32_t bs_count = table->bs_count; - for (i = 0; i < bs_count; i ++) { - flecs_bitset_remove(&bs_columns[i], index); - } + /* Assign entity */ + entity_reg_set(iter->rule, regs, r_out, e); - check_table_sanity(table); + return true; } +/* 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 -void fast_move( - ecs_table_t *dst_table, - int32_t dst_index, - ecs_table_t *src_table, - int32_t src_index) -{ - int32_t i_new = 0, dst_column_count = dst_table->storage_count; - int32_t i_old = 0, src_column_count = src_table->storage_count; - ecs_id_t *dst_ids = dst_table->storage_ids; - ecs_id_t *src_ids = src_table->storage_ids; - - ecs_column_t *src_columns = src_table->data.columns; - ecs_column_t *dst_columns = dst_table->data.columns; - - ecs_type_info_t **dst_type_info = dst_table->type_info; - - for (; (i_new < dst_column_count) && (i_old < src_column_count);) { - ecs_id_t dst_id = dst_ids[i_new]; - ecs_id_t src_id = src_ids[i_old]; - - if (dst_id == src_id) { - ecs_column_t *dst_column = &dst_columns[i_new]; - ecs_column_t *src_column = &src_columns[i_old]; - ecs_type_info_t *ti = dst_type_info[i_new]; - int32_t size = ti->size; - void *dst = ecs_storage_get(dst_column, size, dst_index); - void *src = ecs_storage_get(src_column, size, src_index); - ecs_os_memcpy(dst, src, size); - } - - i_new += dst_id <= src_id; - i_old += dst_id >= src_id; - } -} - -void flecs_table_move( - ecs_world_t *world, - ecs_entity_t dst_entity, - ecs_entity_t src_entity, - ecs_table_t *dst_table, - int32_t dst_index, - ecs_table_t *src_table, - int32_t src_index, - bool construct) +bool eval_store( + ecs_iter_t *it, + ecs_rule_op_t *op, + int32_t op_index, + bool redo) { - ecs_assert(dst_table != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(src_table != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(!dst_table->lock, ECS_LOCKED_STORAGE, NULL); - ecs_assert(!src_table->lock, ECS_LOCKED_STORAGE, NULL); - - ecs_assert(src_index >= 0, ECS_INTERNAL_ERROR, NULL); - ecs_assert(dst_index >= 0, ECS_INTERNAL_ERROR, NULL); - - check_table_sanity(dst_table); - check_table_sanity(src_table); + (void)op_index; - if (!((dst_table->flags | src_table->flags) & EcsTableIsComplex)) { - fast_move(dst_table, dst_index, src_table, src_index); - check_table_sanity(dst_table); - check_table_sanity(src_table); - return; + if (redo) { + /* Only ever return result once */ + return false; } - move_switch_columns(dst_table, dst_index, src_table, src_index, 1, false); - move_bitset_columns(dst_table, dst_index, src_table, src_index, 1, false); + ecs_rule_iter_t *iter = &it->priv.iter.rule; + const ecs_rule_t *rule = iter->rule; + ecs_world_t *world = rule->world; + ecs_var_t *regs = get_registers(iter, op); + int32_t r_in = op->r_in; + int32_t r_out = op->r_out; - bool same_entity = dst_entity == src_entity; + (void)world; - ecs_type_info_t **dst_type_info = dst_table->type_info; - ecs_type_info_t **src_type_info = src_table->type_info; + const ecs_rule_var_t *var_out = &rule->vars[r_out]; + if (var_out->kind == EcsRuleVarKindEntity) { + ecs_entity_t out, in = reg_get_entity(rule, op, regs, r_in); + ecs_assert(in != 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(ecs_is_valid(world, in), ECS_INTERNAL_ERROR, NULL); - int32_t i_new = 0, dst_column_count = dst_table->storage_count; - int32_t i_old = 0, src_column_count = src_table->storage_count; - ecs_id_t *dst_ids = dst_table->storage_ids; - ecs_id_t *src_ids = src_table->storage_ids; + out = iter->registers[r_out].entity; + bool output_is_input = ecs_iter_var_is_constrained(it, r_out); + if (output_is_input && !redo) { + ecs_assert(regs[r_out].entity == iter->registers[r_out].entity, + ECS_INTERNAL_ERROR, NULL); - ecs_column_t *src_columns = src_table->data.columns; - ecs_column_t *dst_columns = dst_table->data.columns; + if (out != in) { + /* If output variable is set it must match the input */ + return false; + } + } - for (; (i_new < dst_column_count) && (i_old < src_column_count);) { - ecs_id_t dst_id = dst_ids[i_new]; - ecs_id_t src_id = src_ids[i_old]; + reg_set_entity(rule, regs, r_out, in); + } else { + ecs_table_range_t out, in = reg_get_range(rule, op, regs, r_in); - if (dst_id == src_id) { - ecs_column_t *dst_column = &dst_columns[i_new]; - ecs_column_t *src_column = &src_columns[i_old]; - ecs_type_info_t *ti = dst_type_info[i_new]; - int32_t size = ti->size; + out = iter->registers[r_out].range; + bool output_is_input = out.table != NULL; - ecs_assert(size != 0, ECS_INTERNAL_ERROR, NULL); - void *dst = ecs_storage_get(dst_column, size, dst_index); - void *src = ecs_storage_get(src_column, size, src_index); + if (output_is_input && !redo) { + ecs_assert(regs[r_out].entity == iter->registers[r_out].entity, + ECS_INTERNAL_ERROR, NULL); - if (same_entity) { - ecs_move_t move = ti->hooks.ctor_move_dtor; - if (move) { - /* ctor + move + dtor */ - move(dst, src, 1, ti); - } else { - ecs_os_memcpy(dst, src, size); - } - } else { - ecs_copy_t copy = ti->hooks.copy_ctor; - if (copy) { - copy(dst, src, 1, ti); - } else { - ecs_os_memcpy(dst, src, size); - } - } - } else { - if (dst_id < src_id) { - add_component(world, dst_table, dst_type_info[i_new], - &dst_columns[i_new], &dst_entity, dst_id, - dst_index, 1, construct); - } else { - remove_component(world, src_table, src_type_info[i_old], - &src_columns[i_old], &src_entity, src_id, - src_index, 1); + if (ecs_os_memcmp_t(&out, &in, ecs_table_range_t)) { + /* If output variable is set it must match the input */ + return false; } } - i_new += dst_id <= src_id; - i_old += dst_id >= src_id; - } + reg_set_range(rule, regs, r_out, &in); - for (; (i_new < dst_column_count); i_new ++) { - add_component(world, dst_table, dst_type_info[i_new], - &dst_columns[i_new], &dst_entity, dst_ids[i_new], dst_index, 1, - construct); + /* Ensure that if the input was an empty entity, information is not + * lost */ + if (!regs[r_out].range.table) { + regs[r_out].entity = reg_get_entity(rule, op, regs, r_in); + ecs_assert(ecs_is_valid(world, regs[r_out].entity), + ECS_INTERNAL_ERROR, NULL); + } } - for (; (i_old < src_column_count); i_old ++) { - remove_component(world, src_table, src_type_info[i_old], - &src_columns[i_old], &src_entity, src_ids[i_old], - src_index, 1); - } + ecs_rule_filter_t filter = pair_to_filter(iter, op, op->filter); + set_term_vars(rule, regs, op->term, filter.mask); - check_table_sanity(dst_table); - check_table_sanity(src_table); + return true; } -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) +/* 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_assert(!table->lock, ECS_LOCKED_STORAGE, NULL); - - check_table_sanity(table); + ecs_rule_iter_t *iter = &it->priv.iter.rule; + ecs_rule_setjmp_ctx_t *ctx = &iter->op_ctx[op_index].is.setjmp; - int32_t cur_count = flecs_table_data_count(data); - int32_t result = grow_data( - world, table, data, to_add, cur_count + to_add, ids); - check_table_sanity(table); - return result; + if (!redo) { + ctx->label = op->on_pass; + return true; + } else { + ctx->label = op->on_fail; + return false; + } } -void flecs_table_set_size( - ecs_world_t *world, - ecs_table_t *table, - ecs_data_t *data, - int32_t size) +/* 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) { - ecs_assert(table != NULL, ECS_LOCKED_STORAGE, NULL); - ecs_assert(!table->lock, ECS_LOCKED_STORAGE, NULL); + (void)it; + (void)op; + (void)op_index; - check_table_sanity(table); + /* Passthrough, result is not used for control flow */ + return !redo; +} - int32_t cur_count = flecs_table_data_count(data); +/* 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; - if (cur_count < size) { - grow_data(world, table, data, 0, size, NULL); - check_table_sanity(table); - } + return !redo; } -bool flecs_table_shrink( - ecs_world_t *world, - ecs_table_t *table) +/* Check if entity is stored in table */ +static +bool eval_intable( + ecs_iter_t *it, + ecs_rule_op_t *op, + int32_t op_index, + bool redo) { - ecs_assert(table != NULL, ECS_LOCKED_STORAGE, NULL); - ecs_assert(!table->lock, ECS_LOCKED_STORAGE, NULL); - (void)world; + (void)op_index; + + if (redo) { + return false; + } - check_table_sanity(table); + ecs_rule_iter_t *iter = &it->priv.iter.rule; + const ecs_rule_t *rule = iter->rule; + ecs_world_t *world = rule->world; + ecs_var_t *regs = get_registers(iter, op); + ecs_table_t *table = table_reg_get(rule, regs, op->r_in).table; - ecs_data_t *data = &table->data; - bool has_payload = data->entities.array != NULL; - ecs_storage_reclaim_t(&data->entities, ecs_entity_t); - ecs_storage_reclaim_t(&data->records, ecs_record_t*); + ecs_rule_pair_t pair = op->filter; + ecs_rule_filter_t filter = pair_to_filter(iter, op, pair); + ecs_entity_t second = ECS_PAIR_SECOND(filter.mask); + ecs_assert(second != 0 && second != EcsWildcard, ECS_INTERNAL_ERROR, NULL); + second = ecs_get_alive(world, second); + ecs_assert(second != 0, ECS_INTERNAL_ERROR, NULL); - int32_t i, count = table->storage_count; - ecs_type_info_t **type_info = table->type_info; - for (i = 0; i < count; i ++) { - ecs_column_t *column = &data->columns[i]; - ecs_type_info_t *ti = type_info[i]; - ecs_storage_reclaim(column, ti->size); - } + ecs_table_t *obj_table = ecs_get_table(world, second); + return obj_table == table; +} - return has_payload; +/* 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; + + /* Yield always returns false, because there are never any operations after + * a yield. */ + return false; } -int32_t flecs_table_data_count( - const ecs_data_t *data) +/* Dispatcher for operations */ +static +bool eval_op( + ecs_iter_t *it, + ecs_rule_op_t *op, + int32_t op_index, + bool redo) { - return data ? data->entities.count : 0; + 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 EcsRuleInTable: + return eval_intable(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 swap_switch_columns( - ecs_table_t *table, - ecs_data_t *data, - int32_t row_1, - int32_t row_2) +void push_registers( + ecs_rule_iter_t *it, + int32_t cur, + int32_t next) { - int32_t i = 0, column_count = table->sw_count; - if (!column_count) { + if (!it->rule->var_count) { return; } - ecs_switch_t *columns = data->sw_columns; + ecs_var_t *src_regs = get_register_frame(it, cur); + ecs_var_t *dst_regs = get_register_frame(it, next); - for (i = 0; i < column_count; i ++) { - ecs_switch_t *sw = &columns[i]; - flecs_switch_swap(sw, row_1, row_2); - } + ecs_os_memcpy_n(dst_regs, src_regs, + ecs_var_t, it->rule->var_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 -void swap_bitset_columns( - ecs_table_t *table, - ecs_data_t *data, - int32_t row_1, - int32_t row_2) +void push_columns( + ecs_rule_iter_t *it, + int32_t cur, + int32_t next) { - int32_t i = 0, column_count = table->bs_count; - if (!column_count) { + if (!it->rule->filter.term_count) { return; } - ecs_bitset_t *columns = data->bs_columns; + int32_t *src_cols = rule_get_columns_frame(it, cur); + int32_t *dst_cols = rule_get_columns_frame(it, next); - for (i = 0; i < column_count; i ++) { - ecs_bitset_t *bs = &columns[i]; - flecs_bitset_swap(bs, row_1, row_2); - } + ecs_os_memcpy_n(dst_cols, src_cols, int32_t, it->rule->filter.term_count); } -void flecs_table_swap( - ecs_world_t *world, - ecs_table_t *table, - int32_t row_1, - int32_t row_2) -{ - (void)world; - - ecs_assert(!table->lock, ECS_LOCKED_STORAGE, NULL); - ecs_assert(row_1 >= 0, ECS_INTERNAL_ERROR, NULL); - ecs_assert(row_2 >= 0, ECS_INTERNAL_ERROR, NULL); - - check_table_sanity(table); - - if (row_1 == row_2) { - return; - } - - /* If the table is monitored indicate that there has been a change */ - mark_table_dirty(world, table, 0); - - ecs_entity_t *entities = table->data.entities.array; - ecs_entity_t e1 = entities[row_1]; - ecs_entity_t e2 = entities[row_2]; - - ecs_record_t **records = table->data.records.array; - ecs_record_t *record_ptr_1 = records[row_1]; - ecs_record_t *record_ptr_2 = records[row_2]; - - ecs_assert(record_ptr_1 != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(record_ptr_2 != NULL, ECS_INTERNAL_ERROR, NULL); +/* 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_var_t *regs = get_register_frame(it, op->frame); + ecs_table_t *table = NULL; + int32_t count = 0; + int32_t offset = 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); + /* 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) { + const ecs_rule_var_t *var = &rule->vars[r]; + ecs_var_t *reg = ®s[r]; - /* 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); - records[row_1] = record_ptr_2; - records[row_2] = record_ptr_1; + if (var->kind == EcsRuleVarKindTable) { + ecs_table_range_t slice = table_reg_get(rule, regs, r); + table = slice.table; + count = slice.count; + offset = slice.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); - swap_switch_columns(table, &table->data, row_1, row_2); - swap_bitset_columns(table, &table->data, row_1, row_2); + ecs_entity_t e = reg->entity; + ecs_assert(ecs_is_valid(world, e), ECS_INTERNAL_ERROR, NULL); + ecs_record_t *record = flecs_entities_get(world, e); + offset = ECS_RECORD_TO_ROW(record->row); - ecs_column_t *columns = table->data.columns; - if (!columns) { - check_table_sanity(table); - return; + /* 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_type_info_t **type_info = table->type_info; + int32_t i, var_count = rule->var_count; + int32_t term_count = rule->filter.term_count; - /* Find the maximum size of column elements - * and allocate a temporary buffer for swapping */ - int32_t i, temp_buffer_size = ECS_SIZEOF(uint64_t), column_count = table->storage_count; - for (i = 0; i < column_count; i++) { - ecs_type_info_t* ti = type_info[i]; - temp_buffer_size = ECS_MAX(temp_buffer_size, ti->size); + for (i = 0; i < var_count; i ++) { + iter->variables[i] = regs[i]; } - void* tmp = ecs_os_alloca(temp_buffer_size); - - /* Swap columns */ - for (i = 0; i < column_count; i ++) { - ecs_type_info_t *ti = type_info[i]; - int32_t size = ti->size; - ecs_assert(size != 0, ECS_INTERNAL_ERROR, NULL); - - void *ptr = columns[i].array; - - void *el_1 = ECS_ELEM(ptr, size, row_1); - void *el_2 = ECS_ELEM(ptr, size, row_2); + for (i = 0; i < term_count; i ++) { + int32_t v = rule->term_vars[i].src; + if (v != -1) { + const ecs_rule_var_t *var = &rule->vars[v]; + if (var->name[0] != '.') { + if (var->kind == EcsRuleVarKindEntity) { + iter->sources[i] = regs[var->id].entity; + } else { + /* This can happen for Any variables, where the actual + * content of the variable is not of interest to the query. + * Just pick the first entity from the table, so that the + * column can be correctly resolved */ + ecs_table_t *t = regs[var->id].range.table; + if (t) { + iter->sources[i] = ecs_storage_first_t( + &t->data.entities, ecs_entity_t)[0]; + } else { + /* Can happen if term is optional */ + iter->sources[i] = 0; + } + } + } + } + } - ecs_os_memcpy(tmp, el_1, size); - ecs_os_memcpy(el_1, el_2, size); - ecs_os_memcpy(el_2, tmp, size); + /* 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_assert(iter->sources != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_entity_t src = iter->sources[i]; + int32_t c = ++ iter->columns[i]; + if (!src) { + src = iter->terms[i].src.id; + if (src != EcsThis && src != EcsAny) { + iter->columns[i] = 0; + } + } else if (c) { + iter->columns[i] = -1; + } } - check_table_sanity(table); -} + /* Set iterator ids */ + for (i = 0; i < term_count; i ++) { + const ecs_rule_term_vars_t *vars = &rule->term_vars[i]; + ecs_term_t *term = &rule->filter.terms[i]; + if (term->oper == EcsOptional || term->oper == EcsNot) { + if (iter->columns[i] == 0) { + iter->ids[i] = term->id; + continue; + } + } -static -void flecs_merge_column( - ecs_column_t *dst, - ecs_column_t *src, - int32_t size, - ecs_type_info_t *ti) -{ - int32_t dst_count = dst->count; + ecs_id_t id = term->id; + ecs_entity_t first = 0; + ecs_entity_t second = 0; + bool is_pair = ECS_HAS_ID_FLAG(id, PAIR); - if (!dst_count) { - ecs_storage_fini(dst); - *dst = *src; - src->array = NULL; - src->count = 0; - src->size = 0; - - /* If the new table is not empty, copy the contents from the - * src into the dst. */ - } else { - int32_t src_count = src->count; - ecs_storage_set_count(dst, size, dst_count + src_count); + if (!is_pair) { + first = id; + } else { + first = ECS_PAIR_FIRST(id); + second = ECS_PAIR_SECOND(id); + } - /* Construct new values */ - if (ti) { - ctor_component(ti, dst, dst_count, src_count); + if (vars->first != -1) { + first = regs[vars->first].entity; } - - void *dst_ptr = ECS_ELEM(dst->array, size, dst_count); - void *src_ptr = src->array; - - /* Move values into column */ - ecs_move_t move = NULL; - if (ti) { - move = ti->hooks.move; + if (vars->second != -1) { + ecs_assert(is_pair, ECS_INTERNAL_ERROR, NULL); + second = regs[vars->second].entity; } - if (move) { - move(dst_ptr, src_ptr, src_count, ti); + + if (!is_pair) { + id = first; } else { - ecs_os_memcpy(dst_ptr, src_ptr, size * src_count); + id = ecs_pair(first, second); } - ecs_storage_fini(src); + iter->ids[i] = id; } + + flecs_iter_populate_data(world, iter, table, offset, count, + iter->ptrs, iter->sizes); } static -void flecs_merge_table_data( - ecs_world_t *world, - ecs_table_t *dst_table, - ecs_table_t *src_table, - int32_t src_count, - int32_t dst_count, - ecs_data_t *src_data, - ecs_data_t *dst_data) +bool is_control_flow( + ecs_rule_op_t *op) { - int32_t i_new = 0, dst_column_count = dst_table->storage_count; - int32_t i_old = 0, src_column_count = src_table->storage_count; - ecs_id_t *dst_ids = dst_table->storage_ids; - ecs_id_t *src_ids = src_table->storage_ids; - - ecs_type_info_t **dst_type_info = dst_table->type_info; - ecs_type_info_t **src_type_info = src_table->type_info; + switch(op->kind) { + case EcsRuleSetJmp: + case EcsRuleJump: + return true; + default: + return false; + } +} - ecs_column_t *src = src_data->columns; - ecs_column_t *dst = dst_data->columns; +static +void rule_iter_set_initial_state( + ecs_iter_t *it, + ecs_rule_iter_t *iter, + const ecs_rule_t *rule) +{ + int32_t i; - ecs_assert(!dst_column_count || dst, ECS_INTERNAL_ERROR, NULL); + /* Make sure that if there are any terms with literal sources, they're + * initialized in the sources array */ + const ecs_filter_t *filter = &rule->filter; + int32_t term_count = filter->term_count; + for (i = 0; i < term_count; i ++) { + ecs_term_t *t = &filter->terms[i]; + ecs_term_id_t *src = &t->src; + ecs_assert(src->flags & EcsIsVariable || src->id != EcsThis, + ECS_INTERNAL_ERROR, NULL); - if (!src_count) { - return; + if (!(src->flags & EcsIsVariable)) { + it->sources[i] = src->id; + } } - /* Merge entities */ - flecs_merge_column(&dst_data->entities, &src_data->entities, - ECS_SIZEOF(ecs_entity_t), NULL); - ecs_assert(dst_data->entities.count == src_count + dst_count, - ECS_INTERNAL_ERROR, NULL); - - /* Merge record pointers */ - flecs_merge_column(&dst_data->records, &src_data->records, - ECS_SIZEOF(ecs_record_t*), 0); + /* Initialize registers of constrained variables */ + if (it->constrained_vars) { + ecs_var_t *regs = get_register_frame(iter, 0); - for (; (i_new < dst_column_count) && (i_old < src_column_count); ) { - ecs_id_t dst_id = dst_ids[i_new]; - ecs_id_t src_id = src_ids[i_old]; - ecs_type_info_t *dst_ti = dst_type_info[i_new]; - int32_t size = dst_ti->size; - ecs_assert(size != 0, ECS_INTERNAL_ERROR, NULL); + for (i = 0; i < it->variable_count; i ++) { + if (ecs_iter_var_is_constrained(it, i)) { + const ecs_rule_var_t *var = &rule->vars[i]; + ecs_assert(var->id == i, ECS_INTERNAL_ERROR, NULL); + + int32_t other_var = var->other; + ecs_var_t *it_var = &it->variables[i]; + ecs_entity_t e = it_var->entity; - if (dst_id == src_id) { - flecs_merge_column(&dst[i_new], &src[i_old], size, dst_ti); - mark_table_dirty(world, dst_table, i_new + 1); - - i_new ++; - i_old ++; - } else if (dst_id < src_id) { - /* New column, make sure vector is large enough. */ - ecs_column_t *column = &dst[i_new]; - ecs_storage_set_count(column, size, src_count + dst_count); - ctor_component(dst_ti, column, 0, src_count + dst_count); - i_new ++; - } else if (dst_id > src_id) { - /* Old column does not occur in new table, destruct */ - ecs_column_t *column = &src[i_old]; - dtor_component(src_type_info[i_old], column, 0, src_count); - ecs_storage_fini(column); - i_old ++; + if (e) { + ecs_assert(ecs_is_valid(it->world, e), + ECS_INTERNAL_ERROR, NULL); + reg_set_entity(rule, regs, i, e); + if (other_var != -1) { + reg_set_entity(rule, regs, other_var, e); + } + } else { + ecs_assert(it_var->range.table != NULL, + ECS_INVALID_PARAMETER, NULL); + reg_set_range(rule, regs, i, &it_var->range); + if (other_var != -1) { + reg_set_range(rule, regs, other_var, &it_var->range); + } + } + } } } +} - move_switch_columns(dst_table, dst_count, src_table, 0, src_count, true); - move_bitset_columns(dst_table, dst_count, src_table, 0, src_count, true); +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); - /* Initialize remaining columns */ - for (; i_new < dst_column_count; i_new ++) { - ecs_column_t *column = &dst[i_new]; - ecs_type_info_t *ti = dst_type_info[i_new]; - int32_t size = ti->size; - ecs_assert(size != 0, ECS_INTERNAL_ERROR, NULL); - ecs_storage_set_count(column, size, src_count + dst_count); - ctor_component(ti, column, 0, src_count + dst_count); + if (flecs_iter_next_row(it)) { + return true; } - /* Destruct remaining columns */ - for (; i_old < src_column_count; i_old ++) { - ecs_column_t *column = &src[i_old]; - dtor_component(src_type_info[i_old], column, 0, src_count); - ecs_storage_fini(column); - } - - /* Mark entity column as dirty */ - mark_table_dirty(world, dst_table, 0); + return flecs_iter_next_instanced(it, ecs_rule_next_instanced(it)); +error: + return false; } -int32_t ecs_table_count( - const ecs_table_t *table) +/* 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_instanced( + ecs_iter_t *it) { - ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); - return flecs_table_data_count(&table->data); -} + ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(it->next == ecs_rule_next, ECS_INVALID_PARAMETER, NULL); -void flecs_table_merge( - ecs_world_t *world, - ecs_table_t *dst_table, - ecs_table_t *src_table, - ecs_data_t *dst_data, - ecs_data_t *src_data) -{ - ecs_assert(src_table != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(!src_table->lock, ECS_LOCKED_STORAGE, NULL); + ecs_rule_iter_t *iter = &it->priv.iter.rule; + const ecs_rule_t *rule = iter->rule; + bool redo = iter->redo; + int32_t last_frame = -1; + bool first_time = !ECS_BIT_IS_SET(it->flags, EcsIterIsValid); - check_table_sanity(dst_table); - check_table_sanity(src_table); - - bool move_data = false; - - /* If there is nothing to merge to, just clear the old table */ - if (!dst_table) { - flecs_table_clear_data(world, src_table, src_data); - check_table_sanity(src_table); - return; - } else { - ecs_assert(!dst_table->lock, ECS_LOCKED_STORAGE, NULL); - } + ecs_poly_assert(rule, ecs_rule_t); - /* If there is no data to merge, drop out */ - if (!src_data) { - return; + /* Mark iterator as valid & ensure iterator resources are up to date */ + flecs_iter_validate(it); + + /* Can't iterate an iterator that's already depleted */ + ecs_check(iter->op != -1, ECS_INVALID_PARAMETER, NULL); + + /* If this is the first time the iterator is iterated, set initial state */ + if (first_time) { + ecs_assert(redo == false, ECS_INTERNAL_ERROR, NULL); + rule_iter_set_initial_state(it, iter, rule); } - if (!dst_data) { - dst_data = &dst_table->data; - if (dst_table == src_table) { - move_data = true; + 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; + + /* 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); } - } - ecs_entity_t *src_entities = src_data->entities.array; - int32_t src_count = src_data->entities.count; - int32_t dst_count = dst_data->entities.count; - ecs_record_t **src_records = src_data->records.array; + /* Dispatch the operation */ + bool result = eval_op(it, op, op_index, redo); + iter->op = result ? op->on_pass : op->on_fail; - /* First, update entity index so old entities point to new type */ - int32_t i; - for(i = 0; i < src_count; i ++) { - ecs_record_t *record; - if (dst_table != src_table) { - record = src_records[i]; - ecs_assert(record != NULL, ECS_INTERNAL_ERROR, NULL); - } else { - record = flecs_entities_ensure(world, src_entities[i]); + /* If the current operation is yield, return results */ + if (op->kind == EcsRuleYield) { + populate_iterator(rule, it, iter, op); + iter->redo = true; + return true; } - uint32_t flags = ECS_RECORD_TO_ROW_FLAGS(record->row); - record->row = ECS_ROW_TO_RECORD(dst_count + i, flags); - record->table = dst_table; - } + /* 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; + } - /* Merge table columns */ - if (move_data) { - *dst_data = *src_data; - } else { - flecs_merge_table_data(world, dst_table, src_table, src_count, dst_count, - src_data, dst_data); - } + /* If jumping backwards, it's a redo */ + redo = iter->op <= op_index; - if (src_count) { - if (!dst_count) { - flecs_table_set_empty(world, dst_table); + if (!is_control_flow(op)) { + last_frame = op->frame; } - flecs_table_set_empty(world, src_table); - dst_table->observed_count += src_table->observed_count; - src_table->observed_count = 0; - } + } while (iter->op != -1); - check_table_sanity(src_table); - check_table_sanity(dst_table); + ecs_iter_fini(it); + +error: + return false; } -void flecs_table_replace_data( - ecs_world_t *world, - ecs_table_t *table, - ecs_data_t *data) -{ - int32_t prev_count = 0; - ecs_data_t *table_data = &table->data; - ecs_assert(!data || data != table_data, ECS_INTERNAL_ERROR, NULL); - ecs_assert(!table->lock, ECS_LOCKED_STORAGE, NULL); - - check_table_sanity(table); - - prev_count = table_data->entities.count; - run_on_remove(world, table, table_data); - flecs_table_clear_data(world, table, table_data); +#endif - if (data) { - table->data = *data; - } else { - flecs_table_init_data(table); - } - int32_t count = ecs_table_count(table); +#ifdef FLECS_MODULE - if (!prev_count && count) { - flecs_table_set_empty(world, table); - } else if (prev_count && !count) { - flecs_table_set_empty(world, table); - } +#include - check_table_sanity(table); -} +char* ecs_module_path_from_c( + const char *c_name) +{ + ecs_strbuf_t str = ECS_STRBUF_INIT; + const char *ptr; + char ch; -int32_t* flecs_table_get_dirty_state( - ecs_table_t *table) -{ - if (!table->dirty_state) { - int32_t column_count = table->storage_count; - table->dirty_state = ecs_os_malloc_n( int32_t, column_count + 1); - ecs_assert(table->dirty_state != NULL, ECS_INTERNAL_ERROR, NULL); - - for (int i = 0; i < column_count + 1; i ++) { - table->dirty_state[i] = 1; + 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); + } } - } - return table->dirty_state; -} -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); + ecs_strbuf_appendstrn(&str, &ch, 1); + } - int32_t column_count = table->storage_count; - return ecs_os_memdup(dirty_state, (column_count + 1) * ECS_SIZEOF(int32_t)); + return ecs_strbuf_get(&str); } -void flecs_table_notify( +ecs_entity_t ecs_import( ecs_world_t *world, - ecs_table_t *table, - ecs_table_event_t *event) + ecs_module_action_t module, + const char *module_name) { - if (world->flags & EcsWorldFini) { - return; - } + ecs_check(!(world->flags & EcsWorldReadonly), + ECS_INVALID_WHILE_READONLY, NULL); - switch(event->kind) { - case EcsTableTriggersForId: - notify_trigger(world, table, event->event); - break; - case EcsTableNoTriggersForId: - break; + ecs_entity_t old_scope = ecs_set_scope(world, 0); + const char *old_name_prefix = world->info.name_prefix; + + 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(); + + /* Load module */ + module(world); + + /* 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_log_pop(); } + + /* Restore to previous state */ + ecs_set_scope(world, old_scope); + world->info.name_prefix = old_name_prefix; + + return e; +error: + return 0; } -void ecs_table_lock( +ecs_entity_t ecs_import_c( ecs_world_t *world, - ecs_table_t *table) + ecs_module_action_t module, + const char *c_name) { - if (table) { - if (ecs_poly_is(world, ecs_world_t) && !(world->flags & EcsWorldReadonly)) { - table->lock ++; - } - } + char *name = ecs_module_path_from_c(c_name); + ecs_entity_t e = ecs_import(world, module, name); + ecs_os_free(name); + return e; } -void ecs_table_unlock( +ecs_entity_t ecs_import_from_library( ecs_world_t *world, - ecs_table_t *table) + const char *library_name, + const char *module_name) { - if (table) { - if (ecs_poly_is(world, ecs_world_t) && !(world->flags & EcsWorldReadonly)) { - table->lock --; - ecs_assert(table->lock >= 0, ECS_INVALID_OPERATION, NULL); - } + ecs_check(library_name != NULL, ECS_INVALID_PARAMETER, NULL); + + char *import_func = (char*)module_name; /* safe */ + char *module = (char*)module_name; + + 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; } -} -bool ecs_table_has_module( - ecs_table_t *table) -{ - return table->flags & EcsTableHasModule; -} + /* 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_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; + *bptr = '\0'; + + module = ecs_os_strdup(import_func); + ecs_assert(module != NULL, ECS_OUT_OF_MEMORY, NULL); + + ecs_os_strcat(bptr, "Import"); } - ecs_table_record_t *tr = flecs_table_record_get(world, storage_table, id); - if (tr) { - return &table->data.columns[tr->column]; + 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); } - return NULL; -} + 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); -const ecs_type_t* ecs_table_get_type( - const ecs_table_t *table) -{ - if (table) { - return &table->type; + if (module != module_name) { + ecs_os_free(module); + } + + return 0; } else { - return NULL; + ecs_trace("library '%s' ('%s') loaded", + library_name, library_filename); } -} - -ecs_table_t* ecs_table_get_storage_table( - const ecs_table_t *table) -{ - return table->storage_table; -} -int32_t ecs_table_type_to_storage_index( - const ecs_table_t *table, - int32_t index) -{ - ecs_assert(index >= 0, ECS_INVALID_PARAMETER, NULL); - ecs_check(index < table->type.count, ECS_INVALID_PARAMETER, NULL); - int32_t *storage_map = table->storage_map; - if (storage_map) { - return storage_map[index]; + 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); } -error: - return -1; -} -int32_t ecs_table_storage_to_type_index( - const ecs_table_t *table, - int32_t index) -{ - ecs_check(index < table->storage_count, ECS_INVALID_PARAMETER, NULL); - ecs_check(table->storage_map != NULL, ECS_INVALID_PARAMETER, NULL); - int32_t offset = table->type.count; - return table->storage_map[offset + index]; -error: - return -1; -} + /* Do not free id, as it will be stored as the component identifier */ + ecs_entity_t result = ecs_import(world, action, module); -int32_t flecs_table_column_to_union_index( - const ecs_table_t *table, - int32_t column) -{ - int32_t sw_count = table->sw_count; - if (sw_count) { - int32_t sw_offset = table->sw_offset; - if (column >= sw_offset && column < (sw_offset + sw_count)){ - return column - sw_offset; - } + if (import_func != module_name) { + ecs_os_free(import_func); } - return -1; -} -ecs_record_t* ecs_record_find( - const ecs_world_t *world, - ecs_entity_t entity) -{ - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(entity != 0, ECS_INVALID_PARAMETER, NULL); + if (module != module_name) { + ecs_os_free(module); + } - world = ecs_get_world(world); + ecs_os_free(library_filename); - ecs_record_t *r = flecs_entities_get(world, entity); - if (r) { - return r; - } + return result; error: - return NULL; + return 0; } -void* ecs_table_get_column( - ecs_table_t *table, - int32_t index) +ecs_entity_t ecs_module_init( + ecs_world_t *world, + const char *c_name, + const ecs_component_desc_t *desc) { - ecs_check(table != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(index < table->type.count, ECS_INVALID_PARAMETER, NULL); + ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_poly_assert(world, ecs_world_t); - int32_t storage_index = table->storage_map[index]; - if (storage_index == -1) { - return NULL; + ecs_entity_t e = desc->entity; + if (!e) { + char *module_path = ecs_module_path_from_c(c_name); + e = ecs_new_from_fullpath(world, module_path); + ecs_set_symbol(world, e, module_path); + ecs_os_free(module_path); } + + ecs_add_id(world, e, EcsModule); - return table->data.columns[storage_index].array; + ecs_component_desc_t private_desc = *desc; + private_desc.entity = e; + + if (desc->type.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; + } + + return e; error: - return NULL; + return 0; } -void* ecs_record_get_column( - const ecs_record_t *r, - int32_t column, - size_t c_size) -{ - (void)c_size; - ecs_table_t *table = r->table; +#endif - ecs_check(column < table->storage_count, ECS_INVALID_PARAMETER, NULL); - ecs_type_info_t *ti = table->type_info[column]; - ecs_column_t *c = &table->data.columns[column]; - ecs_assert(c != NULL, ECS_INTERNAL_ERROR, NULL); +#ifndef FLECS_META_PRIVATE_H +#define FLECS_META_PRIVATE_H - ecs_check(!flecs_utosize(c_size) || flecs_utosize(c_size) == ti->size, - ECS_INVALID_PARAMETER, NULL); - return ecs_storage_get(c, ti->size, ECS_RECORD_TO_ROW(r->row)); -error: - return NULL; -} +#ifdef FLECS_META -void ecs_table_swap_rows( - ecs_world_t* world, - ecs_table_t* table, - int32_t row_1, - int32_t row_2) -{ - flecs_table_swap(world, table, row_1, row_2); -} +void ecs_meta_type_serialized_init( + ecs_iter_t *it); -int32_t flecs_table_observed_count( - const ecs_table_t *table) -{ - return table->observed_count; -} +void ecs_meta_dtor_serialized( + EcsMetaTypeSerialized *ptr); -static const char* mixin_kind_str[] = { - [EcsMixinBase] = "base (should never be requested by application)", - [EcsMixinWorld] = "world", - [EcsMixinEntity] = "entity", - [EcsMixinObservable] = "observable", - [EcsMixinIterable] = "iterable", - [EcsMixinDtor] = "dtor", - [EcsMixinMax] = "max (should never be requested by application)" -}; +bool flecs_unit_validate( + ecs_world_t *world, + ecs_entity_t t, + EcsUnit *data); -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) - } -}; +#endif + +#endif -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), - [EcsMixinEntity] = offsetof(ecs_query_t, entity), - [EcsMixinIterable] = offsetof(ecs_query_t, iterable), - [EcsMixinDtor] = offsetof(ecs_query_t, dtor) - } -}; +#ifdef FLECS_META -ecs_mixins_t ecs_observer_t_mixins = { - .type_name = "ecs_observer_t", - .elems = { - [EcsMixinWorld] = offsetof(ecs_observer_t, world), - [EcsMixinEntity] = offsetof(ecs_observer_t, entity), - [EcsMixinDtor] = offsetof(ecs_observer_t, dtor) +ecs_entity_t ecs_primitive_init( + ecs_world_t *world, + const ecs_primitive_desc_t *desc) +{ + ecs_entity_t t = desc->entity; + if (!t) { + t = ecs_new_low_id(world); } -}; -ecs_mixins_t ecs_filter_t_mixins = { - .type_name = "ecs_filter_t", - .elems = { - [EcsMixinIterable] = offsetof(ecs_filter_t, iterable) - } -}; + ecs_set(world, t, EcsPrimitive, { desc->kind }); -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); + return t; +} - const ecs_mixins_t *mixins = hdr->mixins; - if (!mixins) { - /* Object has no mixins */ - goto not_found; +ecs_entity_t ecs_enum_init( + ecs_world_t *world, + const ecs_enum_desc_t *desc) +{ + ecs_entity_t t = desc->entity; + if (!t) { + t = ecs_new_low_id(world); } - 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; - } + ecs_add(world, t, EcsEnum); - /* Object has mixin, return its address */ - return ECS_OFFSET(hdr, offset); + ecs_entity_t old_scope = ecs_set_scope(world, t); -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); + 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_entity_t c = ecs_entity_init(world, &(ecs_entity_desc_t){ + .name = m_desc->name + }); + + if (!m_desc->value) { + ecs_add_id(world, c, EcsConstant); + } else { + ecs_set_pair_object(world, c, EcsConstant, ecs_i32_t, + {m_desc->value}); } } - -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(); + ecs_set_scope(world, old_scope); + + if (i == 0) { + ecs_err("enum '%s' has no constants", ecs_get_name(world, t)); + ecs_delete(world, t); + return 0; } - return ptr; + return t; } -void* _ecs_poly_init( - ecs_poly_t *poly, - int32_t type, - ecs_size_t size, - ecs_mixins_t *mixins) +ecs_entity_t ecs_bitmask_init( + ecs_world_t *world, + const ecs_bitmask_desc_t *desc) { - 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; -} + ecs_entity_t t = desc->entity; + if (!t) { + t = ecs_new_low_id(world); + } -void _ecs_poly_fini( - ecs_poly_t *poly, - int32_t type) -{ - ecs_assert(poly != NULL, ECS_INVALID_PARAMETER, NULL); - (void)type; + ecs_add(world, t, EcsBitmask); - ecs_header_t *hdr = poly; + ecs_entity_t old_scope = ecs_set_scope(world, t); - /* 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; -} + 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; + } -EcsPoly* _ecs_poly_bind( - ecs_world_t *world, - ecs_entity_t entity, - ecs_entity_t tag) -{ - /* Add tag to the entity for easy querying. This will make it possible to - * query for `Query` instead of `(Poly, Query) */ - if (!ecs_has_id(world, entity, tag)) { - ecs_add_id(world, entity, tag); - } + ecs_entity_t c = ecs_entity_init(world, &(ecs_entity_desc_t){ + .name = m_desc->name + }); - /* Never defer creation of a poly object */ - bool deferred = false; - if (ecs_is_deferred(world)) { - deferred = true; - ecs_defer_suspend(world); + if (!m_desc->value) { + ecs_add_id(world, c, EcsConstant); + } else { + ecs_set_pair_object(world, c, EcsConstant, ecs_u32_t, + {m_desc->value}); + } } - /* If this is a new poly, leave the actual creation up to the caller so they - * call tell the difference between a create or an update */ - EcsPoly *result = ecs_get_mut_pair(world, entity, EcsPoly, tag); + ecs_set_scope(world, old_scope); - if (deferred) { - ecs_defer_resume(world); + if (i == 0) { + ecs_err("bitmask '%s' has no constants", ecs_get_name(world, t)); + ecs_delete(world, t); + return 0; } - return result; + return t; } -void _ecs_poly_modified( +ecs_entity_t ecs_array_init( ecs_world_t *world, - ecs_entity_t entity, - ecs_entity_t tag) -{ - ecs_modified_pair(world, entity, ecs_id(EcsPoly), tag); -} - -const EcsPoly* _ecs_poly_bind_get( - const ecs_world_t *world, - ecs_entity_t entity, - ecs_entity_t tag) -{ - return ecs_get_pair(world, entity, EcsPoly, tag); -} - -ecs_poly_t* _ecs_poly_get( - const ecs_world_t *world, - ecs_entity_t entity, - ecs_entity_t tag) + const ecs_array_desc_t *desc) { - const EcsPoly *p = _ecs_poly_bind_get(world, entity, tag); - if (p) { - return p->poly; + ecs_entity_t t = desc->entity; + if (!t) { + t = ecs_new_low_id(world); } - return NULL; -} -#define assert_object(cond, file, line, type_name)\ - _ecs_assert((cond), ECS_INVALID_PARAMETER, #cond, file, line, type_name);\ - assert(cond) + ecs_set(world, t, EcsArray, { + .type = desc->type, + .count = desc->count + }); -#ifndef FLECS_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, 0); - - const ecs_header_t *hdr = poly; - const char *type_name = hdr->mixins->type_name; - assert_object(hdr->magic == ECS_OBJECT_MAGIC, file, line, type_name); - assert_object(hdr->type == type, file, line, type_name); - return (ecs_poly_t*)poly; + return t; } -#endif -bool _ecs_poly_is( - const ecs_poly_t *poly, - int32_t type) +ecs_entity_t ecs_vector_init( + ecs_world_t *world, + const ecs_vector_desc_t *desc) { - ecs_assert(poly != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_entity_t t = desc->entity; + if (!t) { + t = ecs_new_low_id(world); + } - const ecs_header_t *hdr = poly; - ecs_assert(hdr->magic == ECS_OBJECT_MAGIC, ECS_INVALID_PARAMETER, NULL); - return hdr->type == type; -} + ecs_set(world, t, EcsVector, { + .type = desc->type + }); -ecs_iterable_t* ecs_get_iterable( - const ecs_poly_t *poly) -{ - return (ecs_iterable_t*)assert_mixin(poly, EcsMixinIterable); + return t; } -ecs_observable_t* ecs_get_observable( - const ecs_poly_t *poly) +ecs_entity_t ecs_struct_init( + ecs_world_t *world, + const ecs_struct_desc_t *desc) { - return (ecs_observable_t*)assert_mixin(poly, EcsMixinObservable); -} + ecs_entity_t t = desc->entity; + if (!t) { + t = ecs_new_low_id(world); + } -const ecs_world_t* ecs_get_world( - const ecs_poly_t *poly) -{ - return *(ecs_world_t**)assert_mixin(poly, EcsMixinWorld); -} + ecs_entity_t old_scope = ecs_set_scope(world, t); -ecs_poly_dtor_t* ecs_get_dtor( - const ecs_poly_t *poly) -{ - return (ecs_poly_dtor_t*)assert_mixin(poly, EcsMixinDtor); -} + 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; + } -#include + 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; + } -#ifndef FLECS_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 }; + ecs_entity_t m = ecs_entity_init(world, &(ecs_entity_desc_t){ + .name = m_desc->name + }); -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_set(world, m, EcsMember, { + .type = m_desc->type, + .count = m_desc->count, + .offset = m_desc->offset, + .unit = m_desc->unit + }); + } - v.u = u; + ecs_set_scope(world, old_scope); - 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); + if (i == 0) { + ecs_err("struct '%s' has no members", ecs_get_name(world, t)); + ecs_delete(world, t); + return 0; } - 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 ++; + if (!ecs_has(world, t, EcsStruct)) { + /* Invalid members */ + ecs_delete(world, t); + return 0; + } - return n; + return t; } -/** Convert time to double */ -double ecs_time_to_double( - ecs_time_t t) +ecs_entity_t ecs_unit_init( + ecs_world_t *world, + const ecs_unit_desc_t *desc) { - double result; - result = t.sec; - return result + (double)t.nanosec / (double)1000000000; -} + ecs_entity_t t = desc->entity; + if (!t) { + t = ecs_new_low_id(world); + } -ecs_time_t ecs_time_sub( - ecs_time_t t1, - ecs_time_t t2) -{ - ecs_time_t result; + ecs_entity_t quantity = desc->quantity; + if (quantity) { + if (!ecs_has_id(world, quantity, EcsQuantity)) { + ecs_err("entity '%s' for unit '%s' is not a quantity", + ecs_get_name(world, quantity), ecs_get_name(world, t)); + goto error; + } - if (t1.nanosec >= t2.nanosec) { - result.nanosec = t1.nanosec - t2.nanosec; - result.sec = t1.sec - t2.sec; + ecs_add_pair(world, t, EcsQuantity, desc->quantity); } else { - result.nanosec = t1.nanosec - t2.nanosec + 1000000000; - result.sec = t1.sec - t2.sec - 1; + ecs_remove_pair(world, t, EcsQuantity, EcsWildcard); } - return result; -} + EcsUnit *value = ecs_get_mut(world, t, EcsUnit); + value->base = desc->base; + value->over = desc->over; + value->translation = desc->translation; + value->prefix = desc->prefix; + ecs_os_strset(&value->symbol, desc->symbol); -void ecs_sleepf( - double t) -{ - if (t > 0) { - int sec = (int)t; - int nsec = (int)((t - sec) * 1000000000); - ecs_os_sleep(sec, nsec); + if (!flecs_unit_validate(world, t, value)) { + goto error; } -} -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); -} + ecs_modified(world, t, EcsUnit); -void* ecs_os_memdup( - const void *src, - ecs_size_t size) -{ - if (!src) { - return NULL; + return t; +error: + if (t) { + ecs_delete(world, t); } - - void *dst = ecs_os_malloc(size); - ecs_assert(dst != NULL, ECS_OUT_OF_MEMORY, NULL); - ecs_os_memcpy(dst, src, size); - return dst; + return 0; } -int flecs_entity_compare( - ecs_entity_t e1, - const void *ptr1, - ecs_entity_t e2, - const void *ptr2) +ecs_entity_t ecs_unit_prefix_init( + ecs_world_t *world, + const ecs_unit_prefix_desc_t *desc) { - (void)ptr1; - (void)ptr2; - return (e1 > e2) - (e1 < e2); -} + ecs_entity_t t = desc->entity; + if (!t) { + t = ecs_new_low_id(world); + } -int flecs_entity_compare_qsort( - const void *e1, - const void *e2) -{ - 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_set(world, t, EcsUnitPrefix, { + .symbol = (char*)desc->symbol, + .translation = desc->translation + }); -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; + return t; } -char* ecs_vasprintf( - const char *fmt, - va_list args) +ecs_entity_t ecs_quantity_init( + ecs_world_t *world, + const ecs_entity_desc_t *desc) { - ecs_size_t size = 0; - char *result = NULL; - va_list tmpa; - - va_copy(tmpa, args); + ecs_entity_t t = ecs_entity_init(world, desc); + if (!t) { + return 0; + } - size = vsnprintf(result, 0, fmt, tmpa); + ecs_add_id(world, t, EcsQuantity); - va_end(tmpa); + return t; +} - if ((int32_t)size < 0) { - return NULL; - } +#endif - result = (char *) ecs_os_malloc(size + 1); - if (!result) { - return NULL; - } +#ifdef FLECS_META - ecs_os_vsprintf(result, fmt, args); +static +ecs_vector_t* serialize_type( + ecs_world_t *world, + ecs_entity_t type, + ecs_size_t offset, + ecs_vector_t *ops); - return result; +static +ecs_meta_type_op_kind_t primitive_to_op_kind(ecs_primitive_kind_t kind) { + return EcsOpPrimitive + kind; } -char* ecs_asprintf( - const char *fmt, - ...) -{ - va_list args; - va_start(args, fmt); - char *result = ecs_vasprintf(fmt, args); - va_end(args); - return result; +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; } -/* - 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 -int32_t type_search( - const ecs_table_t *table, - ecs_id_record_t *idr, - ecs_id_t *ids, - ecs_id_t *id_out, - ecs_table_record_t **tr_out) -{ - ecs_table_record_t *tr = ecs_table_cache_get(&idr->cache, table); - if (tr) { - int32_t r = tr->column; - if (tr_out) tr_out[0] = tr; - if (id_out) { - id_out[0] = flecs_to_public_id(ids[r]); - } - return r; - } +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; + op->unit = 0; + return op; +} - return -1; +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; } static -int32_t type_offset_search( - int32_t offset, - ecs_id_t id, - ecs_id_t *ids, - int32_t count, - ecs_id_t *id_out) +ecs_vector_t* serialize_primitive( + ecs_world_t *world, + ecs_entity_t type, + ecs_size_t offset, + ecs_vector_t *ops) { - ecs_assert(ids != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(count > 0, ECS_INTERNAL_ERROR, NULL); - ecs_assert(offset > 0, ECS_INTERNAL_ERROR, NULL); - ecs_assert(id != 0, ECS_INVALID_PARAMETER, NULL); + const EcsPrimitive *ptr = ecs_get(world, type, EcsPrimitive); + ecs_assert(ptr != NULL, ECS_INTERNAL_ERROR, NULL); - while (offset < count) { - ecs_id_t type_id = ids[offset ++]; - if (ecs_id_match(type_id, id)) { - if (id_out) { - id_out[0] = flecs_to_public_id(type_id); - } - return offset - 1; - } - } + 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 -1; + return ops; } static -bool type_can_inherit_id( - const ecs_world_t *world, - const ecs_table_t *table, - const ecs_id_record_t *idr, - ecs_id_t id) -{ - if (idr->flags & EcsIdDontInherit) { - return false; - } - if (idr->flags & EcsIdExclusive) { - if (ECS_HAS_ID_FLAG(id, PAIR)) { - ecs_entity_t er = ECS_PAIR_FIRST(id); - if (flecs_table_record_get( - world, table, ecs_pair(er, EcsWildcard))) - { - return false; - } - } - } - return true; +ecs_vector_t* serialize_enum( + ecs_world_t *world, + ecs_entity_t type, + ecs_size_t offset, + ecs_vector_t *ops) +{ + (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 -int32_t type_search_relation( - const ecs_world_t *world, - const ecs_table_t *table, - int32_t offset, - ecs_id_t id, - ecs_id_record_t *idr, - ecs_id_t rel, - ecs_id_record_t *idr_r, - bool self, - ecs_entity_t *subject_out, - ecs_id_t *id_out, - ecs_table_record_t **tr_out) +ecs_vector_t* serialize_bitmask( + ecs_world_t *world, + ecs_entity_t type, + ecs_size_t offset, + ecs_vector_t *ops) { - ecs_type_t type = table->type; - ecs_id_t *ids = type.array; - int32_t count = type.count; - - if (self) { - if (offset) { - int32_t r = type_offset_search(offset, id, ids, count, id_out); - if (r != -1) { - return r; - } - } else { - int32_t r = type_search(table, idr, ids, id_out, tr_out); - if (r != -1) { - return r; - } - } - } - - ecs_flags32_t flags = table->flags; - if ((flags & EcsTableHasPairs) && rel) { - bool is_a = rel == ecs_pair(EcsIsA, EcsWildcard); - if (is_a) { - if (!(flags & EcsTableHasIsA)) { - return -1; - } - if (!type_can_inherit_id(world, table, idr, id)) { - return -1; - } - idr_r = world->idr_isa_wildcard; - } - - if (!idr_r) { - idr_r = flecs_id_record_get(world, rel); - if (!idr_r) { - return -1; - } - } - - ecs_id_t id_r; - int32_t r, r_column; - if (offset) { - r_column = type_offset_search(offset, rel, ids, count, &id_r); - } else { - r_column = type_search(table, idr_r, ids, &id_r, 0); - } - while (r_column != -1) { - ecs_entity_t obj = ECS_PAIR_SECOND(id_r); - ecs_assert(obj != 0, ECS_INTERNAL_ERROR, NULL); - - ecs_record_t *rec = flecs_entities_get_any(world, obj); - ecs_assert(rec != NULL, ECS_INTERNAL_ERROR, NULL); - - ecs_table_t *obj_table = rec->table; - if (obj_table) { - ecs_assert(obj_table != table, ECS_CYCLE_DETECTED, NULL); - - r = type_search_relation(world, obj_table, 0, id, idr, - rel, idr_r, true, subject_out, id_out, tr_out); - if (r != -1) { - if (subject_out && !subject_out[0]) { - subject_out[0] = ecs_get_alive(world, obj); - } - return r_column; - } - - if (!is_a) { - r = type_search_relation(world, obj_table, 0, id, idr, - ecs_pair(EcsIsA, EcsWildcard), world->idr_isa_wildcard, - true, subject_out, id_out, tr_out); - if (r != -1) { - if (subject_out && !subject_out[0]) { - subject_out[0] = ecs_get_alive(world, obj); - } - return r_column; - } - } - } - - r_column = type_offset_search(r_column + 1, rel, ids, count, &id_r); - } - } + (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 -1; + return ops; } -int32_t ecs_search_relation( - const ecs_world_t *world, - const ecs_table_t *table, - int32_t offset, - ecs_id_t id, - ecs_entity_t rel, - ecs_flags32_t flags, - ecs_entity_t *subject_out, - ecs_id_t *id_out, - struct ecs_table_record_t **tr_out) +static +ecs_vector_t* serialize_array( + ecs_world_t *world, + ecs_entity_t type, + ecs_size_t offset, + ecs_vector_t *ops) { - if (!table) return -1; - - ecs_poly_assert(world, ecs_world_t); - ecs_assert(id != 0, ECS_INVALID_PARAMETER, NULL); - - flags = flags ? flags : (EcsSelf|EcsUp); - - if (!(flags & EcsUp)) { - if (subject_out) subject_out[0] = 0; - return ecs_search_offset(world, table, offset, id, id_out); - } - - ecs_id_record_t *idr = flecs_query_id_record_get(world, id); - if (!idr) { - return -1; - } + (void)world; - int32_t result = type_search_relation(world, table, offset, id, idr, - ecs_pair(rel, EcsWildcard), NULL, flags & EcsSelf, subject_out, - id_out, tr_out); + ecs_meta_type_op_t *op = ops_add(&ops, EcsOpArray); + op->offset = offset; + op->type = type; + op->size = type_size(world, type); - return result; + return ops; } -int32_t ecs_search( - const ecs_world_t *world, - const ecs_table_t *table, - ecs_id_t id, - ecs_id_t *id_out) +static +ecs_vector_t* serialize_array_component( + ecs_world_t *world, + ecs_entity_t type) { - if (!table) return -1; + 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_assert(id != 0, ECS_INVALID_PARAMETER, NULL); + ecs_vector_t *ops = serialize_type(world, ptr->type, 0, NULL); + ecs_assert(ops != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_id_record_t *idr = flecs_query_id_record_get(world, id); - if (!idr) { - return -1; - } + ecs_meta_type_op_t *first = ecs_vector_first(ops, ecs_meta_type_op_t); + first->count = ptr->count; - ecs_type_t type = table->type; - ecs_id_t *ids = type.array; - return type_search(table, idr, ids, id_out, NULL); + return ops; } -int32_t ecs_search_offset( - const ecs_world_t *world, - const ecs_table_t *table, - int32_t offset, - ecs_id_t id, - ecs_id_t *id_out) +static +ecs_vector_t* serialize_vector( + ecs_world_t *world, + ecs_entity_t type, + ecs_size_t offset, + ecs_vector_t *ops) { - if (!offset) { - return ecs_search(world, table, id, id_out); - } - - if (!table) return -1; + (void)world; - ecs_poly_assert(world, ecs_world_t); + ecs_meta_type_op_t *op = ops_add(&ops, EcsOpVector); + op->offset = offset; + op->type = type; + op->size = type_size(world, type); - ecs_type_t type = table->type; - ecs_id_t *ids = type.array; - int32_t count = type.count; - return type_offset_search(offset, id, ids, count, id_out); + return ops; } static -int32_t flecs_relation_depth_walk( - const ecs_world_t *world, - ecs_id_record_t *idr, - ecs_table_t *first, - ecs_table_t *table) +ecs_vector_t* serialize_struct( + ecs_world_t *world, + ecs_entity_t type, + ecs_size_t offset, + ecs_vector_t *ops) { - int32_t result = 0; + const EcsStruct *ptr = ecs_get(world, type, EcsStruct); + ecs_assert(ptr != NULL, ECS_INTERNAL_ERROR, NULL); - const ecs_table_record_t *tr = flecs_id_record_get_table(idr, table); - if (!tr) { - return 0; + 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) { + op->members = member_index = flecs_name_index_new(); } - int32_t i = tr->column, end = i + tr->count; - for (; i != end; i ++) { - ecs_entity_t o = ecs_pair_second(world, table->type.array[i]); - ecs_assert(o != 0, ECS_INTERNAL_ERROR, NULL); + for (i = 0; i < count; i ++) { + ecs_member_t *member = &members[i]; - ecs_table_t *ot = ecs_get_table(world, o); - if (!ot) { - continue; + 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; } - - ecs_assert(ot != first, ECS_CYCLE_DETECTED, NULL); - int32_t cur = flecs_relation_depth_walk(world, idr, first, ot); - if (cur > result) { - result = cur; + + if (op->count <= 1) { + op->count = member->count; } + + const char *member_name = member->name; + op->name = member_name; + op->unit = member->unit; + op->op_count = ecs_vector_count(ops) - cur; + + flecs_name_index_ensure( + member_index, flecs_ito(uint64_t, cur - first - 1), + member_name, 0, 0); } - - return result + 1; + + ops_add(&ops, EcsOpPop); + ops_get(ops, first)->op_count = ecs_vector_count(ops) - first; + + return ops; } -int32_t flecs_relation_depth( - const ecs_world_t *world, - ecs_entity_t r, - ecs_table_t *table) +static +ecs_vector_t* serialize_type( + ecs_world_t *world, + ecs_entity_t type, + ecs_size_t offset, + ecs_vector_t *ops) { - ecs_id_record_t *idr = flecs_id_record_get(world, ecs_pair(r, EcsWildcard)); - if (!idr) { - return 0; + 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; } - return flecs_relation_depth_walk(world, idr, table, table); -} + switch(ptr->kind) { + case EcsPrimitiveType: + ops = serialize_primitive(world, type, offset, ops); + break; + + case EcsEnumType: + ops = serialize_enum(world, type, offset, ops); + break; -#include + case EcsBitmaskType: + ops = serialize_bitmask(world, type, offset, ops); + break; -ecs_filter_t ECS_FILTER_INIT = { .hdr = { .magic = ecs_filter_t_magic }}; + case EcsStructType: + ops = serialize_struct(world, type, offset, ops); + break; -/* Helper type for passing around context required for error messages */ -typedef struct { - const ecs_world_t *world; - ecs_filter_t *filter; - ecs_term_t *term; - int32_t term_index; -} ecs_filter_finalize_ctx_t; + case EcsArrayType: + ops = serialize_array(world, type, offset, ops); + break; -static -char* flecs_filter_str( - const ecs_world_t *world, - const ecs_filter_t *filter, - const ecs_filter_finalize_ctx_t *ctx, - int32_t *term_start_out); + case EcsVectorType: + ops = serialize_vector(world, type, offset, ops); + break; + } + + return ops; +} static -void flecs_filter_error( - const ecs_filter_finalize_ctx_t *ctx, - const char *fmt, - ...) +ecs_vector_t* serialize_component( + ecs_world_t *world, + ecs_entity_t type) { - va_list args; - va_start(args, fmt); + 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; + } - int32_t term_start = 0; + ecs_vector_t *ops = NULL; - char *expr = NULL; - if (ctx->filter) { - expr = flecs_filter_str(ctx->world, ctx->filter, ctx, &term_start); - } else { - expr = ecs_term_str(ctx->world, ctx->term); - } - char *name = NULL; - if (ctx->filter) { - name = ctx->filter->name; + switch(ptr->kind) { + case EcsArrayType: + ops = serialize_array_component(world, type); + break; + default: + ops = serialize_type(world, type, 0, NULL); + break; } - ecs_parser_errorv(name, expr, term_start, fmt, args); - ecs_os_free(expr); - va_end(args); + return ops; } -static -int flecs_term_id_finalize_flags( - ecs_term_id_t *term_id, - ecs_filter_finalize_ctx_t *ctx) +void ecs_meta_type_serialized_init( + ecs_iter_t *it) { - if ((term_id->flags & EcsIsEntity) && (term_id->flags & EcsIsVariable)) { - flecs_filter_error(ctx, "cannot set both IsEntity and IsVariable"); - return -1; - } + ecs_world_t *world = it->world; - if (!(term_id->flags & EcsIsEntity) && !(term_id->flags & EcsIsVariable)) { - if (term_id->id || term_id->name) { - if (term_id->id == EcsThis || - term_id->id == EcsWildcard || - term_id->id == EcsAny || - term_id->id == EcsVariable) - { - /* Builtin variable ids default to variable */ - term_id->flags |= EcsIsVariable; - } else { - term_id->flags |= EcsIsEntity; - } + 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); + + EcsMetaTypeSerialized *ptr = ecs_get_mut( + world, e, EcsMetaTypeSerialized); + if (ptr->ops) { + ecs_meta_dtor_serialized(ptr); } - } - if (term_id->flags & EcsParent) { - term_id->flags |= EcsUp; - term_id->trav = EcsChildOf; + ptr->ops = ops; } +} - if ((term_id->flags & EcsCascade) && !(term_id->flags & (EcsUp|EcsDown))) { - term_id->flags |= EcsUp; - } +#endif - if ((term_id->flags & (EcsUp|EcsDown)) && !term_id->trav) { - term_id->trav = EcsIsA; - } - if (term_id->trav && !(term_id->flags & EcsTraverseFlags)) { - term_id->flags |= EcsUp; - } +#ifdef FLECS_META - return 0; -} +/* EcsMetaTypeSerialized lifecycle */ -static -int flecs_term_id_lookup( - const ecs_world_t *world, - ecs_entity_t scope, - ecs_term_id_t *term_id, - bool free_name, - ecs_filter_finalize_ctx_t *ctx) +void ecs_meta_dtor_serialized( + EcsMetaTypeSerialized *ptr) { - char *name = term_id->name; - if (!name) { - return 0; - } - - if (term_id->flags & EcsIsVariable) { - if (!ecs_os_strcmp(name, "This")) { - term_id->id = EcsThis; - if (free_name) { - ecs_os_free(term_id->name); - } - term_id->name = NULL; + 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_fini(op->members); + ecs_os_free(op->members); } - return 0; } - ecs_assert(term_id->flags & EcsIsEntity, ECS_INTERNAL_ERROR, NULL); + ecs_vector_free(ptr->ops); +} - if (ecs_identifier_is_0(name)) { - if (term_id->id) { - flecs_filter_error(ctx, "name '0' does not match entity id"); - return -1; - } - return 0; - } +static ECS_COPY(EcsMetaTypeSerialized, dst, src, { + ecs_meta_dtor_serialized(dst); - ecs_entity_t e = ecs_lookup_symbol(world, name, true); - if (scope && !e) { - e = ecs_lookup_child(world, scope, name); - } + dst->ops = ecs_vector_copy(src->ops, ecs_meta_type_op_t); - if (!e) { - flecs_filter_error(ctx, "unresolved identifier '%s'", name); - return -1; + 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); + flecs_hashmap_copy(op->members, op->members); + } } +}) - if (term_id->id && term_id->id != e) { - char *e_str = ecs_get_fullpath(world, term_id->id); - flecs_filter_error(ctx, "name '%s' does not match term.id '%s'", - name, e_str); - ecs_os_free(e_str); - return -1; - } +static ECS_MOVE(EcsMetaTypeSerialized, dst, src, { + ecs_meta_dtor_serialized(dst); + dst->ops = src->ops; + src->ops = NULL; +}) - term_id->id = e; +static ECS_DTOR(EcsMetaTypeSerialized, ptr, { + ecs_meta_dtor_serialized(ptr); +}) - if (!ecs_os_strcmp(name, "*") || !ecs_os_strcmp(name, "_") || - !ecs_os_strcmp(name, "$") || !ecs_os_strcmp(name, ".")) - { - term_id->flags &= ~EcsIsEntity; - term_id->flags |= EcsIsVariable; - } - /* Check if looked up id is alive (relevant for numerical ids) */ - if (!ecs_is_alive(world, term_id->id)) { - flecs_filter_error(ctx, "identifier '%s' is not alive", term_id->name); - return -1; - } +/* EcsStruct lifecycle */ - if (free_name) { - ecs_os_free(name); +static void dtor_struct( + EcsStruct *ptr) +{ + 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); +} - term_id->name = NULL; +static ECS_COPY(EcsStruct, dst, src, { + dtor_struct(dst); - return 0; -} + dst->members = ecs_vector_copy(src->members, ecs_member_t); -static -int flecs_term_ids_finalize( - const ecs_world_t *world, - ecs_term_t *term, - ecs_filter_finalize_ctx_t *ctx) -{ - ecs_term_id_t *src = &term->src; - ecs_term_id_t *first = &term->first; - ecs_term_id_t *second = &term->second; + ecs_member_t *members = ecs_vector_first(dst->members, ecs_member_t); + int32_t m, count = ecs_vector_count(dst->members); - /* Include inherited components (like from prefabs) by default for src */ - if (!(src->flags & EcsTraverseFlags)) { - src->flags |= EcsSelf | EcsUp; + for (m = 0; m < count; m ++) { + members[m].name = ecs_os_strdup(members[m].name); } +}) - /* Include subsets for component by default, to support inheritance */ - if (!(first->flags & EcsTraverseFlags)) { - first->flags |= EcsSelf | EcsDown; - } +static ECS_MOVE(EcsStruct, dst, src, { + dtor_struct(dst); + dst->members = src->members; + src->members = NULL; +}) - /* Traverse Self by default for pair target */ - if (!(second->flags & EcsTraverseFlags)) { - second->flags |= EcsSelf; - } +static ECS_DTOR(EcsStruct, ptr, { dtor_struct(ptr); }) - /* Source defaults to This */ - if ((src->id == 0) && (src->name == NULL) && !(src->flags & EcsIsEntity)) { - src->id = EcsThis; - src->flags |= EcsIsVariable; - } - /* Initialize term identifier flags */ - if (flecs_term_id_finalize_flags(src, ctx)) { - return -1; - } - if (flecs_term_id_finalize_flags(first, ctx)) { - return -1; - } +/* EcsEnum lifecycle */ - if (flecs_term_id_finalize_flags(second, ctx)) { - return -1; +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); } + ecs_map_free(ptr->constants); +} - /* Lookup term identifiers by name */ - if (flecs_term_id_lookup(world, 0, src, term->move, ctx)) { - return -1; - } - if (flecs_term_id_lookup(world, 0, first, term->move, ctx)) { - return -1; - } +static ECS_COPY(EcsEnum, dst, src, { + dtor_enum(dst); - ecs_entity_t first_id = 0; - ecs_entity_t oneof = 0; - if (first->flags & EcsIsEntity) { - first_id = first->id; + dst->constants = ecs_map_copy(src->constants); + ecs_assert(ecs_map_count(dst->constants) == ecs_map_count(src->constants), + ECS_INTERNAL_ERROR, NULL); - /* If first element of pair has OneOf property, lookup second element of - * pair in the value of the OneOf property */ - oneof = flecs_get_oneof(world, first_id); + 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 (flecs_term_id_lookup(world, oneof, &term->second, term->move, ctx)) { - return -1; - } +static ECS_MOVE(EcsEnum, dst, src, { + dtor_enum(dst); + dst->constants = src->constants; + src->constants = NULL; +}) - /* If source is 0, reset traversal flags */ - if (src->id == 0 && src->flags & EcsIsEntity) { - src->flags &= ~EcsTraverseFlags; - src->trav = 0; - } - /* If second is 0, reset traversal flags */ - if (second->id == 0 && second->flags & EcsIsEntity) { - second->flags &= ~EcsTraverseFlags; - second->trav = 0; - } +static ECS_DTOR(EcsEnum, ptr, { dtor_enum(ptr); }) - return 0; -} -static -ecs_entity_t flecs_term_id_get_entity( - const ecs_term_id_t *term_id) +/* EcsBitmask lifecycle */ + +static void dtor_bitmask( + EcsBitmask *ptr) { - if (term_id->flags & EcsIsEntity) { - return term_id->id; /* Id is known */ - } else if (term_id->flags & EcsIsVariable) { - /* Return wildcard for variables, as they aren't known yet */ - if (term_id->id != EcsAny) { - /* Any variable should not use wildcard, as this would return all - * ids matching a wildcard, whereas Any returns the first match */ - return EcsWildcard; - } else { - return EcsAny; - } - } else { - return 0; /* Term id is uninitialized */ + 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 -int flecs_term_populate_id( - ecs_term_t *term, - ecs_filter_finalize_ctx_t *ctx) -{ - ecs_entity_t first = flecs_term_id_get_entity(&term->first); - ecs_entity_t second = flecs_term_id_get_entity(&term->second); - ecs_id_t role = term->id_flags; +static ECS_COPY(EcsBitmask, dst, src, { + dtor_bitmask(dst); - if (first & ECS_ID_FLAGS_MASK) { - return -1; - } - if (second & ECS_ID_FLAGS_MASK) { - return -1; - } + dst->constants = ecs_map_copy(src->constants); + ecs_assert(ecs_map_count(dst->constants) == ecs_map_count(src->constants), + ECS_INTERNAL_ERROR, NULL); - if ((second || term->second.flags == EcsIsEntity) && !role) { - role = term->id_flags = ECS_PAIR; + 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); } +}) - if (!second && !ECS_HAS_ID_FLAG(role, PAIR)) { - term->id = first | role; - } else { - if (!ECS_HAS_ID_FLAG(role, PAIR)) { - flecs_filter_error(ctx, "invalid role for pair"); - return -1; - } +static ECS_MOVE(EcsBitmask, dst, src, { + dtor_bitmask(dst); + dst->constants = src->constants; + src->constants = NULL; +}) - term->id = ecs_pair(first, second); - } +static ECS_DTOR(EcsBitmask, ptr, { dtor_bitmask(ptr); }) - return 0; + +/* EcsUnit lifecycle */ + +static void dtor_unit( + EcsUnit *ptr) +{ + ecs_os_free(ptr->symbol); } -static -int flecs_term_populate_from_id( - const ecs_world_t *world, - ecs_term_t *term, - ecs_filter_finalize_ctx_t *ctx) +static ECS_COPY(EcsUnit, dst, src, { + dtor_unit(dst); + dst->symbol = ecs_os_strdup(src->symbol); + dst->base = src->base; + dst->over = src->over; + dst->prefix = src->prefix; + dst->translation = src->translation; +}) + +static ECS_MOVE(EcsUnit, dst, src, { + dtor_unit(dst); + dst->symbol = src->symbol; + dst->base = src->base; + dst->over = src->over; + dst->prefix = src->prefix; + dst->translation = src->translation; + + src->symbol = NULL; + src->base = 0; + src->over = 0; + src->prefix = 0; + src->translation = (ecs_unit_translation_t){0}; +}) + +static ECS_DTOR(EcsUnit, ptr, { dtor_unit(ptr); }) + + +/* EcsUnitPrefix lifecycle */ + +static void dtor_unit_prefix( + EcsUnitPrefix *ptr) { - ecs_entity_t first = 0; - ecs_entity_t second = 0; - ecs_id_t role = term->id & ECS_ID_FLAGS_MASK; + ecs_os_free(ptr->symbol); +} - if (!role && term->id_flags) { - role = term->id_flags; - term->id |= role; - } +static ECS_COPY(EcsUnitPrefix, dst, src, { + dtor_unit_prefix(dst); + dst->symbol = ecs_os_strdup(src->symbol); + dst->translation = src->translation; +}) - if (term->id_flags && term->id_flags != role) { - flecs_filter_error(ctx, "mismatch between term.id & term.id_flags"); - return -1; - } +static ECS_MOVE(EcsUnitPrefix, dst, src, { + dtor_unit_prefix(dst); + dst->symbol = src->symbol; + dst->translation = src->translation; - term->id_flags = role; + src->symbol = NULL; + src->translation = (ecs_unit_translation_t){0}; +}) - if (ECS_HAS_ID_FLAG(term->id, PAIR)) { - first = ECS_PAIR_FIRST(term->id); - second = ECS_PAIR_SECOND(term->id); +static ECS_DTOR(EcsUnitPrefix, ptr, { dtor_unit_prefix(ptr); }) - if (!first) { - flecs_filter_error(ctx, "missing first element in term.id"); - return -1; - } - if (!second) { - if (first != EcsChildOf) { - flecs_filter_error(ctx, "missing second element in term.id"); - return -1; - } else { - /* (ChildOf, 0) is allowed so filter can be used to efficiently - * query for root entities */ - } + +/* Type initialization */ + +static +int init_type( + ecs_world_t *world, + ecs_entity_t type, + ecs_type_kind_t kind, + ecs_size_t size, + ecs_size_t alignment) +{ + ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(type != 0, ECS_INTERNAL_ERROR, NULL); + + EcsMetaType *meta_type = ecs_get_mut(world, type, EcsMetaType); + if (meta_type->kind == 0) { + meta_type->existing = ecs_has(world, type, EcsComponent); + + /* Ensure that component has a default constructor, to prevent crashing + * serializers on uninitialized values. */ + ecs_type_info_t *ti = flecs_type_info_ensure(world, type); + if (!ti->hooks.ctor) { + ti->hooks.ctor = ecs_default_ctor; } } else { - first = term->id & ECS_COMPONENT_MASK; - if (!first) { - flecs_filter_error(ctx, "missing first element in term.id"); + if (meta_type->kind != kind) { + ecs_err("type '%s' reregistered with different kind", + ecs_get_name(world, type)); return -1; } } - ecs_entity_t term_first = flecs_term_id_get_entity(&term->first); - if (term_first) { - if (term_first != first) { - flecs_filter_error(ctx, "mismatch between term.id and term.first"); - return -1; - } + if (!meta_type->existing) { + EcsComponent *comp = ecs_get_mut(world, type, EcsComponent); + comp->size = size; + comp->alignment = alignment; + ecs_modified(world, type, EcsComponent); } else { - if (!(term->first.id = ecs_get_alive(world, first))) { - term->first.id = first; + const EcsComponent *comp = ecs_get(world, type, EcsComponent); + if (comp->size < size) { + ecs_err("computed size for '%s' is larger than actual type", + ecs_get_name(world, type)); + return -1; } - } - - ecs_entity_t term_second = flecs_term_id_get_entity(&term->second); - if (term_second) { - if (ecs_entity_t_lo(term_second) != second) { - flecs_filter_error(ctx, "mismatch between term.id and term.second"); + if (comp->alignment < alignment) { + ecs_err("computed alignment for '%s' is larger than actual type", + ecs_get_name(world, type)); return -1; } - } else if (second) { - if (!(term->second.id = ecs_get_alive(world, second))) { - term->second.id = second; + if (comp->size == size && comp->alignment != alignment) { + ecs_err("computed size for '%s' matches with actual type but " + "alignment is different", ecs_get_name(world, type)); + return -1; } + + meta_type->partial = comp->size != size; } + meta_type->kind = kind; + meta_type->size = size; + meta_type->alignment = alignment; + ecs_modified(world, type, EcsMetaType); + return 0; } +#define init_type_t(world, type, kind, T) \ + init_type(world, type, kind, ECS_SIZEOF(T), ECS_ALIGNOF(T)) + static -int flecs_term_verify( - const ecs_world_t *world, - const ecs_term_t *term, - ecs_filter_finalize_ctx_t *ctx) +void set_struct_member( + ecs_member_t *member, + ecs_entity_t entity, + const char *name, + ecs_entity_t type, + int32_t count, + int32_t offset, + ecs_entity_t unit) { - const ecs_term_id_t *first = &term->first; - const ecs_term_id_t *second = &term->second; - const ecs_term_id_t *src = &term->src; - ecs_entity_t first_id = 0, second_id = 0; - ecs_id_t role = term->id_flags; - ecs_id_t id = term->id; + member->member = entity; + member->type = type; + member->count = count; + member->unit = unit; + member->offset = offset; - if (first->flags & EcsIsEntity) { - first_id = first->id; - } - if (second->flags & EcsIsEntity) { - second_id = second->id; + if (!count) { + member->count = 1; } - if (role != (id & ECS_ID_FLAGS_MASK)) { - flecs_filter_error(ctx, "mismatch between term.id_flags & term.id"); - return -1; - } + ecs_os_strset((char**)&member->name, name); +} - if (ecs_term_id_is_set(second) && !ECS_HAS_ID_FLAG(role, PAIR)) { - flecs_filter_error(ctx, "expected PAIR flag for term with pair"); +static +int add_member_to_struct( + ecs_world_t *world, + ecs_entity_t type, + ecs_entity_t member, + EcsMember *m) +{ + 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; - } else if (!ecs_term_id_is_set(second) && ECS_HAS_ID_FLAG(role, PAIR)) { - if (first_id != EcsChildOf) { - flecs_filter_error(ctx, "unexpected PAIR flag for term without pair"); - return -1; - } else { - /* Exception is made for ChildOf so we can use (ChildOf, 0) to match - * all entities in the root */ - } } - if (!ecs_term_id_is_set(src)) { - flecs_filter_error(ctx, "term.src is not initialized"); + 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_term_id_is_set(first)) { - flecs_filter_error(ctx, "term.first is not initialized"); + 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 (ECS_HAS_ID_FLAG(role, PAIR)) { - if (!ECS_PAIR_FIRST(id)) { - flecs_filter_error(ctx, "invalid 0 for first element in pair id"); - return -1; - } - if ((ECS_PAIR_FIRST(id) != EcsChildOf) && !ECS_PAIR_SECOND(id)) { - flecs_filter_error(ctx, "invalid 0 for second element in pair id"); - return -1; - } + ecs_entity_t unit = m->unit; - if ((first->flags & EcsIsEntity) && - (ecs_entity_t_lo(first_id) != ECS_PAIR_FIRST(id))) - { - flecs_filter_error(ctx, "mismatch between term.id and term.first"); - return -1; - } - if ((first->flags & EcsIsVariable) && - !ecs_id_is_wildcard(ECS_PAIR_FIRST(id))) - { - char *id_str = ecs_id_str(world, id); - flecs_filter_error(ctx, - "expected wildcard for variable term.first (got %s)", id_str); - ecs_os_free(id_str); + if (unit) { + if (!ecs_has(world, unit, EcsUnit)) { + ecs_err("entity '%s' for member '%s' is not a unit", + ecs_get_name(world, unit), name); return -1; } - if ((second->flags & EcsIsEntity) && - (ecs_entity_t_lo(second_id) != ECS_PAIR_SECOND(id))) - { - flecs_filter_error(ctx, "mismatch between term.id and term.second"); - return -1; - } - if ((second->flags & EcsIsVariable) && - !ecs_id_is_wildcard(ECS_PAIR_SECOND(id))) - { - char *id_str = ecs_id_str(world, id); - flecs_filter_error(ctx, - "expected wildcard for variable term.second (got %s)", id_str); - ecs_os_free(id_str); + if (ecs_has(world, m->type, EcsUnit) && m->type != unit) { + ecs_err("unit mismatch for type '%s' and unit '%s' for member '%s'", + ecs_get_name(world, m->type), ecs_get_name(world, unit), name); return -1; } } else { - ecs_entity_t component = id & ECS_COMPONENT_MASK; - if (!component) { - flecs_filter_error(ctx, "missing component id"); - return -1; - } - if ((first->flags & EcsIsEntity) && - (ecs_entity_t_lo(first_id) != ecs_entity_t_lo(component))) - { - flecs_filter_error(ctx, "mismatch between term.id and term.first"); - return -1; - } - if ((first->flags & EcsIsVariable) && !ecs_id_is_wildcard(component)) { - char *id_str = ecs_id_str(world, id); - flecs_filter_error(ctx, - "expected wildcard for variable term.first (got %s)", id_str); - ecs_os_free(id_str); - return -1; + if (ecs_has(world, m->type, EcsUnit)) { + unit = m->type; + m->unit = unit; } } - if (first_id) { - if (ecs_term_id_is_set(second)) { - ecs_flags32_t mask = EcsIsEntity | EcsIsVariable; - if ((src->flags & mask) == (second->flags & mask)) { - bool is_same = false; - if (src->flags & EcsIsEntity) { - is_same = src->id == second->id; - } else if (src->name && second->name) { - is_same = !ecs_os_strcmp(src->name, second->name); - } - - if (is_same && ecs_has_id(world, first_id, EcsAcyclic) - && !ecs_has_id(world, first_id, EcsReflexive)) - { - char *pred_str = ecs_get_fullpath(world, term->first.id); - flecs_filter_error(ctx, "term with acyclic relationship" - " '%s' cannot have same subject and object", - pred_str); - ecs_os_free(pred_str); - return -1; - } - } - } - - if (second_id && !ecs_id_is_wildcard(second_id)) { - ecs_entity_t oneof = flecs_get_oneof(world, first_id); - if (oneof) { - if (!ecs_has_pair(world, second_id, EcsChildOf, oneof)) { - char *second_str = ecs_get_fullpath(world, second_id); - char *oneof_str = ecs_get_fullpath(world, oneof); - char *id_str = ecs_id_str(world, term->id); - flecs_filter_error(ctx, - "invalid target '%s' for %s: must be child of '%s'", - second_str, id_str, oneof_str); - ecs_os_free(second_str); - ecs_os_free(oneof_str); - ecs_os_free(id_str); - return -1; - } - } - } - } + EcsStruct *s = ecs_get_mut(world, type, EcsStruct); + ecs_assert(s != NULL, ECS_INTERNAL_ERROR, NULL); - if (term->src.trav) { - if (!ecs_has_id(world, term->src.trav, EcsAcyclic)) { - char *r_str = ecs_get_fullpath(world, term->src.trav); - flecs_filter_error(ctx, - "cannot traverse non-Acyclic relationship '%s'", r_str); - ecs_os_free(r_str); - return -1; + /* 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, m->offset, unit); + break; } } - return 0; -} - -static -int flecs_term_finalize( - const ecs_world_t *world, - ecs_term_t *term, - ecs_filter_finalize_ctx_t *ctx) -{ - ctx->term = term; - - ecs_term_id_t *src = &term->src; - ecs_term_id_t *first = &term->first; - ecs_term_id_t *second = &term->second; - ecs_flags32_t first_flags = first->flags; - ecs_flags32_t src_flags = src->flags; + /* 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, m->offset, unit); - if (term->id) { - if (flecs_term_populate_from_id(world, term, ctx)) { - return -1; - } + /* Reobtain members array in case it was reallocated */ + members = ecs_vector_first(s->members, ecs_member_t); + count ++; } - if (flecs_term_ids_finalize(world, term, ctx)) { - return -1; + bool explicit_offset = false; + if (m->offset) { + explicit_offset = true; } - /* If EcsVariable is used by itself, assign to predicate (singleton) */ - if ((src->id == EcsVariable) && (src->flags & EcsIsVariable)) { - src->id = first->id; - src->flags &= ~(EcsIsVariable | EcsIsEntity); - src->flags |= first->flags & (EcsIsVariable | EcsIsEntity); - } - if ((second->id == EcsVariable) && (second->flags & EcsIsVariable)) { - second->id = first->id; - second->flags &= ~(EcsIsVariable | EcsIsEntity); - second->flags |= first->flags & (EcsIsVariable | EcsIsEntity); - } + /* Compute member offsets and size & alignment of struct */ + ecs_size_t size = 0; + ecs_size_t alignment = 0; - if (!term->id) { - if (flecs_term_populate_id(term, ctx)) { - return -1; - } - } + if (!explicit_offset) { + for (i = 0; i < count; i ++) { + ecs_member_t *elem = &members[i]; - ecs_entity_t first_id = 0; - if (term->first.flags & EcsIsEntity) { - first_id = term->first.id; - } + ecs_assert(elem->name != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(elem->type != 0, ECS_INTERNAL_ERROR, NULL); - /* If component id is final, don't attempt component inheritance */ - if (first_id) { - if (ecs_has_id(world, first_id, EcsFinal)) { - if (first_flags & EcsDown) { - flecs_filter_error(ctx, "final id cannot be traversed down"); + /* 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; } - first->flags &= ~EcsDown; - first->trav = 0; - } - if (ecs_has_id(world, first_id, EcsDontInherit)) { - if (src_flags & (EcsUp | EcsDown)) { - flecs_filter_error(ctx, - "traversing not allowed for id that can't be inherited"); + + 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); return -1; } - src->flags &= ~(EcsUp | EcsDown); - src->trav = 0; + + 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; + } } + } else { + /* If members have explicit offsets, we can't rely on computed + * size/alignment values. Grab size of just added member instead. It + * doesn't matter if the size doesn't correspond to the actual struct + * size. The init_type function compares computed size with actual + * (component) size to determine if the type is partial. */ + const EcsComponent *cptr = ecs_get(world, m->type, EcsComponent); + ecs_assert(cptr != NULL, ECS_INTERNAL_ERROR, NULL); + size = cptr->size; + alignment = cptr->alignment; } - if (first->id == EcsVariable) { - flecs_filter_error(ctx, "invalid $ for term.first"); + if (size == 0) { + ecs_err("struct '%s' has 0 size", ecs_get_name(world, type)); return -1; } - if (ECS_HAS_ID_FLAG(term->id_flags, AND) || - ECS_HAS_ID_FLAG(term->id_flags, OR) || - ECS_HAS_ID_FLAG(term->id_flags, NOT)) - { - if (term->inout != EcsInOutDefault && term->inout != EcsInOutNone) { - flecs_filter_error(ctx, "AND/OR terms must be filters"); - return -1; - } - - term->inout = EcsInOutNone; + if (alignment == 0) { + ecs_err("struct '%s' has 0 alignment", ecs_get_name(world, type)); + return -1; + } - /* Translate role to operator */ - if (ECS_HAS_ID_FLAG(term->id_flags, AND)) { - term->oper = EcsAndFrom; - } else - if (ECS_HAS_ID_FLAG(term->id_flags, OR)) { - term->oper = EcsOrFrom; - } else - if (ECS_HAS_ID_FLAG(term->id_flags, NOT)) { - term->oper = EcsNotFrom; - } + /* Align struct size to struct alignment */ + size = ECS_ALIGN(size, alignment); - /* Zero out role & strip from id */ - term->id &= ECS_COMPONENT_MASK; - term->id_flags = 0; - } + ecs_modified(world, type, EcsStruct); - if (flecs_term_verify(world, term, ctx)) { + /* Do this last as it triggers the update of EcsMetaTypeSerialized */ + if (init_type(world, type, EcsStructType, size, alignment)) { return -1; } - return 0; -} + /* 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); + ecs_assert(type_mbr != NULL, ECS_INTERNAL_ERROR, NULL); -ecs_id_t flecs_to_public_id( - ecs_id_t id) -{ - if (ECS_PAIR_FIRST(id) == EcsUnion) { - return ecs_pair(ECS_PAIR_SECOND(id), EcsWildcard); - } else { - return id; - } -} + type_mbr->type = type; + type_mbr->count = 1; -ecs_id_t flecs_from_public_id( - ecs_world_t *world, - ecs_id_t id) -{ - if (ECS_HAS_ID_FLAG(id, PAIR)) { - ecs_entity_t first = ECS_PAIR_FIRST(id); - ecs_id_record_t *idr = flecs_id_record_ensure(world, - ecs_pair(first, EcsWildcard)); - if (idr->flags & EcsIdUnion) { - return ecs_pair(EcsUnion, first); - } + ecs_modified(world, type, EcsMember); } - return id; + return 0; } -bool ecs_identifier_is_0( - const char *id) +static +int add_constant_to_enum( + ecs_world_t *world, + ecs_entity_t type, + ecs_entity_t e, + ecs_id_t constant_id) { - return id[0] == '0' && !id[1]; -} + EcsEnum *ptr = ecs_get_mut(world, type, EcsEnum); + + /* 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; -bool ecs_id_match( - ecs_id_t id, - ecs_id_t pattern) -{ - if (id == pattern) { - return true; + 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 (ECS_HAS_ID_FLAG(pattern, PAIR)) { - if (!ECS_HAS_ID_FLAG(id, PAIR)) { - return false; + /* Check if constant sets explicit value */ + int32_t value = 0; + bool value_set = false; + if (ecs_id_is_pair(constant_id)) { + if (ecs_pair_second(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; } - ecs_entity_t id_rel = ECS_PAIR_FIRST(id); - ecs_entity_t id_obj = ECS_PAIR_SECOND(id); - ecs_entity_t pattern_rel = ECS_PAIR_FIRST(pattern); - ecs_entity_t pattern_obj = ECS_PAIR_SECOND(pattern); - - ecs_check(id_rel != 0, ECS_INVALID_PARAMETER, NULL); - ecs_check(id_obj != 0, ECS_INVALID_PARAMETER, NULL); + 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; + } - 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_rel == EcsFlag) { - /* Used for internals, helps to keep track of which ids are used in - * pairs that have additional flags (like OVERRIDE and TOGGLE) */ - if (ECS_HAS_ID_FLAG(id, PAIR) && !ECS_IS_PAIR(id)) { - if (ECS_PAIR_FIRST(id) == pattern_obj) { - return true; - } - if (ECS_PAIR_SECOND(id) == pattern_obj) { - return true; - } + /* 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 (pattern_obj == EcsWildcard) { - if (pattern_rel == id_rel) { - return true; + } else { + if (c->value >= value) { + value = c->value + 1; } } - } else { - if ((id & ECS_ID_FLAGS_MASK) != (pattern & ECS_ID_FLAGS_MASK)) { - return false; - } - - if ((ECS_COMPONENT_MASK & pattern) == EcsWildcard) { - return true; - } } -error: - return false; -} - -bool ecs_id_is_pair( - ecs_id_t id) -{ - return ECS_HAS_ID_FLAG(id, PAIR); -} - -bool ecs_id_is_wildcard( - ecs_id_t id) -{ - if ((id == EcsWildcard) || (id == EcsAny)) { - return true; + if (!ptr->constants) { + ptr->constants = ecs_map_new(ecs_enum_constant_t, 1); } - bool is_pair = ECS_IS_PAIR(id); - if (!is_pair) { - return false; - } + 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_entity_t first = ECS_PAIR_FIRST(id); - ecs_entity_t second = ECS_PAIR_SECOND(id); + ecs_i32_t *cptr = ecs_get_mut_pair_object( + world, e, EcsConstant, ecs_i32_t); + ecs_assert(cptr != NULL, ECS_INTERNAL_ERROR, NULL); + cptr[0] = value; - return (first == EcsWildcard) || (second == EcsWildcard) || - (first == EcsAny) || (second == EcsAny); + return 0; } -bool ecs_id_is_valid( - const ecs_world_t *world, - ecs_id_t id) +static +int add_constant_to_bitmask( + ecs_world_t *world, + ecs_entity_t type, + ecs_entity_t e, + ecs_id_t constant_id) { - if (!id) { - return false; - } - if (ecs_id_is_wildcard(id)) { - return false; - } - - world = ecs_get_world(world); - const ecs_id_record_t *idr = flecs_id_record_get(world, id); - if (idr && idr->flags & EcsIdMarkedForDelete) { - return false; - } - - if (ECS_HAS_ID_FLAG(id, PAIR)) { - if (!ECS_PAIR_FIRST(id)) { - return false; - } - if (!ECS_PAIR_SECOND(id)) { - return false; - } - } else if (id & ECS_ID_FLAGS_MASK) { - if (!ecs_is_valid(world, id & ECS_COMPONENT_MASK)) { - return false; + EcsBitmask *ptr = ecs_get_mut(world, type, EcsBitmask); + + /* 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 true; -} + /* Check if constant sets explicit value */ + uint32_t value = 1; + if (ecs_id_is_pair(constant_id)) { + if (ecs_pair_second(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; + } -ecs_flags32_t ecs_id_get_flags( - const ecs_world_t *world, - ecs_id_t id) -{ - ecs_id_record_t *idr = flecs_id_record_get(world, id); - if (idr) { - return idr->flags; + 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 0; + value = 1u << (ecs_u32_t)ecs_map_count(ptr->constants); } -} - -bool ecs_term_id_is_set( - const ecs_term_id_t *id) -{ - return id->id != 0 || id->name != NULL || id->flags & EcsIsEntity; -} -bool ecs_term_is_initialized( - const ecs_term_t *term) -{ - return term->id != 0 || ecs_term_id_is_set(&term->first); -} + /* 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; + } + } -bool ecs_term_match_this( - const ecs_term_t *term) -{ - return (term->src.flags & EcsIsVariable) && (term->src.id == EcsThis); -} + if (!ptr->constants) { + ptr->constants = ecs_map_new(ecs_bitmask_constant_t, 1); + } -bool ecs_term_match_0( - const ecs_term_t *term) -{ - return (term->src.id == 0) && (term->src.flags & EcsIsEntity); -} + 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; -int ecs_term_finalize( - const ecs_world_t *world, - ecs_term_t *term) -{ - ecs_filter_finalize_ctx_t ctx = {0}; - ctx.world = world; - ctx.term = term; - return flecs_term_finalize(world, term, &ctx); -} + ecs_u32_t *cptr = ecs_get_mut_pair_object( + world, e, EcsConstant, ecs_u32_t); + ecs_assert(cptr != NULL, ECS_INTERNAL_ERROR, NULL); + cptr[0] = value; -ecs_term_t ecs_term_copy( - const ecs_term_t *src) -{ - ecs_term_t dst = *src; - dst.name = ecs_os_strdup(src->name); - dst.first.name = ecs_os_strdup(src->first.name); - dst.src.name = ecs_os_strdup(src->src.name); - dst.second.name = ecs_os_strdup(src->second.name); - return dst; + return 0; } -ecs_term_t ecs_term_move( - ecs_term_t *src) -{ - if (src->move) { - ecs_term_t dst = *src; - src->name = NULL; - src->first.name = NULL; - src->src.name = NULL; - src->second.name = NULL; - dst.move = false; - return dst; - } else { - ecs_term_t dst = ecs_term_copy(src); - dst.move = false; - return dst; - } -} +static +void set_primitive(ecs_iter_t *it) { + ecs_world_t *world = it->world; + EcsPrimitive *type = ecs_field(it, EcsPrimitive, 1); -void ecs_term_fini( - ecs_term_t *term) -{ - ecs_os_free(term->first.name); - ecs_os_free(term->src.name); - ecs_os_free(term->second.name); - ecs_os_free(term->name); + int i, count = it->count; + for (i = 0; i < count; i ++) { + ecs_entity_t e = it->entities[i]; + switch(type->kind) { + case EcsBool: + init_type_t(world, e, EcsPrimitiveType, bool); + break; + case EcsChar: + init_type_t(world, e, EcsPrimitiveType, char); + break; + case EcsByte: + init_type_t(world, e, EcsPrimitiveType, bool); + break; + case EcsU8: + init_type_t(world, e, EcsPrimitiveType, uint8_t); + break; + case EcsU16: + init_type_t(world, e, EcsPrimitiveType, uint16_t); + break; + case EcsU32: + init_type_t(world, e, EcsPrimitiveType, uint32_t); + break; + case EcsU64: + init_type_t(world, e, EcsPrimitiveType, uint64_t); + break; + case EcsI8: + init_type_t(world, e, EcsPrimitiveType, int8_t); + break; + case EcsI16: + init_type_t(world, e, EcsPrimitiveType, int16_t); + break; + case EcsI32: + init_type_t(world, e, EcsPrimitiveType, int32_t); + break; + case EcsI64: + init_type_t(world, e, EcsPrimitiveType, int64_t); + break; + case EcsF32: + init_type_t(world, e, EcsPrimitiveType, float); + break; + case EcsF64: + init_type_t(world, e, EcsPrimitiveType, double); + break; + case EcsUPtr: + init_type_t(world, e, EcsPrimitiveType, uintptr_t); + break; + case EcsIPtr: + init_type_t(world, e, EcsPrimitiveType, intptr_t); + break; + case EcsString: + init_type_t(world, e, EcsPrimitiveType, char*); + break; + case EcsEntity: + init_type_t(world, e, EcsPrimitiveType, ecs_entity_t); + break; + } + } +} - term->first.name = NULL; - term->src.name = NULL; - term->second.name = NULL; - term->name = NULL; +static +void set_member(ecs_iter_t *it) { + ecs_world_t *world = it->world; + EcsMember *member = ecs_field(it, EcsMember, 1); + + int i, count = it->count; + for (i = 0; i < count; i ++) { + ecs_entity_t e = it->entities[i]; + ecs_entity_t parent = ecs_get_target(world, e, EcsChildOf, 0); + if (!parent) { + ecs_err("missing parent for member '%s'", ecs_get_name(world, e)); + continue; + } + + add_member_to_struct(world, parent, e, &member[i]); + } } -int ecs_filter_finalize( - const ecs_world_t *world, - ecs_filter_t *f) -{ - int32_t i, term_count = f->term_count, field_count = 0; - ecs_term_t *terms = f->terms; - bool is_or = false, prev_or = false; - ecs_flags32_t prev_src_flags = 0; - ecs_entity_t prev_src_id = 0; - int32_t filter_terms = 0; +static +void add_enum(ecs_iter_t *it) { + ecs_world_t *world = it->world; - ecs_filter_finalize_ctx_t ctx = {0}; - ctx.world = world; - ctx.filter = f; + int i, count = it->count; + for (i = 0; i < count; i ++) { + ecs_entity_t e = it->entities[i]; - f->flags |= EcsFilterMatchOnlyThis; - for (i = 0; i < term_count; i ++) { - ecs_term_t *term = &terms[i]; - ctx.term_index = i; - if (flecs_term_finalize(world, term, &ctx)) { - return -1; + if (init_type_t(world, e, EcsEnumType, ecs_i32_t)) { + continue; } - is_or = term->oper == EcsOr; - field_count += !(is_or && prev_or); - term->field_index = field_count - 1; + ecs_add_id(world, e, EcsExclusive); + ecs_add_id(world, e, EcsOneOf); + ecs_add_id(world, e, EcsTag); + } +} - if (prev_or && is_or) { - if (prev_src_flags != term->src.flags) { - flecs_filter_error(&ctx, "mismatching src.flags for OR terms"); - return -1; - } - if (prev_src_id != term->src.id) { - flecs_filter_error(&ctx, "mismatching src.id for OR terms"); - return -1; - } - } +static +void add_bitmask(ecs_iter_t *it) { + ecs_world_t *world = it->world; - prev_src_flags = term->src.flags; - prev_src_id = term->src.id; - prev_or = is_or; + int i, count = it->count; + for (i = 0; i < count; i ++) { + ecs_entity_t e = it->entities[i]; - if (ecs_term_match_this(term)) { - ECS_BIT_SET(f->flags, EcsFilterMatchThis); - } else { - ECS_BIT_CLEAR(f->flags, EcsFilterMatchOnlyThis); + if (init_type_t(world, e, EcsBitmaskType, ecs_u32_t)) { + continue; } + } +} - if (term->id == EcsPrefab) { - ECS_BIT_SET(f->flags, EcsFilterMatchPrefab); - } - if (term->id == EcsDisabled) { - ECS_BIT_SET(f->flags, EcsFilterMatchDisabled); - } +static +void add_constant(ecs_iter_t *it) { + ecs_world_t *world = it->world; - if (ECS_BIT_IS_SET(f->flags, EcsFilterIsFilter)) { - term->inout = EcsInOutNone; + int i, count = it->count; + for (i = 0; i < count; i ++) { + ecs_entity_t e = it->entities[i]; + ecs_entity_t parent = ecs_get_target(world, e, EcsChildOf, 0); + if (!parent) { + ecs_err("missing parent for constant '%s'", ecs_get_name(world, e)); + continue; } - if (term->inout == EcsInOutNone) { - filter_terms ++; + 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 (term->oper != EcsNot || !ecs_term_match_this(term)) { - ECS_BIT_CLEAR(f->flags, EcsFilterMatchAnything); +static +void set_array(ecs_iter_t *it) { + ecs_world_t *world = it->world; + EcsArray *array = ecs_field(it, EcsArray, 1); + + 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; + + if (!elem_type) { + ecs_err("array '%s' has no element type", ecs_get_name(world, e)); + continue; } - } - f->field_count = field_count; + if (!elem_count) { + ecs_err("array '%s' has size 0", ecs_get_name(world, e)); + continue; + } - if (filter_terms == term_count) { - ECS_BIT_SET(f->flags, EcsFilterIsFilter); + const EcsComponent *elem_ptr = ecs_get(world, elem_type, EcsComponent); + if (init_type(world, e, EcsArrayType, + elem_ptr->size * elem_count, elem_ptr->alignment)) + { + continue; + } } - - return 0; } -/* Implementation for iterable mixin */ static -void flecs_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); +void set_vector(ecs_iter_t *it) { + ecs_world_t *world = it->world; + EcsVector *array = ecs_field(it, EcsVector, 1); - 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 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 (!elem_type) { + ecs_err("vector '%s' has no element type", ecs_get_name(world, e)); + continue; + } + + if (init_type_t(world, e, EcsVectorType, ecs_vector_t*)) { + continue; + } } } -ecs_filter_t* ecs_filter_init( - const ecs_world_t *stage, - const ecs_filter_desc_t *desc) +bool flecs_unit_validate( + ecs_world_t *world, + ecs_entity_t t, + EcsUnit *data) { - ecs_check(stage != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(desc->_canary == 0, ECS_INVALID_PARAMETER, NULL); - const ecs_world_t *world = ecs_get_world(stage); + char *derived_symbol = NULL; + const char *symbol = data->symbol; - ecs_filter_t *f = desc->storage; - int32_t i, term_count = desc->terms_buffer_count, storage_count = 0, expr_count = 0; - const ecs_term_t *terms = desc->terms_buffer; - ecs_term_t *storage_terms = NULL, *expr_terms = NULL; + ecs_entity_t base = data->base; + ecs_entity_t over = data->over; + ecs_entity_t prefix = data->prefix; + ecs_unit_translation_t translation = data->translation; - if (f) { - ecs_check(f->hdr.magic == ecs_filter_t_magic, - ECS_INVALID_PARAMETER, NULL); - storage_count = f->term_count; - storage_terms = f->terms; - ecs_poly_init(f, ecs_filter_t); - } else { - f = ecs_poly_new(ecs_filter_t); - f->owned = true; - } - if (!storage_terms) { - f->terms_owned = true; + if (base) { + if (!ecs_has(world, base, EcsUnit)) { + ecs_err("entity '%s' for unit '%s' used as base is not a unit", + ecs_get_name(world, base), ecs_get_name(world, t)); + goto error; + } } - ECS_BIT_COND(f->flags, EcsFilterIsInstanced, desc->instanced); - ECS_BIT_SET(f->flags, EcsFilterMatchAnything); - f->flags |= desc->flags; - - /* If terms_buffer was not set, count number of initialized terms in - * static desc::terms array */ - if (!terms) { - ecs_check(term_count == 0, ECS_INVALID_PARAMETER, NULL); - terms = desc->terms; - for (i = 0; i < ECS_TERM_DESC_CACHE_SIZE; i ++) { - if (!ecs_term_is_initialized(&terms[i])) { - break; - } - term_count ++; + if (over) { + if (!base) { + ecs_err("invalid unit '%s': cannot specify over without base", + ecs_get_name(world, t)); + goto error; + } + if (!ecs_has(world, over, EcsUnit)) { + ecs_err("entity '%s' for unit '%s' used as over is not a unit", + ecs_get_name(world, over), ecs_get_name(world, t)); + goto error; } - } else { - ecs_check(term_count != 0, ECS_INVALID_PARAMETER, NULL); } - /* If expr is set, parse query expression */ - const char *name = desc->name, *expr = desc->expr; - if (expr) { -#ifdef FLECS_PARSER - const char *ptr = desc->expr; - ecs_term_t term = {0}; - int32_t expr_size = 0; - while (ptr[0] && (ptr = ecs_parse_term(world, name, expr, ptr, &term))){ - if (!ecs_term_is_initialized(&term)) { - break; - } + if (prefix) { + if (!base) { + ecs_err("invalid unit '%s': cannot specify prefix without base", + ecs_get_name(world, t)); + goto error; + } + const EcsUnitPrefix *prefix_ptr = ecs_get(world, prefix, EcsUnitPrefix); + if (!prefix_ptr) { + ecs_err("entity '%s' for unit '%s' used as prefix is not a prefix", + ecs_get_name(world, over), ecs_get_name(world, t)); + goto error; + } - if (expr_count == expr_size) { - expr_size = expr_size ? expr_size * 2 : 8; - expr_terms = ecs_os_realloc_n(expr_terms, ecs_term_t, expr_size); + if (translation.factor || translation.power) { + if (prefix_ptr->translation.factor != translation.factor || + prefix_ptr->translation.power != translation.power) + { + ecs_err( + "factor for unit '%s' is inconsistent with prefix '%s'", + ecs_get_name(world, t), ecs_get_name(world, prefix)); + goto error; } + } else { + translation = prefix_ptr->translation; + } + } - expr_terms[expr_count ++] = term; - if (ptr[0] == '\n') { - break; + if (base) { + bool must_match = false; /* Must base symbol match symbol? */ + ecs_strbuf_t sbuf = ECS_STRBUF_INIT; + if (prefix) { + const EcsUnitPrefix *ptr = ecs_get(world, prefix, EcsUnitPrefix); + ecs_assert(ptr != NULL, ECS_INTERNAL_ERROR, NULL); + if (ptr->symbol) { + ecs_strbuf_appendstr(&sbuf, ptr->symbol); + must_match = true; } } - if (!ptr) { - /* Set terms in filter object to make sur they get cleaned up */ - f->terms = expr_terms; - f->term_count = expr_count; - f->terms_owned = true; - goto error; + const EcsUnit *uptr = ecs_get(world, base, EcsUnit); + ecs_assert(uptr != NULL, ECS_INTERNAL_ERROR, NULL); + if (uptr->symbol) { + ecs_strbuf_appendstr(&sbuf, uptr->symbol); } -#else - (void)expr; - ecs_abort(ECS_UNSUPPORTED, "parser addon is not available"); -#endif - } - - /* If storage is provided, make sure it's large enough */ - ecs_check(!storage_terms || storage_count >= (term_count + expr_count), - ECS_INVALID_PARAMETER, NULL); - if (term_count || expr_count) { - /* If no storage is provided, create it */ - if (!storage_terms) { - f->terms = ecs_os_calloc_n(ecs_term_t, term_count + expr_count); - f->term_count = term_count + expr_count; - ecs_assert(f->terms_owned == true, ECS_INTERNAL_ERROR, NULL); - } else { - f->terms = storage_terms; - f->term_count = storage_count; + if (over) { + uptr = ecs_get(world, over, EcsUnit); + ecs_assert(uptr != NULL, ECS_INTERNAL_ERROR, NULL); + if (uptr->symbol) { + ecs_strbuf_appendstr(&sbuf, "/"); + ecs_strbuf_appendstr(&sbuf, uptr->symbol); + must_match = true; + } } - /* Copy terms to filter storage */ - for (i = 0; i < term_count; i ++) { - f->terms[i] = ecs_term_copy(&terms[i]); - /* Allow freeing resources from expr parser during finalization */ - f->terms[i].move = true; + derived_symbol = ecs_strbuf_get(&sbuf); + if (derived_symbol && !ecs_os_strlen(derived_symbol)) { + ecs_os_free(derived_symbol); + derived_symbol = NULL; } - /* Move expr terms to filter storage */ - for (i = 0; i < expr_count; i ++) { - f->terms[i + term_count] = ecs_term_move(&expr_terms[i]); - /* Allow freeing resources from expr parser during finalization */ - f->terms[i + term_count].move = true; + if (derived_symbol && symbol && ecs_os_strcmp(symbol, derived_symbol)) { + if (must_match) { + ecs_err("symbol '%s' for unit '%s' does not match base" + " symbol '%s'", symbol, + ecs_get_name(world, t), derived_symbol); + goto error; + } + } + if (!symbol && derived_symbol && (prefix || over)) { + ecs_os_free(data->symbol); + data->symbol = derived_symbol; + } else { + ecs_os_free(derived_symbol); } - ecs_os_free(expr_terms); - } - - /* Ensure all fields are consistent and properly filled out */ - if (ecs_filter_finalize(world, f)) { - goto error; - } - - /* Any allocated resources remaining in terms are now owned by filter */ - for (i = 0; i < f->term_count; i ++) { - f->terms[i].move = false; } - f->name = ecs_os_strdup(name); - f->variable_names[0] = (char*)"."; - f->iterable.init = flecs_filter_iter_init; + data->base = base; + data->over = over; + data->prefix = prefix; + data->translation = translation; - return f; + return true; error: - ecs_filter_fini(f); - return NULL; + ecs_os_free(derived_symbol); + return false; } -void ecs_filter_copy( - ecs_filter_t *dst, - const ecs_filter_t *src) -{ - if (src == dst) { - return; - } - - if (src) { - *dst = *src; +static +void set_unit(ecs_iter_t *it) { + EcsUnit *u = ecs_field(it, EcsUnit, 1); - int32_t i, term_count = src->term_count; - dst->terms = ecs_os_malloc_n(ecs_term_t, term_count); - dst->terms_owned = true; + ecs_world_t *world = it->world; - 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); + int i, count = it->count; + for (i = 0; i < count; i ++) { + ecs_entity_t e = it->entities[i]; + flecs_unit_validate(world, e, &u[i]); } } -void ecs_filter_move( - ecs_filter_t *dst, - ecs_filter_t *src) -{ - if (src == dst) { - return; - } +static +void unit_quantity_monitor(ecs_iter_t *it) { + ecs_world_t *world = it->world; - if (src) { - *dst = *src; - if (src->terms_owned) { - dst->terms = src->terms; - dst->terms_owned = true; - } else { - ecs_filter_copy(dst, src); + int i, count = it->count; + if (it->event == EcsOnAdd) { + for (i = 0; i < count; i ++) { + ecs_entity_t e = it->entities[i]; + ecs_add_pair(world, e, EcsQuantity, e); } - src->terms = NULL; - src->term_count = 0; } else { - ecs_os_memset_t(dst, 0, ecs_filter_t); - } -} - -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]); - } - - if (filter->terms_owned) { - ecs_os_free(filter->terms); + ecs_entity_t e = it->entities[i]; + ecs_remove_pair(world, e, EcsQuantity, e); } } - - ecs_os_free(filter->name); - - filter->terms = NULL; - filter->name = NULL; - - if (filter->owned) { - ecs_os_free(filter); - } } static -void filter_str_add_id( - const ecs_world_t *world, - ecs_strbuf_t *buf, - const ecs_term_id_t *id, - bool is_subject, - ecs_flags32_t default_traverse_flags) -{ - bool is_added = false; - if (!is_subject || id->id != EcsThis) { - if (id->flags & EcsIsVariable) { - ecs_strbuf_appendstr(buf, "$"); - } - if (id->id) { - char *path = ecs_get_fullpath(world, id->id); - ecs_strbuf_appendstr(buf, path); - ecs_os_free(path); - } else if (id->name) { - ecs_strbuf_appendstr(buf, id->name); - } else { - ecs_strbuf_appendstr(buf, "0"); +void ecs_meta_type_init_default_ctor(ecs_iter_t *it) { + ecs_world_t *world = it->world; + EcsMetaType *type = ecs_field(it, EcsMetaType, 1); + + int i; + for (i = 0; i < it->count; i ++) { + /* If a component is defined from reflection data, configure it with the + * default constructor. This ensures that a new component value does not + * contain uninitialized memory, which could cause serializers to crash + * when for example inspecting string fields. */ + if (!type->existing) { + ecs_set_hooks_id(world, it->entities[i], + &(ecs_type_hooks_t){ + .ctor = ecs_default_ctor + }); } - is_added = true; } +} - ecs_flags32_t flags = id->flags; - if (!(flags & EcsTraverseFlags)) { - /* If flags haven't been set yet, initialize with defaults. This can - * happen if an error is thrown while the term is being finalized */ - flags |= default_traverse_flags; +static +void member_on_set(ecs_iter_t *it) { + EcsMember *mbr = it->ptrs[0]; + if (!mbr->count) { + mbr->count = 1; } +} - if ((flags & EcsTraverseFlags) != default_traverse_flags) { - if (is_added) { - ecs_strbuf_list_push(buf, ":", "|"); - } else { - ecs_strbuf_list_push(buf, "", "|"); - } - if (id->flags & EcsSelf) { - ecs_strbuf_list_appendstr(buf, "self"); - } - if (id->flags & EcsUp) { - ecs_strbuf_list_appendstr(buf, "up"); - } - if (id->flags & EcsDown) { - ecs_strbuf_list_appendstr(buf, "down"); - } +void FlecsMetaImport( + ecs_world_t *world) +{ + ECS_MODULE(world, FlecsMeta); - if (id->trav && (id->trav != EcsIsA)) { - ecs_strbuf_list_push(buf, "(", ""); + ecs_set_name_prefix(world, "Ecs"); - char *rel_path = ecs_get_fullpath(world, id->trav); - ecs_strbuf_appendstr(buf, rel_path); - ecs_os_free(rel_path); + 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); + flecs_bootstrap_component(world, EcsUnit); + flecs_bootstrap_component(world, EcsUnitPrefix); - ecs_strbuf_list_pop(buf, ")"); - } + flecs_bootstrap_tag(world, EcsConstant); + flecs_bootstrap_tag(world, EcsQuantity); - ecs_strbuf_list_pop(buf, ""); - } -} + ecs_set_hooks(world, EcsMetaType, { .ctor = ecs_default_ctor }); -static -void term_str_w_strbuf( - const ecs_world_t *world, - const ecs_term_t *term, - ecs_strbuf_t *buf) -{ - const ecs_term_id_t *src = &term->src; - const ecs_term_id_t *second = &term->second; + ecs_set_hooks(world, EcsMetaTypeSerialized, { + .ctor = ecs_default_ctor, + .move = ecs_move(EcsMetaTypeSerialized), + .copy = ecs_copy(EcsMetaTypeSerialized), + .dtor = ecs_dtor(EcsMetaTypeSerialized) + }); - uint8_t def_src_mask = EcsSelf|EcsUp; - uint8_t def_first_mask = EcsSelf|EcsDown; - uint8_t def_second_mask = EcsSelf; + ecs_set_hooks(world, EcsStruct, { + .ctor = ecs_default_ctor, + .move = ecs_move(EcsStruct), + .copy = ecs_copy(EcsStruct), + .dtor = ecs_dtor(EcsStruct) + }); - bool pred_set = ecs_term_id_is_set(&term->first); - bool subj_set = !ecs_term_match_0(term); - bool obj_set = ecs_term_id_is_set(second); + ecs_set_hooks(world, EcsMember, { + .ctor = ecs_default_ctor, + .on_set = member_on_set + }); - if (term->first.flags & EcsIsEntity && term->first.id != 0) { - if (ecs_has_id(world, term->first.id, EcsFinal)) { - def_first_mask = EcsSelf; - } - if (ecs_has_id(world, term->first.id, EcsDontInherit)) { - def_src_mask = EcsSelf; - } - } + ecs_set_hooks(world, EcsEnum, { + .ctor = ecs_default_ctor, + .move = ecs_move(EcsEnum), + .copy = ecs_copy(EcsEnum), + .dtor = ecs_dtor(EcsEnum) + }); - if (term->oper == EcsNot) { - ecs_strbuf_appendstr(buf, "!"); - } else if (term->oper == EcsOptional) { - ecs_strbuf_appendstr(buf, "?"); - } + ecs_set_hooks(world, EcsBitmask, { + .ctor = ecs_default_ctor, + .move = ecs_move(EcsBitmask), + .copy = ecs_copy(EcsBitmask), + .dtor = ecs_dtor(EcsBitmask) + }); - if (!subj_set) { - filter_str_add_id(world, buf, &term->first, false, def_first_mask); - ecs_strbuf_appendstr(buf, "()"); - } else if (ecs_term_match_this(term) && - (src->flags & EcsTraverseFlags) == def_src_mask) - { - if (pred_set) { - if (obj_set) { - ecs_strbuf_appendstr(buf, "("); - } - filter_str_add_id(world, buf, &term->first, false, def_first_mask); - if (obj_set) { - ecs_strbuf_appendstr(buf, ","); - filter_str_add_id( - world, buf, &term->second, false, def_second_mask); - ecs_strbuf_appendstr(buf, ")"); - } - } else if (term->id) { - char *str = ecs_id_str(world, term->id); - ecs_strbuf_appendstr(buf, str); - ecs_os_free(str); - } - } else { - if (term->id_flags && !ECS_HAS_ID_FLAG(term->id_flags, PAIR)) { - ecs_strbuf_appendstr(buf, ecs_id_flag_str(term->id_flags)); - ecs_strbuf_appendch(buf, '|'); - } + ecs_set_hooks(world, EcsUnit, { + .ctor = ecs_default_ctor, + .move = ecs_move(EcsUnit), + .copy = ecs_copy(EcsUnit), + .dtor = ecs_dtor(EcsUnit) + }); - filter_str_add_id(world, buf, &term->first, false, def_first_mask); - ecs_strbuf_appendstr(buf, "("); - if (term->src.flags & EcsIsEntity && term->src.id == term->first.id) { - ecs_strbuf_appendstr(buf, "$"); - } else { - filter_str_add_id(world, buf, &term->src, true, def_src_mask); - } - if (obj_set) { - ecs_strbuf_appendstr(buf, ","); - filter_str_add_id(world, buf, &term->second, false, def_second_mask); - } - ecs_strbuf_appendstr(buf, ")"); - } -} + ecs_set_hooks(world, EcsUnitPrefix, { + .ctor = ecs_default_ctor, + .move = ecs_move(EcsUnitPrefix), + .copy = ecs_copy(EcsUnitPrefix), + .dtor = ecs_dtor(EcsUnitPrefix) + }); -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); -} + /* Register triggers to finalize type information from component data */ + ecs_entity_t old_scope = ecs_set_scope( /* Keep meta scope clean */ + world, EcsFlecsInternals); + ecs_observer_init(world, &(ecs_observer_desc_t){ + .filter.terms[0] = { .id = ecs_id(EcsPrimitive), .src.flags = EcsSelf }, + .events = {EcsOnSet}, + .callback = set_primitive + }); -static -char* flecs_filter_str( - const ecs_world_t *world, - const ecs_filter_t *filter, - const ecs_filter_finalize_ctx_t *ctx, - int32_t *term_start_out) -{ - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(filter != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_observer_init(world, &(ecs_observer_desc_t){ + .filter.terms[0] = { .id = ecs_id(EcsMember), .src.flags = EcsSelf }, + .events = {EcsOnSet}, + .callback = set_member + }); - ecs_strbuf_t buf = ECS_STRBUF_INIT; - ecs_term_t *terms = filter->terms; - int32_t i, count = filter->term_count; - int32_t or_count = 0; + ecs_observer_init(world, &(ecs_observer_desc_t){ + .filter.terms[0] = { .id = ecs_id(EcsEnum), .src.flags = EcsSelf }, + .events = {EcsOnAdd}, + .callback = add_enum + }); - for (i = 0; i < count; i ++) { - ecs_term_t *term = &terms[i]; + ecs_observer_init(world, &(ecs_observer_desc_t){ + .filter.terms[0] = { .id = ecs_id(EcsBitmask), .src.flags = EcsSelf }, + .events = {EcsOnAdd}, + .callback = add_bitmask + }); - if (term_start_out && ctx) { - if (ctx->term_index == i) { - term_start_out[0] = ecs_strbuf_written(&buf); - if (i) { - term_start_out[0] += 2; /* whitespace + , */ - } - } - } + ecs_observer_init(world, &(ecs_observer_desc_t){ + .filter.terms[0] = { .id = EcsConstant, .src.flags = EcsSelf }, + .events = {EcsOnAdd}, + .callback = add_constant + }); - if (i) { - if (terms[i - 1].oper == EcsOr && term->oper == EcsOr) { - ecs_strbuf_appendstr(&buf, " || "); - } else { - ecs_strbuf_appendstr(&buf, ", "); - } - } + ecs_observer_init(world, &(ecs_observer_desc_t){ + .filter.terms[0] = { .id = ecs_pair(EcsConstant, EcsWildcard), .src.flags = EcsSelf }, + .events = {EcsOnSet}, + .callback = add_constant + }); - if (term->oper != EcsOr) { - or_count = 0; - } + ecs_observer_init(world, &(ecs_observer_desc_t){ + .filter.terms[0] = { .id = ecs_id(EcsArray), .src.flags = EcsSelf }, + .events = {EcsOnSet}, + .callback = set_array + }); - 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] "); - } else if (term->inout == EcsInOutNone) { - ecs_strbuf_appendstr(&buf, "[none] "); - } - } + ecs_observer_init(world, &(ecs_observer_desc_t){ + .filter.terms[0] = { .id = ecs_id(EcsVector), .src.flags = EcsSelf }, + .events = {EcsOnSet}, + .callback = set_vector + }); - if (term->oper == EcsOr) { - or_count ++; - } + ecs_observer_init(world, &(ecs_observer_desc_t){ + .filter.terms[0] = { .id = ecs_id(EcsUnit), .src.flags = EcsSelf }, + .events = {EcsOnSet}, + .callback = set_unit + }); - term_str_w_strbuf(world, term, &buf); - } + ecs_observer_init(world, &(ecs_observer_desc_t){ + .filter.terms[0] = { .id = ecs_id(EcsMetaType), .src.flags = EcsSelf }, + .events = {EcsOnSet}, + .callback = ecs_meta_type_serialized_init + }); - return ecs_strbuf_get(&buf); -error: - return NULL; -} + ecs_observer_init(world, &(ecs_observer_desc_t){ + .filter.terms[0] = { .id = ecs_id(EcsMetaType), .src.flags = EcsSelf }, + .events = {EcsOnSet}, + .callback = ecs_meta_type_init_default_ctor + }); -char* ecs_filter_str( - const ecs_world_t *world, - const ecs_filter_t *filter) -{ - return flecs_filter_str(world, filter, NULL, NULL); -} + ecs_observer_init(world, &(ecs_observer_desc_t){ + .filter.terms = { + { .id = ecs_id(EcsUnit) }, + { .id = EcsQuantity } + }, + .events = { EcsMonitor }, + .callback = unit_quantity_monitor + }); + ecs_set_scope(world, old_scope); -int32_t ecs_filter_find_this_var( - const ecs_filter_t *filter) -{ - ecs_check(filter != NULL, ECS_INVALID_PARAMETER, NULL); - - if (ECS_BIT_IS_SET(filter->flags, EcsFilterMatchThis)) { - /* Filters currently only support the This variable at index 0. Only - * return 0 if filter actually has terms for the This variable. */ - return 0; - } + /* Initialize primitive types */ + #define ECS_PRIMITIVE(world, type, primitive_kind)\ + ecs_entity_init(world, &(ecs_entity_desc_t){\ + .id = ecs_id(ecs_##type##_t),\ + .name = #type,\ + .symbol = #type });\ + ecs_set(world, ecs_id(ecs_##type##_t), EcsPrimitive, {\ + .kind = primitive_kind\ + }); -error: - return -1; -} + 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); -/* Check if the id is a pair that has Any as first or second element. Any - * pairs behave just like Wildcard pairs and reuses the same data structures, - * with as only difference that the number of results returned for an Any pair - * is never more than one. This function is used to tell the difference. */ -static -bool is_any_pair( - ecs_id_t id) -{ - if (!ECS_HAS_ID_FLAG(id, PAIR)) { - return false; - } + #undef ECS_PRIMITIVE - if (ECS_PAIR_FIRST(id) == EcsAny) { - return true; - } - if (ECS_PAIR_SECOND(id) == EcsAny) { - return true; - } + /* Set default child components */ + ecs_add_pair(world, ecs_id(EcsStruct), + EcsDefaultChildComponent, ecs_id(EcsMember)); - return false; -} + ecs_add_pair(world, ecs_id(EcsMember), + EcsDefaultChildComponent, ecs_id(EcsMember)); -static -bool flecs_n_term_match_table( - ecs_world_t *world, - const ecs_term_t *term, - const ecs_table_t *table, - ecs_entity_t type_id, - ecs_oper_kind_t oper, - ecs_id_t *id_out, - int32_t *column_out, - ecs_entity_t *subject_out, - int32_t *match_index_out, - bool first, - ecs_flags32_t iter_flags) -{ - (void)column_out; + ecs_add_pair(world, ecs_id(EcsEnum), + EcsDefaultChildComponent, EcsConstant); - const ecs_type_t *type = ecs_get_type(world, type_id); - ecs_assert(type != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_add_pair(world, ecs_id(EcsBitmask), + EcsDefaultChildComponent, EcsConstant); - ecs_id_t *ids = type->array; - int32_t i, count = type->count; - ecs_term_t temp = *term; - temp.oper = EcsAnd; + /* Relationship properties */ + ecs_add_id(world, EcsQuantity, EcsExclusive); + ecs_add_id(world, EcsQuantity, EcsTag); - for (i = 0; i < count; i ++) { - ecs_id_t id = ids[i]; - if (ecs_id_get_flags(world, id) & EcsIdDontInherit) { - continue; + /* Initialize reflection data for meta components */ + ecs_entity_t type_kind = ecs_enum_init(world, &(ecs_enum_desc_t){ + .entity = ecs_entity(world, { .name = "TypeKind" }), + .constants = { + {.name = "PrimitiveType"}, + {.name = "BitmaskType"}, + {.name = "EnumType"}, + {.name = "StructType"}, + {.name = "ArrayType"}, + {.name = "VectorType"} } - bool result; - if (ECS_HAS_ID_FLAG(id, AND) || ECS_HAS_ID_FLAG(id, OR)) { - ecs_oper_kind_t id_oper = ECS_HAS_ID_FLAG(id, AND) - ? EcsAndFrom : ECS_HAS_ID_FLAG(id, OR) - ? EcsOrFrom : 0; - result = flecs_n_term_match_table(world, term, table, - id & ECS_COMPONENT_MASK, id_oper, id_out, column_out, - subject_out, match_index_out, first, iter_flags); - } else { - temp.id = id; - result = flecs_term_match_table(world, &temp, table, id_out, - 0, subject_out, match_index_out, first, iter_flags); + }); + + ecs_struct_init(world, &(ecs_struct_desc_t){ + .entity = ecs_id(EcsMetaType), + .members = { + {.name = (char*)"kind", .type = type_kind} } - if (!result && oper == EcsAndFrom) { - return false; - } else - if (result && oper == EcsOrFrom) { - return true; + }); + + ecs_entity_t primitive_kind = ecs_enum_init(world, &(ecs_enum_desc_t){ + .entity = ecs_entity(world, { .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 (oper == EcsAndFrom) { - if (id_out) { - id_out[0] = type_id; + ecs_struct_init(world, &(ecs_struct_desc_t){ + .entity = ecs_id(EcsPrimitive), + .members = { + {.name = (char*)"kind", .type = primitive_kind} } - return true; - } else - if (oper == EcsOrFrom) { - return false; - } + }); - return false; -} + ecs_struct_init(world, &(ecs_struct_desc_t){ + .entity = ecs_id(EcsMember), + .members = { + {.name = (char*)"type", .type = ecs_id(ecs_entity_t)}, + {.name = (char*)"count", .type = ecs_id(ecs_i32_t)}, + {.name = (char*)"unit", .type = ecs_id(ecs_entity_t)}, + {.name = (char*)"offset", .type = ecs_id(ecs_i32_t)} + } + }); -bool flecs_term_match_table( - ecs_world_t *world, - const ecs_term_t *term, - const ecs_table_t *table, - ecs_id_t *id_out, - int32_t *column_out, - ecs_entity_t *subject_out, - int32_t *match_index_out, - bool first, - ecs_flags32_t iter_flags) -{ - const ecs_term_id_t *src = &term->src; - ecs_oper_kind_t oper = term->oper; - const ecs_table_t *match_table = table; - ecs_id_t id = term->id; - - ecs_entity_t src_id = src->id; - if (ecs_term_match_0(term)) { - if (id_out) { - id_out[0] = id; /* If no entity is matched, just set id */ - } - return true; - } - - if (oper == EcsAndFrom || oper == EcsOrFrom) { - return flecs_n_term_match_table(world, term, table, term->id, - term->oper, id_out, column_out, subject_out, match_index_out, first, - iter_flags); - } - - /* If source is not This, search in table of source */ - if (!ecs_term_match_this(term)) { - if (iter_flags & EcsIterEntityOptional) { - /* Treat entity terms as optional */ - oper = EcsOptional; + ecs_struct_init(world, &(ecs_struct_desc_t){ + .entity = ecs_id(EcsArray), + .members = { + {.name = (char*)"type", .type = ecs_id(ecs_entity_t)}, + {.name = (char*)"count", .type = ecs_id(ecs_i32_t)}, } + }); - match_table = ecs_get_table(world, src_id); - if (match_table) { - } else if (oper != EcsOptional) { - return false; + ecs_struct_init(world, &(ecs_struct_desc_t){ + .entity = ecs_id(EcsVector), + .members = { + {.name = (char*)"type", .type = ecs_id(ecs_entity_t)} } - } else { - /* If filter contains This terms, a table must be provided */ - ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); - } - - if (!match_table) { - return false; - } - - ecs_entity_t source = 0; + }); - /* 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_entity_t ut = ecs_struct_init(world, &(ecs_struct_desc_t){ + .entity = ecs_entity(world, { .name = "unit_translation" }), + .members = { + {.name = (char*)"factor", .type = ecs_id(ecs_i32_t)}, + {.name = (char*)"power", .type = ecs_id(ecs_i32_t)} } + }); - /* Remove base 1 offset */ - column --; - } - - /* Find location, source and id of match in table type */ - ecs_table_record_t *tr = 0; - bool is_any = is_any_pair(id); - - column = ecs_search_relation(world, match_table, - column, id, src->trav, src->flags, &source, id_out, &tr); - - if (tr && match_index_out) { - if (!is_any) { - match_index_out[0] = tr->count; - } else { - match_index_out[0] = 1; + ecs_struct_init(world, &(ecs_struct_desc_t){ + .entity = ecs_id(EcsUnit), + .members = { + {.name = (char*)"symbol", .type = ecs_id(ecs_string_t)}, + {.name = (char*)"prefix", .type = ecs_id(ecs_entity_t)}, + {.name = (char*)"base", .type = ecs_id(ecs_entity_t)}, + {.name = (char*)"over", .type = ecs_id(ecs_entity_t)}, + {.name = (char*)"translation", .type = ut} } - } - - bool result = column != -1; + }); - if (oper == EcsNot) { - if (match_index_out) { - match_index_out[0] = 1; + ecs_struct_init(world, &(ecs_struct_desc_t){ + .entity = ecs_id(EcsUnitPrefix), + .members = { + {.name = (char*)"symbol", .type = ecs_id(ecs_string_t)}, + {.name = (char*)"translation", .type = ut} } - result = !result; - } - - if (oper == EcsOptional) { - result = true; - } + }); +} - if (!result) { - if (iter_flags & EcsFilterPopulate) { - column = 0; - } else { - return false; - } - } +#endif - if (!ecs_term_match_this(term)) { - if (!source) { - source = src_id; - } - } - if (id_out && column < 0) { - id_out[0] = id; - } +#ifdef FLECS_META - if (column_out) { - if (column >= 0) { - column ++; - if (source != 0) { - column *= -1; - } - column_out[0] = column; - } else { - column_out[0] = 0; - } - } +static +const char* flecs_meta_op_kind_str( + ecs_meta_type_op_kind_t kind) +{ + switch(kind) { - if (subject_out) { - subject_out[0] = source; + 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 >>"; } - - return result; } -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 *sources, - int32_t *match_indices, - int32_t *matches_left, - bool first, - int32_t skip_term, - ecs_flags32_t iter_flags) +/* Get current scope */ +static +ecs_meta_scope_t* get_scope( + const ecs_meta_cursor_t *cursor) { - ecs_term_t *terms = filter->terms; - int32_t i, count = filter->term_count; - - bool is_or = false; - bool or_result = false; - int32_t match_count = 1; - if (matches_left) { - match_count = *matches_left; - } - - for (i = 0; i < count; i ++) { - if (i == skip_term) { - continue; - } - - ecs_term_t *term = &terms[i]; - ecs_term_id_t *src = &term->src; - ecs_oper_kind_t oper = term->oper; - const ecs_table_t *match_table = table; - int32_t t_i = term->field_index; - - if (!is_or && oper == EcsOr) { - is_or = true; - or_result = false; - } else if (is_or && oper != EcsOr) { - if (!or_result) { - return false; - } - - is_or = false; - } - - ecs_entity_t src_id = src->id; - if (!src_id) { - if (ids) { - ids[t_i] = term->id; - } - continue; - } - - if (!ecs_term_match_this(term)) { - match_table = ecs_get_table(world, src_id); - } else { - if (ECS_BIT_IS_SET(iter_flags, EcsIterIgnoreThis)) { - or_result = true; - continue; - } - - /* If filter contains This terms, table must be provided */ - ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); - } - - int32_t match_index = 0; - - bool result = flecs_term_match_table(world, term, match_table, - ids ? &ids[t_i] : NULL, - columns ? &columns[t_i] : NULL, - sources ? &sources[t_i] : NULL, - &match_index, - first, - iter_flags); - - if (is_or) { - or_result |= result; - if (result) { - /* If Or term matched, skip following Or terms */ - for (; i < count && terms[i].oper == EcsOr; i ++) { } - i -- ; - } - } else if (!result) { - return false; - } - - if (first && match_index) { - match_count *= match_index; - } - if (match_indices) { - match_indices[t_i] = match_index; - } - } - - if (matches_left) { - *matches_left = match_count; - } + ecs_check(cursor != NULL, ECS_INVALID_PARAMETER, NULL); + return (ecs_meta_scope_t*)&cursor->scope[cursor->depth]; +error: + return NULL; +} - return !is_or || or_result; +/* 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; } +/* Get current operation for scope */ static -void term_iter_init_no_data( - ecs_term_iter_t *iter) +ecs_meta_type_op_t* get_op( + ecs_meta_scope_t *scope) { - iter->term = (ecs_term_t){ .field_index = -1 }; - iter->self_index = NULL; - iter->index = 0; + return &scope->ops[scope->op_cur]; } +/* Get component for type in current scope */ static -void term_iter_init_w_idr( - ecs_term_iter_t *iter, - ecs_id_record_t *idr, - bool empty_tables) +const EcsComponent* get_component_ptr( + const ecs_world_t *world, + ecs_meta_scope_t *scope) { - if (idr) { - if (empty_tables) { - flecs_table_cache_all_iter(&idr->cache, &iter->it); - } else { - flecs_table_cache_iter(&idr->cache, &iter->it); - } - } else { - term_iter_init_no_data(iter); + const EcsComponent *comp = scope->comp; + if (!comp) { + comp = scope->comp = ecs_get(world, scope->type, EcsComponent); + ecs_assert(comp != NULL, ECS_INTERNAL_ERROR, NULL); } - - iter->index = 0; - iter->empty_tables = empty_tables; + return comp; } +/* Get size for type in current scope */ static -void term_iter_init_wildcard( +ecs_size_t get_size( const ecs_world_t *world, - ecs_term_iter_t *iter, - bool empty_tables) + ecs_meta_scope_t *scope) { - iter->term = (ecs_term_t){ .field_index = -1 }; - iter->self_index = flecs_id_record_get(world, EcsAny); - ecs_id_record_t *idr = iter->cur = iter->self_index; - term_iter_init_w_idr(iter, idr, empty_tables); + return get_component_ptr(world, scope)->size; } +/* Get alignment for type in current scope */ static -void term_iter_init( +ecs_size_t get_alignment( const ecs_world_t *world, - ecs_term_t *term, - ecs_term_iter_t *iter, - bool empty_tables) -{ - const ecs_term_id_t *src = &term->src; - - iter->term = *term; - - if (src->flags & EcsSelf) { - iter->self_index = flecs_query_id_record_get(world, term->id); - } - - if (src->flags & EcsUp) { - iter->set_index = flecs_id_record_get(world, - ecs_pair(src->trav, EcsWildcard)); - } + ecs_meta_scope_t *scope) +{ + return get_component_ptr(world, scope)->alignment; +} - ecs_id_record_t *idr; - if (iter->self_index) { - idr = iter->cur = iter->self_index; - } else { - idr = iter->cur = iter->set_index; +static +int32_t get_elem_count( + ecs_meta_scope_t *scope) +{ + if (scope->vector) { + return ecs_vector_count(*(scope->vector)); } - term_iter_init_w_idr(iter, idr, empty_tables); + ecs_meta_type_op_t *op = get_op(scope); + return op->count; } -ecs_iter_t ecs_term_iter( - const ecs_world_t *stage, - ecs_term_t *term) +/* 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_check(stage != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(term != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_meta_type_op_t *op = get_op(scope); + ecs_size_t size = get_size(world, scope); - const ecs_world_t *world = ecs_get_world(stage); + 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); + } - flecs_process_pending_tables(world); + return ECS_OFFSET(scope->ptr, size * scope->elem_cur + op->offset); +} - if (ecs_term_finalize(world, term)) { - ecs_throw(ECS_INVALID_PARAMETER, NULL); +static +int push_type( + const ecs_world_t *world, + ecs_meta_scope_t *scope, + ecs_entity_t type, + void *ptr) +{ + 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_iter_t it = { - .real_world = (ecs_world_t*)world, - .world = (ecs_world_t*)stage, - .field_count = 1, - .next = ecs_term_next + 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 }; - /* Term iter populates the iterator with arrays from its own cache, ensure - * they don't get overwritten by flecs_iter_validate. - * - * Note: the reason the term iterator doesn't use the iterator cache itself - * (which could easily accomodate a single term) is that the filter iterator - * is built on top of the term iterator. The private cache of the term - * iterator keeps the filter iterator code simple, as it doesn't need to - * worry about the term iter overwriting the iterator fields. */ - flecs_iter_init(&it, 0); - - term_iter_init(world, term, &it.priv.iter.term, false); - - return it; -error: - return (ecs_iter_t){ 0 }; + return 0; } -ecs_iter_t ecs_term_chain_iter( - const ecs_iter_t *chain_it, - ecs_term_t *term) +ecs_meta_cursor_t ecs_meta_cursor( + const ecs_world_t *world, + ecs_entity_t type, + void *ptr) { - 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); + ecs_check(type != 0, ECS_INVALID_PARAMETER, NULL); + ecs_check(ptr != NULL, ECS_INVALID_PARAMETER, NULL); - if (ecs_term_finalize(world, term)) { - ecs_throw(ECS_INVALID_PARAMETER, NULL); - } - - ecs_iter_t it = { - .real_world = (ecs_world_t*)world, - .world = chain_it->world, - .terms = term, - .field_count = 1, - .chain_it = (ecs_iter_t*)chain_it, - .next = ecs_term_next + ecs_meta_cursor_t result = { + .world = world, + .valid = true }; - flecs_iter_init(&it, flecs_iter_cache_all); - - term_iter_init(world, term, &it.priv.iter.term, false); + if (push_type(world, result.scope, type, ptr) != 0) { + result.valid = false; + } - return it; + return result; error: - return (ecs_iter_t){ 0 }; + return (ecs_meta_cursor_t){ 0 }; } -static -const ecs_table_record_t *flecs_term_iter_next_table( - ecs_term_iter_t *iter) +void* ecs_meta_get_ptr( + ecs_meta_cursor_t *cursor) { - ecs_id_record_t *idr = iter->cur; - if (!idr) { - return NULL; - } - - return flecs_table_cache_next(&iter->it, ecs_table_record_t); + return get_ptr(cursor->world, get_scope(cursor)); } -static -bool flecs_term_iter_find_superset( - ecs_world_t *world, - ecs_table_t *table, - ecs_term_t *term, - ecs_entity_t *source, - ecs_id_t *id, - int32_t *column) +int ecs_meta_next( + ecs_meta_cursor_t *cursor) { - ecs_term_id_t *src = &term->src; + ecs_meta_scope_t *scope = get_scope(cursor); + ecs_meta_type_op_t *op = get_op(scope); - /* Test if following the relationship finds the id */ - int32_t index = ecs_search_relation(world, table, 0, - term->id, src->trav, src->flags, source, id, 0); + 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; + } - if (index == -1) { - *source = 0; - return false; + return 0; } - ecs_assert(*source != 0, ECS_INTERNAL_ERROR, NULL); - - *column = (index + 1) * -1; + scope->op_cur += op->op_count; + if (scope->op_cur >= scope->op_count) { + ecs_err("out of bounds"); + return -1; + } - return true; + return 0; } -static -bool flecs_term_iter_next( - ecs_world_t *world, - ecs_term_iter_t *iter, - bool match_prefab, - bool match_disabled) +int ecs_meta_elem( + ecs_meta_cursor_t *cursor, + int32_t elem) { - ecs_table_t *table = iter->table; - ecs_entity_t source = 0; - const ecs_table_record_t *tr; - ecs_term_t *term = &iter->term; - - do { - if (table) { - iter->cur_match ++; - if (iter->cur_match >= iter->match_count) { - table = NULL; - } else { - iter->last_column = ecs_search_offset( - world, table, iter->last_column + 1, term->id, 0); - iter->column = iter->last_column + 1; - if (iter->last_column >= 0) { - iter->id = table->type.array[iter->last_column]; - } - } - } - - if (!table) { - if (!(tr = flecs_term_iter_next_table(iter))) { - if (iter->cur != iter->set_index && iter->set_index != NULL) { - if (iter->observed_table_count != 0) { - iter->cur = iter->set_index; - if (iter->empty_tables) { - flecs_table_cache_all_iter( - &iter->set_index->cache, &iter->it); - } else { - flecs_table_cache_iter( - &iter->set_index->cache, &iter->it); - } - iter->index = 0; - tr = flecs_term_iter_next_table(iter); - } - } - - if (!tr) { - return false; - } - } - - table = tr->hdr.table; - if (table->observed_count) { - iter->observed_table_count ++; - } - - if (!match_prefab && (table->flags & EcsTableIsPrefab)) { - continue; - } + ecs_meta_scope_t *scope = get_scope(cursor); + if (!scope->is_collection) { + ecs_err("ecs_meta_elem can be used for collections only"); + return -1; + } - if (!match_disabled && (table->flags & EcsTableIsDisabled)) { - continue; - } + scope->elem_cur = elem; + scope->op_cur = 0; - iter->table = table; - iter->match_count = tr->count; - if (is_any_pair(term->id)) { - iter->match_count = 1; - } + if (scope->elem_cur >= get_elem_count(scope) || (scope->elem_cur < 0)) { + ecs_err("out of collection bounds (%d)", scope->elem_cur); + return -1; + } + + return 0; +} - iter->cur_match = 0; - iter->last_column = tr->column; - iter->column = tr->column + 1; - iter->id = flecs_to_public_id(table->type.array[tr->column]); - } +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; + } - if (iter->cur == iter->set_index) { - if (iter->self_index) { - if (flecs_id_record_get_table(iter->self_index, table) != NULL) { - /* If the table has the id itself and this term matched Self - * we already matched it */ - continue; - } - } + 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; - if (!flecs_term_iter_find_superset( - world, table, term, &source, &iter->id, &iter->column)) - { - continue; - } + ecs_assert(push_op->kind == EcsOpPush, ECS_INTERNAL_ERROR, NULL); - /* The tr->count field refers to the number of relationship instances, - * not to the number of matches. Superset terms can only yield a - * single match. */ - iter->match_count = 1; - } + if (!push_op->members) { + ecs_err("cannot move to member '%s' for non-struct type", name); + return -1; + } - break; - } while (true); + const uint64_t *cur_ptr = flecs_name_index_find_ptr(push_op->members, name, 0, 0); + if (!cur_ptr) { + char *path = ecs_get_fullpath(world, scope->type); + ecs_err("unknown member '%s' for type '%s'", name, path); + ecs_os_free(path); + return -1; + } - iter->subject = source; + scope->op_cur = flecs_uto(int32_t, cur_ptr[0]); - return true; + return 0; } -static -bool flecs_term_iter_set_table( - ecs_world_t *world, - ecs_term_iter_t *iter, - ecs_table_t *table) +int ecs_meta_push( + ecs_meta_cursor_t *cursor) { - const ecs_table_record_t *tr = NULL; - const ecs_id_record_t *idr = iter->self_index; - if (idr) { - tr = ecs_table_cache_get(&idr->cache, table); - if (tr) { - iter->match_count = tr->count; - iter->last_column = tr->column; - iter->column = tr->column + 1; - iter->id = flecs_to_public_id(table->type.array[tr->column]); - } - } + 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 (!tr) { - idr = iter->set_index; - if (idr) { - tr = ecs_table_cache_get(&idr->cache, table); - if (!flecs_term_iter_find_superset(world, table, &iter->term, - &iter->subject, &iter->id, &iter->column)) - { - return false; + if (cursor->depth == 0) { + if (!cursor->is_primitive_scope) { + if (op->kind > EcsOpScope) { + cursor->is_primitive_scope = true; + return 0; } - iter->match_count = 1; } } - if (!tr) { - return false; - } - - /* Populate fields as usual */ - iter->table = table; - iter->cur_match = 0; - - 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); + void *ptr = get_ptr(world, scope); + cursor->depth ++; + ecs_check(cursor->depth < ECS_META_MAX_SCOPE_DEPTH, + ECS_INVALID_PARAMETER, NULL); - flecs_iter_validate(it); + ecs_meta_scope_t *next_scope = get_scope(cursor); - ecs_term_iter_t *iter = &it->priv.iter.term; - ecs_term_t *term = &iter->term; - ecs_world_t *world = it->real_world; - ecs_table_t *table; + /* 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 + }; - it->ids = &iter->id; - it->sources = &iter->subject; - it->columns = &iter->column; - it->terms = &iter->term; - it->sizes = &iter->size; + /* With 'is_inline_array' set to true we ensure that we can never push + * the same inline array twice */ - if (term->inout != EcsInOutNone) { - it->ptrs = &iter->ptr; - } else { - it->ptrs = NULL; + return 0; } - 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, - it->ids, it->columns, it->sources, it->match_indices, true, - it->flags); - } while (!match); - goto yield; + 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; - } else { - if (!flecs_term_iter_next(world, iter, false, false)) { - goto done; + case EcsOpArray: { + if (push_type(world, next_scope, op->type, ptr) != 0) { + goto error; } - 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); + const EcsArray *type_ptr = ecs_get(world, op->type, EcsArray); + next_scope->type = type_ptr->type; + next_scope->is_collection = true; + break; } -yield: - flecs_iter_populate_data(world, it, table, 0, 0, it->ptrs, it->sizes); - ECS_BIT_SET(it->flags, EcsIterIsValid); - return true; -done: -error: - return false; -} - -static -void flecs_init_filter_iter( - ecs_iter_t *it, - const ecs_filter_t *filter) -{ - ecs_assert(filter != NULL, ECS_INTERNAL_ERROR, NULL); - it->priv.iter.filter.filter = filter; - it->field_count = filter->field_count; -} - -int32_t ecs_filter_pivot_term( - const ecs_world_t *world, - const ecs_filter_t *filter) -{ - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(filter != NULL, ECS_INVALID_PARAMETER, NULL); - - ecs_term_t *terms = filter->terms; - int32_t i, term_count = filter->term_count; - int32_t pivot_term = -1, min_count = -1, self_pivot_term = -1; - - for (i = 0; i < term_count; i ++) { - ecs_term_t *term = &terms[i]; - ecs_id_t id = term->id; - - if (term->oper != EcsAnd) { - continue; - } - - if (!ecs_term_match_this(term)) { - continue; + case EcsOpVector: + next_scope->vector = ptr; + if (push_type(world, next_scope, op->type, NULL) != 0) { + goto error; } - ecs_id_record_t *idr = flecs_query_id_record_get(world, id); - if (!idr) { - /* If one of the terms does not match with any data, iterator - * should not return anything */ - return -2; /* -2 indicates filter doesn't match anything */ - } + const EcsVector *type_ptr = ecs_get(world, op->type, EcsVector); + next_scope->type = type_ptr->type; + next_scope->is_collection = true; + break; - int32_t table_count = flecs_table_cache_count(&idr->cache); - if (min_count == -1 || table_count < min_count) { - min_count = table_count; - pivot_term = i; - if ((term->src.flags & EcsTraverseFlags) == EcsSelf) { - self_pivot_term = i; - } - } + default: { + char *path = ecs_get_fullpath(world, scope->type); + ecs_err("invalid push for type '%s'", path); + ecs_os_free(path); + goto error; + } } - if (self_pivot_term != -1) { - pivot_term = self_pivot_term; + if (scope->is_collection) { + next_scope[0].ptr = ECS_OFFSET(next_scope[0].ptr, + scope->elem_cur * get_size(world, scope)); } - return pivot_term; + return 0; error: - return -2; + return -1; } -ecs_iter_t flecs_filter_iter_w_flags( - const ecs_world_t *stage, - const ecs_filter_t *filter, - ecs_flags32_t flags) +int ecs_meta_pop( + ecs_meta_cursor_t *cursor) { - ecs_check(stage != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(filter != NULL, ECS_INVALID_PARAMETER, NULL); - const ecs_world_t *world = ecs_get_world(stage); - - flecs_process_pending_tables(world); - - ecs_iter_t it = { - .real_world = (ecs_world_t*)world, - .world = (ecs_world_t*)stage, - .terms = filter ? filter->terms : NULL, - .next = ecs_filter_next, - .flags = flags - }; - - ecs_filter_iter_t *iter = &it.priv.iter.filter; - iter->pivot_term = -1; + if (cursor->is_primitive_scope) { + cursor->is_primitive_scope = false; + return 0; + } - flecs_init_filter_iter(&it, filter); - ECS_BIT_COND(it.flags, EcsIterIsInstanced, - ECS_BIT_IS_SET(filter->flags, EcsFilterIsInstanced)); + ecs_meta_scope_t *scope = get_scope(cursor); + cursor->depth --; + if (cursor->depth < 0) { + ecs_err("unexpected end of scope"); + return -1; + } - /* Find term that represents smallest superset */ - if (ECS_BIT_IS_SET(flags, EcsIterIgnoreThis)) { - term_iter_init_no_data(&iter->term_iter); - } else if (ECS_BIT_IS_SET(filter->flags, EcsFilterMatchThis)) { - ecs_term_t *terms = filter->terms; - int32_t pivot_term = -1; - ecs_check(terms != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_meta_scope_t *next_scope = get_scope(cursor); + ecs_meta_type_op_t *op = get_op(next_scope); - pivot_term = ecs_filter_pivot_term(world, filter); - iter->kind = EcsIterEvalTables; - iter->pivot_term = pivot_term; + if (!scope->is_inline_array) { + if (op->kind == EcsOpPush) { + next_scope->op_cur += op->op_count - 1; - if (pivot_term == -2) { - /* One or more terms have no matching results */ - term_iter_init_no_data(&iter->term_iter); - } else if (pivot_term == -1) { - /* No terms meet the criteria to be a pivot term, evaluate filter - * against all tables */ - term_iter_init_wildcard(world, &iter->term_iter, - ECS_BIT_IS_SET(filter->flags, EcsFilterMatchEmptyTables)); + /* 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 { - ecs_assert(pivot_term >= 0, ECS_INTERNAL_ERROR, NULL); - term_iter_init(world, &terms[pivot_term], &iter->term_iter, - ECS_BIT_IS_SET(filter->flags, EcsFilterMatchEmptyTables)); + /* 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 { - if (!ECS_BIT_IS_SET(filter->flags, EcsFilterMatchAnything)) { - term_iter_init_no_data(&iter->term_iter); - } else { - iter->kind = EcsIterEvalNone; - } - } - - ECS_BIT_COND(it.flags, EcsIterIsFilter, - ECS_BIT_IS_SET(filter->flags, EcsFilterIsFilter)); - - if (ECS_BIT_IS_SET(filter->flags, EcsFilterMatchThis)) { - /* Make space for one variable if the filter has terms for This var */ - it.variable_count = 1; - - /* Set variable name array */ - it.variable_names = (char**)filter->variable_names; + /* Make sure that this was an inline array */ + ecs_assert(next_scope->op_count > 1, ECS_INTERNAL_ERROR, NULL); } - flecs_iter_init(&it, flecs_iter_cache_all); - - return it; -error: - return (ecs_iter_t){ 0 }; + return 0; } -ecs_iter_t ecs_filter_iter( - const ecs_world_t *stage, - const ecs_filter_t *filter) +bool ecs_meta_is_collection( + const ecs_meta_cursor_t *cursor) { - return flecs_filter_iter_w_flags(stage, filter, 0); + ecs_meta_scope_t *scope = get_scope(cursor); + return scope->is_collection; } -ecs_iter_t ecs_filter_chain_iter( - const ecs_iter_t *chain_it, - const ecs_filter_t *filter) +ecs_entity_t ecs_meta_get_type( + const ecs_meta_cursor_t *cursor) { - ecs_iter_t it = { - .terms = filter->terms, - .field_count = filter->field_count, - .world = chain_it->world, - .real_world = chain_it->real_world, - .chain_it = (ecs_iter_t*)chain_it, - .next = ecs_filter_next - }; - - flecs_iter_init(&it, flecs_iter_cache_all); - ecs_filter_iter_t *iter = &it.priv.iter.filter; - flecs_init_filter_iter(&it, filter); - - iter->kind = EcsIterEvalChain; - - return it; + ecs_meta_scope_t *scope = get_scope(cursor); + ecs_meta_type_op_t *op = get_op(scope); + return op->type; } -bool ecs_filter_next( - ecs_iter_t *it) +ecs_entity_t ecs_meta_get_unit( + const ecs_meta_cursor_t *cursor) { - 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; - } - - return flecs_iter_next_instanced(it, ecs_filter_next_instanced(it)); -error: - return false; + ecs_meta_scope_t *scope = get_scope(cursor); + ecs_meta_type_op_t *op = get_op(scope); + return op->unit; } -bool ecs_filter_next_instanced( - ecs_iter_t *it) +const char* ecs_meta_get_member( + const ecs_meta_cursor_t *cursor) { - 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); - - ecs_filter_iter_t *iter = &it->priv.iter.filter; - const ecs_filter_t *filter = iter->filter; - ecs_world_t *world = it->real_world; - ecs_table_t *table = NULL; - bool match; - - flecs_iter_validate(it); + ecs_meta_scope_t *scope = get_scope(cursor); + ecs_meta_type_op_t *op = get_op(scope); + return op->name; +} - ecs_iter_t *chain_it = it->chain_it; - ecs_iter_kind_t kind = iter->kind; - - 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; - } - - table = chain_it->table; - match = flecs_filter_match_table(world, filter, table, - it->ids, it->columns, it->sources, it->match_indices, NULL, - true, -1, it->flags); - } while (!match); - - goto yield; - } else if (kind == EcsIterEvalTables || kind == EcsIterEvalCondition) { - ecs_term_iter_t *term_iter = &iter->term_iter; - ecs_term_t *term = &term_iter->term; - int32_t pivot_term = iter->pivot_term; - bool first; - - /* Check if the This variable has been set on the iterator. If set, - * the filter should only be applied to the variable value */ - ecs_var_t *this_var = NULL; - ecs_table_t *this_table = NULL; - if (it->variable_count) { - if (ecs_iter_var_is_constrained(it, 0)) { - this_var = it->variables; - this_table = this_var->range.table; - - /* If variable is constrained, make sure it's a value that's - * pointing to a table, as a filter can't iterate single - * entities (yet) */ - ecs_assert(this_table != NULL, ECS_INVALID_OPERATION, NULL); - - /* Can't set variable for filter that does not iterate tables */ - ecs_assert(kind == EcsIterEvalTables, - ECS_INVALID_OPERATION, NULL); - } - } - - do { - /* If there are no matches left for the previous table, this is the - * first match of the next table. */ - first = iter->matches_left == 0; - - if (first) { - if (kind != EcsIterEvalCondition) { - /* Check if this variable was constrained */ - if (this_table != NULL) { - /* If this is the first match of a new result and the - * previous result was equal to the value of a - * constrained var, there's nothing left to iterate */ - if (it->table == this_table) { - goto done; - } - - /* If table doesn't match term iterator, it doesn't - * match filter. */ - if (!flecs_term_iter_set_table( - world, term_iter, this_table)) - { - goto done; - } - - /* But if it does, forward it to filter matching */ - ecs_assert(term_iter->table == this_table, - ECS_INTERNAL_ERROR, NULL); - - /* If This variable is not constrained, iterate as usual */ - } else { - /* Find new match, starting with the leading term */ - if (!flecs_term_iter_next(world, term_iter, - ECS_BIT_IS_SET(filter->flags, - EcsFilterMatchPrefab), - ECS_BIT_IS_SET(filter->flags, - EcsFilterMatchDisabled))) - { - goto done; - } - } +/* Utilities for type conversions and bounds checking */ +struct { + int64_t min, max; +} ecs_meta_bounds_signed[EcsMetaTypeOpKindLast + 1] = { + [EcsOpBool] = {false, true}, + [EcsOpChar] = {INT8_MIN, INT8_MAX}, + [EcsOpByte] = {0, UINT8_MAX}, + [EcsOpU8] = {0, UINT8_MAX}, + [EcsOpU16] = {0, UINT16_MAX}, + [EcsOpU32] = {0, UINT32_MAX}, + [EcsOpU64] = {0, INT64_MAX}, + [EcsOpI8] = {INT8_MIN, INT8_MAX}, + [EcsOpI16] = {INT16_MIN, INT16_MAX}, + [EcsOpI32] = {INT32_MIN, INT32_MAX}, + [EcsOpI64] = {INT64_MIN, INT64_MAX}, + [EcsOpUPtr] = {0, ((sizeof(void*) == 4) ? UINT32_MAX : INT64_MAX)}, + [EcsOpIPtr] = { + ((sizeof(void*) == 4) ? INT32_MIN : INT64_MIN), + ((sizeof(void*) == 4) ? INT32_MAX : INT64_MAX) + }, + [EcsOpEntity] = {0, INT64_MAX}, + [EcsOpEnum] = {INT32_MIN, INT32_MAX}, + [EcsOpBitmask] = {0, INT32_MAX} +}; - ecs_assert(term_iter->match_count != 0, - ECS_INTERNAL_ERROR, NULL); +struct { + uint64_t min, max; +} ecs_meta_bounds_unsigned[EcsMetaTypeOpKindLast + 1] = { + [EcsOpBool] = {false, true}, + [EcsOpChar] = {0, INT8_MAX}, + [EcsOpByte] = {0, UINT8_MAX}, + [EcsOpU8] = {0, UINT8_MAX}, + [EcsOpU16] = {0, UINT16_MAX}, + [EcsOpU32] = {0, UINT32_MAX}, + [EcsOpU64] = {0, UINT64_MAX}, + [EcsOpI8] = {0, INT8_MAX}, + [EcsOpI16] = {0, INT16_MAX}, + [EcsOpI32] = {0, INT32_MAX}, + [EcsOpI64] = {0, INT64_MAX}, + [EcsOpUPtr] = {0, ((sizeof(void*) == 4) ? UINT32_MAX : UINT64_MAX)}, + [EcsOpIPtr] = {0, ((sizeof(void*) == 4) ? INT32_MAX : INT64_MAX)}, + [EcsOpEntity] = {0, UINT64_MAX}, + [EcsOpEnum] = {0, INT32_MAX}, + [EcsOpBitmask] = {0, UINT32_MAX} +}; - if (pivot_term == -1) { - /* Without a pivot term, we're iterating all tables with - * a wildcard, so the match count is meaningless. */ - term_iter->match_count = 1; - } else { - it->match_indices[pivot_term] = term_iter->match_count; - } +struct { + double min, max; +} ecs_meta_bounds_float[EcsMetaTypeOpKindLast + 1] = { + [EcsOpBool] = {false, true}, + [EcsOpChar] = {INT8_MIN, INT8_MAX}, + [EcsOpByte] = {0, UINT8_MAX}, + [EcsOpU8] = {0, UINT8_MAX}, + [EcsOpU16] = {0, UINT16_MAX}, + [EcsOpU32] = {0, UINT32_MAX}, + [EcsOpU64] = {0, (double)UINT64_MAX}, + [EcsOpI8] = {INT8_MIN, INT8_MAX}, + [EcsOpI16] = {INT16_MIN, INT16_MAX}, + [EcsOpI32] = {INT32_MIN, INT32_MAX}, + [EcsOpI64] = {INT64_MIN, (double)INT64_MAX}, + [EcsOpUPtr] = {0, ((sizeof(void*) == 4) ? UINT32_MAX : (double)UINT64_MAX)}, + [EcsOpIPtr] = { + ((sizeof(void*) == 4) ? INT32_MIN : (double)INT64_MIN), + ((sizeof(void*) == 4) ? INT32_MAX : (double)INT64_MAX) + }, + [EcsOpEntity] = {0, (double)UINT64_MAX}, + [EcsOpEnum] = {INT32_MIN, INT32_MAX}, + [EcsOpBitmask] = {0, UINT32_MAX} +}; - iter->matches_left = term_iter->match_count; +#define set_T(T, ptr, value)\ + ((T*)ptr)[0] = ((T)value) - /* Filter iterator takes control over iterating all the - * permutations that match the wildcard. */ - term_iter->match_count = 1; +#define case_T(kind, T, dst, src)\ +case kind:\ + set_T(T, dst, src);\ + break - table = term_iter->table; +#define case_T_checked(kind, T, dst, src, bounds)\ +case kind:\ + if ((src < bounds[kind].min) || (src > bounds[kind].max)){\ + ecs_err("value %.0f is out of bounds for type %s", (double)src,\ + flecs_meta_op_kind_str(kind));\ + return -1;\ + }\ + set_T(T, dst, src);\ + break - if (pivot_term != -1) { - int32_t index = term->field_index; - it->ids[index] = term_iter->id; - it->sources[index] = term_iter->subject; - it->columns[index] = term_iter->column; - } - } else { - /* Progress iterator to next match for table, if any */ - table = it->table; - if (term_iter->index == 0) { - iter->matches_left = 1; - term_iter->index = 1; /* prevents looping again */ - } else { - goto done; - } - } +#define cases_T_float(dst, src)\ + case_T(EcsOpF32, ecs_f32_t, dst, src);\ + case_T(EcsOpF64, ecs_f64_t, dst, src) - /* Match the remainder of the terms */ - match = flecs_filter_match_table(world, filter, table, - it->ids, it->columns, it->sources, - it->match_indices, &iter->matches_left, first, - pivot_term, it->flags); - if (!match) { - it->table = table; - iter->matches_left = 0; - continue; - } +#define cases_T_signed(dst, src, bounds)\ + case_T_checked(EcsOpChar, ecs_char_t, dst, src, bounds);\ + case_T_checked(EcsOpI8, ecs_i8_t, dst, src, bounds);\ + case_T_checked(EcsOpI16, ecs_i16_t, dst, src, bounds);\ + case_T_checked(EcsOpI32, ecs_i32_t, dst, src, bounds);\ + case_T_checked(EcsOpI64, ecs_i64_t, dst, src, bounds);\ + case_T_checked(EcsOpIPtr, ecs_iptr_t, dst, src, bounds);\ + case_T_checked(EcsOpEnum, ecs_i32_t, dst, src, bounds) - /* Table got matched, set This variable */ - if (table) { - ecs_assert(it->variable_count == 1, ECS_INTERNAL_ERROR, NULL); - ecs_assert(it->variables != NULL, ECS_INTERNAL_ERROR, NULL); - it->variables[0].range.table = table; - } +#define cases_T_unsigned(dst, src, bounds)\ + case_T_checked(EcsOpByte, ecs_byte_t, dst, src, bounds);\ + case_T_checked(EcsOpU8, ecs_u8_t, dst, src, bounds);\ + case_T_checked(EcsOpU16, ecs_u16_t, dst, src, bounds);\ + case_T_checked(EcsOpU32, ecs_u32_t, dst, src, bounds);\ + case_T_checked(EcsOpU64, ecs_u64_t, dst, src, bounds);\ + case_T_checked(EcsOpUPtr, ecs_uptr_t, dst, src, bounds);\ + case_T_checked(EcsOpEntity, ecs_u64_t, dst, src, bounds);\ + case_T_checked(EcsOpBitmask, ecs_u32_t, dst, src, bounds) - ecs_assert(iter->matches_left != 0, ECS_INTERNAL_ERROR, NULL); - } +#define cases_T_bool(dst, src)\ +case EcsOpBool:\ + set_T(ecs_bool_t, dst, value != 0);\ + break - /* If this is not the first result for the table, and the table - * is matched more than once, iterate remaining matches */ - if (!first && (iter->matches_left > 0)) { - table = it->table; +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); +} - /* Find first term that still has matches left */ - int32_t i, j, count = it->field_count; - for (i = count - 1; i >= 0; i --) { - int32_t mi = -- it->match_indices[i]; - if (mi) { - if (mi < 0) { - continue; - } - break; - } - } +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 matches_left > 0 we should've found at least one match */ - ecs_assert(i >= 0, ECS_INTERNAL_ERROR, NULL); + switch(op->kind) { + cases_T_bool(ptr, value); + cases_T_unsigned(ptr, value, ecs_meta_bounds_unsigned); + default: + conversion_error(cursor, op, "bool"); + return -1; + } - /* Progress first term to next match (must be at least one) */ - int32_t column = it->columns[i]; - if (column < 0) { - /* If this term was matched on a non-This entity, reconvert - * the column back to a positive value */ - column = -column; - } + return 0; +} - it->columns[i] = column + 1; - flecs_term_match_table(world, &filter->terms[i], table, - &it->ids[i], &it->columns[i], &it->sources[i], - &it->match_indices[i], false, it->flags); +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); - /* Reset remaining terms (if any) to first match */ - for (j = i + 1; j < count; j ++) { - flecs_term_match_table(world, &filter->terms[j], table, - &it->ids[j], &it->columns[j], &it->sources[j], - &it->match_indices[j], true, it->flags); - } - } + switch(op->kind) { + cases_T_bool(ptr, value); + cases_T_signed(ptr, value, ecs_meta_bounds_signed); + default: + conversion_error(cursor, op, "char"); + return -1; + } - match = iter->matches_left != 0; - iter->matches_left --; + return 0; +} - ecs_assert(iter->matches_left >= 0, ECS_INTERNAL_ERROR, NULL); - } while (!match); +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); - goto yield; + switch(op->kind) { + cases_T_bool(ptr, value); + cases_T_signed(ptr, value, ecs_meta_bounds_signed); + cases_T_unsigned(ptr, value, ecs_meta_bounds_signed); + cases_T_float(ptr, value); + default: { + if(!value) return ecs_meta_set_null(cursor); + conversion_error(cursor, op, "int"); + return -1; + } } -done: -error: - ecs_iter_fini(it); - return false; - -yield: - it->offset = 0; - flecs_iter_populate_data(world, it, table, 0, 0, it->ptrs, it->sizes); - ECS_BIT_SET(it->flags, EcsIterIsValid); - return true; + return 0; } -#ifndef FLECS_SYSTEM_PRIVATE_H -#define FLECS_SYSTEM_PRIVATE_H - -#ifdef FLECS_SYSTEM - +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); -#define ecs_system_t_magic (0x65637383) -#define ecs_system_t_tag EcsSystem + switch(op->kind) { + cases_T_bool(ptr, value); + cases_T_signed(ptr, value, ecs_meta_bounds_unsigned); + cases_T_unsigned(ptr, value, ecs_meta_bounds_unsigned); + cases_T_float(ptr, value); + default: + if(!value) return ecs_meta_set_null(cursor); + conversion_error(cursor, op, "uint"); + return -1; + } -extern ecs_mixins_t ecs_system_t_mixins; + return 0; +} -typedef struct ecs_system_t { - ecs_header_t hdr; +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); - ecs_run_action_t run; /* See ecs_system_desc_t */ - ecs_iter_action_t action; /* See ecs_system_desc_t */ + switch(op->kind) { + cases_T_bool(ptr, value); + cases_T_signed(ptr, value, ecs_meta_bounds_float); + cases_T_unsigned(ptr, value, ecs_meta_bounds_float); + cases_T_float(ptr, value); + default: + conversion_error(cursor, op, "float"); + return -1; + } - ecs_query_t *query; /* System query */ - ecs_entity_t query_entity; /* Entity associated with query */ - ecs_entity_t tick_source; /* Tick source associated with system */ - - /* Schedule parameters */ - bool multi_threaded; - bool no_staging; + return 0; +} - int32_t invoke_count; /* Number of times system is invoked */ - float time_spent; /* Time spent on running system */ - ecs_ftime_t time_passed; /* Time passed since last invocation */ - int32_t last_frame; /* Last frame for which the system was considered */ +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); - void *ctx; /* Userdata for system */ - void *binding_ctx; /* Optional language binding context */ + if (!ecs_os_strcmp(value, "0")) { + return 0; + } - ecs_ctx_free_t ctx_free; - ecs_ctx_free_t binding_ctx_free; + 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; + } - /* Mixins */ - ecs_world_t *world; - ecs_entity_t entity; - ecs_poly_dtor_t dtor; -} ecs_system_t; + 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; + } -/* Invoked when system becomes active / inactive */ -void ecs_system_activate( - ecs_world_t *world, - ecs_entity_t system, - bool activate, - const ecs_system_t *system_data); + *(ecs_u32_t*)out |= v[0]; -/* Internal function to run a system */ -ecs_entity_t ecs_run_intern( - ecs_world_t *world, - ecs_stage_t *stage, - ecs_entity_t system, - ecs_system_t *system_data, - int32_t stage_current, - int32_t stage_count, - ecs_ftime_t delta_time, - int32_t offset, - int32_t limit, - void *param); + return 0; +} -#endif +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]; -#endif + const char *prev = value, *ptr = value; + *(ecs_u32_t*)out = 0; -#ifdef FLECS_TIMER + 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; + } -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}); + ptr ++; + prev = ptr; } -} -static -void ProgressTimers(ecs_iter_t *it) { - EcsTimer *timer = ecs_field(it, EcsTimer, 1); - EcsTickSource *tick_source = ecs_field(it, EcsTickSource, 2); + if (add_bitmask_constant(cursor, op, out, prev) != 0) { + return -1; + } - ecs_assert(timer != NULL, ECS_INTERNAL_ERROR, NULL); + return 0; +} - int i; - for (i = 0; i < it->count; i ++) { - tick_source[i].tick = 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); - if (!timer[i].active) { - 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); + } else { + ecs_err("invalid value for boolean '%s'", value); + return -1; + } + 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; } - const ecs_world_info_t *info = ecs_get_world_info(it->world); - ecs_ftime_t time_elapsed = timer[i].time + info->delta_time_raw; - ecs_ftime_t timeout = timer[i].timeout; - - if (time_elapsed >= timeout) { - ecs_ftime_t t = time_elapsed - timeout; - if (t > timeout) { - t = 0; - } + 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; + } - timer[i].time = t; /* Initialize with remainder */ - tick_source[i].tick = true; - tick_source[i].time_elapsed = time_elapsed; + 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; - if (timer[i].single_shot) { - timer[i].active = false; + 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); } - } else { - timer[i].time = time_elapsed; - } + + if (!e) { + ecs_err("unresolved entity identifier '%s'", value); + return -1; + } + } + + 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, flecs_meta_op_kind_str(op->kind)); + return -1; } + + return 0; } -static -void ProgressRateFilters(ecs_iter_t *it) { - EcsRateFilter *filter = ecs_field(it, EcsRateFilter, 1); - EcsTickSource *tick_dst = ecs_field(it, EcsTickSource, 2); +int ecs_meta_set_string_literal( + 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); - int i; - for (i = 0; i < it->count; i ++) { - ecs_entity_t src = filter[i].src; - bool inc = false; + ecs_size_t len = ecs_os_strlen(value); + if (value[0] != '\"' || value[len - 1] != '\"') { + ecs_err("invalid string literal '%s'", value); + return -1; + } - filter[i].time_elapsed += it->delta_time; + switch(op->kind) { + case EcsOpChar: + set_T(ecs_char_t, ptr, value[1]); + break; - 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; - } + default: + case EcsOpEntity: + case EcsOpString: + len -= 2; - 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; + char *result = ecs_os_malloc(len + 1); + ecs_os_memcpy(result, value + 1, len); + result[len] = '\0'; - if (triggered) { - filter[i].time_elapsed = 0; - } - } else { - tick_dst[i].tick = false; + if (ecs_meta_set_string(cursor, result)) { + ecs_os_free(result); + return -1; } - } -} -static -void ProgressTickSource(ecs_iter_t *it) { - EcsTickSource *tick_src = ecs_field(it, EcsTickSource, 1); + ecs_os_free(result); - /* 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; + break; } + + return 0; } -ecs_entity_t ecs_set_timeout( - ecs_world_t *world, - ecs_entity_t timer, - ecs_ftime_t timeout) +int ecs_meta_set_entity( + ecs_meta_cursor_t *cursor, + ecs_entity_t value) { - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - - timer = ecs_set(world, timer, EcsTimer, { - .timeout = timeout, - .single_shot = true, - .active = true - }); + 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_system_t *system_data = ecs_poly_get(world, timer, ecs_system_t); - if (system_data) { - system_data->tick_source = timer; + switch(op->kind) { + case EcsOpEntity: + set_T(ecs_entity_t, ptr, value); + break; + default: + conversion_error(cursor, op, "entity"); + return -1; } -error: - return timer; + return 0; } -ecs_ftime_t ecs_get_timeout( - const ecs_world_t *world, - ecs_entity_t timer) +int ecs_meta_set_null( + ecs_meta_cursor_t *cursor) { - 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; + 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; } -error: + return 0; } -ecs_entity_t ecs_set_interval( - ecs_world_t *world, - ecs_entity_t timer, - ecs_ftime_t interval) +bool ecs_meta_get_bool( + const ecs_meta_cursor_t *cursor) { - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - - timer = ecs_set(world, timer, EcsTimer, { - .timeout = interval, - .active = true - }); - - ecs_system_t *system_data = ecs_poly_get(world, timer, ecs_system_t); - if (system_data) { - system_data->tick_source = timer; + 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 EcsOpBool: return *(ecs_bool_t*)ptr; + case EcsOpI8: return *(ecs_i8_t*)ptr != 0; + case EcsOpU8: return *(ecs_u8_t*)ptr != 0; + case EcsOpChar: return *(ecs_char_t*)ptr != 0; + case EcsOpByte: return *(ecs_u8_t*)ptr != 0; + case EcsOpI16: return *(ecs_i16_t*)ptr != 0; + case EcsOpU16: return *(ecs_u16_t*)ptr != 0; + case EcsOpI32: return *(ecs_i32_t*)ptr != 0; + case EcsOpU32: return *(ecs_u32_t*)ptr != 0; + case EcsOpI64: return *(ecs_i64_t*)ptr != 0; + case EcsOpU64: return *(ecs_u64_t*)ptr != 0; + case EcsOpIPtr: return *(ecs_iptr_t*)ptr != 0; + case EcsOpUPtr: return *(ecs_uptr_t*)ptr != 0; + case EcsOpF32: return *(ecs_f32_t*)ptr != 0; + case EcsOpF64: return *(ecs_f64_t*)ptr != 0; + case EcsOpString: return *(const char**)ptr != NULL; + case EcsOpEnum: return *(ecs_i32_t*)ptr != 0; + case EcsOpBitmask: return *(ecs_u32_t*)ptr != 0; + case EcsOpEntity: return *(ecs_entity_t*)ptr != 0; + default: ecs_throw(ECS_INVALID_PARAMETER, + "invalid element for bool"); } error: - return timer; + return 0; } -ecs_ftime_t ecs_get_interval( - const ecs_world_t *world, - ecs_entity_t timer) +char ecs_meta_get_char( + const ecs_meta_cursor_t *cursor) { - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - - if (!timer) { - return 0; + 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 EcsOpChar: return *(ecs_char_t*)ptr != 0; + default: ecs_throw(ECS_INVALID_PARAMETER, + "invalid element for char"); } +error: + return 0; +} - const EcsTimer *value = ecs_get(world, timer, EcsTimer); - if (value) { - return value->timeout; +int64_t ecs_meta_get_int( + const 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 EcsOpBool: return *(ecs_bool_t*)ptr; + case EcsOpI8: return *(ecs_i8_t*)ptr; + case EcsOpU8: return *(ecs_u8_t*)ptr; + case EcsOpChar: return *(ecs_char_t*)ptr; + case EcsOpByte: return *(ecs_u8_t*)ptr; + case EcsOpI16: return *(ecs_i16_t*)ptr; + case EcsOpU16: return *(ecs_u16_t*)ptr; + case EcsOpI32: return *(ecs_i32_t*)ptr; + case EcsOpU32: return *(ecs_u32_t*)ptr; + case EcsOpI64: return *(ecs_i64_t*)ptr; + case EcsOpU64: return flecs_uto(int64_t, *(ecs_u64_t*)ptr); + case EcsOpIPtr: return *(ecs_iptr_t*)ptr; + case EcsOpUPtr: return flecs_uto(int64_t, *(ecs_uptr_t*)ptr); + case EcsOpF32: return (int64_t)*(ecs_f32_t*)ptr; + case EcsOpF64: return (int64_t)*(ecs_f64_t*)ptr; + case EcsOpString: return atoi(*(const char**)ptr); + case EcsOpEnum: return *(ecs_i32_t*)ptr; + case EcsOpBitmask: return *(ecs_u32_t*)ptr; + case EcsOpEntity: + ecs_throw(ECS_INVALID_PARAMETER, + "invalid conversion from entity to int"); + break; + default: ecs_throw(ECS_INVALID_PARAMETER, "invalid element for int"); } error: return 0; } -void ecs_start_timer( - ecs_world_t *world, - ecs_entity_t timer) +uint64_t ecs_meta_get_uint( + const ecs_meta_cursor_t *cursor) { - EcsTimer *ptr = ecs_get_mut(world, timer, EcsTimer); - ecs_check(ptr != NULL, ECS_INVALID_PARAMETER, NULL); - ptr->active = true; - ptr->time = 0; + 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 EcsOpBool: return *(ecs_bool_t*)ptr; + case EcsOpI8: return flecs_ito(uint64_t, *(ecs_i8_t*)ptr); + case EcsOpU8: return *(ecs_u8_t*)ptr; + case EcsOpChar: return flecs_ito(uint64_t, *(ecs_char_t*)ptr); + case EcsOpByte: return flecs_ito(uint64_t, *(ecs_u8_t*)ptr); + case EcsOpI16: return flecs_ito(uint64_t, *(ecs_i16_t*)ptr); + case EcsOpU16: return *(ecs_u16_t*)ptr; + case EcsOpI32: return flecs_ito(uint64_t, *(ecs_i32_t*)ptr); + case EcsOpU32: return *(ecs_u32_t*)ptr; + case EcsOpI64: return flecs_ito(uint64_t, *(ecs_i64_t*)ptr); + case EcsOpU64: return *(ecs_u64_t*)ptr; + case EcsOpIPtr: return flecs_ito(uint64_t, *(ecs_i64_t*)ptr); + case EcsOpUPtr: return *(ecs_uptr_t*)ptr; + case EcsOpF32: return flecs_ito(uint64_t, *(ecs_f32_t*)ptr); + case EcsOpF64: return flecs_ito(uint64_t, *(ecs_f64_t*)ptr); + case EcsOpString: return flecs_ito(uint64_t, atoi(*(const char**)ptr)); + case EcsOpEnum: return flecs_ito(uint64_t, *(ecs_i32_t*)ptr); + case EcsOpBitmask: return *(ecs_u32_t*)ptr; + case EcsOpEntity: return *(ecs_entity_t*)ptr; + default: ecs_throw(ECS_INVALID_PARAMETER, "invalid element for uint"); + } error: - return; + return 0; } -void ecs_stop_timer( - ecs_world_t *world, - ecs_entity_t timer) +double ecs_meta_get_float( + const ecs_meta_cursor_t *cursor) { - EcsTimer *ptr = ecs_get_mut(world, timer, EcsTimer); - ecs_check(ptr != NULL, ECS_INVALID_PARAMETER, NULL); - ptr->active = false; + 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 EcsOpBool: return *(ecs_bool_t*)ptr; + case EcsOpI8: return *(ecs_i8_t*)ptr; + case EcsOpU8: return *(ecs_u8_t*)ptr; + case EcsOpChar: return *(ecs_char_t*)ptr; + case EcsOpByte: return *(ecs_u8_t*)ptr; + case EcsOpI16: return *(ecs_i16_t*)ptr; + case EcsOpU16: return *(ecs_u16_t*)ptr; + case EcsOpI32: return *(ecs_i32_t*)ptr; + case EcsOpU32: return *(ecs_u32_t*)ptr; + case EcsOpI64: return (double)*(ecs_i64_t*)ptr; + case EcsOpU64: return (double)*(ecs_u64_t*)ptr; + case EcsOpIPtr: return (double)*(ecs_iptr_t*)ptr; + case EcsOpUPtr: return (double)*(ecs_uptr_t*)ptr; + case EcsOpF32: return (double)*(ecs_f32_t*)ptr; + case EcsOpF64: return *(ecs_f64_t*)ptr; + case EcsOpString: return atof(*(const char**)ptr); + case EcsOpEnum: return *(ecs_i32_t*)ptr; + case EcsOpBitmask: return *(ecs_u32_t*)ptr; + case EcsOpEntity: + ecs_throw(ECS_INVALID_PARAMETER, + "invalid conversion from entity to float"); + break; + default: ecs_throw(ECS_INVALID_PARAMETER, "invalid element for float"); + } error: - return; + return 0; } -ecs_entity_t ecs_set_rate( - ecs_world_t *world, - ecs_entity_t filter, - int32_t rate, - ecs_entity_t source) +const char* ecs_meta_get_string( + const ecs_meta_cursor_t *cursor) { - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - - filter = ecs_set(world, filter, EcsRateFilter, { - .rate = rate, - .src = source - }); - - ecs_system_t *system_data = ecs_poly_get(world, filter, ecs_system_t); - if (system_data) { - system_data->tick_source = filter; - } - + 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: return *(const char**)ptr; + default: ecs_throw(ECS_INVALID_PARAMETER, "invalid element for string"); + } error: - return filter; + return 0; } -void ecs_set_tick_source( - ecs_world_t *world, - ecs_entity_t system, - ecs_entity_t tick_source) +ecs_entity_t ecs_meta_get_entity( + const ecs_meta_cursor_t *cursor) { - 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_system_t *system_data = ecs_poly_get(world, system, ecs_system_t); - ecs_check(system_data != NULL, ECS_INVALID_PARAMETER, NULL); - - system_data->tick_source = tick_source; + 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: return *(ecs_entity_t*)ptr; + default: ecs_throw(ECS_INVALID_PARAMETER, "invalid element for entity"); + } error: - return; + return 0; } -void FlecsTimerImport( - ecs_world_t *world) -{ - ECS_MODULE(world, FlecsTimer); - - ECS_IMPORT(world, FlecsPipeline); +#endif - ecs_set_name_prefix(world, "Ecs"); - 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 = ecs_entity(world, {.name = "AddTickSource", .add = { ecs_dependson(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 - }); +#ifdef FLECS_EXPR - /* Timer handling */ - ecs_system_init(world, &(ecs_system_desc_t){ - .entity = ecs_entity(world, {.name = "ProgressTimers", .add = { ecs_dependson(EcsPreFrame)}}), - .query.filter.terms = { - { .id = ecs_id(EcsTimer) }, - { .id = ecs_id(EcsTickSource) } - }, - .callback = ProgressTimers - }); +static +int expr_ser_type( + const ecs_world_t *world, + ecs_vector_t *ser, + const void *base, + ecs_strbuf_t *str); - /* Rate filter handling */ - ecs_system_init(world, &(ecs_system_desc_t){ - .entity = ecs_entity(world, {.name = "ProgressRateFilters", .add = { ecs_dependson(EcsPreFrame)}}), - .query.filter.terms = { - { .id = ecs_id(EcsRateFilter), .inout = EcsIn }, - { .id = ecs_id(EcsTickSource), .inout = EcsOut } - }, - .callback = ProgressRateFilters - }); - - /* TickSource without a timer or rate filter just increases each frame */ - ecs_system_init(world, &(ecs_system_desc_t){ - .entity = ecs_entity(world, { .name = "ProgressTickSource", .add = { ecs_dependson(EcsPreFrame)}}), - .query.filter.terms = { - { .id = ecs_id(EcsTickSource), .inout = EcsOut }, - { .id = ecs_id(EcsRateFilter), .oper = EcsNot }, - { .id = ecs_id(EcsTimer), .oper = EcsNot } - }, - .callback = ProgressTickSource - }); -} - -#endif - - - -#ifdef FLECS_EXPR - -static -int expr_ser_type( - const ecs_world_t *world, - ecs_vector_t *ser, - const void *base, - ecs_strbuf_t *str); - -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, - int32_t in_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, + int32_t in_array); static int expr_ser_type_op( @@ -27958,8667 +27206,8593 @@ const char* ecs_parse_expr( #endif -#include -/* Utilities for C++ API */ -#ifdef FLECS_CPP +#ifdef FLECS_SYSTEM +#endif -/* Convert compiler-specific typenames extracted from __PRETTY_FUNCTION__ to - * a uniform identifier */ +#ifdef FLECS_PIPELINE +#endif -#define ECS_CONST_PREFIX "const " -#define ECS_STRUCT_PREFIX "struct " -#define ECS_CLASS_PREFIX "class " -#define ECS_ENUM_PREFIX "enum " +#ifdef FLECS_STATS -#define ECS_CONST_LEN (-1 + (ecs_size_t)sizeof(ECS_CONST_PREFIX)) -#define ECS_STRUCT_LEN (-1 + (ecs_size_t)sizeof(ECS_STRUCT_PREFIX)) -#define ECS_CLASS_LEN (-1 + (ecs_size_t)sizeof(ECS_CLASS_PREFIX)) -#define ECS_ENUM_LEN (-1 + (ecs_size_t)sizeof(ECS_ENUM_PREFIX)) +#define ECS_GAUGE_RECORD(m, t, value)\ + flecs_gauge_record(m, t, (ecs_float_t)(value)) + +#define ECS_COUNTER_RECORD(m, t, value)\ + flecs_counter_record(m, t, (ecs_float_t)(value)) + +#define ECS_METRIC_FIRST(stats)\ + ECS_CAST(ecs_metric_t*, ECS_OFFSET(&stats->first_, ECS_SIZEOF(int32_t))) + +#define ECS_METRIC_LAST(stats)\ + ECS_CAST(ecs_metric_t*, ECS_OFFSET(&stats->last_, -ECS_SIZEOF(ecs_metric_t))) static -ecs_size_t ecs_cpp_strip_prefix( - char *typeName, - ecs_size_t len, - const char *prefix, - ecs_size_t prefix_len) +int32_t t_next( + int32_t t) { - if ((len > prefix_len) && !ecs_os_strncmp(typeName, prefix, prefix_len)) { - ecs_os_memmove(typeName, typeName + prefix_len, len - prefix_len); - typeName[len - prefix_len] = '\0'; - len -= prefix_len; - } - return len; + return (t + 1) % ECS_STAT_WINDOW; } -static -void ecs_cpp_trim_type_name( - char *typeName) +static +int32_t t_prev( + int32_t t) { - ecs_size_t len = ecs_os_strlen(typeName); - - len = ecs_cpp_strip_prefix(typeName, len, ECS_CONST_PREFIX, ECS_CONST_LEN); - len = ecs_cpp_strip_prefix(typeName, len, ECS_STRUCT_PREFIX, ECS_STRUCT_LEN); - len = ecs_cpp_strip_prefix(typeName, len, ECS_CLASS_PREFIX, ECS_CLASS_LEN); - len = ecs_cpp_strip_prefix(typeName, len, ECS_ENUM_PREFIX, ECS_ENUM_LEN); - - while (typeName[len - 1] == ' ' || - typeName[len - 1] == '&' || - typeName[len - 1] == '*') - { - len --; - typeName[len] = '\0'; - } - - /* Remove const at end of string */ - if (len > ECS_CONST_LEN) { - if (!ecs_os_strncmp(&typeName[len - ECS_CONST_LEN], " const", ECS_CONST_LEN)) { - typeName[len - ECS_CONST_LEN] = '\0'; - } - len -= ECS_CONST_LEN; - } - - /* Check if there are any remaining "struct " strings, which can happen - * if this is a template type on msvc. */ - if (len > ECS_STRUCT_LEN) { - char *ptr = typeName; - while ((ptr = strstr(ptr + 1, ECS_STRUCT_PREFIX)) != 0) { - /* Make sure we're not matched with part of a longer identifier - * that contains 'struct' */ - if (ptr[-1] == '<' || ptr[-1] == ',' || isspace(ptr[-1])) { - ecs_os_memmove(ptr, ptr + ECS_STRUCT_LEN, - ecs_os_strlen(ptr + ECS_STRUCT_LEN) + 1); - len -= ECS_STRUCT_LEN; - } - } - } + return (t - 1 + ECS_STAT_WINDOW) % ECS_STAT_WINDOW; } -char* ecs_cpp_get_type_name( - char *type_name, - const char *func_name, - size_t len) +static +void flecs_gauge_record( + ecs_metric_t *m, + int32_t t, + ecs_float_t value) { - memcpy(type_name, func_name + ECS_FUNC_NAME_FRONT(const char*, type_name), len); - type_name[len] = '\0'; - ecs_cpp_trim_type_name(type_name); - return type_name; + m->gauge.avg[t] = value; + m->gauge.min[t] = value; + m->gauge.max[t] = value; } -char* ecs_cpp_get_symbol_name( - char *symbol_name, - const char *type_name, - size_t len) +static +ecs_float_t flecs_counter_record( + ecs_metric_t *m, + int32_t t, + ecs_float_t value) { - // Symbol is same as name, but with '::' replaced with '.' - ecs_os_strcpy(symbol_name, type_name); - - char *ptr; - size_t i; - for (i = 0, ptr = symbol_name; i < len && *ptr; i ++, ptr ++) { - if (*ptr == ':') { - symbol_name[i] = '.'; - ptr ++; - } else { - symbol_name[i] = *ptr; - } - } - - symbol_name[i] = '\0'; - - return symbol_name; + int32_t tp = t_prev(t); + ecs_float_t prev = m->counter.value[tp]; + m->counter.value[t] = value; + flecs_gauge_record(m, t, value - prev); + return value - prev; } static -const char* cpp_func_rchr( - const char *func_name, - ecs_size_t func_name_len, - char ch) +void flecs_metric_print( + const char *name, + ecs_float_t value) { - const char *r = strrchr(func_name, ch); - if ((r - func_name) >= (func_name_len - flecs_uto(ecs_size_t, ECS_FUNC_NAME_BACK))) { - return NULL; - } - return r; + ecs_size_t len = ecs_os_strlen(name); + ecs_trace("%s: %*s %.2f", name, 32 - len, "", (double)value); } static -const char* cpp_func_max( - const char *a, - const char *b) +void flecs_gauge_print( + const char *name, + int32_t t, + const ecs_metric_t *m) { - if (a > b) return a; - return b; + flecs_metric_print(name, m->gauge.avg[t]); } -char* ecs_cpp_get_constant_name( - char *constant_name, - const char *func_name, - size_t func_name_len) +static +void flecs_counter_print( + const char *name, + int32_t t, + const ecs_metric_t *m) { - ecs_size_t f_len = flecs_uto(ecs_size_t, func_name_len); - const char *start = cpp_func_rchr(func_name, f_len, ' '); - start = cpp_func_max(start, cpp_func_rchr(func_name, f_len, ')')); - start = cpp_func_max(start, cpp_func_rchr(func_name, f_len, ':')); - start = cpp_func_max(start, cpp_func_rchr(func_name, f_len, ',')); - ecs_assert(start != NULL, ECS_INVALID_PARAMETER, func_name); - start ++; - - ecs_size_t len = flecs_uto(ecs_size_t, - (f_len - (start - func_name) - flecs_uto(ecs_size_t, ECS_FUNC_NAME_BACK))); - ecs_os_memcpy_n(constant_name, start, char, len); - constant_name[len] = '\0'; - return constant_name; + flecs_metric_print(name, m->counter.rate.avg[t]); } -// Names returned from the name_helper class do not start with :: -// but are relative to the root. If the namespace of the type -// overlaps with the namespace of the current module, strip it from -// the implicit identifier. -// This allows for registration of component types that are not in the -// module namespace to still be registered under the module scope. -const char* ecs_cpp_trim_module( - ecs_world_t *world, - const char *type_name) +void ecs_metric_reduce( + ecs_metric_t *dst, + const ecs_metric_t *src, + int32_t t_dst, + int32_t t_src) { - ecs_entity_t scope = ecs_get_scope(world); - if (!scope) { - return type_name; - } + ecs_check(dst != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(src != NULL, ECS_INVALID_PARAMETER, NULL); - char *path = ecs_get_path_w_sep(world, 0, scope, "::", NULL); - if (path) { - const char *ptr = strrchr(type_name, ':'); - ecs_assert(ptr != type_name, ECS_INTERNAL_ERROR, NULL); - if (ptr) { - ptr --; - ecs_assert(ptr[0] == ':', ECS_INTERNAL_ERROR, NULL); - ecs_size_t name_path_len = (ecs_size_t)(ptr - type_name); - if (name_path_len <= ecs_os_strlen(path)) { - if (!ecs_os_strncmp(type_name, path, name_path_len)) { - type_name = &type_name[name_path_len + 2]; - } - } - } - } - ecs_os_free(path); + bool min_set = false; + dst->gauge.avg[t_dst] = 0; + dst->gauge.min[t_dst] = 0; + dst->gauge.max[t_dst] = 0; - return type_name; -} + ecs_float_t fwindow = (ecs_float_t)ECS_STAT_WINDOW; -// Validate registered component -void ecs_cpp_component_validate( - ecs_world_t *world, - ecs_entity_t id, - const char *name, - size_t size, - size_t alignment, - bool implicit_name) -{ - /* If entity has a name check if it matches */ - if (ecs_is_valid(world, id) && ecs_get_name(world, id) != NULL) { - if (!implicit_name && id >= EcsFirstUserComponentId) { -# ifndef FLECS_NDEBUG - char *path = ecs_get_path_w_sep( - world, 0, id, "::", NULL); - if (ecs_os_strcmp(path, name)) { - ecs_err( - "component '%s' already registered with name '%s'", - name, path); - ecs_abort(ECS_INCONSISTENT_NAME, NULL); - } - ecs_os_free(path); -# endif + int32_t i; + for (i = 0; i < ECS_STAT_WINDOW; i ++) { + int32_t t = (t_src + i) % ECS_STAT_WINDOW; + dst->gauge.avg[t_dst] += src->gauge.avg[t] / fwindow; + + if (!min_set || (src->gauge.min[t] < dst->gauge.min[t_dst])) { + dst->gauge.min[t_dst] = src->gauge.min[t]; + min_set = true; } - } else { - /* Ensure that the entity id valid */ - if (!ecs_is_alive(world, id)) { - ecs_ensure(world, id); + if ((src->gauge.max[t] > dst->gauge.max[t_dst])) { + dst->gauge.max[t_dst] = src->gauge.max[t]; } - - /* Register name with entity, so that when the entity is created the - * correct id will be resolved from the name. Only do this when the - * entity is empty. */ - ecs_add_path_w_sep(world, id, 0, name, "::", "::"); } + + dst->counter.value[t_dst] = src->counter.value[t_src]; - /* If a component was already registered with this id but with a - * different size, the ecs_component_init function will fail. */ - - /* We need to explicitly call ecs_component_init here again. Even though - * the component was already registered, it may have been registered - * with a different world. This ensures that the component is registered - * with the same id for the current world. - * If the component was registered already, nothing will change. */ - ecs_entity_t ent = ecs_component_init(world, &(ecs_component_desc_t){ - .entity = id, - .type.size = flecs_uto(int32_t, size), - .type.alignment = flecs_uto(int32_t, alignment) - }); - (void)ent; - ecs_assert(ent == id, ECS_INTERNAL_ERROR, NULL); +error: + return; } -ecs_entity_t ecs_cpp_component_register( - ecs_world_t *world, - ecs_entity_t id, - const char *name, - const char *symbol, - ecs_size_t size, - ecs_size_t alignment, - bool implicit_name) +void ecs_metric_reduce_last( + ecs_metric_t *m, + int32_t prev, + int32_t count) { - (void)size; - (void)alignment; + ecs_check(m != NULL, ECS_INVALID_PARAMETER, NULL); + int32_t t = t_next(prev); - /* If the component is not yet registered, ensure no other component - * or entity has been registered with this name. Ensure component is - * looked up from root. */ - bool existing = false; - ecs_entity_t prev_scope = ecs_set_scope(world, 0); - ecs_entity_t ent; - if (id) { - ent = id; - } else { - ent = ecs_lookup_path_w_sep(world, 0, name, "::", "::", false); - existing = ent != 0; + if (m->gauge.min[t] < m->gauge.min[prev]) { + m->gauge.min[prev] = m->gauge.min[t]; } - ecs_set_scope(world, prev_scope); - /* If entity exists, compare symbol name to ensure that the component - * we are trying to register under this name is the same */ - if (ent) { - const EcsComponent *component = ecs_get(world, ent, EcsComponent); - if (component != NULL) { - const char *sym = ecs_get_symbol(world, ent); - ecs_assert(!existing || (sym != NULL), ECS_MISSING_SYMBOL, - ecs_get_name(world, ent)); - (void)existing; + if (m->gauge.max[t] > m->gauge.max[prev]) { + m->gauge.max[prev] = m->gauge.max[t]; + } - if (sym && ecs_os_strcmp(sym, symbol)) { - /* Application is trying to register a type with an entity that - * was already associated with another type. In most cases this - * is an error, with the exception of a scenario where the - * application is wrapping a C type with a C++ type. - * - * In this case the C++ type typically inherits from the C type, - * and adds convenience methods to the derived class without - * changing anything that would change the size or layout. - * - * To meet this condition, the new type must have the same size - * and alignment as the existing type, and the name of the type - * type must be equal to the registered name (not symbol). - * - * The latter ensures that it was the intent of the application - * to alias the type, vs. accidentally registering an unrelated - * type with the same size/alignment. */ - char *type_path = ecs_get_fullpath(world, ent); - if (ecs_os_strcmp(type_path, symbol) || - component->size != size || - component->alignment != alignment) - { - ecs_err( - "component with name '%s' is already registered for"\ - " type '%s' (trying to register for type '%s')", - name, sym, symbol); - ecs_abort(ECS_NAME_IN_USE, NULL); - } - ecs_os_free(type_path); - } - } + ecs_float_t fcount = (ecs_float_t)(count + 1); + ecs_float_t cur = m->gauge.avg[prev]; + ecs_float_t next = m->gauge.avg[t]; - /* If no entity is found, lookup symbol to check if the component was - * registered under a different name. */ - } else if (!implicit_name) { - ent = ecs_lookup_symbol(world, symbol, false); - ecs_assert(ent == 0 || (ent == id), ECS_INCONSISTENT_COMPONENT_ID, symbol); - } + cur *= ((fcount - 1) / fcount); + next *= 1 / fcount; - return ent; + m->gauge.avg[prev] = cur + next; + m->counter.value[prev] = m->counter.value[t]; + +error: + return; } -ecs_entity_t ecs_cpp_component_register_explicit( - ecs_world_t *world, - ecs_entity_t s_id, - ecs_entity_t id, - const char *name, - const char *type_name, - const char *symbol, - size_t size, - size_t alignment, - bool is_component) +void ecs_metric_copy( + ecs_metric_t *m, + int32_t dst, + int32_t src) { - char *existing_name = NULL; + ecs_check(m != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(dst != src, ECS_INVALID_PARAMETER, NULL); - // If an explicit id is provided, it is possible that the symbol and - // name differ from the actual type, as the application may alias - // one type to another. - if (!id) { - if (!name) { - // If no name was provided first check if a type with the provided - // symbol was already registered. - id = ecs_lookup_symbol(world, symbol, false); - if (id) { - existing_name = ecs_get_path_w_sep(world, 0, id, "::", "::"); - name = existing_name; - } else { - // If type is not yet known, derive from type name - name = ecs_cpp_trim_module(world, type_name); - } - } - } else { - // If an explicit id is provided but it has no name, inherit - // the name from the type. - if (!ecs_is_valid(world, id) || !ecs_get_name(world, id)) { - name = ecs_cpp_trim_module(world, type_name); - } - } + m->gauge.avg[dst] = m->gauge.avg[src]; + m->gauge.min[dst] = m->gauge.min[src]; + m->gauge.max[dst] = m->gauge.max[src]; + m->counter.value[dst] = m->counter.value[src]; - ecs_entity_t entity; - if (is_component || size != 0) { - entity = ecs_entity(world, { - .id = s_id, - .name = name, - .sep = "::", - .root_sep = "::", - .symbol = symbol, - .use_low_id = true - }); - ecs_assert(entity != 0, ECS_INVALID_OPERATION, NULL); +error: + return; +} - entity = ecs_component_init(world, &(ecs_component_desc_t){ - .entity = entity, - .type.size = flecs_uto(int32_t, size), - .type.alignment = flecs_uto(int32_t, alignment) - }); - ecs_assert(entity != 0, ECS_INVALID_OPERATION, NULL); - } else { - entity = ecs_entity_init(world, &(ecs_entity_desc_t){ - .id = s_id, - .name = name, - .sep = "::", - .root_sep = "::", - .symbol = symbol, - .use_low_id = true - }); +static +void flecs_stats_reduce( + ecs_metric_t *dst_cur, + ecs_metric_t *dst_last, + ecs_metric_t *src_cur, + int32_t t_dst, + int32_t t_src) +{ + for (; dst_cur <= dst_last; dst_cur ++, src_cur ++) { + ecs_metric_reduce(dst_cur, src_cur, t_dst, t_src); } - - ecs_assert(entity != 0, ECS_INTERNAL_ERROR, NULL); - ecs_assert(!s_id || s_id == entity, ECS_INTERNAL_ERROR, NULL); - ecs_os_free(existing_name); - - return entity; } -ecs_entity_t ecs_cpp_enum_constant_register( - ecs_world_t *world, - ecs_entity_t parent, - ecs_entity_t id, - const char *name, - int value) +static +void flecs_stats_reduce_last( + ecs_metric_t *dst_cur, + ecs_metric_t *dst_last, + ecs_metric_t *src_cur, + int32_t t_dst, + int32_t t_src, + int32_t count) { - ecs_suspend_readonly_state_t readonly_state; - world = flecs_suspend_readonly(world, &readonly_state); + int32_t t_dst_next = t_next(t_dst); + for (; dst_cur <= dst_last; dst_cur ++, src_cur ++) { + /* Reduce into previous value */ + ecs_metric_reduce_last(dst_cur, t_dst, count); - const char *parent_name = ecs_get_name(world, parent); - ecs_size_t parent_name_len = ecs_os_strlen(parent_name); - if (!ecs_os_strncmp(name, parent_name, parent_name_len)) { - name += parent_name_len; - if (name[0] == '_') { - name ++; - } + /* Restore old value */ + dst_cur->gauge.avg[t_dst_next] = src_cur->gauge.avg[t_src]; + dst_cur->gauge.min[t_dst_next] = src_cur->gauge.min[t_src]; + dst_cur->gauge.max[t_dst_next] = src_cur->gauge.max[t_src]; + dst_cur->counter.value[t_dst_next] = src_cur->counter.value[t_src]; } +} - ecs_entity_t prev = ecs_set_scope(world, parent); - id = ecs_entity_init(world, &(ecs_entity_desc_t){ - .id = id, - .name = name - }); - ecs_assert(id != 0, ECS_INVALID_OPERATION, name); - ecs_set_scope(world, prev); - - #ifdef FLECS_DEBUG - const EcsComponent *cptr = ecs_get(world, parent, EcsComponent); - ecs_assert(cptr != NULL, ECS_INVALID_PARAMETER, "enum is not a component"); - ecs_assert(cptr->size == ECS_SIZEOF(int32_t), ECS_UNSUPPORTED, - "enum component must have 32bit size"); - #endif - - ecs_set_id(world, id, parent, sizeof(int), &value); +static +void flecs_stats_repeat_last( + ecs_metric_t *cur, + ecs_metric_t *last, + int32_t t) +{ + int32_t prev = t_prev(t); + for (; cur <= last; cur ++) { + ecs_metric_copy(cur, t, prev); + } +} - flecs_resume_readonly(world, &readonly_state); +static +void flecs_stats_copy_last( + ecs_metric_t *dst_cur, + ecs_metric_t *dst_last, + ecs_metric_t *src_cur, + int32_t t_dst, + int32_t t_src) +{ + for (; dst_cur <= dst_last; dst_cur ++, src_cur ++) { + dst_cur->gauge.avg[t_dst] = src_cur->gauge.avg[t_src]; + dst_cur->gauge.min[t_dst] = src_cur->gauge.min[t_src]; + dst_cur->gauge.max[t_dst] = src_cur->gauge.max[t_src]; + dst_cur->counter.value[t_dst] = src_cur->counter.value[t_src]; + } +} - ecs_trace("#[green]constant#[reset] %s.%s created with value %d", - ecs_get_name(world, parent), name, value); +void ecs_world_stats_get( + const ecs_world_t *world, + ecs_world_stats_t *s) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(s != NULL, ECS_INVALID_PARAMETER, NULL); - return id; -} + world = ecs_get_world(world); -static int32_t flecs_reset_count = 0; + int32_t t = s->t = t_next(s->t); -int32_t ecs_cpp_reset_count_get(void) { - return flecs_reset_count; -} + ecs_ftime_t delta_world_time = ECS_COUNTER_RECORD(&s->world_time_total_raw, t, world->info.world_time_total_raw); + ECS_COUNTER_RECORD(&s->world_time_total, t, world->info.world_time_total); + ECS_COUNTER_RECORD(&s->frame_time_total, t, world->info.frame_time_total); + ECS_COUNTER_RECORD(&s->system_time_total, t, world->info.system_time_total); + ECS_COUNTER_RECORD(&s->merge_time_total, t, world->info.merge_time_total); -int32_t ecs_cpp_reset_count_inc(void) { - return ++flecs_reset_count; -} + ecs_ftime_t delta_frame_count = ECS_COUNTER_RECORD(&s->frame_count_total, t, world->info.frame_count_total); + ECS_COUNTER_RECORD(&s->merge_count_total, t, world->info.merge_count_total); + ECS_COUNTER_RECORD(&s->pipeline_build_count_total, t, world->info.pipeline_build_count_total); + ECS_COUNTER_RECORD(&s->systems_ran_frame, t, world->info.systems_ran_frame); -#endif + if (delta_world_time != 0 && delta_frame_count != 0) { + ECS_GAUGE_RECORD( + &s->fps, t, (ecs_ftime_t)1 / (delta_world_time / (ecs_ftime_t)delta_frame_count)); + } else { + ECS_GAUGE_RECORD(&s->fps, t, 0); + } + ECS_GAUGE_RECORD(&s->delta_time, t, delta_world_time); -#ifdef FLECS_REST + ECS_GAUGE_RECORD(&s->entity_count, t, flecs_sparse_count(ecs_eis(world))); + ECS_GAUGE_RECORD(&s->entity_not_alive_count, t, + flecs_sparse_not_alive_count(ecs_eis(world))); -typedef struct { - ecs_world_t *world; - ecs_entity_t entity; - ecs_http_server_t *srv; - int32_t rc; -} ecs_rest_ctx_t; + ECS_GAUGE_RECORD(&s->id_count, t, world->info.id_count); + ECS_GAUGE_RECORD(&s->tag_id_count, t, world->info.tag_id_count); + ECS_GAUGE_RECORD(&s->component_id_count, t, world->info.component_id_count); + ECS_GAUGE_RECORD(&s->pair_id_count, t, world->info.pair_id_count); + ECS_GAUGE_RECORD(&s->wildcard_id_count, t, world->info.wildcard_id_count); + ECS_GAUGE_RECORD(&s->component_count, t, ecs_sparse_count(world->type_info)); -static ECS_COPY(EcsRest, dst, src, { - ecs_rest_ctx_t *impl = src->impl; - if (impl) { - impl->rc ++; - } + ECS_GAUGE_RECORD(&s->query_count, t, ecs_count_id(world, EcsQuery)); + ECS_GAUGE_RECORD(&s->observer_count, t, ecs_count_id(world, EcsObserver)); + ECS_GAUGE_RECORD(&s->system_count, t, ecs_count_id(world, EcsSystem)); - ecs_os_strset(&dst->ipaddr, src->ipaddr); - dst->port = src->port; - dst->impl = impl; -}) + ECS_COUNTER_RECORD(&s->id_create_count, t, world->info.id_create_total); + ECS_COUNTER_RECORD(&s->id_delete_count, t, world->info.id_delete_total); + ECS_COUNTER_RECORD(&s->table_create_count, t, world->info.table_create_total); + ECS_COUNTER_RECORD(&s->table_delete_count, t, world->info.table_delete_total); -static ECS_MOVE(EcsRest, dst, src, { - *dst = *src; - src->ipaddr = NULL; - src->impl = NULL; -}) + ECS_COUNTER_RECORD(&s->new_count, t, world->info.new_count); + ECS_COUNTER_RECORD(&s->bulk_new_count, t, world->info.bulk_new_count); + ECS_COUNTER_RECORD(&s->delete_count, t, world->info.delete_count); + ECS_COUNTER_RECORD(&s->clear_count, t, world->info.clear_count); + ECS_COUNTER_RECORD(&s->add_count, t, world->info.add_count); + ECS_COUNTER_RECORD(&s->remove_count, t, world->info.remove_count); + ECS_COUNTER_RECORD(&s->set_count, t, world->info.set_count); + ECS_COUNTER_RECORD(&s->discard_count, t, world->info.discard_count); -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); -}) + ECS_GAUGE_RECORD(&s->table_count, t, world->info.table_count); + ECS_GAUGE_RECORD(&s->empty_table_count, t, world->info.empty_table_count); + ECS_GAUGE_RECORD(&s->tag_table_count, t, world->info.tag_table_count); + ECS_GAUGE_RECORD(&s->trivial_table_count, t, world->info.trivial_table_count); + ECS_GAUGE_RECORD(&s->table_storage_count, t, world->info.table_storage_count); + ECS_GAUGE_RECORD(&s->table_record_count, t, world->info.table_record_count); -static char *rest_last_err; + ECS_COUNTER_RECORD(&s->alloc_count, t, ecs_os_api_malloc_count + + ecs_os_api_calloc_count); + ECS_COUNTER_RECORD(&s->realloc_count, t, ecs_os_api_realloc_count); + ECS_COUNTER_RECORD(&s->free_count, t, ecs_os_api_free_count); -static -void flecs_rest_capture_log( - int32_t level, - const char *file, - int32_t line, - const char *msg) -{ - (void)file; (void)line; + int64_t outstanding_allocs = ecs_os_api_malloc_count + + ecs_os_api_calloc_count - ecs_os_api_free_count; + ECS_GAUGE_RECORD(&s->outstanding_alloc_count, t, outstanding_allocs); - if (!rest_last_err && level < 0) { - rest_last_err = ecs_os_strdup(msg); - } +error: + return; } -static -char* flecs_rest_get_captured_log(void) { - char *result = rest_last_err; - rest_last_err = NULL; - return result; +void ecs_world_stats_reduce( + ecs_world_stats_t *dst, + const ecs_world_stats_t *src) +{ + flecs_stats_reduce(ECS_METRIC_FIRST(dst), ECS_METRIC_LAST(dst), + ECS_METRIC_FIRST(src), (dst->t = t_next(dst->t)), src->t); } -static -void flecs_reply_verror( - ecs_http_reply_t *reply, - const char *fmt, - va_list args) +void ecs_world_stats_reduce_last( + ecs_world_stats_t *dst, + const ecs_world_stats_t *src, + int32_t count) { - ecs_strbuf_appendstr(&reply->body, "{\"error\":\""); - ecs_strbuf_vappend(&reply->body, fmt, args); - ecs_strbuf_appendstr(&reply->body, "\"}"); + flecs_stats_reduce_last(ECS_METRIC_FIRST(dst), ECS_METRIC_LAST(dst), + ECS_METRIC_FIRST(src), (dst->t = t_prev(dst->t)), src->t, count); } -static -void flecs_reply_error( - ecs_http_reply_t *reply, - const char *fmt, - ...) +void ecs_world_stats_repeat_last( + ecs_world_stats_t *stats) { - va_list args; - va_start(args, fmt); - flecs_reply_verror(reply, fmt, args); - va_end(args); + flecs_stats_repeat_last(ECS_METRIC_FIRST(stats), ECS_METRIC_LAST(stats), + (stats->t = t_next(stats->t))); } -static -void flecs_rest_bool_param( - const ecs_http_request_t *req, - const char *name, - bool *value_out) +void ecs_world_stats_copy_last( + ecs_world_stats_t *dst, + const ecs_world_stats_t *src) { - 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; - } - } + flecs_stats_copy_last(ECS_METRIC_FIRST(dst), ECS_METRIC_LAST(dst), + ECS_METRIC_FIRST(src), dst->t, t_next(src->t)); } -static -void flecs_rest_int_param( - const ecs_http_request_t *req, - const char *name, - int32_t *value_out) +void ecs_query_stats_get( + const ecs_world_t *world, + const ecs_query_t *query, + ecs_query_stats_t *s) { - const char *value = ecs_http_get_param(req, name); - if (value) { - *value_out = atoi(value); + 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; + + int32_t t = s->t = t_next(s->t); + + if (query->filter.flags & EcsFilterMatchThis) { + ECS_GAUGE_RECORD(&s->matched_entity_count, t, + ecs_query_entity_count(query)); + ECS_GAUGE_RECORD(&s->matched_table_count, t, + ecs_query_table_count(query)); + ECS_GAUGE_RECORD(&s->matched_empty_table_count, t, + ecs_query_empty_table_count(query)); + } else { + ECS_GAUGE_RECORD(&s->matched_entity_count, t, 0); + ECS_GAUGE_RECORD(&s->matched_table_count, t, 0); + ECS_GAUGE_RECORD(&s->matched_empty_table_count, t, 0); } + +error: + return; } -static -void flecs_rest_string_param( - const ecs_http_request_t *req, - const char *name, - char **value_out) +void ecs_query_stats_reduce( + ecs_query_stats_t *dst, + const ecs_query_stats_t *src) { - const char *value = ecs_http_get_param(req, name); - if (value) { - *value_out = (char*)value; - } + flecs_stats_reduce(ECS_METRIC_FIRST(dst), ECS_METRIC_LAST(dst), + ECS_METRIC_FIRST(src), (dst->t = t_next(dst->t)), src->t); } -static -void flecs_rest_parse_json_ser_entity_params( - ecs_entity_to_json_desc_t *desc, - const ecs_http_request_t *req) +void ecs_query_stats_reduce_last( + ecs_query_stats_t *dst, + const ecs_query_stats_t *src, + int32_t count) { - flecs_rest_bool_param(req, "path", &desc->serialize_path); - flecs_rest_bool_param(req, "label", &desc->serialize_label); - flecs_rest_bool_param(req, "brief", &desc->serialize_brief); - flecs_rest_bool_param(req, "link", &desc->serialize_link); - flecs_rest_bool_param(req, "color", &desc->serialize_color); - flecs_rest_bool_param(req, "id_labels", &desc->serialize_id_labels); - flecs_rest_bool_param(req, "base", &desc->serialize_base); - flecs_rest_bool_param(req, "values", &desc->serialize_values); - flecs_rest_bool_param(req, "private", &desc->serialize_private); - flecs_rest_bool_param(req, "type_info", &desc->serialize_type_info); + flecs_stats_reduce_last(ECS_METRIC_FIRST(dst), ECS_METRIC_LAST(dst), + ECS_METRIC_FIRST(src), (dst->t = t_prev(dst->t)), src->t, count); } -static -void flecs_rest_parse_json_ser_iter_params( - ecs_iter_to_json_desc_t *desc, - const ecs_http_request_t *req) +void ecs_query_stats_repeat_last( + ecs_query_stats_t *stats) { - flecs_rest_bool_param(req, "term_ids", &desc->serialize_term_ids); - flecs_rest_bool_param(req, "ids", &desc->serialize_ids); - flecs_rest_bool_param(req, "sources", &desc->serialize_sources); - flecs_rest_bool_param(req, "variables", &desc->serialize_variables); - flecs_rest_bool_param(req, "is_set", &desc->serialize_is_set); - flecs_rest_bool_param(req, "values", &desc->serialize_values); - flecs_rest_bool_param(req, "entities", &desc->serialize_entities); - flecs_rest_bool_param(req, "entity_labels", &desc->serialize_entity_labels); - flecs_rest_bool_param(req, "entity_ids", &desc->serialize_entity_ids); - flecs_rest_bool_param(req, "variable_labels", &desc->serialize_variable_labels); - flecs_rest_bool_param(req, "variable_ids", &desc->serialize_variable_ids); - flecs_rest_bool_param(req, "colors", &desc->serialize_colors); - flecs_rest_bool_param(req, "duration", &desc->measure_eval_duration); - flecs_rest_bool_param(req, "type_info", &desc->serialize_type_info); + flecs_stats_repeat_last(ECS_METRIC_FIRST(stats), ECS_METRIC_LAST(stats), + (stats->t = t_next(stats->t))); } -static -bool flecs_rest_reply_entity( - ecs_world_t *world, - const ecs_http_request_t* req, - ecs_http_reply_t *reply) +void ecs_query_stats_copy_last( + ecs_query_stats_t *dst, + const ecs_query_stats_t *src) { - char *path = &req->path[7]; - ecs_dbg_2("rest: request entity '%s'", path); - - ecs_entity_t e = ecs_lookup_path_w_sep( - world, 0, path, "/", NULL, false); - if (!e) { - ecs_dbg_2("rest: entity '%s' not found", path); - flecs_reply_error(reply, "entity '%s' not found", path); - reply->code = 404; - return true; - } - - ecs_entity_to_json_desc_t desc = ECS_ENTITY_TO_JSON_INIT; - flecs_rest_parse_json_ser_entity_params(&desc, req); - - ecs_entity_to_json_buf(world, e, &reply->body, &desc); - return true; + flecs_stats_copy_last(ECS_METRIC_FIRST(dst), ECS_METRIC_LAST(dst), + ECS_METRIC_FIRST(src), dst->t, t_next(src->t)); } -static -bool flecs_rest_reply_query( - ecs_world_t *world, - const ecs_http_request_t* req, - ecs_http_reply_t *reply) -{ - 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; - } +#ifdef FLECS_SYSTEM - ecs_dbg_2("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_ = flecs_rest_capture_log; +bool ecs_system_stats_get( + const ecs_world_t *world, + ecs_entity_t system, + ecs_system_stats_t *s) +{ + 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_rule_t *r = ecs_rule_init(world, &(ecs_filter_desc_t){ - .expr = q - }); - if (!r) { - char *err = flecs_rest_get_captured_log(); - char *escaped_err = ecs_astresc('"', err); - flecs_reply_error(reply, escaped_err); - reply->code = 400; /* bad request */ - ecs_os_free(escaped_err); - ecs_os_free(err); - } else { - ecs_iter_to_json_desc_t desc = ECS_ITER_TO_JSON_INIT; - flecs_rest_parse_json_ser_iter_params(&desc, req); + world = ecs_get_world(world); - int32_t offset = 0; - int32_t limit = 1000; + const ecs_system_t *ptr = ecs_poly_get(world, system, ecs_system_t); + if (!ptr) { + return false; + } - flecs_rest_int_param(req, "offset", &offset); - flecs_rest_int_param(req, "limit", &limit); + ecs_query_stats_get(world, ptr->query, &s->query); + int32_t t = s->query.t; - ecs_iter_t it = ecs_rule_iter(world, r); - ecs_iter_t pit = ecs_page_iter(&it, offset, limit); - ecs_iter_to_json_buf(world, &pit, &reply->body, &desc); - ecs_rule_fini(r); - } + ECS_COUNTER_RECORD(&s->time_spent, t, ptr->time_spent); + ECS_COUNTER_RECORD(&s->invoke_count, t, ptr->invoke_count); + ECS_GAUGE_RECORD(&s->active, t, !ecs_has_id(world, system, EcsEmpty)); + ECS_GAUGE_RECORD(&s->enabled, t, !ecs_has_id(world, system, EcsDisabled)); - ecs_os_api.log_ = prev_log_; - ecs_log_enable_colors(prev_color); + s->task = !(ptr->query->filter.flags & EcsFilterMatchThis); return true; +error: + return false; } -#ifdef FLECS_MONITOR - -static -void flecs_rest_array_append( - ecs_strbuf_t *reply, - const char *field, - const ecs_float_t *values, - int32_t t) +void ecs_system_stats_reduce( + ecs_system_stats_t *dst, + const ecs_system_stats_t *src) { - ecs_strbuf_list_append(reply, "\"%s\"", field); - ecs_strbuf_appendch(reply, ':'); - ecs_strbuf_list_push(reply, "[", ","); - - int32_t i; - for (i = t + 1; i <= (t + ECS_STAT_WINDOW); i ++) { - int32_t index = i % ECS_STAT_WINDOW; - ecs_strbuf_list_next(reply); - ecs_strbuf_appendflt(reply, (double)values[index], '"'); - } - - ecs_strbuf_list_pop(reply, "]"); + ecs_query_stats_reduce(&dst->query, &src->query); + dst->task = src->task; + flecs_stats_reduce(ECS_METRIC_FIRST(dst), ECS_METRIC_LAST(dst), + ECS_METRIC_FIRST(src), dst->query.t, src->query.t); } -static -void flecs_rest_gauge_append( - ecs_strbuf_t *reply, - const ecs_metric_t *m, - const char *field, - int32_t t) +void ecs_system_stats_reduce_last( + ecs_system_stats_t *dst, + const ecs_system_stats_t *src, + int32_t count) { - ecs_strbuf_list_append(reply, "\"%s\"", field); - ecs_strbuf_appendch(reply, ':'); - ecs_strbuf_list_push(reply, "{", ","); - - flecs_rest_array_append(reply, "avg", m->gauge.avg, t); - flecs_rest_array_append(reply, "min", m->gauge.min, t); - flecs_rest_array_append(reply, "max", m->gauge.max, t); - - ecs_strbuf_list_pop(reply, "}"); + ecs_query_stats_reduce_last(&dst->query, &src->query, count); + dst->task = src->task; + flecs_stats_reduce_last(ECS_METRIC_FIRST(dst), ECS_METRIC_LAST(dst), + ECS_METRIC_FIRST(src), dst->query.t, src->query.t, count); } -static -void flecs_rest_counter_append( - ecs_strbuf_t *reply, - const ecs_metric_t *m, - const char *field, - int32_t t) +void ecs_system_stats_repeat_last( + ecs_system_stats_t *stats) { - flecs_rest_gauge_append(reply, m, field, t); + ecs_query_stats_repeat_last(&stats->query); + flecs_stats_repeat_last(ECS_METRIC_FIRST(stats), ECS_METRIC_LAST(stats), + (stats->query.t)); } -#define ECS_GAUGE_APPEND_T(reply, s, field, t)\ - flecs_rest_gauge_append(reply, &(s)->field, #field, t) - -#define ECS_COUNTER_APPEND_T(reply, s, field, t)\ - flecs_rest_counter_append(reply, &(s)->field, #field, t) +void ecs_system_stats_copy_last( + ecs_system_stats_t *dst, + const ecs_system_stats_t *src) +{ + ecs_query_stats_copy_last(&dst->query, &src->query); + dst->task = src->task; + flecs_stats_copy_last(ECS_METRIC_FIRST(dst), ECS_METRIC_LAST(dst), + ECS_METRIC_FIRST(src), dst->query.t, t_next(src->query.t)); +} -#define ECS_GAUGE_APPEND(reply, s, field)\ - ECS_GAUGE_APPEND_T(reply, s, field, (s)->t) +#endif -#define ECS_COUNTER_APPEND(reply, s, field)\ - ECS_COUNTER_APPEND_T(reply, s, field, (s)->t) +#ifdef FLECS_PIPELINE -static -void flecs_world_stats_to_json( - ecs_strbuf_t *reply, - const EcsWorldStats *monitor_stats) +bool ecs_pipeline_stats_get( + ecs_world_t *stage, + ecs_entity_t pipeline, + ecs_pipeline_stats_t *s) { - const ecs_world_stats_t *stats = &monitor_stats->stats; + ecs_check(stage != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(s != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(pipeline != 0, ECS_INVALID_PARAMETER, NULL); - ecs_strbuf_list_push(reply, "{", ","); - ECS_GAUGE_APPEND(reply, stats, entity_count); - ECS_GAUGE_APPEND(reply, stats, entity_not_alive_count); - ECS_GAUGE_APPEND(reply, stats, id_count); - ECS_GAUGE_APPEND(reply, stats, tag_id_count); - ECS_GAUGE_APPEND(reply, stats, component_id_count); - ECS_GAUGE_APPEND(reply, stats, pair_id_count); - ECS_GAUGE_APPEND(reply, stats, wildcard_id_count); - ECS_GAUGE_APPEND(reply, stats, component_count); - ECS_COUNTER_APPEND(reply, stats, id_create_count); - ECS_COUNTER_APPEND(reply, stats, id_delete_count); - ECS_GAUGE_APPEND(reply, stats, table_count); - ECS_GAUGE_APPEND(reply, stats, empty_table_count); - ECS_GAUGE_APPEND(reply, stats, tag_table_count); - ECS_GAUGE_APPEND(reply, stats, trivial_table_count); - ECS_GAUGE_APPEND(reply, stats, table_record_count); - ECS_GAUGE_APPEND(reply, stats, table_storage_count); - ECS_COUNTER_APPEND(reply, stats, table_create_count); - ECS_COUNTER_APPEND(reply, stats, table_delete_count); - ECS_GAUGE_APPEND(reply, stats, query_count); - ECS_GAUGE_APPEND(reply, stats, observer_count); - ECS_GAUGE_APPEND(reply, stats, system_count); - ECS_COUNTER_APPEND(reply, stats, new_count); - ECS_COUNTER_APPEND(reply, stats, bulk_new_count); - ECS_COUNTER_APPEND(reply, stats, delete_count); - ECS_COUNTER_APPEND(reply, stats, clear_count); - ECS_COUNTER_APPEND(reply, stats, add_count); - ECS_COUNTER_APPEND(reply, stats, remove_count); - ECS_COUNTER_APPEND(reply, stats, set_count); - ECS_COUNTER_APPEND(reply, stats, discard_count); - ECS_COUNTER_APPEND(reply, stats, world_time_total_raw); - ECS_COUNTER_APPEND(reply, stats, world_time_total); - ECS_COUNTER_APPEND(reply, stats, frame_time_total); - ECS_COUNTER_APPEND(reply, stats, system_time_total); - ECS_COUNTER_APPEND(reply, stats, merge_time_total); - ECS_GAUGE_APPEND(reply, stats, fps); - ECS_GAUGE_APPEND(reply, stats, delta_time); - ECS_COUNTER_APPEND(reply, stats, frame_count_total); - ECS_COUNTER_APPEND(reply, stats, merge_count_total); - ECS_COUNTER_APPEND(reply, stats, pipeline_build_count_total); - ECS_COUNTER_APPEND(reply, stats, systems_ran_frame); - ECS_COUNTER_APPEND(reply, stats, alloc_count); - ECS_COUNTER_APPEND(reply, stats, realloc_count); - ECS_COUNTER_APPEND(reply, stats, free_count); - ECS_GAUGE_APPEND(reply, stats, outstanding_alloc_count); - ecs_strbuf_list_pop(reply, "}"); -} - -static -void flecs_system_stats_to_json( - ecs_world_t *world, - ecs_strbuf_t *reply, - ecs_entity_t system, - const ecs_system_stats_t *stats) -{ - ecs_strbuf_list_push(reply, "{", ","); + const ecs_world_t *world = ecs_get_world(stage); + const EcsPipeline *pq = ecs_get(world, pipeline, EcsPipeline); + if (!pq) { + return false; + } - char *path = ecs_get_fullpath(world, system); - ecs_strbuf_list_append(reply, "\"name\":\"%s\"", path); - ecs_os_free(path); + int32_t sys_count = 0, active_sys_count = 0; - if (!stats->task) { - ECS_GAUGE_APPEND(reply, &stats->query, matched_table_count); - ECS_GAUGE_APPEND(reply, &stats->query, matched_entity_count); + /* Count number of active systems */ + ecs_iter_t it = ecs_query_iter(stage, pq->query); + while (ecs_query_next(&it)) { + if (flecs_id_record_get_table(pq->idr_inactive, it.table) != NULL) { + continue; + } + active_sys_count += it.count; } - ECS_COUNTER_APPEND_T(reply, stats, time_spent, stats->query.t); - ecs_strbuf_list_pop(reply, "}"); -} + /* Count total number of systems in pipeline */ + it = ecs_query_iter(stage, pq->query); + while (ecs_query_next(&it)) { + sys_count += it.count; + } -static -void flecs_pipeline_stats_to_json( - ecs_world_t *world, - ecs_strbuf_t *reply, - const EcsPipelineStats *stats) -{ - ecs_strbuf_list_push(reply, "[", ","); + /* 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); - int32_t i, count = ecs_vector_count(stats->stats.systems); - ecs_entity_t *ids = ecs_vector_first(stats->stats.systems, ecs_entity_t); - for (i = 0; i < count; i ++) { - ecs_entity_t id = ids[i]; - - ecs_strbuf_list_next(reply); + if (!sys_count) { + return false; + } - if (id) { - ecs_system_stats_t *sys_stats = ecs_map_get( - &stats->stats.system_stats, ecs_system_stats_t, id); - flecs_system_stats_to_json(world, reply, id, sys_stats); - } else { - /* Sync point */ - ecs_strbuf_list_push(reply, "{", ","); - ecs_strbuf_list_pop(reply, "}"); - } + if (ecs_map_is_initialized(&s->system_stats) && !sys_count) { + ecs_map_fini(&s->system_stats); } + ecs_map_init_if(&s->system_stats, ecs_system_stats_t, sys_count); - ecs_strbuf_list_pop(reply, "]"); -} + /* 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); -static -bool flecs_rest_reply_stats( - ecs_world_t *world, - const ecs_http_request_t* req, - ecs_http_reply_t *reply) -{ - char *period_str = NULL; - flecs_rest_string_param(req, "period", &period_str); - char *category = &req->path[6]; + /* 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)) { + if (flecs_id_record_get_table(pq->idr_inactive, it.table) != NULL) { + continue; + } - ecs_entity_t period = EcsPeriod1s; - if (period_str) { - char *period_name = ecs_asprintf("Period%s", period_str); - period = ecs_lookup_child(world, ecs_id(FlecsMonitor), period_name); - ecs_os_free(period_name); - if (!period) { - flecs_reply_error(reply, "bad request (invalid period string)"); - reply->code = 400; - return false; + 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 */ + } + } } - } - - if (!ecs_os_strcmp(category, "world")) { - const EcsWorldStats *stats = ecs_get_pair(world, EcsWorld, - EcsWorldStats, period); - flecs_world_stats_to_json(&reply->body, stats); - return true; - - } else if (!ecs_os_strcmp(category, "pipeline")) { - const EcsPipelineStats *stats = ecs_get_pair(world, EcsWorld, - EcsPipelineStats, period); - flecs_pipeline_stats_to_json(world, &reply->body, stats); - return true; + systems[i_system ++] = 0; /* Last merge */ + ecs_assert(pip_count == i_system, ECS_INTERNAL_ERROR, NULL); } else { - flecs_reply_error(reply, "bad request (unsupported category)"); - reply->code = 400; - return false; + 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->query); + while (ecs_query_next(&it)) { + int i; + for (i = 0; i < it.count; i ++) { + ecs_system_stats_t *sys_stats = ecs_map_ensure( + &s->system_stats, ecs_system_stats_t, it.entities[i]); + sys_stats->query.t = s->t; + ecs_system_stats_get(world, it.entities[i], sys_stats); + } + } + + s->t = t_next(s->t); + return true; -} -#else -static -bool flecs_rest_reply_stats( - ecs_world_t *world, - const ecs_http_request_t* req, - ecs_http_reply_t *reply) -{ - (void)world; - (void)req; - (void)reply; +error: return false; } -#endif -static -void flecs_rest_reply_table_append_type( - ecs_world_t *world, - ecs_strbuf_t *reply, - const ecs_table_t *table) +void ecs_pipeline_stats_fini( + ecs_pipeline_stats_t *stats) { - ecs_strbuf_list_push(reply, "[", ","); - int32_t i, count = table->type.count; - ecs_id_t *ids = table->type.array; - for (i = 0; i < count; i ++) { - ecs_strbuf_list_next(reply); - ecs_strbuf_appendch(reply, '"'); - ecs_id_str_buf(world, ids[i], reply); - ecs_strbuf_appendch(reply, '"'); - } - ecs_strbuf_list_pop(reply, "]"); + ecs_map_fini(&stats->system_stats); + ecs_vector_free(stats->systems); } -static -void flecs_rest_reply_table_append_memory( - ecs_strbuf_t *reply, - const ecs_table_t *table) +void ecs_pipeline_stats_reduce( + ecs_pipeline_stats_t *dst, + const ecs_pipeline_stats_t *src) { - int32_t used = 0, allocated = 0; - - used += table->data.entities.count * ECS_SIZEOF(ecs_entity_t); - used += table->data.records.count * ECS_SIZEOF(ecs_record_t*); - allocated += table->data.entities.size * ECS_SIZEOF(ecs_entity_t); - allocated += table->data.records.size * ECS_SIZEOF(ecs_record_t*); + int32_t system_count = ecs_vector_count(src->systems); + ecs_vector_set_count(&dst->systems, ecs_entity_t, system_count); + ecs_entity_t *dst_systems = ecs_vector_first(dst->systems, ecs_entity_t); + ecs_entity_t *src_systems = ecs_vector_first(src->systems, ecs_entity_t); + ecs_os_memcpy_n(dst_systems, src_systems, ecs_entity_t, system_count); - int32_t i, storage_count = table->storage_count; - ecs_type_info_t **ti = table->type_info; - ecs_column_t *storages = table->data.columns; + ecs_map_init_if(&dst->system_stats, ecs_system_stats_t, + ecs_map_count(&src->system_stats)); - for (i = 0; i < storage_count; i ++) { - used += storages[i].count * ti[i]->size; - allocated += storages[i].size * ti[i]->size; + ecs_map_iter_t it = ecs_map_iter(&src->system_stats); + ecs_system_stats_t *sys_src, *sys_dst; + ecs_entity_t key; + while ((sys_src = ecs_map_next(&it, ecs_system_stats_t, &key))) { + sys_dst = ecs_map_ensure(&dst->system_stats, ecs_system_stats_t, key); + sys_dst->query.t = dst->t; + ecs_system_stats_reduce(sys_dst, sys_src); } - - ecs_strbuf_list_push(reply, "{", ","); - ecs_strbuf_list_append(reply, "\"used\":%d", used); - ecs_strbuf_list_append(reply, "\"allocated\":%d", allocated); - ecs_strbuf_list_pop(reply, "}"); + dst->t = t_next(dst->t); } -static -void flecs_rest_reply_table_append( - ecs_world_t *world, - ecs_strbuf_t *reply, - const ecs_table_t *table) +void ecs_pipeline_stats_reduce_last( + ecs_pipeline_stats_t *dst, + const ecs_pipeline_stats_t *src, + int32_t count) { - ecs_strbuf_list_next(reply); - ecs_strbuf_list_push(reply, "{", ","); - ecs_strbuf_list_append(reply, "\"id\":%u", (uint32_t)table->id); - ecs_strbuf_list_appendstr(reply, "\"type\":"); - flecs_rest_reply_table_append_type(world, reply, table); - ecs_strbuf_list_append(reply, "\"count\":%d", ecs_table_count(table)); - ecs_strbuf_list_append(reply, "\"memory\":"); - flecs_rest_reply_table_append_memory(reply, table); - ecs_strbuf_list_append(reply, "\"refcount\":%d", table->refcount); - ecs_strbuf_list_pop(reply, "}"); + ecs_map_init_if(&dst->system_stats, ecs_system_stats_t, + ecs_map_count(&src->system_stats)); + + ecs_map_iter_t it = ecs_map_iter(&src->system_stats); + ecs_system_stats_t *sys_src, *sys_dst; + ecs_entity_t key; + while ((sys_src = ecs_map_next(&it, ecs_system_stats_t, &key))) { + sys_dst = ecs_map_ensure(&dst->system_stats, ecs_system_stats_t, key); + sys_dst->query.t = dst->t; + ecs_system_stats_reduce_last(sys_dst, sys_src, count); + } + dst->t = t_prev(dst->t); } -static -bool flecs_rest_reply_tables( - ecs_world_t *world, - const ecs_http_request_t* req, - ecs_http_reply_t *reply) +void ecs_pipeline_stats_repeat_last( + ecs_pipeline_stats_t *stats) { - (void)req; - - ecs_strbuf_list_push(&reply->body, "[", ","); - 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_rest_reply_table_append(world, &reply->body, table); + ecs_map_iter_t it = ecs_map_iter(&stats->system_stats); + ecs_system_stats_t *sys; + ecs_entity_t key; + while ((sys = ecs_map_next(&it, ecs_system_stats_t, &key))) { + sys->query.t = stats->t; + ecs_system_stats_repeat_last(sys); } - ecs_strbuf_list_pop(&reply->body, "]"); - - return true; + stats->t = t_next(stats->t); } -static -void flecs_rest_reply_id_append( - ecs_world_t *world, - ecs_strbuf_t *reply, - const ecs_id_record_t *idr) +void ecs_pipeline_stats_copy_last( + ecs_pipeline_stats_t *dst, + const ecs_pipeline_stats_t *src) { - ecs_strbuf_list_next(reply); - ecs_strbuf_list_push(reply, "{", ","); - ecs_strbuf_list_appendstr(reply, "\"id\":\""); - ecs_id_str_buf(world, idr->id, reply); - ecs_strbuf_appendch(reply, '"'); - - if (idr->type_info) { - if (idr->type_info->component != idr->id) { - ecs_strbuf_list_appendstr(reply, "\"component\":\""); - ecs_id_str_buf(world, idr->type_info->component, reply); - ecs_strbuf_appendch(reply, '"'); - } + ecs_map_init_if(&dst->system_stats, ecs_system_stats_t, + ecs_map_count(&src->system_stats)); - ecs_strbuf_list_append(reply, "\"size\":%d", - idr->type_info->size); - ecs_strbuf_list_append(reply, "\"alignment\":%d", - idr->type_info->alignment); + ecs_map_iter_t it = ecs_map_iter(&src->system_stats); + ecs_system_stats_t *sys_src, *sys_dst; + ecs_entity_t key; + while ((sys_src = ecs_map_next(&it, ecs_system_stats_t, &key))) { + sys_dst = ecs_map_ensure(&dst->system_stats, ecs_system_stats_t, key); + sys_dst->query.t = dst->t; + ecs_system_stats_copy_last(sys_dst, sys_src); } - - ecs_strbuf_list_append(reply, "\"table_count\":%d", - idr->cache.tables.count); - ecs_strbuf_list_append(reply, "\"empty_table_count\":%d", - idr->cache.empty_tables.count); - - ecs_strbuf_list_pop(reply, "}"); } -static -bool flecs_rest_reply_ids( - ecs_world_t *world, - const ecs_http_request_t* req, - ecs_http_reply_t *reply) +#endif + +void ecs_world_stats_log( + const ecs_world_t *world, + const ecs_world_stats_t *s) { - (void)req; + int32_t t = s->t; - ecs_strbuf_list_push(&reply->body, "[", ","); - ecs_map_iter_t it = ecs_map_iter(&world->id_index); - ecs_id_record_t *idr; - while ((idr = ecs_map_next_ptr(&it, ecs_id_record_t*, NULL))) { - flecs_rest_reply_id_append(world, &reply->body, idr); - } - ecs_strbuf_list_pop(&reply->body, "]"); + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(s != NULL, ECS_INVALID_PARAMETER, NULL); - return true; + world = ecs_get_world(world); + + flecs_counter_print("Frame", t, &s->frame_count_total); + ecs_trace("-------------------------------------"); + flecs_counter_print("pipeline rebuilds", t, &s->pipeline_build_count_total); + flecs_counter_print("systems invocations", t, &s->systems_ran_frame); + ecs_trace(""); + flecs_metric_print("target FPS", world->info.target_fps); + flecs_metric_print("time scale", world->info.time_scale); + ecs_trace(""); + flecs_gauge_print("actual FPS", t, &s->fps); + flecs_counter_print("frame time", t, &s->frame_time_total); + flecs_counter_print("system time", t, &s->system_time_total); + flecs_counter_print("merge time", t, &s->merge_time_total); + flecs_counter_print("simulation time elapsed", t, &s->world_time_total); + ecs_trace(""); + flecs_gauge_print("id count", t, &s->id_count); + flecs_gauge_print("tag id count", t, &s->tag_id_count); + flecs_gauge_print("component id count", t, &s->component_id_count); + flecs_gauge_print("pair id count", t, &s->pair_id_count); + flecs_gauge_print("wildcard id count", t, &s->wildcard_id_count); + flecs_gauge_print("component count", t, &s->component_count); + ecs_trace(""); + flecs_gauge_print("alive entity count", t, &s->entity_count); + flecs_gauge_print("not alive entity count", t, &s->entity_not_alive_count); + ecs_trace(""); + flecs_gauge_print("query count", t, &s->query_count); + flecs_gauge_print("observer count", t, &s->observer_count); + flecs_gauge_print("system count", t, &s->system_count); + ecs_trace(""); + flecs_gauge_print("table count", t, &s->table_count); + flecs_gauge_print("empty table count", t, &s->empty_table_count); + flecs_gauge_print("tag table count", t, &s->tag_table_count); + flecs_gauge_print("trivial table count", t, &s->trivial_table_count); + flecs_gauge_print("table storage count", t, &s->table_storage_count); + flecs_gauge_print("table cache record count", t, &s->table_record_count); + ecs_trace(""); + flecs_counter_print("table create count", t, &s->table_create_count); + flecs_counter_print("table delete count", t, &s->table_delete_count); + flecs_counter_print("id create count", t, &s->id_create_count); + flecs_counter_print("id delete count", t, &s->id_delete_count); + ecs_trace(""); + flecs_counter_print("deferred new operations", t, &s->new_count); + flecs_counter_print("deferred bulk_new operations", t, &s->bulk_new_count); + flecs_counter_print("deferred delete operations", t, &s->delete_count); + flecs_counter_print("deferred clear operations", t, &s->clear_count); + flecs_counter_print("deferred add operations", t, &s->add_count); + flecs_counter_print("deferred remove operations", t, &s->remove_count); + flecs_counter_print("deferred set operations", t, &s->set_count); + flecs_counter_print("discarded operations", t, &s->discard_count); + ecs_trace(""); + +error: + return; } -static -bool flecs_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; +#endif - if (req->path == NULL) { - ecs_dbg("rest: bad request (missing path)"); - flecs_reply_error(reply, "bad request (missing path)"); - reply->code = 400; - return false; - } - ecs_strbuf_appendstr(&reply->headers, "Access-Control-Allow-Origin: *\r\n"); - if (req->method == EcsHttpGet) { - /* Entity endpoint */ - if (!ecs_os_strncmp(req->path, "entity/", 7)) { - return flecs_rest_reply_entity(world, req, reply); - - /* Query endpoint */ - } else if (!ecs_os_strcmp(req->path, "query")) { - return flecs_rest_reply_query(world, req, reply); +#ifdef FLECS_UNITS - /* Stats endpoint */ - } else if (!ecs_os_strncmp(req->path, "stats/", 6)) { - return flecs_rest_reply_stats(world, req, reply); +ECS_DECLARE(EcsUnitPrefixes); - /* Tables endpoint */ - } else if (!ecs_os_strncmp(req->path, "tables", 6)) { - return flecs_rest_reply_tables(world, req, reply); +ECS_DECLARE(EcsYocto); +ECS_DECLARE(EcsZepto); +ECS_DECLARE(EcsAtto); +ECS_DECLARE(EcsFemto); +ECS_DECLARE(EcsPico); +ECS_DECLARE(EcsNano); +ECS_DECLARE(EcsMicro); +ECS_DECLARE(EcsMilli); +ECS_DECLARE(EcsCenti); +ECS_DECLARE(EcsDeci); +ECS_DECLARE(EcsDeca); +ECS_DECLARE(EcsHecto); +ECS_DECLARE(EcsKilo); +ECS_DECLARE(EcsMega); +ECS_DECLARE(EcsGiga); +ECS_DECLARE(EcsTera); +ECS_DECLARE(EcsPeta); +ECS_DECLARE(EcsExa); +ECS_DECLARE(EcsZetta); +ECS_DECLARE(EcsYotta); - /* Ids endpoint */ - } else if (!ecs_os_strncmp(req->path, "ids", 3)) { - return flecs_rest_reply_ids(world, req, reply); - } - } else if (req->method == EcsHttpOptions) { - return true; - } +ECS_DECLARE(EcsKibi); +ECS_DECLARE(EcsMebi); +ECS_DECLARE(EcsGibi); +ECS_DECLARE(EcsTebi); +ECS_DECLARE(EcsPebi); +ECS_DECLARE(EcsExbi); +ECS_DECLARE(EcsZebi); +ECS_DECLARE(EcsYobi); - return false; -} +ECS_DECLARE(EcsDuration); + ECS_DECLARE(EcsPicoSeconds); + ECS_DECLARE(EcsNanoSeconds); + ECS_DECLARE(EcsMicroSeconds); + ECS_DECLARE(EcsMilliSeconds); + ECS_DECLARE(EcsSeconds); + ECS_DECLARE(EcsMinutes); + ECS_DECLARE(EcsHours); + ECS_DECLARE(EcsDays); -static -void flecs_on_set_rest(ecs_iter_t *it) -{ - EcsRest *rest = it->ptrs[0]; - - int i; - for(i = 0; i < it->count; i ++) { - if (!rest[i].port) { - rest[i].port = ECS_REST_DEFAULT_PORT; - } +ECS_DECLARE(EcsTime); + ECS_DECLARE(EcsDate); - 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 = flecs_rest_reply, - .ctx = srv_ctx - }); +ECS_DECLARE(EcsMass); + ECS_DECLARE(EcsGrams); + ECS_DECLARE(EcsKiloGrams); - 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; - } +ECS_DECLARE(EcsElectricCurrent); + ECS_DECLARE(EcsAmpere); - srv_ctx->world = it->world; - srv_ctx->entity = it->entities[i]; - srv_ctx->srv = srv; - srv_ctx->rc = 1; +ECS_DECLARE(EcsAmount); + ECS_DECLARE(EcsMole); - rest[i].impl = srv_ctx; +ECS_DECLARE(EcsLuminousIntensity); + ECS_DECLARE(EcsCandela); - ecs_http_server_start(srv_ctx->srv); - } -} +ECS_DECLARE(EcsForce); + ECS_DECLARE(EcsNewton); -static -void DequeueRest(ecs_iter_t *it) { - EcsRest *rest = ecs_field(it, EcsRest, 1); +ECS_DECLARE(EcsLength); + ECS_DECLARE(EcsMeters); + ECS_DECLARE(EcsPicoMeters); + ECS_DECLARE(EcsNanoMeters); + ECS_DECLARE(EcsMicroMeters); + ECS_DECLARE(EcsMilliMeters); + ECS_DECLARE(EcsCentiMeters); + ECS_DECLARE(EcsKiloMeters); + ECS_DECLARE(EcsMiles); - if (it->delta_system_time > (ecs_ftime_t)1.0) { - ecs_warn( - "detected large progress interval (%.2fs), REST request may timeout", - (double)it->delta_system_time); - } +ECS_DECLARE(EcsPressure); + ECS_DECLARE(EcsPascal); + ECS_DECLARE(EcsBar); - 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); - } - } -} +ECS_DECLARE(EcsSpeed); + ECS_DECLARE(EcsMetersPerSecond); + ECS_DECLARE(EcsKiloMetersPerSecond); + ECS_DECLARE(EcsKiloMetersPerHour); + ECS_DECLARE(EcsMilesPerHour); -void FlecsRestImport( - ecs_world_t *world) -{ - ECS_MODULE(world, FlecsRest); +ECS_DECLARE(EcsAcceleration); - ecs_set_name_prefix(world, "Ecs"); +ECS_DECLARE(EcsTemperature); + ECS_DECLARE(EcsKelvin); + ECS_DECLARE(EcsCelsius); + ECS_DECLARE(EcsFahrenheit); - flecs_bootstrap_component(world, EcsRest); +ECS_DECLARE(EcsData); + ECS_DECLARE(EcsBits); + ECS_DECLARE(EcsKiloBits); + ECS_DECLARE(EcsMegaBits); + ECS_DECLARE(EcsGigaBits); + ECS_DECLARE(EcsBytes); + ECS_DECLARE(EcsKiloBytes); + ECS_DECLARE(EcsMegaBytes); + ECS_DECLARE(EcsGigaBytes); + ECS_DECLARE(EcsKibiBytes); + ECS_DECLARE(EcsGibiBytes); + ECS_DECLARE(EcsMebiBytes); - ecs_set_hooks(world, EcsRest, { - .ctor = ecs_default_ctor, - .move = ecs_move(EcsRest), - .copy = ecs_copy(EcsRest), - .dtor = ecs_dtor(EcsRest), - .on_set = flecs_on_set_rest - }); +ECS_DECLARE(EcsDataRate); + ECS_DECLARE(EcsBitsPerSecond); + ECS_DECLARE(EcsKiloBitsPerSecond); + ECS_DECLARE(EcsMegaBitsPerSecond); + ECS_DECLARE(EcsGigaBitsPerSecond); + ECS_DECLARE(EcsBytesPerSecond); + ECS_DECLARE(EcsKiloBytesPerSecond); + ECS_DECLARE(EcsMegaBytesPerSecond); + ECS_DECLARE(EcsGigaBytesPerSecond); - ECS_SYSTEM(world, DequeueRest, EcsPostFrame, EcsRest); -} +ECS_DECLARE(EcsPercentage); -#endif +ECS_DECLARE(EcsAngle); + ECS_DECLARE(EcsRadians); + ECS_DECLARE(EcsDegrees); -#ifndef FLECS_META_PRIVATE_H -#define FLECS_META_PRIVATE_H +ECS_DECLARE(EcsBel); +ECS_DECLARE(EcsDeciBel); +void FlecsUnitsImport( + ecs_world_t *world) +{ + ECS_MODULE(world, FlecsUnits); -#ifdef FLECS_META + ecs_set_name_prefix(world, "Ecs"); -void ecs_meta_type_serialized_init( - ecs_iter_t *it); + EcsUnitPrefixes = ecs_entity_init(world, &(ecs_entity_desc_t){ + .name = "prefixes", + .add = { EcsModule } + }); -void ecs_meta_dtor_serialized( - EcsMetaTypeSerialized *ptr); + /* Initialize unit prefixes */ + ecs_entity_t prev_scope = ecs_set_scope(world, EcsUnitPrefixes); -bool flecs_unit_validate( - ecs_world_t *world, - ecs_entity_t t, - EcsUnit *data); + EcsYocto = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ + .entity = ecs_entity(world, { .name = "Yocto" }), + .symbol = "y", + .translation = { .factor = 10, .power = -24 } + }); + EcsZepto = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ + .entity = ecs_entity(world, { .name = "Zepto" }), + .symbol = "z", + .translation = { .factor = 10, .power = -21 } + }); + EcsAtto = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ + .entity = ecs_entity(world, { .name = "Atto" }), + .symbol = "a", + .translation = { .factor = 10, .power = -18 } + }); + EcsFemto = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ + .entity = ecs_entity(world, { .name = "Femto" }), + .symbol = "a", + .translation = { .factor = 10, .power = -15 } + }); + EcsPico = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ + .entity = ecs_entity(world, { .name = "Pico" }), + .symbol = "p", + .translation = { .factor = 10, .power = -12 } + }); + EcsNano = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ + .entity = ecs_entity(world, { .name = "Nano" }), + .symbol = "n", + .translation = { .factor = 10, .power = -9 } + }); + EcsMicro = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ + .entity = ecs_entity(world, { .name = "Micro" }), + .symbol = "μ", + .translation = { .factor = 10, .power = -6 } + }); + EcsMilli = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ + .entity = ecs_entity(world, { .name = "Milli" }), + .symbol = "m", + .translation = { .factor = 10, .power = -3 } + }); + EcsCenti = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ + .entity = ecs_entity(world, { .name = "Centi" }), + .symbol = "c", + .translation = { .factor = 10, .power = -2 } + }); + EcsDeci = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ + .entity = ecs_entity(world, { .name = "Deci" }), + .symbol = "d", + .translation = { .factor = 10, .power = -1 } + }); + EcsDeca = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ + .entity = ecs_entity(world, { .name = "Deca" }), + .symbol = "da", + .translation = { .factor = 10, .power = 1 } + }); + EcsHecto = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ + .entity = ecs_entity(world, { .name = "Hecto" }), + .symbol = "h", + .translation = { .factor = 10, .power = 2 } + }); + EcsKilo = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ + .entity = ecs_entity(world, { .name = "Kilo" }), + .symbol = "k", + .translation = { .factor = 10, .power = 3 } + }); + EcsMega = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ + .entity = ecs_entity(world, { .name = "Mega" }), + .symbol = "M", + .translation = { .factor = 10, .power = 6 } + }); + EcsGiga = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ + .entity = ecs_entity(world, { .name = "Giga" }), + .symbol = "G", + .translation = { .factor = 10, .power = 9 } + }); + EcsTera = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ + .entity = ecs_entity(world, { .name = "Tera" }), + .symbol = "T", + .translation = { .factor = 10, .power = 12 } + }); + EcsPeta = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ + .entity = ecs_entity(world, { .name = "Peta" }), + .symbol = "P", + .translation = { .factor = 10, .power = 15 } + }); + EcsExa = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ + .entity = ecs_entity(world, { .name = "Exa" }), + .symbol = "E", + .translation = { .factor = 10, .power = 18 } + }); + EcsZetta = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ + .entity = ecs_entity(world, { .name = "Zetta" }), + .symbol = "Z", + .translation = { .factor = 10, .power = 21 } + }); + EcsYotta = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ + .entity = ecs_entity(world, { .name = "Yotta" }), + .symbol = "Y", + .translation = { .factor = 10, .power = 24 } + }); -#endif - -#endif + EcsKibi = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ + .entity = ecs_entity(world, { .name = "Kibi" }), + .symbol = "Ki", + .translation = { .factor = 1024, .power = 1 } + }); + EcsMebi = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ + .entity = ecs_entity(world, { .name = "Mebi" }), + .symbol = "Mi", + .translation = { .factor = 1024, .power = 2 } + }); + EcsGibi = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ + .entity = ecs_entity(world, { .name = "Gibi" }), + .symbol = "Gi", + .translation = { .factor = 1024, .power = 3 } + }); + EcsTebi = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ + .entity = ecs_entity(world, { .name = "Tebi" }), + .symbol = "Ti", + .translation = { .factor = 1024, .power = 4 } + }); + EcsPebi = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ + .entity = ecs_entity(world, { .name = "Pebi" }), + .symbol = "Pi", + .translation = { .factor = 1024, .power = 5 } + }); + EcsExbi = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ + .entity = ecs_entity(world, { .name = "Exbi" }), + .symbol = "Ei", + .translation = { .factor = 1024, .power = 6 } + }); + EcsZebi = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ + .entity = ecs_entity(world, { .name = "Zebi" }), + .symbol = "Zi", + .translation = { .factor = 1024, .power = 7 } + }); + EcsYobi = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ + .entity = ecs_entity(world, { .name = "Yobi" }), + .symbol = "Yi", + .translation = { .factor = 1024, .power = 8 } + }); + ecs_set_scope(world, prev_scope); -#ifdef FLECS_META + /* Duration units */ -static -const char* flecs_meta_op_kind_str( - ecs_meta_type_op_kind_t kind) -{ - switch(kind) { + EcsDuration = ecs_quantity_init(world, &(ecs_entity_desc_t){ + .name = "Duration" }); + prev_scope = ecs_set_scope(world, EcsDuration); - 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 >>"; - } -} + EcsSeconds = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "Seconds" }), + .quantity = EcsDuration, + .symbol = "s" }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsSeconds, + .kind = EcsF32 + }); + EcsPicoSeconds = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "PicoSeconds" }), + .quantity = EcsDuration, + .base = EcsSeconds, + .prefix = EcsPico }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsPicoSeconds, + .kind = EcsF32 + }); -/* Get current scope */ -static -ecs_meta_scope_t* get_scope( - const ecs_meta_cursor_t *cursor) -{ - ecs_check(cursor != NULL, ECS_INVALID_PARAMETER, NULL); - return (ecs_meta_scope_t*)&cursor->scope[cursor->depth]; -error: - return NULL; -} -/* 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; -} + EcsNanoSeconds = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "NanoSeconds" }), + .quantity = EcsDuration, + .base = EcsSeconds, + .prefix = EcsNano }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsNanoSeconds, + .kind = EcsF32 + }); -/* Get current operation for scope */ -static -ecs_meta_type_op_t* get_op( - ecs_meta_scope_t *scope) -{ - return &scope->ops[scope->op_cur]; -} + EcsMicroSeconds = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "MicroSeconds" }), + .quantity = EcsDuration, + .base = EcsSeconds, + .prefix = EcsMicro }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsMicroSeconds, + .kind = EcsF32 + }); -/* 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; -} + EcsMilliSeconds = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "MilliSeconds" }), + .quantity = EcsDuration, + .base = EcsSeconds, + .prefix = EcsMilli }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsMilliSeconds, + .kind = EcsF32 + }); -/* 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; -} + EcsMinutes = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "Minutes" }), + .quantity = EcsDuration, + .base = EcsSeconds, + .symbol = "min", + .translation = { .factor = 60, .power = 1 } }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsMinutes, + .kind = EcsU32 + }); -/* 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; -} + EcsHours = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "Hours" }), + .quantity = EcsDuration, + .base = EcsMinutes, + .symbol = "h", + .translation = { .factor = 60, .power = 1 } }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsHours, + .kind = EcsU32 + }); -static -int32_t get_elem_count( - ecs_meta_scope_t *scope) -{ - if (scope->vector) { - return ecs_vector_count(*(scope->vector)); - } + EcsDays = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "Days" }), + .quantity = EcsDuration, + .base = EcsHours, + .symbol = "d", + .translation = { .factor = 24, .power = 1 } }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsDays, + .kind = EcsU32 + }); + ecs_set_scope(world, prev_scope); - ecs_meta_type_op_t *op = get_op(scope); - return op->count; -} + /* Time units */ -/* 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); + EcsTime = ecs_quantity_init(world, &(ecs_entity_desc_t){ + .name = "Time" }); + prev_scope = ecs_set_scope(world, EcsTime); - 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); - } + EcsDate = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "Date" }), + .quantity = EcsTime }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsDate, + .kind = EcsU32 + }); + ecs_set_scope(world, prev_scope); - return ECS_OFFSET(scope->ptr, size * scope->elem_cur + op->offset); -} + /* Mass units */ -static -int push_type( - const ecs_world_t *world, - ecs_meta_scope_t *scope, - ecs_entity_t type, - void *ptr) -{ - 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 - }; + EcsMass = ecs_quantity_init(world, &(ecs_entity_desc_t){ + .name = "Mass" }); + prev_scope = ecs_set_scope(world, EcsMass); + EcsGrams = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "Grams" }), + .quantity = EcsMass, + .symbol = "g" }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsGrams, + .kind = EcsF32 + }); + EcsKiloGrams = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "KiloGrams" }), + .quantity = EcsMass, + .prefix = EcsKilo, + .base = EcsGrams }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsKiloGrams, + .kind = EcsF32 + }); + ecs_set_scope(world, prev_scope); - return 0; -} + /* Electric current units */ -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); + EcsElectricCurrent = ecs_quantity_init(world, &(ecs_entity_desc_t){ + .name = "ElectricCurrent" }); + prev_scope = ecs_set_scope(world, EcsElectricCurrent); + EcsAmpere = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "Ampere" }), + .quantity = EcsElectricCurrent, + .symbol = "A" }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsAmpere, + .kind = EcsF32 + }); + ecs_set_scope(world, prev_scope); - ecs_meta_cursor_t result = { - .world = world, - .valid = true - }; + /* Amount of substance units */ - if (push_type(world, result.scope, type, ptr) != 0) { - result.valid = false; - } + EcsAmount = ecs_quantity_init(world, &(ecs_entity_desc_t){ + .name = "Amount" }); + prev_scope = ecs_set_scope(world, EcsAmount); + EcsMole = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "Mole" }), + .quantity = EcsAmount, + .symbol = "mol" }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsMole, + .kind = EcsF32 + }); + ecs_set_scope(world, prev_scope); - return result; -error: - return (ecs_meta_cursor_t){ 0 }; -} + /* Luminous intensity units */ -void* ecs_meta_get_ptr( - ecs_meta_cursor_t *cursor) -{ - return get_ptr(cursor->world, get_scope(cursor)); -} + EcsLuminousIntensity = ecs_quantity_init(world, &(ecs_entity_desc_t){ + .name = "LuminousIntensity" }); + prev_scope = ecs_set_scope(world, EcsLuminousIntensity); + EcsCandela = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "Candela" }), + .quantity = EcsLuminousIntensity, + .symbol = "cd" }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsCandela, + .kind = EcsF32 + }); + ecs_set_scope(world, prev_scope); -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); + /* Force units */ - 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; - } + EcsForce = ecs_quantity_init(world, &(ecs_entity_desc_t){ + .name = "Force" }); + prev_scope = ecs_set_scope(world, EcsForce); + EcsNewton = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "Newton" }), + .quantity = EcsForce, + .symbol = "N" }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsNewton, + .kind = EcsF32 + }); + ecs_set_scope(world, prev_scope); - return 0; - } + /* Length units */ - scope->op_cur += op->op_count; - if (scope->op_cur >= scope->op_count) { - ecs_err("out of bounds"); - return -1; - } + EcsLength = ecs_quantity_init(world, &(ecs_entity_desc_t){ + .name = "Length" }); + prev_scope = ecs_set_scope(world, EcsLength); + EcsMeters = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "Meters" }), + .quantity = EcsLength, + .symbol = "m" }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsMeters, + .kind = EcsF32 + }); - return 0; -} + EcsPicoMeters = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "PicoMeters" }), + .quantity = EcsLength, + .base = EcsMeters, + .prefix = EcsPico }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsPicoMeters, + .kind = EcsF32 + }); -int ecs_meta_elem( - ecs_meta_cursor_t *cursor, - int32_t elem) -{ - ecs_meta_scope_t *scope = get_scope(cursor); - if (!scope->is_collection) { - ecs_err("ecs_meta_elem can be used for collections only"); - return -1; - } + EcsNanoMeters = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "NanoMeters" }), + .quantity = EcsLength, + .base = EcsMeters, + .prefix = EcsNano }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsNanoMeters, + .kind = EcsF32 + }); - scope->elem_cur = elem; - scope->op_cur = 0; + EcsMicroMeters = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "MicroMeters" }), + .quantity = EcsLength, + .base = EcsMeters, + .prefix = EcsMicro }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsMicroMeters, + .kind = EcsF32 + }); - if (scope->elem_cur >= get_elem_count(scope) || (scope->elem_cur < 0)) { - ecs_err("out of collection bounds (%d)", scope->elem_cur); - return -1; - } - - return 0; -} + EcsMilliMeters = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "MilliMeters" }), + .quantity = EcsLength, + .base = EcsMeters, + .prefix = EcsMilli }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsMilliMeters, + .kind = EcsF32 + }); -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; - } + EcsCentiMeters = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "CentiMeters" }), + .quantity = EcsLength, + .base = EcsMeters, + .prefix = EcsCenti }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsCentiMeters, + .kind = EcsF32 + }); - 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; + EcsKiloMeters = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "KiloMeters" }), + .quantity = EcsLength, + .base = EcsMeters, + .prefix = EcsKilo }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsKiloMeters, + .kind = EcsF32 + }); + + EcsMiles = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "Miles" }), + .quantity = EcsLength, + .symbol = "mi" + }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsMiles, + .kind = EcsF32 + }); + ecs_set_scope(world, prev_scope); - ecs_assert(push_op->kind == EcsOpPush, ECS_INTERNAL_ERROR, NULL); + /* Pressure units */ - if (!push_op->members) { - ecs_err("cannot move to member '%s' for non-struct type", name); - return -1; - } + EcsPressure = ecs_quantity_init(world, &(ecs_entity_desc_t){ + .name = "Pressure" }); + prev_scope = ecs_set_scope(world, EcsPressure); + EcsPascal = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "Pascal" }), + .quantity = EcsPressure, + .symbol = "Pa" }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsPascal, + .kind = EcsF32 + }); + EcsBar = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "Bar" }), + .quantity = EcsPressure, + .symbol = "bar" }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsBar, + .kind = EcsF32 + }); + ecs_set_scope(world, prev_scope); - const uint64_t *cur_ptr = flecs_name_index_find_ptr(push_op->members, name, 0, 0); - if (!cur_ptr) { - char *path = ecs_get_fullpath(world, scope->type); - ecs_err("unknown member '%s' for type '%s'", name, path); - ecs_os_free(path); - return -1; - } + /* Speed units */ - scope->op_cur = flecs_uto(int32_t, cur_ptr[0]); + EcsSpeed = ecs_quantity_init(world, &(ecs_entity_desc_t){ + .name = "Speed" }); + prev_scope = ecs_set_scope(world, EcsSpeed); + EcsMetersPerSecond = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "MetersPerSecond" }), + .quantity = EcsSpeed, + .base = EcsMeters, + .over = EcsSeconds }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsMetersPerSecond, + .kind = EcsF32 + }); + EcsKiloMetersPerSecond = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "KiloMetersPerSecond" }), + .quantity = EcsSpeed, + .base = EcsKiloMeters, + .over = EcsSeconds }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsKiloMetersPerSecond, + .kind = EcsF32 + }); + EcsKiloMetersPerHour = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "KiloMetersPerHour" }), + .quantity = EcsSpeed, + .base = EcsKiloMeters, + .over = EcsHours }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsKiloMetersPerHour, + .kind = EcsF32 + }); + EcsMilesPerHour = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "MilesPerHour" }), + .quantity = EcsSpeed, + .base = EcsMiles, + .over = EcsHours }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsMilesPerHour, + .kind = EcsF32 + }); + ecs_set_scope(world, prev_scope); + + /* Acceleration */ - return 0; -} + EcsAcceleration = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "Acceleration" }), + .base = EcsMetersPerSecond, + .over = EcsSeconds }); + ecs_quantity_init(world, &(ecs_entity_desc_t){ + .id = EcsAcceleration + }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsAcceleration, + .kind = EcsF32 + }); -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; + /* Temperature units */ - if (cursor->depth == 0) { - if (!cursor->is_primitive_scope) { - if (op->kind > EcsOpScope) { - cursor->is_primitive_scope = true; - return 0; - } - } - } + EcsTemperature = ecs_quantity_init(world, &(ecs_entity_desc_t){ + .name = "Temperature" }); + prev_scope = ecs_set_scope(world, EcsTemperature); + EcsKelvin = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "Kelvin" }), + .quantity = EcsTemperature, + .symbol = "K" }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsKelvin, + .kind = EcsF32 + }); + EcsCelsius = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "Celsius" }), + .quantity = EcsTemperature, + .symbol = "°C" }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsCelsius, + .kind = EcsF32 + }); + EcsFahrenheit = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "Fahrenheit" }), + .quantity = EcsTemperature, + .symbol = "F" }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsFahrenheit, + .kind = EcsF32 + }); + ecs_set_scope(world, prev_scope); - void *ptr = get_ptr(world, scope); - cursor->depth ++; - ecs_check(cursor->depth < ECS_META_MAX_SCOPE_DEPTH, - ECS_INVALID_PARAMETER, NULL); + /* Data units */ - ecs_meta_scope_t *next_scope = get_scope(cursor); + EcsData = ecs_quantity_init(world, &(ecs_entity_desc_t){ + .name = "Data" }); + prev_scope = ecs_set_scope(world, EcsData); - /* 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 - }; + EcsBits = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "Bits" }), + .quantity = EcsData, + .symbol = "bit" }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsBits, + .kind = EcsU64 + }); - /* With 'is_inline_array' set to true we ensure that we can never push - * the same inline array twice */ + EcsKiloBits = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "KiloBits" }), + .quantity = EcsData, + .base = EcsBits, + .prefix = EcsKilo }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsKiloBits, + .kind = EcsU64 + }); - return 0; - } + EcsMegaBits = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "MegaBits" }), + .quantity = EcsData, + .base = EcsBits, + .prefix = EcsMega }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsMegaBits, + .kind = EcsU64 + }); - 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; + EcsGigaBits = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "GigaBits" }), + .quantity = EcsData, + .base = EcsBits, + .prefix = EcsGiga }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsGigaBits, + .kind = EcsU64 + }); - case EcsOpArray: { - if (push_type(world, next_scope, op->type, ptr) != 0) { - goto error; - } + EcsBytes = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "Bytes" }), + .quantity = EcsData, + .symbol = "B", + .base = EcsBits, + .translation = { .factor = 8, .power = 1 } }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsBytes, + .kind = EcsU64 + }); - const EcsArray *type_ptr = ecs_get(world, op->type, EcsArray); - next_scope->type = type_ptr->type; - next_scope->is_collection = true; - break; - } + EcsKiloBytes = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "KiloBytes" }), + .quantity = EcsData, + .base = EcsBytes, + .prefix = EcsKilo }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsKiloBytes, + .kind = EcsU64 + }); - case EcsOpVector: - next_scope->vector = ptr; - if (push_type(world, next_scope, op->type, NULL) != 0) { - goto error; - } + EcsMegaBytes = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "MegaBytes" }), + .quantity = EcsData, + .base = EcsBytes, + .prefix = EcsMega }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsMegaBytes, + .kind = EcsU64 + }); - const EcsVector *type_ptr = ecs_get(world, op->type, EcsVector); - next_scope->type = type_ptr->type; - next_scope->is_collection = true; - break; + EcsGigaBytes = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "GigaBytes" }), + .quantity = EcsData, + .base = EcsBytes, + .prefix = EcsGiga }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsGigaBytes, + .kind = EcsU64 + }); - default: { - char *path = ecs_get_fullpath(world, scope->type); - ecs_err("invalid push for type '%s'", path); - ecs_os_free(path); - goto error; - } - } + EcsKibiBytes = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "KibiBytes" }), + .quantity = EcsData, + .base = EcsBytes, + .prefix = EcsKibi }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsKibiBytes, + .kind = EcsU64 + }); - if (scope->is_collection) { - next_scope[0].ptr = ECS_OFFSET(next_scope[0].ptr, - scope->elem_cur * get_size(world, scope)); - } + EcsMebiBytes = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "MebiBytes" }), + .quantity = EcsData, + .base = EcsBytes, + .prefix = EcsMebi }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsMebiBytes, + .kind = EcsU64 + }); - return 0; -error: - return -1; -} + EcsGibiBytes = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "GibiBytes" }), + .quantity = EcsData, + .base = EcsBytes, + .prefix = EcsGibi }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsGibiBytes, + .kind = EcsU64 + }); -int ecs_meta_pop( - ecs_meta_cursor_t *cursor) -{ - if (cursor->is_primitive_scope) { - cursor->is_primitive_scope = false; - return 0; - } + ecs_set_scope(world, prev_scope); - ecs_meta_scope_t *scope = get_scope(cursor); - cursor->depth --; - if (cursor->depth < 0) { - ecs_err("unexpected end of scope"); - return -1; - } + /* DataRate units */ - ecs_meta_scope_t *next_scope = get_scope(cursor); - ecs_meta_type_op_t *op = get_op(next_scope); + EcsDataRate = ecs_quantity_init(world, &(ecs_entity_desc_t){ + .name = "DataRate" }); + prev_scope = ecs_set_scope(world, EcsDataRate); - if (!scope->is_inline_array) { - if (op->kind == EcsOpPush) { - next_scope->op_cur += op->op_count - 1; + EcsBitsPerSecond = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "BitsPerSecond" }), + .quantity = EcsDataRate, + .base = EcsBits, + .over = EcsSeconds }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsBitsPerSecond, + .kind = EcsU64 + }); - /* 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); - } - } else { - /* Make sure that this was an inline array */ - ecs_assert(next_scope->op_count > 1, ECS_INTERNAL_ERROR, NULL); - } + EcsKiloBitsPerSecond = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "KiloBitsPerSecond" }), + .quantity = EcsDataRate, + .base = EcsKiloBits, + .over = EcsSeconds + }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsKiloBitsPerSecond, + .kind = EcsU64 + }); - return 0; -} + EcsMegaBitsPerSecond = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "MegaBitsPerSecond" }), + .quantity = EcsDataRate, + .base = EcsMegaBits, + .over = EcsSeconds + }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsMegaBitsPerSecond, + .kind = EcsU64 + }); -bool ecs_meta_is_collection( - const ecs_meta_cursor_t *cursor) -{ - ecs_meta_scope_t *scope = get_scope(cursor); - return scope->is_collection; -} + EcsGigaBitsPerSecond = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "GigaBitsPerSecond" }), + .quantity = EcsDataRate, + .base = EcsGigaBits, + .over = EcsSeconds + }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsGigaBitsPerSecond, + .kind = EcsU64 + }); -ecs_entity_t ecs_meta_get_type( - const ecs_meta_cursor_t *cursor) -{ - ecs_meta_scope_t *scope = get_scope(cursor); - ecs_meta_type_op_t *op = get_op(scope); - return op->type; -} + EcsBytesPerSecond = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "BytesPerSecond" }), + .quantity = EcsDataRate, + .base = EcsBytes, + .over = EcsSeconds }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsBytesPerSecond, + .kind = EcsU64 + }); -ecs_entity_t ecs_meta_get_unit( - const ecs_meta_cursor_t *cursor) -{ - ecs_meta_scope_t *scope = get_scope(cursor); - ecs_meta_type_op_t *op = get_op(scope); - return op->unit; -} + EcsKiloBytesPerSecond = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "KiloBytesPerSecond" }), + .quantity = EcsDataRate, + .base = EcsKiloBytes, + .over = EcsSeconds + }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsKiloBytesPerSecond, + .kind = EcsU64 + }); -const char* ecs_meta_get_member( - const ecs_meta_cursor_t *cursor) -{ - ecs_meta_scope_t *scope = get_scope(cursor); - ecs_meta_type_op_t *op = get_op(scope); - return op->name; -} + EcsMegaBytesPerSecond = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "MegaBytesPerSecond" }), + .quantity = EcsDataRate, + .base = EcsMegaBytes, + .over = EcsSeconds + }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsMegaBytesPerSecond, + .kind = EcsU64 + }); -/* Utilities for type conversions and bounds checking */ -struct { - int64_t min, max; -} ecs_meta_bounds_signed[EcsMetaTypeOpKindLast + 1] = { - [EcsOpBool] = {false, true}, - [EcsOpChar] = {INT8_MIN, INT8_MAX}, - [EcsOpByte] = {0, UINT8_MAX}, - [EcsOpU8] = {0, UINT8_MAX}, - [EcsOpU16] = {0, UINT16_MAX}, - [EcsOpU32] = {0, UINT32_MAX}, - [EcsOpU64] = {0, INT64_MAX}, - [EcsOpI8] = {INT8_MIN, INT8_MAX}, - [EcsOpI16] = {INT16_MIN, INT16_MAX}, - [EcsOpI32] = {INT32_MIN, INT32_MAX}, - [EcsOpI64] = {INT64_MIN, INT64_MAX}, - [EcsOpUPtr] = {0, ((sizeof(void*) == 4) ? UINT32_MAX : INT64_MAX)}, - [EcsOpIPtr] = { - ((sizeof(void*) == 4) ? INT32_MIN : INT64_MIN), - ((sizeof(void*) == 4) ? INT32_MAX : INT64_MAX) - }, - [EcsOpEntity] = {0, INT64_MAX}, - [EcsOpEnum] = {INT32_MIN, INT32_MAX}, - [EcsOpBitmask] = {0, INT32_MAX} -}; + EcsGigaBytesPerSecond = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "GigaBytesPerSecond" }), + .quantity = EcsDataRate, + .base = EcsGigaBytes, + .over = EcsSeconds + }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsGigaBytesPerSecond, + .kind = EcsU64 + }); -struct { - uint64_t min, max; -} ecs_meta_bounds_unsigned[EcsMetaTypeOpKindLast + 1] = { - [EcsOpBool] = {false, true}, - [EcsOpChar] = {0, INT8_MAX}, - [EcsOpByte] = {0, UINT8_MAX}, - [EcsOpU8] = {0, UINT8_MAX}, - [EcsOpU16] = {0, UINT16_MAX}, - [EcsOpU32] = {0, UINT32_MAX}, - [EcsOpU64] = {0, UINT64_MAX}, - [EcsOpI8] = {0, INT8_MAX}, - [EcsOpI16] = {0, INT16_MAX}, - [EcsOpI32] = {0, INT32_MAX}, - [EcsOpI64] = {0, INT64_MAX}, - [EcsOpUPtr] = {0, ((sizeof(void*) == 4) ? UINT32_MAX : UINT64_MAX)}, - [EcsOpIPtr] = {0, ((sizeof(void*) == 4) ? INT32_MAX : INT64_MAX)}, - [EcsOpEntity] = {0, UINT64_MAX}, - [EcsOpEnum] = {0, INT32_MAX}, - [EcsOpBitmask] = {0, UINT32_MAX} -}; + ecs_set_scope(world, prev_scope); -struct { - double min, max; -} ecs_meta_bounds_float[EcsMetaTypeOpKindLast + 1] = { - [EcsOpBool] = {false, true}, - [EcsOpChar] = {INT8_MIN, INT8_MAX}, - [EcsOpByte] = {0, UINT8_MAX}, - [EcsOpU8] = {0, UINT8_MAX}, - [EcsOpU16] = {0, UINT16_MAX}, - [EcsOpU32] = {0, UINT32_MAX}, - [EcsOpU64] = {0, (double)UINT64_MAX}, - [EcsOpI8] = {INT8_MIN, INT8_MAX}, - [EcsOpI16] = {INT16_MIN, INT16_MAX}, - [EcsOpI32] = {INT32_MIN, INT32_MAX}, - [EcsOpI64] = {INT64_MIN, (double)INT64_MAX}, - [EcsOpUPtr] = {0, ((sizeof(void*) == 4) ? UINT32_MAX : (double)UINT64_MAX)}, - [EcsOpIPtr] = { - ((sizeof(void*) == 4) ? INT32_MIN : (double)INT64_MIN), - ((sizeof(void*) == 4) ? INT32_MAX : (double)INT64_MAX) - }, - [EcsOpEntity] = {0, (double)UINT64_MAX}, - [EcsOpEnum] = {INT32_MIN, INT32_MAX}, - [EcsOpBitmask] = {0, UINT32_MAX} -}; + /* Percentage */ -#define set_T(T, ptr, value)\ - ((T*)ptr)[0] = ((T)value) + EcsPercentage = ecs_quantity_init(world, &(ecs_entity_desc_t){ + .name = "Percentage" }); + ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = EcsPercentage, + .symbol = "%" + }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsPercentage, + .kind = EcsF32 + }); -#define case_T(kind, T, dst, src)\ -case kind:\ - set_T(T, dst, src);\ - break + /* Angles */ -#define case_T_checked(kind, T, dst, src, bounds)\ -case kind:\ - if ((src < bounds[kind].min) || (src > bounds[kind].max)){\ - ecs_err("value %.0f is out of bounds for type %s", (double)src,\ - flecs_meta_op_kind_str(kind));\ - return -1;\ - }\ - set_T(T, dst, src);\ - break + EcsAngle = ecs_quantity_init(world, &(ecs_entity_desc_t){ + .name = "Angle" }); + prev_scope = ecs_set_scope(world, EcsAngle); + EcsRadians = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "Radians" }), + .quantity = EcsAngle, + .symbol = "rad" }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsRadians, + .kind = EcsF32 + }); -#define cases_T_float(dst, src)\ - case_T(EcsOpF32, ecs_f32_t, dst, src);\ - case_T(EcsOpF64, ecs_f64_t, dst, src) + EcsDegrees = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "Degrees" }), + .quantity = EcsAngle, + .symbol = "°" }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsDegrees, + .kind = EcsF32 + }); + ecs_set_scope(world, prev_scope); -#define cases_T_signed(dst, src, bounds)\ - case_T_checked(EcsOpChar, ecs_char_t, dst, src, bounds);\ - case_T_checked(EcsOpI8, ecs_i8_t, dst, src, bounds);\ - case_T_checked(EcsOpI16, ecs_i16_t, dst, src, bounds);\ - case_T_checked(EcsOpI32, ecs_i32_t, dst, src, bounds);\ - case_T_checked(EcsOpI64, ecs_i64_t, dst, src, bounds);\ - case_T_checked(EcsOpIPtr, ecs_iptr_t, dst, src, bounds);\ - case_T_checked(EcsOpEnum, ecs_i32_t, dst, src, bounds) + /* DeciBel */ -#define cases_T_unsigned(dst, src, bounds)\ - case_T_checked(EcsOpByte, ecs_byte_t, dst, src, bounds);\ - case_T_checked(EcsOpU8, ecs_u8_t, dst, src, bounds);\ - case_T_checked(EcsOpU16, ecs_u16_t, dst, src, bounds);\ - case_T_checked(EcsOpU32, ecs_u32_t, dst, src, bounds);\ - case_T_checked(EcsOpU64, ecs_u64_t, dst, src, bounds);\ - case_T_checked(EcsOpUPtr, ecs_uptr_t, dst, src, bounds);\ - case_T_checked(EcsOpEntity, ecs_u64_t, dst, src, bounds);\ - case_T_checked(EcsOpBitmask, ecs_u32_t, dst, src, bounds) + EcsBel = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "Bel" }), + .symbol = "B" }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsBel, + .kind = EcsF32 + }); + EcsDeciBel = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "DeciBel" }), + .prefix = EcsDeci, + .base = EcsBel }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsDeciBel, + .kind = EcsF32 + }); -#define cases_T_bool(dst, src)\ -case EcsOpBool:\ - set_T(ecs_bool_t, dst, value != 0);\ - break + /* Documentation */ +#ifdef FLECS_DOC + ECS_IMPORT(world, FlecsDoc); -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); -} + ecs_doc_set_brief(world, EcsDuration, + "Time amount (e.g. \"20 seconds\", \"2 hours\")"); + ecs_doc_set_brief(world, EcsSeconds, "Time amount in seconds"); + ecs_doc_set_brief(world, EcsMinutes, "60 seconds"); + ecs_doc_set_brief(world, EcsHours, "60 minutes"); + ecs_doc_set_brief(world, EcsDays, "24 hours"); -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); + ecs_doc_set_brief(world, EcsTime, + "Time passed since an epoch (e.g. \"5pm\", \"March 3rd 2022\")"); + ecs_doc_set_brief(world, EcsDate, + "Seconds passed since January 1st 1970"); - switch(op->kind) { - cases_T_bool(ptr, value); - cases_T_unsigned(ptr, value, ecs_meta_bounds_unsigned); - default: - conversion_error(cursor, op, "bool"); - return -1; - } + ecs_doc_set_brief(world, EcsMass, "Units of mass (e.g. \"5 kilograms\")"); - return 0; -} + ecs_doc_set_brief(world, EcsElectricCurrent, + "Units of electrical current (e.g. \"2 ampere\")"); -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); + ecs_doc_set_brief(world, EcsAmount, + "Units of amount of substance (e.g. \"2 mole\")"); - switch(op->kind) { - cases_T_bool(ptr, value); - cases_T_signed(ptr, value, ecs_meta_bounds_signed); - default: - conversion_error(cursor, op, "char"); - return -1; - } + ecs_doc_set_brief(world, EcsLuminousIntensity, + "Units of luminous intensity (e.g. \"1 candela\")"); - return 0; -} + ecs_doc_set_brief(world, EcsForce, "Units of force (e.g. \"10 newton\")"); -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); + ecs_doc_set_brief(world, EcsLength, + "Units of length (e.g. \"5 meters\", \"20 miles\")"); - switch(op->kind) { - cases_T_bool(ptr, value); - cases_T_signed(ptr, value, ecs_meta_bounds_signed); - cases_T_unsigned(ptr, value, ecs_meta_bounds_signed); - cases_T_float(ptr, value); - default: { - if(!value) return ecs_meta_set_null(cursor); - conversion_error(cursor, op, "int"); - return -1; - } - } + ecs_doc_set_brief(world, EcsPressure, + "Units of pressure (e.g. \"1 bar\", \"1000 pascal\")"); - return 0; -} + ecs_doc_set_brief(world, EcsSpeed, + "Units of movement (e.g. \"5 meters/second\")"); -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); + ecs_doc_set_brief(world, EcsAcceleration, + "Unit of speed increase (e.g. \"5 meters/second/second\")"); - switch(op->kind) { - cases_T_bool(ptr, value); - cases_T_signed(ptr, value, ecs_meta_bounds_unsigned); - cases_T_unsigned(ptr, value, ecs_meta_bounds_unsigned); - cases_T_float(ptr, value); - default: - if(!value) return ecs_meta_set_null(cursor); - conversion_error(cursor, op, "uint"); - return -1; - } + ecs_doc_set_brief(world, EcsTemperature, + "Units of temperature (e.g. \"5 degrees Celsius\")"); - return 0; -} + ecs_doc_set_brief(world, EcsData, + "Units of information (e.g. \"8 bits\", \"100 megabytes\")"); -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); + ecs_doc_set_brief(world, EcsDataRate, + "Units of data transmission (e.g. \"100 megabits/second\")"); - switch(op->kind) { - cases_T_bool(ptr, value); - cases_T_signed(ptr, value, ecs_meta_bounds_float); - cases_T_unsigned(ptr, value, ecs_meta_bounds_float); - cases_T_float(ptr, value); - default: - conversion_error(cursor, op, "float"); - return -1; - } + ecs_doc_set_brief(world, EcsAngle, + "Units of rotation (e.g. \"1.2 radians\", \"180 degrees\")"); - return 0; +#endif } -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); +#endif - if (!ecs_os_strcmp(value, "0")) { - return 0; - } - 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; - } +#ifdef FLECS_SNAPSHOT - 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_u32_t*)out |= v[0]; +/* 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; +}; - return 0; -} +/** Small footprint data structure for storing data associated with a table. */ +typedef struct ecs_table_leaf_t { + ecs_table_t *table; + ecs_type_t type; + ecs_data_t *data; +} ecs_table_leaf_t; 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]; +ecs_data_t* duplicate_data( + ecs_table_t *table, + ecs_data_t *main_data) +{ + if (!ecs_table_count(table)) { + return NULL; + } - const char *prev = value, *ptr = value; + ecs_data_t *result = ecs_os_calloc(ECS_SIZEOF(ecs_data_t)); + int32_t i, column_count = table->storage_count; + result->columns = ecs_os_memdup_n( + main_data->columns, ecs_column_t, column_count); - *(ecs_u32_t*)out = 0; + /* Copy entities and records */ + result->entities = ecs_storage_copy_t(&main_data->entities, ecs_entity_t); + result->records = ecs_storage_copy_t(&main_data->records, ecs_record_t*); - 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; - } + /* Copy each column */ + for (i = 0; i < column_count; i ++) { + ecs_column_t *column = &result->columns[i]; + ecs_type_info_t *ti = table->type_info[i]; + int32_t size = ti->size; + ecs_copy_t copy = ti->hooks.copy; + if (copy) { + ecs_column_t dst = ecs_storage_copy(column, size); + int32_t count = ecs_storage_count(column); + void *dst_ptr = ecs_storage_first(&dst); + void *src_ptr = ecs_storage_first(column); - ptr ++; - prev = ptr; - } + ecs_xtor_t ctor = ti->hooks.ctor; + if (ctor) { + ctor(dst_ptr, count, ti); + } - if (add_bitmask_constant(cursor, op, out, prev) != 0) { - return -1; + copy(dst_ptr, src_ptr, count, ti); + *column = dst; + } else { + *column = ecs_storage_copy(column, size); + } } - return 0; + return result; } -int ecs_meta_set_string( - ecs_meta_cursor_t *cursor, - const char *value) +static +void snapshot_table( + ecs_snapshot_t *snapshot, + ecs_table_t *table) { - 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 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 { - ecs_err("invalid value for boolean '%s'", value); - return -1; - } - 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; + if (table->flags & EcsTableHasBuiltins) { + return; } - 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; - } + + 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 = flecs_type_copy(&table->type); + l->data = duplicate_data(table, &table->data); +} - 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; - } +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); - set_T(ecs_i32_t, ptr, v[0]); - break; + ecs_run_aperiodic((ecs_world_t*)world, 0); + + result->world = (ecs_world_t*)world; + + /* 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); } - 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); - } + /* 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); - if (!e) { - ecs_err("unresolved entity identifier '%s'", value); - return -1; - } - } + /* Array may have holes, so initialize with 0 */ + ecs_os_memset_n(arr, 0, ecs_table_leaf_t, table_count); - 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, flecs_meta_op_kind_str(op->kind)); - return -1; + /* Iterate tables in iterator */ + if (iter) { + while (next(iter)) { + ecs_table_t *table = iter->table; + snapshot_table(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(result, table); + } } - return 0; + return result; } -int ecs_meta_set_string_literal( - ecs_meta_cursor_t *cursor, - const char *value) +/** Create a snapshot */ +ecs_snapshot_t* ecs_snapshot_take( + ecs_world_t *stage) { - 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_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); - switch(op->kind) { - case EcsOpChar: - set_T(ecs_char_t, ptr, value[1]); - break; + ecs_snapshot_t *result = snapshot_create( + world, ecs_eis(world), NULL, NULL); - default: - case EcsOpEntity: - case EcsOpString: - len -= 2; + result->last_id = world->info.last_id; - char *result = ecs_os_malloc(len + 1); - ecs_os_memcpy(result, value + 1, len); - result[len] = '\0'; + return result; +} - if (ecs_meta_set_string(cursor, result)) { - ecs_os_free(result); - return -1; - } +/** Create a filtered snapshot */ +ecs_snapshot_t* ecs_snapshot_take_w_iter( + ecs_iter_t *iter) +{ + ecs_world_t *world = iter->world; + ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_os_free(result); + ecs_snapshot_t *result = snapshot_create( + world, ecs_eis(world), iter, iter ? iter->next : NULL); - break; - } + result->last_id = world->info.last_id; - return 0; + return result; } -int ecs_meta_set_entity( - ecs_meta_cursor_t *cursor, - ecs_entity_t value) +/* 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) { - ecs_meta_scope_t *scope = get_scope(cursor); - ecs_meta_type_op_t *op = get_op(scope); - void *ptr = get_ptr(cursor->world, scope); + flecs_sparse_restore(ecs_eis(world), snapshot->entity_index); + flecs_sparse_free(snapshot->entity_index); + + world->info.last_id = snapshot->last_id; - switch(op->kind) { - case EcsOpEntity: - set_T(ecs_entity_t, ptr, value); - break; - default: - conversion_error(cursor, op, "entity"); - return -1; + 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); + + for (i = 0; i <= count; i ++) { + ecs_table_t *world_table = flecs_sparse_get( + &world->store.tables, ecs_table_t, (uint32_t)i); + + if (world_table && (world_table->flags & EcsTableHasBuiltins)) { + continue; + } + + ecs_table_leaf_t *snapshot_table = NULL; + if (i < snapshot_count) { + snapshot_table = &leafs[i]; + if (!snapshot_table->table) { + snapshot_table = NULL; + } + } + + /* If the world table no longer exists but the snapshot table does, + * reinsert it */ + if (!world_table && snapshot_table) { + ecs_table_t *table = flecs_table_find_or_create(world, + &snapshot_table->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) { + ecs_assert(snapshot_table->table == world_table, + ECS_INTERNAL_ERROR, NULL); + + if (snapshot_table->data) { + flecs_table_replace_data( + world, world_table, snapshot_table->data); + } else { + flecs_table_clear_data( + world, world_table, &world_table->data); + flecs_table_init_data(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->data); + 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); + flecs_type_free(&snapshot_table->type); + } } - return 0; + /* 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); + } + } } -int ecs_meta_set_null( - ecs_meta_cursor_t *cursor) +/* Restoring a filtered snapshots only restores the entities in the snapshot + * to their previous state. */ +static +void restore_filtered( + ecs_world_t *world, + ecs_snapshot_t *snapshot) { - 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_leaf_t *leafs = ecs_vector_first( + snapshot->tables, ecs_table_leaf_t); + int32_t l = 0, snapshot_count = ecs_vector_count(snapshot->tables); - return 0; + for (l = 0; l < snapshot_count; l ++) { + ecs_table_leaf_t *snapshot_table = &leafs[l]; + ecs_table_t *table = snapshot_table->table; + + if (!table) { + continue; + } + + ecs_data_t *data = snapshot_table->data; + if (!data) { + flecs_type_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_storage_count(&data->entities); + ecs_entity_t *entities = ecs_storage_first( + &snapshot_table->data->entities); + for (i = 0; i < entity_count; i ++) { + ecs_entity_t e = entities[i]; + ecs_record_t *r = flecs_entities_get(world, e); + if (r && r->table) { + flecs_table_delete(world, r->table, + ECS_RECORD_TO_ROW(r->row), true); + } else { + /* Make sure that the entity has the same generation count */ + flecs_entities_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->data, snapshot_table->data); + + /* Run OnSet systems for merged entities */ + if (new_count) { + flecs_notify_on_set( + world, table, old_count, new_count, NULL, true); + } + + ecs_os_free(snapshot_table->data->columns); + ecs_os_free(snapshot_table->data); + flecs_type_free(&snapshot_table->type); + } } -bool ecs_meta_get_bool( - const ecs_meta_cursor_t *cursor) +/** Restore a snapshot */ +void ecs_snapshot_restore( + ecs_world_t *world, + ecs_snapshot_t *snapshot) { - 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 EcsOpBool: return *(ecs_bool_t*)ptr; - case EcsOpI8: return *(ecs_i8_t*)ptr != 0; - case EcsOpU8: return *(ecs_u8_t*)ptr != 0; - case EcsOpChar: return *(ecs_char_t*)ptr != 0; - case EcsOpByte: return *(ecs_u8_t*)ptr != 0; - case EcsOpI16: return *(ecs_i16_t*)ptr != 0; - case EcsOpU16: return *(ecs_u16_t*)ptr != 0; - case EcsOpI32: return *(ecs_i32_t*)ptr != 0; - case EcsOpU32: return *(ecs_u32_t*)ptr != 0; - case EcsOpI64: return *(ecs_i64_t*)ptr != 0; - case EcsOpU64: return *(ecs_u64_t*)ptr != 0; - case EcsOpIPtr: return *(ecs_iptr_t*)ptr != 0; - case EcsOpUPtr: return *(ecs_uptr_t*)ptr != 0; - case EcsOpF32: return *(ecs_f32_t*)ptr != 0; - case EcsOpF64: return *(ecs_f64_t*)ptr != 0; - case EcsOpString: return *(const char**)ptr != NULL; - case EcsOpEnum: return *(ecs_i32_t*)ptr != 0; - case EcsOpBitmask: return *(ecs_u32_t*)ptr != 0; - case EcsOpEntity: return *(ecs_entity_t*)ptr != 0; - default: ecs_throw(ECS_INVALID_PARAMETER, - "invalid element for bool"); + ecs_run_aperiodic(world, 0); + + 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); } -error: - return 0; + + ecs_vector_free(snapshot->tables); + + ecs_os_free(snapshot); } -char ecs_meta_get_char( - const ecs_meta_cursor_t *cursor) +ecs_iter_t ecs_snapshot_iter( + ecs_snapshot_t *snapshot) { - 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 EcsOpChar: return *(ecs_char_t*)ptr != 0; - default: ecs_throw(ECS_INVALID_PARAMETER, - "invalid element for char"); - } -error: - return 0; + ecs_snapshot_iter_t iter = { + .tables = snapshot->tables, + .index = 0 + }; + + return (ecs_iter_t){ + .world = snapshot->world, + .table_count = ecs_vector_count(snapshot->tables), + .priv.iter.snapshot = iter, + .next = ecs_snapshot_next + }; } -int64_t ecs_meta_get_int( - const ecs_meta_cursor_t *cursor) +bool ecs_snapshot_next( + ecs_iter_t *it) { - 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 EcsOpBool: return *(ecs_bool_t*)ptr; - case EcsOpI8: return *(ecs_i8_t*)ptr; - case EcsOpU8: return *(ecs_u8_t*)ptr; - case EcsOpChar: return *(ecs_char_t*)ptr; - case EcsOpByte: return *(ecs_u8_t*)ptr; - case EcsOpI16: return *(ecs_i16_t*)ptr; - case EcsOpU16: return *(ecs_u16_t*)ptr; - case EcsOpI32: return *(ecs_i32_t*)ptr; - case EcsOpU32: return *(ecs_u32_t*)ptr; - case EcsOpI64: return *(ecs_i64_t*)ptr; - case EcsOpU64: return flecs_uto(int64_t, *(ecs_u64_t*)ptr); - case EcsOpIPtr: return *(ecs_iptr_t*)ptr; - case EcsOpUPtr: return flecs_uto(int64_t, *(ecs_uptr_t*)ptr); - case EcsOpF32: return (int64_t)*(ecs_f32_t*)ptr; - case EcsOpF64: return (int64_t)*(ecs_f64_t*)ptr; - case EcsOpString: return atoi(*(const char**)ptr); - case EcsOpEnum: return *(ecs_i32_t*)ptr; - case EcsOpBitmask: return *(ecs_u32_t*)ptr; - case EcsOpEntity: - ecs_throw(ECS_INVALID_PARAMETER, - "invalid conversion from entity to int"); - break; - default: ecs_throw(ECS_INVALID_PARAMETER, "invalid element for int"); + ecs_snapshot_iter_t *iter = &it->priv.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; + + for (i = iter->index; i < count; i ++) { + ecs_table_t *table = tables[i].table; + if (!table) { + continue; + } + + ecs_data_t *data = tables[i].data; + + it->table = table; + it->count = ecs_table_count(table); + if (data) { + it->entities = ecs_storage_first(&data->entities); + } else { + it->entities = NULL; + } + + ECS_BIT_SET(it->flags, EcsIterIsValid); + iter->index = i + 1; + + goto yield; } -error: - return 0; + + ECS_BIT_CLEAR(it->flags, EcsIterIsValid); + return false; + +yield: + ECS_BIT_CLEAR(it->flags, EcsIterIsValid); + return true; } -uint64_t ecs_meta_get_uint( - const ecs_meta_cursor_t *cursor) +/** Cleanup snapshot */ +void ecs_snapshot_free( + ecs_snapshot_t *snapshot) { - 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 EcsOpBool: return *(ecs_bool_t*)ptr; - case EcsOpI8: return flecs_ito(uint64_t, *(ecs_i8_t*)ptr); - case EcsOpU8: return *(ecs_u8_t*)ptr; - case EcsOpChar: return flecs_ito(uint64_t, *(ecs_char_t*)ptr); - case EcsOpByte: return flecs_ito(uint64_t, *(ecs_u8_t*)ptr); - case EcsOpI16: return flecs_ito(uint64_t, *(ecs_i16_t*)ptr); - case EcsOpU16: return *(ecs_u16_t*)ptr; - case EcsOpI32: return flecs_ito(uint64_t, *(ecs_i32_t*)ptr); - case EcsOpU32: return *(ecs_u32_t*)ptr; - case EcsOpI64: return flecs_ito(uint64_t, *(ecs_i64_t*)ptr); - case EcsOpU64: return *(ecs_u64_t*)ptr; - case EcsOpIPtr: return flecs_ito(uint64_t, *(ecs_i64_t*)ptr); - case EcsOpUPtr: return *(ecs_uptr_t*)ptr; - case EcsOpF32: return flecs_ito(uint64_t, *(ecs_f32_t*)ptr); - case EcsOpF64: return flecs_ito(uint64_t, *(ecs_f64_t*)ptr); - case EcsOpString: return flecs_ito(uint64_t, atoi(*(const char**)ptr)); - case EcsOpEnum: return flecs_ito(uint64_t, *(ecs_i32_t*)ptr); - case EcsOpBitmask: return *(ecs_u32_t*)ptr; - case EcsOpEntity: return *(ecs_entity_t*)ptr; - default: ecs_throw(ECS_INVALID_PARAMETER, "invalid element for uint"); - } -error: - return 0; + flecs_sparse_free(snapshot->entity_index); + + 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); + } + flecs_type_free(&snapshot_table->type); + } + } + + ecs_vector_free(snapshot->tables); + ecs_os_free(snapshot); } -double ecs_meta_get_float( - const 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 EcsOpBool: return *(ecs_bool_t*)ptr; - case EcsOpI8: return *(ecs_i8_t*)ptr; - case EcsOpU8: return *(ecs_u8_t*)ptr; - case EcsOpChar: return *(ecs_char_t*)ptr; - case EcsOpByte: return *(ecs_u8_t*)ptr; - case EcsOpI16: return *(ecs_i16_t*)ptr; - case EcsOpU16: return *(ecs_u16_t*)ptr; - case EcsOpI32: return *(ecs_i32_t*)ptr; - case EcsOpU32: return *(ecs_u32_t*)ptr; - case EcsOpI64: return (double)*(ecs_i64_t*)ptr; - case EcsOpU64: return (double)*(ecs_u64_t*)ptr; - case EcsOpIPtr: return (double)*(ecs_iptr_t*)ptr; - case EcsOpUPtr: return (double)*(ecs_uptr_t*)ptr; - case EcsOpF32: return (double)*(ecs_f32_t*)ptr; - case EcsOpF64: return *(ecs_f64_t*)ptr; - case EcsOpString: return atof(*(const char**)ptr); - case EcsOpEnum: return *(ecs_i32_t*)ptr; - case EcsOpBitmask: return *(ecs_u32_t*)ptr; - case EcsOpEntity: - ecs_throw(ECS_INVALID_PARAMETER, - "invalid conversion from entity to float"); - break; - default: ecs_throw(ECS_INVALID_PARAMETER, "invalid element for float"); +#endif + + +#ifdef FLECS_SYSTEM + + +ecs_mixins_t ecs_system_t_mixins = { + .type_name = "ecs_system_t", + .elems = { + [EcsMixinWorld] = offsetof(ecs_system_t, world), + [EcsMixinEntity] = offsetof(ecs_system_t, entity), + [EcsMixinDtor] = offsetof(ecs_system_t, dtor) } -error: - return 0; -} +}; -const char* ecs_meta_get_string( - const ecs_meta_cursor_t *cursor) +/* -- Public API -- */ + +ecs_entity_t ecs_run_intern( + ecs_world_t *world, + ecs_stage_t *stage, + ecs_entity_t system, + ecs_system_t *system_data, + int32_t stage_index, + int32_t stage_count, + ecs_ftime_t delta_time, + int32_t offset, + int32_t limit, + void *param) { - 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: return *(const char**)ptr; - default: ecs_throw(ECS_INVALID_PARAMETER, "invalid element for string"); + ecs_ftime_t time_elapsed = delta_time; + ecs_entity_t tick_source = system_data->tick_source; + + /* Support legacy behavior */ + if (!param) { + param = system_data->ctx; } -error: - return 0; -} -ecs_entity_t ecs_meta_get_entity( - const 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 EcsOpEntity: return *(ecs_entity_t*)ptr; - default: ecs_throw(ECS_INVALID_PARAMETER, "invalid element for entity"); + 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 { + /* 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; + } } -error: - return 0; -} -#endif + if (ecs_should_log_3()) { + char *path = ecs_get_fullpath(world, system); + ecs_dbg_3("worker %d: %s", stage_index, path); + ecs_os_free(path); + } + ecs_time_t time_start; + bool measure_time = ECS_BIT_IS_SET(world->flags, EcsWorldMeasureSystemTime); + if (measure_time) { + ecs_os_get_time(&time_start); + } -#ifdef FLECS_META + ecs_world_t *thread_ctx = world; + if (stage) { + thread_ctx = stage->thread_ctx; + } -static -ecs_vector_t* serialize_type( - ecs_world_t *world, - ecs_entity_t type, - ecs_size_t offset, - ecs_vector_t *ops); + ecs_defer_begin(thread_ctx); -static -ecs_meta_type_op_kind_t primitive_to_op_kind(ecs_primitive_kind_t kind) { - return EcsOpPrimitive + kind; -} + /* Prepare the query iterator */ + ecs_iter_t pit, wit, qit = ecs_query_iter(thread_ctx, system_data->query); + ecs_iter_t *it = &qit; -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; -} + if (offset || limit) { + pit = ecs_page_iter(it, offset, limit); + it = &pit; + } -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; - op->unit = 0; - return op; -} + if (stage_count > 1 && system_data->multi_threaded) { + wit = ecs_worker_iter(it, stage_index, stage_count); + it = &wit; + } -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; -} + qit.system = system; + qit.delta_time = delta_time; + qit.delta_system_time = time_elapsed; + qit.frame_offset = offset; + qit.param = param; + qit.ctx = system_data->ctx; + qit.binding_ctx = system_data->binding_ctx; -static -ecs_vector_t* serialize_primitive( - ecs_world_t *world, - ecs_entity_t type, - ecs_size_t offset, - ecs_vector_t *ops) -{ - const EcsPrimitive *ptr = ecs_get(world, type, EcsPrimitive); - ecs_assert(ptr != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_iter_action_t action = system_data->action; + it->callback = action; + + ecs_run_action_t run = system_data->run; + if (run) { + run(it); + } else if (system_data->query->filter.term_count) { + if (it == &qit) { + while (ecs_query_next(&qit)) { + action(&qit); + } + } else { + while (ecs_iter_next(it)) { + action(it); + } + } + } else { + action(&qit); + } - 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 (measure_time) { + system_data->time_spent += (float)ecs_time_measure(&time_start); + } - return ops; + system_data->invoke_count ++; + + ecs_defer_end(thread_ctx); + + return it->interrupted_by; } -static -ecs_vector_t* serialize_enum( +/* -- Public API -- */ + +ecs_entity_t ecs_run_w_filter( ecs_world_t *world, - ecs_entity_t type, - ecs_size_t offset, - ecs_vector_t *ops) + ecs_entity_t system, + ecs_ftime_t delta_time, + int32_t offset, + int32_t limit, + void *param) { - (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; + ecs_stage_t *stage = flecs_stage_from_world(&world); + ecs_system_t *system_data = ecs_poly_get(world, system, ecs_system_t); + ecs_assert(system_data != NULL, ECS_INVALID_PARAMETER, NULL); + return ecs_run_intern(world, stage, system, system_data, 0, 0, delta_time, + offset, limit, param); } -static -ecs_vector_t* serialize_bitmask( +ecs_entity_t ecs_run_worker( ecs_world_t *world, - ecs_entity_t type, - ecs_size_t offset, - ecs_vector_t *ops) + ecs_entity_t system, + int32_t stage_index, + int32_t stage_count, + ecs_ftime_t delta_time, + void *param) { - (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); + ecs_stage_t *stage = flecs_stage_from_world(&world); + ecs_system_t *system_data = ecs_poly_get(world, system, ecs_system_t); + ecs_assert(system_data != NULL, ECS_INVALID_PARAMETER, NULL); - return ops; + return ecs_run_intern( + world, stage, system, system_data, stage_index, stage_count, + delta_time, 0, 0, param); } -static -ecs_vector_t* serialize_array( +ecs_entity_t ecs_run( ecs_world_t *world, - ecs_entity_t type, - ecs_size_t offset, - ecs_vector_t *ops) + ecs_entity_t system, + ecs_ftime_t delta_time, + void *param) { - (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; + return ecs_run_w_filter(world, system, delta_time, 0, 0, param); } -static -ecs_vector_t* serialize_array_component( - ecs_world_t *world, - ecs_entity_t type) +ecs_query_t* ecs_system_get_query( + const ecs_world_t *world, + ecs_entity_t system) { - const EcsArray *ptr = ecs_get(world, type, EcsArray); - if (!ptr) { - return NULL; /* Should never happen, will trigger internal error */ + const ecs_system_t *s = ecs_poly_get(world, system, ecs_system_t); + if (s) { + return s->query; + } else { + return NULL; } +} - 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; +void* ecs_get_system_ctx( + const ecs_world_t *world, + ecs_entity_t system) +{ + const ecs_system_t *s = ecs_poly_get(world, system, ecs_system_t); + if (s) { + return s->ctx; + } else { + return NULL; + } +} - return ops; +void* ecs_get_system_binding_ctx( + const ecs_world_t *world, + ecs_entity_t system) +{ + const ecs_system_t *s = ecs_poly_get(world, system, ecs_system_t); + if (s) { + return s->binding_ctx; + } else { + return NULL; + } } +/* System deinitialization */ static -ecs_vector_t* serialize_vector( - ecs_world_t *world, - ecs_entity_t type, - ecs_size_t offset, - ecs_vector_t *ops) -{ - (void)world; +void flecs_system_fini(ecs_system_t *sys) { + if (sys->ctx_free) { + sys->ctx_free(sys->ctx); + } - ecs_meta_type_op_t *op = ops_add(&ops, EcsOpVector); - op->offset = offset; - op->type = type; - op->size = type_size(world, type); + if (sys->binding_ctx_free) { + sys->binding_ctx_free(sys->binding_ctx); + } - return ops; + ecs_poly_free(sys, ecs_system_t); } -static -ecs_vector_t* serialize_struct( +ecs_entity_t ecs_system_init( ecs_world_t *world, - ecs_entity_t type, - ecs_size_t offset, - ecs_vector_t *ops) + const ecs_system_desc_t *desc) { - 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_poly_assert(world, ecs_world_t); + ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(desc->_canary == 0, ECS_INVALID_PARAMETER, NULL); + ecs_assert(!(world->flags & EcsWorldReadonly), + ECS_INVALID_WHILE_READONLY, NULL); - ecs_hashmap_t *member_index = NULL; - if (count) { - op->members = member_index = flecs_name_index_new(); + ecs_entity_t entity = desc->entity; + if (!entity) { + entity = ecs_new(world, 0); } + EcsPoly *poly = ecs_poly_bind(world, entity, ecs_system_t); + if (!poly->poly) { + ecs_system_t *system = ecs_poly_new(ecs_system_t); + ecs_assert(system != NULL, ECS_INTERNAL_ERROR, NULL); + + poly->poly = system; + system->world = world; + system->dtor = (ecs_poly_dtor_t)flecs_system_fini; + system->entity = entity; - 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); + ecs_query_desc_t query_desc = desc->query; + query_desc.entity = entity; - op = ops_get(ops, cur); - if (!op->type) { - op->type = member->type; + ecs_query_t *query = ecs_query_init(world, &query_desc); + if (!query) { + ecs_delete(world, entity); + return 0; } - if (op->count <= 1) { - op->count = member->count; - } - - const char *member_name = member->name; - op->name = member_name; - op->unit = member->unit; - op->op_count = ecs_vector_count(ops) - cur; - - flecs_name_index_ensure( - member_index, flecs_ito(uint64_t, cur - first - 1), - member_name, 0, 0); - } + /* Prevent the system from moving while we're initializing */ + ecs_defer_begin(world); - ops_add(&ops, EcsOpPop); - ops_get(ops, first)->op_count = ecs_vector_count(ops) - first; + system->query = query; + system->query_entity = query->entity; - return ops; -} + system->run = desc->run; + system->action = desc->callback; -static -ecs_vector_t* serialize_type( - ecs_world_t *world, - ecs_entity_t type, - ecs_size_t offset, - ecs_vector_t *ops) -{ - 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; - } + system->ctx = desc->ctx; + system->binding_ctx = desc->binding_ctx; - switch(ptr->kind) { - case EcsPrimitiveType: - ops = serialize_primitive(world, type, offset, ops); - break; + system->ctx_free = desc->ctx_free; + system->binding_ctx_free = desc->binding_ctx_free; - case EcsEnumType: - ops = serialize_enum(world, type, offset, ops); - break; + system->tick_source = desc->tick_source; - case EcsBitmaskType: - ops = serialize_bitmask(world, type, offset, ops); - break; + system->multi_threaded = desc->multi_threaded; + system->no_staging = desc->no_staging; - case EcsStructType: - ops = serialize_struct(world, type, offset, ops); - break; + if (desc->interval != 0 || desc->rate != 0 || desc->tick_source != 0) { +#ifdef FLECS_TIMER + if (desc->interval != 0) { + ecs_set_interval(world, entity, desc->interval); + } - case EcsArrayType: - ops = serialize_array(world, type, offset, ops); - break; + if (desc->rate) { + ecs_set_rate(world, entity, desc->rate, desc->tick_source); + } else if (desc->tick_source) { + ecs_set_tick_source(world, entity, desc->tick_source); + } +#else + ecs_abort(ECS_UNSUPPORTED, "timer module not available"); +#endif + } - case EcsVectorType: - ops = serialize_vector(world, type, offset, ops); - break; - } + if (ecs_get_name(world, entity)) { + ecs_trace("#[green]system#[reset] %s created", + ecs_get_name(world, entity)); + } - return ops; -} + ecs_defer_end(world); + } else { + ecs_system_t *system = ecs_poly(poly->poly, ecs_system_t); -static -ecs_vector_t* serialize_component( - ecs_world_t *world, - ecs_entity_t type) -{ - 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; + if (desc->run) { + system->run = desc->run; + } + 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) { + ECS_BIT_SET(system->query->filter.flags, EcsFilterIsInstanced); + } + if (desc->multi_threaded) { + system->multi_threaded = desc->multi_threaded; + } + if (desc->no_staging) { + system->no_staging = desc->no_staging; + } } - ecs_vector_t *ops = NULL; - - switch(ptr->kind) { - case EcsArrayType: - ops = serialize_array_component(world, type); - break; - default: - ops = serialize_type(world, type, 0, NULL); - break; - } + ecs_poly_modified(world, entity, ecs_system_t); - return ops; + return entity; +error: + return 0; } -void ecs_meta_type_serialized_init( - ecs_iter_t *it) +void FlecsSystemImport( + ecs_world_t *world) { - ecs_world_t *world = it->world; + ECS_MODULE(world, FlecsSystem); - 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); + ecs_set_name_prefix(world, "Ecs"); - EcsMetaTypeSerialized *ptr = ecs_get_mut( - world, e, EcsMetaTypeSerialized); - if (ptr->ops) { - ecs_meta_dtor_serialized(ptr); - } + flecs_bootstrap_tag(world, EcsSystem); + flecs_bootstrap_component(world, EcsTickSource); - ptr->ops = ops; - } + /* 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, EcsMonitor); + ecs_set_scope(world, old_scope); } #endif -#ifdef FLECS_META +#ifdef FLECS_JSON -/* EcsMetaTypeSerialized lifecycle */ +void flecs_json_next( + ecs_strbuf_t *buf); -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_fini(op->members); - ecs_os_free(op->members); - } - } +void flecs_json_literal( + ecs_strbuf_t *buf, + const char *value); - ecs_vector_free(ptr->ops); -} +void flecs_json_number( + ecs_strbuf_t *buf, + double value); -static ECS_COPY(EcsMetaTypeSerialized, dst, src, { - ecs_meta_dtor_serialized(dst); +void flecs_json_true( + ecs_strbuf_t *buf); - dst->ops = ecs_vector_copy(src->ops, ecs_meta_type_op_t); +void flecs_json_false( + ecs_strbuf_t *buf); - 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); - flecs_hashmap_copy(op->members, op->members); - } - } -}) +void flecs_json_bool( + ecs_strbuf_t *buf, + bool value); -static ECS_MOVE(EcsMetaTypeSerialized, dst, src, { - ecs_meta_dtor_serialized(dst); - dst->ops = src->ops; - src->ops = NULL; -}) +void flecs_json_array_push( + ecs_strbuf_t *buf); -static ECS_DTOR(EcsMetaTypeSerialized, ptr, { - ecs_meta_dtor_serialized(ptr); -}) +void flecs_json_array_pop( + ecs_strbuf_t *buf); +void flecs_json_object_push( + ecs_strbuf_t *buf); -/* EcsStruct lifecycle */ +void flecs_json_object_pop( + ecs_strbuf_t *buf); -static void dtor_struct( - EcsStruct *ptr) -{ - 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); -} +void flecs_json_string( + ecs_strbuf_t *buf, + const char *value); -static ECS_COPY(EcsStruct, dst, src, { - dtor_struct(dst); +void flecs_json_member( + ecs_strbuf_t *buf, + const char *name); - dst->members = ecs_vector_copy(src->members, ecs_member_t); +void flecs_json_path( + ecs_strbuf_t *buf, + const ecs_world_t *world, + ecs_entity_t e); - ecs_member_t *members = ecs_vector_first(dst->members, ecs_member_t); - int32_t m, count = ecs_vector_count(dst->members); +void flecs_json_label( + ecs_strbuf_t *buf, + const ecs_world_t *world, + ecs_entity_t e); - for (m = 0; m < count; m ++) { - members[m].name = ecs_os_strdup(members[m].name); - } -}) +void flecs_json_color( + ecs_strbuf_t *buf, + const ecs_world_t *world, + ecs_entity_t e); -static ECS_MOVE(EcsStruct, dst, src, { - dtor_struct(dst); - dst->members = src->members; - src->members = NULL; -}) +void flecs_json_id( + ecs_strbuf_t *buf, + const ecs_world_t *world, + ecs_id_t id); -static ECS_DTOR(EcsStruct, ptr, { dtor_struct(ptr); }) +ecs_primitive_kind_t flecs_json_op_to_primitive_kind( + ecs_meta_type_op_kind_t kind); + +#endif -/* EcsEnum lifecycle */ +#ifdef FLECS_JSON -static void dtor_enum( - EcsEnum *ptr) +void flecs_json_next( + ecs_strbuf_t *buf) { - 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); + ecs_strbuf_list_next(buf); } -static ECS_COPY(EcsEnum, dst, src, { - dtor_enum(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_enum_constant_t *c; - while ((c = ecs_map_next(&it, ecs_enum_constant_t, NULL))) { - c->name = ecs_os_strdup(c->name); - } -}) - -static ECS_MOVE(EcsEnum, dst, src, { - dtor_enum(dst); - dst->constants = src->constants; - src->constants = NULL; -}) +void flecs_json_literal( + ecs_strbuf_t *buf, + const char *value) +{ + ecs_strbuf_appendstr(buf, value); +} -static ECS_DTOR(EcsEnum, ptr, { dtor_enum(ptr); }) +void flecs_json_number( + ecs_strbuf_t *buf, + double value) +{ + ecs_strbuf_appendflt(buf, value, '"'); +} +void flecs_json_true( + ecs_strbuf_t *buf) +{ + flecs_json_literal(buf, "true"); +} -/* EcsBitmask lifecycle */ +void flecs_json_false( + ecs_strbuf_t *buf) +{ + flecs_json_literal(buf, "false"); +} -static void dtor_bitmask( - EcsBitmask *ptr) +void flecs_json_bool( + ecs_strbuf_t *buf, + bool value) { - 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); + if (value) { + flecs_json_true(buf); + } else { + flecs_json_false(buf); } - 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); - } -}) +void flecs_json_array_push( + ecs_strbuf_t *buf) +{ + ecs_strbuf_list_push(buf, "[", ", "); +} -static ECS_MOVE(EcsBitmask, dst, src, { - dtor_bitmask(dst); - dst->constants = src->constants; - src->constants = NULL; -}) +void flecs_json_array_pop( + ecs_strbuf_t *buf) +{ + ecs_strbuf_list_pop(buf, "]"); +} -static ECS_DTOR(EcsBitmask, ptr, { dtor_bitmask(ptr); }) +void flecs_json_object_push( + ecs_strbuf_t *buf) +{ + ecs_strbuf_list_push(buf, "{", ", "); +} +void flecs_json_object_pop( + ecs_strbuf_t *buf) +{ + ecs_strbuf_list_pop(buf, "}"); +} -/* EcsUnit lifecycle */ +void flecs_json_string( + ecs_strbuf_t *buf, + const char *value) +{ + ecs_strbuf_appendch(buf, '"'); + ecs_strbuf_appendstr(buf, value); + ecs_strbuf_appendch(buf, '"'); +} -static void dtor_unit( - EcsUnit *ptr) +void flecs_json_member( + ecs_strbuf_t *buf, + const char *name) { - ecs_os_free(ptr->symbol); + ecs_strbuf_list_appendstr(buf, "\""); + ecs_strbuf_appendstr(buf, name); + ecs_strbuf_appendstr(buf, "\":"); } -static ECS_COPY(EcsUnit, dst, src, { - dtor_unit(dst); - dst->symbol = ecs_os_strdup(src->symbol); - dst->base = src->base; - dst->over = src->over; - dst->prefix = src->prefix; - dst->translation = src->translation; -}) +void flecs_json_path( + ecs_strbuf_t *buf, + const ecs_world_t *world, + ecs_entity_t e) +{ + ecs_strbuf_appendch(buf, '"'); + ecs_get_path_w_sep_buf(world, 0, e, ".", "", buf); + ecs_strbuf_appendch(buf, '"'); +} -static ECS_MOVE(EcsUnit, dst, src, { - dtor_unit(dst); - dst->symbol = src->symbol; - dst->base = src->base; - dst->over = src->over; - dst->prefix = src->prefix; - dst->translation = src->translation; +void flecs_json_label( + ecs_strbuf_t *buf, + const ecs_world_t *world, + ecs_entity_t e) +{ + const char *lbl = NULL; +#ifdef FLECS_DOC + lbl = ecs_doc_get_name(world, e); +#else + lbl = ecs_get_name(world, e); +#endif - src->symbol = NULL; - src->base = 0; - src->over = 0; - src->prefix = 0; - src->translation = (ecs_unit_translation_t){0}; -}) + if (lbl) { + ecs_strbuf_appendch(buf, '"'); + ecs_strbuf_appendstr(buf, lbl); + ecs_strbuf_appendch(buf, '"'); + } else { + ecs_strbuf_appendstr(buf, "0"); + } +} -static ECS_DTOR(EcsUnit, ptr, { dtor_unit(ptr); }) +void flecs_json_color( + ecs_strbuf_t *buf, + const ecs_world_t *world, + ecs_entity_t e) +{ + (void)world; + (void)e; + const char *color = NULL; +#ifdef FLECS_DOC + color = ecs_doc_get_color(world, e); +#endif -/* EcsUnitPrefix lifecycle */ + if (color) { + ecs_strbuf_appendch(buf, '"'); + ecs_strbuf_appendstr(buf, color); + ecs_strbuf_appendch(buf, '"'); + } else { + ecs_strbuf_appendstr(buf, "0"); + } +} -static void dtor_unit_prefix( - EcsUnitPrefix *ptr) +void flecs_json_id( + ecs_strbuf_t *buf, + const ecs_world_t *world, + ecs_id_t id) { - ecs_os_free(ptr->symbol); + ecs_strbuf_appendch(buf, '"'); + ecs_id_str_buf(world, id, buf); + ecs_strbuf_appendch(buf, '"'); } -static ECS_COPY(EcsUnitPrefix, dst, src, { - dtor_unit_prefix(dst); - dst->symbol = ecs_os_strdup(src->symbol); - dst->translation = src->translation; -}) +ecs_primitive_kind_t flecs_json_op_to_primitive_kind( + ecs_meta_type_op_kind_t kind) +{ + return kind - EcsOpPrimitive; +} -static ECS_MOVE(EcsUnitPrefix, dst, src, { - dtor_unit_prefix(dst); - dst->symbol = src->symbol; - dst->translation = src->translation; +#endif - src->symbol = NULL; - src->translation = (ecs_unit_translation_t){0}; -}) -static ECS_DTOR(EcsUnitPrefix, ptr, { dtor_unit_prefix(ptr); }) +#ifdef FLECS_JSON -/* Type initialization */ +static +int json_ser_type( + const ecs_world_t *world, + ecs_vector_t *ser, + const void *base, + ecs_strbuf_t *str); static -int init_type( - ecs_world_t *world, - ecs_entity_t type, - ecs_type_kind_t kind, - ecs_size_t size, - ecs_size_t alignment) -{ - ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(type != 0, ECS_INTERNAL_ERROR, NULL); +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, + int32_t in_array); - EcsMetaType *meta_type = ecs_get_mut(world, type, EcsMetaType); - if (meta_type->kind == 0) { - meta_type->existing = ecs_has(world, type, EcsComponent); +static +int json_ser_type_op( + const ecs_world_t *world, + ecs_meta_type_op_t *op, + const void *base, + ecs_strbuf_t *str); - /* Ensure that component has a default constructor, to prevent crashing - * serializers on uninitialized values. */ - ecs_type_info_t *ti = flecs_type_info_ensure(world, type); - if (!ti->hooks.ctor) { - ti->hooks.ctor = ecs_default_ctor; - } - } else { - if (meta_type->kind != kind) { - ecs_err("type '%s' reregistered with different kind", - ecs_get_name(world, type)); - return -1; - } - } +/* 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) +{ + const EcsEnum *enum_type = ecs_get(world, op->type, EcsEnum); + ecs_check(enum_type != NULL, ECS_INVALID_PARAMETER, NULL); - if (!meta_type->existing) { - EcsComponent *comp = ecs_get_mut(world, type, EcsComponent); - comp->size = size; - comp->alignment = alignment; - ecs_modified(world, type, EcsComponent); - } else { - const EcsComponent *comp = ecs_get(world, type, EcsComponent); - if (comp->size < size) { - ecs_err("computed size for '%s' is larger than actual type", - ecs_get_name(world, type)); - return -1; - } - if (comp->alignment < alignment) { - ecs_err("computed alignment for '%s' is larger than actual type", - ecs_get_name(world, type)); - return -1; - } - if (comp->size == size && comp->alignment != alignment) { - ecs_err("computed size for '%s' matches with actual type but " - "alignment is different", ecs_get_name(world, type)); - return -1; - } - - meta_type->partial = comp->size != size; + 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; } - meta_type->kind = kind; - meta_type->size = size; - meta_type->alignment = alignment; - ecs_modified(world, type, EcsMetaType); + ecs_strbuf_appendch(str, '"'); + ecs_strbuf_appendstr(str, ecs_get_name(world, constant->constant)); + ecs_strbuf_appendch(str, '"'); return 0; +error: + return -1; } -#define init_type_t(world, type, kind, T) \ - init_type(world, type, kind, ECS_SIZEOF(T), ECS_ALIGNOF(T)) - +/* Serialize bitmask */ static -void set_struct_member( - ecs_member_t *member, - ecs_entity_t entity, - const char *name, - ecs_entity_t type, - int32_t count, - int32_t offset, - ecs_entity_t unit) +int json_ser_bitmask( + const ecs_world_t *world, + ecs_meta_type_op_t *op, + const void *ptr, + ecs_strbuf_t *str) { - member->member = entity; - member->type = type; - member->count = count; - member->unit = unit; - member->offset = offset; + const EcsBitmask *bitmask_type = ecs_get(world, op->type, EcsBitmask); + ecs_check(bitmask_type != NULL, ECS_INVALID_PARAMETER, NULL); - if (!count) { - member->count = 1; - } + uint32_t value = *(uint32_t*)ptr; + ecs_map_key_t key; + ecs_bitmask_constant_t *constant; - ecs_os_strset((char**)&member->name, name); -} + if (!value) { + ecs_strbuf_appendch(str, '0'); + return 0; + } -static -int add_member_to_struct( - ecs_world_t *world, - ecs_entity_t type, - ecs_entity_t member, - EcsMember *m) -{ - 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); + ecs_strbuf_list_push(str, "\"", "|"); - 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; + /* 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; + } } - 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 (value != 0) { + /* All bits must have been matched by a constant */ + goto error; } - 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; - } + ecs_strbuf_list_pop(str, "\""); - ecs_entity_t unit = m->unit; + return 0; +error: + return -1; +} - if (unit) { - if (!ecs_has(world, unit, EcsUnit)) { - ecs_err("entity '%s' for member '%s' is not a unit", - ecs_get_name(world, unit), name); - return -1; - } +/* 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) +{ + flecs_json_array_push(str); - if (ecs_has(world, m->type, EcsUnit) && m->type != unit) { - ecs_err("unit mismatch for type '%s' and unit '%s' for member '%s'", - ecs_get_name(world, m->type), ecs_get_name(world, unit), name); + const void *ptr = base; + + 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, 1)) { return -1; } - } else { - if (ecs_has(world, m->type, EcsUnit)) { - unit = m->type; - m->unit = unit; - } + ptr = ECS_OFFSET(ptr, elem_size); } - EcsStruct *s = ecs_get_mut(world, type, EcsStruct); - ecs_assert(s != NULL, ECS_INTERNAL_ERROR, NULL); + flecs_json_array_pop(str); - /* 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, m->offset, unit); - break; - } - } + return 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, m->offset, unit); +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); - /* Reobtain members array in case it was reallocated */ - members = ecs_vector_first(s->members, ecs_member_t); - count ++; - } + const EcsComponent *comp = ecs_get(world, type, EcsComponent); + ecs_assert(comp != NULL, ECS_INTERNAL_ERROR, NULL); - bool explicit_offset = false; - if (m->offset) { - explicit_offset = true; - } + 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); - /* Compute member offsets and size & alignment of struct */ - ecs_size_t size = 0; - ecs_size_t alignment = 0; + return json_ser_elements( + world, ops, op_count, base, elem_count, comp->size, str); +} - if (!explicit_offset) { - for (i = 0; i < count; i ++) { - ecs_member_t *elem = &members[i]; +/* 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); - ecs_assert(elem->name != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(elem->type != 0, ECS_INTERNAL_ERROR, NULL); + return json_ser_type_elements( + world, a->type, ptr, a->count, str); +} - /* 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; - } +/* 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; + } - ecs_size_t member_size = mbr_comp->size; - ecs_size_t member_alignment = mbr_comp->alignment; + const EcsVector *v = ecs_get(world, op->type, EcsVector); + ecs_assert(v != NULL, ECS_INTERNAL_ERROR, NULL); - 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; - } + const EcsComponent *comp = ecs_get(world, v->type, EcsComponent); + ecs_assert(comp != NULL, ECS_INTERNAL_ERROR, NULL); - member_size *= elem->count; - size = ECS_ALIGN(size, member_alignment); - elem->size = member_size; - elem->offset = size; + int32_t count = ecs_vector_count(value); + void *array = ecs_vector_first_t(value, comp->size, comp->alignment); - size += member_size; + /* Serialize contiguous buffer of vector */ + return json_ser_type_elements(world, v->type, array, count, str); +} - if (member_alignment > alignment) { - alignment = member_alignment; - } +/* 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 EcsOpF32: + ecs_strbuf_appendflt(str, + (ecs_f64_t)*(ecs_f32_t*)ECS_OFFSET(ptr, op->offset), '"'); + break; + case EcsOpF64: + ecs_strbuf_appendflt(str, + *(ecs_f64_t*)ECS_OFFSET(ptr, op->offset), '"'); + break; + case EcsOpEnum: + if (json_ser_enum(world, op, ECS_OFFSET(ptr, op->offset), str)) { + goto error; } - } else { - /* If members have explicit offsets, we can't rely on computed - * size/alignment values. Grab size of just added member instead. It - * doesn't matter if the size doesn't correspond to the actual struct - * size. The init_type function compares computed size with actual - * (component) size to determine if the type is partial. */ - const EcsComponent *cptr = ecs_get(world, m->type, EcsComponent); - ecs_assert(cptr != NULL, ECS_INTERNAL_ERROR, NULL); - size = cptr->size; - alignment = cptr->alignment; - } - - if (size == 0) { - ecs_err("struct '%s' has 0 size", ecs_get_name(world, type)); - return -1; + 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*)ECS_OFFSET(ptr, op->offset); + if (!e) { + ecs_strbuf_appendch(str, '0'); + } else { + flecs_json_path(str, world, e); + } + break; } - if (alignment == 0) { - ecs_err("struct '%s' has 0 alignment", ecs_get_name(world, type)); - return -1; + default: + if (ecs_primitive_to_expr_buf(world, + flecs_json_op_to_primitive_kind(op->kind), + ECS_OFFSET(ptr, op->offset), str)) + { + /* Unknown operation */ + ecs_throw(ECS_INTERNAL_ERROR, NULL); + return -1; + } + break; } - /* Align struct size to struct alignment */ - size = ECS_ALIGN(size, alignment); - - ecs_modified(world, type, EcsStruct); + return 0; +error: + return -1; +} - /* Do this last as it triggers the update of EcsMetaTypeSerialized */ - if (init_type(world, type, EcsStructType, size, alignment)) { - 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, + int32_t in_array) +{ + for (int i = 0; i < op_count; i ++) { + ecs_meta_type_op_t *op = &ops[i]; - /* 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); - ecs_assert(type_mbr != NULL, ECS_INTERNAL_ERROR, NULL); + if (in_array <= 0) { + if (op->name) { + flecs_json_member(str, op->name); + } - type_mbr->type = type; - type_mbr->count = 1; + int32_t elem_count = op->count; + if (elem_count > 1) { + /* Serialize inline array */ + if (json_ser_elements(world, op, op->op_count, base, + elem_count, op->size, str)) + { + return -1; + } - ecs_modified(world, type, EcsMember); + i += op->op_count - 1; + continue; + } + } + + switch(op->kind) { + case EcsOpPush: + flecs_json_object_push(str); + in_array --; + break; + case EcsOpPop: + flecs_json_object_pop(str); + in_array ++; + break; + default: + if (json_ser_type_op(world, op, base, str)) { + goto error; + } + break; + } } return 0; +error: + return -1; } +/* Iterate over the type ops of a type */ static -int add_constant_to_enum( - ecs_world_t *world, - ecs_entity_t type, - ecs_entity_t e, - ecs_id_t constant_id) +int json_ser_type( + const ecs_world_t *world, + ecs_vector_t *v_ops, + const void *base, + ecs_strbuf_t *str) { - EcsEnum *ptr = ecs_get_mut(world, type, EcsEnum); - - /* 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; + 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, 0); +} - 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); +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) +{ + if (count) { + ecs_size_t size = comp->size; + + flecs_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); + + flecs_json_array_pop(buf); + } else { + if (json_ser_type(world, ser->ops, ptr, buf)) { + return -1; } } - /* Check if constant sets explicit value */ - int32_t value = 0; - bool value_set = false; - if (ecs_id_is_pair(constant_id)) { - if (ecs_pair_second(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; - } + return 0; +} - 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; +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; } - /* 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; - } - } + 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; } - if (!ptr->constants) { - ptr->constants = ecs_map_new(ecs_enum_constant_t, 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; } - 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; + return ecs_strbuf_get(&str); +} - ecs_i32_t *cptr = ecs_get_mut_pair_object( - world, e, EcsConstant, ecs_i32_t); - ecs_assert(cptr != NULL, ECS_INTERNAL_ERROR, NULL); - cptr[0] = value; +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); +} - return 0; +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); } static -int add_constant_to_bitmask( - ecs_world_t *world, - ecs_entity_t type, - ecs_entity_t e, - ecs_id_t constant_id) +bool skip_id( + const ecs_world_t *world, + ecs_id_t id, + const ecs_entity_to_json_desc_t *desc, + ecs_entity_t ent, + ecs_entity_t inst, + ecs_entity_t *pred_out, + ecs_entity_t *obj_out, + ecs_entity_t *role_out, + bool *hidden_out) { - EcsBitmask *ptr = ecs_get_mut(world, type, EcsBitmask); - - /* 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); + bool is_base = ent != inst; + ecs_entity_t pred = 0, obj = 0, role = 0; + bool hidden = false; + + if (ECS_HAS_ID_FLAG(id, PAIR)) { + pred = ecs_pair_first(world, id); + obj = ecs_pair_second(world, id); + } else { + pred = id & ECS_COMPONENT_MASK; + if (id & ECS_ID_FLAGS_MASK) { + role = id & ECS_ID_FLAGS_MASK; } } - /* Check if constant sets explicit value */ - uint32_t value = 1; - if (ecs_id_is_pair(constant_id)) { - if (ecs_pair_second(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; + if (!desc || !desc->serialize_meta_ids) { + if (pred == EcsIsA || pred == EcsChildOf || + pred == ecs_id(EcsIdentifier)) + { + 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 { - value = 1u << (ecs_u32_t)ecs_map_count(ptr->constants); +#ifdef FLECS_DOC + if (pred == ecs_id(EcsDocDescription)) { + return true; + } +#endif } - /* 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; + if (is_base) { + if (ecs_has_id(world, pred, EcsDontInherit)) { + return true; } } - - if (!ptr->constants) { - ptr->constants = ecs_map_new(ecs_bitmask_constant_t, 1); + if (!desc || !desc->serialize_private) { + if (ecs_has_id(world, pred, EcsPrivate)) { + return true; + } + } + if (is_base) { + if (ecs_get_target_for_id(world, inst, EcsIsA, id) != ent) { + hidden = true; + } + } + if (hidden && (!desc || !desc->serialize_hidden)) { + return true; } - 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); - ecs_assert(cptr != NULL, ECS_INTERNAL_ERROR, NULL); - cptr[0] = value; + *pred_out = pred; + *obj_out = obj; + *role_out = role; + if (hidden_out) *hidden_out = hidden; - return 0; + return false; } static -void set_primitive(ecs_iter_t *it) { - ecs_world_t *world = it->world; - EcsPrimitive *type = ecs_field(it, EcsPrimitive, 1); - - int i, count = it->count; - for (i = 0; i < count; i ++) { - ecs_entity_t e = it->entities[i]; - switch(type->kind) { - case EcsBool: - init_type_t(world, e, EcsPrimitiveType, bool); - break; - case EcsChar: - init_type_t(world, e, EcsPrimitiveType, char); - break; - case EcsByte: - init_type_t(world, e, EcsPrimitiveType, bool); - break; - case EcsU8: - init_type_t(world, e, EcsPrimitiveType, uint8_t); - break; - case EcsU16: - init_type_t(world, e, EcsPrimitiveType, uint16_t); - break; - case EcsU32: - init_type_t(world, e, EcsPrimitiveType, uint32_t); - break; - case EcsU64: - init_type_t(world, e, EcsPrimitiveType, uint64_t); - break; - case EcsI8: - init_type_t(world, e, EcsPrimitiveType, int8_t); - break; - case EcsI16: - init_type_t(world, e, EcsPrimitiveType, int16_t); - break; - case EcsI32: - init_type_t(world, e, EcsPrimitiveType, int32_t); - break; - case EcsI64: - init_type_t(world, e, EcsPrimitiveType, int64_t); - break; - case EcsF32: - init_type_t(world, e, EcsPrimitiveType, float); - break; - case EcsF64: - init_type_t(world, e, EcsPrimitiveType, double); - break; - case EcsUPtr: - init_type_t(world, e, EcsPrimitiveType, uintptr_t); - break; - case EcsIPtr: - init_type_t(world, e, EcsPrimitiveType, intptr_t); - break; - case EcsString: - init_type_t(world, e, EcsPrimitiveType, char*); - break; - case EcsEntity: - init_type_t(world, e, EcsPrimitiveType, ecs_entity_t); - break; - } +int append_type_labels( + const ecs_world_t *world, + ecs_strbuf_t *buf, + const ecs_id_t *ids, + int32_t count, + ecs_entity_t ent, + ecs_entity_t inst, + const ecs_entity_to_json_desc_t *desc) +{ + (void)world; (void)buf; (void)ids; (void)count; (void)ent; (void)inst; + (void)desc; + +#ifdef FLECS_DOC + if (!desc || !desc->serialize_id_labels) { + return 0; } -} -static -void set_member(ecs_iter_t *it) { - ecs_world_t *world = it->world; - EcsMember *member = ecs_field(it, EcsMember, 1); + flecs_json_member(buf, "id_labels"); + flecs_json_array_push(buf); - int i, count = it->count; + int32_t i; for (i = 0; i < count; i ++) { - ecs_entity_t e = it->entities[i]; - ecs_entity_t parent = ecs_get_target(world, e, EcsChildOf, 0); - if (!parent) { - ecs_err("missing parent for member '%s'", ecs_get_name(world, e)); + ecs_entity_t pred = 0, obj = 0, role = 0; + if (skip_id(world, ids[i], desc, ent, inst, &pred, &obj, &role, 0)) { continue; } - add_member_to_struct(world, parent, e, &member[i]); - } -} + if (obj && (pred == EcsUnion)) { + pred = obj; + obj = ecs_get_target(world, ent, pred, 0); + } -static -void add_enum(ecs_iter_t *it) { - ecs_world_t *world = it->world; + if (desc && desc->serialize_id_labels) { + flecs_json_next(buf); - int i, count = it->count; - for (i = 0; i < count; i ++) { - ecs_entity_t e = it->entities[i]; + flecs_json_array_push(buf); + flecs_json_next(buf); + flecs_json_label(buf, world, pred); + if (obj) { + flecs_json_next(buf); + flecs_json_label(buf, world, obj); + } - if (init_type_t(world, e, EcsEnumType, ecs_i32_t)) { - continue; + flecs_json_array_pop(buf); } - - ecs_add_id(world, e, EcsExclusive); - ecs_add_id(world, e, EcsOneOf); - ecs_add_id(world, e, EcsTag); } + + flecs_json_array_pop(buf); +#endif + 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_type_t(world, e, EcsBitmaskType, ecs_u32_t)) { - continue; - } +int append_type_values( + const ecs_world_t *world, + ecs_strbuf_t *buf, + const ecs_id_t *ids, + int32_t count, + ecs_entity_t ent, + ecs_entity_t inst, + const ecs_entity_to_json_desc_t *desc) +{ + if (!desc || !desc->serialize_values) { + return 0; } -} -static -void add_constant(ecs_iter_t *it) { - ecs_world_t *world = it->world; + flecs_json_member(buf, "values"); + flecs_json_array_push(buf); - int i, count = it->count; + int32_t i; for (i = 0; i < count; i ++) { - ecs_entity_t e = it->entities[i]; - ecs_entity_t parent = ecs_get_target(world, e, EcsChildOf, 0); - if (!parent) { - ecs_err("missing parent for constant '%s'", ecs_get_name(world, e)); + bool hidden; + ecs_entity_t pred = 0, obj = 0, role = 0; + ecs_id_t id = ids[i]; + if (skip_id(world, id, desc, ent, inst, &pred, &obj, &role, + &hidden)) + { continue; } - 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 (!hidden) { + bool serialized = false; + 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); + + flecs_json_next(buf); + if (json_ser_type(world, ser->ops, ptr, buf) != 0) { + /* Entity contains invalid value */ + return -1; + } + serialized = true; + } + } + if (!serialized) { + flecs_json_next(buf); + flecs_json_number(buf, 0); + } + } else { + if (!desc || desc->serialize_hidden) { + flecs_json_next(buf); + flecs_json_number(buf, 0); + } } } + + flecs_json_array_pop(buf); + + return 0; } static -void set_array(ecs_iter_t *it) { - ecs_world_t *world = it->world; - EcsArray *array = ecs_field(it, EcsArray, 1); - - 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; +int append_type_info( + const ecs_world_t *world, + ecs_strbuf_t *buf, + const ecs_id_t *ids, + int32_t count, + ecs_entity_t ent, + ecs_entity_t inst, + const ecs_entity_to_json_desc_t *desc) +{ + if (!desc || !desc->serialize_type_info) { + return 0; + } - if (!elem_type) { - ecs_err("array '%s' has no element type", ecs_get_name(world, e)); - continue; - } + flecs_json_member(buf, "type_info"); + flecs_json_array_push(buf); - if (!elem_count) { - ecs_err("array '%s' has size 0", ecs_get_name(world, e)); + int32_t i; + for (i = 0; i < count; i ++) { + bool hidden; + ecs_entity_t pred = 0, obj = 0, role = 0; + ecs_id_t id = ids[i]; + if (skip_id(world, id, desc, ent, inst, &pred, &obj, &role, + &hidden)) + { continue; } - const EcsComponent *elem_ptr = ecs_get(world, elem_type, EcsComponent); - if (init_type(world, e, EcsArrayType, - elem_ptr->size * elem_count, elem_ptr->alignment)) - { - continue; + if (!hidden) { + ecs_entity_t typeid = ecs_get_typeid(world, id); + if (typeid) { + flecs_json_next(buf); + if (ecs_type_info_to_json_buf(world, typeid, buf) != 0) { + return -1; + } + } else { + flecs_json_next(buf); + flecs_json_number(buf, 0); + } + } else { + if (!desc || desc->serialize_hidden) { + flecs_json_next(buf); + flecs_json_number(buf, 0); + } } } + + flecs_json_array_pop(buf); + + return 0; } static -void set_vector(ecs_iter_t *it) { - ecs_world_t *world = it->world; - EcsVector *array = ecs_field(it, EcsVector, 1); +int append_type_hidden( + const ecs_world_t *world, + ecs_strbuf_t *buf, + const ecs_id_t *ids, + int32_t count, + ecs_entity_t ent, + ecs_entity_t inst, + const ecs_entity_to_json_desc_t *desc) +{ + if (!desc || !desc->serialize_hidden) { + return 0; + } - 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 (ent == inst) { + return 0; /* if this is not a base, components are never hidden */ + } - if (!elem_type) { - ecs_err("vector '%s' has no element type", ecs_get_name(world, e)); - continue; - } + flecs_json_member(buf, "hidden"); + flecs_json_array_push(buf); - if (init_type_t(world, e, EcsVectorType, ecs_vector_t*)) { + int32_t i; + for (i = 0; i < count; i ++) { + bool hidden; + ecs_entity_t pred = 0, obj = 0, role = 0; + ecs_id_t id = ids[i]; + if (skip_id(world, id, desc, ent, inst, &pred, &obj, &role, + &hidden)) + { continue; } + + flecs_json_next(buf); + flecs_json_bool(buf, hidden); } -} -bool flecs_unit_validate( - ecs_world_t *world, - ecs_entity_t t, - EcsUnit *data) -{ - char *derived_symbol = NULL; - const char *symbol = data->symbol; + flecs_json_array_pop(buf); + + return 0; +} - ecs_entity_t base = data->base; - ecs_entity_t over = data->over; - ecs_entity_t prefix = data->prefix; - ecs_unit_translation_t translation = data->translation; - if (base) { - if (!ecs_has(world, base, EcsUnit)) { - ecs_err("entity '%s' for unit '%s' used as base is not a unit", - ecs_get_name(world, base), ecs_get_name(world, t)); - 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) +{ + const ecs_id_t *ids = NULL; + int32_t i, count = 0; + + const ecs_type_t *type = ecs_get_type(world, ent); + if (type) { + ids = type->array; + count = type->count; } - if (over) { - if (!base) { - ecs_err("invalid unit '%s': cannot specify over without base", - ecs_get_name(world, t)); - goto error; - } - if (!ecs_has(world, over, EcsUnit)) { - ecs_err("entity '%s' for unit '%s' used as over is not a unit", - ecs_get_name(world, over), ecs_get_name(world, t)); - goto error; + flecs_json_member(buf, "ids"); + flecs_json_array_push(buf); + + for (i = 0; i < count; i ++) { + ecs_entity_t pred = 0, obj = 0, role = 0; + if (skip_id(world, ids[i], desc, ent, inst, &pred, &obj, &role, 0)) { + continue; } - } - if (prefix) { - if (!base) { - ecs_err("invalid unit '%s': cannot specify prefix without base", - ecs_get_name(world, t)); - goto error; + if (obj && (pred == EcsUnion)) { + pred = obj; + obj = ecs_get_target(world, ent, pred, 0); } - const EcsUnitPrefix *prefix_ptr = ecs_get(world, prefix, EcsUnitPrefix); - if (!prefix_ptr) { - ecs_err("entity '%s' for unit '%s' used as prefix is not a prefix", - ecs_get_name(world, over), ecs_get_name(world, t)); - goto error; + + flecs_json_next(buf); + flecs_json_array_push(buf); + flecs_json_next(buf); + flecs_json_path(buf, world, pred); + if (obj || role) { + flecs_json_next(buf); + if (obj) { + flecs_json_path(buf, world, obj); + } else { + flecs_json_number(buf, 0); + } + if (role) { + flecs_json_next(buf); + flecs_json_string(buf, ecs_id_flag_str(role)); + } } + flecs_json_array_pop(buf); + } - if (translation.factor || translation.power) { - if (prefix_ptr->translation.factor != translation.factor || - prefix_ptr->translation.power != translation.power) + flecs_json_array_pop(buf); + + if (append_type_labels(world, buf, ids, count, ent, inst, desc)) { + return -1; + } + + if (append_type_values(world, buf, ids, count, ent, inst, desc)) { + return -1; + } + + if (append_type_info(world, buf, ids, count, ent, inst, desc)) { + return -1; + } + + if (append_type_hidden(world, buf, ids, count, ent, inst, desc)) { + return -1; + } + + 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) +{ + const ecs_type_t *type = ecs_get_type(world, ent); + ecs_id_t *ids = NULL; + int32_t i, count = 0; + if (type) { + ids = type->array; + count = type->count; + } + + 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_second(world, id), inst, desc)) { - ecs_err( - "factor for unit '%s' is inconsistent with prefix '%s'", - ecs_get_name(world, t), ecs_get_name(world, prefix)); - goto error; + return -1; } - } else { - translation = prefix_ptr->translation; } } - if (base) { - bool must_match = false; /* Must base symbol match symbol? */ - ecs_strbuf_t sbuf = ECS_STRBUF_INIT; - if (prefix) { - const EcsUnitPrefix *ptr = ecs_get(world, prefix, EcsUnitPrefix); - ecs_assert(ptr != NULL, ECS_INTERNAL_ERROR, NULL); - if (ptr->symbol) { - ecs_strbuf_appendstr(&sbuf, ptr->symbol); - must_match = true; - } + ecs_strbuf_list_next(buf); + flecs_json_object_push(buf); + flecs_json_member(buf, "path"); + flecs_json_path(buf, world, ent); + + if (append_type(world, buf, ent, inst, desc)) { + return -1; + } + + flecs_json_object_pop(buf); + + return 0; +} + +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; + } + + flecs_json_object_push(buf); + + if (!desc || desc->serialize_path) { + char *path = ecs_get_fullpath(world, entity); + flecs_json_member(buf, "path"); + flecs_json_string(buf, path); + ecs_os_free(path); + } + +#ifdef FLECS_DOC + if (desc && desc->serialize_label) { + flecs_json_member(buf, "label"); + const char *doc_name = ecs_doc_get_name(world, entity); + if (doc_name) { + flecs_json_string(buf, doc_name); + } else { + char num_buf[20]; + ecs_os_sprintf(num_buf, "%u", (uint32_t)entity); + flecs_json_string(buf, num_buf); } + } - const EcsUnit *uptr = ecs_get(world, base, EcsUnit); - ecs_assert(uptr != NULL, ECS_INTERNAL_ERROR, NULL); - if (uptr->symbol) { - ecs_strbuf_appendstr(&sbuf, uptr->symbol); + if (desc && desc->serialize_brief) { + const char *doc_brief = ecs_doc_get_brief(world, entity); + if (doc_brief) { + flecs_json_member(buf, "brief"); + flecs_json_string(buf, doc_brief); } + } - if (over) { - uptr = ecs_get(world, over, EcsUnit); - ecs_assert(uptr != NULL, ECS_INTERNAL_ERROR, NULL); - if (uptr->symbol) { - ecs_strbuf_appendstr(&sbuf, "/"); - ecs_strbuf_appendstr(&sbuf, uptr->symbol); - must_match = true; - } + if (desc && desc->serialize_link) { + const char *doc_link = ecs_doc_get_link(world, entity); + if (doc_link) { + flecs_json_member(buf, "link"); + flecs_json_string(buf, doc_link); } + } - derived_symbol = ecs_strbuf_get(&sbuf); - if (derived_symbol && !ecs_os_strlen(derived_symbol)) { - ecs_os_free(derived_symbol); - derived_symbol = NULL; + if (desc && desc->serialize_color) { + const char *doc_color = ecs_doc_get_color(world, entity); + if (doc_color) { + flecs_json_member(buf, "color"); + flecs_json_string(buf, doc_color); } + } +#endif - if (derived_symbol && symbol && ecs_os_strcmp(symbol, derived_symbol)) { - if (must_match) { - ecs_err("symbol '%s' for unit '%s' does not match base" - " symbol '%s'", symbol, - ecs_get_name(world, t), derived_symbol); - goto error; + const ecs_type_t *type = ecs_get_type(world, entity); + ecs_id_t *ids = NULL; + int32_t i, count = 0; + if (type) { + ids = type->array; + count = type->count; + } + + if (!desc || desc->serialize_base) { + if (ecs_has_pair(world, entity, EcsIsA, EcsWildcard)) { + flecs_json_member(buf, "is_a"); + flecs_json_array_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_second(world, id), entity, desc)) + { + return -1; + } + } } - } - if (!symbol && derived_symbol && (prefix || over)) { - ecs_os_free(data->symbol); - data->symbol = derived_symbol; - } else { - ecs_os_free(derived_symbol); + + flecs_json_array_pop(buf); } } - data->base = base; - data->over = over; - data->prefix = prefix; - data->translation = translation; + if (append_type(world, buf, entity, entity, desc)) { + goto error; + } - return true; + flecs_json_object_pop(buf); + + return 0; error: - ecs_os_free(derived_symbol); - return false; + return -1; } -static -void set_unit(ecs_iter_t *it) { - EcsUnit *u = ecs_field(it, EcsUnit, 1); - - ecs_world_t *world = it->world; +char* ecs_entity_to_json( + const ecs_world_t *world, + ecs_entity_t entity, + const ecs_entity_to_json_desc_t *desc) +{ + ecs_strbuf_t buf = ECS_STRBUF_INIT; - int i, count = it->count; - for (i = 0; i < count; i ++) { - ecs_entity_t e = it->entities[i]; - flecs_unit_validate(world, e, &u[i]); + if (ecs_entity_to_json_buf(world, entity, &buf, desc) != 0) { + ecs_strbuf_reset(&buf); + return NULL; } + + return ecs_strbuf_get(&buf); } static -void unit_quantity_monitor(ecs_iter_t *it) { - ecs_world_t *world = it->world; - - int i, count = it->count; - if (it->event == EcsOnAdd) { - for (i = 0; i < count; i ++) { - ecs_entity_t e = it->entities[i]; - ecs_add_pair(world, e, EcsQuantity, e); - } +bool skip_variable( + const char *name) +{ + if (!name || name[0] == '_' || name[0] == '.') { + return true; } else { - for (i = 0; i < count; i ++) { - ecs_entity_t e = it->entities[i]; - ecs_remove_pair(world, e, EcsQuantity, e); - } + return false; } } static -void ecs_meta_type_init_default_ctor(ecs_iter_t *it) { - ecs_world_t *world = it->world; - EcsMetaType *type = ecs_field(it, EcsMetaType, 1); - - int i; - for (i = 0; i < it->count; i ++) { - /* If a component is defined from reflection data, configure it with the - * default constructor. This ensures that a new component value does not - * contain uninitialized memory, which could cause serializers to crash - * when for example inspecting string fields. */ - if (!type->existing) { - ecs_set_hooks_id(world, it->entities[i], - &(ecs_type_hooks_t){ - .ctor = ecs_default_ctor - }); - } - } +void serialize_id( + const ecs_world_t *world, + ecs_id_t id, + ecs_strbuf_t *buf) +{ + flecs_json_id(buf, world, id); } static -void member_on_set(ecs_iter_t *it) { - EcsMember *mbr = it->ptrs[0]; - if (!mbr->count) { - mbr->count = 1; +void serialize_iter_ids( + const ecs_world_t *world, + const ecs_iter_t *it, + ecs_strbuf_t *buf) +{ + int32_t field_count = it->field_count; + if (!field_count) { + return; + } + + flecs_json_member(buf, "ids"); + flecs_json_array_push(buf); + + for (int i = 0; i < field_count; i ++) { + flecs_json_next(buf); + serialize_id(world, it->terms[i].id, buf); } + + flecs_json_array_pop(buf); } -void FlecsMetaImport( - ecs_world_t *world) +static +void serialize_type_info( + const ecs_world_t *world, + const ecs_iter_t *it, + ecs_strbuf_t *buf) { - ECS_MODULE(world, FlecsMeta); + int32_t field_count = it->field_count; + if (!field_count) { + return; + } - ecs_set_name_prefix(world, "Ecs"); + flecs_json_member(buf, "type_info"); + flecs_json_object_push(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); - flecs_bootstrap_component(world, EcsUnit); - flecs_bootstrap_component(world, EcsUnitPrefix); + for (int i = 0; i < field_count; i ++) { + flecs_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"); + } + } - flecs_bootstrap_tag(world, EcsConstant); - flecs_bootstrap_tag(world, EcsQuantity); + flecs_json_object_pop(buf); +} - ecs_set_hooks(world, EcsMetaType, { .ctor = ecs_default_ctor }); +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; - ecs_set_hooks(world, EcsMetaTypeSerialized, { - .ctor = ecs_default_ctor, - .move = ecs_move(EcsMetaTypeSerialized), - .copy = ecs_copy(EcsMetaTypeSerialized), - .dtor = ecs_dtor(EcsMetaTypeSerialized) - }); + for (int i = 0; i < var_count; i ++) { + const char *var_name = variable_names[i]; + if (skip_variable(var_name)) continue; - ecs_set_hooks(world, EcsStruct, { - .ctor = ecs_default_ctor, - .move = ecs_move(EcsStruct), - .copy = ecs_copy(EcsStruct), - .dtor = ecs_dtor(EcsStruct) - }); + if (!actual_count) { + flecs_json_member(buf, "vars"); + flecs_json_array_push(buf); + actual_count ++; + } - ecs_set_hooks(world, EcsMember, { - .ctor = ecs_default_ctor, - .on_set = member_on_set - }); + ecs_strbuf_list_next(buf); + flecs_json_string(buf, var_name); + } - ecs_set_hooks(world, EcsEnum, { - .ctor = ecs_default_ctor, - .move = ecs_move(EcsEnum), - .copy = ecs_copy(EcsEnum), - .dtor = ecs_dtor(EcsEnum) - }); + if (actual_count) { + flecs_json_array_pop(buf); + } +} - ecs_set_hooks(world, EcsBitmask, { - .ctor = ecs_default_ctor, - .move = ecs_move(EcsBitmask), - .copy = ecs_copy(EcsBitmask), - .dtor = ecs_dtor(EcsBitmask) - }); +static +void serialize_iter_result_ids( + const ecs_world_t *world, + const ecs_iter_t *it, + ecs_strbuf_t *buf) +{ + flecs_json_member(buf, "ids"); + flecs_json_array_push(buf); - ecs_set_hooks(world, EcsUnit, { - .ctor = ecs_default_ctor, - .move = ecs_move(EcsUnit), - .copy = ecs_copy(EcsUnit), - .dtor = ecs_dtor(EcsUnit) - }); + for (int i = 0; i < it->field_count; i ++) { + flecs_json_next(buf); + serialize_id(world, ecs_field_id(it, i + 1), buf); + } - ecs_set_hooks(world, EcsUnitPrefix, { - .ctor = ecs_default_ctor, - .move = ecs_move(EcsUnitPrefix), - .copy = ecs_copy(EcsUnitPrefix), - .dtor = ecs_dtor(EcsUnitPrefix) - }); + flecs_json_array_pop(buf); +} - /* Register triggers to finalize type information from component data */ - ecs_entity_t old_scope = ecs_set_scope( /* Keep meta scope clean */ - world, EcsFlecsInternals); - ecs_observer_init(world, &(ecs_observer_desc_t){ - .filter.terms[0] = { .id = ecs_id(EcsPrimitive), .src.flags = EcsSelf }, - .events = {EcsOnSet}, - .callback = set_primitive - }); +static +void serialize_iter_result_sources( + const ecs_world_t *world, + const ecs_iter_t *it, + ecs_strbuf_t *buf) +{ + flecs_json_member(buf, "sources"); + flecs_json_array_push(buf); - ecs_observer_init(world, &(ecs_observer_desc_t){ - .filter.terms[0] = { .id = ecs_id(EcsMember), .src.flags = EcsSelf }, - .events = {EcsOnSet}, - .callback = set_member - }); + for (int i = 0; i < it->field_count; i ++) { + flecs_json_next(buf); + ecs_entity_t subj = it->sources[i]; + if (subj) { + flecs_json_path(buf, world, subj); + } else { + flecs_json_literal(buf, "0"); + } + } - ecs_observer_init(world, &(ecs_observer_desc_t){ - .filter.terms[0] = { .id = ecs_id(EcsEnum), .src.flags = EcsSelf }, - .events = {EcsOnAdd}, - .callback = add_enum - }); + flecs_json_array_pop(buf); +} - ecs_observer_init(world, &(ecs_observer_desc_t){ - .filter.terms[0] = { .id = ecs_id(EcsBitmask), .src.flags = EcsSelf }, - .events = {EcsOnAdd}, - .callback = add_bitmask - }); +static +void serialize_iter_result_is_set( + const ecs_iter_t *it, + ecs_strbuf_t *buf) +{ + flecs_json_member(buf, "is_set"); + flecs_json_array_push(buf); - ecs_observer_init(world, &(ecs_observer_desc_t){ - .filter.terms[0] = { .id = EcsConstant, .src.flags = EcsSelf }, - .events = {EcsOnAdd}, - .callback = add_constant - }); - - ecs_observer_init(world, &(ecs_observer_desc_t){ - .filter.terms[0] = { .id = ecs_pair(EcsConstant, EcsWildcard), .src.flags = EcsSelf }, - .events = {EcsOnSet}, - .callback = add_constant - }); - - ecs_observer_init(world, &(ecs_observer_desc_t){ - .filter.terms[0] = { .id = ecs_id(EcsArray), .src.flags = EcsSelf }, - .events = {EcsOnSet}, - .callback = set_array - }); - - ecs_observer_init(world, &(ecs_observer_desc_t){ - .filter.terms[0] = { .id = ecs_id(EcsVector), .src.flags = EcsSelf }, - .events = {EcsOnSet}, - .callback = set_vector - }); - - ecs_observer_init(world, &(ecs_observer_desc_t){ - .filter.terms[0] = { .id = ecs_id(EcsUnit), .src.flags = EcsSelf }, - .events = {EcsOnSet}, - .callback = set_unit - }); + for (int i = 0; i < it->field_count; i ++) { + ecs_strbuf_list_next(buf); + if (ecs_field_is_set(it, i + 1)) { + flecs_json_true(buf); + } else { + flecs_json_false(buf); + } + } - ecs_observer_init(world, &(ecs_observer_desc_t){ - .filter.terms[0] = { .id = ecs_id(EcsMetaType), .src.flags = EcsSelf }, - .events = {EcsOnSet}, - .callback = ecs_meta_type_serialized_init - }); + flecs_json_array_pop(buf); +} - ecs_observer_init(world, &(ecs_observer_desc_t){ - .filter.terms[0] = { .id = ecs_id(EcsMetaType), .src.flags = EcsSelf }, - .events = {EcsOnSet}, - .callback = ecs_meta_type_init_default_ctor - }); +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_var_t *variables = it->variables; + int32_t var_count = it->variable_count; + int32_t actual_count = 0; - ecs_observer_init(world, &(ecs_observer_desc_t){ - .filter.terms = { - { .id = ecs_id(EcsUnit) }, - { .id = EcsQuantity } - }, - .events = { EcsMonitor }, - .callback = unit_quantity_monitor - }); - ecs_set_scope(world, old_scope); + for (int i = 0; i < var_count; i ++) { + const char *var_name = variable_names[i]; + if (skip_variable(var_name)) continue; - /* Initialize primitive types */ - #define ECS_PRIMITIVE(world, type, primitive_kind)\ - ecs_entity_init(world, &(ecs_entity_desc_t){\ - .id = ecs_id(ecs_##type##_t),\ - .name = #type,\ - .symbol = #type });\ - ecs_set(world, ecs_id(ecs_##type##_t), EcsPrimitive, {\ - .kind = primitive_kind\ - }); + if (!actual_count) { + flecs_json_member(buf, "vars"); + flecs_json_array_push(buf); + actual_count ++; + } - 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); + ecs_strbuf_list_next(buf); + flecs_json_path(buf, world, variables[i].entity); + } - #undef ECS_PRIMITIVE + if (actual_count) { + flecs_json_array_pop(buf); + } +} - /* Set default child components */ - ecs_add_pair(world, ecs_id(EcsStruct), - EcsDefaultChildComponent, ecs_id(EcsMember)); +static +void serialize_iter_result_variable_labels( + const ecs_world_t *world, + const ecs_iter_t *it, + ecs_strbuf_t *buf) +{ + char **variable_names = it->variable_names; + ecs_var_t *variables = it->variables; + int32_t var_count = it->variable_count; + int32_t actual_count = 0; - ecs_add_pair(world, ecs_id(EcsMember), - EcsDefaultChildComponent, ecs_id(EcsMember)); + for (int i = 0; i < var_count; i ++) { + const char *var_name = variable_names[i]; + if (skip_variable(var_name)) continue; - ecs_add_pair(world, ecs_id(EcsEnum), - EcsDefaultChildComponent, EcsConstant); + if (!actual_count) { + flecs_json_member(buf, "var_labels"); + flecs_json_array_push(buf); + actual_count ++; + } - ecs_add_pair(world, ecs_id(EcsBitmask), - EcsDefaultChildComponent, EcsConstant); + ecs_strbuf_list_next(buf); + flecs_json_label(buf, world, variables[i].entity); + } - /* Relationship properties */ - ecs_add_id(world, EcsQuantity, EcsExclusive); - ecs_add_id(world, EcsQuantity, EcsTag); + if (actual_count) { + flecs_json_array_pop(buf); + } +} - /* Initialize reflection data for meta components */ - ecs_entity_t type_kind = ecs_enum_init(world, &(ecs_enum_desc_t){ - .entity = ecs_entity(world, { .name = "TypeKind" }), - .constants = { - {.name = "PrimitiveType"}, - {.name = "BitmaskType"}, - {.name = "EnumType"}, - {.name = "StructType"}, - {.name = "ArrayType"}, - {.name = "VectorType"} - } - }); +static +void serialize_iter_result_variable_ids( + const ecs_iter_t *it, + ecs_strbuf_t *buf) +{ + char **variable_names = it->variable_names; + ecs_var_t *variables = it->variables; + int32_t var_count = it->variable_count; + int32_t actual_count = 0; - ecs_struct_init(world, &(ecs_struct_desc_t){ - .entity = ecs_id(EcsMetaType), - .members = { - {.name = (char*)"kind", .type = type_kind} - } - }); + for (int i = 0; i < var_count; i ++) { + const char *var_name = variable_names[i]; + if (skip_variable(var_name)) continue; - ecs_entity_t primitive_kind = ecs_enum_init(world, &(ecs_enum_desc_t){ - .entity = ecs_entity(world, { .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 (!actual_count) { + flecs_json_member(buf, "var_ids"); + flecs_json_array_push(buf); + actual_count ++; } - }); - ecs_struct_init(world, &(ecs_struct_desc_t){ - .entity = ecs_id(EcsPrimitive), - .members = { - {.name = (char*)"kind", .type = primitive_kind} - } - }); + ecs_strbuf_list_next(buf); + flecs_json_number(buf, (double)variables[i].entity); + } - ecs_struct_init(world, &(ecs_struct_desc_t){ - .entity = ecs_id(EcsMember), - .members = { - {.name = (char*)"type", .type = ecs_id(ecs_entity_t)}, - {.name = (char*)"count", .type = ecs_id(ecs_i32_t)}, - {.name = (char*)"unit", .type = ecs_id(ecs_entity_t)}, - {.name = (char*)"offset", .type = ecs_id(ecs_i32_t)} - } - }); + if (actual_count) { + flecs_json_array_pop(buf); + } +} - ecs_struct_init(world, &(ecs_struct_desc_t){ - .entity = ecs_id(EcsArray), - .members = { - {.name = (char*)"type", .type = ecs_id(ecs_entity_t)}, - {.name = (char*)"count", .type = ecs_id(ecs_i32_t)}, - } - }); +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; + } - ecs_struct_init(world, &(ecs_struct_desc_t){ - .entity = ecs_id(EcsVector), - .members = { - {.name = (char*)"type", .type = ecs_id(ecs_entity_t)} - } - }); + flecs_json_member(buf, "entities"); + flecs_json_array_push(buf); - ecs_entity_t ut = ecs_struct_init(world, &(ecs_struct_desc_t){ - .entity = ecs_entity(world, { .name = "unit_translation" }), - .members = { - {.name = (char*)"factor", .type = ecs_id(ecs_i32_t)}, - {.name = (char*)"power", .type = ecs_id(ecs_i32_t)} - } - }); + ecs_entity_t *entities = it->entities; - ecs_struct_init(world, &(ecs_struct_desc_t){ - .entity = ecs_id(EcsUnit), - .members = { - {.name = (char*)"symbol", .type = ecs_id(ecs_string_t)}, - {.name = (char*)"prefix", .type = ecs_id(ecs_entity_t)}, - {.name = (char*)"base", .type = ecs_id(ecs_entity_t)}, - {.name = (char*)"over", .type = ecs_id(ecs_entity_t)}, - {.name = (char*)"translation", .type = ut} - } - }); + for (int i = 0; i < count; i ++) { + flecs_json_next(buf); + flecs_json_path(buf, world, entities[i]); + } - ecs_struct_init(world, &(ecs_struct_desc_t){ - .entity = ecs_id(EcsUnitPrefix), - .members = { - {.name = (char*)"symbol", .type = ecs_id(ecs_string_t)}, - {.name = (char*)"translation", .type = ut} - } - }); + flecs_json_array_pop(buf); } -#endif +static +void serialize_iter_result_entity_labels( + const ecs_world_t *world, + const ecs_iter_t *it, + ecs_strbuf_t *buf) +{ + int32_t count = it->count; + if (!it->count) { + return; + } + flecs_json_member(buf, "entity_labels"); + flecs_json_array_push(buf); -#ifdef FLECS_META + ecs_entity_t *entities = it->entities; -ecs_entity_t ecs_primitive_init( - ecs_world_t *world, - const ecs_primitive_desc_t *desc) -{ - ecs_entity_t t = desc->entity; - if (!t) { - t = ecs_new_low_id(world); + for (int i = 0; i < count; i ++) { + flecs_json_next(buf); + flecs_json_label(buf, world, entities[i]); } - ecs_set(world, t, EcsPrimitive, { desc->kind }); - - return t; + flecs_json_array_pop(buf); } -ecs_entity_t ecs_enum_init( - ecs_world_t *world, - const ecs_enum_desc_t *desc) +static +void serialize_iter_result_entity_ids( + const ecs_iter_t *it, + ecs_strbuf_t *buf) { - ecs_entity_t t = desc->entity; - if (!t) { - t = ecs_new_low_id(world); + int32_t count = it->count; + if (!it->count) { + return; } - ecs_add(world, t, EcsEnum); + flecs_json_member(buf, "entity_ids"); + flecs_json_array_push(buf); - ecs_entity_t old_scope = ecs_set_scope(world, t); + ecs_entity_t *entities = it->entities; - 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; - } + for (int i = 0; i < count; i ++) { + flecs_json_next(buf); + flecs_json_number(buf, (double)entities[i]); + } - ecs_entity_t c = ecs_entity_init(world, &(ecs_entity_desc_t){ - .name = m_desc->name - }); + flecs_json_array_pop(buf); +} - if (!m_desc->value) { - ecs_add_id(world, c, EcsConstant); - } else { - ecs_set_pair_object(world, c, EcsConstant, ecs_i32_t, - {m_desc->value}); - } +static +void serialize_iter_result_colors( + const ecs_world_t *world, + const ecs_iter_t *it, + ecs_strbuf_t *buf) +{ + int32_t count = it->count; + if (!it->count) { + return; } - ecs_set_scope(world, old_scope); + flecs_json_member(buf, "colors"); + flecs_json_array_push(buf); - if (i == 0) { - ecs_err("enum '%s' has no constants", ecs_get_name(world, t)); - ecs_delete(world, t); - return 0; + ecs_entity_t *entities = it->entities; + + for (int i = 0; i < count; i ++) { + flecs_json_next(buf); + flecs_json_color(buf, world, entities[i]); } - return t; + flecs_json_array_pop(buf); } -ecs_entity_t ecs_bitmask_init( - ecs_world_t *world, - const ecs_bitmask_desc_t *desc) +static +void serialize_iter_result_values( + const ecs_world_t *world, + const ecs_iter_t *it, + ecs_strbuf_t *buf) { - ecs_entity_t t = desc->entity; - if (!t) { - t = ecs_new_low_id(world); - } + flecs_json_member(buf, "values"); + flecs_json_array_push(buf); - ecs_add(world, t, EcsBitmask); + int32_t i, term_count = it->field_count; + for (i = 0; i < term_count; i ++) { + ecs_strbuf_list_next(buf); - ecs_entity_t old_scope = ecs_set_scope(world, t); + const void *ptr = NULL; + if (it->ptrs) { + ptr = it->ptrs[i]; + } - 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 (!ptr) { + /* No data in column. Append 0 if this is not an optional term */ + if (ecs_field_is_set(it, i + 1)) { + flecs_json_literal(buf, "0"); + continue; + } } - ecs_entity_t c = ecs_entity_init(world, &(ecs_entity_desc_t){ - .name = m_desc->name - }); + if (ecs_field_is_writeonly(it, i + 1)) { + flecs_json_literal(buf, "0"); + continue; + } - if (!m_desc->value) { - ecs_add_id(world, c, EcsConstant); - } else { - ecs_set_pair_object(world, c, EcsConstant, ecs_u32_t, - {m_desc->value}); + /* 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. */ + flecs_json_literal(buf, "0"); + continue; } - } - ecs_set_scope(world, old_scope); + const EcsComponent *comp = ecs_get(world, type, EcsComponent); + if (!comp) { + /* Also odd, typeid but not a component? */ + flecs_json_literal(buf, "0"); + continue; + } - if (i == 0) { - ecs_err("bitmask '%s' has no constants", ecs_get_name(world, t)); - ecs_delete(world, t); - return 0; - } + const EcsMetaTypeSerialized *ser = ecs_get( + world, type, EcsMetaTypeSerialized); + if (!ser) { + /* Not odd, component just has no reflection data */ + flecs_json_literal(buf, "0"); + continue; + } - return t; -} + /* If term is not set, append empty array. This indicates that the term + * could have had data but doesn't */ + if (!ecs_field_is_set(it, i + 1)) { + ecs_assert(ptr == NULL, ECS_INTERNAL_ERROR, NULL); + flecs_json_array_push(buf); + flecs_json_array_pop(buf); + continue; + } -ecs_entity_t ecs_array_init( - ecs_world_t *world, - const ecs_array_desc_t *desc) -{ - ecs_entity_t t = desc->entity; - if (!t) { - t = ecs_new_low_id(world); + if (ecs_field_is_self(it, i + 1)) { + int32_t count = it->count; + 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); + } } - ecs_set(world, t, EcsArray, { - .type = desc->type, - .count = desc->count - }); - - return t; + flecs_json_array_pop(buf); } -ecs_entity_t ecs_vector_init( - ecs_world_t *world, - const ecs_vector_desc_t *desc) +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) { - ecs_entity_t t = desc->entity; - if (!t) { - t = ecs_new_low_id(world); + flecs_json_next(buf); + flecs_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->serialize_ids) { + serialize_iter_result_ids(world, it, buf); } - ecs_set(world, t, EcsVector, { - .type = desc->type - }); + /* Include information on which entity the term is matched with */ + if (!desc || desc->serialize_ids) { + serialize_iter_result_sources(world, it, buf); + } - return t; -} + /* Write variable values for current result */ + if (!desc || desc->serialize_variables) { + serialize_iter_result_variables(world, it, buf); + } -ecs_entity_t ecs_struct_init( - ecs_world_t *world, - const ecs_struct_desc_t *desc) -{ - ecs_entity_t t = desc->entity; - if (!t) { - t = ecs_new_low_id(world); + /* Write labels for variables */ + if (desc && desc->serialize_variable_labels) { + serialize_iter_result_variable_labels(world, it, buf); } - 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; - } + /* Write ids for variables */ + if (desc && desc->serialize_variable_ids) { + serialize_iter_result_variable_ids(it, buf); + } - 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; - } + /* Include information on which terms are set, to support optional terms */ + if (!desc || desc->serialize_is_set) { + serialize_iter_result_is_set(it, buf); + } - ecs_entity_t m = ecs_entity_init(world, &(ecs_entity_desc_t){ - .name = m_desc->name - }); + /* Write entity ids for current result (for queries with This terms) */ + if (!desc || desc->serialize_entities) { + serialize_iter_result_entities(world, it, buf); + } - ecs_set(world, m, EcsMember, { - .type = m_desc->type, - .count = m_desc->count, - .offset = m_desc->offset, - .unit = m_desc->unit - }); + /* Write labels for entities */ + if (desc && desc->serialize_entity_labels) { + serialize_iter_result_entity_labels(world, it, buf); } - ecs_set_scope(world, old_scope); + /* Write ids for entities */ + if (desc && desc->serialize_entity_ids) { + serialize_iter_result_entity_ids(it, buf); + } - if (i == 0) { - ecs_err("struct '%s' has no members", ecs_get_name(world, t)); - ecs_delete(world, t); - return 0; + /* Write colors for entities */ + if (desc && desc->serialize_colors) { + serialize_iter_result_colors(world, it, buf); } - if (!ecs_has(world, t, EcsStruct)) { - /* Invalid members */ - ecs_delete(world, t); - return 0; + /* Serialize component values */ + if (!desc || desc->serialize_values) { + serialize_iter_result_values(world, it, buf); } - return t; + flecs_json_object_pop(buf); } -ecs_entity_t ecs_unit_init( - ecs_world_t *world, - const ecs_unit_desc_t *desc) +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_entity_t t = desc->entity; - if (!t) { - t = ecs_new_low_id(world); + ecs_time_t duration = {0}; + if (desc && desc->measure_eval_duration) { + ecs_time_measure(&duration); } - ecs_entity_t quantity = desc->quantity; - if (quantity) { - if (!ecs_has_id(world, quantity, EcsQuantity)) { - ecs_err("entity '%s' for unit '%s' is not a quantity", - ecs_get_name(world, quantity), ecs_get_name(world, t)); - goto error; - } + flecs_json_object_push(buf); - ecs_add_pair(world, t, EcsQuantity, desc->quantity); - } else { - ecs_remove_pair(world, t, EcsQuantity, EcsWildcard); + /* Serialize component ids of the terms (usually provided by query) */ + if (!desc || desc->serialize_term_ids) { + serialize_iter_ids(world, it, buf); } - EcsUnit *value = ecs_get_mut(world, t, EcsUnit); - value->base = desc->base; - value->over = desc->over; - value->translation = desc->translation; - value->prefix = desc->prefix; - ecs_os_strset(&value->symbol, desc->symbol); - - if (!flecs_unit_validate(world, t, value)) { - goto error; + /* Serialize type info if enabled */ + if (desc && desc->serialize_type_info) { + serialize_type_info(world, it, buf); } - ecs_modified(world, t, EcsUnit); + /* Serialize variable names, if iterator has any */ + serialize_iter_variables(it, buf); - return t; -error: - if (t) { - ecs_delete(world, t); + /* Serialize results */ + flecs_json_member(buf, "results"); + flecs_json_array_push(buf); + + /* Use instancing for improved performance */ + ECS_BIT_SET(it->flags, EcsIterIsInstanced); + + ecs_iter_next_action_t next = it->next; + while (next(it)) { + serialize_iter_result(world, it, buf, desc); } - return 0; -} -ecs_entity_t ecs_unit_prefix_init( - ecs_world_t *world, - const ecs_unit_prefix_desc_t *desc) -{ - ecs_entity_t t = desc->entity; - if (!t) { - t = ecs_new_low_id(world); + flecs_json_array_pop(buf); + + if (desc && desc->measure_eval_duration) { + double dt = ecs_time_measure(&duration); + flecs_json_member(buf, "eval_duration"); + flecs_json_number(buf, dt); } - ecs_set(world, t, EcsUnitPrefix, { - .symbol = (char*)desc->symbol, - .translation = desc->translation - }); + flecs_json_object_pop(buf); - return t; + return 0; } -ecs_entity_t ecs_quantity_init( - ecs_world_t *world, - const ecs_entity_desc_t *desc) +char* ecs_iter_to_json( + const ecs_world_t *world, + ecs_iter_t *it, + const ecs_iter_to_json_desc_t *desc) { - ecs_entity_t t = ecs_entity_init(world, desc); - if (!t) { - return 0; - } + ecs_strbuf_t buf = ECS_STRBUF_INIT; - ecs_add_id(world, t, EcsQuantity); + if (ecs_iter_to_json_buf(world, it, &buf, desc)) { + ecs_strbuf_reset(&buf); + return NULL; + } - return t; + return ecs_strbuf_get(&buf); } #endif -#ifdef FLECS_MODULE +#ifdef FLECS_JSON -#include +static +int json_typeinfo_ser_type( + const ecs_world_t *world, + ecs_entity_t type, + ecs_strbuf_t *buf); -char* ecs_module_path_from_c( - const char *c_name) +static +int json_typeinfo_ser_primitive( + ecs_primitive_kind_t kind, + ecs_strbuf_t *str) { - ecs_strbuf_t str = ECS_STRBUF_INIT; - const char *ptr; - char ch; - - 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_strbuf_appendstrn(&str, &ch, 1); + switch(kind) { + case EcsBool: + flecs_json_string(str, "bool"); + break; + case EcsChar: + case EcsString: + flecs_json_string(str, "text"); + break; + case EcsByte: + flecs_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: + flecs_json_string(str, "int"); + break; + case EcsF32: + case EcsF64: + flecs_json_string(str, "float"); + break; + case EcsEntity: + flecs_json_string(str, "entity"); + break; + default: + return -1; } - return ecs_strbuf_get(&str); + return 0; } -ecs_entity_t ecs_import( - ecs_world_t *world, - ecs_module_action_t module, - const char *module_name) +static +void json_typeinfo_ser_constants( + const ecs_world_t *world, + ecs_entity_t type, + ecs_strbuf_t *str) { - ecs_check(!(world->flags & EcsWorldReadonly), - ECS_INVALID_WHILE_READONLY, NULL); - - ecs_entity_t old_scope = ecs_set_scope(world, 0); - const char *old_name_prefix = world->info.name_prefix; - - 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(); - - /* Load module */ - module(world); - - /* 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_iter_t it = ecs_term_iter(world, &(ecs_term_t) { + .id = ecs_pair(EcsChildOf, type) + }); - ecs_log_pop(); + while (ecs_term_next(&it)) { + int32_t i, count = it.count; + for (i = 0; i < count; i ++) { + flecs_json_next(str); + flecs_json_string(str, ecs_get_name(world, it.entities[i])); + } } - - /* Restore to previous state */ - ecs_set_scope(world, old_scope); - world->info.name_prefix = old_name_prefix; - - return e; -error: - return 0; } -ecs_entity_t ecs_import_c( - ecs_world_t *world, - ecs_module_action_t module, - const char *c_name) +static +void json_typeinfo_ser_enum( + const ecs_world_t *world, + ecs_entity_t type, + ecs_strbuf_t *str) { - char *name = ecs_module_path_from_c(c_name); - ecs_entity_t e = ecs_import(world, module, name); - ecs_os_free(name); - return e; + ecs_strbuf_list_appendstr(str, "\"enum\""); + json_typeinfo_ser_constants(world, type, str); } -ecs_entity_t ecs_import_from_library( - ecs_world_t *world, - const char *library_name, - const char *module_name) +static +void json_typeinfo_ser_bitmask( + const ecs_world_t *world, + ecs_entity_t type, + ecs_strbuf_t *str) { - ecs_check(library_name != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_strbuf_list_appendstr(str, "\"bitmask\""); + json_typeinfo_ser_constants(world, type, str); +} - char *import_func = (char*)module_name; /* safe */ - char *module = (char*)module_name; +static +int json_typeinfo_ser_array( + const ecs_world_t *world, + ecs_entity_t elem_type, + int32_t count, + ecs_strbuf_t *str) +{ + ecs_strbuf_list_appendstr(str, "\"array\""); - 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; + flecs_json_next(str); + if (json_typeinfo_ser_type(world, elem_type, str)) { + goto error; } - /* 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 ++; - } - } - } - - *bptr = '\0'; - - module = ecs_os_strdup(import_func); - ecs_assert(module != NULL, ECS_OUT_OF_MEMORY, NULL); - - ecs_os_strcat(bptr, "Import"); - } + ecs_strbuf_list_append(str, "%u", count); + return 0; +error: + return -1; +} - 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); +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; } - 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); + return 0; +error: + return -1; +} - if (module != module_name) { - ecs_os_free(module); - } +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); - return 0; - } else { - ecs_trace("library '%s' ('%s') loaded", - library_name, library_filename); - } + ecs_strbuf_list_appendstr(str, "\"vector\""); - 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); + flecs_json_next(str); + if (json_typeinfo_ser_type(world, arr->type, str)) { + goto error; } - /* Do not free id, as it will be stored as the component identifier */ - ecs_entity_t result = ecs_import(world, action, module); + return 0; +error: + return -1; +} - if (import_func != module_name) { - ecs_os_free(import_func); - } +/* Serialize unit information */ +static +int json_typeinfo_ser_unit( + const ecs_world_t *world, + ecs_strbuf_t *str, + ecs_entity_t unit) +{ + flecs_json_member(str, "unit"); + flecs_json_path(str, world, unit); - if (module != module_name) { - ecs_os_free(module); + const EcsUnit *uptr = ecs_get(world, unit, EcsUnit); + if (uptr) { + if (uptr->symbol) { + flecs_json_member(str, "symbol"); + flecs_json_string(str, uptr->symbol); + } + ecs_entity_t quantity = ecs_get_target(world, unit, EcsQuantity, 0); + if (quantity) { + flecs_json_member(str, "quantity"); + flecs_json_path(str, world, quantity); + } } - ecs_os_free(library_filename); - - return result; -error: return 0; } -ecs_entity_t ecs_module_init( - ecs_world_t *world, - const char *c_name, - const ecs_component_desc_t *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) { - ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_poly_assert(world, ecs_world_t); + flecs_json_array_push(str); - ecs_entity_t e = desc->entity; - if (!e) { - char *module_path = ecs_module_path_from_c(c_name); - e = ecs_new_from_fullpath(world, module_path); - ecs_set_symbol(world, e, module_path); - ecs_os_free(module_path); + 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( + flecs_json_op_to_primitive_kind(op->kind), str)) + { + /* Unknown operation */ + ecs_throw(ECS_INTERNAL_ERROR, NULL); + return -1; + } + break; } - - ecs_add_id(world, e, EcsModule); - ecs_component_desc_t private_desc = *desc; - private_desc.entity = e; + ecs_entity_t unit = op->unit; + if (unit) { + flecs_json_next(str); + flecs_json_next(str); - if (desc->type.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; + flecs_json_object_push(str); + json_typeinfo_ser_unit(world, str, unit); + flecs_json_object_pop(str); } - return e; -error: + flecs_json_array_pop(str); + return 0; +error: + return -1; } -#endif - - -#ifdef FLECS_META_C - -#include - -#define ECS_META_IDENTIFIER_LENGTH (256) - -#define ecs_meta_error(ctx, ptr, ...)\ - ecs_parser_error((ctx)->name, (ctx)->desc, ptr - (ctx)->desc, __VA_ARGS__); - -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; - -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 meta_member_t { - meta_type_t type; - ecs_meta_token_t name; - int64_t count; - bool is_partial; -} meta_member_t; - -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; - +/* Iterate over a slice of the type ops array */ 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; +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]; - while ((ch = *ptr)) { - if (ch == '(' || ch == '<') { - stack[sp] = ch; + if (op != ops) { + if (op->name) { + flecs_json_member(str, op->name); + } - 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; + int32_t elem_count = op->count; + if (elem_count > 1 && op != ops) { + flecs_json_array_push(str); + json_typeinfo_ser_array(world, op->type, op->count, str); + flecs_json_array_pop(str); + i += op->op_count - 1; + continue; } } - - ptr ++; - - if (!sp) { + + switch(op->kind) { + case EcsOpPush: + flecs_json_object_push(str); + break; + case EcsOpPop: + flecs_json_object_pop(str); + break; + default: + if (json_typeinfo_ser_type_op(world, op, str)) { + goto error; + } break; } } - return ptr; + return 0; error: - return NULL; + return -1; } static -const char* parse_c_digit( - const char *ptr, - int64_t *value_out) +int json_typeinfo_ser_type( + const ecs_world_t *world, + ecs_entity_t type, + ecs_strbuf_t *buf) { - char token[24]; - ptr = ecs_parse_eol_and_whitespace(ptr); - ptr = ecs_parse_digit(ptr, token); - if (!ptr) { - goto error; + const EcsComponent *comp = ecs_get(world, type, EcsComponent); + if (!comp) { + ecs_strbuf_appendstr(buf, "0"); + return 0; } - *value_out = strtol(token, NULL, 0); + const EcsMetaTypeSerialized *ser = ecs_get( + world, type, EcsMetaTypeSerialized); + if (!ser) { + ecs_strbuf_appendstr(buf, "0"); + return 0; + } - return ecs_parse_eol_and_whitespace(ptr); -error: - return NULL; + 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); } -static -const char* parse_c_identifier( - const char *ptr, - char *buff, - char *params, - meta_parse_ctx_t *ctx) +int ecs_type_info_to_json_buf( + const ecs_world_t *world, + ecs_entity_t type, + ecs_strbuf_t *buf) { - 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; - - if (params) { - params[0] = '\0'; - } + return json_typeinfo_ser_type(world, type, buf); +} - /* Ignore whitespaces */ - ptr = ecs_parse_eol_and_whitespace(ptr); +char* ecs_type_info_to_json( + const ecs_world_t *world, + ecs_entity_t type) +{ + ecs_strbuf_t str = ECS_STRBUF_INIT; - if (!isalpha(*ptr)) { - ecs_meta_error(ctx, ptr, - "invalid identifier (starts with '%c')", *ptr); - goto error; + if (ecs_type_info_to_json_buf(world, type, &str) != 0) { + ecs_strbuf_reset(&str); + return NULL; } - while ((ch = *ptr) && !isspace(ch) && ch != ';' && ch != ',' && ch != ')' && ch != '>' && ch != '}') { - /* Type definitions can contain macros 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'; + return ecs_strbuf_get(&str); +} - ptr = end; - } else { - *bptr = ch; - bptr ++; - ptr ++; - } - } +#endif - *bptr = '\0'; - if (!ch) { - ecs_meta_error(ctx, ptr, "unexpected end of token"); - goto error; - } - return ptr; -error: - return NULL; -} +#ifdef FLECS_JSON -static -const char * meta_open_scope( +const char* ecs_parse_json( + const ecs_world_t *world, const char *ptr, - meta_parse_ctx_t *ctx) + ecs_entity_t type, + void *data_out, + const ecs_parse_json_desc_t *desc) { - /* Skip initial whitespaces */ - ptr = ecs_parse_eol_and_whitespace(ptr); + char token[ECS_MAX_TOKEN_SIZE]; + int depth = 0; - /* 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; - } + const char *name = NULL; + const char *expr = NULL; - ptr ++; - ptr = ecs_parse_eol_and_whitespace(ptr); + ptr = ecs_parse_fluff(ptr, NULL); + + ecs_meta_cursor_t cur = ecs_meta_cursor(world, type, data_out); + if (cur.valid == false) { + return NULL; } - /* Is this the end of the type definition? */ - if (!*ptr) { - ecs_meta_error(ctx, ptr, "missing '}' at end of struct definition"); - goto error; - } + if (desc) { + name = desc->name; + expr = desc->expr; + } - /* 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; + while ((ptr = ecs_parse_expr_token(name, expr, ptr, token))) { + + ptr = ecs_parse_fluff(ptr, NULL); + + if (!ecs_os_strcmp(token, "{")) { + depth ++; + if (ecs_meta_push(&cur) != 0) { + goto error; + } + + if (ecs_meta_is_collection(&cur)) { + ecs_parser_error(name, expr, ptr - expr, "expected '['"); + return NULL; + } } - return NULL; - } - return ptr; -error: - return NULL; -} + else if (!ecs_os_strcmp(token, "}")) { + depth --; -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; - } + if (ecs_meta_is_collection(&cur)) { + ecs_parser_error(name, expr, ptr - expr, "expected ']'"); + return NULL; + } - token->is_value_set = false; + if (ecs_meta_pop(&cur) != 0) { + goto error; + } + } - /* Parse token, constant identifier */ - ptr = parse_c_identifier(ptr, token->name, NULL, ctx); - if (!ptr) { - return NULL; - } + else if (!ecs_os_strcmp(token, "[")) { + depth ++; + if (ecs_meta_push(&cur) != 0) { + goto error; + } - ptr = ecs_parse_eol_and_whitespace(ptr); - if (!ptr) { - return NULL; - } + if (!ecs_meta_is_collection(&cur)) { + ecs_parser_error(name, expr, ptr - expr, "expected '{'"); + return NULL; + } + } - /* Explicit value assignment */ - if (*ptr == '=') { - int64_t value = 0; - ptr = parse_c_digit(ptr + 1, &value); - token->value = value; - token->is_value_set = true; - } + else if (!ecs_os_strcmp(token, "]")) { + depth --; - /* Expect a ',' or '}' */ - if (*ptr != ',' && *ptr != '}') { - ecs_meta_error(ctx, ptr, "missing , after enum constant"); - goto error; - } + if (!ecs_meta_is_collection(&cur)) { + ecs_parser_error(name, expr, ptr - expr, "expected '}'"); + return NULL; + } - if (*ptr == ',') { - return ptr + 1; - } else { - return ptr; - } -error: - return NULL; -} + if (ecs_meta_pop(&cur) != 0) { + goto error; + } + } -static -const char* meta_parse_type( - const char *ptr, - meta_type_t *token, - meta_parse_ctx_t *ctx) -{ - token->is_ptr = false; - token->is_const = false; + else if (!ecs_os_strcmp(token, ",")) { + if (ecs_meta_next(&cur) != 0) { + goto error; + } + } - ptr = ecs_parse_eol_and_whitespace(ptr); + else if (!ecs_os_strcmp(token, "null")) { + if (ecs_meta_set_null(&cur) != 0) { + goto error; + } + } - /* Parse token, expect type identifier or ECS_PROPERTY */ - ptr = parse_c_identifier(ptr, token->type, token->params, ctx); - if (!ptr) { - goto error; - } + else if (token[0] == '\"') { + if (ptr[0] == ':') { + /* Member assignment */ + ptr ++; - if (!strcmp(token->type, "ECS_PRIVATE")) { - /* Members from this point are not stored in metadata */ - ptr += ecs_os_strlen(ptr); - goto done; - } + /* 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 token is const, set const flag and continue parsing type */ - if (!strcmp(token->type, "const")) { - token->is_const = true; + if (ecs_meta_member(&cur, token + 1) != 0) { + goto error; + } + } else { + if (ecs_meta_set_string_literal(&cur, token) != 0) { + goto error; + } + } + } - /* Parse type after const */ - ptr = parse_c_identifier(ptr + 1, token->type, token->params, ctx); - } + else { + if (ecs_meta_set_string(&cur, token) != 0) { + goto error; + } + } - /* Check if type is a pointer */ - ptr = ecs_parse_eol_and_whitespace(ptr); - if (*ptr == '*') { - token->is_ptr = true; - ptr ++; + if (!depth) { + break; + } } -done: return ptr; error: return NULL; } -static -const char* meta_parse_member( - const char *ptr, - meta_member_t *token, - meta_parse_ctx_t *ctx) -{ - ptr = meta_open_scope(ptr, ctx); - if (!ptr) { - return NULL; - } - - token->count = 1; - token->is_partial = false; +#endif - /* Parse member type */ - ptr = meta_parse_type(ptr, &token->type, ctx); - if (!ptr) { - token->is_partial = true; - goto error; - } - /* Next token is the identifier */ - ptr = parse_c_identifier(ptr, token->name, NULL, ctx); - if (!ptr) { - goto error; - } +#ifdef FLECS_REST - /* Skip whitespace between member and [ or ; */ - ptr = ecs_parse_eol_and_whitespace(ptr); +typedef struct { + ecs_world_t *world; + ecs_entity_t entity; + ecs_http_server_t *srv; + int32_t rc; +} ecs_rest_ctx_t; - /* 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 ECS_COPY(EcsRest, dst, src, { + ecs_rest_ctx_t *impl = src->impl; + if (impl) { + impl->rc ++; } - 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; - - } else if (array_end - array_start == 0) { - ecs_meta_error(ctx, ptr, "dynamic size arrays are not supported"); - goto error; - } + ecs_os_strset(&dst->ipaddr, src->ipaddr); + dst->port = src->port; + dst->impl = impl; +}) - token->count = atoi(array_start + 1); +static ECS_MOVE(EcsRest, dst, src, { + *dst = *src; + src->ipaddr = NULL; + src->impl = NULL; +}) - 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'; +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); +}) - /* Expect a ; */ - if (*ptr != ';') { - ecs_meta_error(ctx, ptr, "missing ; after member declaration"); - goto error; - } +static char *rest_last_err; - return ptr + 1; -error: - return NULL; +static +void flecs_rest_capture_log( + int32_t level, + const char *file, + int32_t line, + const char *msg) +{ + (void)file; (void)line; + + if (!rest_last_err && level < 0) { + rest_last_err = ecs_os_strdup(msg); + } } static -int meta_parse_desc( - const char *ptr, - meta_params_t *token, - meta_parse_ctx_t *ctx) -{ - 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; - } - - ptr ++; - - /* Parse type identifier */ - ptr = meta_parse_type(ptr, &token->type, ctx); - if (!ptr) { - goto error; - } +char* flecs_rest_get_captured_log(void) { + char *result = rest_last_err; + rest_last_err = NULL; + return result; +} - ptr = ecs_parse_eol_and_whitespace(ptr); +static +void flecs_reply_verror( + ecs_http_reply_t *reply, + const char *fmt, + va_list args) +{ + ecs_strbuf_appendstr(&reply->body, "{\"error\":\""); + ecs_strbuf_vappend(&reply->body, fmt, args); + ecs_strbuf_appendstr(&reply->body, "\"}"); +} - /* 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; - } +static +void flecs_reply_error( + ecs_http_reply_t *reply, + const char *fmt, + ...) +{ + va_list args; + va_start(args, fmt); + flecs_reply_verror(reply, fmt, args); + va_end(args); +} - token->count = value; - token->is_fixed_size = true; +static +void flecs_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 { - 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; + value_out[0] = false; } } - - if (*ptr != ')' && *ptr != '>') { - ecs_meta_error(ctx, ptr, - "expected ')' at end of collection definition"); - goto error; - } - - return 0; -error: - return -1; } 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) +void flecs_rest_int_param( + const ecs_http_request_t *req, + const char *name, + int32_t *value_out) { - 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; - } - if (!params.is_fixed_size) { - ecs_meta_error(ctx, params_decl, "missing size for array"); - goto error; - } - - if (!params.count) { - ecs_meta_error(ctx, params_decl, "invalid array size"); - goto error; - } - - 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); + const char *value = ecs_http_get_param(req, name); + if (value) { + *value_out = atoi(value); } +} - if (!e) { - e = ecs_new_id(world); +static +void flecs_rest_string_param( + const ecs_http_request_t *req, + const char *name, + char **value_out) +{ + const char *value = ecs_http_get_param(req, name); + if (value) { + *value_out = (char*)value; } +} - ecs_check(params.count <= INT32_MAX, ECS_INVALID_PARAMETER, NULL); +static +void flecs_rest_parse_json_ser_entity_params( + ecs_entity_to_json_desc_t *desc, + const ecs_http_request_t *req) +{ + flecs_rest_bool_param(req, "path", &desc->serialize_path); + flecs_rest_bool_param(req, "label", &desc->serialize_label); + flecs_rest_bool_param(req, "brief", &desc->serialize_brief); + flecs_rest_bool_param(req, "link", &desc->serialize_link); + flecs_rest_bool_param(req, "color", &desc->serialize_color); + flecs_rest_bool_param(req, "id_labels", &desc->serialize_id_labels); + flecs_rest_bool_param(req, "base", &desc->serialize_base); + flecs_rest_bool_param(req, "values", &desc->serialize_values); + flecs_rest_bool_param(req, "private", &desc->serialize_private); + flecs_rest_bool_param(req, "type_info", &desc->serialize_type_info); +} - return ecs_set(world, e, EcsArray, { element_type, (int32_t)params.count }); -error: - return 0; +static +void flecs_rest_parse_json_ser_iter_params( + ecs_iter_to_json_desc_t *desc, + const ecs_http_request_t *req) +{ + flecs_rest_bool_param(req, "term_ids", &desc->serialize_term_ids); + flecs_rest_bool_param(req, "ids", &desc->serialize_ids); + flecs_rest_bool_param(req, "sources", &desc->serialize_sources); + flecs_rest_bool_param(req, "variables", &desc->serialize_variables); + flecs_rest_bool_param(req, "is_set", &desc->serialize_is_set); + flecs_rest_bool_param(req, "values", &desc->serialize_values); + flecs_rest_bool_param(req, "entities", &desc->serialize_entities); + flecs_rest_bool_param(req, "entity_labels", &desc->serialize_entity_labels); + flecs_rest_bool_param(req, "entity_ids", &desc->serialize_entity_ids); + flecs_rest_bool_param(req, "variable_labels", &desc->serialize_variable_labels); + flecs_rest_bool_param(req, "variable_ids", &desc->serialize_variable_ids); + flecs_rest_bool_param(req, "colors", &desc->serialize_colors); + flecs_rest_bool_param(req, "duration", &desc->measure_eval_duration); + flecs_rest_bool_param(req, "type_info", &desc->serialize_type_info); } static -ecs_entity_t meta_lookup_vector( +bool flecs_rest_reply_entity( ecs_world_t *world, - ecs_entity_t e, - const char *params_decl, - meta_parse_ctx_t *ctx) + const ecs_http_request_t* req, + ecs_http_reply_t *reply) { - 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; - } - - if (params.is_key_value) { - ecs_meta_error(ctx, params_decl, - "unexpected key value parameters for vector"); - goto error; - } - - ecs_entity_t element_type = meta_lookup( - world, ¶ms.type, params_decl, 1, ¶m_ctx); + char *path = &req->path[7]; + ecs_dbg_2("rest: request entity '%s'", path); + ecs_entity_t e = ecs_lookup_path_w_sep( + world, 0, path, "/", NULL, false); if (!e) { - e = ecs_new_id(world); + ecs_dbg_2("rest: entity '%s' not found", path); + flecs_reply_error(reply, "entity '%s' not found", path); + reply->code = 404; + return true; } - return ecs_set(world, e, EcsVector, { element_type }); -error: - return 0; + ecs_entity_to_json_desc_t desc = ECS_ENTITY_TO_JSON_INIT; + flecs_rest_parse_json_ser_entity_params(&desc, req); + + ecs_entity_to_json_buf(world, e, &reply->body, &desc); + return true; } static -ecs_entity_t meta_lookup_bitmask( +bool flecs_rest_reply_query( ecs_world_t *world, - ecs_entity_t e, - const char *params_decl, - meta_parse_ctx_t *ctx) + const ecs_http_request_t* req, + ecs_http_reply_t *reply) { - (void)e; + 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; + } - meta_parse_ctx_t param_ctx = { - .name = ctx->name, - .desc = params_decl - }; + ecs_dbg_2("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_ = flecs_rest_capture_log; - meta_params_t params; - if (meta_parse_desc(params_decl, ¶ms, ¶m_ctx)) { - goto error; - } + ecs_rule_t *r = ecs_rule_init(world, &(ecs_filter_desc_t){ + .expr = q + }); + if (!r) { + char *err = flecs_rest_get_captured_log(); + char *escaped_err = ecs_astresc('"', err); + flecs_reply_error(reply, escaped_err); + reply->code = 400; /* bad request */ + ecs_os_free(escaped_err); + ecs_os_free(err); + } else { + ecs_iter_to_json_desc_t desc = ECS_ITER_TO_JSON_INIT; + flecs_rest_parse_json_ser_iter_params(&desc, req); - if (params.is_key_value) { - ecs_meta_error(ctx, params_decl, - "unexpected key value parameters for bitmask"); - goto error; - } + int32_t offset = 0; + int32_t limit = 1000; - if (params.is_fixed_size) { - ecs_meta_error(ctx, params_decl, - "unexpected size for bitmask"); - goto error; - } + flecs_rest_int_param(req, "offset", &offset); + flecs_rest_int_param(req, "limit", &limit); - ecs_entity_t bitmask_type = meta_lookup( - world, ¶ms.type, params_decl, 1, ¶m_ctx); - ecs_check(bitmask_type != 0, ECS_INVALID_PARAMETER, NULL); + ecs_iter_t it = ecs_rule_iter(world, r); + ecs_iter_t pit = ecs_page_iter(&it, offset, limit); + ecs_iter_to_json_buf(world, &pit, &reply->body, &desc); + ecs_rule_fini(r); + } -#ifndef FLECS_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 + ecs_os_api.log_ = prev_log_; + ecs_log_enable_colors(prev_color); - return bitmask_type; -error: - return 0; + return true; } +#ifdef FLECS_MONITOR + 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) +void flecs_rest_array_append( + ecs_strbuf_t *reply, + const char *field, + const ecs_float_t *values, + int32_t t) { - 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; + ecs_strbuf_list_append(reply, "\"%s\"", field); + ecs_strbuf_appendch(reply, ':'); + ecs_strbuf_list_push(reply, "[", ","); - /* Parse vector type */ - if (!token->is_ptr) { - if (!ecs_os_strcmp(typename, "ecs_array")) { - type = meta_lookup_array(world, 0, token->params, ctx); + int32_t i; + for (i = t + 1; i <= (t + ECS_STAT_WINDOW); i ++) { + int32_t index = i % ECS_STAT_WINDOW; + ecs_strbuf_list_next(reply); + ecs_strbuf_appendflt(reply, (double)values[index], '"'); + } - } else if (!ecs_os_strcmp(typename, "ecs_vector") || - !ecs_os_strcmp(typename, "flecs::vector")) - { - type = meta_lookup_vector(world, 0, token->params, ctx); + ecs_strbuf_list_pop(reply, "]"); +} - } else if (!ecs_os_strcmp(typename, "flecs::bitmask")) { - type = meta_lookup_bitmask(world, 0, token->params, ctx); +static +void flecs_rest_gauge_append( + ecs_strbuf_t *reply, + const ecs_metric_t *m, + const char *field, + int32_t t) +{ + ecs_strbuf_list_append(reply, "\"%s\"", field); + ecs_strbuf_appendch(reply, ':'); + ecs_strbuf_list_push(reply, "{", ","); - } else if (!ecs_os_strcmp(typename, "flecs::byte")) { - type = ecs_id(ecs_byte_t); + flecs_rest_array_append(reply, "avg", m->gauge.avg, t); + flecs_rest_array_append(reply, "min", m->gauge.min, t); + flecs_rest_array_append(reply, "max", m->gauge.max, t); - } else if (!ecs_os_strcmp(typename, "char")) { - type = ecs_id(ecs_char_t); + ecs_strbuf_list_pop(reply, "}"); +} - } else if (!ecs_os_strcmp(typename, "bool") || - !ecs_os_strcmp(typename, "_Bool")) - { - type = ecs_id(ecs_bool_t); +static +void flecs_rest_counter_append( + ecs_strbuf_t *reply, + const ecs_metric_t *m, + const char *field, + int32_t t) +{ + flecs_rest_gauge_append(reply, m, field, t); +} - } 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); +#define ECS_GAUGE_APPEND_T(reply, s, field, t)\ + flecs_rest_gauge_append(reply, &(s)->field, #field, 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); +#define ECS_COUNTER_APPEND_T(reply, s, field, t)\ + flecs_rest_counter_append(reply, &(s)->field, #field, t) - } 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); +#define ECS_GAUGE_APPEND(reply, s, field)\ + ECS_GAUGE_APPEND_T(reply, s, field, (s)->t) - } else if (!ecs_os_strcmp(typename, "ecs_entity_t")) { - type = ecs_id(ecs_entity_t); +#define ECS_COUNTER_APPEND(reply, s, field)\ + ECS_COUNTER_APPEND_T(reply, s, field, (s)->t) - } 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"; - } +static +void flecs_world_stats_to_json( + ecs_strbuf_t *reply, + const EcsWorldStats *monitor_stats) +{ + const ecs_world_stats_t *stats = &monitor_stats->stats; - type = ecs_lookup_symbol(world, typename, true); - } + ecs_strbuf_list_push(reply, "{", ","); + ECS_GAUGE_APPEND(reply, stats, entity_count); + ECS_GAUGE_APPEND(reply, stats, entity_not_alive_count); + ECS_GAUGE_APPEND(reply, stats, id_count); + ECS_GAUGE_APPEND(reply, stats, tag_id_count); + ECS_GAUGE_APPEND(reply, stats, component_id_count); + ECS_GAUGE_APPEND(reply, stats, pair_id_count); + ECS_GAUGE_APPEND(reply, stats, wildcard_id_count); + ECS_GAUGE_APPEND(reply, stats, component_count); + ECS_COUNTER_APPEND(reply, stats, id_create_count); + ECS_COUNTER_APPEND(reply, stats, id_delete_count); + ECS_GAUGE_APPEND(reply, stats, table_count); + ECS_GAUGE_APPEND(reply, stats, empty_table_count); + ECS_GAUGE_APPEND(reply, stats, tag_table_count); + ECS_GAUGE_APPEND(reply, stats, trivial_table_count); + ECS_GAUGE_APPEND(reply, stats, table_record_count); + ECS_GAUGE_APPEND(reply, stats, table_storage_count); + ECS_COUNTER_APPEND(reply, stats, table_create_count); + ECS_COUNTER_APPEND(reply, stats, table_delete_count); + ECS_GAUGE_APPEND(reply, stats, query_count); + ECS_GAUGE_APPEND(reply, stats, observer_count); + ECS_GAUGE_APPEND(reply, stats, system_count); + ECS_COUNTER_APPEND(reply, stats, new_count); + ECS_COUNTER_APPEND(reply, stats, bulk_new_count); + ECS_COUNTER_APPEND(reply, stats, delete_count); + ECS_COUNTER_APPEND(reply, stats, clear_count); + ECS_COUNTER_APPEND(reply, stats, add_count); + ECS_COUNTER_APPEND(reply, stats, remove_count); + ECS_COUNTER_APPEND(reply, stats, set_count); + ECS_COUNTER_APPEND(reply, stats, discard_count); + ECS_COUNTER_APPEND(reply, stats, world_time_total_raw); + ECS_COUNTER_APPEND(reply, stats, world_time_total); + ECS_COUNTER_APPEND(reply, stats, frame_time_total); + ECS_COUNTER_APPEND(reply, stats, system_time_total); + ECS_COUNTER_APPEND(reply, stats, merge_time_total); + ECS_GAUGE_APPEND(reply, stats, fps); + ECS_GAUGE_APPEND(reply, stats, delta_time); + ECS_COUNTER_APPEND(reply, stats, frame_count_total); + ECS_COUNTER_APPEND(reply, stats, merge_count_total); + ECS_COUNTER_APPEND(reply, stats, pipeline_build_count_total); + ECS_COUNTER_APPEND(reply, stats, systems_ran_frame); + ECS_COUNTER_APPEND(reply, stats, alloc_count); + ECS_COUNTER_APPEND(reply, stats, realloc_count); + ECS_COUNTER_APPEND(reply, stats, free_count); + ECS_GAUGE_APPEND(reply, stats, outstanding_alloc_count); + ecs_strbuf_list_pop(reply, "}"); +} - if (count != 1) { - ecs_check(count <= INT32_MAX, ECS_INVALID_PARAMETER, NULL); +static +void flecs_system_stats_to_json( + ecs_world_t *world, + ecs_strbuf_t *reply, + ecs_entity_t system, + const ecs_system_stats_t *stats) +{ + ecs_strbuf_list_push(reply, "{", ","); - type = ecs_set(world, 0, EcsArray, {type, (int32_t)count}); - } + char *path = ecs_get_fullpath(world, system); + ecs_strbuf_list_append(reply, "\"name\":\"%s\"", path); + ecs_os_free(path); - if (!type) { - ecs_meta_error(ctx, ptr, "unknown type '%s'", typename); - goto error; + if (!stats->task) { + ECS_GAUGE_APPEND(reply, &stats->query, matched_table_count); + ECS_GAUGE_APPEND(reply, &stats->query, matched_entity_count); } - return type; -error: - return 0; + ECS_COUNTER_APPEND_T(reply, stats, time_spent, stats->query.t); + ecs_strbuf_list_pop(reply, "}"); } static -int meta_parse_struct( +void flecs_pipeline_stats_to_json( ecs_world_t *world, - ecs_entity_t t, - const char *desc) + ecs_strbuf_t *reply, + const EcsPipelineStats *stats) { - const char *ptr = desc; - const char *name = ecs_get_name(world, t); - - meta_member_t token; - meta_parse_ctx_t ctx = { - .name = name, - .desc = ptr - }; + ecs_strbuf_list_push(reply, "[", ","); - ecs_entity_t old_scope = ecs_set_scope(world, t); - - 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 - }); + int32_t i, count = ecs_vector_count(stats->stats.systems); + ecs_entity_t *ids = ecs_vector_first(stats->stats.systems, ecs_entity_t); + for (i = 0; i < count; i ++) { + ecs_entity_t id = ids[i]; + + ecs_strbuf_list_next(reply); - ecs_entity_t type = meta_lookup( - world, &token.type, ptr, 1, &ctx); - if (!type) { - goto error; + if (id) { + ecs_system_stats_t *sys_stats = ecs_map_get( + &stats->stats.system_stats, ecs_system_stats_t, id); + flecs_system_stats_to_json(world, reply, id, sys_stats); + } else { + /* Sync point */ + ecs_strbuf_list_push(reply, "{", ","); + ecs_strbuf_list_pop(reply, "}"); } - - ecs_set(world, m, EcsMember, { - .type = type, - .count = (ecs_size_t)token.count - }); } - ecs_set_scope(world, old_scope); - - return 0; -error: - return -1; + ecs_strbuf_list_pop(reply, "]"); } static -int meta_parse_constants( +bool flecs_rest_reply_stats( ecs_world_t *world, - ecs_entity_t t, - const char *desc, - bool is_bitmask) + const ecs_http_request_t* req, + ecs_http_reply_t *reply) { - ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(t != 0, ECS_INTERNAL_ERROR, NULL); - ecs_assert(desc != NULL, ECS_INTERNAL_ERROR, NULL); + char *period_str = NULL; + flecs_rest_string_param(req, "period", &period_str); + char *category = &req->path[6]; - const char *ptr = desc; - const char *name = ecs_get_name(world, t); + ecs_entity_t period = EcsPeriod1s; + if (period_str) { + char *period_name = ecs_asprintf("Period%s", period_str); + period = ecs_lookup_child(world, ecs_id(FlecsMonitor), period_name); + ecs_os_free(period_name); + if (!period) { + flecs_reply_error(reply, "bad request (invalid period string)"); + reply->code = 400; + return false; + } + } - meta_parse_ctx_t ctx = { - .name = name, - .desc = ptr - }; + if (!ecs_os_strcmp(category, "world")) { + const EcsWorldStats *stats = ecs_get_pair(world, EcsWorld, + EcsWorldStats, period); + flecs_world_stats_to_json(&reply->body, stats); + return true; - meta_constant_t token; - int64_t last_value = 0; + } else if (!ecs_os_strcmp(category, "pipeline")) { + const EcsPipelineStats *stats = ecs_get_pair(world, EcsWorld, + EcsPipelineStats, period); + flecs_pipeline_stats_to_json(world, &reply->body, stats); + return true; - ecs_entity_t old_scope = ecs_set_scope(world, t); + } else { + flecs_reply_error(reply, "bad request (unsupported category)"); + reply->code = 400; + return false; + } - 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; - } + return true; +} +#else +static +bool flecs_rest_reply_stats( + ecs_world_t *world, + const ecs_http_request_t* req, + ecs_http_reply_t *reply) +{ + (void)world; + (void)req; + (void)reply; + return false; +} +#endif - ecs_entity_t c = ecs_entity_init(world, &(ecs_entity_desc_t){ - .name = token.name - }); +static +void flecs_rest_reply_table_append_type( + ecs_world_t *world, + ecs_strbuf_t *reply, + const ecs_table_t *table) +{ + ecs_strbuf_list_push(reply, "[", ","); + int32_t i, count = table->type.count; + ecs_id_t *ids = table->type.array; + for (i = 0; i < count; i ++) { + ecs_strbuf_list_next(reply); + ecs_strbuf_appendch(reply, '"'); + ecs_id_str_buf(world, ids[i], reply); + ecs_strbuf_appendch(reply, '"'); + } + ecs_strbuf_list_pop(reply, "]"); +} - 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 flecs_rest_reply_table_append_memory( + ecs_strbuf_t *reply, + const ecs_table_t *table) +{ + int32_t used = 0, allocated = 0; - last_value ++; - } + used += table->data.entities.count * ECS_SIZEOF(ecs_entity_t); + used += table->data.records.count * ECS_SIZEOF(ecs_record_t*); + allocated += table->data.entities.size * ECS_SIZEOF(ecs_entity_t); + allocated += table->data.records.size * ECS_SIZEOF(ecs_record_t*); - ecs_set_scope(world, old_scope); + int32_t i, storage_count = table->storage_count; + ecs_type_info_t **ti = table->type_info; + ecs_column_t *storages = table->data.columns; - return 0; -error: - return -1; + for (i = 0; i < storage_count; i ++) { + used += storages[i].count * ti[i]->size; + allocated += storages[i].size * ti[i]->size; + } + + ecs_strbuf_list_push(reply, "{", ","); + ecs_strbuf_list_append(reply, "\"used\":%d", used); + ecs_strbuf_list_append(reply, "\"allocated\":%d", allocated); + ecs_strbuf_list_pop(reply, "}"); } static -int meta_parse_enum( +void flecs_rest_reply_table_append( ecs_world_t *world, - ecs_entity_t t, - const char *desc) + ecs_strbuf_t *reply, + const ecs_table_t *table) { - ecs_add(world, t, EcsEnum); - return meta_parse_constants(world, t, desc, false); + ecs_strbuf_list_next(reply); + ecs_strbuf_list_push(reply, "{", ","); + ecs_strbuf_list_append(reply, "\"id\":%u", (uint32_t)table->id); + ecs_strbuf_list_appendstr(reply, "\"type\":"); + flecs_rest_reply_table_append_type(world, reply, table); + ecs_strbuf_list_append(reply, "\"count\":%d", ecs_table_count(table)); + ecs_strbuf_list_append(reply, "\"memory\":"); + flecs_rest_reply_table_append_memory(reply, table); + ecs_strbuf_list_append(reply, "\"refcount\":%d", table->refcount); + ecs_strbuf_list_pop(reply, "}"); } static -int meta_parse_bitmask( +bool flecs_rest_reply_tables( ecs_world_t *world, - ecs_entity_t t, - const char *desc) + const ecs_http_request_t* req, + ecs_http_reply_t *reply) { - ecs_add(world, t, EcsBitmask); - return meta_parse_constants(world, t, desc, true); + (void)req; + + ecs_strbuf_list_push(&reply->body, "[", ","); + 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_rest_reply_table_append(world, &reply->body, table); + } + ecs_strbuf_list_pop(&reply->body, "]"); + + return true; } -int ecs_meta_from_desc( +static +void flecs_rest_reply_id_append( ecs_world_t *world, - ecs_entity_t component, - ecs_type_kind_t kind, - const char *desc) + ecs_strbuf_t *reply, + const ecs_id_record_t *idr) { - 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; + ecs_strbuf_list_next(reply); + ecs_strbuf_list_push(reply, "{", ","); + ecs_strbuf_list_appendstr(reply, "\"id\":\""); + ecs_id_str_buf(world, idr->id, reply); + ecs_strbuf_appendch(reply, '"'); + + if (idr->type_info) { + if (idr->type_info->component != idr->id) { + ecs_strbuf_list_appendstr(reply, "\"component\":\""); + ecs_id_str_buf(world, idr->type_info->component, reply); + ecs_strbuf_appendch(reply, '"'); } - break; - default: - break; + + ecs_strbuf_list_append(reply, "\"size\":%d", + idr->type_info->size); + ecs_strbuf_list_append(reply, "\"alignment\":%d", + idr->type_info->alignment); } - return 0; -error: - return -1; -} + ecs_strbuf_list_append(reply, "\"table_count\":%d", + idr->cache.tables.count); + ecs_strbuf_list_append(reply, "\"empty_table_count\":%d", + idr->cache.empty_tables.count); -#endif + ecs_strbuf_list_pop(reply, "}"); +} +static +bool flecs_rest_reply_ids( + ecs_world_t *world, + const ecs_http_request_t* req, + ecs_http_reply_t *reply) +{ + (void)req; -#ifdef FLECS_LOG + ecs_strbuf_list_push(&reply->body, "[", ","); + ecs_map_iter_t it = ecs_map_iter(&world->id_index); + ecs_id_record_t *idr; + while ((idr = ecs_map_next_ptr(&it, ecs_id_record_t*, NULL))) { + flecs_rest_reply_id_append(world, &reply->body, idr); + } + ecs_strbuf_list_pop(&reply->body, "]"); -#include + return true; +} static -void ecs_colorize_buf( - char *msg, - bool enable_colors, - ecs_strbuf_t *buf) +bool flecs_rest_reply( + const ecs_http_request_t* req, + ecs_http_reply_t *reply, + void *ctx) { - char *ptr, ch, prev = '\0'; - bool isNum = false; - char isStr = '\0'; - bool isVar = false; - bool overrideColor = false; - bool autoColor = true; - bool dontAppend = false; + ecs_rest_ctx_t *impl = ctx; + ecs_world_t *world = impl->world; - for (ptr = msg; (ch = *ptr); ptr++) { - dontAppend = false; + if (req->path == NULL) { + ecs_dbg("rest: bad request (missing path)"); + flecs_reply_error(reply, "bad request (missing path)"); + reply->code = 400; + return false; + } - 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; - } + ecs_strbuf_appendstr(&reply->headers, "Access-Control-Allow-Origin: *\r\n"); - 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; - } + if (req->method == EcsHttpGet) { + /* Entity endpoint */ + if (!ecs_os_strncmp(req->path, "entity/", 7)) { + return flecs_rest_reply_entity(world, req, reply); + + /* Query endpoint */ + } else if (!ecs_os_strcmp(req->path, "query")) { + return flecs_rest_reply_query(world, req, reply); - if (isVar && !isalpha(ch) && !isdigit(ch) && ch != '_') { - if (enable_colors) ecs_strbuf_appendstr(buf, ECS_NORMAL); - isVar = false; - } + /* Stats endpoint */ + } else if (!ecs_os_strncmp(req->path, "stats/", 6)) { + return flecs_rest_reply_stats(world, req, reply); - if (!isStr && !isVar && ch == '$' && isalpha(ptr[1])) { - if (enable_colors) ecs_strbuf_appendstr(buf, ECS_CYAN); - isVar = true; - } + /* Tables endpoint */ + } else if (!ecs_os_strncmp(req->path, "tables", 6)) { + return flecs_rest_reply_tables(world, req, reply); + + /* Ids endpoint */ + } else if (!ecs_os_strncmp(req->path, "ids", 3)) { + return flecs_rest_reply_ids(world, req, reply); } + } else if (req->method == EcsHttpOptions) { + return true; + } - if (!isVar && !isStr && !isNum && ch == '#' && ptr[1] == '[') { - bool isColor = true; - overrideColor = true; + return false; +} - /* 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; - } +static +void flecs_on_set_rest(ecs_iter_t *it) +{ + EcsRest *rest = it->ptrs[0]; - if (isColor) { - ptr += 2; - while ((ch = *ptr) != ']') ptr ++; - dontAppend = true; - } - if (!autoColor) { - overrideColor = true; - } + int i; + for(i = 0; i < it->count; i ++) { + if (!rest[i].port) { + rest[i].port = ECS_REST_DEFAULT_PORT; } - 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; - } - } + 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 = flecs_rest_reply, + .ctx = srv_ctx + }); - if (!dontAppend) { - ecs_strbuf_appendstrn(buf, ptr, 1); + 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 (!overrideColor) { - if (((ch == '\'') || (ch == '"')) && !isStr) { - if (enable_colors) ecs_strbuf_appendstr(buf, ECS_NORMAL); - } - } + srv_ctx->world = it->world; + srv_ctx->entity = it->entities[i]; + srv_ctx->srv = srv; + srv_ctx->rc = 1; - prev = ch; - } + rest[i].impl = srv_ctx; - if (isNum || isStr || isVar || overrideColor) { - if (enable_colors) ecs_strbuf_appendstr(buf, ECS_NORMAL); + ecs_http_server_start(srv_ctx->srv); } } -void _ecs_logv( - int level, - const char *file, - int32_t line, - const char *fmt, - va_list args) -{ - (void)level; - (void)line; - - ecs_strbuf_t msg_buf = ECS_STRBUF_INIT; +static +void DequeueRest(ecs_iter_t *it) { + EcsRest *rest = ecs_field(it, EcsRest, 1); - if (level > ecs_os_api.log_level_) { - return; + if (it->delta_system_time > (ecs_ftime_t)1.0) { + ecs_warn( + "detected large progress interval (%.2fs), REST request may timeout", + (double)it->delta_system_time); } - /* 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.flags_ & EcsOsApiLogWithColors, &msg_buf); - ecs_os_free(msg_nocolor); - - char *msg = ecs_strbuf_get(&msg_buf); - if (msg) { - ecs_os_api.log_(level, file, line, msg); - ecs_os_free(msg); - } else { - ecs_os_api.log_(level, file, line, ""); - } + 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 _ecs_log( - int level, - const char *file, - int32_t line, - const char *fmt, - ...) +void FlecsRestImport( + ecs_world_t *world) { - va_list args; - va_start(args, fmt); - _ecs_logv(level, file, line, fmt, args); - va_end(args); -} + ECS_MODULE(world, FlecsRest); -void _ecs_log_push( - int32_t level) -{ - if (level <= ecs_os_api.log_level_) { - ecs_os_api.log_indent_ ++; - } -} + ecs_set_name_prefix(world, "Ecs"); -void _ecs_log_pop( - int32_t level) -{ - if (level <= ecs_os_api.log_level_) { - ecs_os_api.log_indent_ --; - } -} + flecs_bootstrap_component(world, EcsRest); -void _ecs_parser_errorv( - const char *name, - const char *expr, - int64_t column_arg, - const char *fmt, - va_list args) -{ - int32_t column = flecs_itoi32(column_arg); - - if (ecs_os_api.log_level_ >= -2) { - ecs_strbuf_t msg_buf = ECS_STRBUF_INIT; + ecs_set_hooks(world, EcsRest, { + .ctor = ecs_default_ctor, + .move = ecs_move(EcsRest), + .copy = ecs_copy(EcsRest), + .dtor = ecs_dtor(EcsRest), + .on_set = flecs_on_set_rest + }); - ecs_strbuf_vappend(&msg_buf, fmt, args); + ECS_SYSTEM(world, DequeueRest, EcsPostFrame, EcsRest); +} - if (expr) { - ecs_strbuf_appendstr(&msg_buf, "\n"); +#endif - /* 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 --; - } - if (ptr == expr) { - /* ptr is already at start of line */ - } else { - column -= (int32_t)(ptr - expr + 1); - expr = ptr + 1; - } - } - /* 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); - } +#ifdef FLECS_COREDOC - ecs_strbuf_appendstr(&msg_buf, "\n"); +#define URL_ROOT "https://flecs.docsforge.com/master/relationships-manual/" - if (column != -1) { - ecs_strbuf_append(&msg_buf, "%*s^", column, ""); - } - } +void FlecsCoreDocImport( + ecs_world_t *world) +{ + ECS_MODULE(world, FlecsCoreDoc); - char *msg = ecs_strbuf_get(&msg_buf); - ecs_os_err(name, 0, msg); - ecs_os_free(msg); - } -} + ECS_IMPORT(world, FlecsMeta); + ECS_IMPORT(world, FlecsDoc); -void _ecs_parser_error( - const char *name, - const char *expr, - int64_t column, - const char *fmt, - ...) -{ - 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_set_name_prefix(world, "Ecs"); -void _ecs_abort( - int32_t err, - const char *file, - int32_t line, - const char *fmt, - ...) -{ - 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_os_api.log_last_error_ = err; -} + /* Initialize reflection data for core components */ -bool _ecs_assert( - bool condition, - int32_t err, - const char *cond_str, - const char *file, - int32_t line, - const char *fmt, - ...) -{ - 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_struct_init(world, &(ecs_struct_desc_t){ + .entity = ecs_id(EcsComponent), + .members = { + {.name = (char*)"size", .type = ecs_id(ecs_i32_t)}, + {.name = (char*)"alignment", .type = ecs_id(ecs_i32_t)} } - ecs_os_api.log_last_error_ = err; - } - - return condition; -} - -void _ecs_deprecated( - const char *file, - int32_t line, - const char *msg) -{ - _ecs_err(file, line, "%s", msg); -} + }); -bool ecs_should_log(int32_t level) { -# if !defined(FLECS_LOG_3) - if (level == 3) { - return false; - } -# endif -# if !defined(FLECS_LOG_2) - if (level == 2) { - return false; - } -# endif -# if !defined(FLECS_LOG_1) - if (level == 1) { - return false; - } -# endif + ecs_struct_init(world, &(ecs_struct_desc_t){ + .entity = ecs_id(EcsDocDescription), + .members = { + {.name = "value", .type = ecs_id(ecs_string_t)} + } + }); - return level <= ecs_os_api.log_level_; -} + /* 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"); -#define ECS_ERR_STR(code) case code: return &(#code[4]) + ecs_doc_set_brief(world, EcsFlecsCore, "Flecs module with builtin components"); -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_NAME_IN_USE); - 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_MISSING_SYMBOL); - ECS_ERR_STR(ECS_ALREADY_IN_USE); - ECS_ERR_STR(ECS_CYCLE_DETECTED); - 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_READONLY); - 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_ACCESS_VIOLATION); - ECS_ERR_STR(ECS_COMPONENT_NOT_REGISTERED); - ECS_ERR_STR(ECS_INCONSISTENT_COMPONENT_ID); - 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); - ECS_ERR_STR(ECS_ID_IN_USE); - } + ecs_doc_set_brief(world, EcsWorld, "Entity associated with world"); - return "unknown error code"; -} + 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"); -#else + 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"); -/* Empty bodies for when logging is disabled */ + ecs_doc_set_brief(world, EcsTransitive, "Transitive relationship property"); + ecs_doc_set_brief(world, EcsReflexive, "Reflexive relationship property"); + ecs_doc_set_brief(world, EcsFinal, "Final relationship property"); + ecs_doc_set_brief(world, EcsDontInherit, "DontInherit relationship property"); + ecs_doc_set_brief(world, EcsTag, "Tag relationship property"); + ecs_doc_set_brief(world, EcsAcyclic, "Acyclic relationship property"); + ecs_doc_set_brief(world, EcsExclusive, "Exclusive relationship property"); + ecs_doc_set_brief(world, EcsSymmetric, "Symmetric relationship property"); + ecs_doc_set_brief(world, EcsWith, "With relationship property"); + ecs_doc_set_brief(world, EcsOnDelete, "OnDelete relationship cleanup property"); + ecs_doc_set_brief(world, EcsOnDeleteTarget, "OnDeleteTarget relationship cleanup property"); + ecs_doc_set_brief(world, EcsDefaultChildComponent, "Sets default component hint for children of entity"); + ecs_doc_set_brief(world, EcsRemove, "Remove relationship cleanup property"); + ecs_doc_set_brief(world, EcsDelete, "Delete relationship cleanup property"); + ecs_doc_set_brief(world, EcsPanic, "Panic relationship cleanup property"); + ecs_doc_set_brief(world, EcsIsA, "Builtin IsA relationship"); + ecs_doc_set_brief(world, EcsChildOf, "Builtin ChildOf relationship"); + ecs_doc_set_brief(world, EcsDependsOn, "Builtin DependsOn relationship"); + 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"); -void _ecs_log( - int32_t level, - const char *file, - int32_t line, - const char *fmt, - ...) -{ - (void)level; - (void)file; - (void)line; - (void)fmt; -} + ecs_doc_set_link(world, EcsTransitive, URL_ROOT "#transitive-property"); + ecs_doc_set_link(world, EcsReflexive, URL_ROOT "#reflexive-property"); + ecs_doc_set_link(world, EcsFinal, URL_ROOT "#final-property"); + ecs_doc_set_link(world, EcsDontInherit, URL_ROOT "#dontinherit-property"); + ecs_doc_set_link(world, EcsTag, URL_ROOT "#tag-property"); + ecs_doc_set_link(world, EcsAcyclic, URL_ROOT "#acyclic-property"); + ecs_doc_set_link(world, EcsExclusive, URL_ROOT "#exclusive-property"); + ecs_doc_set_link(world, EcsSymmetric, URL_ROOT "#symmetric-property"); + ecs_doc_set_link(world, EcsWith, URL_ROOT "#with-property"); + ecs_doc_set_link(world, EcsOnDelete, URL_ROOT "#cleanup-properties"); + ecs_doc_set_link(world, EcsOnDeleteTarget, URL_ROOT "#cleanup-properties"); + ecs_doc_set_link(world, EcsRemove, URL_ROOT "#cleanup-properties"); + ecs_doc_set_link(world, EcsDelete, URL_ROOT "#cleanup-properties"); + ecs_doc_set_link(world, EcsPanic, URL_ROOT "#cleanup-properties"); + ecs_doc_set_link(world, EcsIsA, URL_ROOT "#the-isa-relationship"); + ecs_doc_set_link(world, EcsChildOf, URL_ROOT "#the-childof-relationship"); + + /* 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"); -void _ecs_parser_error( - const char *name, - const char *expr, - int64_t column, - const char *fmt, - ...) -{ - (void)name; - (void)expr; - (void)column; - (void)fmt; -} + 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"); -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; -} + 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"); -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; -} + /* 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"); -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_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"); } #endif -int ecs_log_set_level( - int level) -{ - int prev = level; - ecs_os_api.log_level_ = level; - return prev; -} - -bool ecs_log_enable_colors( - bool enabled) -{ - bool prev = ecs_os_api.flags_ & EcsOsApiLogWithColors; - ECS_BIT_COND(ecs_os_api.flags_, EcsOsApiLogWithColors, enabled); - return prev; -} +/* 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. */ -bool ecs_log_enable_timestamp( - bool enabled) -{ - bool prev = ecs_os_api.flags_ & EcsOsApiLogWithTimeStamp; - ECS_BIT_COND(ecs_os_api.flags_, EcsOsApiLogWithTimeStamp, enabled); - return prev; -} +/* 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. + */ -bool ecs_log_enable_timedelta( - bool enabled) -{ - bool prev = ecs_os_api.flags_ & EcsOsApiLogWithTimeDelta; - ECS_BIT_COND(ecs_os_api.flags_, EcsOsApiLogWithTimeDelta, enabled); - return prev; -} -int ecs_log_last_error(void) -{ - int result = ecs_os_api.log_last_error_; - ecs_os_api.log_last_error_ = 0; - return result; -} +#ifdef FLECS_HTTP +#if defined(ECS_TARGET_WINDOWS) +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#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 +/* Max length of request method */ +#define ECS_HTTP_METHOD_LEN_MAX (8) -#ifdef FLECS_JSON +/* Timeout (s) before connection purge */ +#define ECS_HTTP_CONNECTION_PURGE_TIMEOUT (1.0) -void flecs_json_next( - ecs_strbuf_t *buf); +/* Number of dequeues before purging */ +#define ECS_HTTP_CONNECTION_PURGE_RETRY_COUNT (5) -void flecs_json_literal( - ecs_strbuf_t *buf, - const char *value); +/* Minimum interval between dequeueing requests (ms) */ +#define ECS_HTTP_MIN_DEQUEUE_INTERVAL (100) -void flecs_json_number( - ecs_strbuf_t *buf, - double value); +/* Minimum interval between printing statistics (ms) */ +#define ECS_HTTP_MIN_STATS_INTERVAL (10 * 1000) -void flecs_json_true( - ecs_strbuf_t *buf); +/* Max length of headers in reply */ +#define ECS_HTTP_REPLY_HEADER_SIZE (1024) -void flecs_json_false( - ecs_strbuf_t *buf); +/* Receive buffer size */ +#define ECS_HTTP_SEND_RECV_BUFFER_SIZE (16 * 1024) -void flecs_json_bool( - ecs_strbuf_t *buf, - bool value); +/* Max length of request (path + query + headers + body) */ +#define ECS_HTTP_REQUEST_LEN_MAX (10 * 1024 * 1024) -void flecs_json_array_push( - ecs_strbuf_t *buf); +/* HTTP server struct */ +struct ecs_http_server_t { + bool should_run; + bool running; -void flecs_json_array_pop( - ecs_strbuf_t *buf); + ecs_http_socket_t sock; + ecs_os_mutex_t lock; + ecs_os_thread_t thread; -void flecs_json_object_push( - ecs_strbuf_t *buf); + ecs_http_reply_action_t callback; + void *ctx; -void flecs_json_object_pop( - ecs_strbuf_t *buf); + ecs_sparse_t *connections; /* sparse */ + ecs_sparse_t *requests; /* sparse */ -void flecs_json_string( - ecs_strbuf_t *buf, - const char *value); + bool initialized; -void flecs_json_member( - ecs_strbuf_t *buf, - const char *name); + uint16_t port; + const char *ipaddr; -void flecs_json_path( - ecs_strbuf_t *buf, - const ecs_world_t *world, - ecs_entity_t e); + ecs_ftime_t dequeue_timeout; /* used to not lock request queue too often */ + ecs_ftime_t stats_timeout; /* used for periodic reporting of statistics */ -void flecs_json_label( - ecs_strbuf_t *buf, - const ecs_world_t *world, - ecs_entity_t e); + ecs_ftime_t request_time; /* time spent on requests in last stats interval */ + ecs_ftime_t request_time_total; /* total time spent on requests */ + int32_t requests_processed; /* requests processed in last stats interval */ + int32_t requests_processed_total; /* total requests processed */ + int32_t dequeue_count; /* number of dequeues in last stats interval */ +}; -void flecs_json_color( - ecs_strbuf_t *buf, - const ecs_world_t *world, - ecs_entity_t e); - -void flecs_json_id( - ecs_strbuf_t *buf, - const ecs_world_t *world, - ecs_id_t id); - -ecs_primitive_kind_t flecs_json_op_to_primitive_kind( - ecs_meta_type_op_kind_t kind); - -#endif +/** Fragment state, used by HTTP request parser */ +typedef enum { + HttpFragStateBegin, + HttpFragStateMethod, + HttpFragStatePath, + HttpFragStateVersion, + HttpFragStateHeaderStart, + HttpFragStateHeaderName, + HttpFragStateHeaderValueStart, + HttpFragStateHeaderValue, + HttpFragStateCR, + HttpFragStateCRLF, + HttpFragStateCRLFCR, + HttpFragStateBody, + HttpFragStateDone +} HttpFragState; +/** 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; -#ifdef FLECS_JSON +/** Extend public connection type with fragment data */ +typedef struct { + ecs_http_connection_t pub; + ecs_http_fragment_t frag; + ecs_http_socket_t sock; -static -int json_ser_type( - const ecs_world_t *world, - ecs_vector_t *ser, - const void *base, - ecs_strbuf_t *str); + /* Connection is purged after both timeout expires and connection has + * exceeded retry count. This ensures that a connection does not immediately + * timeout when a frame takes longer than usual */ + ecs_ftime_t dequeue_timeout; + int32_t dequeue_retries; +} ecs_http_connection_impl_t; -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, - int32_t in_array); +typedef struct { + ecs_http_request_t pub; + uint64_t conn_id; /* for sanity check */ + void *res; +} ecs_http_request_impl_t; 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_size_t http_send( + ecs_http_socket_t sock, + const void *buf, + ecs_size_t size, + int flags) +{ +#ifndef ECS_TARGET_WINDOWS + 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 +} -/* 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_size_t http_recv( + ecs_http_socket_t sock, + void *buf, + ecs_size_t size, + int flags) { - 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_size_t ret; +#ifndef ECS_TARGET_WINDOWS + 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); } - ecs_strbuf_appendch(str, '"'); - ecs_strbuf_appendstr(str, ecs_get_name(world, constant->constant)); - ecs_strbuf_appendch(str, '"'); - - return 0; -error: - return -1; + return ret; } -/* 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) +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 EcsBitmask *bitmask_type = ecs_get(world, op->type, EcsBitmask); - ecs_check(bitmask_type != NULL, ECS_INVALID_PARAMETER, NULL); - - uint32_t value = *(uint32_t*)ptr; - ecs_map_key_t key; - ecs_bitmask_constant_t *constant; - - if (!value) { - ecs_strbuf_appendch(str, '0'); - return 0; - } - - ecs_strbuf_list_push(str, "\"", "|"); - - /* 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; - } - } - - if (value != 0) { - /* All bits must have been matched by a constant */ - goto error; - } - - ecs_strbuf_list_pop(str, "\""); - - return 0; -error: - return -1; + 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); } -/* 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) +int http_bind( + ecs_http_socket_t sock, + const struct sockaddr* addr, + ecs_size_t addr_len) { - flecs_json_array_push(str); - - const void *ptr = base; - - 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, 1)) { - return -1; - } - ptr = ECS_OFFSET(ptr, elem_size); - } - - flecs_json_array_pop(str); - - return 0; + ecs_assert(addr_len > 0, ECS_INTERNAL_ERROR, NULL); + return bind(sock, addr, (uint32_t)addr_len); } 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) +bool http_socket_is_valid( + ecs_http_socket_t sock) { - 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); +#if defined(ECS_TARGET_WINDOWS) + return sock != INVALID_SOCKET; +#else + return sock >= 0; +#endif +} - 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 defined(ECS_TARGET_WINDOWS) +#define HTTP_SOCKET_INVALID INVALID_SOCKET +#else +#define HTTP_SOCKET_INVALID (-1) +#endif - return json_ser_elements( - world, ops, op_count, base, elem_count, comp->size, str); +static +void http_close( + ecs_http_socket_t *sock) +{ + ecs_assert(sock != NULL, ECS_INTERNAL_ERROR, NULL); +#if defined(ECS_TARGET_WINDOWS) + closesocket(*sock); +#else + ecs_dbg_2("http: closing socket %u", *sock); + shutdown(*sock, SHUT_RDWR); + close(*sock); +#endif + *sock = HTTP_SOCKET_INVALID; } -/* 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) +ecs_http_socket_t http_accept( + ecs_http_socket_t sock, + struct sockaddr* addr, + ecs_size_t *addr_len) { - const EcsArray *a = ecs_get(world, op->type, EcsArray); - ecs_assert(a != NULL, ECS_INTERNAL_ERROR, NULL); + 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; +} - return json_ser_type_elements( - world, a->type, ptr, a->count, str); +static +void reply_free(ecs_http_reply_t* response) { + ecs_assert(response != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_os_free(response->body.content); } -/* 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; - } +void request_free(ecs_http_request_impl_t *req) { + ecs_assert(req != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(req->pub.conn != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(req->pub.conn->server != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(req->pub.conn->server->requests != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(req->pub.conn->id == req->conn_id, ECS_INTERNAL_ERROR, NULL); + ecs_os_free(req->res); + flecs_sparse_remove(req->pub.conn->server->requests, req->pub.id); +} - const EcsVector *v = ecs_get(world, op->type, EcsVector); - ecs_assert(v != NULL, ECS_INTERNAL_ERROR, NULL); +static +void connection_free(ecs_http_connection_impl_t *conn) { + ecs_assert(conn != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(conn->pub.id != 0, ECS_INTERNAL_ERROR, NULL); + uint64_t conn_id = conn->pub.id; - const EcsComponent *comp = ecs_get(world, v->type, EcsComponent); - ecs_assert(comp != NULL, ECS_INTERNAL_ERROR, NULL); + if (http_socket_is_valid(conn->sock)) { + http_close(&conn->sock); + } - int32_t count = ecs_vector_count(value); - void *array = ecs_vector_first_t(value, comp->size, comp->alignment); + flecs_sparse_remove(conn->pub.server->connections, conn_id); +} - /* Serialize contiguous buffer of vector */ - return json_ser_type_elements(world, v->type, array, count, str); +// 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); } -/* 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) +void decode_url_str( + char *str) { - switch(op->kind) { - case EcsOpPush: - case EcsOpPop: - /* Should not be parsed as single op */ - ecs_throw(ECS_INVALID_PARAMETER, NULL); - break; - case EcsOpF32: - ecs_strbuf_appendflt(str, - (ecs_f64_t)*(ecs_f32_t*)ECS_OFFSET(ptr, op->offset), '"'); - break; - case EcsOpF64: - ecs_strbuf_appendflt(str, - *(ecs_f64_t*)ECS_OFFSET(ptr, op->offset), '"'); - 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*)ECS_OFFSET(ptr, op->offset); - if (!e) { - ecs_strbuf_appendch(str, '0'); + 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 { - flecs_json_path(str, world, e); - } - break; - } - - default: - if (ecs_primitive_to_expr_buf(world, - flecs_json_op_to_primitive_kind(op->kind), - ECS_OFFSET(ptr, op->offset), str)) - { - /* Unknown operation */ - ecs_throw(ECS_INTERNAL_ERROR, NULL); - return -1; + dst[0] = ptr[0]; + dst ++; } - break; } - - return 0; -error: - return -1; + dst[0] = '\0'; } -/* 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, - int32_t in_array) +void parse_method( + ecs_http_fragment_t *frag) { - for (int i = 0; i < op_count; i ++) { - ecs_meta_type_op_t *op = &ops[i]; - - if (in_array <= 0) { - if (op->name) { - flecs_json_member(str, op->name); - } - - int32_t elem_count = op->count; - if (elem_count > 1) { - /* 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: - flecs_json_object_push(str); - in_array --; - break; - case EcsOpPop: - flecs_json_object_pop(str); - in_array ++; - break; - default: - if (json_ser_type_op(world, op, base, str)) { - goto error; - } - break; - } + 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 if (!ecs_os_strcmp(method, "OPTIONS")) frag->method = EcsHttpOptions; + else { + frag->method = EcsHttpMethodUnsupported; + frag->invalid = true; } - - return 0; -error: - return -1; + ecs_strbuf_reset(&frag->buf); } -/* 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) +bool header_writable( + ecs_http_fragment_t *frag) { - 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, 0); + return frag->header_count < ECS_HTTP_HEADER_COUNT_MAX; } 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 header_buf_reset( + ecs_http_fragment_t *frag) { - if (count) { - ecs_size_t size = comp->size; + frag->header_buf[0] = '\0'; + frag->header_buf_ptr = frag->header_buf; +} - flecs_json_array_push(buf); +static +void header_buf_append( + ecs_http_fragment_t *frag, + char ch) +{ + 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'; + } +} - do { - ecs_strbuf_list_next(buf); - if (json_ser_type(world, ser->ops, ptr, buf)) { - return -1; - } - - ptr = ECS_OFFSET(ptr, size); - } while (-- count); +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; - flecs_json_array_pop(buf); + if (frag->invalid) { /* invalid request received, don't enqueue */ + ecs_strbuf_reset(&frag->buf); } else { - if (json_ser_type(world, ser->ops, ptr, buf)) { - return -1; - } - } - - return 0; -} + 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); + req->conn_id = conn->pub.id; -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; - } + 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); + } - 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; + req->pub.header_count = frag->header_count; + req->pub.param_count = frag->param_count; + req->res = res; + ecs_os_mutex_unlock(srv->lock); + } } - - 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) +static +bool parse_request( + ecs_http_connection_impl_t *conn, + uint64_t conn_id, + const char* req_frag, + ecs_size_t req_frag_len) { - 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; - } + ecs_http_fragment_t *frag = &conn->frag; - return ecs_strbuf_get(&str); -} + 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"); -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); -} + 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; + } + } -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 (frag->state == HttpFragStateDone) { + frag->state = HttpFragStateBegin; + if (conn->pub.id == conn_id) { + enqueue_request(conn); + } + return true; + } else { + return false; + } } static -bool skip_id( - const ecs_world_t *world, - ecs_id_t id, - const ecs_entity_to_json_desc_t *desc, - ecs_entity_t ent, - ecs_entity_t inst, - ecs_entity_t *pred_out, - ecs_entity_t *obj_out, - ecs_entity_t *role_out, - bool *hidden_out) +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) { - bool is_base = ent != inst; - ecs_entity_t pred = 0, obj = 0, role = 0; - bool hidden = false; + ecs_strbuf_appendstr(hdrs, "HTTP/1.1 "); + ecs_strbuf_append(hdrs, "%d ", code); + ecs_strbuf_appendstr(hdrs, status); + ecs_strbuf_appendstr(hdrs, "\r\n"); - if (ECS_HAS_ID_FLAG(id, PAIR)) { - pred = ecs_pair_first(world, id); - obj = ecs_pair_second(world, id); - } else { - pred = id & ECS_COMPONENT_MASK; - if (id & ECS_ID_FLAGS_MASK) { - role = id & ECS_ID_FLAGS_MASK; - } - } + ecs_strbuf_appendstr(hdrs, "Content-Type: "); + ecs_strbuf_appendstr(hdrs, content_type); + ecs_strbuf_appendstr(hdrs, "\r\n"); - if (!desc || !desc->serialize_meta_ids) { - if (pred == EcsIsA || pred == EcsChildOf || - pred == ecs_id(EcsIdentifier)) - { - return true; - } -#ifdef FLECS_DOC - if (pred == ecs_id(EcsDocDescription)) { - return true; - } -#endif - } + ecs_strbuf_appendstr(hdrs, "Content-Length: "); + ecs_strbuf_append(hdrs, "%d", content_len); + ecs_strbuf_appendstr(hdrs, "\r\n"); - if (is_base) { - if (ecs_has_id(world, pred, EcsDontInherit)) { - return true; - } - } - if (!desc || !desc->serialize_private) { - if (ecs_has_id(world, pred, EcsPrivate)) { - return true; - } - } - if (is_base) { - if (ecs_get_target_for_id(world, inst, EcsIsA, id) != ent) { - hidden = true; - } - } - if (hidden && (!desc || !desc->serialize_hidden)) { - return true; - } + ecs_strbuf_appendstr(hdrs, "Server: flecs\r\n"); - *pred_out = pred; - *obj_out = obj; - *role_out = role; - if (hidden_out) *hidden_out = hidden; + ecs_strbuf_mergebuff(hdrs, extra_headers); - return false; + ecs_strbuf_appendstr(hdrs, "\r\n"); } static -int append_type_labels( - const ecs_world_t *world, - ecs_strbuf_t *buf, - const ecs_id_t *ids, - int32_t count, - ecs_entity_t ent, - ecs_entity_t inst, - const ecs_entity_to_json_desc_t *desc) +void send_reply( + ecs_http_connection_impl_t* conn, + ecs_http_reply_t* reply) { - (void)world; (void)buf; (void)ids; (void)count; (void)ent; (void)inst; - (void)desc; - -#ifdef FLECS_DOC - if (!desc || !desc->serialize_id_labels) { - return 0; - } - - flecs_json_member(buf, "id_labels"); - flecs_json_array_push(buf); + 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; - int32_t i; - for (i = 0; i < count; i ++) { - ecs_entity_t pred = 0, obj = 0, role = 0; - if (skip_id(world, ids[i], desc, ent, inst, &pred, &obj, &role, 0)) { - continue; - } + char *content = ecs_strbuf_get(&reply->body); + int32_t content_length = reply->body.length - 1; - if (obj && (pred == EcsUnion)) { - pred = obj; - obj = ecs_get_target(world, ent, pred, 0); - } + /* First, send the response HTTP headers */ + append_send_headers(&hdr_buf, reply->code, reply->status, + reply->content_type, &reply->headers, content_length); - if (desc && desc->serialize_id_labels) { - flecs_json_next(buf); + 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); - flecs_json_array_push(buf); - flecs_json_next(buf); - flecs_json_label(buf, world, pred); - if (obj) { - flecs_json_next(buf); - flecs_json_label(buf, world, obj); - } + if (written != hdrs_len) { + ecs_err("failed to write HTTP response headers to '%s:%s': %s", + conn->pub.host, conn->pub.port, ecs_os_strerror(errno)); + return; + } - flecs_json_array_pop(buf); + /* 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:%s': %s", + conn->pub.host, conn->pub.port, ecs_os_strerror(errno)); } } - - flecs_json_array_pop(buf); -#endif - return 0; } static -int append_type_values( - const ecs_world_t *world, - ecs_strbuf_t *buf, - const ecs_id_t *ids, - int32_t count, - ecs_entity_t ent, - ecs_entity_t inst, - const ecs_entity_to_json_desc_t *desc) +void recv_request( + ecs_http_server_t *srv, + ecs_http_connection_impl_t *conn, + uint64_t conn_id, + ecs_http_socket_t sock) { - if (!desc || !desc->serialize_values) { - return 0; - } - - flecs_json_member(buf, "values"); - flecs_json_array_push(buf); + ecs_size_t bytes_read; + char recv_buf[ECS_HTTP_SEND_RECV_BUFFER_SIZE]; - int32_t i; - for (i = 0; i < count; i ++) { - bool hidden; - ecs_entity_t pred = 0, obj = 0, role = 0; - ecs_id_t id = ids[i]; - if (skip_id(world, id, desc, ent, inst, &pred, &obj, &role, - &hidden)) - { - continue; + while ((bytes_read = http_recv( + sock, recv_buf, ECS_SIZEOF(recv_buf), 0)) > 0) + { + ecs_os_mutex_lock(srv->lock); + bool is_alive = conn->pub.id == conn_id; + if (is_alive) { + conn->dequeue_timeout = 0; + conn->dequeue_retries = 0; } + ecs_os_mutex_unlock(srv->lock); - if (!hidden) { - bool serialized = false; - 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); - - flecs_json_next(buf); - if (json_ser_type(world, ser->ops, ptr, buf) != 0) { - /* Entity contains invalid value */ - return -1; - } - serialized = true; - } - } - if (!serialized) { - flecs_json_next(buf); - flecs_json_number(buf, 0); + if (is_alive) { + if (parse_request(conn, conn_id, recv_buf, bytes_read)) { + return; } } else { - if (!desc || desc->serialize_hidden) { - flecs_json_next(buf); - flecs_json_number(buf, 0); - } + return; } } - - flecs_json_array_pop(buf); - - return 0; } static -int append_type_info( - const ecs_world_t *world, - ecs_strbuf_t *buf, - const ecs_id_t *ids, - int32_t count, - ecs_entity_t ent, - ecs_entity_t inst, - const ecs_entity_to_json_desc_t *desc) +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) { - if (!desc || !desc->serialize_type_info) { - return 0; - } + /* 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); + uint64_t conn_id = conn->pub.id = flecs_sparse_last_id(srv->connections); + conn->pub.server = srv; + conn->sock = sock_conn; + ecs_os_mutex_unlock(srv->lock); - flecs_json_member(buf, "type_info"); - flecs_json_array_push(buf); + char *remote_host = conn->pub.host; + char *remote_port = conn->pub.port; - int32_t i; - for (i = 0; i < count; i ++) { - bool hidden; - ecs_entity_t pred = 0, obj = 0, role = 0; - ecs_id_t id = ids[i]; - if (skip_id(world, id, desc, ent, inst, &pred, &obj, &role, - &hidden)) - { - continue; - } - - if (!hidden) { - ecs_entity_t typeid = ecs_get_typeid(world, id); - if (typeid) { - flecs_json_next(buf); - if (ecs_type_info_to_json_buf(world, typeid, buf) != 0) { - return -1; - } - } else { - flecs_json_next(buf); - flecs_json_number(buf, 0); - } - } else { - if (!desc || desc->serialize_hidden) { - flecs_json_next(buf); - flecs_json_number(buf, 0); - } - } + /* 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"); } - flecs_json_array_pop(buf); - - return 0; + ecs_dbg_2("http: connection established from '%s:%s'", + remote_host, remote_port); + + recv_request(srv, conn, conn_id, sock_conn); + + ecs_dbg_2("http: request received from '%s:%s'", + remote_host, remote_port); } static -int append_type_hidden( - const ecs_world_t *world, - ecs_strbuf_t *buf, - const ecs_id_t *ids, - int32_t count, - ecs_entity_t ent, - ecs_entity_t inst, - const ecs_entity_to_json_desc_t *desc) +void accept_connections( + ecs_http_server_t* srv, + const struct sockaddr* addr, + ecs_size_t addr_len) { - if (!desc || !desc->serialize_hidden) { - return 0; - } - - if (ent == inst) { - return 0; /* if this is not a base, components are never hidden */ - } - - flecs_json_member(buf, "hidden"); - flecs_json_array_push(buf); - - int32_t i; - for (i = 0; i < count; i ++) { - bool hidden; - ecs_entity_t pred = 0, obj = 0, role = 0; - ecs_id_t id = ids[i]; - if (skip_id(world, id, desc, ent, inst, &pred, &obj, &role, - &hidden)) - { - continue; +#ifdef ECS_TARGET_WINDOWS + /* 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; } - - flecs_json_next(buf); - flecs_json_bool(buf, hidden); + } else { + http_close(&testsocket); } +#endif - flecs_json_array_pop(buf); - - return 0; -} - + /* Resolve name + port (used for logging) */ + char addr_host[256]; + char addr_port[20]; -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) -{ - const ecs_id_t *ids = NULL; - int32_t i, count = 0; + ecs_http_socket_t sock = HTTP_SOCKET_INVALID; + ecs_assert(srv->sock == HTTP_SOCKET_INVALID, ECS_INTERNAL_ERROR, NULL); - const ecs_type_t *type = ecs_get_type(world, ent); - if (type) { - ids = type->array; - count = type->count; + 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"); } - flecs_json_member(buf, "ids"); - flecs_json_array_push(buf); + ecs_os_mutex_lock(srv->lock); + if (srv->should_run) { + ecs_dbg_2("http: initializing connection socket"); - for (i = 0; i < count; i ++) { - ecs_entity_t pred = 0, obj = 0, role = 0; - if (skip_id(world, ids[i], desc, ent, inst, &pred, &obj, &role, 0)) { - continue; + sock = socket(addr->sa_family, SOCK_STREAM, IPPROTO_TCP); + if (!http_socket_is_valid(sock)) { + ecs_err("unable to create new connection socket: %s", + ecs_os_strerror(errno)); + ecs_os_mutex_unlock(srv->lock); + goto done; } - if (obj && (pred == EcsUnion)) { - pred = obj; - obj = ecs_get_target(world, ent, pred, 0); + int reuse = 1; + int result = setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, + (char*)&reuse, ECS_SIZEOF(reuse)); + if (result) { + ecs_warn("failed to setsockopt: %s", ecs_os_strerror(errno)); } - flecs_json_next(buf); - flecs_json_array_push(buf); - flecs_json_next(buf); - flecs_json_path(buf, world, pred); - if (obj || role) { - flecs_json_next(buf); - if (obj) { - flecs_json_path(buf, world, obj); - } else { - flecs_json_number(buf, 0); - } - if (role) { - flecs_json_next(buf); - flecs_json_string(buf, ecs_id_flag_str(role)); + if (addr->sa_family == AF_INET6) { + int ipv6only = 0; + if (setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, + (char*)&ipv6only, ECS_SIZEOF(ipv6only))) + { + ecs_warn("failed to setsockopt: %s", ecs_os_strerror(errno)); } } - flecs_json_array_pop(buf); - } - flecs_json_array_pop(buf); + result = http_bind(sock, addr, addr_len); + if (result) { + ecs_err("http: failed to bind to '%s:%s': %s", + addr_host, addr_port, ecs_os_strerror(errno)); + ecs_os_mutex_unlock(srv->lock); + goto done; + } - if (append_type_labels(world, buf, ids, count, ent, inst, desc)) { - return -1; - } - - if (append_type_values(world, buf, ids, count, ent, inst, desc)) { - return -1; + srv->sock = sock; + + result = listen(srv->sock, SOMAXCONN); + if (result) { + ecs_warn("http: could not listen for SOMAXCONN (%d) connections: %s", + SOMAXCONN, ecs_os_strerror(errno)); + } + + ecs_trace("http: listening for incoming connections on '%s:%s'", + addr_host, addr_port); + } else { + ecs_dbg_2("http: server shut down while initializing"); } + ecs_os_mutex_unlock(srv->lock); - if (append_type_info(world, buf, ids, count, ent, inst, desc)) { - return -1; + ecs_http_socket_t sock_conn; + struct sockaddr_storage remote_addr; + ecs_size_t remote_addr_len; + + 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) { + if (srv->should_run) { + ecs_dbg("http: connection attempt failed: %s", + ecs_os_strerror(errno)); + } + continue; + } + + init_connection(srv, sock_conn, &remote_addr, remote_addr_len); } - if (append_type_hidden(world, buf, ids, count, ent, inst, desc)) { - return -1; +done: + ecs_os_mutex_lock(srv->lock); + if (http_socket_is_valid(srv->sock) && errno != EBADF) { + http_close(&sock); + srv->sock = sock; } + ecs_os_mutex_unlock(srv->lock); - return 0; + ecs_trace("http: no longer accepting connections on '%s:%s'", + addr_host, addr_port); } 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) -{ - const ecs_type_t *type = ecs_get_type(world, ent); - ecs_id_t *ids = NULL; - int32_t i, count = 0; - if (type) { - ids = type->array; - count = type->count; - } +void* http_server_thread(void* arg) { + ecs_http_server_t *srv = arg; + struct sockaddr_in addr; + ecs_os_zeromem(&addr); + addr.sin_family = AF_INET; + addr.sin_port = htons(srv->port); - 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_second(world, id), inst, desc)) - { - return -1; - } - } + if (!srv->ipaddr) { + addr.sin_addr.s_addr = htonl(INADDR_ANY); + } else { + inet_pton(AF_INET, srv->ipaddr, &(addr.sin_addr)); } - ecs_strbuf_list_next(buf); - flecs_json_object_push(buf); - flecs_json_member(buf, "path"); - flecs_json_path(buf, world, ent); + accept_connections(srv, (struct sockaddr*)&addr, ECS_SIZEOF(addr)); + return NULL; +} - if (append_type(world, buf, ent, inst, desc)) { - return -1; +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; + + if (srv->callback((ecs_http_request_t*)req, &reply, srv->ctx) == false) { + reply.code = 404; + reply.status = "Resource not found"; } - flecs_json_object_pop(buf); + send_reply(conn, &reply); + ecs_dbg_2("http: reply sent to '%s:%s'", conn->pub.host, conn->pub.port); - return 0; + reply_free(&reply); + request_free(req); + connection_free(conn); } -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) +static +int32_t dequeue_requests( + ecs_http_server_t *srv, + ecs_ftime_t delta_time) { - if (!entity || !ecs_is_valid(world, entity)) { - return -1; - } - - flecs_json_object_push(buf); + ecs_os_mutex_lock(srv->lock); - if (!desc || desc->serialize_path) { - char *path = ecs_get_fullpath(world, entity); - flecs_json_member(buf, "path"); - flecs_json_string(buf, path); - ecs_os_free(path); + int32_t i, request_count = flecs_sparse_count(srv->requests); + for (i = request_count - 1; i >= 1; i --) { + ecs_http_request_impl_t *req = flecs_sparse_get_dense( + srv->requests, ecs_http_request_impl_t, i); + handle_request(srv, req); } -#ifdef FLECS_DOC - if (desc && desc->serialize_label) { - flecs_json_member(buf, "label"); - const char *doc_name = ecs_doc_get_name(world, entity); - if (doc_name) { - flecs_json_string(buf, doc_name); - } else { - char num_buf[20]; - ecs_os_sprintf(num_buf, "%u", (uint32_t)entity); - flecs_json_string(buf, num_buf); - } - } + int32_t connections_count = flecs_sparse_count(srv->connections); + for (i = connections_count - 1; i >= 1; i --) { + ecs_http_connection_impl_t *conn = flecs_sparse_get_dense( + srv->connections, ecs_http_connection_impl_t, i); - if (desc && desc->serialize_brief) { - const char *doc_brief = ecs_doc_get_brief(world, entity); - if (doc_brief) { - flecs_json_member(buf, "brief"); - flecs_json_string(buf, doc_brief); + conn->dequeue_timeout += delta_time; + conn->dequeue_retries ++; + + if ((conn->dequeue_timeout > + (ecs_ftime_t)ECS_HTTP_CONNECTION_PURGE_TIMEOUT) && + (conn->dequeue_retries > ECS_HTTP_CONNECTION_PURGE_RETRY_COUNT)) + { + ecs_dbg("http: purging connection '%s:%s' (sock = %d)", + conn->pub.host, conn->pub.port, conn->sock); + connection_free(conn); } } - if (desc && desc->serialize_link) { - const char *doc_link = ecs_doc_get_link(world, entity); - if (doc_link) { - flecs_json_member(buf, "link"); - flecs_json_string(buf, doc_link); + ecs_os_mutex_unlock(srv->lock); + + return request_count - 1; +} + +const char* ecs_http_get_header( + const ecs_http_request_t* req, + const char* name) +{ + 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; +} - if (desc && desc->serialize_color) { - const char *doc_color = ecs_doc_get_color(world, entity); - if (doc_color) { - flecs_json_member(buf, "color"); - flecs_json_string(buf, doc_color); +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; } } -#endif + return NULL; +} - const ecs_type_t *type = ecs_get_type(world, entity); - ecs_id_t *ids = NULL; - int32_t i, count = 0; - if (type) { - ids = type->array; - count = type->count; - } +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"); - if (!desc || desc->serialize_base) { - if (ecs_has_pair(world, entity, EcsIsA, EcsWildcard)) { - flecs_json_member(buf, "is_a"); - flecs_json_array_push(buf); + ecs_http_server_t* srv = ecs_os_calloc_t(ecs_http_server_t); + srv->lock = ecs_os_mutex_new(); + srv->sock = HTTP_SOCKET_INVALID; - 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_second(world, id), entity, desc)) - { - return -1; - } - } - } + srv->should_run = false; + srv->initialized = true; - flecs_json_array_pop(buf); - } - } + srv->callback = desc->callback; + srv->ctx = desc->ctx; + srv->port = desc->port; + srv->ipaddr = desc->ipaddr; - if (append_type(world, buf, entity, entity, desc)) { - goto error; - } + srv->connections = flecs_sparse_new(ecs_http_connection_impl_t); + srv->requests = flecs_sparse_new(ecs_http_request_impl_t); - flecs_json_object_pop(buf); + /* Start at id 1 */ + flecs_sparse_new_id(srv->connections); + flecs_sparse_new_id(srv->requests); - return 0; +#ifndef ECS_TARGET_WINDOWS + /* 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 -1; + return NULL; } -char* ecs_entity_to_json( - const ecs_world_t *world, - ecs_entity_t entity, - const ecs_entity_to_json_desc_t *desc) +void ecs_http_server_fini( + ecs_http_server_t* srv) { - ecs_strbuf_t buf = ECS_STRBUF_INIT; - - if (ecs_entity_to_json_buf(world, entity, &buf, desc) != 0) { - ecs_strbuf_reset(&buf); - return NULL; + if (srv->should_run) { + ecs_http_server_stop(srv); } - - return ecs_strbuf_get(&buf); + ecs_os_mutex_free(srv->lock); + flecs_sparse_free(srv->connections); + flecs_sparse_free(srv->requests); + ecs_os_free(srv); } -static -bool skip_variable( - const char *name) +int ecs_http_server_start( + ecs_http_server_t *srv) { - if (!name || name[0] == '_' || name[0] == '.') { - return true; - } else { - return false; + 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); + + srv->should_run = true; + + ecs_dbg("http: starting server thread"); + + srv->thread = ecs_os_thread_new(http_server_thread, srv); + if (!srv->thread) { + goto error; } -} -static -void serialize_id( - const ecs_world_t *world, - ecs_id_t id, - ecs_strbuf_t *buf) -{ - flecs_json_id(buf, world, id); + return 0; +error: + return -1; } -static -void serialize_iter_ids( - const ecs_world_t *world, - const ecs_iter_t *it, - ecs_strbuf_t *buf) +void ecs_http_server_stop( + ecs_http_server_t* srv) { - int32_t field_count = it->field_count; - if (!field_count) { - return; - } + 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); - flecs_json_member(buf, "ids"); - flecs_json_array_push(buf); + /* Stop server thread */ + ecs_dbg("http: shutting down server thread"); - for (int i = 0; i < field_count; i ++) { - flecs_json_next(buf); - serialize_id(world, it->terms[i].id, buf); + ecs_os_mutex_lock(srv->lock); + srv->should_run = false; + if (http_socket_is_valid(srv->sock)) { + http_close(&srv->sock); } + ecs_os_mutex_unlock(srv->lock); - flecs_json_array_pop(buf); -} + ecs_os_thread_join(srv->thread); + ecs_trace("http: server thread shut down"); -static -void serialize_type_info( - const ecs_world_t *world, - const ecs_iter_t *it, - ecs_strbuf_t *buf) -{ - int32_t field_count = it->field_count; - if (!field_count) { - return; + /* Cleanup all outstanding requests */ + int i, count = flecs_sparse_count(srv->requests); + for (i = count - 1; i >= 1; i --) { + request_free(flecs_sparse_get_dense( + srv->requests, ecs_http_request_impl_t, i)); } - flecs_json_member(buf, "type_info"); - flecs_json_object_push(buf); - - for (int i = 0; i < field_count; i ++) { - flecs_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"); - } + /* Close all connections */ + count = flecs_sparse_count(srv->connections); + for (i = count - 1; i >= 1; i --) { + connection_free(flecs_sparse_get_dense( + srv->connections, ecs_http_connection_impl_t, i)); } - flecs_json_object_pop(buf); -} + ecs_assert(flecs_sparse_count(srv->connections) == 1, + ECS_INTERNAL_ERROR, NULL); + ecs_assert(flecs_sparse_count(srv->requests) == 1, + ECS_INTERNAL_ERROR, NULL); -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; + srv->thread = 0; +error: + return; +} - for (int i = 0; i < var_count; i ++) { - const char *var_name = variable_names[i]; - if (skip_variable(var_name)) continue; +void ecs_http_server_dequeue( + ecs_http_server_t* srv, + ecs_ftime_t 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->dequeue_timeout += delta_time; + srv->stats_timeout += delta_time; - if (!actual_count) { - flecs_json_member(buf, "vars"); - flecs_json_array_push(buf); - actual_count ++; - } + if ((1000 * srv->dequeue_timeout) > + (ecs_ftime_t)ECS_HTTP_MIN_DEQUEUE_INTERVAL) + { + srv->dequeue_timeout = 0; - ecs_strbuf_list_next(buf); - flecs_json_string(buf, var_name); + ecs_time_t t = {0}; + ecs_time_measure(&t); + int32_t request_count = dequeue_requests(srv, srv->dequeue_timeout); + srv->requests_processed += request_count; + srv->requests_processed_total += request_count; + ecs_ftime_t time_spent = (ecs_ftime_t)ecs_time_measure(&t); + srv->request_time += time_spent; + srv->request_time_total += time_spent; + srv->dequeue_count ++; } - if (actual_count) { - flecs_json_array_pop(buf); + if ((1000 * srv->stats_timeout) > + (ecs_ftime_t)ECS_HTTP_MIN_STATS_INTERVAL) + { + srv->stats_timeout = 0; + ecs_dbg("http: processed %d requests in %.3fs (avg %.3fs / dequeue)", + srv->requests_processed, (double)srv->request_time, + (double)(srv->request_time / (ecs_ftime_t)srv->dequeue_count)); + srv->requests_processed = 0; + srv->request_time = 0; + srv->dequeue_count = 0; } + +error: + return; } -static -void serialize_iter_result_ids( - const ecs_world_t *world, - const ecs_iter_t *it, - ecs_strbuf_t *buf) -{ - flecs_json_member(buf, "ids"); - flecs_json_array_push(buf); +#endif - for (int i = 0; i < it->field_count; i ++) { - flecs_json_next(buf); - serialize_id(world, ecs_field_id(it, i + 1), buf); - } - flecs_json_array_pop(buf); + +#ifdef FLECS_DOC + +static ECS_COPY(EcsDocDescription, dst, src, { + ecs_os_strset((char**)&dst->value, src->value); + +}) + +static ECS_MOVE(EcsDocDescription, dst, src, { + ecs_os_free((char*)dst->value); + dst->value = src->value; + src->value = NULL; +}) + +static ECS_DTOR(EcsDocDescription, ptr, { + ecs_os_free((char*)ptr->value); +}) + +void ecs_doc_set_name( + ecs_world_t *world, + ecs_entity_t entity, + const char *name) +{ + ecs_set_pair(world, entity, EcsDocDescription, EcsName, { + .value = (char*)name + }); } -static -void serialize_iter_result_sources( - const ecs_world_t *world, - const ecs_iter_t *it, - ecs_strbuf_t *buf) +void ecs_doc_set_brief( + ecs_world_t *world, + ecs_entity_t entity, + const char *description) { - flecs_json_member(buf, "sources"); - flecs_json_array_push(buf); + ecs_set_pair(world, entity, EcsDocDescription, EcsDocBrief, { + .value = (char*)description + }); +} - for (int i = 0; i < it->field_count; i ++) { - flecs_json_next(buf); - ecs_entity_t subj = it->sources[i]; - if (subj) { - flecs_json_path(buf, world, subj); - } else { - flecs_json_literal(buf, "0"); - } - } +void ecs_doc_set_detail( + ecs_world_t *world, + ecs_entity_t entity, + const char *description) +{ + ecs_set_pair(world, entity, EcsDocDescription, EcsDocDetail, { + .value = (char*)description + }); +} - flecs_json_array_pop(buf); +void ecs_doc_set_link( + ecs_world_t *world, + ecs_entity_t entity, + const char *link) +{ + ecs_set_pair(world, entity, EcsDocDescription, EcsDocLink, { + .value = (char*)link + }); } -static -void serialize_iter_result_is_set( - const ecs_iter_t *it, - ecs_strbuf_t *buf) +void ecs_doc_set_color( + ecs_world_t *world, + ecs_entity_t entity, + const char *color) { - flecs_json_member(buf, "is_set"); - flecs_json_array_push(buf); + ecs_set_pair(world, entity, EcsDocDescription, EcsDocColor, { + .value = (char*)color + }); +} - for (int i = 0; i < it->field_count; i ++) { - ecs_strbuf_list_next(buf); - if (ecs_field_is_set(it, i + 1)) { - flecs_json_true(buf); - } else { - flecs_json_false(buf); - } +const char* ecs_doc_get_name( + const ecs_world_t *world, + ecs_entity_t entity) +{ + const EcsDocDescription *ptr = ecs_get_pair( + world, entity, EcsDocDescription, EcsName); + if (ptr) { + return ptr->value; + } else { + return ecs_get_name(world, entity); } - - flecs_json_array_pop(buf); } -static -void serialize_iter_result_variables( +const char* ecs_doc_get_brief( const ecs_world_t *world, - const ecs_iter_t *it, - ecs_strbuf_t *buf) + ecs_entity_t entity) { - char **variable_names = it->variable_names; - ecs_var_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) { - flecs_json_member(buf, "vars"); - flecs_json_array_push(buf); - actual_count ++; - } + const EcsDocDescription *ptr = ecs_get_pair( + world, entity, EcsDocDescription, EcsDocBrief); + if (ptr) { + return ptr->value; + } else { + return NULL; + } +} - ecs_strbuf_list_next(buf); - flecs_json_path(buf, world, variables[i].entity); +const char* ecs_doc_get_detail( + const ecs_world_t *world, + ecs_entity_t entity) +{ + const EcsDocDescription *ptr = ecs_get_pair( + world, entity, EcsDocDescription, EcsDocDetail); + if (ptr) { + return ptr->value; + } else { + return NULL; } +} - if (actual_count) { - flecs_json_array_pop(buf); +const char* ecs_doc_get_link( + const ecs_world_t *world, + ecs_entity_t entity) +{ + const EcsDocDescription *ptr = ecs_get_pair( + world, entity, EcsDocDescription, EcsDocLink); + if (ptr) { + return ptr->value; + } else { + return NULL; } } -static -void serialize_iter_result_variable_labels( +const char* ecs_doc_get_color( const ecs_world_t *world, - const ecs_iter_t *it, - ecs_strbuf_t *buf) + ecs_entity_t entity) { - char **variable_names = it->variable_names; - ecs_var_t *variables = it->variables; - int32_t var_count = it->variable_count; - int32_t actual_count = 0; + const EcsDocDescription *ptr = ecs_get_pair( + world, entity, EcsDocDescription, EcsDocColor); + if (ptr) { + return ptr->value; + } else { + return NULL; + } +} - for (int i = 0; i < var_count; i ++) { - const char *var_name = variable_names[i]; - if (skip_variable(var_name)) continue; +void FlecsDocImport( + ecs_world_t *world) +{ + ECS_MODULE(world, FlecsDoc); - if (!actual_count) { - flecs_json_member(buf, "var_labels"); - flecs_json_array_push(buf); - actual_count ++; - } + ecs_set_name_prefix(world, "EcsDoc"); - ecs_strbuf_list_next(buf); - flecs_json_label(buf, world, variables[i].entity); - } + flecs_bootstrap_component(world, EcsDocDescription); + flecs_bootstrap_tag(world, EcsDocBrief); + flecs_bootstrap_tag(world, EcsDocDetail); + flecs_bootstrap_tag(world, EcsDocLink); + flecs_bootstrap_tag(world, EcsDocColor); - if (actual_count) { - flecs_json_array_pop(buf); - } + ecs_set_hooks(world, EcsDocDescription, { + .ctor = ecs_default_ctor, + .move = ecs_move(EcsDocDescription), + .copy = ecs_copy(EcsDocDescription), + .dtor = ecs_dtor(EcsDocDescription) + }); + + ecs_add_id(world, ecs_id(EcsDocDescription), EcsDontInherit); } -static -void serialize_iter_result_variable_ids( - const ecs_iter_t *it, - ecs_strbuf_t *buf) -{ - char **variable_names = it->variable_names; - ecs_var_t *variables = it->variables; - int32_t var_count = it->variable_count; - int32_t actual_count = 0; +#endif - for (int i = 0; i < var_count; i ++) { - const char *var_name = variable_names[i]; - if (skip_variable(var_name)) continue; - if (!actual_count) { - flecs_json_member(buf, "var_ids"); - flecs_json_array_push(buf); - actual_count ++; - } +#ifdef FLECS_PARSER - ecs_strbuf_list_next(buf); - flecs_json_number(buf, (double)variables[i].entity); - } +#include - if (actual_count) { - flecs_json_array_pop(buf); - } -} +#define ECS_ANNOTATION_LENGTH_MAX (16) -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; - } +#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_BRACKET_OPEN '[' +#define TOK_BRACKET_CLOSE ']' +#define TOK_WILDCARD '*' +#define TOK_VARIABLE '$' +#define TOK_PAREN_OPEN '(' +#define TOK_PAREN_CLOSE ')' - flecs_json_member(buf, "entities"); - flecs_json_array_push(buf); +#define TOK_SELF "self" +#define TOK_UP "up" +#define TOK_DOWN "down" +#define TOK_CASCADE "cascade" +#define TOK_PARENT "parent" - ecs_entity_t *entities = it->entities; +#define TOK_OVERRIDE "OVERRIDE" - for (int i = 0; i < count; i ++) { - flecs_json_next(buf); - flecs_json_path(buf, world, entities[i]); - } +#define TOK_ROLE_AND "AND" +#define TOK_ROLE_OR "OR" +#define TOK_ROLE_NOT "NOT" +#define TOK_ROLE_TOGGLE "TOGGLE" - flecs_json_array_pop(buf); -} +#define TOK_IN "in" +#define TOK_OUT "out" +#define TOK_INOUT "inout" +#define TOK_INOUT_NONE "none" -static -void serialize_iter_result_entity_labels( - const ecs_world_t *world, - const ecs_iter_t *it, - ecs_strbuf_t *buf) +#define ECS_MAX_TOKEN_SIZE (256) + +typedef char ecs_token_t[ECS_MAX_TOKEN_SIZE]; + +const char* ecs_parse_eol_and_whitespace( + const char *ptr) { - int32_t count = it->count; - if (!it->count) { - return; + while (isspace(*ptr)) { + ptr ++; } - flecs_json_member(buf, "entity_labels"); - flecs_json_array_push(buf); - - ecs_entity_t *entities = it->entities; + return ptr; +} - for (int i = 0; i < count; i ++) { - flecs_json_next(buf); - flecs_json_label(buf, world, entities[i]); +/** Skip spaces when parsing signature */ +const char* ecs_parse_whitespace( + const char *ptr) +{ + while ((*ptr != '\n') && isspace(*ptr)) { + ptr ++; } - flecs_json_array_pop(buf); + return ptr; } -static -void serialize_iter_result_entity_ids( - const ecs_iter_t *it, - ecs_strbuf_t *buf) +const char* ecs_parse_digit( + const char *ptr, + char *token) { - int32_t count = it->count; - if (!it->count) { - return; + 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; } - flecs_json_member(buf, "entity_ids"); - flecs_json_array_push(buf); + tptr[0] = ch; + tptr ++; + ptr ++; - ecs_entity_t *entities = it->entities; + for (; (ch = *ptr); ptr ++) { + if (!isdigit(ch)) { + break; + } - for (int i = 0; i < count; i ++) { - flecs_json_next(buf); - flecs_json_number(buf, (double)entities[i]); + tptr[0] = ch; + tptr ++; } - flecs_json_array_pop(buf); + tptr[0] = '\0'; + + return ptr; } static -void serialize_iter_result_colors( - const ecs_world_t *world, - const ecs_iter_t *it, - ecs_strbuf_t *buf) +bool is_newline_comment( + const char *ptr) { - int32_t count = it->count; - if (!it->count) { - return; + if (ptr[0] == '/' && ptr[1] == '/') { + return true; } + return false; +} - flecs_json_member(buf, "colors"); - flecs_json_array_push(buf); +const char* ecs_parse_fluff( + const char *ptr, + char **last_comment) +{ + const char *last_comment_start = NULL; - ecs_entity_t *entities = it->entities; + do { + /* Skip whitespaces before checking for a comment */ + ptr = ecs_parse_whitespace(ptr); - for (int i = 0; i < count; i ++) { - flecs_json_next(buf); - flecs_json_color(buf, world, entities[i]); + /* Newline comment, skip until newline character */ + if (is_newline_comment(ptr)) { + ptr += 2; + last_comment_start = ptr; + + while (ptr[0] && ptr[0] != TOK_NEWLINE) { + ptr ++; + } + } + + /* If a newline character is found, skip it */ + if (ptr[0] == TOK_NEWLINE) { + ptr ++; + } + + } while (isspace(ptr[0]) || is_newline_comment(ptr)); + + if (last_comment) { + *last_comment = (char*)last_comment_start; } - flecs_json_array_pop(buf); + return ptr; } +/* -- Private functions -- */ + static -void serialize_iter_result_values( - const ecs_world_t *world, - const ecs_iter_t *it, - ecs_strbuf_t *buf) +bool valid_identifier_start_char( + char ch) { - flecs_json_member(buf, "values"); - flecs_json_array_push(buf); - - int32_t i, term_count = it->field_count; - for (i = 0; i < term_count; i ++) { - ecs_strbuf_list_next(buf); - - const void *ptr = NULL; - if (it->ptrs) { - ptr = it->ptrs[i]; - } - - if (!ptr) { - /* No data in column. Append 0 if this is not an optional term */ - if (ecs_field_is_set(it, i + 1)) { - flecs_json_literal(buf, "0"); - continue; - } - } - - if (ecs_field_is_writeonly(it, i + 1)) { - flecs_json_literal(buf, "0"); - 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. */ - flecs_json_literal(buf, "0"); - continue; - } + if (ch && (isalpha(ch) || (ch == '_') || (ch == '*') || + (ch == '0') || (ch == TOK_VARIABLE) || isdigit(ch))) + { + return true; + } - const EcsComponent *comp = ecs_get(world, type, EcsComponent); - if (!comp) { - /* Also odd, typeid but not a component? */ - flecs_json_literal(buf, "0"); - continue; - } + return false; +} - const EcsMetaTypeSerialized *ser = ecs_get( - world, type, EcsMetaTypeSerialized); - if (!ser) { - /* Not odd, component just has no reflection data */ - flecs_json_literal(buf, "0"); - continue; - } +static +bool valid_token_start_char( + char ch) +{ + if ((ch == '"') || (ch == '{') || (ch == '}') || (ch == ',') || (ch == '-') + || (ch == '[') || (ch == ']') || valid_identifier_start_char(ch)) + { + return true; + } - /* If term is not set, append empty array. This indicates that the term - * could have had data but doesn't */ - if (!ecs_field_is_set(it, i + 1)) { - ecs_assert(ptr == NULL, ECS_INTERNAL_ERROR, NULL); - flecs_json_array_push(buf); - flecs_json_array_pop(buf); - continue; - } + return false; +} - if (ecs_field_is_self(it, i + 1)) { - int32_t count = it->count; - 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); - } +static +bool valid_token_char( + char ch) +{ + if (ch && + (isalpha(ch) || isdigit(ch) || ch == '_' || ch == '.' || ch == '"')) + { + return true; } - flecs_json_array_pop(buf); + return false; } 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) +bool valid_operator_char( + char ch) { - flecs_json_next(buf); - flecs_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->serialize_ids) { - serialize_iter_result_ids(world, it, buf); + if (ch == TOK_OPTIONAL || ch == TOK_NOT) { + return true; } - /* Include information on which entity the term is matched with */ - if (!desc || desc->serialize_ids) { - serialize_iter_result_sources(world, it, buf); - } + return false; +} - /* Write variable values for current result */ - if (!desc || desc->serialize_variables) { - serialize_iter_result_variables(world, it, buf); - } +const char* ecs_parse_token( + const char *name, + const char *expr, + const char *ptr, + char *token_out) +{ + int64_t column = ptr - expr; - /* Write labels for variables */ - if (desc && desc->serialize_variable_labels) { - serialize_iter_result_variable_labels(world, it, buf); - } + ptr = ecs_parse_whitespace(ptr); + char *tptr = token_out, ch = ptr[0]; - /* Write ids for variables */ - if (desc && desc->serialize_variable_ids) { - serialize_iter_result_variable_ids(it, buf); + 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; } - /* Include information on which terms are set, to support optional terms */ - if (!desc || desc->serialize_is_set) { - serialize_iter_result_is_set(it, buf); - } + tptr[0] = ch; + tptr ++; + ptr ++; - /* Write entity ids for current result (for queries with This terms) */ - if (!desc || desc->serialize_entities) { - serialize_iter_result_entities(world, it, buf); + if (ch == '{' || ch == '}' || ch == '[' || ch == ']' || ch == ',') { + tptr[0] = 0; + return ptr; } - /* Write labels for entities */ - if (desc && desc->serialize_entity_labels) { - serialize_iter_result_entity_labels(world, it, buf); - } + int tmpl_nesting = 0; + bool in_str = ch == '"'; - /* Write ids for entities */ - if (desc && desc->serialize_entity_ids) { - serialize_iter_result_entity_ids(it, buf); + 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 ++; } - /* Write colors for entities */ - if (desc && desc->serialize_colors) { - serialize_iter_result_colors(world, it, buf); + tptr[0] = '\0'; + + if (tmpl_nesting != 0) { + ecs_parser_error(name, expr, column, + "identifier '%s' has mismatching < > pairs", ptr); + return NULL; } - /* Serialize component values */ - if (!desc || desc->serialize_values) { - serialize_iter_result_values(world, it, buf); + 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; } - flecs_json_object_pop(buf); + return ptr; } -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) +static +const char* ecs_parse_identifier( + const char *name, + const char *expr, + const char *ptr, + char *token_out) { - ecs_time_t duration = {0}; - if (desc && desc->measure_eval_duration) { - ecs_time_measure(&duration); + if (!valid_identifier_start_char(ptr[0])) { + ecs_parser_error(name, expr, (ptr - expr), + "expected start of identifier"); + return NULL; } - flecs_json_object_push(buf); + ptr = ecs_parse_token(name, expr, ptr, token_out); - /* Serialize component ids of the terms (usually provided by query) */ - if (!desc || desc->serialize_term_ids) { - serialize_iter_ids(world, it, buf); - } + return ptr; +} - /* Serialize type info if enabled */ - if (desc && desc->serialize_type_info) { - serialize_type_info(world, it, buf); +static +int parse_identifier( + const char *token, + ecs_term_id_t *out) +{ + const char *tptr = token; + if (tptr[0] == TOK_VARIABLE && tptr[1]) { + out->flags |= EcsIsVariable; + tptr ++; } - /* Serialize variable names, if iterator has any */ - serialize_iter_variables(it, buf); - - /* Serialize results */ - flecs_json_member(buf, "results"); - flecs_json_array_push(buf); + out->name = ecs_os_strdup(tptr); - /* Use instancing for improved performance */ - ECS_BIT_SET(it->flags, EcsIterIsInstanced); + return 0; +} - ecs_iter_next_action_t next = it->next; - while (next(it)) { - serialize_iter_result(world, it, buf, desc); +static +ecs_entity_t parse_role( + const char *name, + const char *sig, + int64_t column, + const char *token) +{ + 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_NOT)) { + return ECS_NOT; + } else if (!ecs_os_strcmp(token, TOK_OVERRIDE)) { + return ECS_OVERRIDE; + } else if (!ecs_os_strcmp(token, TOK_ROLE_TOGGLE)) { + return ECS_TOGGLE; + } else { + ecs_parser_error(name, sig, column, "invalid role '%s'", token); + return 0; } +} - flecs_json_array_pop(buf); - - if (desc && desc->measure_eval_duration) { - double dt = ecs_time_measure(&duration); - flecs_json_member(buf, "eval_duration"); - flecs_json_number(buf, dt); +static +ecs_oper_kind_t parse_operator( + char ch) +{ + if (ch == TOK_OPTIONAL) { + return EcsOptional; + } else if (ch == TOK_NOT) { + return EcsNot; + } else { + ecs_abort(ECS_INTERNAL_ERROR, NULL); } - - flecs_json_object_pop(buf); - - return 0; } -char* ecs_iter_to_json( - const ecs_world_t *world, - ecs_iter_t *it, - const ecs_iter_to_json_desc_t *desc) +static +const char* parse_annotation( + const char *name, + const char *sig, + int64_t column, + const char *ptr, + ecs_inout_kind_t *inout_kind_out) { - ecs_strbuf_t buf = ECS_STRBUF_INIT; + char token[ECS_MAX_TOKEN_SIZE]; - if (ecs_iter_to_json_buf(world, it, &buf, desc)) { - ecs_strbuf_reset(&buf); + ptr = ecs_parse_identifier(name, sig, ptr, token); + if (!ptr) { return NULL; } - return ecs_strbuf_get(&buf); -} + 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_NONE)) { + *inout_kind_out = EcsInOutNone; + } -#endif + ptr = ecs_parse_whitespace(ptr); + if (ptr[0] != TOK_BRACKET_CLOSE) { + ecs_parser_error(name, sig, column, "expected ]"); + return NULL; + } + return ptr + 1; +} -#ifdef FLECS_JSON +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_UP)) { + return EcsUp; + } else if (!ecs_os_strcmp(token, TOK_DOWN)) { + return EcsDown; + } else if (!ecs_os_strcmp(token, TOK_CASCADE)) { + return EcsCascade; + } else if (!ecs_os_strcmp(token, TOK_PARENT)) { + return EcsParent; + } else { + return 0; + } +} -const char* ecs_parse_json( +static +const char* parse_term_flags( const ecs_world_t *world, + const char *name, + const char *expr, + int64_t column, const char *ptr, - ecs_entity_t type, - void *data_out, - const ecs_parse_json_desc_t *desc) + char *token, + ecs_term_id_t *id, + char tok_end) { - char token[ECS_MAX_TOKEN_SIZE]; - int depth = 0; - - const char *name = NULL; - const char *expr = NULL; + 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; + } + } - ptr = ecs_parse_fluff(ptr, NULL); + do { + uint8_t tok = parse_set_token(token); + if (!tok) { + ecs_parser_error(name, expr, column, + "invalid set token '%s'", token); + return NULL; + } - ecs_meta_cursor_t cur = ecs_meta_cursor(world, type, data_out); - if (cur.valid == false) { - return NULL; - } + if (id->flags & tok) { + ecs_parser_error(name, expr, column, + "duplicate set token '%s'", token); + return NULL; + } + + id->flags |= tok; - if (desc) { - name = desc->name; - expr = desc->expr; - } + if (ptr[0] == TOK_PAREN_OPEN) { + ptr ++; - while ((ptr = ecs_parse_expr_token(name, expr, ptr, token))) { + /* 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; + } - ptr = ecs_parse_fluff(ptr, NULL); + id->trav = ecs_lookup_fullpath(world, token); + if (!id->trav) { + ecs_parser_error(name, expr, column, + "unresolved identifier '%s'", token); + return NULL; + } - if (!ecs_os_strcmp(token, "{")) { - depth ++; - if (ecs_meta_push(&cur) != 0) { - goto error; + 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; + } } - if (ecs_meta_is_collection(&cur)) { - ecs_parser_error(name, expr, ptr - expr, "expected '['"); - return NULL; + 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; + } } } - else if (!ecs_os_strcmp(token, "}")) { - depth --; - - if (ecs_meta_is_collection(&cur)) { - ecs_parser_error(name, expr, ptr - expr, "expected ']'"); - return NULL; + /* 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; + } } - if (ecs_meta_pop(&cur) != 0) { - goto error; - } + /* End of set expression */ + } else if (ptr[0] == tok_end || ptr[0] == TOK_AND || !ptr[0]) { + break; } + } while (true); - else if (!ecs_os_strcmp(token, "[")) { - depth ++; - if (ecs_meta_push(&cur) != 0) { - goto error; - } + return ptr; +} - if (!ecs_meta_is_collection(&cur)) { - ecs_parser_error(name, expr, ptr - expr, "expected '{'"); - return NULL; - } - } +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; - else if (!ecs_os_strcmp(token, "]")) { - depth --; + int32_t arg = 0; - if (!ecs_meta_is_collection(&cur)) { - ecs_parser_error(name, expr, ptr - expr, "expected '}'"); + 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; } - if (ecs_meta_pop(&cur) != 0) { - goto error; + ptr = ecs_parse_identifier(name, expr, ptr, token); + if (!ptr) { + return NULL; } - } - else if (!ecs_os_strcmp(token, ",")) { - if (ecs_meta_next(&cur) != 0) { - goto error; + ecs_term_id_t *term_id = NULL; + + if (arg == 0) { + term_id = &term->src; + } else if (arg == 1) { + term_id = &term->second; } - } - else if (!ecs_os_strcmp(token, "null")) { - if (ecs_meta_set_null(&cur) != 0) { - goto error; - } - } - - 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 \""); + /* 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; - } else { - token[len - 1] = '\0'; } - if (ecs_meta_member(&cur, token + 1) != 0) { - goto error; + ptr = ecs_parse_whitespace(ptr + 1); + ptr = parse_term_flags(world, name, expr, (ptr - expr), ptr, + NULL, term_id, TOK_PAREN_CLOSE); + if (!ptr) { + return NULL; } - } else { - if (ecs_meta_set_string_literal(&cur, token) != 0) { - goto error; + + /* Check for term flags */ + } else if (!ecs_os_strcmp(token, TOK_CASCADE) || + !ecs_os_strcmp(token, TOK_SELF) || + !ecs_os_strcmp(token, TOK_UP) || + !ecs_os_strcmp(token, TOK_DOWN) || + !(ecs_os_strcmp(token, TOK_PARENT))) + { + ptr = parse_term_flags(world, name, expr, (ptr - expr), ptr, + token, term_id, TOK_PAREN_CLOSE); + if (!ptr) { + return NULL; } - } - } - else { - if (ecs_meta_set_string(&cur, token) != 0) { - goto error; + /* Regular identifier */ + } else if (parse_identifier(token, term_id)) { + ecs_parser_error(name, expr, (ptr - expr), + "invalid identifier '%s'", token); + return NULL; } - } - - if (!depth) { - break; - } - } - - return ptr; -error: - return NULL; -} -#endif + if (ptr[0] == TOK_AND) { + ptr = ecs_parse_whitespace(ptr + 1); + term->id_flags = ECS_PAIR; -#ifdef FLECS_JSON + } else if (ptr[0] == TOK_PAREN_CLOSE) { + ptr = ecs_parse_whitespace(ptr + 1); + break; -void flecs_json_next( - ecs_strbuf_t *buf) -{ - ecs_strbuf_list_next(buf); -} + } else { + ecs_parser_error(name, expr, (ptr - expr), + "expected ',' or ')'"); + return NULL; + } -void flecs_json_literal( - ecs_strbuf_t *buf, - const char *value) -{ - ecs_strbuf_appendstr(buf, value); -} + } else { + ecs_parser_error(name, expr, (ptr - expr), + "expected identifier or set expression"); + return NULL; + } -void flecs_json_number( - ecs_strbuf_t *buf, - double value) -{ - ecs_strbuf_appendflt(buf, value, '"'); -} + arg ++; -void flecs_json_true( - ecs_strbuf_t *buf) -{ - flecs_json_literal(buf, "true"); -} + } while (true); -void flecs_json_false( - ecs_strbuf_t *buf) -{ - flecs_json_literal(buf, "false"); + return ptr; } -void flecs_json_bool( - ecs_strbuf_t *buf, - bool value) +static +void parser_unexpected_char( + const char *name, + const char *expr, + const char *ptr, + char ch) { - if (value) { - flecs_json_true(buf); + if (ch && (ch != '\n')) { + ecs_parser_error(name, expr, (ptr - expr), + "unexpected character '%c'", ch); } else { - flecs_json_false(buf); + ecs_parser_error(name, expr, (ptr - expr), + "unexpected end of term"); } } -void flecs_json_array_push( - ecs_strbuf_t *buf) +static +const char* parse_term( + const ecs_world_t *world, + const char *name, + const char *expr, + ecs_term_t *term_out) { - ecs_strbuf_list_push(buf, "[", ", "); -} + const char *ptr = expr; + char token[ECS_MAX_TOKEN_SIZE] = {0}; + ecs_term_t term = { .move = true /* parser never owns resources */ }; -void flecs_json_array_pop( - ecs_strbuf_t *buf) -{ - ecs_strbuf_list_pop(buf, "]"); -} + ptr = ecs_parse_whitespace(ptr); -void flecs_json_object_push( - ecs_strbuf_t *buf) -{ - ecs_strbuf_list_push(buf, "{", ", "); -} + /* 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); + } -void flecs_json_object_pop( - ecs_strbuf_t *buf) -{ - ecs_strbuf_list_pop(buf, "}"); -} + if (valid_operator_char(ptr[0])) { + term.oper = parse_operator(ptr[0]); + ptr = ecs_parse_whitespace(ptr + 1); + } -void flecs_json_string( - ecs_strbuf_t *buf, - const char *value) -{ - ecs_strbuf_appendch(buf, '"'); - ecs_strbuf_appendstr(buf, value); - ecs_strbuf_appendch(buf, '"'); -} + /* 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; + } -void flecs_json_member( - ecs_strbuf_t *buf, - const char *name) -{ - ecs_strbuf_list_appendstr(buf, "\""); - ecs_strbuf_appendstr(buf, name); - ecs_strbuf_appendstr(buf, "\":"); -} + /* Is token a type role? */ + if (ptr[0] == TOK_BITWISE_OR && ptr[1] != TOK_BITWISE_OR) { + ptr ++; + goto parse_role; + } -void flecs_json_path( - ecs_strbuf_t *buf, - const ecs_world_t *world, - ecs_entity_t e) -{ - ecs_strbuf_appendch(buf, '"'); - ecs_get_path_w_sep_buf(world, 0, e, ".", "", buf); - ecs_strbuf_appendch(buf, '"'); -} + /* Is token a predicate? */ + if (ptr[0] == TOK_PAREN_OPEN) { + goto parse_predicate; + } -void flecs_json_label( - ecs_strbuf_t *buf, - const ecs_world_t *world, - ecs_entity_t e) -{ - const char *lbl = NULL; -#ifdef FLECS_DOC - lbl = ecs_doc_get_name(world, e); -#else - lbl = ecs_get_name(world, e); -#endif + /* Next token must be a predicate */ + goto parse_predicate; - if (lbl) { - ecs_strbuf_appendch(buf, '"'); - ecs_strbuf_appendstr(buf, lbl); - ecs_strbuf_appendch(buf, '"'); + /* Pair with implicit subject */ + } else if (ptr[0] == TOK_PAREN_OPEN) { + goto parse_pair; + + /* Nothing else expected here */ } else { - ecs_strbuf_appendstr(buf, "0"); + parser_unexpected_char(name, expr, ptr, ptr[0]); + goto error; } -} -void flecs_json_color( - ecs_strbuf_t *buf, - const ecs_world_t *world, - ecs_entity_t e) -{ - (void)world; - (void)e; +parse_role: + term.id_flags = parse_role(name, expr, (ptr - expr), token); + if (!term.id_flags) { + goto error; + } - const char *color = NULL; -#ifdef FLECS_DOC - color = ecs_doc_get_color(world, e); -#endif + ptr = ecs_parse_whitespace(ptr); - if (color) { - ecs_strbuf_appendch(buf, '"'); - ecs_strbuf_appendstr(buf, color); - ecs_strbuf_appendch(buf, '"'); + /* 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; + + } else if (ptr[0] == TOK_PAREN_OPEN) { + goto parse_pair; } else { - ecs_strbuf_appendstr(buf, "0"); + ecs_parser_error(name, expr, (ptr - expr), + "expected identifier after role"); + goto error; } -} - -void flecs_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 flecs_json_op_to_primitive_kind( - ecs_meta_type_op_kind_t kind) -{ - return kind - EcsOpPrimitive; -} +parse_predicate: + if (parse_identifier(token, &term.first)) { + ecs_parser_error(name, expr, (ptr - expr), + "invalid identifier '%s'", token); + goto error; + } -#endif + /* Set expression */ + if (ptr[0] == TOK_COLON) { + ptr = ecs_parse_whitespace(ptr + 1); + ptr = parse_term_flags(world, name, expr, (ptr - expr), ptr, NULL, + &term.first, TOK_COLON); + if (!ptr) { + goto error; + } + ptr = ecs_parse_whitespace(ptr); -#ifdef FLECS_JSON + if (ptr[0] == TOK_AND || !ptr[0]) { + goto parse_done; + } -static -int json_typeinfo_ser_type( - const ecs_world_t *world, - ecs_entity_t type, - ecs_strbuf_t *buf); + if (ptr[0] != TOK_COLON) { + ecs_parser_error(name, expr, (ptr - expr), + "unexpected token '%c' after predicate set expression", ptr[0]); + goto error; + } -static -int json_typeinfo_ser_primitive( - ecs_primitive_kind_t kind, - ecs_strbuf_t *str) -{ - switch(kind) { - case EcsBool: - flecs_json_string(str, "bool"); - break; - case EcsChar: - case EcsString: - flecs_json_string(str, "text"); - break; - case EcsByte: - flecs_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: - flecs_json_string(str, "int"); - break; - case EcsF32: - case EcsF64: - flecs_json_string(str, "float"); - break; - case EcsEntity: - flecs_json_string(str, "entity"); - break; - default: - return -1; + ptr = ecs_parse_whitespace(ptr + 1); + } else { + ptr = ecs_parse_whitespace(ptr); } - 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 ++) { - flecs_json_next(str); - flecs_json_string(str, ecs_get_name(world, it.entities[i])); + if (ptr[0] == TOK_PAREN_OPEN) { + ptr ++; + if (ptr[0] == TOK_PAREN_CLOSE) { + term.src.flags = EcsIsEntity; + term.src.id = 0; + ptr ++; + ptr = ecs_parse_whitespace(ptr); + } else { + ptr = parse_arguments( + world, name, expr, (ptr - expr), ptr, token, &term); } + + goto parse_done; } -} -static -void json_typeinfo_ser_enum( - const ecs_world_t *world, - ecs_entity_t type, - ecs_strbuf_t *str) -{ - ecs_strbuf_list_appendstr(str, "\"enum\""); - json_typeinfo_ser_constants(world, type, str); -} + goto parse_done; -static -void json_typeinfo_ser_bitmask( - const ecs_world_t *world, - ecs_entity_t type, - ecs_strbuf_t *str) -{ - ecs_strbuf_list_appendstr(str, "\"bitmask\""); - json_typeinfo_ser_constants(world, type, str); -} +parse_pair: + ptr = ecs_parse_identifier(name, expr, ptr + 1, token); + if (!ptr) { + goto error; + } -static -int json_typeinfo_ser_array( - const ecs_world_t *world, - ecs_entity_t elem_type, - int32_t count, - ecs_strbuf_t *str) -{ - ecs_strbuf_list_appendstr(str, "\"array\""); + if (ptr[0] == TOK_AND) { + ptr ++; + term.src.id = EcsThis; + term.src.flags |= EcsIsVariable; + goto parse_pair_predicate; + } else if (ptr[0] == TOK_PAREN_CLOSE) { + term.src.id = EcsThis; + term.src.flags |= EcsIsVariable; + goto parse_pair_predicate; + } else { + parser_unexpected_char(name, expr, ptr, ptr[0]); + goto error; + } - flecs_json_next(str); - if (json_typeinfo_ser_type(world, elem_type, str)) { +parse_pair_predicate: + if (parse_identifier(token, &term.first)) { + ecs_parser_error(name, expr, (ptr - expr), + "invalid identifier '%s'", token); goto error; } - ecs_strbuf_list_append(str, "%u", count); - return 0; -error: - return -1; -} + ptr = ecs_parse_whitespace(ptr); + if (valid_token_start_char(ptr[0])) { + ptr = ecs_parse_identifier(name, expr, ptr, token); + if (!ptr) { + goto error; + } -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)) { + 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; } - return 0; +parse_pair_object: + if (parse_identifier(token, &term.second)) { + ecs_parser_error(name, expr, (ptr - expr), + "invalid identifier '%s'", token); + goto error; + } + + if (term.id_flags != 0) { + if (!ECS_HAS_ID_FLAG(term.id_flags, PAIR)) { + ecs_parser_error(name, expr, (ptr - expr), + "invalid combination of role '%s' with pair", + ecs_id_flag_str(term.id_flags)); + goto error; + } + } else { + term.id_flags = ECS_PAIR; + } + + ptr = ecs_parse_whitespace(ptr); + goto parse_done; + +parse_done: + *term_out = term; + return ptr; + error: - return -1; + ecs_term_fini(&term); + *term_out = (ecs_term_t){0}; + return NULL; } static -int json_typeinfo_ser_vector( - const ecs_world_t *world, - ecs_entity_t type, - ecs_strbuf_t *str) +bool is_valid_end_of_term( + const char *ptr) { - const EcsVector *arr = ecs_get(world, type, EcsVector); - ecs_assert(arr != NULL, ECS_INTERNAL_ERROR, NULL); - - ecs_strbuf_list_appendstr(str, "\"vector\""); - - flecs_json_next(str); - if (json_typeinfo_ser_type(world, arr->type, str)) { - goto error; + 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 0; -error: - return -1; + return false; } -/* Serialize unit information */ -static -int json_typeinfo_ser_unit( +char* ecs_parse_term( const ecs_world_t *world, - ecs_strbuf_t *str, - ecs_entity_t unit) + const char *name, + const char *expr, + const char *ptr, + ecs_term_t *term) { - flecs_json_member(str, "unit"); - flecs_json_path(str, world, unit); + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(ptr != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(term != NULL, ECS_INVALID_PARAMETER, NULL); - const EcsUnit *uptr = ecs_get(world, unit, EcsUnit); - if (uptr) { - if (uptr->symbol) { - flecs_json_member(str, "symbol"); - flecs_json_string(str, uptr->symbol); - } - ecs_entity_t quantity = ecs_get_target(world, unit, EcsQuantity, 0); - if (quantity) { - flecs_json_member(str, "quantity"); - flecs_json_path(str, world, quantity); + ecs_term_id_t *src = &term->src; + + 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"); + } } } + + ptr = ecs_parse_eol_and_whitespace(ptr); + if (!ptr[0]) { + *term = (ecs_term_t){0}; + return (char*)ptr; + } - return 0; -} + if (ptr == expr && !strcmp(expr, "0")) { + return (char*)&ptr[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) -{ - flecs_json_array_push(str); + /* Parse next element */ + ptr = parse_term(world, name, ptr, term); + if (!ptr) { + goto error; + } - 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( - flecs_json_op_to_primitive_kind(op->kind), str)) - { - /* Unknown operation */ - ecs_throw(ECS_INTERNAL_ERROR, NULL); - return -1; + /* Check for $() notation */ + if (!ecs_os_strcmp(term->first.name, "$")) { + if (term->src.name) { + ecs_os_free(term->first.name); + + term->first = term->src; + + if (term->second.name) { + term->src = term->second; + } else { + term->src.id = EcsThis; + term->src.name = NULL; + term->src.flags |= EcsIsVariable; + } + + term->second.name = ecs_os_strdup(term->first.name); + term->second.flags |= EcsIsVariable; } - break; } - ecs_entity_t unit = op->unit; - if (unit) { - flecs_json_next(str); - flecs_json_next(str); + /* Post-parse consistency checks */ - flecs_json_object_push(str); - json_typeinfo_ser_unit(world, str, unit); - flecs_json_object_pop(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; + } + + term->oper = EcsOr; } - flecs_json_array_pop(str); + /* 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; + } - return 0; -error: - return -1; -} + /* If the term just contained a 0, the expression has nothing. Ensure + * that after the 0 nothing else follows */ + if (!ecs_os_strcmp(term->first.name, "0")) { + if (ptr[0]) { + ecs_parser_error(name, expr, (ptr - expr), + "unexpected term after 0"); + goto error; + } -/* 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 (src->flags != 0) { + ecs_parser_error(name, expr, (ptr - expr), + "invalid combination of 0 with non-default subject"); + goto error; + } - if (op != ops) { - if (op->name) { - flecs_json_member(str, op->name); - } + src->flags = EcsIsEntity; + src->id = 0; + ecs_os_free(term->first.name); + term->first.name = NULL; + } - int32_t elem_count = op->count; - if (elem_count > 1 && op != ops) { - flecs_json_array_push(str); - json_typeinfo_ser_array(world, op->type, op->count, str); - flecs_json_array_pop(str); - i += op->op_count - 1; - continue; - } - } - - switch(op->kind) { - case EcsOpPush: - flecs_json_object_push(str); - break; - case EcsOpPop: - flecs_json_object_pop(str); - break; - default: - if (json_typeinfo_ser_type_op(world, op, str)) { - goto error; + /* Cannot combine EcsNothing with operators other than AND */ + if (term->oper != EcsAnd && ecs_term_match_0(term)) { + ecs_parser_error(name, expr, (ptr - expr), + "invalid operator for empty source"); + goto error; + } + + /* Verify consistency of OR expression */ + if (prev_or && term->oper == EcsOr) { + term->oper = EcsOr; + } + + /* Automatically assign This if entity is not assigned and the set is + * nothing */ + if (!(src->flags & EcsIsEntity)) { + if (!src->name) { + if (!src->id) { + src->id = EcsThis; + src->flags |= EcsIsVariable; } - 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; + if (src->name && !ecs_os_strcmp(src->name, "0")) { + src->id = 0; + src->flags = EcsIsEntity; } - const EcsMetaTypeSerialized *ser = ecs_get( - world, type, EcsMetaTypeSerialized); - if (!ser) { - ecs_strbuf_appendstr(buf, "0"); - return 0; + /* Process role */ + if (term->id_flags == ECS_AND) { + term->oper = EcsAndFrom; + term->id_flags = 0; + } else if (term->id_flags == ECS_OR) { + term->oper = EcsOrFrom; + term->id_flags = 0; + } else if (term->id_flags == ECS_NOT) { + term->oper = EcsNotFrom; + term->id_flags = 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); + ptr = ecs_parse_whitespace(ptr); - return json_typeinfo_ser_type_ops(world, ops, count, buf); + return (char*)ptr; +error: + if (term) { + ecs_term_fini(term); + } + return NULL; } -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); -} +#endif -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; - } +#ifdef FLECS_META_C - return ecs_strbuf_get(&str); -} +#include -#endif +#define ECS_META_IDENTIFIER_LENGTH (256) +#define ecs_meta_error(ctx, ptr, ...)\ + ecs_parser_error((ctx)->name, (ctx)->desc, ptr - (ctx)->desc, __VA_ARGS__); +typedef char ecs_meta_token_t[ECS_META_IDENTIFIER_LENGTH]; -#ifdef FLECS_SYSTEM -#endif +typedef struct meta_parse_ctx_t { + const char *name; + const char *desc; +} meta_parse_ctx_t; -#ifdef FLECS_PIPELINE -#ifndef FLECS_PIPELINE_PRIVATE_H -#define FLECS_PIPELINE_PRIVATE_H +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 meta_member_t { + meta_type_t type; + ecs_meta_token_t name; + int64_t count; + bool is_partial; +} meta_member_t; -/** 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; +typedef struct meta_constant_t { + ecs_meta_token_t name; + int64_t value; + bool is_value_set; +} meta_constant_t; -typedef struct { - ecs_query_t *query; /* Pipeline query */ - - ecs_vector_t *ops; /* Pipeline schedule */ - int32_t match_count; /* Used to track of rebuild is necessary */ - int32_t rebuild_count; /* Number of pipeline rebuilds */ - ecs_entity_t last_system; /* Last system ran by pipeline */ +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; - ecs_id_record_t *idr_inactive; /* Cached record for quick inactive test */ +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_iter_t *iters; /* Iterator for worker(s) */ - int32_t iter_count; + while ((ch = *ptr)) { + if (ch == '(' || ch == '<') { + stack[sp] = ch; - /* Members for continuing pipeline iteration after pipeline rebuild */ - ecs_pipeline_op_t *cur_op; /* Current pipeline op */ - int32_t cur_i; /* Index in current result */ -} EcsPipeline; + 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; + } + } -//////////////////////////////////////////////////////////////////////////////// -//// Pipeline API -//////////////////////////////////////////////////////////////////////////////// + ptr ++; -/** 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 (!sp) { + break; + } + } -void ecs_pipeline_reset_iter( - ecs_world_t *world, - EcsPipeline *q); + return ptr; +error: + return NULL; +} -//////////////////////////////////////////////////////////////////////////////// -//// Worker API -//////////////////////////////////////////////////////////////////////////////// +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) { + goto error; + } -void ecs_worker_begin( - ecs_world_t *world); + *value_out = strtol(token, NULL, 0); -bool ecs_worker_sync( - ecs_world_t *world, - EcsPipeline *p); + return ecs_parse_eol_and_whitespace(ptr); +error: + return NULL; +} -void ecs_worker_end( - ecs_world_t *world); +static +const char* parse_c_identifier( + const char *ptr, + char *buff, + char *params, + meta_parse_ctx_t *ctx) +{ + ecs_assert(ptr != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(buff != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(ctx != NULL, ECS_INTERNAL_ERROR, NULL); -void ecs_workers_progress( - ecs_world_t *world, - ecs_entity_t pipeline, - ecs_ftime_t delta_time); + char *bptr = buff, ch; -#endif + if (params) { + params[0] = '\0'; + } -#endif + /* Ignore whitespaces */ + ptr = ecs_parse_eol_and_whitespace(ptr); -#ifdef FLECS_STATS + if (!isalpha(*ptr)) { + ecs_meta_error(ctx, ptr, + "invalid identifier (starts with '%c')", *ptr); + goto error; + } -#define ECS_GAUGE_RECORD(m, t, value)\ - flecs_gauge_record(m, t, (ecs_float_t)(value)) + while ((ch = *ptr) && !isspace(ch) && ch != ';' && ch != ',' && ch != ')' && ch != '>' && ch != '}') { + /* Type definitions can contain macros or templates */ + if (ch == '(' || ch == '<') { + if (!params) { + ecs_meta_error(ctx, ptr, "unexpected %c", *ptr); + goto error; + } -#define ECS_COUNTER_RECORD(m, t, value)\ - flecs_counter_record(m, t, (ecs_float_t)(value)) + const char *end = skip_scope(ptr, ctx); + ecs_os_strncpy(params, ptr, (ecs_size_t)(end - ptr)); + params[end - ptr] = '\0'; -#define ECS_METRIC_FIRST(stats)\ - ECS_CAST(ecs_metric_t*, ECS_OFFSET(&stats->first_, ECS_SIZEOF(int32_t))) + ptr = end; + } else { + *bptr = ch; + bptr ++; + ptr ++; + } + } -#define ECS_METRIC_LAST(stats)\ - ECS_CAST(ecs_metric_t*, ECS_OFFSET(&stats->last_, -ECS_SIZEOF(ecs_metric_t))) + *bptr = '\0'; -static -int32_t t_next( - int32_t t) -{ - return (t + 1) % ECS_STAT_WINDOW; + if (!ch) { + ecs_meta_error(ctx, ptr, "unexpected end of token"); + goto error; + } + + return ptr; +error: + return NULL; } static -int32_t t_prev( - int32_t t) -{ - return (t - 1 + ECS_STAT_WINDOW) % ECS_STAT_WINDOW; -} - -static -void flecs_gauge_record( - ecs_metric_t *m, - int32_t t, - ecs_float_t value) +const char * meta_open_scope( + const char *ptr, + meta_parse_ctx_t *ctx) { - m->gauge.avg[t] = value; - m->gauge.min[t] = value; - m->gauge.max[t] = value; -} + /* Skip initial whitespaces */ + ptr = ecs_parse_eol_and_whitespace(ptr); -static -ecs_float_t flecs_counter_record( - ecs_metric_t *m, - int32_t t, - ecs_float_t value) -{ - int32_t tp = t_prev(t); - ecs_float_t prev = m->counter.value[tp]; - m->counter.value[t] = value; - flecs_gauge_record(m, t, value - prev); - return value - prev; -} + /* 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; + } -static -void flecs_metric_print( - const char *name, - ecs_float_t value) -{ - ecs_size_t len = ecs_os_strlen(name); - ecs_trace("%s: %*s %.2f", name, 32 - len, "", (double)value); -} + ptr ++; + ptr = ecs_parse_eol_and_whitespace(ptr); + } -static -void flecs_gauge_print( - const char *name, - int32_t t, - const ecs_metric_t *m) -{ - flecs_metric_print(name, m->gauge.avg[t]); + /* Is this the end of the type definition? */ + if (!*ptr) { + ecs_meta_error(ctx, ptr, "missing '}' at end of struct definition"); + goto error; + } + + /* 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 -void flecs_counter_print( - const char *name, - int32_t t, - const ecs_metric_t *m) -{ - flecs_metric_print(name, m->counter.rate.avg[t]); -} +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 ecs_metric_reduce( - ecs_metric_t *dst, - const ecs_metric_t *src, - int32_t t_dst, - int32_t t_src) -{ - ecs_check(dst != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(src != NULL, ECS_INVALID_PARAMETER, NULL); + token->is_value_set = false; - bool min_set = false; - dst->gauge.avg[t_dst] = 0; - dst->gauge.min[t_dst] = 0; - dst->gauge.max[t_dst] = 0; + /* Parse token, constant identifier */ + ptr = parse_c_identifier(ptr, token->name, NULL, ctx); + if (!ptr) { + return NULL; + } - ecs_float_t fwindow = (ecs_float_t)ECS_STAT_WINDOW; + ptr = ecs_parse_eol_and_whitespace(ptr); + if (!ptr) { + return NULL; + } - int32_t i; - for (i = 0; i < ECS_STAT_WINDOW; i ++) { - int32_t t = (t_src + i) % ECS_STAT_WINDOW; - dst->gauge.avg[t_dst] += src->gauge.avg[t] / fwindow; + /* 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 (!min_set || (src->gauge.min[t] < dst->gauge.min[t_dst])) { - dst->gauge.min[t_dst] = src->gauge.min[t]; - min_set = true; - } - if ((src->gauge.max[t] > dst->gauge.max[t_dst])) { - dst->gauge.max[t_dst] = src->gauge.max[t]; - } + /* Expect a ',' or '}' */ + if (*ptr != ',' && *ptr != '}') { + ecs_meta_error(ctx, ptr, "missing , after enum constant"); + goto error; } - - dst->counter.value[t_dst] = src->counter.value[t_src]; + if (*ptr == ',') { + return ptr + 1; + } else { + return ptr; + } error: - return; + return NULL; } -void ecs_metric_reduce_last( - ecs_metric_t *m, - int32_t prev, - int32_t count) +static +const char* meta_parse_type( + const char *ptr, + meta_type_t *token, + meta_parse_ctx_t *ctx) { - ecs_check(m != NULL, ECS_INVALID_PARAMETER, NULL); - int32_t t = t_next(prev); + token->is_ptr = false; + token->is_const = false; - if (m->gauge.min[t] < m->gauge.min[prev]) { - m->gauge.min[prev] = m->gauge.min[t]; + ptr = ecs_parse_eol_and_whitespace(ptr); + + /* Parse token, expect type identifier or ECS_PROPERTY */ + ptr = parse_c_identifier(ptr, token->type, token->params, ctx); + if (!ptr) { + goto error; } - if (m->gauge.max[t] > m->gauge.max[prev]) { - m->gauge.max[prev] = m->gauge.max[t]; + if (!strcmp(token->type, "ECS_PRIVATE")) { + /* Members from this point are not stored in metadata */ + ptr += ecs_os_strlen(ptr); + goto done; } - ecs_float_t fcount = (ecs_float_t)(count + 1); - ecs_float_t cur = m->gauge.avg[prev]; - ecs_float_t next = m->gauge.avg[t]; + /* If token is const, set const flag and continue parsing type */ + if (!strcmp(token->type, "const")) { + token->is_const = true; - cur *= ((fcount - 1) / fcount); - next *= 1 / fcount; + /* Parse type after const */ + ptr = parse_c_identifier(ptr + 1, token->type, token->params, ctx); + } - m->gauge.avg[prev] = cur + next; - m->counter.value[prev] = m->counter.value[t]; + /* 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; + return NULL; } -void ecs_metric_copy( - ecs_metric_t *m, - int32_t dst, - int32_t src) +static +const char* meta_parse_member( + const char *ptr, + meta_member_t *token, + meta_parse_ctx_t *ctx) { - ecs_check(m != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(dst != src, ECS_INVALID_PARAMETER, NULL); + ptr = meta_open_scope(ptr, ctx); + if (!ptr) { + return NULL; + } - m->gauge.avg[dst] = m->gauge.avg[src]; - m->gauge.min[dst] = m->gauge.min[src]; - m->gauge.max[dst] = m->gauge.max[src]; - m->counter.value[dst] = m->counter.value[src]; + token->count = 1; + token->is_partial = false; -error: - return; -} + /* Parse member type */ + ptr = meta_parse_type(ptr, &token->type, ctx); + if (!ptr) { + token->is_partial = true; + goto error; + } -static -void flecs_stats_reduce( - ecs_metric_t *dst_cur, - ecs_metric_t *dst_last, - ecs_metric_t *src_cur, - int32_t t_dst, - int32_t t_src) -{ - for (; dst_cur <= dst_last; dst_cur ++, src_cur ++) { - ecs_metric_reduce(dst_cur, src_cur, t_dst, t_src); + /* Next token is the identifier */ + ptr = parse_c_identifier(ptr, token->name, NULL, ctx); + if (!ptr) { + goto error; } -} -static -void flecs_stats_reduce_last( - ecs_metric_t *dst_cur, - ecs_metric_t *dst_last, - ecs_metric_t *src_cur, - int32_t t_dst, - int32_t t_src, - int32_t count) -{ - int32_t t_dst_next = t_next(t_dst); - for (; dst_cur <= dst_last; dst_cur ++, src_cur ++) { - /* Reduce into previous value */ - ecs_metric_reduce_last(dst_cur, t_dst, count); + /* Skip whitespace between member and [ or ; */ + ptr = ecs_parse_eol_and_whitespace(ptr); - /* Restore old value */ - dst_cur->gauge.avg[t_dst_next] = src_cur->gauge.avg[t_src]; - dst_cur->gauge.min[t_dst_next] = src_cur->gauge.min[t_src]; - dst_cur->gauge.max[t_dst_next] = src_cur->gauge.max[t_src]; - dst_cur->counter.value[t_dst_next] = src_cur->counter.value[t_src]; + /* 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 flecs_stats_repeat_last( - ecs_metric_t *cur, - ecs_metric_t *last, - int32_t t) -{ - int32_t prev = t_prev(t); - for (; cur <= last; cur ++) { - ecs_metric_copy(cur, t, prev); + 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; + + } else if (array_end - array_start == 0) { + ecs_meta_error(ctx, ptr, "dynamic size arrays are not supported"); + goto error; + } + + token->count = atoi(array_start + 1); + + 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'; + } } + + /* Expect a ; */ + if (*ptr != ';') { + ecs_meta_error(ctx, ptr, "missing ; after member declaration"); + goto error; + } + + return ptr + 1; +error: + return NULL; } static -void flecs_stats_copy_last( - ecs_metric_t *dst_cur, - ecs_metric_t *dst_last, - ecs_metric_t *src_cur, - int32_t t_dst, - int32_t t_src) +int meta_parse_desc( + const char *ptr, + meta_params_t *token, + meta_parse_ctx_t *ctx) { - for (; dst_cur <= dst_last; dst_cur ++, src_cur ++) { - dst_cur->gauge.avg[t_dst] = src_cur->gauge.avg[t_src]; - dst_cur->gauge.min[t_dst] = src_cur->gauge.min[t_src]; - dst_cur->gauge.max[t_dst] = src_cur->gauge.max[t_src]; - dst_cur->counter.value[t_dst] = src_cur->counter.value[t_src]; + 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; } -} -void ecs_world_stats_get( - const ecs_world_t *world, - ecs_world_stats_t *s) -{ - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(s != NULL, ECS_INVALID_PARAMETER, NULL); + ptr ++; - world = ecs_get_world(world); + /* Parse type identifier */ + ptr = meta_parse_type(ptr, &token->type, ctx); + if (!ptr) { + goto error; + } - int32_t t = s->t = t_next(s->t); + ptr = ecs_parse_eol_and_whitespace(ptr); - ecs_ftime_t delta_world_time = ECS_COUNTER_RECORD(&s->world_time_total_raw, t, world->info.world_time_total_raw); - ECS_COUNTER_RECORD(&s->world_time_total, t, world->info.world_time_total); - ECS_COUNTER_RECORD(&s->frame_time_total, t, world->info.frame_time_total); - ECS_COUNTER_RECORD(&s->system_time_total, t, world->info.system_time_total); - ECS_COUNTER_RECORD(&s->merge_time_total, t, world->info.merge_time_total); + /* 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; + } - ecs_ftime_t delta_frame_count = ECS_COUNTER_RECORD(&s->frame_count_total, t, world->info.frame_count_total); - ECS_COUNTER_RECORD(&s->merge_count_total, t, world->info.merge_count_total); - ECS_COUNTER_RECORD(&s->pipeline_build_count_total, t, world->info.pipeline_build_count_total); - ECS_COUNTER_RECORD(&s->systems_ran_frame, t, world->info.systems_ran_frame); + token->count = value; + token->is_fixed_size = true; + } else { + token->key_type = token->type; - if (delta_world_time != 0 && delta_frame_count != 0) { - ECS_GAUGE_RECORD( - &s->fps, t, (ecs_ftime_t)1 / (delta_world_time / (ecs_ftime_t)delta_frame_count)); - } else { - ECS_GAUGE_RECORD(&s->fps, t, 0); + /* Parse element type */ + ptr = meta_parse_type(ptr, &token->type, ctx); + ptr = ecs_parse_eol_and_whitespace(ptr); + + token->is_key_value = true; + } } - ECS_GAUGE_RECORD(&s->delta_time, t, delta_world_time); + if (*ptr != ')' && *ptr != '>') { + ecs_meta_error(ctx, ptr, + "expected ')' at end of collection definition"); + goto error; + } - ECS_GAUGE_RECORD(&s->entity_count, t, flecs_sparse_count(ecs_eis(world))); - ECS_GAUGE_RECORD(&s->entity_not_alive_count, t, - flecs_sparse_not_alive_count(ecs_eis(world))); + return 0; +error: + return -1; +} - ECS_GAUGE_RECORD(&s->id_count, t, world->info.id_count); - ECS_GAUGE_RECORD(&s->tag_id_count, t, world->info.tag_id_count); - ECS_GAUGE_RECORD(&s->component_id_count, t, world->info.component_id_count); - ECS_GAUGE_RECORD(&s->pair_id_count, t, world->info.pair_id_count); - ECS_GAUGE_RECORD(&s->wildcard_id_count, t, world->info.wildcard_id_count); - ECS_GAUGE_RECORD(&s->component_count, t, ecs_sparse_count(world->type_info)); +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_GAUGE_RECORD(&s->query_count, t, ecs_count_id(world, EcsQuery)); - ECS_GAUGE_RECORD(&s->observer_count, t, ecs_count_id(world, EcsObserver)); - ECS_GAUGE_RECORD(&s->system_count, t, ecs_count_id(world, EcsSystem)); +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 + }; - ECS_COUNTER_RECORD(&s->id_create_count, t, world->info.id_create_total); - ECS_COUNTER_RECORD(&s->id_delete_count, t, world->info.id_delete_total); - ECS_COUNTER_RECORD(&s->table_create_count, t, world->info.table_create_total); - ECS_COUNTER_RECORD(&s->table_delete_count, t, world->info.table_delete_total); + 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; + } - ECS_COUNTER_RECORD(&s->new_count, t, world->info.new_count); - ECS_COUNTER_RECORD(&s->bulk_new_count, t, world->info.bulk_new_count); - ECS_COUNTER_RECORD(&s->delete_count, t, world->info.delete_count); - ECS_COUNTER_RECORD(&s->clear_count, t, world->info.clear_count); - ECS_COUNTER_RECORD(&s->add_count, t, world->info.add_count); - ECS_COUNTER_RECORD(&s->remove_count, t, world->info.remove_count); - ECS_COUNTER_RECORD(&s->set_count, t, world->info.set_count); - ECS_COUNTER_RECORD(&s->discard_count, t, world->info.discard_count); + if (!params.count) { + ecs_meta_error(ctx, params_decl, "invalid array size"); + goto error; + } - ECS_GAUGE_RECORD(&s->table_count, t, world->info.table_count); - ECS_GAUGE_RECORD(&s->empty_table_count, t, world->info.empty_table_count); - ECS_GAUGE_RECORD(&s->tag_table_count, t, world->info.tag_table_count); - ECS_GAUGE_RECORD(&s->trivial_table_count, t, world->info.trivial_table_count); - ECS_GAUGE_RECORD(&s->table_storage_count, t, world->info.table_storage_count); - ECS_GAUGE_RECORD(&s->table_record_count, t, world->info.table_record_count); + 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); + } - ECS_COUNTER_RECORD(&s->alloc_count, t, ecs_os_api_malloc_count + - ecs_os_api_calloc_count); - ECS_COUNTER_RECORD(&s->realloc_count, t, ecs_os_api_realloc_count); - ECS_COUNTER_RECORD(&s->free_count, t, ecs_os_api_free_count); + if (!e) { + e = ecs_new_id(world); + } - int64_t outstanding_allocs = ecs_os_api_malloc_count + - ecs_os_api_calloc_count - ecs_os_api_free_count; - ECS_GAUGE_RECORD(&s->outstanding_alloc_count, t, outstanding_allocs); + ecs_check(params.count <= INT32_MAX, ECS_INVALID_PARAMETER, NULL); + return ecs_set(world, e, EcsArray, { element_type, (int32_t)params.count }); error: - return; + return 0; } -void ecs_world_stats_reduce( - ecs_world_stats_t *dst, - const ecs_world_stats_t *src) +static +ecs_entity_t meta_lookup_vector( + ecs_world_t *world, + ecs_entity_t e, + const char *params_decl, + meta_parse_ctx_t *ctx) { - flecs_stats_reduce(ECS_METRIC_FIRST(dst), ECS_METRIC_LAST(dst), - ECS_METRIC_FIRST(src), (dst->t = t_next(dst->t)), src->t); -} + meta_parse_ctx_t param_ctx = { + .name = ctx->name, + .desc = params_decl + }; -void ecs_world_stats_reduce_last( - ecs_world_stats_t *dst, - const ecs_world_stats_t *src, - int32_t count) -{ - flecs_stats_reduce_last(ECS_METRIC_FIRST(dst), ECS_METRIC_LAST(dst), - ECS_METRIC_FIRST(src), (dst->t = t_prev(dst->t)), src->t, count); -} + meta_params_t params; + if (meta_parse_desc(params_decl, ¶ms, ¶m_ctx)) { + goto error; + } -void ecs_world_stats_repeat_last( - ecs_world_stats_t *stats) -{ - flecs_stats_repeat_last(ECS_METRIC_FIRST(stats), ECS_METRIC_LAST(stats), - (stats->t = t_next(stats->t))); -} + if (params.is_key_value) { + ecs_meta_error(ctx, params_decl, + "unexpected key value parameters for vector"); + goto error; + } -void ecs_world_stats_copy_last( - ecs_world_stats_t *dst, - const ecs_world_stats_t *src) -{ - flecs_stats_copy_last(ECS_METRIC_FIRST(dst), ECS_METRIC_LAST(dst), - ECS_METRIC_FIRST(src), dst->t, t_next(src->t)); -} - -void ecs_query_stats_get( - const ecs_world_t *world, - const ecs_query_t *query, - ecs_query_stats_t *s) -{ - 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; - - int32_t t = s->t = t_next(s->t); + ecs_entity_t element_type = meta_lookup( + world, ¶ms.type, params_decl, 1, ¶m_ctx); - if (query->filter.flags & EcsFilterMatchThis) { - ECS_GAUGE_RECORD(&s->matched_entity_count, t, - ecs_query_entity_count(query)); - ECS_GAUGE_RECORD(&s->matched_table_count, t, - ecs_query_table_count(query)); - ECS_GAUGE_RECORD(&s->matched_empty_table_count, t, - ecs_query_empty_table_count(query)); - } else { - ECS_GAUGE_RECORD(&s->matched_entity_count, t, 0); - ECS_GAUGE_RECORD(&s->matched_table_count, t, 0); - ECS_GAUGE_RECORD(&s->matched_empty_table_count, t, 0); + if (!e) { + e = ecs_new_id(world); } + return ecs_set(world, e, EcsVector, { element_type }); error: - return; -} - -void ecs_query_stats_reduce( - ecs_query_stats_t *dst, - const ecs_query_stats_t *src) -{ - flecs_stats_reduce(ECS_METRIC_FIRST(dst), ECS_METRIC_LAST(dst), - ECS_METRIC_FIRST(src), (dst->t = t_next(dst->t)), src->t); -} - -void ecs_query_stats_reduce_last( - ecs_query_stats_t *dst, - const ecs_query_stats_t *src, - int32_t count) -{ - flecs_stats_reduce_last(ECS_METRIC_FIRST(dst), ECS_METRIC_LAST(dst), - ECS_METRIC_FIRST(src), (dst->t = t_prev(dst->t)), src->t, count); -} - -void ecs_query_stats_repeat_last( - ecs_query_stats_t *stats) -{ - flecs_stats_repeat_last(ECS_METRIC_FIRST(stats), ECS_METRIC_LAST(stats), - (stats->t = t_next(stats->t))); + return 0; } -void ecs_query_stats_copy_last( - ecs_query_stats_t *dst, - const ecs_query_stats_t *src) +static +ecs_entity_t meta_lookup_bitmask( + ecs_world_t *world, + ecs_entity_t e, + const char *params_decl, + meta_parse_ctx_t *ctx) { - flecs_stats_copy_last(ECS_METRIC_FIRST(dst), ECS_METRIC_LAST(dst), - ECS_METRIC_FIRST(src), dst->t, t_next(src->t)); -} - -#ifdef FLECS_SYSTEM + (void)e; -bool ecs_system_stats_get( - const ecs_world_t *world, - ecs_entity_t system, - ecs_system_stats_t *s) -{ - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(s != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(system != 0, ECS_INVALID_PARAMETER, NULL); + meta_parse_ctx_t param_ctx = { + .name = ctx->name, + .desc = params_decl + }; - world = ecs_get_world(world); + meta_params_t params; + if (meta_parse_desc(params_decl, ¶ms, ¶m_ctx)) { + goto error; + } - const ecs_system_t *ptr = ecs_poly_get(world, system, ecs_system_t); - if (!ptr) { - return false; + if (params.is_key_value) { + ecs_meta_error(ctx, params_decl, + "unexpected key value parameters for bitmask"); + goto error; } - ecs_query_stats_get(world, ptr->query, &s->query); - int32_t t = s->query.t; + if (params.is_fixed_size) { + ecs_meta_error(ctx, params_decl, + "unexpected size for bitmask"); + goto error; + } - ECS_COUNTER_RECORD(&s->time_spent, t, ptr->time_spent); - ECS_COUNTER_RECORD(&s->invoke_count, t, ptr->invoke_count); - ECS_GAUGE_RECORD(&s->active, t, !ecs_has_id(world, system, EcsEmpty)); - ECS_GAUGE_RECORD(&s->enabled, t, !ecs_has_id(world, system, EcsDisabled)); + ecs_entity_t bitmask_type = meta_lookup( + world, ¶ms.type, params_decl, 1, ¶m_ctx); + ecs_check(bitmask_type != 0, ECS_INVALID_PARAMETER, NULL); - s->task = !(ptr->query->filter.flags & EcsFilterMatchThis); +#ifndef FLECS_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 true; + return bitmask_type; error: - return false; + return 0; } -void ecs_system_stats_reduce( - ecs_system_stats_t *dst, - const ecs_system_stats_t *src) +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_query_stats_reduce(&dst->query, &src->query); - dst->task = src->task; - flecs_stats_reduce(ECS_METRIC_FIRST(dst), ECS_METRIC_LAST(dst), - ECS_METRIC_FIRST(src), dst->query.t, src->query.t); -} + 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); -void ecs_system_stats_reduce_last( - ecs_system_stats_t *dst, - const ecs_system_stats_t *src, - int32_t count) -{ - ecs_query_stats_reduce_last(&dst->query, &src->query, count); - dst->task = src->task; - flecs_stats_reduce_last(ECS_METRIC_FIRST(dst), ECS_METRIC_LAST(dst), - ECS_METRIC_FIRST(src), dst->query.t, src->query.t, count); -} + const char *typename = token->type; + ecs_entity_t type = 0; -void ecs_system_stats_repeat_last( - ecs_system_stats_t *stats) -{ - ecs_query_stats_repeat_last(&stats->query); - flecs_stats_repeat_last(ECS_METRIC_FIRST(stats), ECS_METRIC_LAST(stats), - (stats->query.t)); -} + /* Parse vector type */ + if (!token->is_ptr) { + if (!ecs_os_strcmp(typename, "ecs_array")) { + type = meta_lookup_array(world, 0, token->params, ctx); -void ecs_system_stats_copy_last( - ecs_system_stats_t *dst, - const ecs_system_stats_t *src) -{ - ecs_query_stats_copy_last(&dst->query, &src->query); - dst->task = src->task; - flecs_stats_copy_last(ECS_METRIC_FIRST(dst), ECS_METRIC_LAST(dst), - ECS_METRIC_FIRST(src), dst->query.t, t_next(src->query.t)); -} + } else if (!ecs_os_strcmp(typename, "ecs_vector") || + !ecs_os_strcmp(typename, "flecs::vector")) + { + type = meta_lookup_vector(world, 0, token->params, ctx); -#endif + } else if (!ecs_os_strcmp(typename, "flecs::bitmask")) { + type = meta_lookup_bitmask(world, 0, token->params, ctx); -#ifdef FLECS_PIPELINE + } else if (!ecs_os_strcmp(typename, "flecs::byte")) { + type = ecs_id(ecs_byte_t); -bool ecs_pipeline_stats_get( - ecs_world_t *stage, - ecs_entity_t pipeline, - ecs_pipeline_stats_t *s) -{ - ecs_check(stage != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(s != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(pipeline != 0, ECS_INVALID_PARAMETER, NULL); + } else if (!ecs_os_strcmp(typename, "char")) { + type = ecs_id(ecs_char_t); - const ecs_world_t *world = ecs_get_world(stage); - const EcsPipeline *pq = ecs_get(world, pipeline, EcsPipeline); - if (!pq) { - return false; - } + } else if (!ecs_os_strcmp(typename, "bool") || + !ecs_os_strcmp(typename, "_Bool")) + { + type = ecs_id(ecs_bool_t); - int32_t sys_count = 0, active_sys_count = 0; + } 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); - /* Count number of active systems */ - ecs_iter_t it = ecs_query_iter(stage, pq->query); - while (ecs_query_next(&it)) { - if (flecs_id_record_get_table(pq->idr_inactive, it.table) != NULL) { - continue; + } 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(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(typename, "ecs_entity_t")) { + type = ecs_id(ecs_entity_t); + + } 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"; } - active_sys_count += it.count; - } - /* Count total number of systems in pipeline */ - it = ecs_query_iter(stage, pq->query); - while (ecs_query_next(&it)) { - sys_count += it.count; - } + type = ecs_lookup_symbol(world, typename, true); + } - /* 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 (count != 1) { + ecs_check(count <= INT32_MAX, ECS_INVALID_PARAMETER, NULL); - if (!sys_count) { - return false; + type = ecs_set(world, 0, EcsArray, {type, (int32_t)count}); } - if (ecs_map_is_initialized(&s->system_stats) && !sys_count) { - ecs_map_fini(&s->system_stats); + if (!type) { + ecs_meta_error(ctx, ptr, "unknown type '%s'", typename); + goto error; } - ecs_map_init_if(&s->system_stats, ecs_system_stats_t, sys_count); - /* 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); + return type; +error: + return 0; +} - /* 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)) { - if (flecs_id_record_get_table(pq->idr_inactive, it.table) != NULL) { - continue; - } +static +int meta_parse_struct( + ecs_world_t *world, + ecs_entity_t t, + const char *desc) +{ + const char *ptr = desc; + const char *name = ecs_get_name(world, t); - 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 */ - } - } - } + meta_member_t token; + meta_parse_ctx_t ctx = { + .name = name, + .desc = ptr + }; - 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; - } + ecs_entity_t old_scope = ecs_set_scope(world, t); - /* Separately populate system stats map from build query, which includes - * systems that aren't currently active */ - it = ecs_query_iter(stage, pq->query); - while (ecs_query_next(&it)) { - int i; - for (i = 0; i < it.count; i ++) { - ecs_system_stats_t *sys_stats = ecs_map_ensure( - &s->system_stats, ecs_system_stats_t, it.entities[i]); - sys_stats->query.t = s->t; - ecs_system_stats_get(world, it.entities[i], sys_stats); + 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 + }); + + 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 + }); } - s->t = t_next(s->t); + ecs_set_scope(world, old_scope); - return true; + return 0; error: - return false; + return -1; } -void ecs_pipeline_stats_fini( - ecs_pipeline_stats_t *stats) +static +int meta_parse_constants( + ecs_world_t *world, + ecs_entity_t t, + const char *desc, + bool is_bitmask) { - ecs_map_fini(&stats->system_stats); - ecs_vector_free(stats->systems); -} + ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(t != 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(desc != NULL, ECS_INTERNAL_ERROR, NULL); -void ecs_pipeline_stats_reduce( - ecs_pipeline_stats_t *dst, - const ecs_pipeline_stats_t *src) -{ - int32_t system_count = ecs_vector_count(src->systems); - ecs_vector_set_count(&dst->systems, ecs_entity_t, system_count); - ecs_entity_t *dst_systems = ecs_vector_first(dst->systems, ecs_entity_t); - ecs_entity_t *src_systems = ecs_vector_first(src->systems, ecs_entity_t); - ecs_os_memcpy_n(dst_systems, src_systems, ecs_entity_t, system_count); + const char *ptr = desc; + const char *name = ecs_get_name(world, t); - ecs_map_init_if(&dst->system_stats, ecs_system_stats_t, - ecs_map_count(&src->system_stats)); + meta_parse_ctx_t ctx = { + .name = name, + .desc = ptr + }; - ecs_map_iter_t it = ecs_map_iter(&src->system_stats); - ecs_system_stats_t *sys_src, *sys_dst; - ecs_entity_t key; - while ((sys_src = ecs_map_next(&it, ecs_system_stats_t, &key))) { - sys_dst = ecs_map_ensure(&dst->system_stats, ecs_system_stats_t, key); - sys_dst->query.t = dst->t; - ecs_system_stats_reduce(sys_dst, sys_src); - } - dst->t = t_next(dst->t); -} + meta_constant_t token; + int64_t last_value = 0; -void ecs_pipeline_stats_reduce_last( - ecs_pipeline_stats_t *dst, - const ecs_pipeline_stats_t *src, - int32_t count) -{ - ecs_map_init_if(&dst->system_stats, ecs_system_stats_t, - ecs_map_count(&src->system_stats)); + ecs_entity_t old_scope = ecs_set_scope(world, t); - ecs_map_iter_t it = ecs_map_iter(&src->system_stats); - ecs_system_stats_t *sys_src, *sys_dst; - ecs_entity_t key; - while ((sys_src = ecs_map_next(&it, ecs_system_stats_t, &key))) { - sys_dst = ecs_map_ensure(&dst->system_stats, ecs_system_stats_t, key); - sys_dst->query.t = dst->t; - ecs_system_stats_reduce_last(sys_dst, sys_src, count); + 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; + } + + 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}); + } + + last_value ++; } - dst->t = t_prev(dst->t); + + ecs_set_scope(world, old_scope); + + return 0; +error: + return -1; } -void ecs_pipeline_stats_repeat_last( - ecs_pipeline_stats_t *stats) +static +int meta_parse_enum( + ecs_world_t *world, + ecs_entity_t t, + const char *desc) { - ecs_map_iter_t it = ecs_map_iter(&stats->system_stats); - ecs_system_stats_t *sys; - ecs_entity_t key; - while ((sys = ecs_map_next(&it, ecs_system_stats_t, &key))) { - sys->query.t = stats->t; - ecs_system_stats_repeat_last(sys); - } - stats->t = t_next(stats->t); + ecs_add(world, t, EcsEnum); + return meta_parse_constants(world, t, desc, false); } -void ecs_pipeline_stats_copy_last( - ecs_pipeline_stats_t *dst, - const ecs_pipeline_stats_t *src) +static +int meta_parse_bitmask( + ecs_world_t *world, + ecs_entity_t t, + const char *desc) { - ecs_map_init_if(&dst->system_stats, ecs_system_stats_t, - ecs_map_count(&src->system_stats)); + ecs_add(world, t, EcsBitmask); + return meta_parse_constants(world, t, desc, true); +} - ecs_map_iter_t it = ecs_map_iter(&src->system_stats); - ecs_system_stats_t *sys_src, *sys_dst; - ecs_entity_t key; - while ((sys_src = ecs_map_next(&it, ecs_system_stats_t, &key))) { - sys_dst = ecs_map_ensure(&dst->system_stats, ecs_system_stats_t, key); - sys_dst->query.t = dst->t; - ecs_system_stats_copy_last(sys_dst, sys_src); +int ecs_meta_from_desc( + ecs_world_t *world, + ecs_entity_t component, + ecs_type_kind_t kind, + const char *desc) +{ + 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; } + + return 0; +error: + return -1; } #endif -void ecs_world_stats_log( - const ecs_world_t *world, - const ecs_world_stats_t *s) -{ - int32_t t = s->t; - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(s != NULL, ECS_INVALID_PARAMETER, NULL); - - world = ecs_get_world(world); - - flecs_counter_print("Frame", t, &s->frame_count_total); - ecs_trace("-------------------------------------"); - flecs_counter_print("pipeline rebuilds", t, &s->pipeline_build_count_total); - flecs_counter_print("systems invocations", t, &s->systems_ran_frame); - ecs_trace(""); - flecs_metric_print("target FPS", world->info.target_fps); - flecs_metric_print("time scale", world->info.time_scale); - ecs_trace(""); - flecs_gauge_print("actual FPS", t, &s->fps); - flecs_counter_print("frame time", t, &s->frame_time_total); - flecs_counter_print("system time", t, &s->system_time_total); - flecs_counter_print("merge time", t, &s->merge_time_total); - flecs_counter_print("simulation time elapsed", t, &s->world_time_total); - ecs_trace(""); - flecs_gauge_print("id count", t, &s->id_count); - flecs_gauge_print("tag id count", t, &s->tag_id_count); - flecs_gauge_print("component id count", t, &s->component_id_count); - flecs_gauge_print("pair id count", t, &s->pair_id_count); - flecs_gauge_print("wildcard id count", t, &s->wildcard_id_count); - flecs_gauge_print("component count", t, &s->component_count); - ecs_trace(""); - flecs_gauge_print("alive entity count", t, &s->entity_count); - flecs_gauge_print("not alive entity count", t, &s->entity_not_alive_count); - ecs_trace(""); - flecs_gauge_print("query count", t, &s->query_count); - flecs_gauge_print("observer count", t, &s->observer_count); - flecs_gauge_print("system count", t, &s->system_count); - ecs_trace(""); - flecs_gauge_print("table count", t, &s->table_count); - flecs_gauge_print("empty table count", t, &s->empty_table_count); - flecs_gauge_print("tag table count", t, &s->tag_table_count); - flecs_gauge_print("trivial table count", t, &s->trivial_table_count); - flecs_gauge_print("table storage count", t, &s->table_storage_count); - flecs_gauge_print("table cache record count", t, &s->table_record_count); - ecs_trace(""); - flecs_counter_print("table create count", t, &s->table_create_count); - flecs_counter_print("table delete count", t, &s->table_delete_count); - flecs_counter_print("id create count", t, &s->id_create_count); - flecs_counter_print("id delete count", t, &s->id_delete_count); - ecs_trace(""); - flecs_counter_print("deferred new operations", t, &s->new_count); - flecs_counter_print("deferred bulk_new operations", t, &s->bulk_new_count); - flecs_counter_print("deferred delete operations", t, &s->delete_count); - flecs_counter_print("deferred clear operations", t, &s->clear_count); - flecs_counter_print("deferred add operations", t, &s->add_count); - flecs_counter_print("deferred remove operations", t, &s->remove_count); - flecs_counter_print("deferred set operations", t, &s->set_count); - flecs_counter_print("discarded operations", t, &s->discard_count); - ecs_trace(""); - -error: - return; -} - -#endif - - -#ifdef FLECS_APP +#ifdef FLECS_APP static int default_run_action( @@ -36725,13531 +35899,14364 @@ int ecs_app_set_frame_action( #endif -#ifdef FLECS_RULES +/* Id flags */ +const ecs_id_t ECS_PAIR = (1ull << 63); +const ecs_id_t ECS_OVERRIDE = (1ull << 62); +const ecs_id_t ECS_TOGGLE = (1ull << 61); +const ecs_id_t ECS_AND = (1ull << 60); +const ecs_id_t ECS_OR = (1ull << 59); +const ecs_id_t ECS_NOT = (1ull << 58); -#define ECS_RULE_MAX_VAR_COUNT (32) +/** Builtin component ids */ +const ecs_entity_t ecs_id(EcsComponent) = 1; +const ecs_entity_t ecs_id(EcsIdentifier) = 2; +const ecs_entity_t ecs_id(EcsIterable) = 3; +const ecs_entity_t ecs_id(EcsPoly) = 4; -#define RULE_PAIR_PREDICATE (1) -#define RULE_PAIR_OBJECT (2) +const ecs_entity_t EcsQuery = 5; +const ecs_entity_t EcsObserver = 7; -/* 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 id; - } first; - union { - int32_t reg; - ecs_entity_t id; - } second; - int32_t reg_mask; /* bit 1 = predicate, bit 2 = object */ +/* System module component ids */ +const ecs_entity_t EcsSystem = 10; +const ecs_entity_t ecs_id(EcsTickSource) = 11; - bool transitive; /* Is predicate transitive */ - bool final; /* Is predicate final */ - bool reflexive; /* Is predicate reflexive */ - bool acyclic; /* Is predicate acyclic */ - bool second_0; -} ecs_rule_pair_t; +/** Timer module component ids */ +const ecs_entity_t ecs_id(EcsTimer) = 13; +const ecs_entity_t ecs_id(EcsRateFilter) = 14; -/* 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 */ +/** 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; +const ecs_entity_t ecs_id(EcsUnit) = 24; +const ecs_entity_t ecs_id(EcsUnitPrefix) = 25; - bool wildcard; /* Does the filter contain wildcards */ - bool first_wildcard; /* Is predicate a wildcard */ - bool second_wildcard; /* Is object a wildcard */ - bool same_var; /* True if first & second are both the same variable */ +/* 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 EcsFlecsInternals = ECS_HI_COMPONENT_ID + 3; +const ecs_entity_t EcsModule = ECS_HI_COMPONENT_ID + 4; +const ecs_entity_t EcsPrivate = ECS_HI_COMPONENT_ID + 5; +const ecs_entity_t EcsPrefab = ECS_HI_COMPONENT_ID + 6; +const ecs_entity_t EcsDisabled = ECS_HI_COMPONENT_ID + 7; - 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; +const ecs_entity_t EcsSlotOf = ECS_HI_COMPONENT_ID + 8; +const ecs_entity_t EcsFlag = ECS_HI_COMPONENT_ID + 9; -/* 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; +/* Relationship properties */ +const ecs_entity_t EcsWildcard = ECS_HI_COMPONENT_ID + 10; +const ecs_entity_t EcsAny = ECS_HI_COMPONENT_ID + 11; +const ecs_entity_t EcsThis = ECS_HI_COMPONENT_ID + 12; +const ecs_entity_t EcsVariable = ECS_HI_COMPONENT_ID + 13; -/* 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 */ - EcsRuleInTable, /* Test if entity (subject) is in table (r_in) */ - EcsRuleEq, /* Test if entity in (subject) and (r_in) are equal */ - EcsRuleYield /* Yield result */ -} ecs_rule_op_kind_t; +const ecs_entity_t EcsTransitive = ECS_HI_COMPONENT_ID + 14; +const ecs_entity_t EcsReflexive = ECS_HI_COMPONENT_ID + 15; +const ecs_entity_t EcsSymmetric = ECS_HI_COMPONENT_ID + 16; +const ecs_entity_t EcsFinal = ECS_HI_COMPONENT_ID + 17; +const ecs_entity_t EcsDontInherit = ECS_HI_COMPONENT_ID + 18; +const ecs_entity_t EcsTag = ECS_HI_COMPONENT_ID + 19; +const ecs_entity_t EcsUnion = ECS_HI_COMPONENT_ID + 20; +const ecs_entity_t EcsExclusive = ECS_HI_COMPONENT_ID + 21; +const ecs_entity_t EcsAcyclic = ECS_HI_COMPONENT_ID + 22; +const ecs_entity_t EcsWith = ECS_HI_COMPONENT_ID + 23; +const ecs_entity_t EcsOneOf = ECS_HI_COMPONENT_ID + 24; -/* 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 */ +/* Builtin relationships */ +const ecs_entity_t EcsChildOf = ECS_HI_COMPONENT_ID + 25; +const ecs_entity_t EcsIsA = ECS_HI_COMPONENT_ID + 26; +const ecs_entity_t EcsDependsOn = ECS_HI_COMPONENT_ID + 27; - int32_t on_pass; /* Jump location when match succeeds */ - int32_t on_fail; /* Jump location when match fails */ - int32_t frame; /* Register frame */ +/* Identifier tags */ +const ecs_entity_t EcsName = ECS_HI_COMPONENT_ID + 30; +const ecs_entity_t EcsSymbol = ECS_HI_COMPONENT_ID + 31; +const ecs_entity_t EcsAlias = ECS_HI_COMPONENT_ID + 32; - int32_t term; /* Corresponding term index in signature */ - int32_t r_in; /* Optional In/Out registers */ - int32_t r_out; +/* Events */ +const ecs_entity_t EcsOnAdd = ECS_HI_COMPONENT_ID + 33; +const ecs_entity_t EcsOnRemove = ECS_HI_COMPONENT_ID + 34; +const ecs_entity_t EcsOnSet = ECS_HI_COMPONENT_ID + 35; +const ecs_entity_t EcsUnSet = ECS_HI_COMPONENT_ID + 36; +const ecs_entity_t EcsOnDelete = ECS_HI_COMPONENT_ID + 37; +const ecs_entity_t EcsOnCreateTable = ECS_HI_COMPONENT_ID + 38; +const ecs_entity_t EcsOnDeleteTable = ECS_HI_COMPONENT_ID + 39; +const ecs_entity_t EcsOnTableEmpty = ECS_HI_COMPONENT_ID + 40; +const ecs_entity_t EcsOnTableFill = ECS_HI_COMPONENT_ID + 41; +const ecs_entity_t EcsOnCreateTrigger = ECS_HI_COMPONENT_ID + 42; +const ecs_entity_t EcsOnDeleteTrigger = ECS_HI_COMPONENT_ID + 43; +const ecs_entity_t EcsOnDeleteObservable = ECS_HI_COMPONENT_ID + 44; +const ecs_entity_t EcsOnComponentHooks = ECS_HI_COMPONENT_ID + 45; +const ecs_entity_t EcsOnDeleteTarget = ECS_HI_COMPONENT_ID + 46; - 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; +/* 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 EcsPanic = ECS_HI_COMPONENT_ID + 52; -/* With context. Shared with select. */ -typedef struct ecs_rule_with_ctx_t { - ecs_id_record_t *idr; /* Currently evaluated table set */ - ecs_table_cache_iter_t it; - int32_t column; -} ecs_rule_with_ctx_t; +/* Misc */ +const ecs_entity_t EcsDefaultChildComponent = ECS_HI_COMPONENT_ID + 55; -/* 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; +/* Systems */ +const ecs_entity_t EcsMonitor = ECS_HI_COMPONENT_ID + 61; +const ecs_entity_t EcsEmpty = ECS_HI_COMPONENT_ID + 63; +const ecs_entity_t ecs_id(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; -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; +const ecs_entity_t EcsPhase = ECS_HI_COMPONENT_ID + 75; -/* Superset context */ -typedef struct ecs_rule_superset_frame_t { - ecs_table_t *table; - int32_t column; -} ecs_rule_superset_frame_t; +/* Meta primitive components (don't use low ids to save id space) */ +const ecs_entity_t ecs_id(ecs_bool_t) = ECS_HI_COMPONENT_ID + 80; +const ecs_entity_t ecs_id(ecs_char_t) = ECS_HI_COMPONENT_ID + 81; +const ecs_entity_t ecs_id(ecs_byte_t) = ECS_HI_COMPONENT_ID + 82; +const ecs_entity_t ecs_id(ecs_u8_t) = ECS_HI_COMPONENT_ID + 83; +const ecs_entity_t ecs_id(ecs_u16_t) = ECS_HI_COMPONENT_ID + 84; +const ecs_entity_t ecs_id(ecs_u32_t) = ECS_HI_COMPONENT_ID + 85; +const ecs_entity_t ecs_id(ecs_u64_t) = ECS_HI_COMPONENT_ID + 86; +const ecs_entity_t ecs_id(ecs_uptr_t) = ECS_HI_COMPONENT_ID + 87; +const ecs_entity_t ecs_id(ecs_i8_t) = ECS_HI_COMPONENT_ID + 88; +const ecs_entity_t ecs_id(ecs_i16_t) = ECS_HI_COMPONENT_ID + 89; +const ecs_entity_t ecs_id(ecs_i32_t) = ECS_HI_COMPONENT_ID + 90; +const ecs_entity_t ecs_id(ecs_i64_t) = ECS_HI_COMPONENT_ID + 91; +const ecs_entity_t ecs_id(ecs_iptr_t) = ECS_HI_COMPONENT_ID + 92; +const ecs_entity_t ecs_id(ecs_f32_t) = ECS_HI_COMPONENT_ID + 93; +const ecs_entity_t ecs_id(ecs_f64_t) = ECS_HI_COMPONENT_ID + 94; +const ecs_entity_t ecs_id(ecs_string_t) = ECS_HI_COMPONENT_ID + 95; +const ecs_entity_t ecs_id(ecs_entity_t) = ECS_HI_COMPONENT_ID + 96; +const ecs_entity_t EcsConstant = ECS_HI_COMPONENT_ID + 97; +const ecs_entity_t EcsQuantity = ECS_HI_COMPONENT_ID + 98; -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; +/* 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; +const ecs_entity_t EcsDocColor = ECS_HI_COMPONENT_ID + 104; -/* Each context */ -typedef struct ecs_rule_each_ctx_t { - int32_t row; /* Currently evaluated row in evaluated table */ -} ecs_rule_each_ctx_t; +/* REST module components */ +const ecs_entity_t ecs_id(EcsRest) = ECS_HI_COMPONENT_ID + 105; -/* Jump context */ -typedef struct ecs_rule_setjmp_ctx_t { - int32_t label; /* Operation label to jump to */ -} ecs_rule_setjmp_ctx_t; +/* Default lookup path */ +static ecs_entity_t ecs_default_lookup_path[2] = { 0, 0 }; -/* 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; +/* -- Private functions -- */ -/* 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 other; /* Id to table variable (-1 if none exists) */ - 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; +const ecs_stage_t* flecs_stage_from_readonly_world( + const ecs_world_t *world) +{ + ecs_assert(ecs_poly_is(world, ecs_world_t) || + ecs_poly_is(world, ecs_stage_t), + ECS_INTERNAL_ERROR, + NULL); -/* Variable ids per term */ -typedef struct ecs_rule_term_vars_t { - int32_t first; - int32_t src; - int32_t second; -} ecs_rule_term_vars_t; + if (ecs_poly_is(world, ecs_world_t)) { + return &world->stages[0]; -/* Top-level rule datastructure */ -struct ecs_rule_t { - ecs_header_t hdr; + } else if (ecs_poly_is(world, ecs_stage_t)) { + return (ecs_stage_t*)world; + } - ecs_world_t *world; /* Ref to world so rule can be used by itself */ - ecs_rule_op_t *operations; /* Operations array */ - ecs_filter_t filter; /* Filter of rule */ + return NULL; +} - /* Variable array */ - ecs_rule_var_t vars[ECS_RULE_MAX_VAR_COUNT]; +ecs_stage_t *flecs_stage_from_world( + ecs_world_t **world_ptr) +{ + ecs_world_t *world = *world_ptr; - /* Passed to iterator */ - char *var_names[ECS_RULE_MAX_VAR_COUNT]; + ecs_assert(ecs_poly_is(world, ecs_world_t) || + ecs_poly_is(world, ecs_stage_t), + ECS_INTERNAL_ERROR, + NULL); - /* Variable ids used in terms */ - ecs_rule_term_vars_t term_vars[ECS_RULE_MAX_VAR_COUNT]; + if (ecs_poly_is(world, ecs_world_t)) { + ecs_assert(!(world->flags & EcsWorldReadonly) || (ecs_get_stage_count(world) <= 1), + ECS_INVALID_OPERATION, NULL); + return &world->stages[0]; - /* Variable evaluation order */ - int32_t var_eval_order[ECS_RULE_MAX_VAR_COUNT]; + } 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; +} - int32_t var_count; /* Number of variables in signature */ - int32_t subj_var_count; - int32_t frame_count; /* Number of register frames */ - int32_t operation_count; /* Number of operations in rule */ +ecs_world_t* flecs_suspend_readonly( + const ecs_world_t *stage_world, + ecs_suspend_readonly_state_t *state) +{ + ecs_assert(stage_world != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(state != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_iterable_t iterable; /* Iterable mixin */ -}; + ecs_world_t *world = (ecs_world_t*)ecs_get_world(stage_world); + ecs_poly_assert(world, ecs_world_t); -/* 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) + bool is_readonly = ECS_BIT_IS_SET(world->flags, EcsWorldReadonly); + bool is_deferred = ecs_is_deferred(world); + + if (!is_readonly && !is_deferred) { + state->is_readonly = false; + state->is_deferred = false; + return world; } -}; -static -void rule_error( - const ecs_rule_t *rule, - const char *fmt, - ...) -{ - char *fstr = ecs_filter_str(rule->world, &rule->filter); - va_list valist; - va_start(valist, fmt); - ecs_parser_errorv(rule->filter.name, fstr, -1, fmt, valist); - va_end(valist); - ecs_os_free(fstr); -} + ecs_dbg_3("suspending readonly mode"); -static -bool subj_is_set( - ecs_term_t *term) -{ - return ecs_term_id_is_set(&term->src); -} + /* Cannot suspend when running with multiple threads */ + ecs_assert(ecs_get_stage_count(world) <= 1, + ECS_INVALID_WHILE_READONLY, NULL); -static -bool obj_is_set( - ecs_term_t *term) -{ - return ecs_term_id_is_set(&term->second) || ECS_HAS_ID_FLAG(term->id_flags, PAIR); + state->is_readonly = is_readonly; + state->is_deferred = is_deferred; + + /* Silence readonly checks */ + world->flags &= ~EcsWorldReadonly; + + /* Hack around safety checks (this ought to look ugly) */ + ecs_world_t *temp_world = world; + ecs_stage_t *stage = flecs_stage_from_world(&temp_world); + state->defer_count = stage->defer; + state->defer_queue = stage->defer_queue; + state->defer_stack = stage->defer_stack; + flecs_stack_init(&stage->defer_stack); + state->scope = stage->scope; + state->with = stage->with; + stage->defer = 0; + stage->defer_queue = NULL; + + return world; } -static -ecs_rule_op_t* create_operation( - ecs_rule_t *rule) +void flecs_resume_readonly( + ecs_world_t *world, + ecs_suspend_readonly_state_t *state) { - int32_t cur = rule->operation_count ++; - rule->operations = ecs_os_realloc( - rule->operations, (cur + 1) * ECS_SIZEOF(ecs_rule_op_t)); - - ecs_rule_op_t *result = &rule->operations[cur]; - ecs_os_memset_t(result, 0, ecs_rule_op_t); + ecs_poly_assert(world, ecs_world_t); + ecs_assert(state != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_world_t *temp_world = world; + ecs_stage_t *stage = flecs_stage_from_world(&temp_world); - return result; -} + if (state->is_readonly || state->is_deferred) { + ecs_dbg_3("resuming readonly mode"); + + ecs_run_aperiodic(world, 0); -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 = "."; + /* Restore readonly state / defer count */ + ECS_BIT_COND(world->flags, EcsWorldReadonly, state->is_readonly); + stage->defer = state->defer_count; + if (stage->defer_queue) { + ecs_assert(ecs_vector_count(stage->defer_queue) == 0, + ECS_INTERNAL_ERROR, NULL); + ecs_vector_free(stage->defer_queue); + } + stage->defer_queue = state->defer_queue; + flecs_stack_fini(&stage->defer_stack); + stage->defer_stack = state->defer_stack; + stage->scope = state->scope; + stage->with = state->with; } - - return name; } +/* 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 -ecs_rule_var_t* create_variable( - ecs_rule_t *rule, - ecs_rule_var_kind_t kind, - const char *name) +void eval_component_monitor( + ecs_world_t *world) { - int32_t cur = ++ rule->var_count; - - name = get_var_name(name); - if (name && !ecs_os_strcmp(name, "*")) { - /* Wildcards are treated as anonymous variables */ - name = NULL; + ecs_poly_assert(world, ecs_world_t); + + if (!world->monitors.is_dirty) { + return; } - ecs_rule_var_t *var = &rule->vars[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); - } - - var->kind = kind; - - /* 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; + world->monitors.is_dirty = false; - /* 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; + ecs_map_iter_t it = ecs_map_iter(&world->monitors.monitors); + ecs_monitor_t *m; + while ((m = ecs_map_next(&it, ecs_monitor_t, NULL))) { + if (!m->is_dirty) { + continue; + } - return var; -} + m->is_dirty = false; -static -ecs_rule_var_t* create_anonymous_variable( - ecs_rule_t *rule, - ecs_rule_var_kind_t kind) -{ - return create_variable(rule, kind, NULL); + ecs_vector_each(m->queries, ecs_query_t*, q_ptr, { + flecs_query_notify(world, *q_ptr, &(ecs_query_event_t) { + .kind = EcsQueryTableRematch + }); + }); + } } -/* 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) +void flecs_monitor_mark_dirty( + ecs_world_t *world, + ecs_entity_t id) { - ecs_assert(rule != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(name != NULL, ECS_INTERNAL_ERROR, NULL); - - name = get_var_name(name); + ecs_map_t *monitors = &world->monitors.monitors; - const ecs_rule_var_t *variables = rule->vars; - int32_t i, count = rule->var_count; - - for (i = 0; i < count; i ++) { - const ecs_rule_var_t *variable = &variables[i]; - if (!ecs_os_strcmp(name, variable->name)) { - if (kind == EcsRuleVarKindUnknown || kind == variable->kind) { - return (ecs_rule_var_t*)variable; - } + /* Only flag if there are actually monitors registered, so that we + * don't waste cycles evaluating monitors if there's no interest */ + if (ecs_map_is_initialized(monitors)) { + ecs_monitor_t *m = ecs_map_get(monitors, ecs_monitor_t, id); + if (m) { + m->is_dirty = true; + world->monitors.is_dirty = true; } } - - return 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 flecs_monitor_register( + ecs_world_t *world, + ecs_entity_t id, + ecs_query_t *query) { - 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_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(id != 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(query != NULL, ECS_INTERNAL_ERROR, NULL); - return var; -} + ecs_map_t *monitors = &world->monitors.monitors; -static -const char *term_id_var_name( - ecs_term_id_t *term_id) -{ - if (term_id->flags & EcsIsVariable) { - if (term_id->name) { - return term_id->name; - } else if (term_id->id == EcsThis) { - return "."; - } else if (term_id->id == EcsWildcard) { - return "*"; - } else if (term_id->id == EcsAny) { - return "_"; - } else if (term_id->id == EcsVariable) { - return "$"; - } else { - ecs_check(term_id->name != NULL, ECS_INVALID_PARAMETER, NULL); - } - } + ecs_map_init_if(monitors, ecs_monitor_t, 1); -error: - return NULL; + ecs_monitor_t *m = ecs_map_ensure(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; } -static -int ensure_term_id_variable( - ecs_rule_t *rule, - ecs_term_t *term, - ecs_term_id_t *term_id) +void flecs_monitor_unregister( + ecs_world_t *world, + ecs_entity_t id, + ecs_query_t *query) { - if (term_id->flags & EcsIsVariable) { - if (term_id->id == EcsAny) { - /* Any variables aren't translated to rule variables since their - * result isn't stored. */ - return 0; - } + ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(id != 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(query != NULL, ECS_INTERNAL_ERROR, NULL); - const char *name = term_id_var_name(term_id); + ecs_map_t *monitors = &world->monitors.monitors; - /* If this is a Not term, it should not introduce new variables. It may - * however create entity variables if there already was an existing - * table variable */ - if (term->oper == EcsNot) { - if (!find_variable(rule, EcsRuleVarKindUnknown, name)) { - rule_error(rule, "variable in '%s' only appears in Not term", - name); - return -1; - } - } + if (!ecs_map_is_initialized(monitors)) { + return; + } - ecs_rule_var_t *var = ensure_variable(rule, EcsRuleVarKindEntity, name); - ecs_os_strset(&term_id->name, var->name); - return 0; + ecs_monitor_t *m = ecs_map_get(monitors, ecs_monitor_t, id); + if (!m) { + return; } - return 0; -} + int32_t i, count = ecs_vector_count(m->queries); + ecs_query_t **queries = ecs_vector_first(m->queries, ecs_query_t*); + for (i = 0; i < count; i ++) { + if (queries[i] == query) { + ecs_vector_remove(m->queries, ecs_query_t*, i); + count --; + break; + } + } -static -bool term_id_is_variable( - ecs_term_id_t *term_id) -{ - return term_id->flags & EcsIsVariable; -} + if (!count) { + ecs_vector_free(m->queries); + ecs_map_remove(monitors, id); + } -/* Get variable from a term identifier */ -static -ecs_rule_var_t* term_id_to_var( - ecs_rule_t *rule, - ecs_term_id_t *id) -{ - if (id->flags & EcsIsVariable) { - return find_variable(rule, EcsRuleVarKindUnknown, term_id_var_name(id)); + if (!ecs_map_count(monitors)) { + ecs_map_fini(monitors); } - return NULL; } -/* Get variable from a term predicate */ static -ecs_rule_var_t* term_pred( - ecs_rule_t *rule, - ecs_term_t *term) +void init_store( + ecs_world_t *world) { - return term_id_to_var(rule, &term->first); -} + ecs_os_memset(&world->store, 0, ECS_SIZEOF(ecs_store_t)); + + /* Initialize entity index */ + flecs_sparse_init(&world->store.entity_index, ecs_record_t); + flecs_sparse_set_id_source(&world->store.entity_index, + &world->info.last_id); -/* 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->src); -} + /* Initialize root table */ + flecs_sparse_init(&world->store.tables, ecs_table_t); -/* 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->second); - } else { - return NULL; - } -} + /* Initialize table map */ + flecs_table_hashmap_init(&world->store.table_map); -/* Return predicate variable from pair */ -static -ecs_rule_var_t* pair_pred( - ecs_rule_t *rule, - const ecs_rule_pair_t *pair) -{ - if (pair->reg_mask & RULE_PAIR_PREDICATE) { - return &rule->vars[pair->first.reg]; - } else { - return NULL; - } + /* Initialize one root table per stage */ + flecs_init_root_table(world); } -/* Return object variable from pair */ static -ecs_rule_var_t* pair_obj( - ecs_rule_t *rule, - const ecs_rule_pair_t *pair) +void clean_tables( + ecs_world_t *world) { - if (pair->reg_mask & RULE_PAIR_OBJECT) { - return &rule->vars[pair->second.reg]; - } else { - return NULL; + int32_t i, count = flecs_sparse_count(&world->store.tables); + + /* Ensure that first table in sparse set has id 0. This is a dummy table + * that only exists so that there is no table with id 0 */ + ecs_table_t *first = flecs_sparse_get_dense(&world->store.tables, + ecs_table_t, 0); + ecs_assert(first->id == 0, ECS_INTERNAL_ERROR, NULL); + (void)first; + + for (i = 1; i < count; i ++) { + ecs_table_t *t = flecs_sparse_get_dense(&world->store.tables, + ecs_table_t, i); + flecs_table_release(world, t); } -} -/* 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) -{ - return rule->frame_count ++; -} + /* Free table types separately so that if application destructors rely on + * a type it's still valid. */ + for (i = 1; i < count; i ++) { + ecs_table_t *t = flecs_sparse_get_dense(&world->store.tables, + ecs_table_t, i); + flecs_table_free_type(t); + } -/* 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_var_t* get_register_frame( - const ecs_rule_iter_t *it, - int32_t frame) -{ - if (it->registers) { - return &it->registers[frame * it->rule->var_count]; - } else { - return NULL; + /* Clear the root table */ + if (count) { + flecs_table_reset(world, &world->store.root); } } -/* 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_var_t* get_registers( - const ecs_rule_iter_t *it, - ecs_rule_op_t *op) -{ - return get_register_frame(it, op->frame); -} +void fini_roots(ecs_world_t *world) { + ecs_id_record_t *idr = flecs_id_record_get(world, ecs_pair(EcsChildOf, 0)); -/* 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]; -} + ecs_run_aperiodic(world, EcsAperiodicEmptyTables); -static -int32_t* rule_get_columns( - ecs_rule_iter_t *it, - ecs_rule_op_t *op) -{ - return rule_get_columns_frame(it, op->frame); -} + ecs_table_cache_iter_t it; + bool has_roots = flecs_table_cache_iter(&idr->cache, &it); + ecs_assert(has_roots == true, ECS_INTERNAL_ERROR, NULL); + (void)has_roots; -static -void entity_reg_set( - const ecs_rule_t *rule, - ecs_var_t *regs, - int32_t r, - ecs_entity_t entity) -{ - (void)rule; - ecs_assert(rule->vars[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; -} + /* Delete root entities that are not modules. This prioritizes deleting + * regular entities first, which reduces the chance of components getting + * destructed in random order because it got deleted before entities, + * thereby bypassing the OnDeleteTarget policy. */ + ecs_defer_begin(world); -static -ecs_entity_t entity_reg_get( - const ecs_rule_t *rule, - ecs_var_t *regs, - int32_t r) -{ - (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; -} + const ecs_table_record_t *tr; + while ((tr = flecs_table_cache_next(&it, ecs_table_record_t))) { + ecs_table_t *table = tr->hdr.table; + if (table->flags & EcsTableHasBuiltins) { + continue; /* Filter out modules */ + } -static -void table_reg_set( - const ecs_rule_t *rule, - ecs_var_t *regs, - int32_t r, - ecs_table_t *table) -{ - (void)rule; - ecs_assert(rule->vars[r].kind == EcsRuleVarKindTable, - ECS_INTERNAL_ERROR, NULL); + int32_t i, count = table->data.entities.count; + ecs_entity_t *entities = table->data.entities.array; - regs[r].range.table = table; - regs[r].range.offset = 0; - regs[r].range.count = 0; - regs[r].entity = 0; -} + /* Count backwards so that we're always deleting the last entity in the + * table which reduces moving components around */ + for (i = count - 1; i >= 0; i --) { + ecs_record_t *r = flecs_entities_get(world, entities[i]); + ecs_flags32_t flags = ECS_RECORD_TO_ROW_FLAGS(r->row); + if (!(flags & EcsEntityObservedTarget)) { + continue; /* Filter out entities that aren't objects */ + } -static -ecs_table_range_t table_reg_get( - const ecs_rule_t *rule, - ecs_var_t *regs, - int32_t r) -{ - (void)rule; - ecs_assert(rule->vars[r].kind == EcsRuleVarKindTable, - ECS_INTERNAL_ERROR, NULL); + ecs_delete(world, entities[i]); + } + } - return regs[r].range; + ecs_defer_end(world); } static -ecs_entity_t reg_get_entity( - const ecs_rule_t *rule, - ecs_rule_op_t *op, - ecs_var_t *regs, - int32_t r) -{ - 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); +void fini_store(ecs_world_t *world) { + clean_tables(world); + flecs_sparse_fini(&world->store.tables); + flecs_table_release(world, &world->store.root); + flecs_sparse_clear(&world->store.entity_index); + flecs_hashmap_fini(&world->store.table_map); + ecs_vector_free(world->store.records); + ecs_vector_free(world->store.marked_ids); - return op->subject; + ecs_graph_edge_hdr_t *cur, *next = world->store.first_free; + while ((cur = next)) { + next = cur->next; + ecs_os_free(cur); } - if (rule->vars[r].kind == EcsRuleVarKindTable) { - int32_t offset = regs[r].range.offset; +} - ecs_assert(regs[r].range.count == 1, ECS_INTERNAL_ERROR, NULL); - ecs_data_t *data = &table_reg_get(rule, regs, r).table->data; - ecs_assert(data != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_entity_t *entities = ecs_storage_first(&data->entities); - ecs_assert(entities != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(offset < ecs_storage_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->vars[r].kind == EcsRuleVarKindEntity) { - return entity_reg_get(rule, regs, r); +/* Implementation for iterable mixin */ +static +bool world_iter_next( + ecs_iter_t *it) +{ + if (ECS_BIT_IS_SET(it->flags, EcsIterIsValid)) { + ECS_BIT_CLEAR(it->flags, EcsIterIsValid); + return false; } - /* Must return an entity */ - ecs_assert(false, ECS_INTERNAL_ERROR, NULL); + 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); + flecs_iter_validate(it); -error: - return 0; + return true; } static -ecs_table_range_t table_from_entity( +void world_iter_init( const ecs_world_t *world, - ecs_entity_t entity) + const ecs_poly_t *poly, + ecs_iter_t *iter, + ecs_term_t *filter) { - ecs_assert(entity != 0, ECS_INTERNAL_ERROR, NULL); - - entity = ecs_get_alive(world, entity); - - ecs_table_range_t slice = {0}; - ecs_record_t *record = flecs_entities_get(world, entity); - if (record) { - slice.table = record->table; - slice.offset = ECS_RECORD_TO_ROW(record->row); - slice.count = 1; - } + ecs_poly_assert(poly, ecs_world_t); + (void)poly; - return slice; -} - -static -ecs_table_range_t reg_get_range( - const ecs_rule_t *rule, - ecs_rule_op_t *op, - ecs_var_t *regs, - int32_t r) -{ - if (r == UINT8_MAX) { - ecs_check(ecs_is_valid(rule->world, op->subject), - ECS_INVALID_PARAMETER, NULL); - return table_from_entity(rule->world, op->subject); - } - if (rule->vars[r].kind == EcsRuleVarKindTable) { - return table_reg_get(rule, regs, r); - } - if (rule->vars[r].kind == EcsRuleVarKindEntity) { - return table_from_entity(rule->world, entity_reg_get(rule, regs, r)); - } -error: - return (ecs_table_range_t){0}; -} - -static -void reg_set_entity( - const ecs_rule_t *rule, - ecs_var_t *regs, - int32_t r, - ecs_entity_t entity) -{ - if (rule->vars[r].kind == EcsRuleVarKindTable) { - ecs_world_t *world = rule->world; - ecs_check(ecs_is_valid(world, entity), ECS_INVALID_PARAMETER, NULL); - regs[r].range = table_from_entity(world, entity); - regs[r].entity = entity; + if (filter) { + iter[0] = ecs_term_iter(world, filter); } else { - entity_reg_set(rule, regs, r, entity); + iter[0] = (ecs_iter_t){ + .world = (ecs_world_t*)world, + .real_world = (ecs_world_t*)ecs_get_world(world), + .next = world_iter_next + }; } -error: - return; } static -void reg_set_range( - const ecs_rule_t *rule, - ecs_var_t *regs, - int32_t r, - const ecs_table_range_t *range) -{ - if (rule->vars[r].kind == EcsRuleVarKindEntity) { - ecs_check(range->count == 1, ECS_INTERNAL_ERROR, NULL); - regs[r].range = *range; - regs[r].entity = ecs_storage_get_t(&range->table->data.entities, - ecs_entity_t, range->offset)[0]; - } else { - regs[r].range = *range; - regs[r].entity = 0; - } -error: - return; +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_MONITOR + ecs_trace("FLECS_MONITOR"); + #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_UNITS + ecs_trace("FLECS_UNITS"); + #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(); } -/* 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_rule_pair_t result = {0}; +/* -- Public functions -- */ - /* Terms must always have at least one argument (the subject) */ - ecs_assert(subj_is_set(term), ECS_INTERNAL_ERROR, NULL); +ecs_world_t *ecs_mini(void) { +#ifdef FLECS_OS_API_IMPL + ecs_set_os_api_impl(); +#endif + ecs_os_init(); - /* 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->first.flags & EcsIsVariable) { - if (term->first.id != EcsAny) { - /* Always lookup var as an entity, as pairs never refer to tables */ - const ecs_rule_var_t *var = find_variable( - rule, EcsRuleVarKindEntity, term_id_var_name(&term->first)); + ecs_trace("#[bold]bootstrapping world"); + ecs_log_push(); - /* Variables should have been declared */ - ecs_assert(var != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(var->kind == EcsRuleVarKindEntity, - ECS_INTERNAL_ERROR, NULL); - result.first.reg = var->id; + ecs_trace("tracing enabled, call ecs_log_set_level(-1) to disable"); - /* Set flag so the operation can see the predicate is a variable */ - result.reg_mask |= RULE_PAIR_PREDICATE; - result.final = true; - } else { - result.first.id = EcsWildcard; - result.final = true; - } - } else { - /* If the predicate is not a variable, simply store its id. */ - ecs_entity_t pred_id = term->first.id; - result.first.id = pred_id; + if (!ecs_os_has_heap()) { + ecs_abort(ECS_MISSING_OS_API, NULL); + } - /* 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_os_has_threading()) { + ecs_trace("threading unavailable, to use threads set OS API first (see examples)"); + } - if (ecs_has_id(rule->world, pred_id, EcsFinal)) { - result.final = true; - } + if (!ecs_os_has_time()) { + ecs_trace("time management not available"); + } - if (ecs_has_id(rule->world, pred_id, EcsReflexive)) { - result.reflexive = true; - } + log_addons(); - if (ecs_has_id(rule->world, pred_id, EcsAcyclic)) { - result.acyclic = true; - } - } +#ifdef FLECS_SANITIZE + ecs_trace("sanitize build, rebuild without FLECS_SANITIZE for (much) " + "improved performance"); +#elif defined(FLECS_DEBUG) + ecs_trace("debug build, rebuild with NDEBUG or FLECS_NDEBUG for improved " + "performance"); +#else + ecs_trace("#[green]release#[reset] build"); +#endif - /* The pair doesn't do anything with the subject (sources 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; - } +#ifdef __clang__ + ecs_trace("compiled with clang %s", __clang_version__); +#elif defined(__GNUC__) + ecs_trace("compiled with gcc %d.%d", __GNUC__, __GNUC_MINOR__); +#elif defined (_MSC_VER) + ecs_trace("compiled with msvc %d", _MSC_VER); +#endif - /* 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); + ecs_world_t *world = ecs_os_calloc_t(ecs_world_t); + ecs_assert(world != NULL, ECS_OUT_OF_MEMORY, NULL); + ecs_poly_init(world, ecs_world_t); - /* Same as above, if the object is a variable, store it and flag it */ - if (term->second.flags & EcsIsVariable) { - if (term->second.id != EcsAny) { - const ecs_rule_var_t *var = find_variable( - rule, EcsRuleVarKindEntity, term_id_var_name(&term->second)); + world->self = world; + world->type_info = flecs_sparse_new(ecs_type_info_t); + ecs_map_init(&world->id_index, ecs_id_record_t*, ECS_HI_COMPONENT_ID); + flecs_observable_init(&world->observable); + world->iterable.init = world_iter_init; + world->pending_tables = flecs_sparse_new(ecs_table_t*); + world->pending_buffer = flecs_sparse_new(ecs_table_t*); + flecs_name_index_init(&world->aliases); + flecs_name_index_init(&world->symbols); - /* Variables should have been declared */ - ecs_assert(var != NULL, ECS_INTERNAL_ERROR, - term_id_var_name(&term->second)); - ecs_assert(var->kind == EcsRuleVarKindEntity, ECS_INTERNAL_ERROR, - term_id_var_name(&term->second)); + world->info.time_scale = 1.0; - result.second.reg = var->id; - result.reg_mask |= RULE_PAIR_OBJECT; - } else { - result.second.id = EcsWildcard; - } - } else { - /* If the object is not a variable, simply store its id */ - result.second.id = term->second.id; - if (!result.second.id) { - result.second_0 = true; - } + if (ecs_os_has_time()) { + ecs_os_get_time(&world->world_start_time); } - return result; -} + ecs_set_stage_count(world, 1); + ecs_default_lookup_path[0] = EcsFlecsCore; + ecs_set_lookup_path(world, ecs_default_lookup_path); + init_store(world); -/* 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) -{ - ecs_entity_t first = pair.first.id; - ecs_entity_t second = pair.second.id; - ecs_rule_filter_t result = { - .lo_var = -1, - .hi_var = -1 - }; + flecs_bootstrap(world); - /* 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_var_t *regs = get_register_frame(it, op->frame - 1); + ecs_trace("world ready!"); + ecs_log_pop(); - if (pair.reg_mask & RULE_PAIR_OBJECT) { - second = entity_reg_get(it->rule, regs, pair.second.reg); - second = ecs_entity_t_lo(second); /* Filters don't have generations */ + return world; +} - if (second == EcsWildcard) { - result.wildcard = true; - result.second_wildcard = true; - result.lo_var = pair.second.reg; - } - } +ecs_world_t *ecs_init(void) { + ecs_world_t *world = ecs_mini(); - if (pair.reg_mask & RULE_PAIR_PREDICATE) { - first = entity_reg_get(it->rule, regs, pair.first.reg); - first = ecs_entity_t_lo(first); /* Filters don't have generations */ +#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 +#ifdef FLECS_UNITS + ecs_trace("#[green]module#[reset] flecs.units is not automatically imported"); +#endif + ecs_trace("addons imported!"); + ecs_log_pop(); +#endif + return world; +} - if (first == EcsWildcard) { - if (result.wildcard) { - result.same_var = pair.first.reg == pair.second.reg; - } +#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;\ + }\ + }\ + }\ + } - result.wildcard = true; - result.first_wildcard = true; +ecs_world_t* ecs_init_w_args( + int argc, + char *argv[]) +{ + ecs_world_t *world = ecs_init(); - if (second) { - result.hi_var = pair.first.reg; - } else { - result.lo_var = pair.first.reg; - } - } - } + (void)argc; + (void) argv; - if (!second && !pair.second_0) { - result.mask = first; - } else { - result.mask = ecs_pair(first, second); +#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 - return result; + return world; } -/* 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) +void ecs_quit( + ecs_world_t *world) { - const ecs_rule_t *rule = it->rule; - const ecs_rule_var_t *vars = rule->vars; - (void)vars; + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + flecs_stage_from_world(&world); + world->flags |= EcsWorldQuit; +error: + return; +} - ecs_var_t *regs = get_registers(it, op); - ecs_assert(column < type.count, ECS_INTERNAL_ERROR, NULL); - ecs_entity_t *elem = &type.array[column]; +bool ecs_should_quit( + const ecs_world_t *world) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + world = ecs_get_world(world); + return ECS_BIT_IS_SET(world->flags, EcsWorldQuit); +error: + return true; +} - int32_t obj_var = filter->lo_var; - int32_t pred_var = filter->hi_var; +void flecs_notify_tables( + ecs_world_t *world, + ecs_id_t id, + ecs_table_event_t *event) +{ + ecs_poly_assert(world, ecs_world_t); - if (obj_var != -1) { - ecs_assert(vars[obj_var].kind == EcsRuleVarKindEntity, - 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); + } - entity_reg_set(rule, regs, obj_var, - ecs_get_alive(rule->world, ECS_PAIR_SECOND(*elem))); - } + /* If id is specified, only broadcast to tables with id */ + } else { + ecs_id_record_t *idr = flecs_id_record_get(world, id); + if (!idr) { + return; + } - if (pred_var != -1) { - ecs_assert(vars[pred_var].kind == EcsRuleVarKindEntity, - ECS_INTERNAL_ERROR, NULL); + ecs_table_cache_iter_t it; + const ecs_table_record_t *tr; - entity_reg_set(rule, regs, pred_var, - ecs_get_alive(rule->world, - ECS_PAIR_FIRST(*elem))); + flecs_table_cache_all_iter(&idr->cache, &it); + while ((tr = flecs_table_cache_next(&it, ecs_table_record_t))) { + flecs_table_notify(world, tr->hdr.table, event); + } } } -/* Returns whether variable is a subject */ -static -bool is_subject( - ecs_rule_t *rule, - ecs_rule_var_t *var) +void ecs_default_ctor( + void *ptr, + int32_t count, + const ecs_type_info_t *ti) { - ecs_assert(rule != NULL, ECS_INTERNAL_ERROR, NULL); - - if (!var) { - return false; - } - - if (var->id < rule->subj_var_count) { - return true; - } - - return false; + ecs_os_memset(ptr, 0, ti->size * count); } static -bool skip_term(ecs_term_t *term) { - if (ecs_term_match_0(term)) { - return true; - } - return false; +void default_copy_ctor(void *dst_ptr, const void *src_ptr, + int32_t count, const ecs_type_info_t *ti) +{ + const ecs_type_hooks_t *cl = &ti->hooks; + cl->ctor(dst_ptr, count, ti); + cl->copy(dst_ptr, src_ptr, count, ti); } static -int32_t get_variable_depth( - ecs_rule_t *rule, - ecs_rule_var_t *var, - ecs_rule_var_t *root, - int recur); +void default_move_ctor(void *dst_ptr, void *src_ptr, + int32_t count, const ecs_type_info_t *ti) +{ + const ecs_type_hooks_t *cl = &ti->hooks; + cl->ctor(dst_ptr, count, ti); + cl->move(dst_ptr, src_ptr, count, ti); +} static -int32_t crawl_variable( - ecs_rule_t *rule, - ecs_rule_var_t *var, - ecs_rule_var_t *root, - int recur) +void default_ctor_w_move_w_dtor(void *dst_ptr, void *src_ptr, + int32_t count, const ecs_type_info_t *ti) { - ecs_term_t *terms = rule->filter.terms; - int32_t i, count = rule->filter.term_count; - - for (i = 0; i < count; i ++) { - ecs_term_t *term = &terms[i]; - if (skip_term(term)) { - continue; - } - - ecs_rule_var_t - *first = term_pred(rule, term), - *src = term_subj(rule, term), - *second = term_obj(rule, term); - - /* Variable must at least appear once in term */ - if (var != first && var != src && var != second) { - continue; - } - - if (first && first != var && !first->marked) { - get_variable_depth(rule, first, root, recur + 1); - } - - if (src && src != var && !src->marked) { - get_variable_depth(rule, src, root, recur + 1); - } - - if (second && second != var && !second->marked) { - get_variable_depth(rule, second, root, recur + 1); - } - } - - return 0; + const ecs_type_hooks_t *cl = &ti->hooks; + cl->ctor(dst_ptr, count, ti); + cl->move(dst_ptr, src_ptr, count, ti); + cl->dtor(src_ptr, count, ti); } static -int32_t get_depth_from_var( - ecs_rule_t *rule, - ecs_rule_var_t *var, - ecs_rule_var_t *root, - int recur) +void default_move_ctor_w_dtor(void *dst_ptr, void *src_ptr, + int32_t count, const ecs_type_info_t *ti) { - /* 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; - } - - /* 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; - } + const ecs_type_hooks_t *cl = &ti->hooks; + cl->move_ctor(dst_ptr, src_ptr, count, ti); + cl->dtor(src_ptr, count, ti); } static -int32_t get_depth_from_term( - ecs_rule_t *rule, - ecs_rule_var_t *cur, - ecs_rule_var_t *first, - ecs_rule_var_t *second, - ecs_rule_var_t *root, - int recur) +void default_move(void *dst_ptr, void *src_ptr, + int32_t count, const ecs_type_info_t *ti) { - int32_t result = UINT8_MAX; - - /* If neither of the other parts of the terms are variables, this - * variable is guaranteed to have no dependencies. */ - if (!first && !second) { - 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 (first && cur != first) { - int32_t depth = get_depth_from_var(rule, first, root, recur); - if (depth == UINT8_MAX) { - return UINT8_MAX; - } - - /* If the found depth is lower than the depth found, overwrite it */ - if (depth < result) { - result = depth; - } - } - - /* Same for second */ - if (second && cur != second) { - int32_t depth = get_depth_from_var(rule, second, root, recur); - if (depth == UINT8_MAX) { - return UINT8_MAX; - } - - if (depth < result) { - result = depth; - } - } - } + const ecs_type_hooks_t *cl = &ti->hooks; + cl->move(dst_ptr, src_ptr, count, ti); +} - return result; +static +void default_dtor(void *dst_ptr, void *src_ptr, + int32_t count, const ecs_type_info_t *ti) +{ + /* 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. */ + const ecs_type_hooks_t *cl = &ti->hooks; + cl->dtor(dst_ptr, count, ti); + ecs_os_memcpy(dst_ptr, src_ptr, flecs_uto(ecs_size_t, ti->size) * count); } -/* Find the depth of the dependency tree from the variable to the root */ static -int32_t get_variable_depth( - ecs_rule_t *rule, - ecs_rule_var_t *var, - ecs_rule_var_t *root, - int recur) +void default_move_w_dtor(void *dst_ptr, void *src_ptr, + int32_t count, const ecs_type_info_t *ti) { - var->marked = true; + /* 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. */ + const ecs_type_hooks_t *cl = &ti->hooks; + cl->move(dst_ptr, src_ptr, count, ti); + cl->dtor(src_ptr, count, ti); +} - /* 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; +void ecs_set_hooks_id( + ecs_world_t *world, + ecs_entity_t component, + const ecs_type_hooks_t *h) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - int32_t i, count = rule->filter.term_count; - int32_t result = UINT8_MAX; + flecs_stage_from_world(&world); - for (i = 0; i < count; i ++) { - ecs_term_t *term = &terms[i]; - if (skip_term(term)) { - continue; - } + /* Ensure that no tables have yet been created for the component */ + ecs_assert( ecs_id_in_use(world, component) == false, + ECS_ALREADY_IN_USE, ecs_get_name(world, component)); + ecs_assert( ecs_id_in_use(world, ecs_pair(component, EcsWildcard)) == false, + ECS_ALREADY_IN_USE, ecs_get_name(world, component)); - ecs_rule_var_t - *first = term_pred(rule, term), - *src = term_subj(rule, term), - *second = term_obj(rule, term); + ecs_type_info_t *ti = flecs_type_info_ensure(world, component); + ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); - if (src != var) { - continue; - } + ecs_check(!ti->component || ti->component == component, + ECS_INCONSISTENT_COMPONENT_ACTION, NULL); - if (!is_subject(rule, first)) { - first = NULL; - } + if (!ti->size) { + const EcsComponent *component_ptr = ecs_get( + world, component, EcsComponent); - if (!is_subject(rule, second)) { - second = NULL; - } + /* 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 depth = get_depth_from_term(rule, var, first, second, root, recur); - if (depth < result) { - result = depth; - } + ti->size = component_ptr->size; + ti->alignment = component_ptr->alignment; } - if (result == UINT8_MAX) { - result = 0; - } + if (h->ctor) ti->hooks.ctor = h->ctor; + if (h->dtor) ti->hooks.dtor = h->dtor; + if (h->copy) ti->hooks.copy = h->copy; + if (h->move) ti->hooks.move = h->move; + if (h->copy_ctor) ti->hooks.copy_ctor = h->copy_ctor; + if (h->move_ctor) ti->hooks.move_ctor = h->move_ctor; + if (h->ctor_move_dtor) ti->hooks.ctor_move_dtor = h->ctor_move_dtor; + if (h->move_dtor) ti->hooks.move_dtor = h->move_dtor; - var->depth = result; + if (h->on_add) ti->hooks.on_add = h->on_add; + if (h->on_remove) ti->hooks.on_remove = h->on_remove; + if (h->on_set) ti->hooks.on_set = h->on_set; - /* Dependencies are calculated from subject to (first, second). If there were - * sources 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 (h->ctx) ti->hooks.ctx = h->ctx; + if (h->binding_ctx) ti->hooks.binding_ctx = h->binding_ctx; + if (h->ctx_free) ti->hooks.ctx_free = h->ctx_free; + if (h->binding_ctx_free) ti->hooks.binding_ctx_free = h->binding_ctx_free; - ecs_rule_var_t - *src = term_subj(rule, term), - *first = term_pred(rule, term), - *second = term_obj(rule, term); + /* 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 (!h->ctor && (h->dtor || h->copy || h->move)) { + ti->hooks.ctor = ecs_default_ctor; + } - /* Only evaluate first & second 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 (src != var) { - continue; - } + /* Set default copy ctor, move ctor and merge */ + if (h->copy && !h->copy_ctor) { + ti->hooks.copy_ctor = default_copy_ctor; + } - crawl_variable(rule, src, root, recur); + if (h->move && !h->move_ctor) { + ti->hooks.move_ctor = default_move_ctor; + } - if (first && first != var) { - crawl_variable(rule, first, root, recur); + if (!h->ctor_move_dtor) { + if (h->move) { + if (h->dtor) { + if (h->move_ctor) { + /* If an explicit move ctor has been set, use callback + * that uses the move ctor vs. using a ctor+move */ + ti->hooks.ctor_move_dtor = default_move_ctor_w_dtor; + } else { + /* If no explicit move_ctor has been set, use + * combination of ctor + move + dtor */ + ti->hooks.ctor_move_dtor = default_ctor_w_move_w_dtor; + } + } else { + /* If no dtor has been set, this is just a move ctor */ + ti->hooks.ctor_move_dtor = ti->hooks.move_ctor; + } } + } - if (second && second != var) { - crawl_variable(rule, second, root, recur); - } + if (!h->move_dtor) { + if (h->move) { + if (h->dtor) { + ti->hooks.move_dtor = default_move_w_dtor; + } else { + ti->hooks.move_dtor = default_move; + } + } else { + if (h->dtor) { + ti->hooks.move_dtor = default_dtor; + } + } } - return var->depth; +error: + return; } -/* 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) +const ecs_type_hooks_t* ecs_get_hooks_id( + ecs_world_t *world, + ecs_entity_t id) { - const ecs_rule_var_t *v1 = ptr1; - const ecs_rule_var_t *v2 = ptr2; - - if (v1->kind < v2->kind) { - return -1; - } else if (v1->kind > v2->kind) { - return 1; + const ecs_type_info_t *ti = ecs_get_type_info(world, id); + if (ti) { + return &ti->hooks; } + return NULL; +} - if (v1->depth < v2->depth) { - return -1; - } else if (v1->depth > v2->depth) { - return 1; - } +void ecs_atfini( + ecs_world_t *world, + ecs_fini_action_t action, + void *ctx) +{ + ecs_poly_assert(world, ecs_world_t); + ecs_check(action != NULL, ECS_INVALID_PARAMETER, NULL); - if (v1->occurs < v2->occurs) { - return 1; - } else { - return -1; - } + ecs_action_elem_t *elem = ecs_vector_add(&world->fini_actions, + ecs_action_elem_t); + ecs_assert(elem != NULL, ECS_INTERNAL_ERROR, NULL); - return (v1->id < v2->id) - (v1->id > v2->id); + elem->action = action; + elem->ctx = ctx; +error: + return; } -/* 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. */ +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); + + elem->action = action; + elem->ctx = ctx; +error: + return; +} + +/* Unset data in tables */ static -int ensure_all_variables( - ecs_rule_t *rule) +void fini_unset_tables( + ecs_world_t *world) { - ecs_term_t *terms = rule->filter.terms; - int32_t i, count = rule->filter.term_count; + ecs_sparse_t *tables = &world->store.tables; + int32_t i, count = flecs_sparse_count(tables); for (i = 0; i < count; i ++) { - ecs_term_t *term = &terms[i]; - if (skip_term(term)) { - continue; - } + ecs_table_t *table = flecs_sparse_get_dense(tables, ecs_table_t, i); + flecs_table_remove_actions(world, table); + } +} - /* If predicate is a variable, make sure it has been registered */ - if (term->first.flags & EcsIsVariable) { - if (ensure_term_id_variable(rule, term, &term->first) != 0) { - return -1; - } - } +/* 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); + }); - /* 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 (!ecs_term_match_this(term)) { - if (ensure_term_id_variable(rule, term, &term->src) != 0) { - return -1; - } - } + ecs_vector_free(world->fini_actions); +} - /* If object is a variable, make sure it has been registered */ - if (obj_is_set(term) && (term->second.flags & EcsIsVariable)) { - if (ensure_term_id_variable(rule, term, &term->second) != 0) { - return -1; - } - } +/* Cleanup remaining type info elements */ +static +void fini_type_info( + ecs_world_t *world) +{ + int32_t i, count = flecs_sparse_count(world->type_info); + ecs_sparse_t *type_info = world->type_info; + for (i = 0; i < count; i ++) { + ecs_type_info_t *ti = flecs_sparse_get_dense(type_info, + ecs_type_info_t, i); + flecs_type_info_fini(ti); } + flecs_sparse_free(world->type_info); +} - return 0; +ecs_entity_t flecs_get_oneof( + const ecs_world_t *world, + ecs_entity_t e) +{ + if (ecs_has_id(world, e, EcsOneOf)) { + return e; + } else { + return ecs_get_target(world, e, EcsOneOf, 0); + } } -/* Scan for variables, put them in optimal dependency order. */ -static -int scan_variables( - ecs_rule_t *rule) +/* The destroyer of worlds */ +int ecs_fini( + ecs_world_t *world) { - /* Objects found in rule. One will be elected root */ - int32_t subject_count = 0; + ecs_poly_assert(world, ecs_world_t); + ecs_assert(!(world->flags & EcsWorldReadonly), ECS_INVALID_OPERATION, NULL); + ecs_assert(!(world->flags & EcsWorldFini), ECS_INVALID_OPERATION, NULL); + ecs_assert(world->stages[0].defer == 0, ECS_INVALID_OPERATION, + "call defer_end before destroying world"); - /* If this (.) is found, it always takes precedence in root election */ - int32_t this_var = UINT8_MAX; + ecs_trace("#[bold]shutting down world"); + ecs_log_push(); - /* 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; + world->flags |= EcsWorldQuit; - /* Step 1: find all possible roots */ - ecs_term_t *terms = rule->filter.terms; - int32_t i, term_count = rule->filter.term_count; + /* Delete root entities first using regular APIs. This ensures that cleanup + * policies get a chance to execute. */ + ecs_dbg_1("#[bold]cleanup root entities"); + ecs_log_push_1(); + fini_roots(world); + ecs_log_pop_1(); - for (i = 0; i < term_count; i ++) { - ecs_term_t *term = &terms[i]; + world->flags |= EcsWorldFini; - /* Evaluate the subject. The predicate and object are not evaluated, - * since they never can be elected as root. */ - if (term_id_is_variable(&term->src)) { - const char *subj_name = term_id_var_name(&term->src); - - ecs_rule_var_t *src = find_variable( - rule, EcsRuleVarKindTable, subj_name); - if (!src) { - src = create_variable(rule, EcsRuleVarKindTable, subj_name); - if (subject_count >= ECS_RULE_MAX_VAR_COUNT) { - rule_error(rule, "too many variables in rule"); - goto error; - } + /* Run fini actions (simple callbacks ran when world is deleted) before + * destroying the storage */ + ecs_dbg_1("#[bold]run fini actions"); + ecs_log_push_1(); + fini_actions(world); + ecs_log_pop_1(); - /* Make sure that variable name in term array matches with the - * rule name. */ - if (term->src.id != EcsThis && term->src.id != EcsAny) { - ecs_os_strset(&term->src.name, src->name); - term->src.id = 0; - } - } + ecs_dbg_1("#[bold]cleanup remaining entities"); + ecs_log_push_1(); - if (++ src->occurs > max_occur) { - max_occur = src->occurs; - max_occur_var = src->id; - } - } - } + /* Operations invoked during UnSet/OnRemove/destructors are deferred and + * will be discarded after world cleanup */ + ecs_defer_begin(world); - rule->subj_var_count = rule->var_count; - - if (ensure_all_variables(rule) != 0) { - goto error; - } - - /* Variables in a term with a literal subject have depth 0 */ - for (i = 0; i < term_count; i ++) { - ecs_term_t *term = &terms[i]; - - if (!(term->src.flags & EcsIsVariable)) { - ecs_rule_var_t - *first = term_pred(rule, term), - *second = term_obj(rule, term); - - if (first) { - first->depth = 0; - } - if (second) { - second->depth = 0; - } - } - } - - /* 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; - } - } + /* Run UnSet/OnRemove actions for components while the store is still + * unmodified by cleanup. */ + fini_unset_tables(world); - ecs_rule_var_t *root = &rule->vars[root_var]; - root->depth = get_variable_depth(rule, root, root, 0); + /* This will destroy all entities and components. After this point no more + * user code is executed. */ + fini_store(world); - /* Verify that there are no unconstrained variables. Unconstrained variables - * are variables that are unreachable from the root. */ - for (i = 0; i < rule->subj_var_count; i ++) { - if (rule->vars[i].depth == UINT8_MAX) { - rule_error(rule, "unconstrained variable '%s'", - rule->vars[i].name); - goto error; - } - } + /* 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->stages[0]); + ecs_log_pop_1(); - /* 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; - } + /* All queries are cleaned up, so monitors should've been cleaned up too */ + ecs_assert(!ecs_map_is_initialized(&world->monitors.monitors), + ECS_INTERNAL_ERROR, NULL); - ecs_rule_var_t - *first = term_pred(rule, term), - *second = term_obj(rule, term); + ecs_dbg_1("#[bold]cleanup world datastructures"); + ecs_log_push_1(); + flecs_sparse_fini(&world->store.entity_index); + flecs_fini_id_records(world); + fini_type_info(world); + flecs_observable_fini(&world->observable); + flecs_name_index_fini(&world->aliases); + flecs_name_index_fini(&world->symbols); + ecs_set_stage_count(world, 0); + ecs_log_pop_1(); - if (!first && term_id_is_variable(&term->first) && - term->first.id != EcsAny) - { - rule_error(rule, "missing predicate variable '%s'", - term_id_var_name(&term->first)); - goto error; - } - if (!second && term_id_is_variable(&term->second) && - term->second.id != EcsAny) - { - rule_error(rule, "missing object variable '%s'", - term_id_var_name(&term->second)); - goto error; - } - } + /* End of the world */ + ecs_poly_free(world, ecs_world_t); + ecs_os_fini(); - /* 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. */ - int32_t var_count = rule->var_count; - ecs_rule_var_t vars[ECS_RULE_MAX_VAR_COUNT]; - ecs_os_memcpy_n(vars, rule->vars, ecs_rule_var_t, var_count); - ecs_qsort_t(&vars, var_count, ecs_rule_var_t, compare_variable); - for (i = 0; i < var_count; i ++) { - rule->var_eval_order[i] = vars[i].id; - } + ecs_trace("world destroyed, bye!"); + ecs_log_pop(); -done: return 0; -error: - return -1; } -/* Get entity variable from table variable */ -static -ecs_rule_var_t* to_entity( - ecs_rule_t *rule, - ecs_rule_var_t *var) +bool ecs_is_fini( + const ecs_world_t *world) { - if (!var) { - return NULL; - } + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + world = ecs_get_world(world); + return ECS_BIT_IS_SET(world->flags, EcsWorldFini); +} - ecs_rule_var_t *evar = NULL; - if (var->kind == EcsRuleVarKindTable) { - evar = find_variable(rule, EcsRuleVarKindEntity, var->name); - } else { - evar = var; - } +void ecs_dim( + ecs_world_t *world, + int32_t entity_count) +{ + ecs_poly_assert(world, ecs_world_t); + flecs_entities_set_size(world, entity_count + ECS_HI_COMPONENT_ID); +} - return evar; +void flecs_eval_component_monitors( + ecs_world_t *world) +{ + ecs_poly_assert(world, ecs_world_t); + flecs_process_pending_tables(world); + eval_component_monitor(world); } -/* 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) +void ecs_measure_frame_time( + ecs_world_t *world, + bool enable) { - if (!var) { - return NULL; - } + ecs_poly_assert(world, ecs_world_t); + ecs_check(ecs_os_has_time(), ECS_MISSING_OS_API, NULL); - ecs_rule_var_t *tvar, *evar = to_entity(rule, var); - if (!evar) { - return var; + if (world->info.target_fps == (ecs_ftime_t)0 || enable) { + ECS_BIT_COND(world->flags, EcsWorldMeasureFrameTime, enable); } +error: + return; +} - if (var->kind == EcsRuleVarKindTable) { - tvar = var; - } else { - tvar = find_variable(rule, EcsRuleVarKindTable, var->name); - } +void ecs_measure_system_time( + ecs_world_t *world, + bool enable) +{ + ecs_poly_assert(world, ecs_world_t); + ecs_check(ecs_os_has_time(), ECS_MISSING_OS_API, NULL); + ECS_BIT_COND(world->flags, EcsWorldMeasureSystemTime, enable); +error: + return; +} - /* If variable is used as predicate or object, it should have been - * registered as an entity. */ - ecs_assert(evar != NULL, ECS_INTERNAL_ERROR, NULL); +void ecs_set_target_fps( + ecs_world_t *world, + ecs_ftime_t fps) +{ + ecs_poly_assert(world, ecs_world_t); + ecs_check(ecs_os_has_time(), ECS_MISSING_OS_API, 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; + ecs_measure_frame_time(world, true); + world->info.target_fps = fps; +error: + return; +} - /* Entity will either be written or has been written */ - written[evar->id] = true; +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; +} - push_frame(rule); +void ecs_set_context( + ecs_world_t *world, + void *context) +{ + ecs_poly_assert(world, ecs_world_t); + world->context = context; +} - return evar; - } else { - return tvar; - } - } - } else if (evar && written[evar->id]) { - return evar; +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->info.last_id, + ECS_INVALID_PARAMETER, NULL); + + if (world->info.last_id < id_start) { + world->info.last_id = id_start - 1; } - return var; + world->info.min_id = id_start; + world->info.max_id = id_end; +error: + return; } -/* 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) +bool ecs_enable_range_check( + ecs_world_t *world, + bool enable) { - return most_specific_var(rule, var, written, false); + ecs_poly_assert(world, ecs_world_t); + bool old_value = world->range_check_enabled; + world->range_check_enabled = enable; + return old_value; } -/* 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) +void ecs_set_entity_generation( + ecs_world_t *world, + ecs_entity_t entity_with_generation) { - return most_specific_var(rule, var, written, true); + flecs_sparse_set_generation( + &world->store.entity_index, entity_with_generation); } - -/* 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) +const ecs_type_info_t* flecs_type_info_get( + const ecs_world_t *world, + ecs_entity_t component) { - if (!var) { - return NULL; - } - - /* Ensure we're working with the most specific version of src we can get */ - ecs_rule_var_t *evar = ensure_most_specific_var(rule, var, written); + ecs_poly_assert(world, ecs_world_t); - /* 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); + ecs_assert(component != 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(!(component & ECS_ID_FLAGS_MASK), ECS_INTERNAL_ERROR, NULL); - /* Make sure the variable has been written */ - ecs_assert(written[evar->id] == true, ECS_INTERNAL_ERROR, NULL); - - return evar; + return flecs_sparse_get(world->type_info, ecs_type_info_t, component); } -static -ecs_rule_op_t* insert_operation( - ecs_rule_t *rule, - int32_t term_index, - bool *written) +ecs_type_info_t* flecs_type_info_ensure( + ecs_world_t *world, + ecs_entity_t component) { - ecs_rule_pair_t pair = {0}; + ecs_poly_assert(world, ecs_world_t); + ecs_assert(component != 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]; + const ecs_type_info_t *ti = flecs_type_info_get(world, component); + ecs_type_info_t *ti_mut = NULL; + if (!ti) { + ti_mut = flecs_sparse_ensure( + world->type_info, ecs_type_info_t, component); + ecs_assert(ti_mut != NULL, ECS_INTERNAL_ERROR, NULL); + ti_mut->component = component; + } else { + ti_mut = (ecs_type_info_t*)ti; + } - pair = term_to_pair(rule, term); + return ti_mut; +} - /* 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 *first = &rule->vars[pair.first.reg]; - first = get_most_specific_var(rule, first, written); - pair.first.reg = first->id; - } +bool flecs_type_info_init_id( + ecs_world_t *world, + ecs_entity_t component, + ecs_size_t size, + ecs_size_t alignment, + const ecs_type_hooks_t *li) +{ + bool changed = false; - if (pair.reg_mask & RULE_PAIR_OBJECT) { - ecs_rule_var_t *second = &rule->vars[pair.second.reg]; - second = get_most_specific_var(rule, second, written); - pair.second.reg = second->id; - } + flecs_entities_ensure(world, component); + + ecs_type_info_t *ti = NULL; + if (!size || !alignment) { + ecs_assert(size == 0 && alignment == 0, + ECS_INVALID_COMPONENT_SIZE, NULL); + ecs_assert(li == NULL, ECS_INCONSISTENT_COMPONENT_ACTION, NULL); + flecs_sparse_remove(world->type_info, component); } else { - /* Not all operations have a filter (like Each) */ + ti = flecs_type_info_ensure(world, component); + ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); + changed |= ti->size != size; + changed |= ti->alignment != alignment; + ti->size = size; + ti->alignment = alignment; + if (li) { + ecs_set_hooks_id(world, component, li); + } } - 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; - - /* Store corresponding signature term so we can correlate and - * store the table columns with signature columns. */ - op->term = term_index; - - return op; -} + /* Set type info for id record of component */ + ecs_id_record_t *idr = flecs_id_record_ensure(world, component); + changed |= flecs_id_record_set_type_info(world, idr, ti); + bool is_tag = idr->flags & EcsIdTag; -/* 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) -{ - ecs_rule_op_t *op = create_operation(rule); - op->kind = EcsRuleInput; + /* All id records with component as relationship inherit type info */ + idr = flecs_id_record_ensure(world, ecs_pair(component, EcsWildcard)); + do { + if (is_tag) { + changed |= flecs_id_record_set_type_info(world, idr, NULL); + } else if (ti) { + changed |= flecs_id_record_set_type_info(world, idr, ti); + } else if ((idr->type_info != NULL) && + (idr->type_info->component == component)) + { + changed |= flecs_id_record_set_type_info(world, idr, NULL); + } + } while ((idr = idr->first.next)); - /* The first time Input is evaluated it goes to the next/first operation */ - op->on_pass = 1; + /* All non-tag id records with component as object inherit type info, + * if relationship doesn't have type info */ + idr = flecs_id_record_ensure(world, ecs_pair(EcsWildcard, component)); + do { + if (!(idr->flags & EcsIdTag) && !idr->type_info) { + changed |= flecs_id_record_set_type_info(world, idr, ti); + } + } while ((idr = idr->first.next)); - /* When Input is evaluated with redo = true it will return false, which will - * finish the program as op becomes -1. */ - op->on_fail = -1; + /* Type info of (*, component) should always point to component */ + ecs_assert(flecs_id_record_get(world, ecs_pair(EcsWildcard, component))-> + type_info == ti, ECS_INTERNAL_ERROR, NULL); - push_frame(rule); + return changed; } -/* 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) +void flecs_type_info_fini( + ecs_type_info_t *ti) { - 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 (ti->hooks.ctx_free) { + ti->hooks.ctx_free(ti->hooks.ctx); } - - /* 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 (ti->hooks.binding_ctx_free) { + ti->hooks.binding_ctx_free(ti->hooks.binding_ctx); } +} - op->frame = push_frame(rule); +void flecs_type_info_free( + ecs_world_t *world, + ecs_entity_t component) +{ + if (world->flags & EcsWorldFini) { + /* If world is in the final teardown stages, cleanup policies are no + * longer applied and it can't be guaranteed that a component is not + * deleted before entities that use it. The remaining type info elements + * will be deleted after the store is finalized. */ + return; + } + ecs_type_info_t *ti = flecs_sparse_remove_get( + world->type_info, ecs_type_info_t, component); + if (ti) { + flecs_type_info_fini(ti); + } } -/* Return superset/subset including the root */ static -void insert_reflexive_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 reflexive) +ecs_ftime_t flecs_insert_sleep( + ecs_world_t *world, + ecs_time_t *stop) { - ecs_assert(out != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_poly_assert(world, ecs_world_t); - ecs_rule_var_t *first = pair_pred(rule, &pair); - ecs_rule_var_t *second = pair_obj(rule, &pair); + ecs_time_t start = *stop; + ecs_ftime_t delta_time = (ecs_ftime_t)ecs_time_measure(stop); - 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 reflexive, we only - * need to insert the set operation. */ - if (reflexive) { - insert_operation(rule, -1, written); - insert_operation(rule, -1, written); - insert_operation(rule, -1, written); + if (world->info.target_fps == (ecs_ftime_t)0.0) { + return delta_time; } - ecs_rule_op_t *op = insert_operation(rule, -1, written); - ecs_rule_op_t *setjmp = &rule->operations[setjmp_lbl]; - ecs_rule_op_t *store = &rule->operations[store_lbl]; - ecs_rule_op_t *set = &rule->operations[set_lbl]; - ecs_rule_op_t *jump = op; - - if (!reflexive) { - set_lbl = setjmp_lbl; - set = op; - setjmp = NULL; - store = NULL; - jump = NULL; - next_op = set_lbl + 1; - prev_op = set_lbl - 1; - } + ecs_ftime_t target_delta_time = + ((ecs_ftime_t)1.0 / (ecs_ftime_t)world->info.target_fps); - /* The SetJmp operation stores a conditional jump label that either - * points to the Store or *Set operation */ - if (reflexive) { - setjmp->kind = EcsRuleSetJmp; - setjmp->on_pass = store_lbl; - setjmp->on_fail = set_lbl; - } + /* Calculate the time we need to sleep by taking the measured delta from the + * previous frame, and subtracting it from target_delta_time. */ + ecs_ftime_t sleep = target_delta_time - delta_time; - /* 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 (reflexive) { - 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; + /* Pick a sleep interval that is 4 times smaller than the time one frame + * should take. */ + ecs_ftime_t sleep_time = sleep / (ecs_ftime_t)4.0; - if (!first) { - store->filter.first = pair.first; - } else { - store->filter.first.reg = first->id; - store->filter.reg_mask |= RULE_PAIR_PREDICATE; + 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 the object of the filter is not a variable, store literal */ - if (!second) { - store->r_in = UINT8_MAX; - store->subject = ecs_get_alive(rule->world, pair.second.id); - store->filter.second = pair.second; - } else { - store->r_in = second->id; - store->filter.second.reg = second->id; - store->filter.reg_mask |= RULE_PAIR_OBJECT; - } - } + ecs_time_t now = start; + delta_time = (ecs_ftime_t)ecs_time_measure(&now); + } while ((target_delta_time - delta_time) > + (sleep_time / (ecs_ftime_t)2.0)); - /* 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 delta_time; +} - /* Predicate can be a variable if it's non-final */ - if (!first) { - set->filter.first = pair.first; - } else { - set->filter.first.reg = first->id; - set->filter.reg_mask |= RULE_PAIR_PREDICATE; - } +static +ecs_ftime_t flecs_start_measure_frame( + ecs_world_t *world, + ecs_ftime_t user_delta_time) +{ + ecs_poly_assert(world, ecs_world_t); - if (!second) { - set->filter.second = pair.second; - } else { - set->filter.second.reg = second->id; - set->filter.reg_mask |= RULE_PAIR_OBJECT; - } + ecs_ftime_t delta_time = 0; - if (reflexive) { - /* 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; + if ((world->flags & EcsWorldMeasureFrameTime) || (user_delta_time == 0)) { + ecs_time_t t = world->frame_start_time; + do { + if (world->frame_start_time.nanosec || world->frame_start_time.sec){ + delta_time = flecs_insert_sleep(world, &t); + + ecs_time_measure(&t); + } else { + ecs_time_measure(&t); + if (world->info.target_fps != 0) { + delta_time = (ecs_ftime_t)1.0 / world->info.target_fps; + } else { + /* Best guess */ + delta_time = (ecs_ftime_t)1.0 / (ecs_ftime_t)60.0; + } + } - /* 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; + /* Keep trying while delta_time is zero */ + } while (delta_time == 0); + + world->frame_start_time = t; + + /* Keep track of total time passed in world */ + world->info.world_time_total_raw += (ecs_ftime_t)delta_time; } - written[out->id] = true; + return (ecs_ftime_t)delta_time; } static -ecs_rule_var_t* store_reflexive_set( - ecs_rule_t *rule, - ecs_rule_op_kind_t op_kind, - ecs_rule_pair_t *pair, - bool *written, - bool reflexive, - bool as_entity) +void flecs_stop_measure_frame( + ecs_world_t* world) { - /* Ensure we're using the most specific version of second */ - ecs_rule_var_t *second = pair_obj(rule, pair); - if (second) { - pair->second.reg = second->id; + ecs_poly_assert(world, ecs_world_t); + + if (world->flags & EcsWorldMeasureFrameTime) { + ecs_time_t t = world->frame_start_time; + world->info.frame_time_total += (ecs_ftime_t)ecs_time_measure(&t); } +} - ecs_rule_var_kind_t var_kind = EcsRuleVarKindTable; +ecs_ftime_t ecs_frame_begin( + ecs_world_t *world, + ecs_ftime_t user_delta_time) +{ + ecs_poly_assert(world, ecs_world_t); + ecs_check(!(world->flags & EcsWorldReadonly), ECS_INVALID_OPERATION, NULL); + ecs_check(user_delta_time != 0 || ecs_os_has_time(), + ECS_MISSING_OS_API, "get_time"); - /* Create anonymous variable for storing the set */ - ecs_rule_var_t *av = create_anonymous_variable(rule, var_kind); - int32_t ave_id = 0, av_id = av->id; + /* Start measuring total frame time */ + ecs_ftime_t delta_time = flecs_start_measure_frame(world, user_delta_time); + if (user_delta_time == 0) { + user_delta_time = delta_time; + } - /* 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 && as_entity) { - create_variable(rule, EcsRuleVarKindEntity, av->name); - av = &rule->vars[av_id]; - ave_id = av_id + 1; - } + world->info.delta_time_raw = user_delta_time; + world->info.delta_time = user_delta_time * world->info.time_scale; - /* Generate the operations */ - insert_reflexive_set(rule, op_kind, av, *pair, -1, written, reflexive); + /* Keep track of total scaled time passed in world */ + world->info.world_time_total += world->info.delta_time; - /* Make sure to return entity variable, and that it is populated */ - if (as_entity) { - return ensure_entity_written(rule, &rule->vars[ave_id], written); - } else { - return &rule->vars[av_id]; - } + ecs_run_aperiodic(world, 0); + + return world->info.delta_time; +error: + return (ecs_ftime_t)0; } -static -bool is_known( - ecs_rule_var_t *var, - bool *written) +void ecs_frame_end( + ecs_world_t *world) { - if (!var) { - return true; - } else { - return written[var->id]; + ecs_poly_assert(world, ecs_world_t); + ecs_check(!(world->flags & EcsWorldReadonly), ECS_INVALID_OPERATION, NULL); + + world->info.frame_count_total ++; + + ecs_stage_t *stages = world->stages; + int32_t i, count = world->stage_count; + for (i = 0; i < count; i ++) { + flecs_stage_merge_post_frame(world, &stages[i]); } + + flecs_stop_measure_frame(world); +error: + return; } -static -bool is_pair_known( - ecs_rule_t *rule, - ecs_rule_pair_t *pair, - bool *written) +const ecs_world_info_t* ecs_get_world_info( + const ecs_world_t *world) { - ecs_rule_var_t *pred_var = pair_pred(rule, pair); - if (!is_known(pred_var, written) || pair->first.id == EcsWildcard) { - return false; - } + world = ecs_get_world(world); + return &world->info; +} - ecs_rule_var_t *obj_var = pair_obj(rule, pair); - if (!is_known(obj_var, written) || pair->second.id == EcsWildcard) { - return false; +void flecs_notify_queries( + ecs_world_t *world, + ecs_query_event_t *event) +{ + ecs_poly_assert(world, ecs_world_t); + + ecs_id_record_t *idr = flecs_id_record_get(world, + ecs_pair(ecs_id(EcsPoly), EcsQuery)); + if (!idr) { + return; } - return true; + ecs_run_aperiodic(world, EcsAperiodicEmptyTables); + + ecs_table_cache_iter_t it; + const ecs_table_record_t *tr; + if (flecs_table_cache_iter(&idr->cache, &it)) { + while ((tr = flecs_table_cache_next(&it, ecs_table_record_t))) { + ecs_table_t *table = tr->hdr.table; + EcsPoly *queries = ecs_table_get_column(table, tr->column); + int32_t i, count = ecs_table_count(table); + + for (i = 0; i < count; i ++) { + ecs_query_t *query = queries[i].poly; + if (query->flags & EcsQueryIsSubquery) { + continue; + } + + ecs_poly_assert(query, ecs_query_t); + flecs_query_notify(world, query, event); + } + } + } } -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 flecs_delete_table( + ecs_world_t *world, + ecs_table_t *table) { - (void)rule; - - op->has_in = true; - if (!var) { - op->r_in = UINT8_MAX; - op->subject = term->src.id; - - /* 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; - } + ecs_poly_assert(world, ecs_world_t); + flecs_table_release(world, table); } 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 flecs_process_empty_queries( + ecs_world_t *world) { - (void)rule; + ecs_poly_assert(world, ecs_world_t); - op->has_out = true; - if (!var) { - op->r_out = UINT8_MAX; - op->subject = term->src.id; + ecs_id_record_t *idr = flecs_id_record_get(world, + ecs_pair(ecs_id(EcsPoly), EcsQuery)); + if (!idr) { + return; + } - /* Invalid entities should have been caught during parsing */ - ecs_assert(ecs_is_valid(rule->world, op->subject), - ECS_INTERNAL_ERROR, NULL); - } else { - op->r_out = var->id; + ecs_run_aperiodic(world, EcsAperiodicEmptyTables); + + /* Make sure that we defer adding the inactive tags until after iterating + * the query */ + flecs_defer_begin(world, &world->stages[0]); + + ecs_table_cache_iter_t it; + const ecs_table_record_t *tr; + if (flecs_table_cache_iter(&idr->cache, &it)) { + while ((tr = flecs_table_cache_next(&it, ecs_table_record_t))) { + ecs_table_t *table = tr->hdr.table; + EcsPoly *queries = ecs_table_get_column(table, tr->column); + int32_t i, count = ecs_table_count(table); + + for (i = 0; i < count; i ++) { + ecs_query_t *query = queries[i].poly; + ecs_entity_t *entities = table->data.entities.array; + if (!ecs_query_table_count(query)) { + ecs_add_id(world, entities[i], EcsEmpty); + } + } + } } + + flecs_defer_end(world, &world->stages[0]); } -static -void insert_select_or_with( - ecs_rule_t *rule, - int32_t c, - ecs_term_t *term, - ecs_rule_var_t *src, - ecs_rule_pair_t *pair, - bool *written) +/** Walk over tables that had a state change which requires bookkeeping */ +void flecs_process_pending_tables( + const ecs_world_t *world_r) { - ecs_rule_op_t *op; - bool eval_subject_supersets = false; + ecs_poly_assert(world_r, ecs_world_t); - /* Find any entity and/or table variables for subject */ - ecs_rule_var_t *tvar = NULL, *evar = to_entity(rule, src), *var = evar; - if (src && src->kind == EcsRuleVarKindTable) { - tvar = src; - if (!evar) { - var = tvar; - } + /* We can't update the administration while in readonly mode, but we can + * ensure that when this function is called there are no pending events. */ + if (world_r->flags & EcsWorldReadonly) { + ecs_assert(flecs_sparse_count(world_r->pending_tables) == 0, + ECS_INTERNAL_ERROR, NULL); + return; } - int32_t lbl_start = rule->operation_count; - ecs_rule_pair_t filter; - if (pair) { - filter = *pair; - } else { - filter = term_to_pair(rule, term); + /* Safe to cast, world is not readonly */ + ecs_world_t *world = (ecs_world_t*)world_r; + + /* If pending buffer is NULL there already is a stackframe that's iterating + * the table list. This can happen when an observer for a table event results + * in a mutation that causes another table to change state. A typical + * example of this is a system that becomes active/inactive as the result of + * a query (and as a result, its matched tables) becoming empty/non empty */ + if (!world->pending_buffer) { + return; } - /* Only insert implicit IsA if filter isn't already an IsA */ - if ((!filter.transitive || filter.first.id != EcsIsA) && term->oper != EcsNot) { - if (!var) { - ecs_rule_pair_t isa_pair = { - .first.id = EcsIsA, - .second.id = term->src.id - }; - - evar = src = store_reflexive_set(rule, EcsRuleSuperSet, &isa_pair, - written, true, true); - tvar = NULL; - eval_subject_supersets = true; + /* Swap buffer. The logic could in theory have been implemented with a + * single sparse set, but that would've complicated (and slowed down) the + * iteration. Additionally, by using a double buffer approach we can still + * keep most of the original ordering of events intact, which is desirable + * as it means that the ordering of tables in the internal datastructures is + * more predictable. */ + int32_t i, count = flecs_sparse_count(world->pending_tables); + if (!count) { + return; + } - } else if (ecs_id_is_wildcard(term->id) && - ECS_PAIR_FIRST(term->id) != EcsThis && - ECS_PAIR_SECOND(term->id) != EcsThis) - { - ecs_assert(src != NULL, ECS_INTERNAL_ERROR, NULL); + do { + ecs_sparse_t *pending_tables = world->pending_tables; + world->pending_tables = world->pending_buffer; + world->pending_buffer = NULL; - op = insert_operation(rule, -1, written); + /* Make sure that any ECS operations that occur while delivering the + * events does not cause inconsistencies, like sending an Empty + * notification for a table that just became non-empty. */ + ecs_defer_begin(world); - if (!is_known(src, written)) { - op->kind = EcsRuleSelect; - set_output_to_subj(rule, op, term, src); - written[src->id] = true; - } else { - op->kind = EcsRuleWith; - set_input_to_subj(rule, op, term, src); + for (i = 0; i < count; i ++) { + ecs_table_t *table = flecs_sparse_get_dense( + pending_tables, ecs_table_t*, i)[0]; + if (!table->id) { + /* Table is being deleted, ignore empty events */ + continue; } - ecs_rule_pair_t isa_pair = { - .first.id = EcsIsA, - .second.reg = src->id, - .reg_mask = RULE_PAIR_OBJECT - }; + /* For each id in the table, add it to the empty/non empty list + * based on its current state */ + if (flecs_table_records_update_empty(table)) { + /* Only emit an event when there was a change in the + * administration. It is possible that a table ended up in the + * pending_tables list by going from empty->non-empty, but then + * became empty again. By the time we run this code, no changes + * in the administration would actually be made. */ + int32_t table_count = ecs_table_count(table); + flecs_emit(world, world, &(ecs_event_desc_t){ + .event = table_count + ? EcsOnTableFill + : EcsOnTableEmpty + , + .table = table, + .ids = &table->type, + .observable = world, + .table_event = true + }); - op->filter = filter; - if (op->filter.reg_mask & RULE_PAIR_PREDICATE) { - op->filter.first.id = EcsWildcard; - } - if (op->filter.reg_mask & RULE_PAIR_OBJECT) { - op->filter.second.id = EcsWildcard; + world->info.empty_table_count += (table_count == 0) * 2 - 1; } - op->filter.reg_mask = 0; - - push_frame(rule); - - tvar = src = store_reflexive_set(rule, EcsRuleSuperSet, &isa_pair, - written, true, false); - - evar = NULL; } - } + flecs_sparse_clear(pending_tables); - /* If no pair is provided, create operation from specified term */ - if (!pair) { - op = insert_operation(rule, c, written); + ecs_defer_end(world); - /* 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; + world->pending_buffer = pending_tables; + } while ((count = flecs_sparse_count(world->pending_tables))); +} - /* Assign the term id, so that the operation will still be correctly - * associated with the correct expression term. */ - op->term = c; +void flecs_table_set_empty( + ecs_world_t *world, + ecs_table_t *table) +{ + ecs_poly_assert(world, ecs_world_t); + ecs_assert(!(world->flags & EcsWorldReadonly), ECS_INTERNAL_ERROR, NULL); + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + + if (ecs_table_count(table)) { + table->generation = 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, src); - - /* 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, src); - - /* If subject is neither table nor entitiy, with operates on literal */ - } else if (!tvar && !evar) { - op->kind = EcsRuleWith; - set_input_to_subj(rule, op, term, src); + flecs_sparse_set_generation(world->pending_tables, (uint32_t)table->id); + flecs_sparse_ensure(world->pending_tables, ecs_table_t*, + (uint32_t)table->id)[0] = table; +} - /* If subject is table or entity but not known, use select */ - } else { - ecs_assert(src != NULL, ECS_INTERNAL_ERROR, NULL); - op->kind = EcsRuleSelect; - set_output_to_subj(rule, op, term, src); - written[src->id] = true; +bool ecs_id_in_use( + ecs_world_t *world, + ecs_id_t id) +{ + ecs_id_record_t *idr = flecs_id_record_get(world, id); + if (!idr) { + return false; } + return (flecs_table_cache_count(&idr->cache) != 0) || + (flecs_table_cache_empty_count(&idr->cache) != 0); +} - /* 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); - - /* 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; +void ecs_run_aperiodic( + ecs_world_t *world, + ecs_flags32_t flags) +{ + ecs_poly_assert(world, ecs_world_t); + + if (!flags || (flags & EcsAperiodicEmptyTables)) { + flecs_process_pending_tables(world); } - - if (op->filter.reg_mask & RULE_PAIR_PREDICATE) { - written[op->filter.first.reg] = true; + if ((flags & EcsAperiodicEmptyQueries)) { + flecs_process_empty_queries(world); } - - if (op->filter.reg_mask & RULE_PAIR_OBJECT) { - written[op->filter.second.reg] = true; + if (!flags || (flags & EcsAperiodicComponentMonitors)) { + flecs_eval_component_monitors(world); } } -static -void prepare_predicate( - ecs_rule_t *rule, - ecs_rule_pair_t *pair, - int32_t term, - bool *written) +int32_t ecs_delete_empty_tables( + ecs_world_t *world, + ecs_id_t id, + uint16_t clear_generation, + uint16_t delete_generation, + int32_t min_id_count, + double time_budget_seconds) { - /* 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 = { - .first.id = EcsIsA, - .second.id = pair->first.id - }; - - ecs_rule_var_t *first = store_reflexive_set(rule, EcsRuleSubSet, - &isa_pair, written, true, true); - - pair->first.reg = first->id; - pair->reg_mask |= RULE_PAIR_PREDICATE; + ecs_poly_assert(world, ecs_world_t); - if (term != -1) { - rule->term_vars[term].first = first->id; - } - } -} + /* Make sure empty tables are in the empty table lists */ + ecs_run_aperiodic(world, EcsAperiodicEmptyTables); -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 *src = term_subj(rule, term); - if ((src = get_most_specific_var(rule, src, written))) { - subj_id = src->id; - } + ecs_time_t start = {0}, cur = {0}; + int32_t delete_count = 0, clear_count = 0; + bool time_budget = false; - ecs_rule_var_t *second = term_obj(rule, term); - if ((second = get_most_specific_var(rule, second, written))) { - obj_id = second->id; + ecs_time_measure(&start); + if (time_budget_seconds != 0) { + time_budget = true; } - bool subj_known = is_known(src, written); - bool same_obj_subj = false; - if (src && second) { - same_obj_subj = !ecs_os_strcmp(src->name, second->name); + if (!id) { + id = EcsAny; /* Iterate all empty tables */ } - if (!filter->transitive) { - insert_select_or_with(rule, c, term, src, filter, written); - if (src) src = &rule->vars[subj_id]; - if (second) second = &rule->vars[obj_id]; - - } else if (filter->transitive) { - if (subj_known) { - if (is_known(second, written)) { - if (filter->second.id != EcsWildcard) { - ecs_rule_var_t *obj_subsets = store_reflexive_set( - rule, EcsRuleSubSet, filter, written, true, true); - - if (src) { - src = &rule->vars[subj_id]; - } + ecs_id_record_t *idr = flecs_id_record_get(world, id); + ecs_table_cache_iter_t it; + if (idr && flecs_table_cache_empty_iter((ecs_table_cache_t*)idr, &it)) { + ecs_table_record_t *tr; + while ((tr = flecs_table_cache_next(&it, ecs_table_record_t))) { + if (time_budget) { + cur = start; + if (ecs_time_measure(&cur) > time_budget_seconds) { + goto done; + } + } - rule->term_vars[c].second = obj_subsets->id; + ecs_table_t *table = tr->hdr.table; + ecs_assert(ecs_table_count(table) == 0, ECS_INTERNAL_ERROR, NULL); + if (table->refcount > 1) { + /* Don't delete claimed tables */ + continue; + } - ecs_rule_pair_t pair = *filter; - pair.second.reg = obj_subsets->id; - pair.reg_mask |= RULE_PAIR_OBJECT; + if (table->type.count < min_id_count) { + continue; + } - insert_select_or_with(rule, c, term, src, &pair, written); - } else { - insert_select_or_with(rule, c, term, src, filter, written); + uint16_t gen = ++ table->generation; + if (delete_generation && (gen > delete_generation)) { + if (flecs_table_release(world, table)) { + delete_count ++; } - } else { - ecs_assert(second != NULL, ECS_INTERNAL_ERROR, NULL); + } else if (clear_generation && (gen > clear_generation)) { + if (flecs_table_shrink(world, table)) { + clear_count ++; + } + } + } + } - /* If subject is literal, find supersets for subject */ - if (src == NULL || src->kind == EcsRuleVarKindEntity) { - second = to_entity(rule, second); +done: + if (delete_count) { + ecs_dbg_1("#[red]deleted#[normal] %d empty tables in %.2fs", + delete_count, ecs_time_measure(&start)); + } + if (clear_count) { + ecs_dbg_1("#[red]cleared#[normal] %d empty tables in %.2fs", + clear_count, ecs_time_measure(&start)); + } + return delete_count; +} - ecs_rule_pair_t set_pair = *filter; - set_pair.reg_mask &= RULE_PAIR_PREDICATE; - if (src) { - set_pair.second.reg = src->id; - set_pair.reg_mask |= RULE_PAIR_OBJECT; - } else { - set_pair.second.id = term->src.id; - } +void flecs_observable_init( + ecs_observable_t *observable) +{ + observable->events = ecs_sparse_new(ecs_event_record_t); +} - insert_reflexive_set(rule, EcsRuleSuperSet, second, set_pair, - c, written, filter->reflexive); +void flecs_observable_fini( + ecs_observable_t *observable) +{ + ecs_sparse_t *triggers = observable->events; + int32_t i, count = flecs_sparse_count(triggers); - /* 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); + 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); + (void)et; - src = &rule->vars[subj_id]; - second = &rule->vars[obj_id]; - second = to_entity(rule, second); + /* All triggers should've unregistered by now */ + ecs_assert(!ecs_map_is_initialized(&et->event_ids), + ECS_INTERNAL_ERROR, NULL); + } - ecs_rule_pair_t set_pair = *filter; - set_pair.second.reg = av->id; - set_pair.reg_mask |= RULE_PAIR_OBJECT; + flecs_sparse_free(observable->events); +} - /* Insert with to find initial object for relationship */ - insert_select_or_with( - rule, c, term, src, &set_pair, written); +static +void notify_subset( + ecs_world_t *world, + ecs_iter_t *it, + ecs_observable_t *observable, + ecs_entity_t entity, + ecs_entity_t event, + const ecs_type_t *ids) +{ + ecs_id_t pair = ecs_pair(EcsWildcard, entity); + ecs_id_record_t *idr = flecs_id_record_get(world, pair); + if (!idr) { + return; + } - push_frame(rule); + /* Iterate acyclic relationships */ + ecs_id_record_t *cur = idr; + while ((cur = cur->acyclic.next)) { + flecs_process_pending_tables(world); - /* Find supersets for returned initial object. Make sure - * this is always reflexive since it needs to return the - * object from the pair that the entity has itself. */ - insert_reflexive_set(rule, EcsRuleSuperSet, second, set_pair, - c, written, true); - } - } + ecs_table_cache_iter_t idt; + if (!flecs_table_cache_iter(&cur->cache, &idt)) { + return; + } - /* src is not known */ - } else { - ecs_assert(src != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_entity_t rel = ECS_PAIR_FIRST(cur->id); + const ecs_table_record_t *tr; + while ((tr = flecs_table_cache_next(&idt, ecs_table_record_t))) { + ecs_table_t *table = tr->hdr.table; - if (is_known(second, written)) { - ecs_rule_pair_t set_pair = *filter; - set_pair.reg_mask &= RULE_PAIR_PREDICATE; /* clear object mask */ + int32_t e, entity_count = ecs_table_count(table); + it->table = table; + it->other_table = NULL; + it->offset = 0; + it->count = entity_count; - if (second) { - set_pair.second.reg = second->id; - set_pair.reg_mask |= RULE_PAIR_OBJECT; - } else { - set_pair.second.id = term->second.id; - } + /* Treat as new event as this could invoke observers again for + * different tables. */ + world->event_id ++; - if (second) { - rule->term_vars[c].second = second->id; - } else { - ecs_rule_var_t *av = create_anonymous_variable(rule, - EcsRuleVarKindEntity); - rule->term_vars[c].second = av->id; - written[av->id] = true; - } + flecs_set_observers_notify(it, observable, ids, event, + ecs_pair(rel, EcsWildcard)); - insert_reflexive_set(rule, EcsRuleSubSet, src, set_pair, c, - written, filter->reflexive); - } else if (src == second) { - insert_select_or_with(rule, c, term, src, filter, written); - } else { - ecs_assert(second != NULL, ECS_INTERNAL_ERROR, NULL); + if (!table->observed_count) { + continue; + } - ecs_rule_var_t *av = NULL; - if (!filter->reflexive) { - av = create_anonymous_variable(rule, EcsRuleVarKindEntity); - } + ecs_entity_t *entities = ecs_storage_first(&table->data.entities); + ecs_record_t **records = ecs_storage_first(&table->data.records); - src = &rule->vars[subj_id]; - second = &rule->vars[obj_id]; - second = to_entity(rule, second); + for (e = 0; e < entity_count; e ++) { + uint32_t flags = ECS_RECORD_TO_ROW_FLAGS(records[e]->row); + if (flags & EcsEntityObservedAcyclic) { + /* Only notify for entities that are used in pairs with + * acyclic relationships */ + notify_subset(world, it, observable, entities[e], event, ids); + } + } + } + } +} - /* Insert instruction to find all sources and objects */ - ecs_rule_op_t *op = insert_operation(rule, -1, written); - op->kind = EcsRuleSelect; - set_output_to_subj(rule, op, term, src); - op->filter.first = filter->first; +void flecs_emit( + ecs_world_t *world, + ecs_world_t *stage, + ecs_event_desc_t *desc) +{ + ecs_poly_assert(world, ecs_world_t); + ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(desc->event != 0, ECS_INVALID_PARAMETER, NULL); + ecs_check(desc->event != EcsWildcard, ECS_INVALID_PARAMETER, NULL); + ecs_check(desc->ids != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(desc->ids->count != 0, ECS_INVALID_PARAMETER, NULL); + ecs_check(desc->table != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(desc->observable != NULL, ECS_INVALID_PARAMETER, NULL); - if (filter->reflexive) { - op->filter.second.id = EcsWildcard; - op->filter.reg_mask = filter->reg_mask & RULE_PAIR_PREDICATE; - } else { - op->filter.second.reg = av->id; - op->filter.reg_mask = filter->reg_mask | RULE_PAIR_OBJECT; - written[av->id] = true; - } + const ecs_type_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_entity_t relationship = desc->relationship; - written[src->id] = true; + if (!count) { + count = ecs_table_count(table) - row; + } - /* Create new frame for operations that create reflexive set */ - push_frame(rule); + ecs_iter_t it = { + .world = stage, + .real_world = world, + .table = table, + .field_count = 1, + .other_table = desc->other_table, + .offset = row, + .count = count, + .param = (void*)desc->param, + .flags = desc->table_event ? EcsIterTableOnly : 0 + }; - /* Insert superset instruction to find all supersets */ - if (filter->reflexive) { - src = ensure_most_specific_var(rule, src, written); - ecs_assert(src->kind == EcsRuleVarKindEntity, - ECS_INTERNAL_ERROR, NULL); - ecs_assert(written[src->id] == true, - ECS_INTERNAL_ERROR, NULL); + world->event_id ++; - ecs_rule_pair_t super_filter = {0}; - super_filter.first = filter->first; - super_filter.second.reg = src->id; - super_filter.reg_mask = filter->reg_mask | RULE_PAIR_OBJECT; + ecs_observable_t *observable = ecs_get_observable(desc->observable); + ecs_check(observable != NULL, ECS_INVALID_PARAMETER, NULL); - insert_reflexive_set(rule, EcsRuleSuperSet, second, - super_filter, c, written, true); - } else { - insert_reflexive_set(rule, EcsRuleSuperSet, second, - op->filter, c, written, true); - } - } - } + if (!desc->relationship) { + flecs_observers_notify(&it, observable, ids, event); + } else { + flecs_set_observers_notify(&it, observable, ids, event, + ecs_pair(relationship, EcsWildcard)); } - if (same_obj_subj) { - /* Can't have relationship with same variables that is acyclic and not - * reflexive, this should've been caught earlier. */ - ecs_assert(!filter->acyclic || filter->reflexive, - ECS_INTERNAL_ERROR, NULL); - - /* If relationship is reflexive and entity has an instance of R, no checks - * are needed because R(X, X) is always true. */ - if (!filter->reflexive) { - push_frame(rule); + if (count && !desc->table_event) { + if (!table->observed_count) { + return; + } - /* Insert check if the (R, X) pair that was found matches with one - * of the entities in the table with the pair. */ - ecs_rule_op_t *op = insert_operation(rule, -1, written); - second = get_most_specific_var(rule, second, written); - ecs_assert(second->kind == EcsRuleVarKindEntity, - ECS_INTERNAL_ERROR, NULL); - ecs_assert(written[src->id] == true, ECS_INTERNAL_ERROR, NULL); - ecs_assert(written[second->id] == true, ECS_INTERNAL_ERROR, NULL); - - set_input_to_subj(rule, op, term, src); - op->filter.second.reg = second->id; - op->filter.reg_mask = RULE_PAIR_OBJECT; + ecs_record_t **recs = ecs_storage_get_t( + &table->data.records, ecs_record_t*, row); + for (i = 0; i < count; i ++) { + ecs_record_t *r = recs[i]; + if (!r) { + /* If the event is emitted after a bulk operation, it's possible + * that it hasn't been populated with entities yet. */ + continue; + } - if (src->kind == EcsRuleVarKindTable) { - op->kind = EcsRuleInTable; - } else { - op->kind = EcsRuleEq; + uint32_t flags = ECS_RECORD_TO_ROW_FLAGS(recs[i]->row); + if (flags & EcsEntityObservedAcyclic) { + notify_subset(world, &it, observable, ecs_storage_first_t( + &table->data.entities, ecs_entity_t)[row + i], event, ids); } } } + +error: + return; } -static -void insert_term_1( - ecs_rule_t *rule, - ecs_term_t *term, - ecs_rule_pair_t *filter, - int32_t c, - bool *written) +void ecs_emit( + ecs_world_t *stage, + ecs_event_desc_t *desc) { - ecs_rule_var_t *src = term_subj(rule, term); - src = get_most_specific_var(rule, src, written); - insert_select_or_with(rule, c, term, src, filter, written); + ecs_world_t *world = (ecs_world_t*)ecs_get_world(stage); + flecs_emit(world, stage, desc); } -static -void insert_term( - ecs_rule_t *rule, - ecs_term_t *term, - int32_t c, - bool *written) -{ - bool obj_set = obj_is_set(term); - ensure_most_specific_var(rule, term_pred(rule, term), written); - if (obj_set) { - ensure_most_specific_var(rule, term_obj(rule, term), written); - } +#include - /* 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; - } +ecs_filter_t ECS_FILTER_INIT = { .hdr = { .magic = ecs_filter_t_magic }}; - ecs_rule_pair_t filter = term_to_pair(rule, term); - prepare_predicate(rule, &filter, c, written); +/* Helper type for passing around context required for error messages */ +typedef struct { + const ecs_world_t *world; + ecs_filter_t *filter; + ecs_term_t *term; + int32_t term_index; +} ecs_filter_finalize_ctx_t; - 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); - } +static +char* flecs_filter_str( + const ecs_world_t *world, + const ecs_filter_t *filter, + const ecs_filter_finalize_ctx_t *ctx, + int32_t *term_start_out); - /* 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; +static +void flecs_filter_error( + const ecs_filter_finalize_ctx_t *ctx, + const char *fmt, + ...) +{ + va_list args; + va_start(args, fmt); - not_post->on_pass = prev - 1; - not_post->on_fail = prev - 1; - not_pre = &rule->operations[prev]; - not_pre->on_fail = rule->operation_count; + int32_t term_start = 0; + + char *expr = NULL; + if (ctx->filter) { + expr = flecs_filter_str(ctx->world, ctx->filter, ctx, &term_start); + } else { + expr = ecs_term_str(ctx->world, ctx->term); + } + char *name = NULL; + if (ctx->filter) { + name = ctx->filter->name; } + ecs_parser_errorv(name, expr, term_start, fmt, args); + ecs_os_free(expr); - if (term->oper == EcsOptional) { - /* Insert Not 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; + va_end(args); +} - /* 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; +static +int flecs_term_id_finalize_flags( + ecs_term_id_t *term_id, + ecs_filter_finalize_ctx_t *ctx) +{ + if ((term_id->flags & EcsIsEntity) && (term_id->flags & EcsIsVariable)) { + flecs_filter_error(ctx, "cannot set both IsEntity and IsVariable"); + return -1; + } + + if (!(term_id->flags & EcsIsEntity) && !(term_id->flags & EcsIsVariable)) { + if (term_id->id || term_id->name) { + if (term_id->id == EcsThis || + term_id->id == EcsWildcard || + term_id->id == EcsAny || + term_id->id == EcsVariable) + { + /* Builtin variable ids default to variable */ + term_id->flags |= EcsIsVariable; + } else { + term_id->flags |= EcsIsEntity; } } + } - 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; + if (term_id->flags & EcsParent) { + term_id->flags |= EcsUp; + term_id->trav = EcsChildOf; } - push_frame(rule); + if ((term_id->flags & EcsCascade) && !(term_id->flags & (EcsUp|EcsDown))) { + term_id->flags |= EcsUp; + } + + if ((term_id->flags & (EcsUp|EcsDown)) && !term_id->trav) { + term_id->trav = EcsIsA; + } + + if (term_id->trav && !(term_id->flags & EcsTraverseFlags)) { + term_id->flags |= EcsUp; + } + + return 0; } -/* Create program from operations that will execute the query */ static -void compile_program( - ecs_rule_t *rule) +int flecs_term_id_lookup( + const ecs_world_t *world, + ecs_entity_t scope, + ecs_term_id_t *term_id, + bool free_name, + ecs_filter_finalize_ctx_t *ctx) { - /* Trace which variables have been written while inserting instructions. - * This determines which instruction needs to be inserted */ - bool written[ECS_RULE_MAX_VAR_COUNT] = { false }; + char *name = term_id->name; + if (!name) { + return 0; + } - ecs_term_t *terms = rule->filter.terms; - int32_t v, c, term_count = rule->filter.term_count; - ecs_rule_op_t *op; + if (term_id->flags & EcsIsVariable) { + if (!ecs_os_strcmp(name, "This")) { + term_id->id = EcsThis; + if (free_name) { + ecs_os_free(term_id->name); + } + term_id->name = NULL; + } + return 0; + } - /* Insert input, which is always the first instruction */ - insert_input(rule); + ecs_assert(term_id->flags & EcsIsEntity, ECS_INTERNAL_ERROR, NULL); - /* 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; + if (ecs_identifier_is_0(name)) { + if (term_id->id) { + flecs_filter_error(ctx, "name '0' does not match entity id"); + return -1; } + return 0; + } - if (term->oper == EcsOptional || term->oper == EcsNot) { - continue; - } + ecs_entity_t e = ecs_lookup_symbol(world, name, true); + if (scope && !e) { + e = ecs_lookup_child(world, scope, name); + } - ecs_rule_var_t* src = term_subj(rule, term); - if (src) { - continue; - } + if (!e) { + flecs_filter_error(ctx, "unresolved identifier '%s'", name); + return -1; + } - insert_term(rule, term, c, written); + if (term_id->id && term_id->id != e) { + char *e_str = ecs_get_fullpath(world, term_id->id); + flecs_filter_error(ctx, "name '%s' does not match term.id '%s'", + name, e_str); + ecs_os_free(e_str); + return -1; } - /* Insert variables based on dependency order */ - for (v = 0; v < rule->subj_var_count; v ++) { - int32_t var_id = rule->var_eval_order[v]; - ecs_rule_var_t *var = &rule->vars[var_id]; + term_id->id = e; - ecs_assert(var->kind == EcsRuleVarKindTable, ECS_INTERNAL_ERROR, NULL); + if (!ecs_os_strcmp(name, "*") || !ecs_os_strcmp(name, "_") || + !ecs_os_strcmp(name, "$") || !ecs_os_strcmp(name, ".")) + { + term_id->flags &= ~EcsIsEntity; + term_id->flags |= EcsIsVariable; + } - for (c = 0; c < term_count; c ++) { - ecs_term_t *term = &terms[c]; - if (skip_term(term)) { - continue; - } + /* Check if looked up id is alive (relevant for numerical ids) */ + if (!ecs_is_alive(world, term_id->id)) { + flecs_filter_error(ctx, "identifier '%s' is not alive", term_id->name); + return -1; + } - if (term->oper == EcsOptional || term->oper == EcsNot) { - continue; - } + if (free_name) { + ecs_os_free(name); + } - /* Only process columns for which variable is subject */ - ecs_rule_var_t* src = term_subj(rule, term); - if (src != var) { - continue; - } + term_id->name = NULL; - insert_term(rule, term, c, written); + return 0; +} - var = &rule->vars[var_id]; - } +static +int flecs_term_ids_finalize( + const ecs_world_t *world, + ecs_term_t *term, + ecs_filter_finalize_ctx_t *ctx) +{ + ecs_term_id_t *src = &term->src; + ecs_term_id_t *first = &term->first; + ecs_term_id_t *second = &term->second; + + /* Include inherited components (like from prefabs) by default for src */ + if (!(src->flags & EcsTraverseFlags)) { + src->flags |= EcsSelf | EcsUp; } - /* Insert terms with Not operators */ - for (c = 0; c < term_count; c ++) { - ecs_term_t *term = &terms[c]; - if (term->oper != EcsNot) { - continue; - } + /* Include subsets for component by default, to support inheritance */ + if (!(first->flags & EcsTraverseFlags)) { + first->flags |= EcsSelf | EcsDown; + } - insert_term(rule, term, c, written); + /* Traverse Self by default for pair target */ + if (!(second->flags & EcsTraverseFlags)) { + second->flags |= EcsSelf; } - /* 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); + /* Source defaults to This */ + if ((src->id == 0) && (src->name == NULL) && !(src->flags & EcsIsEntity)) { + src->id = EcsThis; + src->flags |= EcsIsVariable; } - /* Verify all subject variables have been written. Source variables are of - * the table type, and a select/subset should have been inserted for each */ - for (v = 0; v < rule->subj_var_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->vars[v].name); - ecs_assert(var != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(written[var->id], ECS_INTERNAL_ERROR, var->name); - (void)var; - } + /* Initialize term identifier flags */ + if (flecs_term_id_finalize_flags(src, ctx)) { + return -1; + } + if (flecs_term_id_finalize_flags(first, ctx)) { + return -1; } - /* 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->var_count; v ++) { - if (!written[v]) { - ecs_rule_var_t *var = &rule->vars[v]; - ecs_assert(var->kind == EcsRuleVarKindEntity, - ECS_INTERNAL_ERROR, NULL); + if (flecs_term_id_finalize_flags(second, ctx)) { + return -1; + } - 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); + /* Lookup term identifiers by name */ + if (flecs_term_id_lookup(world, 0, src, term->move, ctx)) { + return -1; + } + if (flecs_term_id_lookup(world, 0, first, term->move, ctx)) { + return -1; + } - /* 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); - } - } + ecs_entity_t first_id = 0; + ecs_entity_t oneof = 0; + if (first->flags & EcsIsEntity) { + first_id = first->id; - /* Insert yield, which is always the last operation */ - insert_yield(rule); -} + /* If first element of pair has OneOf property, lookup second element of + * pair in the value of the OneOf property */ + oneof = flecs_get_oneof(world, first_id); + } -static -void create_variable_name_array( - ecs_rule_t *rule) -{ - if (rule->var_count) { - int i; - for (i = 0; i < rule->var_count; i ++) { - ecs_rule_var_t *var = &rule->vars[i]; + if (flecs_term_id_lookup(world, oneof, &term->second, term->move, ctx)) { + return -1; + } - if (var->kind != EcsRuleVarKindEntity) { - /* Table variables are hidden for applications. */ - rule->var_names[var->id] = NULL; - } else { - rule->var_names[var->id] = var->name; - } - } + /* If source is 0, reset traversal flags */ + if (src->id == 0 && src->flags & EcsIsEntity) { + src->flags &= ~EcsTraverseFlags; + src->trav = 0; } + /* If second is 0, reset traversal flags */ + if (second->id == 0 && second->flags & EcsIsEntity) { + second->flags &= ~EcsTraverseFlags; + second->trav = 0; + } + + return 0; } static -void create_variable_cross_references( - ecs_rule_t *rule) +ecs_entity_t flecs_term_id_get_entity( + const ecs_term_id_t *term_id) { - if (rule->var_count) { - int i; - for (i = 0; i < rule->var_count; i ++) { - ecs_rule_var_t *var = &rule->vars[i]; - if (var->kind == EcsRuleVarKindEntity) { - ecs_rule_var_t *tvar = find_variable( - rule, EcsRuleVarKindTable, var->name); - if (tvar) { - var->other = tvar->id; - } else { - var->other = -1; - } - } else { - ecs_rule_var_t *evar = find_variable( - rule, EcsRuleVarKindEntity, var->name); - if (evar) { - var->other = evar->id; - } else { - var->other = -1; - } - } + if (term_id->flags & EcsIsEntity) { + return term_id->id; /* Id is known */ + } else if (term_id->flags & EcsIsVariable) { + /* Return wildcard for variables, as they aren't known yet */ + if (term_id->id != EcsAny) { + /* Any variable should not use wildcard, as this would return all + * ids matching a wildcard, whereas Any returns the first match */ + return EcsWildcard; + } else { + return EcsAny; } + } else { + return 0; /* Term id is uninitialized */ } } -/* Implementation for iterable mixin */ static -void rule_iter_init( - const ecs_world_t *world, - const ecs_poly_t *poly, - ecs_iter_t *iter, - ecs_term_t *filter) +int flecs_term_populate_id( + ecs_term_t *term, + ecs_filter_finalize_ctx_t *ctx) { - ecs_poly_assert(poly, ecs_rule_t); + ecs_entity_t first = flecs_term_id_get_entity(&term->first); + ecs_entity_t second = flecs_term_id_get_entity(&term->second); + ecs_id_t role = term->id_flags; - 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); + if (first & ECS_ID_FLAGS_MASK) { + return -1; + } + if (second & ECS_ID_FLAGS_MASK) { + return -1; } -} -static -int32_t find_term_var_id( - ecs_rule_t *rule, - ecs_term_id_t *term_id) -{ - if (term_id_is_variable(term_id)) { - const char *var_name = term_id_var_name(term_id); - ecs_rule_var_t *var = find_variable( - rule, EcsRuleVarKindEntity, var_name); - if (var) { - return var->id; - } else { - /* If this is Any look for table variable. Since Any is only - * required to return a single result, there is no need to - * insert an each instruction for a matching table. */ - if (term_id->id == EcsAny) { - var = find_variable( - rule, EcsRuleVarKindTable, var_name); - if (var) { - return var->id; - } - } + if ((second || term->second.flags == EcsIsEntity) && !role) { + role = term->id_flags = ECS_PAIR; + } + + if (!second && !ECS_HAS_ID_FLAG(role, PAIR)) { + term->id = first | role; + } else { + if (!ECS_HAS_ID_FLAG(role, PAIR)) { + flecs_filter_error(ctx, "invalid role for pair"); + return -1; } + + term->id = ecs_pair(first, second); } - - return -1; + + return 0; } -ecs_rule_t* ecs_rule_init( - ecs_world_t *world, - const ecs_filter_desc_t *const_desc) +static +int flecs_term_populate_from_id( + const ecs_world_t *world, + ecs_term_t *term, + ecs_filter_finalize_ctx_t *ctx) { - ecs_rule_t *result = ecs_poly_new(ecs_rule_t); + ecs_entity_t first = 0; + ecs_entity_t second = 0; + ecs_id_t role = term->id & ECS_ID_FLAGS_MASK; - /* Initialize the query */ - ecs_filter_desc_t desc = *const_desc; - desc.storage = &result->filter; /* Use storage of rule */ - result->filter = ECS_FILTER_INIT; - if (ecs_filter_init(world, &desc) == NULL) { - goto error; + if (!role && term->id_flags) { + role = term->id_flags; + term->id |= role; } - result->world = world; - - /* Rule has no terms */ - if (!result->filter.term_count) { - rule_error(result, "rule has no terms"); - goto error; + if (term->id_flags && term->id_flags != role) { + flecs_filter_error(ctx, "mismatch between term.id & term.id_flags"); + return -1; } - ecs_term_t *terms = result->filter.terms; - int32_t i, term_count = result->filter.term_count; + term->id_flags = role; - /* 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; + if (ECS_HAS_ID_FLAG(term->id, PAIR)) { + first = ECS_PAIR_FIRST(term->id); + second = ECS_PAIR_SECOND(term->id); + + if (!first) { + flecs_filter_error(ctx, "missing first element in term.id"); + return -1; + } + if (!second) { + if (first != EcsChildOf) { + flecs_filter_error(ctx, "missing second element in term.id"); + return -1; + } else { + /* (ChildOf, 0) is allowed so filter can be used to efficiently + * query for root entities */ + } + } + } else { + first = term->id & ECS_COMPONENT_MASK; + if (!first) { + flecs_filter_error(ctx, "missing first element in term.id"); + return -1; } - } - if (i == term_count) { - rule_error(result, "rule cannot only have terms with Not operator"); - goto error; } - /* Translate terms with a Not operator and Wildcard to Any, as we don't need - * implicit variables for those */ - for (i = 0; i < term_count; i ++) { - ecs_term_t *term = &terms[i]; - if (term->oper == EcsNot) { - if (term->first.id == EcsWildcard) { - term->first.id = EcsAny; - } - if (term->src.id == EcsWildcard) { - term->src.id = EcsAny; - } - if (term->second.id == EcsWildcard) { - term->second.id = EcsAny; - } + ecs_entity_t term_first = flecs_term_id_get_entity(&term->first); + if (term_first) { + if (term_first != first) { + flecs_filter_error(ctx, "mismatch between term.id and term.first"); + return -1; + } + } else { + if (!(term->first.id = ecs_get_alive(world, first))) { + term->first.id = first; } } - /* Find all variables & resolve dependencies */ - if (scan_variables(result) != 0) { - goto error; + ecs_entity_t term_second = flecs_term_id_get_entity(&term->second); + if (term_second) { + if (ecs_entity_t_lo(term_second) != second) { + flecs_filter_error(ctx, "mismatch between term.id and term.second"); + return -1; + } + } else if (second) { + if (!(term->second.id = ecs_get_alive(world, second))) { + term->second.id = second; + } } - /* Create lookup array for subject variables */ - for (i = 0; i < term_count; i ++) { - ecs_term_t *term = &terms[i]; - ecs_rule_term_vars_t *vars = &result->term_vars[i]; - vars->first = find_term_var_id(result, &term->first); - vars->src = find_term_var_id(result, &term->src); - vars->second = find_term_var_id(result, &term->second); + return 0; +} + +static +int flecs_term_verify( + const ecs_world_t *world, + const ecs_term_t *term, + ecs_filter_finalize_ctx_t *ctx) +{ + const ecs_term_id_t *first = &term->first; + const ecs_term_id_t *second = &term->second; + const ecs_term_id_t *src = &term->src; + ecs_entity_t first_id = 0, second_id = 0; + ecs_id_t role = term->id_flags; + ecs_id_t id = term->id; + + if (first->flags & EcsIsEntity) { + first_id = first->id; + } + if (second->flags & EcsIsEntity) { + second_id = second->id; } - /* Generate the opcode array */ - compile_program(result); + if (role != (id & ECS_ID_FLAGS_MASK)) { + flecs_filter_error(ctx, "mismatch between term.id_flags & term.id"); + return -1; + } - /* 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_term_id_is_set(second) && !ECS_HAS_ID_FLAG(role, PAIR)) { + flecs_filter_error(ctx, "expected PAIR flag for term with pair"); + return -1; + } else if (!ecs_term_id_is_set(second) && ECS_HAS_ID_FLAG(role, PAIR)) { + if (first_id != EcsChildOf) { + flecs_filter_error(ctx, "unexpected PAIR flag for term without pair"); + return -1; + } else { + /* Exception is made for ChildOf so we can use (ChildOf, 0) to match + * all entities in the root */ + } + } - /* Create cross-references between variables so it's easy to go from entity - * to table variable and vice versa */ - create_variable_cross_references(result); + if (!ecs_term_id_is_set(src)) { + flecs_filter_error(ctx, "term.src is not initialized"); + return -1; + } - result->iterable.init = rule_iter_init; + if (!ecs_term_id_is_set(first)) { + flecs_filter_error(ctx, "term.first is not initialized"); + return -1; + } - return result; -error: - ecs_rule_fini(result); - return NULL; -} + if (ECS_HAS_ID_FLAG(role, PAIR)) { + if (!ECS_PAIR_FIRST(id)) { + flecs_filter_error(ctx, "invalid 0 for first element in pair id"); + return -1; + } + if ((ECS_PAIR_FIRST(id) != EcsChildOf) && !ECS_PAIR_SECOND(id)) { + flecs_filter_error(ctx, "invalid 0 for second element in pair id"); + return -1; + } -void ecs_rule_fini( - ecs_rule_t *rule) -{ - int32_t i; - for (i = 0; i < rule->var_count; i ++) { - ecs_os_free(rule->vars[i].name); + if ((first->flags & EcsIsEntity) && + (ecs_entity_t_lo(first_id) != ECS_PAIR_FIRST(id))) + { + flecs_filter_error(ctx, "mismatch between term.id and term.first"); + return -1; + } + if ((first->flags & EcsIsVariable) && + !ecs_id_is_wildcard(ECS_PAIR_FIRST(id))) + { + char *id_str = ecs_id_str(world, id); + flecs_filter_error(ctx, + "expected wildcard for variable term.first (got %s)", id_str); + ecs_os_free(id_str); + return -1; + } + + if ((second->flags & EcsIsEntity) && + (ecs_entity_t_lo(second_id) != ECS_PAIR_SECOND(id))) + { + flecs_filter_error(ctx, "mismatch between term.id and term.second"); + return -1; + } + if ((second->flags & EcsIsVariable) && + !ecs_id_is_wildcard(ECS_PAIR_SECOND(id))) + { + char *id_str = ecs_id_str(world, id); + flecs_filter_error(ctx, + "expected wildcard for variable term.second (got %s)", id_str); + ecs_os_free(id_str); + return -1; + } + } else { + ecs_entity_t component = id & ECS_COMPONENT_MASK; + if (!component) { + flecs_filter_error(ctx, "missing component id"); + return -1; + } + if ((first->flags & EcsIsEntity) && + (ecs_entity_t_lo(first_id) != ecs_entity_t_lo(component))) + { + flecs_filter_error(ctx, "mismatch between term.id and term.first"); + return -1; + } + if ((first->flags & EcsIsVariable) && !ecs_id_is_wildcard(component)) { + char *id_str = ecs_id_str(world, id); + flecs_filter_error(ctx, + "expected wildcard for variable term.first (got %s)", id_str); + ecs_os_free(id_str); + return -1; + } } - ecs_filter_fini(&rule->filter); + if (first_id) { + if (ecs_term_id_is_set(second)) { + ecs_flags32_t mask = EcsIsEntity | EcsIsVariable; + if ((src->flags & mask) == (second->flags & mask)) { + bool is_same = false; + if (src->flags & EcsIsEntity) { + is_same = src->id == second->id; + } else if (src->name && second->name) { + is_same = !ecs_os_strcmp(src->name, second->name); + } - ecs_os_free(rule->operations); - ecs_os_free(rule); -} + if (is_same && ecs_has_id(world, first_id, EcsAcyclic) + && !ecs_has_id(world, first_id, EcsReflexive)) + { + char *pred_str = ecs_get_fullpath(world, term->first.id); + flecs_filter_error(ctx, "term with acyclic relationship" + " '%s' cannot have same subject and object", + pred_str); + ecs_os_free(pred_str); + return -1; + } + } + } -const ecs_filter_t* ecs_rule_get_filter( - const ecs_rule_t *rule) -{ - return &rule->filter; -} + if (second_id && !ecs_id_is_wildcard(second_id)) { + ecs_entity_t oneof = flecs_get_oneof(world, first_id); + if (oneof) { + if (!ecs_has_pair(world, second_id, EcsChildOf, oneof)) { + char *second_str = ecs_get_fullpath(world, second_id); + char *oneof_str = ecs_get_fullpath(world, oneof); + char *id_str = ecs_id_str(world, term->id); + flecs_filter_error(ctx, + "invalid target '%s' for %s: must be child of '%s'", + second_str, id_str, oneof_str); + ecs_os_free(second_str); + ecs_os_free(oneof_str); + ecs_os_free(id_str); + return -1; + } + } + } + } -/* 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; + if (term->src.trav) { + if (!ecs_has_id(world, term->src.trav, EcsAcyclic)) { + char *r_str = ecs_get_fullpath(world, term->src.trav); + flecs_filter_error(ctx, + "cannot traverse non-Acyclic relationship '%s'", r_str); + ecs_os_free(r_str); + return -1; + } } - return (ecs_rule_var_t*)&rule->vars[var_id]; + return 0; } -/* 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) +static +int flecs_term_finalize( + const ecs_world_t *world, + ecs_term_t *term, + ecs_filter_finalize_ctx_t *ctx) { - 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]; - - 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 first = pair.first.id; - ecs_entity_t second = pair.second.id; - const char *pred_name = NULL, *obj_name = NULL; - char *pred_name_alloc = NULL, *obj_name_alloc = NULL; + ctx->term = term; - if (pair.reg_mask & RULE_PAIR_PREDICATE) { - ecs_assert(rule->vars != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_rule_var_t *type_var = &rule->vars[pair.first.reg]; - pred_name = type_var->name; - } else if (first) { - pred_name_alloc = ecs_get_fullpath(world, ecs_get_alive(world, first)); - pred_name = pred_name_alloc; - } + ecs_term_id_t *src = &term->src; + ecs_term_id_t *first = &term->first; + ecs_term_id_t *second = &term->second; + ecs_flags32_t first_flags = first->flags; + ecs_flags32_t src_flags = src->flags; - if (pair.reg_mask & RULE_PAIR_OBJECT) { - ecs_assert(rule->vars != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_rule_var_t *obj_var = &rule->vars[pair.second.reg]; - obj_name = obj_var->name; - } else if (second) { - obj_name_alloc = ecs_get_fullpath(world, ecs_get_alive(world, second)); - obj_name = obj_name_alloc; - } else if (pair.second_0) { - obj_name = "0"; + if (term->id) { + if (flecs_term_populate_from_id(world, term, ctx)) { + return -1; } + } - ecs_strbuf_append(&buf, "%2d: [S:%2d, P:%2d, F:%2d, T:%2d] ", i, - op->frame, op->on_pass, op->on_fail, op->term); + if (flecs_term_ids_finalize(world, term, ctx)) { + return -1; + } - bool has_filter = false; + /* If EcsVariable is used by itself, assign to predicate (singleton) */ + if ((src->id == EcsVariable) && (src->flags & EcsIsVariable)) { + src->id = first->id; + src->flags &= ~(EcsIsVariable | EcsIsEntity); + src->flags |= first->flags & (EcsIsVariable | EcsIsEntity); + } + if ((second->id == EcsVariable) && (second->flags & EcsIsVariable)) { + second->id = first->id; + second->flags &= ~(EcsIsVariable | EcsIsEntity); + second->flags |= first->flags & (EcsIsVariable | EcsIsEntity); + } - 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 EcsRuleInTable: - ecs_strbuf_append(&buf, "intable "); - has_filter = true; - break; - case EcsRuleEq: - ecs_strbuf_append(&buf, "eq "); - has_filter = true; - break; - case EcsRuleYield: - ecs_strbuf_append(&buf, "yield "); - break; - default: - continue; + if (!term->id) { + if (flecs_term_populate_id(term, ctx)) { + return -1; } + } - 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_entity_t first_id = 0; + if (term->first.flags & EcsIsEntity) { + first_id = term->first.id; + } - 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); + /* If component id is final, don't attempt component inheritance */ + if (first_id) { + if (ecs_has_id(world, first_id, EcsFinal)) { + if (first_flags & EcsDown) { + flecs_filter_error(ctx, "final id cannot be traversed down"); + return -1; } - 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); + first->flags &= ~EcsDown; + first->trav = 0; + } + if (ecs_has_id(world, first_id, EcsDontInherit)) { + if (src_flags & (EcsUp | EcsDown)) { + flecs_filter_error(ctx, + "traversing not allowed for id that can't be inherited"); + return -1; } + src->flags &= ~(EcsUp | EcsDown); + src->trav = 0; } + } - if (has_filter) { - if (!pred_name) { - pred_name = "-"; - } - if (!obj_name && !pair.second_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); + if (first->id == EcsVariable) { + flecs_filter_error(ctx, "invalid $ for term.first"); + return -1; + } + + if (ECS_HAS_ID_FLAG(term->id_flags, AND) || + ECS_HAS_ID_FLAG(term->id_flags, OR) || + ECS_HAS_ID_FLAG(term->id_flags, NOT)) + { + if (term->inout != EcsInOutDefault && term->inout != EcsInOutNone) { + flecs_filter_error(ctx, "AND/OR terms must be filters"); + return -1; } - ecs_strbuf_appendstr(&buf, "\n"); + term->inout = EcsInOutNone; - ecs_os_free(pred_name_alloc); - ecs_os_free(obj_name_alloc); + /* Translate role to operator */ + if (ECS_HAS_ID_FLAG(term->id_flags, AND)) { + term->oper = EcsAndFrom; + } else + if (ECS_HAS_ID_FLAG(term->id_flags, OR)) { + term->oper = EcsOrFrom; + } else + if (ECS_HAS_ID_FLAG(term->id_flags, NOT)) { + term->oper = EcsNotFrom; + } + + /* Zero out role & strip from id */ + term->id &= ECS_COMPONENT_MASK; + term->id_flags = 0; } - return ecs_strbuf_get(&buf); -error: - return NULL; -} + if (flecs_term_verify(world, term, ctx)) { + return -1; + } -/* Public function that returns number of variables. This enables an application - * to iterate the variables and obtain their values. */ -int32_t ecs_rule_var_count( - const ecs_rule_t *rule) -{ - ecs_assert(rule != NULL, ECS_INTERNAL_ERROR, NULL); - return rule->var_count; + return 0; } -/* Public function to find a variable by name */ -int32_t ecs_rule_find_var( - const ecs_rule_t *rule, - const char *name) +ecs_id_t flecs_to_public_id( + ecs_id_t id) { - ecs_rule_var_t *v = find_variable(rule, EcsRuleVarKindEntity, name); - if (v) { - return v->id; + if (ECS_PAIR_FIRST(id) == EcsUnion) { + return ecs_pair(ECS_PAIR_SECOND(id), EcsWildcard); } else { - return -1; + return id; } } -/* Public function to get the name of a variable. */ -const char* ecs_rule_var_name( - const ecs_rule_t *rule, - int32_t var_id) +ecs_id_t flecs_from_public_id( + ecs_world_t *world, + ecs_id_t id) { - return rule->vars[var_id].name; -} + if (ECS_HAS_ID_FLAG(id, PAIR)) { + ecs_entity_t first = ECS_PAIR_FIRST(id); + ecs_id_record_t *idr = flecs_id_record_ensure(world, + ecs_pair(first, EcsWildcard)); + if (idr->flags & EcsIdUnion) { + return ecs_pair(EcsUnion, first); + } + } -/* Public function to get the type of a variable. */ -bool ecs_rule_var_is_entity( - const ecs_rule_t *rule, - int32_t var_id) -{ - return rule->vars[var_id].kind == EcsRuleVarKindEntity; + return id; } -static -void ecs_rule_iter_free( - ecs_iter_t *iter) +bool ecs_identifier_is_0( + const char *id) { - ecs_rule_iter_t *it = &iter->priv.iter.rule; - ecs_os_free(it->registers); - ecs_os_free(it->columns); - ecs_os_free(it->op_ctx); - iter->columns = NULL; - it->registers = NULL; - it->columns = NULL; - it->op_ctx = NULL; + return id[0] == '0' && !id[1]; } -/* Create rule iterator */ -ecs_iter_t ecs_rule_iter( - const ecs_world_t *world, - const ecs_rule_t *rule) +bool ecs_id_match( + ecs_id_t id, + ecs_id_t pattern) { - ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_assert(rule != NULL, ECS_INVALID_PARAMETER, NULL); + if (id == pattern) { + return true; + } - ecs_iter_t result = {0}; - int i; + if (ECS_HAS_ID_FLAG(pattern, PAIR)) { + if (!ECS_HAS_ID_FLAG(id, PAIR)) { + return false; + } - result.world = (ecs_world_t*)world; - result.real_world = (ecs_world_t*)ecs_get_world(rule->world); - - flecs_process_pending_tables(result.real_world); + ecs_entity_t id_rel = ECS_PAIR_FIRST(id); + ecs_entity_t id_obj = ECS_PAIR_SECOND(id); + ecs_entity_t pattern_rel = ECS_PAIR_FIRST(pattern); + ecs_entity_t pattern_obj = ECS_PAIR_SECOND(pattern); - ecs_rule_iter_t *it = &result.priv.iter.rule; - it->rule = rule; + ecs_check(id_rel != 0, ECS_INVALID_PARAMETER, NULL); + ecs_check(id_obj != 0, ECS_INVALID_PARAMETER, NULL); - if (rule->operation_count) { - if (rule->var_count) { - it->registers = ecs_os_malloc_n(ecs_var_t, - rule->operation_count * rule->var_count); - } + ecs_check(pattern_rel != 0, ECS_INVALID_PARAMETER, NULL); + ecs_check(pattern_obj != 0, ECS_INVALID_PARAMETER, NULL); - it->op_ctx = ecs_os_calloc_n(ecs_rule_op_ctx_t, rule->operation_count); - - if (rule->filter.term_count) { - it->columns = ecs_os_malloc_n(int32_t, - rule->operation_count * rule->filter.term_count); + if (pattern_rel == EcsWildcard) { + if (pattern_obj == EcsWildcard || pattern_obj == id_obj) { + return true; + } + } else if (pattern_rel == EcsFlag) { + /* Used for internals, helps to keep track of which ids are used in + * pairs that have additional flags (like OVERRIDE and TOGGLE) */ + if (ECS_HAS_ID_FLAG(id, PAIR) && !ECS_IS_PAIR(id)) { + if (ECS_PAIR_FIRST(id) == pattern_obj) { + return true; + } + if (ECS_PAIR_SECOND(id) == pattern_obj) { + return true; + } + } + } else if (pattern_obj == EcsWildcard) { + if (pattern_rel == id_rel) { + return true; + } } - - for (i = 0; i < rule->filter.term_count; i ++) { - it->columns[i] = -1; + } else { + if ((id & ECS_ID_FLAGS_MASK) != (pattern & ECS_ID_FLAGS_MASK)) { + return false; } - } - - it->op = 0; - for (i = 0; i < rule->var_count; i ++) { - if (rule->vars[i].kind == EcsRuleVarKindEntity) { - entity_reg_set(rule, it->registers, i, EcsWildcard); - } else { - table_reg_set(rule, it->registers, i, NULL); + if ((ECS_COMPONENT_MASK & pattern) == EcsWildcard) { + return true; } } - result.variable_names = (char**)rule->var_names; - result.variable_count = rule->var_count; - result.field_count = rule->filter.term_count; - result.terms = rule->filter.terms; - result.next = ecs_rule_next; - result.fini = ecs_rule_iter_free; - ECS_BIT_COND(result.flags, EcsIterIsFilter, - ECS_BIT_IS_SET(rule->filter.flags, EcsFilterIsFilter)); - - flecs_iter_init(&result, - flecs_iter_cache_ids | - /* flecs_iter_cache_columns | provided by rule iterator */ - flecs_iter_cache_sources | - flecs_iter_cache_sizes | - flecs_iter_cache_ptrs | - /* flecs_iter_cache_match_indices | not necessary for iteration */ - flecs_iter_cache_variables); - - result.columns = it->columns; /* prevent alloc */ - - return result; +error: + return false; } -/* 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 -int32_t find_next_same_var( - ecs_type_t type, - int32_t column, - ecs_id_t pattern) +bool ecs_id_is_pair( + ecs_id_t id) { - /* 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 second are the same */ - ecs_id_t *ids = type.array; - int32_t i, count = type.count; - for (i = column + 1; i < count; i ++) { - ecs_id_t id = ids[i]; - if (!ECS_HAS_ID_FLAG(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_FIRST(id) == ECS_PAIR_SECOND(id)) { - /* Found a match! */ - return i; - } - } - - /* No pairs found with same rel/second */ - return -1; + return ECS_HAS_ID_FLAG(id, PAIR); } -static -int32_t find_next_column( - const ecs_world_t *world, - const ecs_table_t *table, - int32_t column, - ecs_rule_filter_t *filter) +bool ecs_id_is_wildcard( + ecs_id_t id) { - ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); - - ecs_entity_t pattern = filter->mask; - ecs_type_t type = table->type; - - if (column == -1) { - ecs_table_record_t *tr = flecs_table_record_get(world, table, pattern); - if (!tr) { - return -1; - } - column = tr->column; - } else { - column = ecs_search_offset(world, table, column + 1, filter->mask, 0); - if (column == -1) { - return -1; - } + if ((id == EcsWildcard) || (id == EcsAny)) { + return true; } - if (filter->same_var) { - column = find_next_same_var(type, column - 1, filter->mask); + bool is_pair = ECS_IS_PAIR(id); + if (!is_pair) { + return false; } - return column; + ecs_entity_t first = ECS_PAIR_FIRST(id); + ecs_entity_t second = ECS_PAIR_SECOND(id); + + return (first == EcsWildcard) || (second == EcsWildcard) || + (first == EcsAny) || (second == EcsAny); } -/* 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) +bool ecs_id_is_valid( + const ecs_world_t *world, + ecs_id_t id) { - ecs_table_cache_iter_t *it = &op_ctx->it; - ecs_table_t *table = NULL; - int32_t column = -1; - - const ecs_table_record_t *tr; - while ((column == -1) && (tr = flecs_table_cache_next(it, ecs_table_record_t))) { - table = tr->hdr.table; - - /* Should only iterate non-empty tables */ - ecs_assert(ecs_table_count(table) != 0, ECS_INTERNAL_ERROR, NULL); + if (!id) { + return false; + } + if (ecs_id_is_wildcard(id)) { + return false; + } - column = tr->column; - if (filter->same_var) { - column = find_next_same_var(table->type, column - 1, filter->mask); - } + world = ecs_get_world(world); + const ecs_id_record_t *idr = flecs_id_record_get(world, id); + if (idr && idr->flags & EcsIdMarkedForDelete) { + return false; } - if (column == -1) { - table = NULL; + if (ECS_HAS_ID_FLAG(id, PAIR)) { + if (!ECS_PAIR_FIRST(id)) { + return false; + } + if (!ECS_PAIR_SECOND(id)) { + return false; + } + } else if (id & ECS_ID_FLAGS_MASK) { + if (!ecs_is_valid(world, id & ECS_COMPONENT_MASK)) { + return false; + } } - return (ecs_table_record_t){.hdr.table = table, .column = column}; + return true; } - -static -ecs_id_record_t* find_tables( - ecs_world_t *world, +ecs_flags32_t ecs_id_get_flags( + const ecs_world_t *world, ecs_id_t id) { ecs_id_record_t *idr = flecs_id_record_get(world, id); - if (!idr || !flecs_table_cache_count(&idr->cache)) { - /* Skip ids that don't have (non-empty) tables */ - return NULL; + if (idr) { + return idr->flags; + } else { + return 0; } - return idr; } -static -ecs_id_t rule_get_column( - ecs_type_t type, - int32_t column) +bool ecs_term_id_is_set( + const ecs_term_id_t *id) { - ecs_assert(column < type.count, ECS_INTERNAL_ERROR, NULL); - ecs_id_t *comp = &type.array[column]; - return *comp; + return id->id != 0 || id->name != NULL || id->flags & EcsIsEntity; } -static -void set_source( - ecs_iter_t *it, - ecs_rule_op_t *op, - ecs_var_t *regs, - int32_t r) +bool ecs_term_is_initialized( + const ecs_term_t *term) { - if (op->term == -1) { - /* If operation is not associated with a term, don't set anything */ - return; - } + return term->id != 0 || ecs_term_id_is_set(&term->first); +} - ecs_assert(op->term >= 0, ECS_INTERNAL_ERROR, NULL); +bool ecs_term_match_this( + const ecs_term_t *term) +{ + return (term->src.flags & EcsIsVariable) && (term->src.id == EcsThis); +} - const ecs_rule_t *rule = it->priv.iter.rule.rule; - if ((r != UINT8_MAX) && rule->vars[r].kind == EcsRuleVarKindEntity) { - it->sources[op->term] = reg_get_entity(rule, op, regs, r); - } else { - it->sources[op->term] = 0; - } +bool ecs_term_match_0( + const ecs_term_t *term) +{ + return (term->src.id == 0) && (term->src.flags & EcsIsEntity); } -static -void set_term_vars( - const ecs_rule_t *rule, - ecs_var_t *regs, - int32_t term, - ecs_id_t id) +int ecs_term_finalize( + const ecs_world_t *world, + ecs_term_t *term) { - if (term != -1) { - ecs_world_t *world = rule->world; - const ecs_rule_term_vars_t *vars = &rule->term_vars[term]; - if (vars->first != -1) { - regs[vars->first].entity = ecs_pair_first(world, id); - ecs_assert(ecs_is_valid(world, regs[vars->first].entity), - ECS_INTERNAL_ERROR, NULL); - } - if (vars->second != -1) { - regs[vars->second].entity = ecs_pair_second(world, id); - ecs_assert(ecs_is_valid(world, regs[vars->second].entity), - ECS_INTERNAL_ERROR, NULL); - } - } + ecs_filter_finalize_ctx_t ctx = {0}; + ctx.world = world; + ctx.term = term; + return flecs_term_finalize(world, term, &ctx); } -/* 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) +ecs_term_t ecs_term_copy( + const ecs_term_t *src) { - (void)it; - (void)op; - (void)op_index; + ecs_term_t dst = *src; + dst.name = ecs_os_strdup(src->name); + dst.first.name = ecs_os_strdup(src->first.name); + dst.src.name = ecs_os_strdup(src->src.name); + dst.second.name = ecs_os_strdup(src->second.name); + return dst; +} - if (!redo) { - /* First operation executed by the iterator. Always return true. */ - return true; +ecs_term_t ecs_term_move( + ecs_term_t *src) +{ + if (src->move) { + ecs_term_t dst = *src; + src->name = NULL; + src->first.name = NULL; + src->src.name = NULL; + src->second.name = NULL; + dst.move = false; + return dst; } 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; + ecs_term_t dst = ecs_term_copy(src); + dst.move = false; + return dst; } } -static -bool eval_superset( - ecs_iter_t *it, - ecs_rule_op_t *op, - int32_t op_index, - bool redo) +void ecs_term_fini( + ecs_term_t *term) { - ecs_rule_iter_t *iter = &it->priv.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_var_t *regs = get_registers(iter, op); - - /* Get register indices for output */ - int32_t sp; - int32_t r = op->r_out; + ecs_os_free(term->first.name); + ecs_os_free(term->src.name); + ecs_os_free(term->second.name); + ecs_os_free(term->name); - /* Register cannot be a literal, since we need to store things in it */ - ecs_assert(r != UINT8_MAX, ECS_INTERNAL_ERROR, NULL); + term->first.name = NULL; + term->src.name = NULL; + term->second.name = NULL; + term->name = NULL; +} - /* Get queried for id, fill out potential variables */ - ecs_rule_pair_t pair = op->filter; +int ecs_filter_finalize( + const ecs_world_t *world, + ecs_filter_t *f) +{ + int32_t i, term_count = f->term_count, field_count = 0; + ecs_term_t *terms = f->terms; + bool is_or = false, prev_or = false; + ecs_flags32_t prev_src_flags = 0; + ecs_entity_t prev_src_id = 0; + int32_t filter_terms = 0; - ecs_rule_filter_t filter = pair_to_filter(iter, op, pair); - ecs_entity_t rel = ECS_PAIR_FIRST(filter.mask); - ecs_rule_filter_t super_filter = { - .mask = ecs_pair(rel, EcsWildcard) - }; - ecs_table_t *table = NULL; + ecs_filter_finalize_ctx_t ctx = {0}; + ctx.world = world; + ctx.filter = f; - /* Check if input register is constrained */ - ecs_entity_t result = iter->registers[r].entity; - bool output_is_input = ecs_iter_var_is_constrained(it, r); - if (output_is_input && !redo) { - ecs_assert(regs[r].entity == iter->registers[r].entity, - ECS_INTERNAL_ERROR, NULL); - } + f->flags |= EcsFilterMatchOnlyThis; + for (i = 0; i < term_count; i ++) { + ecs_term_t *term = &terms[i]; + ctx.term_index = i; + if (flecs_term_finalize(world, term, &ctx)) { + return -1; + } - if (!redo) { - op_ctx->stack = op_ctx->storage; - sp = op_ctx->sp = 0; - frame = &op_ctx->stack[sp]; + is_or = term->oper == EcsOr; + field_count += !(is_or && prev_or); + term->field_index = field_count - 1; - /* Get table of object for which to get supersets */ - ecs_entity_t second = ECS_PAIR_SECOND(filter.mask); - if (second == EcsWildcard) { - ecs_assert(pair.reg_mask & RULE_PAIR_OBJECT, - ECS_INTERNAL_ERROR, NULL); - table = regs[pair.second.reg].range.table; - } else { - table = table_from_entity(world, second).table; + if (prev_or && is_or) { + if (prev_src_flags != term->src.flags) { + flecs_filter_error(&ctx, "mismatching src.flags for OR terms"); + return -1; + } + if (prev_src_id != term->src.id) { + flecs_filter_error(&ctx, "mismatching src.id for OR terms"); + return -1; + } } - int32_t column; + prev_src_flags = term->src.flags; + prev_src_id = term->src.id; + prev_or = is_or; - /* If output variable is already set, check if it matches */ - if (output_is_input) { - ecs_id_t id = ecs_pair(rel, result); - ecs_entity_t src = 0; - column = ecs_search_relation(world, table, 0, id, rel, - 0, &src, 0, 0); - if (column != -1) { - if (src != 0) { - table = ecs_get_table(world, src); - } - } + if (ecs_term_match_this(term)) { + ECS_BIT_SET(f->flags, EcsFilterMatchThis); } else { - column = find_next_column(world, table, -1, &super_filter); + ECS_BIT_CLEAR(f->flags, EcsFilterMatchOnlyThis); } - /* If no matching column was found, there are no supersets */ - if (column == -1) { - return false; + if (term->id == EcsPrefab) { + ECS_BIT_SET(f->flags, EcsFilterMatchPrefab); + } + if (term->id == EcsDisabled) { + ECS_BIT_SET(f->flags, EcsFilterMatchDisabled); } - ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); - - ecs_id_t col_id = rule_get_column(table->type, column); - ecs_assert(ECS_HAS_ID_FLAG(col_id, PAIR), ECS_INTERNAL_ERROR, NULL); - ecs_entity_t col_obj = ecs_pair_second(world, col_id); - - reg_set_entity(rule, regs, r, col_obj); + if (ECS_BIT_IS_SET(f->flags, EcsFilterIsFilter)) { + term->inout = EcsInOutNone; + } - frame->table = table; - frame->column = column; + if (term->inout == EcsInOutNone) { + filter_terms ++; + } - return true; - } else if (output_is_input) { - return false; + if (term->oper != EcsNot || !ecs_term_match_this(term)) { + ECS_BIT_CLEAR(f->flags, EcsFilterMatchAnything); + } } - sp = op_ctx->sp; - frame = &op_ctx->stack[sp]; - table = frame->table; - int32_t column = frame->column; - - ecs_id_t col_id = rule_get_column(table->type, column); - ecs_entity_t col_obj = ecs_pair_second(world, col_id); - ecs_table_t *next_table = table_from_entity(world, col_obj).table; + f->field_count = field_count; - if (next_table) { - sp ++; - frame = &op_ctx->stack[sp]; - frame->table = next_table; - frame->column = -1; + if (filter_terms == term_count) { + ECS_BIT_SET(f->flags, EcsFilterIsFilter); } - do { - frame = &op_ctx->stack[sp]; - table = frame->table; - column = frame->column; - - column = find_next_column(world, table, column, &super_filter); - if (column != -1) { - op_ctx->sp = sp; - frame->column = column; - col_id = rule_get_column(table->type, column); - col_obj = ecs_pair_second(world, col_id); - reg_set_entity(rule, regs, r, col_obj); - return true; - } - - sp --; - } while (sp >= 0); - - return false; + return 0; } +/* Implementation for iterable mixin */ static -bool eval_subset( - ecs_iter_t *it, - ecs_rule_op_t *op, - int32_t op_index, - bool redo) +void flecs_filter_iter_init( + const ecs_world_t *world, + const ecs_poly_t *poly, + ecs_iter_t *iter, + ecs_term_t *filter) { - ecs_rule_iter_t *iter = &it->priv.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_var_t *regs = get_registers(iter, op); - - /* 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_poly_assert(poly, ecs_filter_t); - /* 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; + 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); + } +} - 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_filter_t* ecs_filter_init( + const ecs_world_t *stage, + const ecs_filter_desc_t *desc) +{ + ecs_check(stage != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(desc->_canary == 0, ECS_INVALID_PARAMETER, NULL); + const ecs_world_t *world = ecs_get_world(stage); - flecs_table_cache_iter(&idr->cache, &frame->with_ctx.it); - table_record = find_next_table(&filter, &frame->with_ctx); - - /* If first table set has no non-empty table, yield nothing */ - if (!table_record.hdr.table) { - return false; - } + ecs_filter_t *f = desc->storage; + int32_t i, term_count = desc->terms_buffer_count, storage_count = 0, expr_count = 0; + const ecs_term_t *terms = desc->terms_buffer; + ecs_term_t *storage_terms = NULL, *expr_terms = NULL; - frame->row = 0; - frame->column = table_record.column; - table_reg_set(rule, regs, r, (frame->table = table_record.hdr.table)); - goto yield; + if (f) { + ecs_check(f->hdr.magic == ecs_filter_t_magic, + ECS_INVALID_PARAMETER, NULL); + storage_count = f->term_count; + storage_terms = f->terms; + ecs_poly_init(f, ecs_filter_t); + } else { + f = ecs_poly_new(ecs_filter_t); + f->owned = true; + } + if (!storage_terms) { + f->terms_owned = true; } - do { - sp = op_ctx->sp; - frame = &op_ctx->stack[sp]; - table = frame->table; - row = frame->row; - - /* 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 (table_record.hdr.table) { - table = frame->table = table_record.hdr.table; - ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); - frame->row = 0; - frame->column = table_record.column; - table_reg_set(rule, regs, r, table); - goto yield; - } 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; + ECS_BIT_COND(f->flags, EcsFilterIsInstanced, desc->instanced); + ECS_BIT_SET(f->flags, EcsFilterMatchAnything); + f->flags |= desc->flags; - ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(idr != NULL, ECS_INTERNAL_ERROR, NULL); + /* If terms_buffer was not set, count number of initialized terms in + * static desc::terms array */ + if (!terms) { + ecs_check(term_count == 0, ECS_INVALID_PARAMETER, NULL); + terms = desc->terms; + for (i = 0; i < ECS_TERM_DESC_CACHE_SIZE; i ++) { + if (!ecs_term_is_initialized(&terms[i])) { + break; } + term_count ++; } + } else { + ecs_check(term_count != 0, ECS_INVALID_PARAMETER, NULL); + } - int32_t row_count = ecs_table_count(table); + /* If expr is set, parse query expression */ + const char *name = desc->name, *expr = desc->expr; + if (expr) { +#ifdef FLECS_PARSER + const char *ptr = desc->expr; + ecs_term_t term = {0}; + int32_t expr_size = 0; + while (ptr[0] && (ptr = ecs_parse_term(world, name, expr, ptr, &term))){ + if (!ecs_term_is_initialized(&term)) { + break; + } - /* Table must have at least row elements */ - ecs_assert(row_count > row, ECS_INTERNAL_ERROR, NULL); + if (expr_count == expr_size) { + expr_size = expr_size ? expr_size * 2 : 8; + expr_terms = ecs_os_realloc_n(expr_terms, ecs_term_t, expr_size); + } - ecs_entity_t *entities = ecs_storage_first(&table->data.entities); - ecs_assert(entities != NULL, ECS_INTERNAL_ERROR, NULL); + expr_terms[expr_count ++] = term; + if (ptr[0] == '\n') { + break; + } + } - /* The entity used to find the next table set */ - do { - ecs_entity_t e = entities[row]; + if (!ptr) { + /* Set terms in filter object to make sur they get cleaned up */ + f->terms = expr_terms; + f->term_count = expr_count; + f->terms_owned = true; + goto error; + } +#else + (void)expr; + ecs_abort(ECS_UNSUPPORTED, "parser addon is not available"); +#endif + } - /* 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.second.id = e; - filter = pair_to_filter(iter, op, pair); + /* If storage is provided, make sure it's large enough */ + ecs_check(!storage_terms || storage_count >= (term_count + expr_count), + ECS_INVALID_PARAMETER, NULL); - /* Find table set for expression */ - table = NULL; - idr = find_tables(world, filter.mask); + if (term_count || expr_count) { + /* If no storage is provided, create it */ + if (!storage_terms) { + f->terms = ecs_os_calloc_n(ecs_term_t, term_count + expr_count); + f->term_count = term_count + expr_count; + ecs_assert(f->terms_owned == true, ECS_INTERNAL_ERROR, NULL); + } else { + f->terms = storage_terms; + f->term_count = storage_count; + } - /* 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; - flecs_table_cache_iter(&idr->cache, &new_frame->with_ctx.it); - table_record = find_next_table(&filter, &new_frame->with_ctx); + /* Copy terms to filter storage */ + for (i = 0; i < term_count; i ++) { + f->terms[i] = ecs_term_copy(&terms[i]); + /* Allow freeing resources from expr parser during finalization */ + f->terms[i].move = true; + } - /* If set contains non-empty table, push it to stack */ - if (table_record.hdr.table) { - table = table_record.hdr.table; - op_ctx->sp ++; - new_frame->table = table; - new_frame->row = 0; - new_frame->column = table_record.column; - frame = new_frame; - } - } + /* Move expr terms to filter storage */ + for (i = 0; i < expr_count; i ++) { + f->terms[i + term_count] = ecs_term_move(&expr_terms[i]); + /* Allow freeing resources from expr parser during finalization */ + f->terms[i + term_count].move = true; + } + ecs_os_free(expr_terms); + } - /* If no table was found for the current entity, advance row */ - if (!table) { - row = ++ frame->row; - } - } while (!table && row < row_count); - } while (!table); + /* Ensure all fields are consistent and properly filled out */ + if (ecs_filter_finalize(world, f)) { + goto error; + } - table_reg_set(rule, regs, r, table); + /* Any allocated resources remaining in terms are now owned by filter */ + for (i = 0; i < f->term_count; i ++) { + f->terms[i].move = false; + } -yield: - set_term_vars(rule, regs, op->term, frame->table->type.array[frame->column]); + f->name = ecs_os_strdup(name); + f->variable_names[0] = (char*)"."; + f->iterable.init = flecs_filter_iter_init; - return true; + return f; +error: + ecs_filter_fini(f); + return NULL; } -/* 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 ecs_filter_copy( + ecs_filter_t *dst, + const ecs_filter_t *src) { - ecs_rule_iter_t *iter = &it->priv.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_var_t *regs = get_registers(iter, op); + if (src == dst) { + return; + } - /* Get register indices for output */ - int32_t r = op->r_out; - ecs_assert(r != UINT8_MAX, ECS_INTERNAL_ERROR, NULL); + if (src) { + *dst = *src; - /* 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); + int32_t i, term_count = src->term_count; + dst->terms = ecs_os_malloc_n(ecs_term_t, term_count); + dst->terms_owned = true; - int32_t column = -1; - ecs_table_t *table = NULL; - ecs_id_record_t *idr; + 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); + } +} - if (!redo && op->term != -1) { - columns[op->term] = -1; +void ecs_filter_move( + ecs_filter_t *dst, + ecs_filter_t *src) +{ + if (src == dst) { + return; } - /* 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. */ + if (src) { + *dst = *src; + if (src->terms_owned) { + dst->terms = src->terms; + dst->terms_owned = true; + } else { + ecs_filter_copy(dst, src); + } + src->terms = NULL; + src->term_count = 0; } 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 */ - - idr = op_ctx->idr = find_tables(world, pattern); + ecs_os_memset_t(dst, 0, ecs_filter_t); } +} - /* If no table set was found for queried for entity, there are no results */ - if (!idr) { - return false; +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]); + } + + if (filter->terms_owned) { + ecs_os_free(filter->terms); + } } - /* If the input register is not NULL, this is a variable that's been set by - * the application. */ - table = iter->registers[r].range.table; - bool output_is_input = table != NULL; + ecs_os_free(filter->name); - if (output_is_input && !redo) { - ecs_assert(regs[r].range.table == iter->registers[r].range.table, - ECS_INTERNAL_ERROR, NULL); + filter->terms = NULL; + filter->name = NULL; - table = iter->registers[r].range.table; + if (filter->owned) { + ecs_os_free(filter); + } +} - /* Check if table can be found in the id record. If not, the provided - * table does not match with the query. */ - ecs_table_record_t *tr = ecs_table_cache_get(&idr->cache, table); - if (!tr) { - return false; +static +void filter_str_add_id( + const ecs_world_t *world, + ecs_strbuf_t *buf, + const ecs_term_id_t *id, + bool is_subject, + ecs_flags32_t default_traverse_flags) +{ + bool is_added = false; + if (!is_subject || id->id != EcsThis) { + if (id->flags & EcsIsVariable) { + ecs_strbuf_appendstr(buf, "$"); } - - column = op_ctx->column = tr->column; + if (id->id) { + char *path = ecs_get_fullpath(world, id->id); + ecs_strbuf_appendstr(buf, path); + ecs_os_free(path); + } else if (id->name) { + ecs_strbuf_appendstr(buf, id->name); + } else { + ecs_strbuf_appendstr(buf, "0"); + } + is_added = true; } - /* If this is not a redo, start at the beginning */ - if (!redo) { - if (!table) { - flecs_table_cache_iter(&idr->cache, &op_ctx->it); - - /* 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.hdr.table) { - return false; - } - - table = table_record.hdr.table; - - /* Set current column to first occurrence of queried for entity */ - column = op_ctx->column = table_record.column; + ecs_flags32_t flags = id->flags; + if (!(flags & EcsTraverseFlags)) { + /* If flags haven't been set yet, initialize with defaults. This can + * happen if an error is thrown while the term is being finalized */ + flags |= default_traverse_flags; + } - /* Store table in register */ - table_reg_set(rule, regs, r, table); + if ((flags & EcsTraverseFlags) != default_traverse_flags) { + if (is_added) { + ecs_strbuf_list_push(buf, ":", "|"); + } else { + ecs_strbuf_list_push(buf, "", "|"); } - - /* 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).table; - ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); - - column = op_ctx->column; - column = find_next_column(world, table, column, &filter); - op_ctx->column = column; + if (id->flags & EcsSelf) { + ecs_strbuf_list_appendstr(buf, "self"); + } + if (id->flags & EcsUp) { + ecs_strbuf_list_appendstr(buf, "up"); + } + if (id->flags & EcsDown) { + ecs_strbuf_list_appendstr(buf, "down"); } - /* If no next match was found for this table, move to next table */ - if (column == -1) { - if (output_is_input) { - return false; - } - - table_record = find_next_table(&filter, op_ctx); - if (!table_record.hdr.table) { - return false; - } + if (id->trav && (id->trav != EcsIsA)) { + ecs_strbuf_list_push(buf, "(", ""); - /* Assign new table to table register */ - table_reg_set(rule, regs, r, (table = table_record.hdr.table)); + char *rel_path = ecs_get_fullpath(world, id->trav); + ecs_strbuf_appendstr(buf, rel_path); + ecs_os_free(rel_path); - /* Assign first matching column */ - column = op_ctx->column = table_record.column; + ecs_strbuf_list_pop(buf, ")"); } - } - - /* 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 (op->term != -1) { - columns[op->term] = column; - } - /* If this is a wildcard query, fill out the variable registers */ - if (filter.wildcard) { - reify_variables(iter, op, &filter, table->type, column); + ecs_strbuf_list_pop(buf, ""); } - - return true; } -/* 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 term_str_w_strbuf( + const ecs_world_t *world, + const ecs_term_t *term, + ecs_strbuf_t *buf) { - ecs_rule_iter_t *iter = &it->priv.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_var_t *regs = get_registers(iter, op); + const ecs_term_id_t *src = &term->src; + const ecs_term_id_t *second = &term->second; - /* Get register indices for input */ - int32_t r = op->r_in; + uint8_t def_src_mask = EcsSelf|EcsUp; + uint8_t def_first_mask = EcsSelf|EcsDown; + uint8_t def_second_mask = EcsSelf; - /* 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); + bool pred_set = ecs_term_id_is_set(&term->first); + bool subj_set = !ecs_term_match_0(term); + bool obj_set = ecs_term_id_is_set(second); - /* 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; + if (term->first.flags & EcsIsEntity && term->first.id != 0) { + if (ecs_has_id(world, term->first.id, EcsFinal)) { + def_first_mask = EcsSelf; + } + if (ecs_has_id(world, term->first.id, EcsDontInherit)) { + def_src_mask = EcsSelf; + } } - int32_t column = -1; - ecs_table_t *table = NULL; - ecs_id_record_t *idr; - - if (op->term != -1) { - columns[op->term] = -1; + if (term->oper == EcsNot) { + ecs_strbuf_appendstr(buf, "!"); + } else if (term->oper == EcsOptional) { + ecs_strbuf_appendstr(buf, "?"); } - /* 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 { - /* Predicates can be reflexive, 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_reflexive - * instruction to limit branches for non-transitive queries (and to keep - * code more readable). - */ - if (pair.transitive && pair.reflexive) { - ecs_entity_t src = 0, second = 0; - - if (r == UINT8_MAX) { - src = op->subject; - } else { - const ecs_rule_var_t *v_subj = &rule->vars[r]; + if (!subj_set) { + filter_str_add_id(world, buf, &term->first, false, def_first_mask); + ecs_strbuf_appendstr(buf, "()"); + } else if (ecs_term_match_this(term) && + (src->flags & EcsTraverseFlags) == def_src_mask) + { + if (pred_set) { + if (obj_set) { + ecs_strbuf_appendstr(buf, "("); + } + filter_str_add_id(world, buf, &term->first, false, def_first_mask); + if (obj_set) { + ecs_strbuf_appendstr(buf, ","); + filter_str_add_id( + world, buf, &term->second, false, def_second_mask); + ecs_strbuf_appendstr(buf, ")"); + } + } else if (term->id) { + char *str = ecs_id_str(world, term->id); + ecs_strbuf_appendstr(buf, str); + ecs_os_free(str); + } + } else { + if (term->id_flags && !ECS_HAS_ID_FLAG(term->id_flags, PAIR)) { + ecs_strbuf_appendstr(buf, ecs_id_flag_str(term->id_flags)); + ecs_strbuf_appendch(buf, '|'); + } - if (v_subj->kind == EcsRuleVarKindEntity) { - src = entity_reg_get(rule, regs, r); + filter_str_add_id(world, buf, &term->first, false, def_first_mask); + ecs_strbuf_appendstr(buf, "("); + if (term->src.flags & EcsIsEntity && term->src.id == term->first.id) { + ecs_strbuf_appendstr(buf, "$"); + } else { + filter_str_add_id(world, buf, &term->src, true, def_src_mask); + } + if (obj_set) { + ecs_strbuf_appendstr(buf, ","); + filter_str_add_id(world, buf, &term->second, false, def_second_mask); + } + ecs_strbuf_appendstr(buf, ")"); + } +} - /* This is the input for the op, so should always be set */ - ecs_assert(src != 0, ECS_INTERNAL_ERROR, NULL); +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); +} + +static +char* flecs_filter_str( + const ecs_world_t *world, + const ecs_filter_t *filter, + const ecs_filter_finalize_ctx_t *ctx, + int32_t *term_start_out) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(filter != NULL, ECS_INVALID_PARAMETER, NULL); + + ecs_strbuf_t buf = ECS_STRBUF_INIT; + ecs_term_t *terms = filter->terms; + int32_t i, count = filter->term_count; + int32_t or_count = 0; + + for (i = 0; i < count; i ++) { + ecs_term_t *term = &terms[i]; + + if (term_start_out && ctx) { + if (ctx->term_index == i) { + term_start_out[0] = ecs_strbuf_written(&buf); + if (i) { + term_start_out[0] += 2; /* whitespace + , */ } } + } - /* If src is set, it means that it is an entity. Try to also - * resolve the object. */ - if (src) { - /* If the object is not a wildcard, it has been reified. Get the - * value from either the register or as a literal */ - if (!filter.second_wildcard) { - second = ECS_PAIR_SECOND(filter.mask); - if (ecs_strip_generation(src) == second) { - return true; - } - } + if (i) { + if (terms[i - 1].oper == EcsOr && term->oper == EcsOr) { + ecs_strbuf_appendstr(&buf, " || "); + } else { + ecs_strbuf_appendstr(&buf, ", "); } } - /* 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); - } + if (term->oper != EcsOr) { + or_count = 0; + } - /* 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; - } + 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] "); + } else if (term->inout == EcsInOutNone) { + ecs_strbuf_appendstr(&buf, "[none] "); + } + } - table = reg_get_range(rule, op, regs, r).table; - if (!table) { - return false; + if (term->oper == EcsOr) { + or_count ++; + } + + term_str_w_strbuf(world, term, &buf); } - /* If this is not a redo, start at the beginning */ - if (!redo) { - column = op_ctx->column = find_next_column(world, table, -1, &filter); + return ecs_strbuf_get(&buf); +error: + return NULL; +} + +char* ecs_filter_str( + const ecs_world_t *world, + const ecs_filter_t *filter) +{ + return flecs_filter_str(world, filter, NULL, NULL); +} + +int32_t ecs_filter_find_this_var( + const ecs_filter_t *filter) +{ + ecs_check(filter != NULL, ECS_INVALID_PARAMETER, NULL); - /* 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, op_ctx->column, &filter); - op_ctx->column = column; + if (ECS_BIT_IS_SET(filter->flags, EcsFilterMatchThis)) { + /* Filters currently only support the This variable at index 0. Only + * return 0 if filter actually has terms for the This variable. */ + return 0; } - /* If no next match was found for this table, no more data */ - if (column == -1) { +error: + return -1; +} + +/* Check if the id is a pair that has Any as first or second element. Any + * pairs behave just like Wildcard pairs and reuses the same data structures, + * with as only difference that the number of results returned for an Any pair + * is never more than one. This function is used to tell the difference. */ +static +bool is_any_pair( + ecs_id_t id) +{ + if (!ECS_HAS_ID_FLAG(id, PAIR)) { return false; } - if (op->term != -1) { - columns[op->term] = column; + if (ECS_PAIR_FIRST(id) == EcsAny) { + return true; } - - /* 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 this is a wildcard query, fill out the variable registers */ - if (filter.wildcard) { - reify_variables(iter, op, &filter, table->type, column); + if (ECS_PAIR_SECOND(id) == EcsAny) { + return true; } - set_source(it, op, regs, r); - - return true; + return false; } -/* 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) +bool flecs_n_term_match_table( + ecs_world_t *world, + const ecs_term_t *term, + const ecs_table_t *table, + ecs_entity_t type_id, + ecs_oper_kind_t oper, + ecs_id_t *id_out, + int32_t *column_out, + ecs_entity_t *subject_out, + int32_t *match_index_out, + bool first, + ecs_flags32_t iter_flags) { - ecs_rule_iter_t *iter = &it->priv.iter.rule; - ecs_rule_each_ctx_t *op_ctx = &iter->op_ctx[op_index].is.each; - ecs_var_t *regs = get_registers(iter, op); - int32_t r_in = op->r_in; - int32_t r_out = op->r_out; - ecs_entity_t e; + (void)column_out; - /* Make sure in/out registers are of the correct kind */ - ecs_assert(iter->rule->vars[r_in].kind == EcsRuleVarKindTable, - ECS_INTERNAL_ERROR, NULL); - ecs_assert(iter->rule->vars[r_out].kind == EcsRuleVarKindEntity, - ECS_INTERNAL_ERROR, NULL); + const ecs_type_t *type = ecs_get_type(world, type_id); + ecs_assert(type != NULL, ECS_INTERNAL_ERROR, NULL); - /* Get table, make sure that it contains data. The select operation should - * ensure that empty tables are never forwarded. */ - ecs_table_range_t slice = table_reg_get(iter->rule, regs, r_in); - ecs_table_t *table = slice.table; - if (table) { - int32_t row, count = slice.count; - int32_t offset = slice.offset; + ecs_id_t *ids = type->array; + int32_t i, count = type->count; + ecs_term_t temp = *term; + temp.oper = EcsAnd; - if (!count) { - count = ecs_table_count(table); - ecs_assert(count != 0, ECS_INTERNAL_ERROR, NULL); - } else { - count += offset; + for (i = 0; i < count; i ++) { + ecs_id_t id = ids[i]; + if (ecs_id_get_flags(world, id) & EcsIdDontInherit) { + continue; } - - ecs_entity_t *entities = ecs_storage_first(&table->data.entities); - ecs_assert(entities != NULL, 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; + bool result; + if (ECS_HAS_ID_FLAG(id, AND) || ECS_HAS_ID_FLAG(id, OR)) { + ecs_oper_kind_t id_oper = ECS_HAS_ID_FLAG(id, AND) + ? EcsAndFrom : ECS_HAS_ID_FLAG(id, OR) + ? EcsOrFrom : 0; + result = flecs_n_term_match_table(world, term, table, + id & ECS_COMPONENT_MASK, id_oper, id_out, column_out, + subject_out, match_index_out, first, iter_flags); } else { - row = ++ op_ctx->row; + temp.id = id; + result = flecs_term_match_table(world, &temp, table, id_out, + 0, subject_out, match_index_out, first, iter_flags); } - - /* If row exceeds number of entities in table, return false */ - if (row >= count) { + if (!result && oper == EcsAndFrom) { return false; + } else + if (result && oper == EcsOrFrom) { + return true; } + } - /* Skip builtin entities that could confuse operations */ - e = entities[row]; - while (e == EcsWildcard || e == EcsThis || e == EcsAny) { - 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 (oper == EcsAndFrom) { + if (id_out) { + id_out[0] = type_id; } + return true; + } else + if (oper == EcsOrFrom) { + return false; } - /* Assign entity */ - entity_reg_set(iter->rule, regs, r_out, e); - - return true; + return false; } -/* 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) +bool flecs_term_match_table( + ecs_world_t *world, + const ecs_term_t *term, + const ecs_table_t *table, + ecs_id_t *id_out, + int32_t *column_out, + ecs_entity_t *subject_out, + int32_t *match_index_out, + bool first, + ecs_flags32_t iter_flags) { - (void)op_index; + const ecs_term_id_t *src = &term->src; + ecs_oper_kind_t oper = term->oper; + const ecs_table_t *match_table = table; + ecs_id_t id = term->id; - if (redo) { - /* Only ever return result once */ - return false; + ecs_entity_t src_id = src->id; + if (ecs_term_match_0(term)) { + if (id_out) { + id_out[0] = id; /* If no entity is matched, just set id */ + } + return true; } - ecs_rule_iter_t *iter = &it->priv.iter.rule; - const ecs_rule_t *rule = iter->rule; - ecs_world_t *world = rule->world; - ecs_var_t *regs = get_registers(iter, op); - int32_t r_in = op->r_in; - int32_t r_out = op->r_out; + if (oper == EcsAndFrom || oper == EcsOrFrom) { + return flecs_n_term_match_table(world, term, table, term->id, + term->oper, id_out, column_out, subject_out, match_index_out, first, + iter_flags); + } - (void)world; + /* If source is not This, search in table of source */ + if (!ecs_term_match_this(term)) { + if (iter_flags & EcsIterEntityOptional) { + /* Treat entity terms as optional */ + oper = EcsOptional; + } - const ecs_rule_var_t *var_out = &rule->vars[r_out]; - if (var_out->kind == EcsRuleVarKindEntity) { - ecs_entity_t out, in = reg_get_entity(rule, op, regs, r_in); - ecs_assert(in != 0, ECS_INTERNAL_ERROR, NULL); - ecs_assert(ecs_is_valid(world, in), ECS_INTERNAL_ERROR, NULL); + match_table = ecs_get_table(world, src_id); + if (match_table) { + } else if (oper != EcsOptional) { + return false; + } + } else { + /* If filter contains This terms, a table must be provided */ + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + } - out = iter->registers[r_out].entity; - bool output_is_input = ecs_iter_var_is_constrained(it, r_out); - if (output_is_input && !redo) { - ecs_assert(regs[r_out].entity == iter->registers[r_out].entity, - ECS_INTERNAL_ERROR, NULL); + if (!match_table) { + return false; + } - if (out != in) { - /* If output variable is set it must match the input */ - return false; - } + ecs_entity_t source = 0; + + /* 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; } - reg_set_entity(rule, regs, r_out, in); - } else { - ecs_table_range_t out, in = reg_get_range(rule, op, regs, r_in); + /* Remove base 1 offset */ + column --; + } - out = iter->registers[r_out].range; - bool output_is_input = out.table != NULL; + /* Find location, source and id of match in table type */ + ecs_table_record_t *tr = 0; + bool is_any = is_any_pair(id); - if (output_is_input && !redo) { - ecs_assert(regs[r_out].entity == iter->registers[r_out].entity, - ECS_INTERNAL_ERROR, NULL); + column = ecs_search_relation(world, match_table, + column, id, src->trav, src->flags, &source, id_out, &tr); - if (ecs_os_memcmp_t(&out, &in, ecs_table_range_t)) { - /* If output variable is set it must match the input */ - return false; - } + if (tr && match_index_out) { + if (!is_any) { + match_index_out[0] = tr->count; + } else { + match_index_out[0] = 1; } + } - reg_set_range(rule, regs, r_out, &in); + bool result = column != -1; - /* Ensure that if the input was an empty entity, information is not - * lost */ - if (!regs[r_out].range.table) { - regs[r_out].entity = reg_get_entity(rule, op, regs, r_in); - ecs_assert(ecs_is_valid(world, regs[r_out].entity), - ECS_INTERNAL_ERROR, NULL); + if (oper == EcsNot) { + if (match_index_out) { + match_index_out[0] = 1; } + result = !result; } - ecs_rule_filter_t filter = pair_to_filter(iter, op, op->filter); - set_term_vars(rule, regs, op->term, filter.mask); + if (oper == EcsOptional) { + result = true; + } - return true; -} + if (!result) { + if (iter_flags & EcsFilterPopulate) { + column = 0; + } else { + return false; + } + } -/* 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->priv.iter.rule; - ecs_rule_setjmp_ctx_t *ctx = &iter->op_ctx[op_index].is.setjmp; + if (!ecs_term_match_this(term)) { + if (!source) { + source = src_id; + } + } - if (!redo) { - ctx->label = op->on_pass; - return true; - } else { - ctx->label = op->on_fail; - return false; + if (id_out && column < 0) { + id_out[0] = id; + } + + if (column_out) { + if (column >= 0) { + column ++; + if (source != 0) { + column *= -1; + } + column_out[0] = column; + } else { + column_out[0] = 0; + } + } + + if (subject_out) { + subject_out[0] = source; } + + return result; } -/* 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) +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 *sources, + int32_t *match_indices, + int32_t *matches_left, + bool first, + int32_t skip_term, + ecs_flags32_t iter_flags) { - (void)it; - (void)op; - (void)op_index; + ecs_term_t *terms = filter->terms; + int32_t i, count = filter->term_count; - /* Passthrough, result is not used for control flow */ - return !redo; -} + bool is_or = false; + bool or_result = false; + int32_t match_count = 1; + if (matches_left) { + match_count = *matches_left; + } -/* 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; + for (i = 0; i < count; i ++) { + if (i == skip_term) { + continue; + } - return !redo; -} + ecs_term_t *term = &terms[i]; + ecs_term_id_t *src = &term->src; + ecs_oper_kind_t oper = term->oper; + const ecs_table_t *match_table = table; + int32_t t_i = term->field_index; -/* Check if entity is stored in table */ -static -bool eval_intable( - ecs_iter_t *it, - ecs_rule_op_t *op, - int32_t op_index, - bool redo) -{ - (void)op_index; - - if (redo) { - return false; - } + if (!is_or && oper == EcsOr) { + is_or = true; + or_result = false; + } else if (is_or && oper != EcsOr) { + if (!or_result) { + return false; + } - ecs_rule_iter_t *iter = &it->priv.iter.rule; - const ecs_rule_t *rule = iter->rule; - ecs_world_t *world = rule->world; - ecs_var_t *regs = get_registers(iter, op); - ecs_table_t *table = table_reg_get(rule, regs, op->r_in).table; + is_or = false; + } - ecs_rule_pair_t pair = op->filter; - ecs_rule_filter_t filter = pair_to_filter(iter, op, pair); - ecs_entity_t second = ECS_PAIR_SECOND(filter.mask); - ecs_assert(second != 0 && second != EcsWildcard, ECS_INTERNAL_ERROR, NULL); - second = ecs_get_alive(world, second); - ecs_assert(second != 0, ECS_INTERNAL_ERROR, NULL); + ecs_entity_t src_id = src->id; + if (!src_id) { + if (ids) { + ids[t_i] = term->id; + } + continue; + } - ecs_table_t *obj_table = ecs_get_table(world, second); - return obj_table == table; -} + if (!ecs_term_match_this(term)) { + match_table = ecs_get_table(world, src_id); + } else { + if (ECS_BIT_IS_SET(iter_flags, EcsIterIgnoreThis)) { + or_result = true; + continue; + } + + /* If filter contains This terms, table must be provided */ + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + } -/* 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; + int32_t match_index = 0; - /* Yield always returns false, because there are never any operations after - * a yield. */ - return false; + bool result = flecs_term_match_table(world, term, match_table, + ids ? &ids[t_i] : NULL, + columns ? &columns[t_i] : NULL, + sources ? &sources[t_i] : NULL, + &match_index, + first, + iter_flags); + + if (is_or) { + or_result |= result; + if (result) { + /* If Or term matched, skip following Or terms */ + for (; i < count && terms[i].oper == EcsOr; i ++) { } + i -- ; + } + } else if (!result) { + return false; + } + + if (first && match_index) { + match_count *= match_index; + } + if (match_indices) { + match_indices[t_i] = match_index; + } + } + + if (matches_left) { + *matches_left = match_count; + } + + return !is_or || or_result; } -/* Dispatcher for operations */ static -bool eval_op( - ecs_iter_t *it, - ecs_rule_op_t *op, - int32_t op_index, - bool redo) +void term_iter_init_no_data( + ecs_term_iter_t *iter) { - 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 EcsRuleInTable: - return eval_intable(it, op, op_index, redo); - case EcsRuleYield: - return eval_yield(it, op, op_index, redo); - default: - return false; - } + iter->term = (ecs_term_t){ .field_index = -1 }; + iter->self_index = NULL; + iter->index = 0; } -/* 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) +void term_iter_init_w_idr( + ecs_term_iter_t *iter, + ecs_id_record_t *idr, + bool empty_tables) { - if (!it->rule->var_count) { - return; + if (idr) { + if (empty_tables) { + flecs_table_cache_all_iter(&idr->cache, &iter->it); + } else { + flecs_table_cache_iter(&idr->cache, &iter->it); + } + } else { + term_iter_init_no_data(iter); } - ecs_var_t *src_regs = get_register_frame(it, cur); - ecs_var_t *dst_regs = get_register_frame(it, next); - - ecs_os_memcpy_n(dst_regs, src_regs, - ecs_var_t, it->rule->var_count); + iter->index = 0; + iter->empty_tables = empty_tables; } -/* 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) +void term_iter_init_wildcard( + const ecs_world_t *world, + ecs_term_iter_t *iter, + bool empty_tables) { - if (!it->rule->filter.term_count) { - return; - } - - 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); + iter->term = (ecs_term_t){ .field_index = -1 }; + iter->self_index = flecs_id_record_get(world, EcsAny); + ecs_id_record_t *idr = iter->cur = iter->self_index; + term_iter_init_w_idr(iter, idr, empty_tables); } -/* 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_var_t *regs = get_register_frame(it, op->frame); - ecs_table_t *table = NULL; - int32_t count = 0; - int32_t offset = 0; +void term_iter_init( + const ecs_world_t *world, + ecs_term_t *term, + ecs_term_iter_t *iter, + bool empty_tables) +{ + const ecs_term_id_t *src = &term->src; - /* 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) { - const ecs_rule_var_t *var = &rule->vars[r]; - ecs_var_t *reg = ®s[r]; + iter->term = *term; - if (var->kind == EcsRuleVarKindTable) { - ecs_table_range_t slice = table_reg_get(rule, regs, r); - table = slice.table; - count = slice.count; - offset = slice.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); + if (src->flags & EcsSelf) { + iter->self_index = flecs_query_id_record_get(world, term->id); + } - ecs_entity_t e = reg->entity; - ecs_assert(ecs_is_valid(world, e), ECS_INTERNAL_ERROR, NULL); - ecs_record_t *record = flecs_entities_get(world, e); - offset = ECS_RECORD_TO_ROW(record->row); + if (src->flags & EcsUp) { + iter->set_index = flecs_id_record_get(world, + ecs_pair(src->trav, EcsWildcard)); + } - /* 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_id_record_t *idr; + if (iter->self_index) { + idr = iter->cur = iter->self_index; + } else { + idr = iter->cur = iter->set_index; } - int32_t i, var_count = rule->var_count; - int32_t term_count = rule->filter.term_count; + term_iter_init_w_idr(iter, idr, empty_tables); +} - for (i = 0; i < var_count; i ++) { - iter->variables[i] = regs[i]; - } +ecs_iter_t ecs_term_iter( + const ecs_world_t *stage, + ecs_term_t *term) +{ + ecs_check(stage != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(term != NULL, ECS_INVALID_PARAMETER, NULL); - for (i = 0; i < term_count; i ++) { - int32_t v = rule->term_vars[i].src; - if (v != -1) { - const ecs_rule_var_t *var = &rule->vars[v]; - if (var->name[0] != '.') { - if (var->kind == EcsRuleVarKindEntity) { - iter->sources[i] = regs[var->id].entity; - } else { - /* This can happen for Any variables, where the actual - * content of the variable is not of interest to the query. - * Just pick the first entity from the table, so that the - * column can be correctly resolved */ - ecs_table_t *t = regs[var->id].range.table; - if (t) { - iter->sources[i] = ecs_storage_first_t( - &t->data.entities, ecs_entity_t)[0]; - } else { - /* Can happen if term is optional */ - iter->sources[i] = 0; - } - } - } - } - } + const ecs_world_t *world = ecs_get_world(stage); - /* 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_assert(iter->sources != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_entity_t src = iter->sources[i]; - int32_t c = ++ iter->columns[i]; - if (!src) { - src = iter->terms[i].src.id; - if (src != EcsThis && src != EcsAny) { - iter->columns[i] = 0; - } - } else if (c) { - iter->columns[i] = -1; - } + flecs_process_pending_tables(world); + + if (ecs_term_finalize(world, term)) { + ecs_throw(ECS_INVALID_PARAMETER, NULL); } - /* Set iterator ids */ - for (i = 0; i < term_count; i ++) { - const ecs_rule_term_vars_t *vars = &rule->term_vars[i]; - ecs_term_t *term = &rule->filter.terms[i]; - if (term->oper == EcsOptional || term->oper == EcsNot) { - if (iter->columns[i] == 0) { - iter->ids[i] = term->id; - continue; - } - } + ecs_iter_t it = { + .real_world = (ecs_world_t*)world, + .world = (ecs_world_t*)stage, + .field_count = 1, + .next = ecs_term_next + }; - ecs_id_t id = term->id; - ecs_entity_t first = 0; - ecs_entity_t second = 0; - bool is_pair = ECS_HAS_ID_FLAG(id, PAIR); + /* Term iter populates the iterator with arrays from its own cache, ensure + * they don't get overwritten by flecs_iter_validate. + * + * Note: the reason the term iterator doesn't use the iterator cache itself + * (which could easily accomodate a single term) is that the filter iterator + * is built on top of the term iterator. The private cache of the term + * iterator keeps the filter iterator code simple, as it doesn't need to + * worry about the term iter overwriting the iterator fields. */ + flecs_iter_init(&it, 0); - if (!is_pair) { - first = id; - } else { - first = ECS_PAIR_FIRST(id); - second = ECS_PAIR_SECOND(id); - } + term_iter_init(world, term, &it.priv.iter.term, false); - if (vars->first != -1) { - first = regs[vars->first].entity; - } - if (vars->second != -1) { - ecs_assert(is_pair, ECS_INTERNAL_ERROR, NULL); - second = regs[vars->second].entity; - } + return it; +error: + return (ecs_iter_t){ 0 }; +} - if (!is_pair) { - id = first; - } else { - id = ecs_pair(first, second); - } +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); - iter->ids[i] = id; + ecs_world_t *world = chain_it->real_world; + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + + if (ecs_term_finalize(world, term)) { + ecs_throw(ECS_INVALID_PARAMETER, NULL); } - flecs_iter_populate_data(world, iter, table, offset, count, - iter->ptrs, iter->sizes); + ecs_iter_t it = { + .real_world = (ecs_world_t*)world, + .world = chain_it->world, + .terms = term, + .field_count = 1, + .chain_it = (ecs_iter_t*)chain_it, + .next = ecs_term_next + }; + + flecs_iter_init(&it, flecs_iter_cache_all); + + term_iter_init(world, term, &it.priv.iter.term, false); + + return it; +error: + return (ecs_iter_t){ 0 }; } static -bool is_control_flow( - ecs_rule_op_t *op) +const ecs_table_record_t *flecs_term_iter_next_table( + ecs_term_iter_t *iter) { - switch(op->kind) { - case EcsRuleSetJmp: - case EcsRuleJump: - return true; - default: - return false; + ecs_id_record_t *idr = iter->cur; + if (!idr) { + return NULL; } + + return flecs_table_cache_next(&iter->it, ecs_table_record_t); } static -void rule_iter_set_initial_state( - ecs_iter_t *it, - ecs_rule_iter_t *iter, - const ecs_rule_t *rule) +bool flecs_term_iter_find_superset( + ecs_world_t *world, + ecs_table_t *table, + ecs_term_t *term, + ecs_entity_t *source, + ecs_id_t *id, + int32_t *column) { - int32_t i; + ecs_term_id_t *src = &term->src; - /* Make sure that if there are any terms with literal sources, they're - * initialized in the sources array */ - const ecs_filter_t *filter = &rule->filter; - int32_t term_count = filter->term_count; - for (i = 0; i < term_count; i ++) { - ecs_term_t *t = &filter->terms[i]; - ecs_term_id_t *src = &t->src; - ecs_assert(src->flags & EcsIsVariable || src->id != EcsThis, - ECS_INTERNAL_ERROR, NULL); + /* Test if following the relationship finds the id */ + int32_t index = ecs_search_relation(world, table, 0, + term->id, src->trav, src->flags, source, id, 0); - if (!(src->flags & EcsIsVariable)) { - it->sources[i] = src->id; - } + if (index == -1) { + *source = 0; + return false; } - /* Initialize registers of constrained variables */ - if (it->constrained_vars) { - ecs_var_t *regs = get_register_frame(iter, 0); + ecs_assert(*source != 0, ECS_INTERNAL_ERROR, NULL); - for (i = 0; i < it->variable_count; i ++) { - if (ecs_iter_var_is_constrained(it, i)) { - const ecs_rule_var_t *var = &rule->vars[i]; - ecs_assert(var->id == i, ECS_INTERNAL_ERROR, NULL); - - int32_t other_var = var->other; - ecs_var_t *it_var = &it->variables[i]; - ecs_entity_t e = it_var->entity; + *column = (index + 1) * -1; - if (e) { - ecs_assert(ecs_is_valid(it->world, e), - ECS_INTERNAL_ERROR, NULL); - reg_set_entity(rule, regs, i, e); - if (other_var != -1) { - reg_set_entity(rule, regs, other_var, e); - } - } else { - ecs_assert(it_var->range.table != NULL, - ECS_INVALID_PARAMETER, NULL); - reg_set_range(rule, regs, i, &it_var->range); - if (other_var != -1) { - reg_set_range(rule, regs, other_var, &it_var->range); + return true; +} + +static +bool flecs_term_iter_next( + ecs_world_t *world, + ecs_term_iter_t *iter, + bool match_prefab, + bool match_disabled) +{ + ecs_table_t *table = iter->table; + ecs_entity_t source = 0; + const ecs_table_record_t *tr; + ecs_term_t *term = &iter->term; + + do { + if (table) { + iter->cur_match ++; + if (iter->cur_match >= iter->match_count) { + table = NULL; + } else { + iter->last_column = ecs_search_offset( + world, table, iter->last_column + 1, term->id, 0); + iter->column = iter->last_column + 1; + if (iter->last_column >= 0) { + iter->id = table->type.array[iter->last_column]; + } + } + } + + if (!table) { + if (!(tr = flecs_term_iter_next_table(iter))) { + if (iter->cur != iter->set_index && iter->set_index != NULL) { + if (iter->observed_table_count != 0) { + iter->cur = iter->set_index; + if (iter->empty_tables) { + flecs_table_cache_all_iter( + &iter->set_index->cache, &iter->it); + } else { + flecs_table_cache_iter( + &iter->set_index->cache, &iter->it); + } + iter->index = 0; + tr = flecs_term_iter_next_table(iter); } } + + if (!tr) { + return false; + } + } + + table = tr->hdr.table; + if (table->observed_count) { + iter->observed_table_count ++; + } + + if (!match_prefab && (table->flags & EcsTableIsPrefab)) { + continue; + } + + if (!match_disabled && (table->flags & EcsTableIsDisabled)) { + continue; + } + + iter->table = table; + iter->match_count = tr->count; + if (is_any_pair(term->id)) { + iter->match_count = 1; + } + + iter->cur_match = 0; + iter->last_column = tr->column; + iter->column = tr->column + 1; + iter->id = flecs_to_public_id(table->type.array[tr->column]); + } + + if (iter->cur == iter->set_index) { + if (iter->self_index) { + if (flecs_id_record_get_table(iter->self_index, table) != NULL) { + /* If the table has the id itself and this term matched Self + * we already matched it */ + continue; + } + } + + if (!flecs_term_iter_find_superset( + world, table, term, &source, &iter->id, &iter->column)) + { + continue; } + + /* The tr->count field refers to the number of relationship instances, + * not to the number of matches. Superset terms can only yield a + * single match. */ + iter->match_count = 1; } - } + + break; + } while (true); + + iter->subject = source; + + return true; } -bool ecs_rule_next( - ecs_iter_t *it) +static +bool flecs_term_iter_set_table( + ecs_world_t *world, + ecs_term_iter_t *iter, + ecs_table_t *table) { - ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(it->next == ecs_rule_next, ECS_INVALID_PARAMETER, NULL); + const ecs_table_record_t *tr = NULL; + const ecs_id_record_t *idr = iter->self_index; + if (idr) { + tr = ecs_table_cache_get(&idr->cache, table); + if (tr) { + iter->match_count = tr->count; + iter->last_column = tr->column; + iter->column = tr->column + 1; + iter->id = flecs_to_public_id(table->type.array[tr->column]); + } + } - if (flecs_iter_next_row(it)) { - return true; + if (!tr) { + idr = iter->set_index; + if (idr) { + tr = ecs_table_cache_get(&idr->cache, table); + if (!flecs_term_iter_find_superset(world, table, &iter->term, + &iter->subject, &iter->id, &iter->column)) + { + return false; + } + iter->match_count = 1; + } } - return flecs_iter_next_instanced(it, ecs_rule_next_instanced(it)); -error: - return false; + if (!tr) { + return false; + } + + /* Populate fields as usual */ + iter->table = table; + iter->cur_match = 0; + + return true; } -/* 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_instanced( +bool ecs_term_next( ecs_iter_t *it) { ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(it->next == ecs_rule_next, ECS_INVALID_PARAMETER, NULL); - - ecs_rule_iter_t *iter = &it->priv.iter.rule; - const ecs_rule_t *rule = iter->rule; - bool redo = iter->redo; - int32_t last_frame = -1; - bool first_time = !ECS_BIT_IS_SET(it->flags, EcsIterIsValid); - - ecs_poly_assert(rule, ecs_rule_t); + ecs_check(it->next == ecs_term_next, ECS_INVALID_PARAMETER, NULL); - /* Mark iterator as valid & ensure iterator resources are up to date */ flecs_iter_validate(it); - /* Can't iterate an iterator that's already depleted */ - ecs_check(iter->op != -1, ECS_INVALID_PARAMETER, NULL); + ecs_term_iter_t *iter = &it->priv.iter.term; + ecs_term_t *term = &iter->term; + ecs_world_t *world = it->real_world; + ecs_table_t *table; - /* If this is the first time the iterator is iterated, set initial state */ - if (first_time) { - ecs_assert(redo == false, ECS_INTERNAL_ERROR, NULL); - rule_iter_set_initial_state(it, iter, rule); + it->ids = &iter->id; + it->sources = &iter->subject; + it->columns = &iter->column; + it->terms = &iter->term; + it->sizes = &iter->size; + + if (term->inout != EcsInOutNone) { + it->ptrs = &iter->ptr; + } else { + it->ptrs = NULL; } - 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; + ecs_iter_t *chain_it = it->chain_it; + if (chain_it) { + ecs_iter_next_action_t next = chain_it->next; + bool match; - /* 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); + do { + if (!next(chain_it)) { + goto done; + } + + table = chain_it->table; + match = flecs_term_match_table(world, term, table, + it->ids, it->columns, it->sources, it->match_indices, true, + it->flags); + } while (!match); + goto yield; + + } else { + if (!flecs_term_iter_next(world, iter, false, false)) { + goto done; } - /* Dispatch the operation */ - bool result = eval_op(it, op, op_index, redo); - iter->op = result ? op->on_pass : op->on_fail; + table = iter->table; - /* If the current operation is yield, return results */ - if (op->kind == EcsRuleYield) { - populate_iterator(rule, it, iter, op); - iter->redo = true; - return true; + /* 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); + } + +yield: + flecs_iter_populate_data(world, it, table, 0, 0, it->ptrs, it->sizes); + ECS_BIT_SET(it->flags, EcsIterIsValid); + return true; +done: +error: + return false; +} + +static +void flecs_init_filter_iter( + ecs_iter_t *it, + const ecs_filter_t *filter) +{ + ecs_assert(filter != NULL, ECS_INTERNAL_ERROR, NULL); + it->priv.iter.filter.filter = filter; + it->field_count = filter->field_count; +} + +int32_t ecs_filter_pivot_term( + const ecs_world_t *world, + const ecs_filter_t *filter) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(filter != NULL, ECS_INVALID_PARAMETER, NULL); + + ecs_term_t *terms = filter->terms; + int32_t i, term_count = filter->term_count; + int32_t pivot_term = -1, min_count = -1, self_pivot_term = -1; + + for (i = 0; i < term_count; i ++) { + ecs_term_t *term = &terms[i]; + ecs_id_t id = term->id; + + if (term->oper != EcsAnd) { + continue; } - /* 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 (!ecs_term_match_this(term)) { + continue; } - /* If jumping backwards, it's a redo */ - redo = iter->op <= op_index; + ecs_id_record_t *idr = flecs_query_id_record_get(world, id); + if (!idr) { + /* If one of the terms does not match with any data, iterator + * should not return anything */ + return -2; /* -2 indicates filter doesn't match anything */ + } - if (!is_control_flow(op)) { - last_frame = op->frame; + int32_t table_count = flecs_table_cache_count(&idr->cache); + if (min_count == -1 || table_count < min_count) { + min_count = table_count; + pivot_term = i; + if ((term->src.flags & EcsTraverseFlags) == EcsSelf) { + self_pivot_term = i; + } } - } while (iter->op != -1); + } - ecs_iter_fini(it); + if (self_pivot_term != -1) { + pivot_term = self_pivot_term; + } + return pivot_term; error: - return false; + return -2; } -#endif +ecs_iter_t flecs_filter_iter_w_flags( + const ecs_world_t *stage, + const ecs_filter_t *filter, + ecs_flags32_t flags) +{ + ecs_check(stage != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(filter != NULL, ECS_INVALID_PARAMETER, NULL); + const ecs_world_t *world = ecs_get_world(stage); + + flecs_process_pending_tables(world); -/* 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_iter_t it = { + .real_world = (ecs_world_t*)world, + .world = (ecs_world_t*)stage, + .terms = filter ? filter->terms : NULL, + .next = ecs_filter_next, + .flags = flags + }; -/* 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. - */ + ecs_filter_iter_t *iter = &it.priv.iter.filter; + iter->pivot_term = -1; + flecs_init_filter_iter(&it, filter); + ECS_BIT_COND(it.flags, EcsIterIsInstanced, + ECS_BIT_IS_SET(filter->flags, EcsFilterIsInstanced)); -#ifdef FLECS_HTTP + /* Find term that represents smallest superset */ + if (ECS_BIT_IS_SET(flags, EcsIterIgnoreThis)) { + term_iter_init_no_data(&iter->term_iter); + } else if (ECS_BIT_IS_SET(filter->flags, EcsFilterMatchThis)) { + ecs_term_t *terms = filter->terms; + int32_t pivot_term = -1; + ecs_check(terms != NULL, ECS_INVALID_PARAMETER, NULL); -#if defined(ECS_TARGET_WINDOWS) -#ifndef WIN32_LEAN_AND_MEAN -#define WIN32_LEAN_AND_MEAN -#endif -#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 + pivot_term = ecs_filter_pivot_term(world, filter); + iter->kind = EcsIterEvalTables; + iter->pivot_term = pivot_term; -/* Max length of request method */ -#define ECS_HTTP_METHOD_LEN_MAX (8) + if (pivot_term == -2) { + /* One or more terms have no matching results */ + term_iter_init_no_data(&iter->term_iter); + } else if (pivot_term == -1) { + /* No terms meet the criteria to be a pivot term, evaluate filter + * against all tables */ + term_iter_init_wildcard(world, &iter->term_iter, + ECS_BIT_IS_SET(filter->flags, EcsFilterMatchEmptyTables)); + } else { + ecs_assert(pivot_term >= 0, ECS_INTERNAL_ERROR, NULL); + term_iter_init(world, &terms[pivot_term], &iter->term_iter, + ECS_BIT_IS_SET(filter->flags, EcsFilterMatchEmptyTables)); + } + } else { + if (!ECS_BIT_IS_SET(filter->flags, EcsFilterMatchAnything)) { + term_iter_init_no_data(&iter->term_iter); + } else { + iter->kind = EcsIterEvalNone; + } + } -/* Timeout (s) before connection purge */ -#define ECS_HTTP_CONNECTION_PURGE_TIMEOUT (1.0) + ECS_BIT_COND(it.flags, EcsIterIsFilter, + ECS_BIT_IS_SET(filter->flags, EcsFilterIsFilter)); -/* Number of dequeues before purging */ -#define ECS_HTTP_CONNECTION_PURGE_RETRY_COUNT (5) + if (ECS_BIT_IS_SET(filter->flags, EcsFilterMatchThis)) { + /* Make space for one variable if the filter has terms for This var */ + it.variable_count = 1; -/* Minimum interval between dequeueing requests (ms) */ -#define ECS_HTTP_MIN_DEQUEUE_INTERVAL (100) + /* Set variable name array */ + it.variable_names = (char**)filter->variable_names; + } -/* Minimum interval between printing statistics (ms) */ -#define ECS_HTTP_MIN_STATS_INTERVAL (10 * 1000) + flecs_iter_init(&it, flecs_iter_cache_all); -/* Max length of headers in reply */ -#define ECS_HTTP_REPLY_HEADER_SIZE (1024) + return it; +error: + return (ecs_iter_t){ 0 }; +} -/* Receive buffer size */ -#define ECS_HTTP_SEND_RECV_BUFFER_SIZE (16 * 1024) +ecs_iter_t ecs_filter_iter( + const ecs_world_t *stage, + const ecs_filter_t *filter) +{ + return flecs_filter_iter_w_flags(stage, filter, 0); +} -/* Max length of request (path + query + headers + body) */ -#define ECS_HTTP_REQUEST_LEN_MAX (10 * 1024 * 1024) +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, + .field_count = filter->field_count, + .world = chain_it->world, + .real_world = chain_it->real_world, + .chain_it = (ecs_iter_t*)chain_it, + .next = ecs_filter_next + }; -/* HTTP server struct */ -struct ecs_http_server_t { - bool should_run; - bool running; + flecs_iter_init(&it, flecs_iter_cache_all); + ecs_filter_iter_t *iter = &it.priv.iter.filter; + flecs_init_filter_iter(&it, filter); - ecs_http_socket_t sock; - ecs_os_mutex_t lock; - ecs_os_thread_t thread; + iter->kind = EcsIterEvalChain; - ecs_http_reply_action_t callback; - void *ctx; + return it; +} - ecs_sparse_t *connections; /* sparse */ - ecs_sparse_t *requests; /* sparse */ +bool ecs_filter_next( + ecs_iter_t *it) +{ + ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(it->next == ecs_filter_next, ECS_INVALID_PARAMETER, NULL); - bool initialized; + if (flecs_iter_next_row(it)) { + return true; + } - uint16_t port; - const char *ipaddr; + return flecs_iter_next_instanced(it, ecs_filter_next_instanced(it)); +error: + return false; +} - ecs_ftime_t dequeue_timeout; /* used to not lock request queue too often */ - ecs_ftime_t stats_timeout; /* used for periodic reporting of statistics */ +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); - ecs_ftime_t request_time; /* time spent on requests in last stats interval */ - ecs_ftime_t request_time_total; /* total time spent on requests */ - int32_t requests_processed; /* requests processed in last stats interval */ - int32_t requests_processed_total; /* total requests processed */ - int32_t dequeue_count; /* number of dequeues in last stats interval */ -}; + ecs_filter_iter_t *iter = &it->priv.iter.filter; + const ecs_filter_t *filter = iter->filter; + ecs_world_t *world = it->real_world; + ecs_table_t *table = NULL; + bool match; -/** Fragment state, used by HTTP request parser */ -typedef enum { - HttpFragStateBegin, - HttpFragStateMethod, - HttpFragStatePath, - HttpFragStateVersion, - HttpFragStateHeaderStart, - HttpFragStateHeaderName, - HttpFragStateHeaderValueStart, - HttpFragStateHeaderValue, - HttpFragStateCR, - HttpFragStateCRLF, - HttpFragStateCRLFCR, - HttpFragStateBody, - HttpFragStateDone -} HttpFragState; + flecs_iter_validate(it); -/** 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; + ecs_iter_t *chain_it = it->chain_it; + ecs_iter_kind_t kind = iter->kind; -/** Extend public connection type with fragment data */ -typedef struct { - ecs_http_connection_t pub; - ecs_http_fragment_t frag; - ecs_http_socket_t sock; + 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; + } - /* Connection is purged after both timeout expires and connection has - * exceeded retry count. This ensures that a connection does not immediately - * timeout when a frame takes longer than usual */ - ecs_ftime_t dequeue_timeout; - int32_t dequeue_retries; -} ecs_http_connection_impl_t; + table = chain_it->table; + match = flecs_filter_match_table(world, filter, table, + it->ids, it->columns, it->sources, it->match_indices, NULL, + true, -1, it->flags); + } while (!match); -typedef struct { - ecs_http_request_t pub; - uint64_t conn_id; /* for sanity check */ - void *res; -} ecs_http_request_impl_t; + goto yield; + } else if (kind == EcsIterEvalTables || kind == EcsIterEvalCondition) { + ecs_term_iter_t *term_iter = &iter->term_iter; + ecs_term_t *term = &term_iter->term; + int32_t pivot_term = iter->pivot_term; + bool first; -static -ecs_size_t http_send( - ecs_http_socket_t sock, - const void *buf, - ecs_size_t size, - int flags) -{ -#ifndef ECS_TARGET_WINDOWS - 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 -} + /* Check if the This variable has been set on the iterator. If set, + * the filter should only be applied to the variable value */ + ecs_var_t *this_var = NULL; + ecs_table_t *this_table = NULL; + if (it->variable_count) { + if (ecs_iter_var_is_constrained(it, 0)) { + this_var = it->variables; + this_table = this_var->range.table; -static -ecs_size_t http_recv( - ecs_http_socket_t sock, - void *buf, - ecs_size_t size, - int flags) -{ - ecs_size_t ret; -#ifndef ECS_TARGET_WINDOWS - 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 variable is constrained, make sure it's a value that's + * pointing to a table, as a filter can't iterate single + * entities (yet) */ + ecs_assert(this_table != NULL, ECS_INVALID_OPERATION, NULL); - return ret; -} + /* Can't set variable for filter that does not iterate tables */ + ecs_assert(kind == EcsIterEvalTables, + ECS_INVALID_OPERATION, 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) -{ - 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); -} + do { + /* If there are no matches left for the previous table, this is the + * first match of the next table. */ + first = iter->matches_left == 0; -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); -} + if (first) { + if (kind != EcsIterEvalCondition) { + /* Check if this variable was constrained */ + if (this_table != NULL) { + /* If this is the first match of a new result and the + * previous result was equal to the value of a + * constrained var, there's nothing left to iterate */ + if (it->table == this_table) { + goto done; + } -static -bool http_socket_is_valid( - ecs_http_socket_t sock) -{ -#if defined(ECS_TARGET_WINDOWS) - return sock != INVALID_SOCKET; -#else - return sock >= 0; -#endif -} + /* If table doesn't match term iterator, it doesn't + * match filter. */ + if (!flecs_term_iter_set_table( + world, term_iter, this_table)) + { + goto done; + } -#if defined(ECS_TARGET_WINDOWS) -#define HTTP_SOCKET_INVALID INVALID_SOCKET -#else -#define HTTP_SOCKET_INVALID (-1) -#endif + /* But if it does, forward it to filter matching */ + ecs_assert(term_iter->table == this_table, + ECS_INTERNAL_ERROR, NULL); -static -void http_close( - ecs_http_socket_t *sock) -{ - ecs_assert(sock != NULL, ECS_INTERNAL_ERROR, NULL); -#if defined(ECS_TARGET_WINDOWS) - closesocket(*sock); -#else - ecs_dbg_2("http: closing socket %u", *sock); - shutdown(*sock, SHUT_RDWR); - close(*sock); -#endif - *sock = HTTP_SOCKET_INVALID; -} + /* If This variable is not constrained, iterate as usual */ + } else { + /* Find new match, starting with the leading term */ + if (!flecs_term_iter_next(world, term_iter, + ECS_BIT_IS_SET(filter->flags, + EcsFilterMatchPrefab), + ECS_BIT_IS_SET(filter->flags, + EcsFilterMatchDisabled))) + { + goto done; + } + } -static -ecs_http_socket_t http_accept( - ecs_http_socket_t sock, - struct sockaddr* addr, - ecs_size_t *addr_len) -{ - 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(term_iter->match_count != 0, + ECS_INTERNAL_ERROR, NULL); -static -void reply_free(ecs_http_reply_t* response) { - ecs_assert(response != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_os_free(response->body.content); -} + if (pivot_term == -1) { + /* Without a pivot term, we're iterating all tables with + * a wildcard, so the match count is meaningless. */ + term_iter->match_count = 1; + } else { + it->match_indices[pivot_term] = term_iter->match_count; + } -static -void request_free(ecs_http_request_impl_t *req) { - ecs_assert(req != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(req->pub.conn != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(req->pub.conn->server != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(req->pub.conn->server->requests != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(req->pub.conn->id == req->conn_id, ECS_INTERNAL_ERROR, NULL); - ecs_os_free(req->res); - flecs_sparse_remove(req->pub.conn->server->requests, req->pub.id); -} + iter->matches_left = term_iter->match_count; -static -void connection_free(ecs_http_connection_impl_t *conn) { - ecs_assert(conn != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(conn->pub.id != 0, ECS_INTERNAL_ERROR, NULL); - uint64_t conn_id = conn->pub.id; + /* Filter iterator takes control over iterating all the + * permutations that match the wildcard. */ + term_iter->match_count = 1; - if (http_socket_is_valid(conn->sock)) { - http_close(&conn->sock); + table = term_iter->table; + + if (pivot_term != -1) { + int32_t index = term->field_index; + it->ids[index] = term_iter->id; + it->sources[index] = term_iter->subject; + it->columns[index] = term_iter->column; + } + } else { + /* Progress iterator to next match for table, if any */ + table = it->table; + if (term_iter->index == 0) { + iter->matches_left = 1; + term_iter->index = 1; /* prevents looping again */ + } else { + goto done; + } + } + + /* Match the remainder of the terms */ + match = flecs_filter_match_table(world, filter, table, + it->ids, it->columns, it->sources, + it->match_indices, &iter->matches_left, first, + pivot_term, it->flags); + if (!match) { + it->table = table; + iter->matches_left = 0; + continue; + } + + /* Table got matched, set This variable */ + if (table) { + ecs_assert(it->variable_count == 1, ECS_INTERNAL_ERROR, NULL); + ecs_assert(it->variables != NULL, ECS_INTERNAL_ERROR, NULL); + it->variables[0].range.table = table; + } + + ecs_assert(iter->matches_left != 0, ECS_INTERNAL_ERROR, NULL); + } + + /* If this is not the first result for the table, and the table + * is matched more than once, iterate remaining matches */ + if (!first && (iter->matches_left > 0)) { + table = it->table; + + /* Find first term that still has matches left */ + int32_t i, j, count = it->field_count; + for (i = count - 1; i >= 0; i --) { + int32_t mi = -- it->match_indices[i]; + if (mi) { + if (mi < 0) { + continue; + } + break; + } + } + + /* If matches_left > 0 we should've found at least one match */ + ecs_assert(i >= 0, ECS_INTERNAL_ERROR, NULL); + + /* Progress first term to next match (must be at least one) */ + int32_t column = it->columns[i]; + if (column < 0) { + /* If this term was matched on a non-This entity, reconvert + * the column back to a positive value */ + column = -column; + } + + it->columns[i] = column + 1; + flecs_term_match_table(world, &filter->terms[i], table, + &it->ids[i], &it->columns[i], &it->sources[i], + &it->match_indices[i], false, it->flags); + + /* Reset remaining terms (if any) to first match */ + for (j = i + 1; j < count; j ++) { + flecs_term_match_table(world, &filter->terms[j], table, + &it->ids[j], &it->columns[j], &it->sources[j], + &it->match_indices[j], true, it->flags); + } + } + + match = iter->matches_left != 0; + iter->matches_left --; + + ecs_assert(iter->matches_left >= 0, ECS_INTERNAL_ERROR, NULL); + } while (!match); + + goto yield; } - flecs_sparse_remove(conn->pub.server->connections, conn_id); -} +done: +error: + ecs_iter_fini(it); + return false; -// 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); +yield: + it->offset = 0; + flecs_iter_populate_data(world, it, table, 0, 0, it->ptrs, it->sizes); + ECS_BIT_SET(it->flags, EcsIterIsValid); + return true; } + 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 ++; +int32_t type_search( + const ecs_table_t *table, + ecs_id_record_t *idr, + ecs_id_t *ids, + ecs_id_t *id_out, + ecs_table_record_t **tr_out) +{ + ecs_table_record_t *tr = ecs_table_cache_get(&idr->cache, table); + if (tr) { + int32_t r = tr->column; + if (tr_out) tr_out[0] = tr; + if (id_out) { + id_out[0] = flecs_to_public_id(ids[r]); } + return r; } - 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 if (!ecs_os_strcmp(method, "OPTIONS")) frag->method = EcsHttpOptions; - else { - frag->method = EcsHttpMethodUnsupported; - frag->invalid = true; - } - ecs_strbuf_reset(&frag->buf); + return -1; } static -bool header_writable( - ecs_http_fragment_t *frag) +int32_t type_offset_search( + int32_t offset, + ecs_id_t id, + ecs_id_t *ids, + int32_t count, + ecs_id_t *id_out) { - return frag->header_count < ECS_HTTP_HEADER_COUNT_MAX; -} + ecs_assert(ids != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(count > 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(offset > 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(id != 0, ECS_INVALID_PARAMETER, NULL); -static -void header_buf_reset( - ecs_http_fragment_t *frag) -{ - frag->header_buf[0] = '\0'; - frag->header_buf_ptr = frag->header_buf; + while (offset < count) { + ecs_id_t type_id = ids[offset ++]; + if (ecs_id_match(type_id, id)) { + if (id_out) { + id_out[0] = flecs_to_public_id(type_id); + } + return offset - 1; + } + } + + return -1; } static -void header_buf_append( - ecs_http_fragment_t *frag, - char ch) +bool type_can_inherit_id( + const ecs_world_t *world, + const ecs_table_t *table, + const ecs_id_record_t *idr, + ecs_id_t id) { - 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'; + if (idr->flags & EcsIdDontInherit) { + return false; + } + if (idr->flags & EcsIdExclusive) { + if (ECS_HAS_ID_FLAG(id, PAIR)) { + ecs_entity_t er = ECS_PAIR_FIRST(id); + if (flecs_table_record_get( + world, table, ecs_pair(er, EcsWildcard))) + { + return false; + } + } } + return true; } static -void enqueue_request( - ecs_http_connection_impl_t *conn) +int32_t type_search_relation( + const ecs_world_t *world, + const ecs_table_t *table, + int32_t offset, + ecs_id_t id, + ecs_id_record_t *idr, + ecs_id_t rel, + ecs_id_record_t *idr_r, + bool self, + ecs_entity_t *subject_out, + ecs_id_t *id_out, + ecs_table_record_t **tr_out) { - ecs_http_server_t *srv = conn->pub.server; - ecs_http_fragment_t *frag = &conn->frag; - - 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); - req->conn_id = conn->pub.id; + ecs_type_t type = table->type; + ecs_id_t *ids = type.array; + int32_t count = type.count; - 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]]; + if (self) { + if (offset) { + int32_t r = type_offset_search(offset, id, ids, count, id_out); + if (r != -1) { + return r; } - 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); + } else { + int32_t r = type_search(table, idr, ids, id_out, tr_out); + if (r != -1) { + return r; } - - req->pub.header_count = frag->header_count; - req->pub.param_count = frag->param_count; - req->res = res; - ecs_os_mutex_unlock(srv->lock); } } -} - -static -bool parse_request( - ecs_http_connection_impl_t *conn, - uint64_t conn_id, - const char* req_frag, - ecs_size_t req_frag_len) -{ - ecs_http_fragment_t *frag = &conn->frag; - 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); - } + ecs_flags32_t flags = table->flags; + if ((flags & EcsTableHasPairs) && rel) { + bool is_a = rel == ecs_pair(EcsIsA, EcsWildcard); + if (is_a) { + if (!(flags & EcsTableHasIsA)) { + return -1; } - 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); + if (!type_can_inherit_id(world, table, idr, id)) { + return -1; } - 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"); + idr_r = world->idr_isa_wildcard; + } - 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; + if (!idr_r) { + idr_r = flecs_id_record_get(world, rel); + if (!idr_r) { + return -1; } - /* 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; + } + + ecs_id_t id_r; + int32_t r, r_column; + if (offset) { + r_column = type_offset_search(offset, rel, ids, count, &id_r); + } else { + r_column = type_search(table, idr_r, ids, &id_r, 0); + } + while (r_column != -1) { + ecs_entity_t obj = ECS_PAIR_SECOND(id_r); + ecs_assert(obj != 0, ECS_INTERNAL_ERROR, NULL); + + ecs_record_t *rec = flecs_entities_get_any(world, obj); + ecs_assert(rec != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_table_t *obj_table = rec->table; + if (obj_table) { + ecs_assert(obj_table != table, ECS_CYCLE_DETECTED, NULL); + + r = type_search_relation(world, obj_table, 0, id, idr, + rel, idr_r, true, subject_out, id_out, tr_out); + if (r != -1) { + if (subject_out && !subject_out[0]) { + subject_out[0] = ecs_get_alive(world, obj); } - frag->parse_content_length = false; + return r_column; } - 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 ++; + + if (!is_a) { + r = type_search_relation(world, obj_table, 0, id, idr, + ecs_pair(EcsIsA, EcsWildcard), world->idr_isa_wildcard, + true, subject_out, id_out, tr_out); + if (r != -1) { + if (subject_out && !subject_out[0]) { + subject_out[0] = ecs_get_alive(world, obj); + } + return r_column; } } - 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; - } - } - if (frag->state == HttpFragStateDone) { - frag->state = HttpFragStateBegin; - if (conn->pub.id == conn_id) { - enqueue_request(conn); + r_column = type_offset_search(r_column + 1, rel, ids, count, &id_r); } - return true; - } else { - return false; } + + return -1; } -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) +int32_t ecs_search_relation( + const ecs_world_t *world, + const ecs_table_t *table, + int32_t offset, + ecs_id_t id, + ecs_entity_t rel, + ecs_flags32_t flags, + ecs_entity_t *subject_out, + ecs_id_t *id_out, + struct ecs_table_record_t **tr_out) { - ecs_strbuf_appendstr(hdrs, "HTTP/1.1 "); - ecs_strbuf_append(hdrs, "%d ", code); - ecs_strbuf_appendstr(hdrs, status); - ecs_strbuf_appendstr(hdrs, "\r\n"); + if (!table) return -1; - ecs_strbuf_appendstr(hdrs, "Content-Type: "); - ecs_strbuf_appendstr(hdrs, content_type); - ecs_strbuf_appendstr(hdrs, "\r\n"); + ecs_poly_assert(world, ecs_world_t); + ecs_assert(id != 0, ECS_INVALID_PARAMETER, NULL); - ecs_strbuf_appendstr(hdrs, "Content-Length: "); - ecs_strbuf_append(hdrs, "%d", content_len); - ecs_strbuf_appendstr(hdrs, "\r\n"); + flags = flags ? flags : (EcsSelf|EcsUp); - ecs_strbuf_appendstr(hdrs, "Server: flecs\r\n"); + if (!(flags & EcsUp)) { + if (subject_out) subject_out[0] = 0; + return ecs_search_offset(world, table, offset, id, id_out); + } - ecs_strbuf_mergebuff(hdrs, extra_headers); + ecs_id_record_t *idr = flecs_query_id_record_get(world, id); + if (!idr) { + return -1; + } - ecs_strbuf_appendstr(hdrs, "\r\n"); + int32_t result = type_search_relation(world, table, offset, id, idr, + ecs_pair(rel, EcsWildcard), NULL, flags & EcsSelf, subject_out, + id_out, tr_out); + + return result; } -static -void send_reply( - ecs_http_connection_impl_t* conn, - ecs_http_reply_t* reply) +int32_t ecs_search( + const ecs_world_t *world, + const ecs_table_t *table, + ecs_id_t id, + ecs_id_t *id_out) { - 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; + if (!table) return -1; - char *content = ecs_strbuf_get(&reply->body); - int32_t content_length = reply->body.length - 1; + ecs_poly_assert(world, ecs_world_t); + ecs_assert(id != 0, ECS_INVALID_PARAMETER, NULL); - /* First, send the response HTTP headers */ - append_send_headers(&hdr_buf, reply->code, reply->status, - reply->content_type, &reply->headers, content_length); + ecs_id_record_t *idr = flecs_query_id_record_get(world, id); + if (!idr) { + return -1; + } - 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); + ecs_type_t type = table->type; + ecs_id_t *ids = type.array; + return type_search(table, idr, ids, id_out, NULL); +} - if (written != hdrs_len) { - ecs_err("failed to write HTTP response headers to '%s:%s': %s", - conn->pub.host, conn->pub.port, ecs_os_strerror(errno)); - return; +int32_t ecs_search_offset( + const ecs_world_t *world, + const ecs_table_t *table, + int32_t offset, + ecs_id_t id, + ecs_id_t *id_out) +{ + if (!offset) { + return ecs_search(world, table, id, id_out); } - /* 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:%s': %s", - conn->pub.host, conn->pub.port, ecs_os_strerror(errno)); - } - } + if (!table) return -1; + + ecs_poly_assert(world, ecs_world_t); + + ecs_type_t type = table->type; + ecs_id_t *ids = type.array; + int32_t count = type.count; + return type_offset_search(offset, id, ids, count, id_out); } static -void recv_request( - ecs_http_server_t *srv, - ecs_http_connection_impl_t *conn, - uint64_t conn_id, - ecs_http_socket_t sock) +int32_t flecs_relation_depth_walk( + const ecs_world_t *world, + ecs_id_record_t *idr, + ecs_table_t *first, + ecs_table_t *table) { - ecs_size_t bytes_read; - char recv_buf[ECS_HTTP_SEND_RECV_BUFFER_SIZE]; + int32_t result = 0; - while ((bytes_read = http_recv( - sock, recv_buf, ECS_SIZEOF(recv_buf), 0)) > 0) - { - ecs_os_mutex_lock(srv->lock); - bool is_alive = conn->pub.id == conn_id; - if (is_alive) { - conn->dequeue_timeout = 0; - conn->dequeue_retries = 0; - } - ecs_os_mutex_unlock(srv->lock); + const ecs_table_record_t *tr = flecs_id_record_get_table(idr, table); + if (!tr) { + return 0; + } - if (is_alive) { - if (parse_request(conn, conn_id, recv_buf, bytes_read)) { - return; - } - } else { - return; + int32_t i = tr->column, end = i + tr->count; + for (; i != end; i ++) { + ecs_entity_t o = ecs_pair_second(world, table->type.array[i]); + ecs_assert(o != 0, ECS_INTERNAL_ERROR, NULL); + + ecs_table_t *ot = ecs_get_table(world, o); + if (!ot) { + continue; + } + + ecs_assert(ot != first, ECS_CYCLE_DETECTED, NULL); + int32_t cur = flecs_relation_depth_walk(world, idr, first, ot); + if (cur > result) { + result = cur; } } + + return result + 1; } -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) +int32_t flecs_relation_depth( + const ecs_world_t *world, + ecs_entity_t r, + ecs_table_t *table) { - /* 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); - uint64_t conn_id = conn->pub.id = flecs_sparse_last_id(srv->connections); - conn->pub.server = srv; - conn->sock = sock_conn; - ecs_os_mutex_unlock(srv->lock); + ecs_id_record_t *idr = flecs_id_record_get(world, ecs_pair(r, EcsWildcard)); + if (!idr) { + return 0; + } + return flecs_relation_depth_walk(world, idr, table, table); +} - char *remote_host = conn->pub.host; - char *remote_port = conn->pub.port; +#include - /* 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"); +static +bool flecs_multi_observer_invoke(ecs_iter_t *it) { + ecs_observer_t *o = it->ctx; + ecs_world_t *world = it->real_world; + + if (o->last_event_id[0] == world->event_id) { + /* Already handled this event */ + return false; } - ecs_dbg_2("http: connection established from '%s:%s'", - remote_host, remote_port); + o->last_event_id[0] = world->event_id; - recv_request(srv, conn, conn_id, sock_conn); + ecs_iter_t user_it = *it; + user_it.field_count = o->filter.field_count; + user_it.terms = o->filter.terms; + user_it.flags = 0; + ECS_BIT_COND(user_it.flags, EcsIterIsFilter, + ECS_BIT_IS_SET(o->filter.flags, EcsFilterIsFilter)); + user_it.ids = NULL; + user_it.columns = NULL; + user_it.sources = NULL; + user_it.sizes = NULL; + user_it.ptrs = NULL; + flecs_iter_init(&user_it, flecs_iter_cache_all); - ecs_dbg_2("http: request received from '%s:%s'", - remote_host, remote_port); -} + 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]; -static -void accept_connections( - ecs_http_server_t* srv, - const struct sockaddr* addr, - ecs_size_t addr_len) -{ -#ifdef ECS_TARGET_WINDOWS - /* 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; - } - } else { - http_close(&testsocket); + int32_t column = it->columns[0]; + if (term->oper == EcsNot) { + table = it->other_table; + prev_table = it->table; } -#endif - - /* Resolve name + port (used for logging) */ - char addr_host[256]; - char addr_port[20]; - ecs_http_socket_t sock = HTTP_SOCKET_INVALID; - ecs_assert(srv->sock == HTTP_SOCKET_INVALID, ECS_INTERNAL_ERROR, NULL); + if (!table) { + table = &world->store.root; + } + if (!prev_table) { + prev_table = &world->store.root; + } - 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 (column < 0) { + column = -column; } - ecs_os_mutex_lock(srv->lock); - if (srv->should_run) { - ecs_dbg_2("http: initializing connection socket"); + user_it.columns[0] = 0; + user_it.columns[pivot_term] = column; - sock = socket(addr->sa_family, SOCK_STREAM, IPPROTO_TCP); - if (!http_socket_is_valid(sock)) { - ecs_err("unable to create new connection socket: %s", - ecs_os_strerror(errno)); - ecs_os_mutex_unlock(srv->lock); - goto done; - } - - int reuse = 1; - int result = setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, - (char*)&reuse, ECS_SIZEOF(reuse)); - if (result) { - ecs_warn("failed to setsockopt: %s", ecs_os_strerror(errno)); - } - - if (addr->sa_family == AF_INET6) { - int ipv6only = 0; - if (setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, - (char*)&ipv6only, ECS_SIZEOF(ipv6only))) + if (flecs_filter_match_table(world, &o->filter, table, user_it.ids, + user_it.columns, user_it.sources, NULL, NULL, false, -1, + user_it.flags)) + { + /* Monitor observers only invoke when the filter matches for the first + * time with an entity */ + if (o->is_monitor) { + if (flecs_filter_match_table(world, &o->filter, prev_table, + NULL, NULL, NULL, NULL, NULL, true, -1, user_it.flags)) { - ecs_warn("failed to setsockopt: %s", ecs_os_strerror(errno)); + goto done; } } - result = http_bind(sock, addr, addr_len); - if (result) { - ecs_err("http: failed to bind to '%s:%s': %s", - addr_host, addr_port, ecs_os_strerror(errno)); - ecs_os_mutex_unlock(srv->lock); - goto done; - } - - srv->sock = sock; - - result = listen(srv->sock, SOMAXCONN); - if (result) { - ecs_warn("http: could not listen for SOMAXCONN (%d) connections: %s", - SOMAXCONN, ecs_os_strerror(errno)); + /* While filter matching needs to be reversed for a Not term, the + * component data must be fetched from the table we got notified for. + * Repeat the matching process for the non-matching table so we get the + * correct column ids and sources, which we need for populate_data */ + if (term->oper == EcsNot) { + flecs_filter_match_table(world, &o->filter, prev_table, user_it.ids, + user_it.columns, user_it.sources, NULL, NULL, false, -1, + user_it.flags | EcsFilterPopulate); } - ecs_trace("http: listening for incoming connections on '%s:%s'", - addr_host, addr_port); - } else { - ecs_dbg_2("http: server shut down while initializing"); - } - ecs_os_mutex_unlock(srv->lock); + flecs_iter_populate_data(world, &user_it, it->table, it->offset, + it->count, user_it.ptrs, user_it.sizes); - ecs_http_socket_t sock_conn; - struct sockaddr_storage remote_addr; - ecs_size_t remote_addr_len; + user_it.ids[pivot_term] = it->event_id; + user_it.system = o->entity; + user_it.term_index = pivot_term; + user_it.ctx = o->ctx; + user_it.binding_ctx = o->binding_ctx; + user_it.field_count = o->filter.field_count; + flecs_iter_validate(&user_it); - while (srv->should_run) { - remote_addr_len = ECS_SIZEOF(remote_addr); - sock_conn = http_accept(srv->sock, (struct sockaddr*) &remote_addr, - &remote_addr_len); + ecs_assert(o->callback != NULL, ECS_INVALID_PARAMETER, NULL); + o->callback(&user_it); - if (sock_conn == -1) { - if (srv->should_run) { - ecs_dbg("http: connection attempt failed: %s", - ecs_os_strerror(errno)); - } - continue; - } + ecs_iter_fini(&user_it); - init_connection(srv, sock_conn, &remote_addr, remote_addr_len); + return true; } done: - ecs_os_mutex_lock(srv->lock); - if (http_socket_is_valid(srv->sock) && errno != EBADF) { - http_close(&sock); - srv->sock = sock; - } - ecs_os_mutex_unlock(srv->lock); - - ecs_trace("http: no longer accepting connections on '%s:%s'", - addr_host, addr_port); + ecs_iter_fini(&user_it); + return false; } static -void* http_server_thread(void* arg) { - ecs_http_server_t *srv = arg; - struct sockaddr_in addr; - ecs_os_zeromem(&addr); - addr.sin_family = AF_INET; - addr.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)); +ecs_entity_t flecs_observer_get_actual_event( + ecs_observer_t *observer, + ecs_entity_t event) +{ + /* If operator is Not, reverse the event */ + if (observer->filter.terms[0].oper == EcsNot) { + if (event == EcsOnAdd) { + event = EcsOnRemove; + } else if (event == EcsOnRemove) { + event = EcsOnAdd; + } } - accept_connections(srv, (struct sockaddr*)&addr, ECS_SIZEOF(addr)); - return NULL; + return event; } static -void handle_request( - ecs_http_server_t *srv, - ecs_http_request_impl_t *req) +void flecs_unregister_event_observer( + ecs_event_record_t *evt, + ecs_id_t id) { - ecs_http_reply_t reply = ECS_HTTP_REPLY_INIT; - ecs_http_connection_impl_t *conn = - (ecs_http_connection_impl_t*)req->pub.conn; + if (ecs_map_remove(&evt->event_ids, id) == 0) { + ecs_map_fini(&evt->event_ids); + } +} - if (srv->callback((ecs_http_request_t*)req, &reply, srv->ctx) == false) { - reply.code = 404; - reply.status = "Resource not found"; +static +ecs_event_id_record_t* flecs_ensure_event_id_record( + ecs_map_t *map, + ecs_id_t id) +{ + ecs_event_id_record_t **idt = ecs_map_ensure( + map, ecs_event_id_record_t*, id); + if (!idt[0]) { + idt[0] = ecs_os_calloc_t(ecs_event_id_record_t); } - send_reply(conn, &reply); - ecs_dbg_2("http: reply sent to '%s:%s'", conn->pub.host, conn->pub.port); + return idt[0]; +} - reply_free(&reply); - request_free(req); - connection_free(conn); +static +ecs_flags32_t flecs_id_flag_for_event( + ecs_entity_t e) +{ + if (e == EcsOnAdd) { + return EcsIdHasOnAdd; + } + if (e == EcsOnRemove) { + return EcsIdHasOnRemove; + } + if (e == EcsOnSet) { + return EcsIdHasOnSet; + } + if (e == EcsUnSet) { + return EcsIdHasUnSet; + } + return 0; } static -int32_t dequeue_requests( - ecs_http_server_t *srv, - ecs_ftime_t delta_time) +void flecs_inc_observer_count( + ecs_world_t *world, + ecs_entity_t event, + ecs_event_record_t *evt, + ecs_id_t id, + int32_t value) { - ecs_os_mutex_lock(srv->lock); + ecs_event_id_record_t *idt = flecs_ensure_event_id_record(&evt->event_ids, id); + ecs_assert(idt != NULL, ECS_INTERNAL_ERROR, NULL); + + int32_t result = idt->observer_count += value; + if (result == 1) { + /* Notify framework that there are observers 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 + }); - int32_t i, request_count = flecs_sparse_count(srv->requests); - for (i = request_count - 1; i >= 1; i --) { - ecs_http_request_impl_t *req = flecs_sparse_get_dense( - srv->requests, ecs_http_request_impl_t, i); - handle_request(srv, req); - } + ecs_flags32_t flags = flecs_id_flag_for_event(event); + if (flags) { + ecs_id_record_t *idr = flecs_id_record_get(world, id); + if (idr) { + idr->flags |= flags; + } + } + } else if (result == 0) { + /* Ditto, but the reverse */ + flecs_notify_tables(world, id, &(ecs_table_event_t){ + .kind = EcsTableNoTriggersForId, + .event = event + }); - int32_t connections_count = flecs_sparse_count(srv->connections); - for (i = connections_count - 1; i >= 1; i --) { - ecs_http_connection_impl_t *conn = flecs_sparse_get_dense( - srv->connections, ecs_http_connection_impl_t, i); + ecs_flags32_t flags = flecs_id_flag_for_event(event); + if (flags) { + ecs_id_record_t *idr = flecs_id_record_get(world, id); + if (idr) { + idr->flags &= ~flags; + } + } - conn->dequeue_timeout += delta_time; - conn->dequeue_retries ++; - - if ((conn->dequeue_timeout > - (ecs_ftime_t)ECS_HTTP_CONNECTION_PURGE_TIMEOUT) && - (conn->dequeue_retries > ECS_HTTP_CONNECTION_PURGE_RETRY_COUNT)) + /* Remove admin for id for event */ + if (!ecs_map_is_initialized(&idt->observers) && + !ecs_map_is_initialized(&idt->set_observers)) { - ecs_dbg("http: purging connection '%s:%s' (sock = %d)", - conn->pub.host, conn->pub.port, conn->sock); - connection_free(conn); + flecs_unregister_event_observer(evt, id); + ecs_os_free(idt); } } - - ecs_os_mutex_unlock(srv->lock); - - return request_count - 1; } -const char* ecs_http_get_header( - const ecs_http_request_t* req, - const char* name) +static +void flecs_register_observer_for_id( + ecs_world_t *world, + ecs_observable_t *observable, + ecs_observer_t *observer, + ecs_id_t id, + size_t offset) { - 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; + ecs_sparse_t *events = observable->events; + ecs_assert(events != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_id_t term_id = observer->register_id; + + int i; + for (i = 0; i < observer->event_count; i ++) { + ecs_entity_t event = flecs_observer_get_actual_event( + observer, observer->events[i]); + + /* Get observers for event */ + ecs_event_record_t *evt = flecs_sparse_ensure( + events, ecs_event_record_t, event); + ecs_assert(evt != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_map_init_if(&evt->event_ids, ecs_event_id_record_t*, 1); + + /* Get observers for (component) id for event */ + ecs_event_id_record_t *idt = flecs_ensure_event_id_record( + &evt->event_ids, id); + ecs_assert(idt != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_map_t *observers = ECS_OFFSET(idt, offset); + ecs_map_init_if(observers, ecs_observer_t*, 1); + + ecs_map_ensure(observers, ecs_observer_t*, + observer->entity)[0] = observer; + + flecs_inc_observer_count(world, event, evt, term_id, 1); + if (term_id != id) { + flecs_inc_observer_count(world, event, evt, id, 1); } } - return NULL; } -const char* ecs_http_get_param( - const ecs_http_request_t* req, - const char* name) +static +void flecs_uni_observer_register( + ecs_world_t *world, + ecs_observable_t *observable, + ecs_observer_t *observer) { - 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; + ecs_term_t *term = &observer->filter.terms[0]; + ecs_id_t id = observer->register_id; + + if (term->src.flags & EcsSelf) { + if (ecs_term_match_this(term)) { + flecs_register_observer_for_id(world, observable, observer, id, + offsetof(ecs_event_id_record_t, observers)); + } else { + flecs_register_observer_for_id(world, observable, observer, id, + offsetof(ecs_event_id_record_t, entity_observers)); } } - return NULL; + + if (observer->filter.terms[0].src.flags & EcsUp) { + ecs_id_t pair = ecs_pair(term->src.trav, EcsWildcard); + flecs_register_observer_for_id(world, observable, observer, pair, + offsetof(ecs_event_id_record_t, set_observers)); + } } -ecs_http_server_t* ecs_http_server_init( - const ecs_http_server_desc_t *desc) +static +void flecs_unregister_observer_for_id( + ecs_world_t *world, + ecs_observable_t *observable, + ecs_observer_t *observer, + ecs_id_t id, + size_t offset) { - ecs_check(ecs_os_has_threading(), ECS_UNSUPPORTED, - "missing OS API implementation"); + ecs_sparse_t *events = observable->events; + ecs_assert(events != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_id_t term_id = observer->register_id; - ecs_http_server_t* srv = ecs_os_calloc_t(ecs_http_server_t); - srv->lock = ecs_os_mutex_new(); - srv->sock = HTTP_SOCKET_INVALID; + int i; + for (i = 0; i < observer->event_count; i ++) { + ecs_entity_t event = flecs_observer_get_actual_event( + observer, observer->events[i]); - srv->should_run = false; - srv->initialized = true; + /* Get observers for event */ + ecs_event_record_t *evt = flecs_sparse_get( + events, ecs_event_record_t, event); + ecs_assert(evt != NULL, ECS_INTERNAL_ERROR, NULL); - srv->callback = desc->callback; - srv->ctx = desc->ctx; - srv->port = desc->port; - srv->ipaddr = desc->ipaddr; + /* Get observers for (component) id */ + ecs_event_id_record_t *idt = ecs_map_get_ptr( + &evt->event_ids, ecs_event_id_record_t*, id); + ecs_assert(idt != NULL, ECS_INTERNAL_ERROR, NULL); - srv->connections = flecs_sparse_new(ecs_http_connection_impl_t); - srv->requests = flecs_sparse_new(ecs_http_request_impl_t); + ecs_map_t *id_observers = ECS_OFFSET(idt, offset); - /* Start at id 1 */ - flecs_sparse_new_id(srv->connections); - flecs_sparse_new_id(srv->requests); + if (ecs_map_remove(id_observers, observer->entity) == 0) { + ecs_map_fini(id_observers); + } -#ifndef ECS_TARGET_WINDOWS - /* Ignore pipe signal. SIGPIPE can occur when a message is sent to a client - * but te client already disconnected. */ - signal(SIGPIPE, SIG_IGN); -#endif + flecs_inc_observer_count(world, event, evt, term_id, -1); - return srv; -error: - return NULL; + if (id != term_id) { + /* Id is different from term_id in case of a set observer. If they're + * the same, flecs_inc_observer_count could already have done cleanup */ + if (!ecs_map_is_initialized(&idt->observers) && + !ecs_map_is_initialized(&idt->set_observers) && + !idt->observer_count) + { + flecs_unregister_event_observer(evt, id); + } + + flecs_inc_observer_count(world, event, evt, id, -1); + } + } } -void ecs_http_server_fini( - ecs_http_server_t* srv) +static +void flecs_unregister_observer( + ecs_world_t *world, + ecs_observable_t *observable, + ecs_observer_t *observer) { - if (srv->should_run) { - ecs_http_server_stop(srv); + ecs_assert(observer != NULL, ECS_INTERNAL_ERROR, NULL); + if (!observer->filter.terms) { + ecs_assert(observer->filter.term_count == 0, ECS_INTERNAL_ERROR, NULL); + return; + } + + ecs_term_t *term = &observer->filter.terms[0]; + ecs_id_t id = observer->register_id; + + if (term->src.flags & EcsSelf) { + if (ecs_term_match_this(term)) { + flecs_unregister_observer_for_id(world, observable, observer, id, + offsetof(ecs_event_id_record_t, observers)); + } else { + flecs_unregister_observer_for_id(world, observable, observer, id, + offsetof(ecs_event_id_record_t, entity_observers)); + } + } + + if (term->src.flags & EcsUp) { + ecs_id_t pair = ecs_pair(term->src.trav, EcsWildcard); + flecs_unregister_observer_for_id(world, observable, observer, pair, + offsetof(ecs_event_id_record_t, set_observers)); } - ecs_os_mutex_free(srv->lock); - flecs_sparse_free(srv->connections); - flecs_sparse_free(srv->requests); - ecs_os_free(srv); } -int ecs_http_server_start( - ecs_http_server_t *srv) +static +ecs_map_t* flecs_get_observers_for_event( + const ecs_observable_t *observable, + ecs_entity_t event) { - 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); - - srv->should_run = true; + ecs_check(observable != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(event != 0, ECS_INTERNAL_ERROR, NULL); - ecs_dbg("http: starting server thread"); + ecs_sparse_t *events = observable->events; + ecs_assert(events != NULL, ECS_INTERNAL_ERROR, NULL); - srv->thread = ecs_os_thread_new(http_server_thread, srv); - if (!srv->thread) { - goto error; + const ecs_event_record_t *evt = flecs_sparse_get( + events, ecs_event_record_t, event); + + if (evt) { + return (ecs_map_t*)&evt->event_ids; } - return 0; error: - return -1; + return NULL; } -void ecs_http_server_stop( - ecs_http_server_t* srv) +static +ecs_event_id_record_t* flecs_get_observers_for_id( + const ecs_map_t *evt, + ecs_id_t id) { - 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); + return ecs_map_get_ptr(evt, ecs_event_id_record_t*, id); +} - /* Stop server thread */ - ecs_dbg("http: shutting down server thread"); +static +void flecs_init_observer_iter( + ecs_iter_t *it, + bool *iter_set) +{ + ecs_assert(it != NULL, ECS_INTERNAL_ERROR, NULL); + + if (*iter_set) { + return; + } - ecs_os_mutex_lock(srv->lock); - srv->should_run = false; - if (http_socket_is_valid(srv->sock)) { - http_close(&srv->sock); + if (ECS_BIT_IS_SET(it->flags, EcsIterTableOnly)) { + it->ids = it->priv.cache.ids; + it->ids[0] = it->event_id; + return; } - ecs_os_mutex_unlock(srv->lock); - ecs_os_thread_join(srv->thread); - ecs_trace("http: server thread shut down"); + flecs_iter_init(it, flecs_iter_cache_all); + flecs_iter_validate(it); - /* Cleanup all outstanding requests */ - int i, count = flecs_sparse_count(srv->requests); - for (i = count - 1; i >= 1; i --) { - request_free(flecs_sparse_get_dense( - srv->requests, ecs_http_request_impl_t, i)); - } + *iter_set = true; - /* Close all connections */ - count = flecs_sparse_count(srv->connections); - for (i = count - 1; i >= 1; i --) { - connection_free(flecs_sparse_get_dense( - srv->connections, ecs_http_connection_impl_t, i)); - } + it->ids[0] = it->event_id; - ecs_assert(flecs_sparse_count(srv->connections) == 1, + ecs_assert(it->table != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(!it->count || it->offset < ecs_table_count(it->table), ECS_INTERNAL_ERROR, NULL); - ecs_assert(flecs_sparse_count(srv->requests) == 1, + ecs_assert((it->offset + it->count) <= ecs_table_count(it->table), ECS_INTERNAL_ERROR, NULL); - srv->thread = 0; -error: - return; -} - -void ecs_http_server_dequeue( - ecs_http_server_t* srv, - ecs_ftime_t 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); + int32_t index = ecs_search_relation(it->real_world, it->table, 0, + it->event_id, EcsIsA, 0, it->sources, 0, 0); - srv->dequeue_timeout += delta_time; - srv->stats_timeout += delta_time; + if (index == -1) { + it->columns[0] = 0; + } else if (it->sources[0]) { + it->columns[0] = -index - 1; + } else { + it->columns[0] = index + 1; + } - if ((1000 * srv->dequeue_timeout) > - (ecs_ftime_t)ECS_HTTP_MIN_DEQUEUE_INTERVAL) - { - srv->dequeue_timeout = 0; + ecs_term_t term = { + .id = it->event_id + }; - ecs_time_t t = {0}; - ecs_time_measure(&t); - int32_t request_count = dequeue_requests(srv, srv->dequeue_timeout); - srv->requests_processed += request_count; - srv->requests_processed_total += request_count; - ecs_ftime_t time_spent = (ecs_ftime_t)ecs_time_measure(&t); - srv->request_time += time_spent; - srv->request_time_total += time_spent; - srv->dequeue_count ++; + it->field_count = 1; + it->terms = &term; + flecs_iter_populate_data(it->real_world, it, it->table, it->offset, + it->count, it->ptrs, it->sizes); +} + +static +bool flecs_ignore_observer( + ecs_world_t *world, + ecs_observer_t *observer, + ecs_table_t *table) +{ + int32_t *last_event_id = observer->last_event_id; + if (last_event_id && last_event_id[0] == world->event_id) { + return true; } - if ((1000 * srv->stats_timeout) > - (ecs_ftime_t)ECS_HTTP_MIN_STATS_INTERVAL) + if (!table) { + return false; + } + + ecs_filter_t *filter = &observer->filter; + if (!(filter->flags & EcsFilterMatchPrefab) && + (table->flags & EcsTableIsPrefab)) { - srv->stats_timeout = 0; - ecs_dbg("http: processed %d requests in %.3fs (avg %.3fs / dequeue)", - srv->requests_processed, (double)srv->request_time, - (double)(srv->request_time / (ecs_ftime_t)srv->dequeue_count)); - srv->requests_processed = 0; - srv->request_time = 0; - srv->dequeue_count = 0; + return true; } - -error: - return; + if (!(filter->flags & EcsFilterMatchDisabled) && + (table->flags & EcsTableIsDisabled)) + { + return true; + } + + return false; } -#endif - - +bool ecs_observer_default_run_action(ecs_iter_t *it) { + ecs_observer_t *observer = it->ctx; + if (observer->is_multi) { + return flecs_multi_observer_invoke(it); + } else { + it->callback(it); + return true; + } +} -#ifdef FLECS_UNITS +static +void flecs_default_multi_observer_run_callback(ecs_iter_t *it) { + flecs_multi_observer_invoke(it); +} -ECS_DECLARE(EcsUnitPrefixes); +static +void flecs_default_uni_observer_run_callback(ecs_iter_t *it) { + ecs_observer_t *observer = it->ctx; + it->ctx = observer->ctx; + it->callback = observer->callback; + it->callback(it); +} -ECS_DECLARE(EcsYocto); -ECS_DECLARE(EcsZepto); -ECS_DECLARE(EcsAtto); -ECS_DECLARE(EcsFemto); -ECS_DECLARE(EcsPico); -ECS_DECLARE(EcsNano); -ECS_DECLARE(EcsMicro); -ECS_DECLARE(EcsMilli); -ECS_DECLARE(EcsCenti); -ECS_DECLARE(EcsDeci); -ECS_DECLARE(EcsDeca); -ECS_DECLARE(EcsHecto); -ECS_DECLARE(EcsKilo); -ECS_DECLARE(EcsMega); -ECS_DECLARE(EcsGiga); -ECS_DECLARE(EcsTera); -ECS_DECLARE(EcsPeta); -ECS_DECLARE(EcsExa); -ECS_DECLARE(EcsZetta); -ECS_DECLARE(EcsYotta); +/* For convenience, so applications can (in theory) use a single run callback + * that uses ecs_iter_next to iterate results */ +static +bool flecs_default_observer_next_callback(ecs_iter_t *it) { + if (it->interrupted_by) { + return false; + } else { + /* Use interrupted_by to signal the next iteration must return false */ + it->interrupted_by = it->system; + return true; + } +} -ECS_DECLARE(EcsKibi); -ECS_DECLARE(EcsMebi); -ECS_DECLARE(EcsGibi); -ECS_DECLARE(EcsTebi); -ECS_DECLARE(EcsPebi); -ECS_DECLARE(EcsExbi); -ECS_DECLARE(EcsZebi); -ECS_DECLARE(EcsYobi); +/* Run action for children of multi observer */ +static +void flecs_multi_observer_builtin_run(ecs_iter_t *it) { + ecs_observer_t *observer = it->ctx; + ecs_run_action_t run = observer->run; -ECS_DECLARE(EcsDuration); - ECS_DECLARE(EcsPicoSeconds); - ECS_DECLARE(EcsNanoSeconds); - ECS_DECLARE(EcsMicroSeconds); - ECS_DECLARE(EcsMilliSeconds); - ECS_DECLARE(EcsSeconds); - ECS_DECLARE(EcsMinutes); - ECS_DECLARE(EcsHours); - ECS_DECLARE(EcsDays); + if (run) { + it->next = flecs_default_observer_next_callback; + it->callback = flecs_default_multi_observer_run_callback; + it->interrupted_by = 0; + run(it); + } else { + flecs_multi_observer_invoke(it); + } +} -ECS_DECLARE(EcsTime); - ECS_DECLARE(EcsDate); +/* Run action for uni observer */ +static +void flecs_uni_observer_builtin_run( + ecs_observer_t *observer, + ecs_iter_t *it) +{ + ECS_BIT_COND(it->flags, EcsIterIsFilter, + observer->filter.terms[0].inout == EcsInOutNone); -ECS_DECLARE(EcsMass); - ECS_DECLARE(EcsGrams); - ECS_DECLARE(EcsKiloGrams); + it->system = observer->entity; + it->ctx = observer->ctx; + it->binding_ctx = observer->binding_ctx; + it->term_index = observer->term_index; + it->terms = observer->filter.terms; -ECS_DECLARE(EcsElectricCurrent); - ECS_DECLARE(EcsAmpere); + void *ptrs = it->ptrs; + if (it->flags & EcsIterIsFilter) { + it->ptrs = NULL; + } -ECS_DECLARE(EcsAmount); - ECS_DECLARE(EcsMole); + ecs_entity_t event = it->event; + it->event = flecs_observer_get_actual_event(observer, event); -ECS_DECLARE(EcsLuminousIntensity); - ECS_DECLARE(EcsCandela); + if (observer->run) { + it->next = flecs_default_observer_next_callback; + it->callback = flecs_default_uni_observer_run_callback; + it->ctx = observer; + observer->run(it); + } else { + observer->callback(it); + } -ECS_DECLARE(EcsForce); - ECS_DECLARE(EcsNewton); + it->event = event; + it->ptrs = ptrs; +} -ECS_DECLARE(EcsLength); - ECS_DECLARE(EcsMeters); - ECS_DECLARE(EcsPicoMeters); - ECS_DECLARE(EcsNanoMeters); - ECS_DECLARE(EcsMicroMeters); - ECS_DECLARE(EcsMilliMeters); - ECS_DECLARE(EcsCentiMeters); - ECS_DECLARE(EcsKiloMeters); - ECS_DECLARE(EcsMiles); +static +void flecs_notify_self_observers( + ecs_world_t *world, + ecs_iter_t *it, + const ecs_map_t *observers) +{ + ecs_assert(observers != NULL, ECS_INTERNAL_ERROR, NULL); -ECS_DECLARE(EcsPressure); - ECS_DECLARE(EcsPascal); - ECS_DECLARE(EcsBar); + ecs_map_iter_t mit = ecs_map_iter(observers); + ecs_observer_t *observer; + while ((observer = ecs_map_next_ptr(&mit, ecs_observer_t*, NULL))) { + if (flecs_ignore_observer(world, observer, it->table)) { + continue; + } -ECS_DECLARE(EcsSpeed); - ECS_DECLARE(EcsMetersPerSecond); - ECS_DECLARE(EcsKiloMetersPerSecond); - ECS_DECLARE(EcsKiloMetersPerHour); - ECS_DECLARE(EcsMilesPerHour); + flecs_uni_observer_builtin_run(observer, it); + } +} -ECS_DECLARE(EcsAcceleration); +static +void flecs_notify_entity_observers( + ecs_world_t *world, + ecs_iter_t *it, + const ecs_map_t *observers) +{ + ecs_assert(observers != NULL, ECS_INTERNAL_ERROR, NULL); -ECS_DECLARE(EcsTemperature); - ECS_DECLARE(EcsKelvin); - ECS_DECLARE(EcsCelsius); - ECS_DECLARE(EcsFahrenheit); + if (ECS_BIT_IS_SET(it->flags, EcsIterTableOnly)) { + return; + } -ECS_DECLARE(EcsData); - ECS_DECLARE(EcsBits); - ECS_DECLARE(EcsKiloBits); - ECS_DECLARE(EcsMegaBits); - ECS_DECLARE(EcsGigaBits); - ECS_DECLARE(EcsBytes); - ECS_DECLARE(EcsKiloBytes); - ECS_DECLARE(EcsMegaBytes); - ECS_DECLARE(EcsGigaBytes); - ECS_DECLARE(EcsKibiBytes); - ECS_DECLARE(EcsGibiBytes); - ECS_DECLARE(EcsMebiBytes); + ecs_map_iter_t mit = ecs_map_iter(observers); + ecs_observer_t *observer; + int32_t offset = it->offset, count = it->count; + ecs_entity_t *entities = it->entities; + + ecs_entity_t dummy = 0; + it->entities = &dummy; -ECS_DECLARE(EcsDataRate); - ECS_DECLARE(EcsBitsPerSecond); - ECS_DECLARE(EcsKiloBitsPerSecond); - ECS_DECLARE(EcsMegaBitsPerSecond); - ECS_DECLARE(EcsGigaBitsPerSecond); - ECS_DECLARE(EcsBytesPerSecond); - ECS_DECLARE(EcsKiloBytesPerSecond); - ECS_DECLARE(EcsMegaBytesPerSecond); - ECS_DECLARE(EcsGigaBytesPerSecond); + while ((observer = ecs_map_next_ptr(&mit, ecs_observer_t*, NULL))) { + if (flecs_ignore_observer(world, observer, it->table)) { + continue; + } -ECS_DECLARE(EcsPercentage); + int32_t i, entity_count = it->count; + for (i = 0; i < entity_count; i ++) { + if (entities[i] != observer->filter.terms[0].src.id) { + continue; + } -ECS_DECLARE(EcsAngle); - ECS_DECLARE(EcsRadians); - ECS_DECLARE(EcsDegrees); + it->offset = i; + it->count = 1; + it->sources[0] = entities[i]; + flecs_uni_observer_builtin_run(observer, it); + } + } -ECS_DECLARE(EcsBel); -ECS_DECLARE(EcsDeciBel); + it->offset = offset; + it->count = count; + it->entities = entities; + it->sources[0] = 0; +} -void FlecsUnitsImport( - ecs_world_t *world) +static +void flecs_notify_set_base_observers( + ecs_world_t *world, + ecs_iter_t *it, + const ecs_map_t *observers) { - ECS_MODULE(world, FlecsUnits); + ecs_assert(observers != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_set_name_prefix(world, "Ecs"); + ecs_entity_t event_id = it->event_id; + ecs_entity_t rel = ECS_PAIR_FIRST(event_id); + ecs_entity_t obj = ecs_pair_second(world, event_id); + if (!obj) { + /* Don't notify for deleted (or in progress of being deleted) object */ + return; + } - EcsUnitPrefixes = ecs_entity_init(world, &(ecs_entity_desc_t){ - .name = "prefixes", - .add = { EcsModule } - }); + ecs_record_t *obj_record = flecs_entities_get(world, obj); + if (!obj_record) { + return; + } - /* Initialize unit prefixes */ + ecs_table_t *obj_table = obj_record->table; + if (!obj_table) { + return; + } - ecs_entity_t prev_scope = ecs_set_scope(world, EcsUnitPrefixes); + ecs_map_iter_t mit = ecs_map_iter(observers); + ecs_observer_t *observer; + while ((observer = ecs_map_next_ptr(&mit, ecs_observer_t*, NULL))) { + if (flecs_ignore_observer(world, observer, it->table)) { + continue; + } - EcsYocto = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ - .entity = ecs_entity(world, { .name = "Yocto" }), - .symbol = "y", - .translation = { .factor = 10, .power = -24 } - }); - EcsZepto = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ - .entity = ecs_entity(world, { .name = "Zepto" }), - .symbol = "z", - .translation = { .factor = 10, .power = -21 } - }); - EcsAtto = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ - .entity = ecs_entity(world, { .name = "Atto" }), - .symbol = "a", - .translation = { .factor = 10, .power = -18 } - }); - EcsFemto = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ - .entity = ecs_entity(world, { .name = "Femto" }), - .symbol = "a", - .translation = { .factor = 10, .power = -15 } - }); - EcsPico = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ - .entity = ecs_entity(world, { .name = "Pico" }), - .symbol = "p", - .translation = { .factor = 10, .power = -12 } - }); - EcsNano = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ - .entity = ecs_entity(world, { .name = "Nano" }), - .symbol = "n", - .translation = { .factor = 10, .power = -9 } - }); - EcsMicro = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ - .entity = ecs_entity(world, { .name = "Micro" }), - .symbol = "μ", - .translation = { .factor = 10, .power = -6 } - }); - EcsMilli = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ - .entity = ecs_entity(world, { .name = "Milli" }), - .symbol = "m", - .translation = { .factor = 10, .power = -3 } - }); - EcsCenti = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ - .entity = ecs_entity(world, { .name = "Centi" }), - .symbol = "c", - .translation = { .factor = 10, .power = -2 } - }); - EcsDeci = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ - .entity = ecs_entity(world, { .name = "Deci" }), - .symbol = "d", - .translation = { .factor = 10, .power = -1 } - }); - EcsDeca = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ - .entity = ecs_entity(world, { .name = "Deca" }), - .symbol = "da", - .translation = { .factor = 10, .power = 1 } - }); - EcsHecto = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ - .entity = ecs_entity(world, { .name = "Hecto" }), - .symbol = "h", - .translation = { .factor = 10, .power = 2 } - }); - EcsKilo = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ - .entity = ecs_entity(world, { .name = "Kilo" }), - .symbol = "k", - .translation = { .factor = 10, .power = 3 } - }); - EcsMega = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ - .entity = ecs_entity(world, { .name = "Mega" }), - .symbol = "M", - .translation = { .factor = 10, .power = 6 } - }); - EcsGiga = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ - .entity = ecs_entity(world, { .name = "Giga" }), - .symbol = "G", - .translation = { .factor = 10, .power = 9 } - }); - EcsTera = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ - .entity = ecs_entity(world, { .name = "Tera" }), - .symbol = "T", - .translation = { .factor = 10, .power = 12 } - }); - EcsPeta = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ - .entity = ecs_entity(world, { .name = "Peta" }), - .symbol = "P", - .translation = { .factor = 10, .power = 15 } - }); - EcsExa = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ - .entity = ecs_entity(world, { .name = "Exa" }), - .symbol = "E", - .translation = { .factor = 10, .power = 18 } - }); - EcsZetta = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ - .entity = ecs_entity(world, { .name = "Zetta" }), - .symbol = "Z", - .translation = { .factor = 10, .power = 21 } - }); - EcsYotta = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ - .entity = ecs_entity(world, { .name = "Yotta" }), - .symbol = "Y", - .translation = { .factor = 10, .power = 24 } - }); + ecs_term_t *term = &observer->filter.terms[0]; + ecs_id_t id = term->id; + int32_t column = ecs_search_relation(world, obj_table, 0, id, rel, + 0, it->sources, it->ids, 0); - EcsKibi = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ - .entity = ecs_entity(world, { .name = "Kibi" }), - .symbol = "Ki", - .translation = { .factor = 1024, .power = 1 } - }); - EcsMebi = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ - .entity = ecs_entity(world, { .name = "Mebi" }), - .symbol = "Mi", - .translation = { .factor = 1024, .power = 2 } - }); - EcsGibi = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ - .entity = ecs_entity(world, { .name = "Gibi" }), - .symbol = "Gi", - .translation = { .factor = 1024, .power = 3 } - }); - EcsTebi = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ - .entity = ecs_entity(world, { .name = "Tebi" }), - .symbol = "Ti", - .translation = { .factor = 1024, .power = 4 } - }); - EcsPebi = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ - .entity = ecs_entity(world, { .name = "Pebi" }), - .symbol = "Pi", - .translation = { .factor = 1024, .power = 5 } - }); - EcsExbi = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ - .entity = ecs_entity(world, { .name = "Exbi" }), - .symbol = "Ei", - .translation = { .factor = 1024, .power = 6 } - }); - EcsZebi = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ - .entity = ecs_entity(world, { .name = "Zebi" }), - .symbol = "Zi", - .translation = { .factor = 1024, .power = 7 } - }); - EcsYobi = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ - .entity = ecs_entity(world, { .name = "Yobi" }), - .symbol = "Yi", - .translation = { .factor = 1024, .power = 8 } - }); + bool result = column != -1; + if (term->oper == EcsNot) { + result = !result; + } + if (!result) { + continue; + } - ecs_set_scope(world, prev_scope); + if ((term->src.flags & EcsSelf) && flecs_table_record_get( + world, it->table, id) != NULL) + { + continue; + } - /* Duration units */ + if (!ECS_BIT_IS_SET(it->flags, EcsIterTableOnly)) { + if (!it->sources[0]) { + it->sources[0] = obj; + } - EcsDuration = ecs_quantity_init(world, &(ecs_entity_desc_t){ - .name = "Duration" }); - prev_scope = ecs_set_scope(world, EcsDuration); + /* Populate pointer from object */ + int32_t s_column = ecs_table_type_to_storage_index( + obj_table, column); + if (s_column != -1) { + ecs_column_t *c = &obj_table->data.columns[s_column]; + int32_t row = ECS_RECORD_TO_ROW(obj_record->row); + ecs_type_info_t *ti = obj_table->type_info[s_column]; + void *ptr = ecs_storage_get(c, ti->size, row); + it->ptrs[0] = ptr; + it->sizes[0] = ti->size; + } + } - EcsSeconds = ecs_unit_init(world, &(ecs_unit_desc_t){ - .entity = ecs_entity(world, { .name = "Seconds" }), - .quantity = EcsDuration, - .symbol = "s" }); - ecs_primitive_init(world, &(ecs_primitive_desc_t){ - .entity = EcsSeconds, - .kind = EcsF32 - }); - EcsPicoSeconds = ecs_unit_init(world, &(ecs_unit_desc_t){ - .entity = ecs_entity(world, { .name = "PicoSeconds" }), - .quantity = EcsDuration, - .base = EcsSeconds, - .prefix = EcsPico }); - ecs_primitive_init(world, &(ecs_primitive_desc_t){ - .entity = EcsPicoSeconds, - .kind = EcsF32 - }); + it->event_id = observer->filter.terms[0].id; + flecs_uni_observer_builtin_run(observer, it); + } +} +static +void flecs_notify_set_observers( + ecs_world_t *world, + ecs_iter_t *it, + const ecs_map_t *observers) +{ + ecs_assert(observers != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(it->count != 0, ECS_INTERNAL_ERROR, NULL); - EcsNanoSeconds = ecs_unit_init(world, &(ecs_unit_desc_t){ - .entity = ecs_entity(world, { .name = "NanoSeconds" }), - .quantity = EcsDuration, - .base = EcsSeconds, - .prefix = EcsNano }); - ecs_primitive_init(world, &(ecs_primitive_desc_t){ - .entity = EcsNanoSeconds, - .kind = EcsF32 - }); + if (ECS_BIT_IS_SET(it->flags, EcsIterTableOnly)) { + return; + } - EcsMicroSeconds = ecs_unit_init(world, &(ecs_unit_desc_t){ - .entity = ecs_entity(world, { .name = "MicroSeconds" }), - .quantity = EcsDuration, - .base = EcsSeconds, - .prefix = EcsMicro }); - ecs_primitive_init(world, &(ecs_primitive_desc_t){ - .entity = EcsMicroSeconds, - .kind = EcsF32 - }); + ecs_map_iter_t mit = ecs_map_iter(observers); + ecs_observer_t *observer; + while ((observer = ecs_map_next_ptr(&mit, ecs_observer_t*, NULL))) { + if (!ecs_id_match(it->event_id, observer->filter.terms[0].id)) { + continue; + } - EcsMilliSeconds = ecs_unit_init(world, &(ecs_unit_desc_t){ - .entity = ecs_entity(world, { .name = "MilliSeconds" }), - .quantity = EcsDuration, - .base = EcsSeconds, - .prefix = EcsMilli }); - ecs_primitive_init(world, &(ecs_primitive_desc_t){ - .entity = EcsMilliSeconds, - .kind = EcsF32 - }); + if (flecs_ignore_observer(world, observer, it->table)) { + continue; + } - EcsMinutes = ecs_unit_init(world, &(ecs_unit_desc_t){ - .entity = ecs_entity(world, { .name = "Minutes" }), - .quantity = EcsDuration, - .base = EcsSeconds, - .symbol = "min", - .translation = { .factor = 60, .power = 1 } }); - ecs_primitive_init(world, &(ecs_primitive_desc_t){ - .entity = EcsMinutes, - .kind = EcsU32 - }); + ecs_entity_t src = it->entities[0]; + int32_t i, count = it->count; + ecs_filter_t *filter = &observer->filter; + ecs_term_t *term = &filter->terms[0]; + ecs_entity_t src_id = term->src.id; - EcsHours = ecs_unit_init(world, &(ecs_unit_desc_t){ - .entity = ecs_entity(world, { .name = "Hours" }), - .quantity = EcsDuration, - .base = EcsMinutes, - .symbol = "h", - .translation = { .factor = 60, .power = 1 } }); - ecs_primitive_init(world, &(ecs_primitive_desc_t){ - .entity = EcsHours, - .kind = EcsU32 - }); + /* If observer is for a specific entity, make sure it is in the table + * being triggered for */ + if (!(ecs_term_match_this(term))) { + for (i = 0; i < count; i ++) { + if (it->entities[i] == src_id) { + break; + } + } + + if (i == count) { + continue; + } + + /* If the entity matches, observer for no other entities */ + it->entities[0] = 0; + it->count = 1; + } + + if (flecs_term_match_table(world, term, it->table, it->ids, + it->columns, it->sources, NULL, true, it->flags)) + { + if (!it->sources[0]) { + /* Do not match owned components */ + continue; + } + + /* Triggers for supersets can be instanced */ + bool instanced = filter->flags & EcsFilterIsInstanced; + bool is_filter = ECS_BIT_IS_SET(it->flags, EcsIterIsFilter); + if (it->count == 1 || instanced || is_filter || !it->sizes[0]) { + ECS_BIT_COND(it->flags, EcsIterIsInstanced, instanced); + flecs_uni_observer_builtin_run(observer, it); + ECS_BIT_CLEAR(it->flags, EcsIterIsInstanced); + } else { + ecs_entity_t *entities = it->entities; + it->count = 1; + for (i = 0; i < count; i ++) { + it->entities = &entities[i]; + flecs_uni_observer_builtin_run(observer, it); + } + it->entities = entities; + } + } + + it->entities[0] = src; + it->count = count; + } +} + +static +void flecs_notify_observers_for_id( + ecs_world_t *world, + const ecs_map_t *evt, + ecs_id_t event_id, + ecs_iter_t *it, + bool *iter_set) +{ + const ecs_event_id_record_t *idt = flecs_get_observers_for_id(evt, event_id); + if (!idt) { + return; + } + + if (ecs_map_is_initialized(&idt->observers)) { + flecs_init_observer_iter(it, iter_set); + flecs_notify_self_observers(world, it, &idt->observers); + } + if (ecs_map_is_initialized(&idt->entity_observers)) { + flecs_init_observer_iter(it, iter_set); + flecs_notify_entity_observers(world, it, &idt->entity_observers); + } + if (ecs_map_is_initialized(&idt->set_observers)) { + flecs_init_observer_iter(it, iter_set); + flecs_notify_set_base_observers(world, it, &idt->set_observers); + } +} + +static +void flecs_notify_set_observers_for_id( + ecs_world_t *world, + const ecs_map_t *evt, + ecs_iter_t *it, + bool *iter_set, + ecs_id_t set_id) +{ + const ecs_event_id_record_t *idt = flecs_get_observers_for_id(evt, set_id); + if (idt && ecs_map_is_initialized(&idt->set_observers)) { + flecs_init_observer_iter(it, iter_set); + flecs_notify_set_observers(world, it, &idt->set_observers); + } +} + +static +void flecs_uni_observer_trigger_existing( + ecs_world_t *world, + ecs_observer_t *observer) +{ + ecs_iter_action_t callback = observer->callback; + + /* If yield existing is enabled, observer for each thing that matches + * the event, if the event is iterable. */ + int i, count = observer->event_count; + for (i = 0; i < count; i ++) { + ecs_entity_t evt = observer->events[i]; + const EcsIterable *iterable = ecs_get(world, evt, EcsIterable); + if (!iterable) { + continue; + } + + ecs_iter_t it; + iterable->init(world, world, &it, &observer->filter.terms[0]); + it.system = observer->entity; + it.ctx = observer->ctx; + it.binding_ctx = observer->binding_ctx; + it.event = evt; + + ecs_iter_next_action_t next = it.next; + ecs_assert(next != NULL, ECS_INTERNAL_ERROR, NULL); + while (next(&it)) { + it.event_id = it.ids[0]; + callback(&it); + } + } +} + +static +void flecs_multi_observer_yield_existing( + ecs_world_t *world, + ecs_observer_t *observer) +{ + ecs_run_action_t run = observer->run; + if (!run) { + run = flecs_default_multi_observer_run_callback; + } + + ecs_run_aperiodic(world, EcsAperiodicEmptyTables); + + int32_t pivot_term = ecs_filter_pivot_term(world, &observer->filter); + if (pivot_term < 0) { + return; + } + + /* If yield existing is enabled, invoke for each thing that matches + * the event, if the event is iterable. */ + int i, count = observer->event_count; + for (i = 0; i < count; i ++) { + ecs_entity_t evt = observer->events[i]; + const EcsIterable *iterable = ecs_get(world, evt, EcsIterable); + if (!iterable) { + continue; + } + + ecs_iter_t it; + iterable->init(world, world, &it, &observer->filter.terms[pivot_term]); + it.terms = observer->filter.terms; + it.field_count = 1; + it.term_index = pivot_term; + it.system = observer->entity; + it.ctx = observer; + it.binding_ctx = observer->binding_ctx; + it.event = evt; + + ecs_iter_next_action_t next = it.next; + ecs_assert(next != NULL, ECS_INTERNAL_ERROR, NULL); + while (next(&it)) { + it.event_id = it.ids[0]; + run(&it); + world->event_id ++; + } + } +} + +bool flecs_check_observers_for_event( + 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 = flecs_get_observers_for_event(observable, event); + if (!evt) { + return false; + } + + ecs_event_id_record_t *edr = flecs_get_observers_for_id(evt, id); + if (edr) { + return edr->observer_count != 0; + } else { + return false; + } +} + +void flecs_observers_notify( + ecs_iter_t *it, + ecs_observable_t *observable, + const ecs_type_t *ids, + ecs_entity_t event) +{ + 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; + ecs_world_t *world = it->real_world; + + for (e = 0; e < 2; e ++) { + event = events[e]; + const ecs_map_t *evt = flecs_get_observers_for_event(observable, event); + if (!evt) { + continue; + } + + it->event = event; + + for (i = 0; i < ids_count; i ++) { + ecs_id_t id = ids_array[i]; + ecs_entity_t role = id & ECS_ID_FLAGS_MASK; + bool iter_set = false; + + it->event_id = id; + + flecs_notify_observers_for_id(world, evt, id, it, &iter_set); + + if (ECS_HAS_ID_FLAG(role, PAIR)) { + ecs_entity_t r = ECS_PAIR_FIRST(id); + ecs_entity_t o = ECS_PAIR_SECOND(id); + + ecs_id_t tid = ecs_pair(r, EcsWildcard); + flecs_notify_observers_for_id(world, evt, tid, it, &iter_set); + + tid = ecs_pair(EcsWildcard, o); + flecs_notify_observers_for_id(world, evt, tid, it, &iter_set); + + tid = ecs_pair(EcsWildcard, EcsWildcard); + flecs_notify_observers_for_id(world, evt, tid, it, &iter_set); + } else { + flecs_notify_observers_for_id( + world, evt, EcsWildcard, it, &iter_set); + } + + flecs_notify_observers_for_id(world, evt, EcsAny, it, &iter_set); + + if (iter_set) { + ecs_iter_fini(it); + } + } + } +} + +void flecs_set_observers_notify( + ecs_iter_t *it, + ecs_observable_t *observable, + const ecs_type_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; + ecs_world_t *world = it->real_world; + + for (e = 0; e < 2; e ++) { + event = events[e]; + const ecs_map_t *evt = flecs_get_observers_for_event(observable, event); + if (!evt) { + continue; + } + + 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; + + flecs_notify_set_observers_for_id(world, evt, it, &iter_set, set_id); + + if (iter_set) { + ecs_iter_fini(it); + } + } + } +} + +static +int flecs_uni_observer_init( + ecs_world_t *world, + ecs_observer_t *observer, + const ecs_observer_desc_t *desc) +{ + ecs_term_t *term = &observer->filter.terms[0]; + observer->last_event_id = desc->last_event_id; + observer->register_id = flecs_from_public_id(world, term->id); + term->field_index = desc->term_index; + + if (ecs_id_is_tag(world, term->id)) { + /* If id is a tag, downgrade OnSet/UnSet to OnAdd/OnRemove. */ + int32_t e, count = observer->event_count; + for (e = 0; e < count; e ++) { + if (observer->events[e] == EcsOnSet) { + observer->events[e] = EcsOnAdd; + } else + if (observer->events[e] == EcsUnSet) { + observer->events[e] = EcsOnRemove; + } + } + } + + flecs_uni_observer_register(world, observer->observable, observer); + + if (desc->yield_existing) { + flecs_uni_observer_trigger_existing(world, observer); + } + + return 0; +} + +static +int flecs_multi_observer_init( + ecs_world_t *world, + ecs_observer_t *observer, + const ecs_observer_desc_t *desc) +{ + /* Create last event id for filtering out the same event that arrives from + * more than one term */ + observer->last_event_id = ecs_os_calloc_t(int32_t); + + /* Mark observer as multi observer */ + observer->is_multi = true; + + /* Create a child observer for each term in the filter */ + ecs_filter_t *filter = &observer->filter; + ecs_observer_desc_t child_desc = *desc; + child_desc.last_event_id = observer->last_event_id; + child_desc.run = NULL; + child_desc.callback = flecs_multi_observer_builtin_run; + child_desc.ctx = observer; + child_desc.filter.expr = NULL; + child_desc.filter.terms_buffer = NULL; + child_desc.filter.terms_buffer_count = 0; + child_desc.binding_ctx = NULL; + child_desc.binding_ctx_free = NULL; + child_desc.yield_existing = false; + ecs_os_zeromem(&child_desc.entity); + ecs_os_zeromem(&child_desc.filter.terms); + ecs_os_memcpy_n(child_desc.events, observer->events, + ecs_entity_t, observer->event_count); + + int i, term_count = filter->term_count; + bool optional_only = filter->flags & EcsFilterMatchThis; + for (i = 0; i < term_count; i ++) { + if (filter->terms[i].oper != EcsOptional) { + if (ecs_term_match_this(&filter->terms[i])) { + optional_only = false; + } + } + } + + if (filter->flags & EcsFilterMatchPrefab) { + child_desc.filter.flags |= EcsFilterMatchPrefab; + } + if (filter->flags & EcsFilterMatchDisabled) { + child_desc.filter.flags |= EcsFilterMatchDisabled; + } + + /* Create observers as children of observer */ + ecs_entity_t old_scope = ecs_set_scope(world, observer->entity); + + for (i = 0; i < term_count; i ++) { + ecs_term_t *term = &child_desc.filter.terms[0]; + child_desc.term_index = filter->terms[i].field_index; + *term = filter->terms[i]; + + ecs_oper_kind_t oper = term->oper; + ecs_id_t id = term->id; + + /* AndFrom & OrFrom terms insert multiple observers */ + if (oper == EcsAndFrom || oper == EcsOrFrom) { + const ecs_type_t *type = ecs_get_type(world, id); + int32_t ti, ti_count = type->count; + ecs_id_t *ti_ids = type->array; + + /* Correct operator will be applied when an event occurs, and + * the observer is evaluated on the observer source */ + term->oper = EcsAnd; + for (ti = 0; ti < ti_count; ti ++) { + ecs_id_t ti_id = ti_ids[ti]; + ecs_id_record_t *idr = flecs_id_record_get(world, ti_id); + if (idr->flags & EcsIdDontInherit) { + continue; + } + + term->first.name = NULL; + term->first.id = ti_ids[ti]; + term->id = ti_ids[ti]; + + if (ecs_observer_init(world, &child_desc) == 0) { + goto error; + } + } + continue; + } + + /* If observer only contains optional terms, match everything */ + if (optional_only) { + term->id = EcsAny; + term->first.id = EcsAny; + term->src.id = EcsThis; + term->src.flags = EcsIsVariable; + term->second.id = 0; + } else if (term->oper == EcsOptional) { + continue; + } + if (ecs_observer_init(world, &child_desc) == 0) { + goto error; + } + + if (optional_only) { + break; + } + } + + ecs_set_scope(world, old_scope); + + if (desc->yield_existing) { + flecs_multi_observer_yield_existing(world, observer); + } + + return 0; +error: + return -1; +} + +ecs_entity_t ecs_observer_init( + ecs_world_t *world, + const ecs_observer_desc_t *desc) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(desc->_canary == 0, ECS_INVALID_PARAMETER, NULL); + ecs_check(!(world->flags & EcsWorldFini), ECS_INVALID_OPERATION, NULL); + + ecs_entity_t entity = desc->entity; + if (!entity) { + entity = ecs_new(world, 0); + } + EcsPoly *poly = ecs_poly_bind(world, entity, ecs_observer_t); + if (!poly->poly) { + ecs_check(desc->callback != NULL || desc->run != NULL, + ECS_INVALID_OPERATION, NULL); + + ecs_observer_t *observer = ecs_poly_new(ecs_observer_t); + ecs_assert(observer != NULL, ECS_INTERNAL_ERROR, NULL); + + observer->world = world; + observer->dtor = (ecs_poly_dtor_t)flecs_observer_fini; + observer->entity = entity; + + /* 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 = ecs_get_name(world, entity); + ecs_filter_t *filter = filter_desc.storage = &observer->filter; + *filter = ECS_FILTER_INIT; + + /* Parse filter */ + if (ecs_filter_init(world, &filter_desc) == NULL) { + flecs_observer_fini(observer); + return 0; + } + + /* Observer must have at least one term */ + ecs_check(observer->filter.term_count > 0, ECS_INVALID_PARAMETER, NULL); + + poly->poly = observer; + + ecs_observable_t *observable = desc->observable; + if (!observable) { + observable = ecs_get_observable(world); + } + + observer->run = desc->run; + observer->callback = desc->callback; + 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->term_index = desc->term_index; + observer->observable = observable; + + /* Check if observer is monitor. Monitors are created as multi observers + * since they require pre/post checking of the filter to test if the + * entity is entering/leaving the monitor. */ + int i; + for (i = 0; i < ECS_OBSERVER_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); + + bool multi = false; + + if (filter->term_count == 1 && !desc->last_event_id) { + ecs_term_t *term = &filter->terms[0]; + /* If the filter has a single term but it is a *From operator, we + * need to create a multi observer */ + multi |= (term->oper == EcsAndFrom) || (term->oper == EcsOrFrom); + + /* An observer with only optional terms is a special case that is + * only handled by multi observers */ + multi |= term->oper == EcsOptional; + } + + if (filter->term_count == 1 && !observer->is_monitor && !multi) { + if (flecs_uni_observer_init(world, observer, desc)) { + goto error; + } + } else { + if (flecs_multi_observer_init(world, observer, desc)) { + goto error; + } + } + + if (ecs_get_name(world, entity)) { + ecs_trace("#[green]observer#[reset] %s created", + ecs_get_name(world, entity)); + } + } else { + /* If existing entity handle was provided, override existing params */ + if (desc->callback) { + ecs_poly(poly->poly, ecs_observer_t)->callback = desc->callback; + } + if (desc->ctx) { + ecs_poly(poly->poly, ecs_observer_t)->ctx = desc->ctx; + } + if (desc->binding_ctx) { + ecs_poly(poly->poly, ecs_observer_t)->binding_ctx = desc->binding_ctx; + } + } + + ecs_poly_modified(world, entity, ecs_observer_t); + + return entity; +error: + ecs_delete(world, entity); + return 0; +} + +void* ecs_get_observer_ctx( + const ecs_world_t *world, + ecs_entity_t observer) +{ + const EcsPoly *o = ecs_poly_bind_get(world, observer, ecs_observer_t); + if (o) { + ecs_poly_assert(o->poly, ecs_observer_t); + return ((ecs_observer_t*)o->poly)->ctx; + } else { + return NULL; + } +} + +void* ecs_get_observer_binding_ctx( + const ecs_world_t *world, + ecs_entity_t observer) +{ + const EcsPoly *o = ecs_poly_bind_get(world, observer, ecs_observer_t); + if (o) { + ecs_poly_assert(o->poly, ecs_observer_t); + return ((ecs_observer_t*)o->poly)->binding_ctx; + } else { + return NULL; + } +} + +void flecs_observer_fini( + ecs_observer_t *observer) +{ + if (observer->is_multi) { + /* Child observers get deleted up by entity cleanup logic */ + ecs_os_free(observer->last_event_id); + } else { + if (observer->filter.term_count) { + flecs_unregister_observer( + observer->world, observer->observable, observer); + } else { + /* Observer creation failed while creating filter */ + } + } + + /* Cleanup filters */ + ecs_filter_fini(&observer->filter); + + /* Cleanup context */ + if (observer->ctx_free) { + observer->ctx_free(observer->ctx); + } + + if (observer->binding_ctx_free) { + observer->binding_ctx_free(observer->binding_ctx); + } + + ecs_poly_free(observer, ecs_observer_t); +} + + +static +void table_cache_list_remove( + ecs_table_cache_t *cache, + ecs_table_cache_hdr_t *elem) +{ + ecs_table_cache_hdr_t *next = elem->next; + ecs_table_cache_hdr_t *prev = elem->prev; + + if (next) { + next->prev = prev; + } + if (prev) { + prev->next = next; + } + + cache->empty_tables.count -= !!elem->empty; + cache->tables.count -= !elem->empty; + + if (cache->empty_tables.first == elem) { + cache->empty_tables.first = next; + } else if (cache->tables.first == elem) { + cache->tables.first = next; + } + if (cache->empty_tables.last == elem) { + cache->empty_tables.last = prev; + } + if (cache->tables.last == elem) { + cache->tables.last = prev; + } +} + +static +void table_cache_list_insert( + ecs_table_cache_t *cache, + ecs_table_cache_hdr_t *elem) +{ + ecs_table_cache_hdr_t *last; + if (elem->empty) { + last = cache->empty_tables.last; + cache->empty_tables.last = elem; + if ((++ cache->empty_tables.count) == 1) { + cache->empty_tables.first = elem; + } + } else { + last = cache->tables.last; + cache->tables.last = elem; + if ((++ cache->tables.count) == 1) { + cache->tables.first = elem; + } + } + + elem->next = NULL; + elem->prev = last; + + if (last) { + last->next = elem; + } +} + +void ecs_table_cache_init( + ecs_table_cache_t *cache) +{ + ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_map_init(&cache->index, ecs_table_cache_hdr_t*, 4); +} + +void ecs_table_cache_fini( + ecs_table_cache_t *cache) +{ + ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_map_fini(&cache->index); +} + +bool ecs_table_cache_is_empty( + const ecs_table_cache_t *cache) +{ + return ecs_map_count(&cache->index) == 0; +} + +void ecs_table_cache_insert( + ecs_table_cache_t *cache, + const ecs_table_t *table, + ecs_table_cache_hdr_t *result) +{ + ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(ecs_table_cache_get(cache, table) == NULL, + ECS_INTERNAL_ERROR, NULL); + ecs_assert(result != NULL, ECS_INTERNAL_ERROR, NULL); + + bool empty; + if (!table) { + empty = false; + } else { + empty = ecs_table_count(table) == 0; + } + + result->cache = cache; + result->table = (ecs_table_t*)table; + result->empty = empty; + + table_cache_list_insert(cache, result); + + if (table) { + ecs_map_set_ptr(&cache->index, table->id, result); + } + + ecs_assert(empty || cache->tables.first != NULL, + ECS_INTERNAL_ERROR, NULL); + ecs_assert(!empty || cache->empty_tables.first != NULL, + ECS_INTERNAL_ERROR, NULL); +} + +void ecs_table_cache_replace( + ecs_table_cache_t *cache, + const ecs_table_t *table, + ecs_table_cache_hdr_t *elem) +{ + ecs_table_cache_hdr_t **oldptr = ecs_map_get(&cache->index, + ecs_table_cache_hdr_t*, table->id); + ecs_assert(oldptr != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_table_cache_hdr_t *old = *oldptr; + ecs_assert(old != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_table_cache_hdr_t *prev = old->prev, *next = old->next; + if (prev) { + ecs_assert(prev->next == old, ECS_INTERNAL_ERROR, NULL); + prev->next = elem; + } + if (next) { + ecs_assert(next->prev == old, ECS_INTERNAL_ERROR, NULL); + next->prev = elem; + } + + if (cache->empty_tables.first == old) { + cache->empty_tables.first = elem; + } + if (cache->empty_tables.last == old) { + cache->empty_tables.last = elem; + } + if (cache->tables.first == old) { + cache->tables.first = elem; + } + if (cache->tables.last == old) { + cache->tables.last = elem; + } + + *oldptr = elem; + elem->prev = prev; + elem->next = next; +} + +void* ecs_table_cache_get( + const ecs_table_cache_t *cache, + const ecs_table_t *table) +{ + ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); + if (table) { + return ecs_map_get_ptr(&cache->index, ecs_table_cache_hdr_t*, table->id); + } else { + ecs_table_cache_hdr_t *elem = cache->tables.first; + ecs_assert(!elem || elem->table == NULL, ECS_INTERNAL_ERROR, NULL); + return elem; + } +} + +void* ecs_table_cache_remove( + ecs_table_cache_t *cache, + const ecs_table_t *table, + ecs_table_cache_hdr_t *elem) +{ + ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + + if (!ecs_map_is_initialized(&cache->index)) { + return NULL; + } + + if (!elem) { + elem = ecs_map_get_ptr( + &cache->index, ecs_table_cache_hdr_t*, table->id); + if (!elem) { + return false; + } + } + + ecs_assert(elem != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(elem->cache == cache, ECS_INTERNAL_ERROR, NULL); + ecs_assert(elem->table == table, ECS_INTERNAL_ERROR, NULL); + + table_cache_list_remove(cache, elem); + + ecs_map_remove(&cache->index, table->id); + + return elem; +} + +bool ecs_table_cache_set_empty( + ecs_table_cache_t *cache, + const ecs_table_t *table, + bool empty) +{ + ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_table_cache_hdr_t *elem = ecs_map_get_ptr( + &cache->index, ecs_table_cache_hdr_t*, table->id); + if (!elem) { + return false; + } + + if (elem->empty == empty) { + return false; + } + + table_cache_list_remove(cache, elem); + elem->empty = empty; + table_cache_list_insert(cache, elem); + + return true; +} + +bool flecs_table_cache_iter( + ecs_table_cache_t *cache, + ecs_table_cache_iter_t *out) +{ + ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(out != NULL, ECS_INTERNAL_ERROR, NULL); + out->next = cache->tables.first; + out->next_list = NULL; + out->cur = NULL; + return out->next != NULL; +} + +bool flecs_table_cache_empty_iter( + ecs_table_cache_t *cache, + ecs_table_cache_iter_t *out) +{ + ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(out != NULL, ECS_INTERNAL_ERROR, NULL); + out->next = cache->empty_tables.first; + out->next_list = NULL; + out->cur = NULL; + return out->next != NULL; +} + +bool flecs_table_cache_all_iter( + ecs_table_cache_t *cache, + ecs_table_cache_iter_t *out) +{ + ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(out != NULL, ECS_INTERNAL_ERROR, NULL); + out->next = cache->empty_tables.first; + out->next_list = cache->tables.first; + out->cur = NULL; + return out->next != NULL || out->next_list != NULL; +} + +ecs_table_cache_hdr_t* _flecs_table_cache_next( + ecs_table_cache_iter_t *it) +{ + ecs_table_cache_hdr_t *next = it->next; + if (!next) { + next = it->next_list; + it->next_list = NULL; + if (!next) { + return false; + } + } + + it->cur = next; + it->next = next->next; + return next; +} - EcsDays = ecs_unit_init(world, &(ecs_unit_desc_t){ - .entity = ecs_entity(world, { .name = "Days" }), - .quantity = EcsDuration, - .base = EcsHours, - .symbol = "d", - .translation = { .factor = 24, .power = 1 } }); - ecs_primitive_init(world, &(ecs_primitive_desc_t){ - .entity = EcsDays, - .kind = EcsU32 - }); - ecs_set_scope(world, prev_scope); +#include +#include - /* Time units */ +void ecs_os_api_impl(ecs_os_api_t *api); - EcsTime = ecs_quantity_init(world, &(ecs_entity_desc_t){ - .name = "Time" }); - prev_scope = ecs_set_scope(world, EcsTime); +static bool ecs_os_api_initialized = false; +static bool ecs_os_api_initializing = false; +static int ecs_os_api_init_count = 0; - EcsDate = ecs_unit_init(world, &(ecs_unit_desc_t){ - .entity = ecs_entity(world, { .name = "Date" }), - .quantity = EcsTime }); - ecs_primitive_init(world, &(ecs_primitive_desc_t){ - .entity = EcsDate, - .kind = EcsU32 - }); - ecs_set_scope(world, prev_scope); +#ifndef __EMSCRIPTEN__ +ecs_os_api_t ecs_os_api = { + .flags_ = EcsOsApiHighResolutionTimer | EcsOsApiLogWithColors, + .log_level_ = -1 /* Disable tracing by default, but log warnings/errors */ +}; +#else +/* Disable colors by default for emscripten */ +ecs_os_api_t ecs_os_api = { + .flags_ = EcsOsApiHighResolutionTimer, + .log_level_ = -1 /* Disable tracing by default, but log warnings/errors */ +}; +#endif - /* Mass units */ +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; - EcsMass = ecs_quantity_init(world, &(ecs_entity_desc_t){ - .name = "Mass" }); - prev_scope = ecs_set_scope(world, EcsMass); - EcsGrams = ecs_unit_init(world, &(ecs_unit_desc_t){ - .entity = ecs_entity(world, { .name = "Grams" }), - .quantity = EcsMass, - .symbol = "g" }); - ecs_primitive_init(world, &(ecs_primitive_desc_t){ - .entity = EcsGrams, - .kind = EcsF32 - }); - EcsKiloGrams = ecs_unit_init(world, &(ecs_unit_desc_t){ - .entity = ecs_entity(world, { .name = "KiloGrams" }), - .quantity = EcsMass, - .prefix = EcsKilo, - .base = EcsGrams }); - ecs_primitive_init(world, &(ecs_primitive_desc_t){ - .entity = EcsKiloGrams, - .kind = EcsF32 - }); - ecs_set_scope(world, prev_scope); +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; + } +} - /* Electric current units */ +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_(); + } + } +} - EcsElectricCurrent = ecs_quantity_init(world, &(ecs_entity_desc_t){ - .name = "ElectricCurrent" }); - prev_scope = ecs_set_scope(world, EcsElectricCurrent); - EcsAmpere = ecs_unit_init(world, &(ecs_unit_desc_t){ - .entity = ecs_entity(world, { .name = "Ampere" }), - .quantity = EcsElectricCurrent, - .symbol = "A" }); - ecs_primitive_init(world, &(ecs_primitive_desc_t){ - .entity = EcsAmpere, - .kind = EcsF32 - }); - ecs_set_scope(world, prev_scope); +void ecs_os_fini(void) { + if (!--ecs_os_api_init_count) { + if (ecs_os_api.fini_) { + ecs_os_api.fini_(); + } + } +} - /* Amount of substance units */ +#if !defined(ECS_TARGET_WINDOWS) && !defined(ECS_TARGET_EM) && !defined(ECS_TARGET_ANDROID) +#include +#define ECS_BT_BUF_SIZE 100 +static +void dump_backtrace( + FILE *stream) +{ + int nptrs; + void *buffer[ECS_BT_BUF_SIZE]; + char **strings; - EcsAmount = ecs_quantity_init(world, &(ecs_entity_desc_t){ - .name = "Amount" }); - prev_scope = ecs_set_scope(world, EcsAmount); - EcsMole = ecs_unit_init(world, &(ecs_unit_desc_t){ - .entity = ecs_entity(world, { .name = "Mole" }), - .quantity = EcsAmount, - .symbol = "mol" }); - ecs_primitive_init(world, &(ecs_primitive_desc_t){ - .entity = EcsMole, - .kind = EcsF32 - }); - ecs_set_scope(world, prev_scope); + nptrs = backtrace(buffer, ECS_BT_BUF_SIZE); - /* Luminous intensity units */ + strings = backtrace_symbols(buffer, nptrs); + if (strings == NULL) { + return; + } - EcsLuminousIntensity = ecs_quantity_init(world, &(ecs_entity_desc_t){ - .name = "LuminousIntensity" }); - prev_scope = ecs_set_scope(world, EcsLuminousIntensity); - EcsCandela = ecs_unit_init(world, &(ecs_unit_desc_t){ - .entity = ecs_entity(world, { .name = "Candela" }), - .quantity = EcsLuminousIntensity, - .symbol = "cd" }); - ecs_primitive_init(world, &(ecs_primitive_desc_t){ - .entity = EcsCandela, - .kind = EcsF32 - }); - ecs_set_scope(world, prev_scope); + for (int j = 3; j < nptrs; j++) { + fprintf(stream, "%s\n", strings[j]); + } - /* Force units */ + free(strings); +} +#else +static +void dump_backtrace( + FILE *stream) +{ + (void)stream; +} +#endif - EcsForce = ecs_quantity_init(world, &(ecs_entity_desc_t){ - .name = "Force" }); - prev_scope = ecs_set_scope(world, EcsForce); - EcsNewton = ecs_unit_init(world, &(ecs_unit_desc_t){ - .entity = ecs_entity(world, { .name = "Newton" }), - .quantity = EcsForce, - .symbol = "N" }); - ecs_primitive_init(world, &(ecs_primitive_desc_t){ - .entity = EcsNewton, - .kind = EcsF32 - }); - ecs_set_scope(world, prev_scope); +static +void log_msg( + int32_t level, + const char *file, + int32_t line, + const char *msg) +{ + FILE *stream; + if (level >= 0) { + stream = stdout; + } else { + stream = stderr; + } - /* Length units */ + bool use_colors = ecs_os_api.flags_ & EcsOsApiLogWithColors; + bool timestamp = ecs_os_api.flags_ & EcsOsApiLogWithTimeStamp; + bool deltatime = ecs_os_api.flags_ & EcsOsApiLogWithTimeDelta; - EcsLength = ecs_quantity_init(world, &(ecs_entity_desc_t){ - .name = "Length" }); - prev_scope = ecs_set_scope(world, EcsLength); - EcsMeters = ecs_unit_init(world, &(ecs_unit_desc_t){ - .entity = ecs_entity(world, { .name = "Meters" }), - .quantity = EcsLength, - .symbol = "m" }); - ecs_primitive_init(world, &(ecs_primitive_desc_t){ - .entity = EcsMeters, - .kind = EcsF32 - }); + time_t now = 0; - EcsPicoMeters = ecs_unit_init(world, &(ecs_unit_desc_t){ - .entity = ecs_entity(world, { .name = "PicoMeters" }), - .quantity = EcsLength, - .base = EcsMeters, - .prefix = EcsPico }); - ecs_primitive_init(world, &(ecs_primitive_desc_t){ - .entity = EcsPicoMeters, - .kind = EcsF32 - }); + if (deltatime) { + now = time(NULL); + time_t delta = 0; + if (ecs_os_api.log_last_timestamp_) { + delta = now - ecs_os_api.log_last_timestamp_; + } + ecs_os_api.log_last_timestamp_ = (int64_t)now; - EcsNanoMeters = ecs_unit_init(world, &(ecs_unit_desc_t){ - .entity = ecs_entity(world, { .name = "NanoMeters" }), - .quantity = EcsLength, - .base = EcsMeters, - .prefix = EcsNano }); - ecs_primitive_init(world, &(ecs_primitive_desc_t){ - .entity = EcsNanoMeters, - .kind = EcsF32 - }); + if (delta) { + if (delta < 10) { + fputs(" ", stream); + } + if (delta < 100) { + fputs(" ", stream); + } + char time_buf[20]; + ecs_os_sprintf(time_buf, "%u", (uint32_t)delta); + fputs("+", stream); + fputs(time_buf, stream); + fputs(" ", stream); + } else { + fputs(" ", stream); + } + } - EcsMicroMeters = ecs_unit_init(world, &(ecs_unit_desc_t){ - .entity = ecs_entity(world, { .name = "MicroMeters" }), - .quantity = EcsLength, - .base = EcsMeters, - .prefix = EcsMicro }); - ecs_primitive_init(world, &(ecs_primitive_desc_t){ - .entity = EcsMicroMeters, - .kind = EcsF32 - }); + if (timestamp) { + if (!now) { + now = time(NULL); + } + char time_buf[20]; + ecs_os_sprintf(time_buf, "%u", (uint32_t)now); + fputs(time_buf, stream); + fputs(" ", stream); + } - EcsMilliMeters = ecs_unit_init(world, &(ecs_unit_desc_t){ - .entity = ecs_entity(world, { .name = "MilliMeters" }), - .quantity = EcsLength, - .base = EcsMeters, - .prefix = EcsMilli }); - ecs_primitive_init(world, &(ecs_primitive_desc_t){ - .entity = EcsMilliMeters, - .kind = EcsF32 - }); + if (level >= 0) { + if (level == 0) { + if (use_colors) fputs(ECS_MAGENTA, stream); + } else { + if (use_colors) fputs(ECS_GREY, stream); + } + fputs("info", stream); + } else if (level == -2) { + if (use_colors) fputs(ECS_YELLOW, stream); + fputs("warning", stream); + } else if (level == -3) { + if (use_colors) fputs(ECS_RED, stream); + fputs("error", stream); + } else if (level == -4) { + if (use_colors) fputs(ECS_RED, stream); + fputs("fatal", stream); + } - EcsCentiMeters = ecs_unit_init(world, &(ecs_unit_desc_t){ - .entity = ecs_entity(world, { .name = "CentiMeters" }), - .quantity = EcsLength, - .base = EcsMeters, - .prefix = EcsCenti }); - ecs_primitive_init(world, &(ecs_primitive_desc_t){ - .entity = EcsCentiMeters, - .kind = EcsF32 - }); + if (use_colors) fputs(ECS_NORMAL, stream); + fputs(": ", stream); - EcsKiloMeters = ecs_unit_init(world, &(ecs_unit_desc_t){ - .entity = ecs_entity(world, { .name = "KiloMeters" }), - .quantity = EcsLength, - .base = EcsMeters, - .prefix = EcsKilo }); - ecs_primitive_init(world, &(ecs_primitive_desc_t){ - .entity = EcsKiloMeters, - .kind = EcsF32 - }); - - EcsMiles = ecs_unit_init(world, &(ecs_unit_desc_t){ - .entity = ecs_entity(world, { .name = "Miles" }), - .quantity = EcsLength, - .symbol = "mi" - }); - ecs_primitive_init(world, &(ecs_primitive_desc_t){ - .entity = EcsMiles, - .kind = EcsF32 - }); - ecs_set_scope(world, prev_scope); + if (level >= 0) { + if (ecs_os_api.log_indent_) { + char indent[32]; + int i, indent_count = ecs_os_api.log_indent_; + if (indent_count > 15) indent_count = 15; - /* Pressure units */ + for (i = 0; i < indent_count; i ++) { + indent[i * 2] = '|'; + indent[i * 2 + 1] = ' '; + } - EcsPressure = ecs_quantity_init(world, &(ecs_entity_desc_t){ - .name = "Pressure" }); - prev_scope = ecs_set_scope(world, EcsPressure); - EcsPascal = ecs_unit_init(world, &(ecs_unit_desc_t){ - .entity = ecs_entity(world, { .name = "Pascal" }), - .quantity = EcsPressure, - .symbol = "Pa" }); - ecs_primitive_init(world, &(ecs_primitive_desc_t){ - .entity = EcsPascal, - .kind = EcsF32 - }); - EcsBar = ecs_unit_init(world, &(ecs_unit_desc_t){ - .entity = ecs_entity(world, { .name = "Bar" }), - .quantity = EcsPressure, - .symbol = "bar" }); - ecs_primitive_init(world, &(ecs_primitive_desc_t){ - .entity = EcsBar, - .kind = EcsF32 - }); - ecs_set_scope(world, prev_scope); + if (ecs_os_api.log_indent_ != indent_count) { + indent[i * 2 - 2] = '+'; + } - /* Speed units */ + indent[i * 2] = '\0'; - EcsSpeed = ecs_quantity_init(world, &(ecs_entity_desc_t){ - .name = "Speed" }); - prev_scope = ecs_set_scope(world, EcsSpeed); - EcsMetersPerSecond = ecs_unit_init(world, &(ecs_unit_desc_t){ - .entity = ecs_entity(world, { .name = "MetersPerSecond" }), - .quantity = EcsSpeed, - .base = EcsMeters, - .over = EcsSeconds }); - ecs_primitive_init(world, &(ecs_primitive_desc_t){ - .entity = EcsMetersPerSecond, - .kind = EcsF32 - }); - EcsKiloMetersPerSecond = ecs_unit_init(world, &(ecs_unit_desc_t){ - .entity = ecs_entity(world, { .name = "KiloMetersPerSecond" }), - .quantity = EcsSpeed, - .base = EcsKiloMeters, - .over = EcsSeconds }); - ecs_primitive_init(world, &(ecs_primitive_desc_t){ - .entity = EcsKiloMetersPerSecond, - .kind = EcsF32 - }); - EcsKiloMetersPerHour = ecs_unit_init(world, &(ecs_unit_desc_t){ - .entity = ecs_entity(world, { .name = "KiloMetersPerHour" }), - .quantity = EcsSpeed, - .base = EcsKiloMeters, - .over = EcsHours }); - ecs_primitive_init(world, &(ecs_primitive_desc_t){ - .entity = EcsKiloMetersPerHour, - .kind = EcsF32 - }); - EcsMilesPerHour = ecs_unit_init(world, &(ecs_unit_desc_t){ - .entity = ecs_entity(world, { .name = "MilesPerHour" }), - .quantity = EcsSpeed, - .base = EcsMiles, - .over = EcsHours }); - ecs_primitive_init(world, &(ecs_primitive_desc_t){ - .entity = EcsMilesPerHour, - .kind = EcsF32 - }); - ecs_set_scope(world, prev_scope); - - /* Acceleration */ + fputs(indent, stream); + } + } - EcsAcceleration = ecs_unit_init(world, &(ecs_unit_desc_t){ - .entity = ecs_entity(world, { .name = "Acceleration" }), - .base = EcsMetersPerSecond, - .over = EcsSeconds }); - ecs_quantity_init(world, &(ecs_entity_desc_t){ - .id = EcsAcceleration - }); - ecs_primitive_init(world, &(ecs_primitive_desc_t){ - .entity = EcsAcceleration, - .kind = EcsF32 - }); + if (level < 0) { + if (file) { + const char *file_ptr = strrchr(file, '/'); + if (!file_ptr) { + file_ptr = strrchr(file, '\\'); + } - /* Temperature units */ + if (file_ptr) { + file = file_ptr + 1; + } - EcsTemperature = ecs_quantity_init(world, &(ecs_entity_desc_t){ - .name = "Temperature" }); - prev_scope = ecs_set_scope(world, EcsTemperature); - EcsKelvin = ecs_unit_init(world, &(ecs_unit_desc_t){ - .entity = ecs_entity(world, { .name = "Kelvin" }), - .quantity = EcsTemperature, - .symbol = "K" }); - ecs_primitive_init(world, &(ecs_primitive_desc_t){ - .entity = EcsKelvin, - .kind = EcsF32 - }); - EcsCelsius = ecs_unit_init(world, &(ecs_unit_desc_t){ - .entity = ecs_entity(world, { .name = "Celsius" }), - .quantity = EcsTemperature, - .symbol = "°C" }); - ecs_primitive_init(world, &(ecs_primitive_desc_t){ - .entity = EcsCelsius, - .kind = EcsF32 - }); - EcsFahrenheit = ecs_unit_init(world, &(ecs_unit_desc_t){ - .entity = ecs_entity(world, { .name = "Fahrenheit" }), - .quantity = EcsTemperature, - .symbol = "F" }); - ecs_primitive_init(world, &(ecs_primitive_desc_t){ - .entity = EcsFahrenheit, - .kind = EcsF32 - }); - ecs_set_scope(world, prev_scope); + fputs(file, stream); + fputs(": ", stream); + } - /* Data units */ + if (line) { + fprintf(stream, "%d: ", line); + } + } - EcsData = ecs_quantity_init(world, &(ecs_entity_desc_t){ - .name = "Data" }); - prev_scope = ecs_set_scope(world, EcsData); + fputs(msg, stream); - EcsBits = ecs_unit_init(world, &(ecs_unit_desc_t){ - .entity = ecs_entity(world, { .name = "Bits" }), - .quantity = EcsData, - .symbol = "bit" }); - ecs_primitive_init(world, &(ecs_primitive_desc_t){ - .entity = EcsBits, - .kind = EcsU64 - }); + fputs("\n", stream); - EcsKiloBits = ecs_unit_init(world, &(ecs_unit_desc_t){ - .entity = ecs_entity(world, { .name = "KiloBits" }), - .quantity = EcsData, - .base = EcsBits, - .prefix = EcsKilo }); - ecs_primitive_init(world, &(ecs_primitive_desc_t){ - .entity = EcsKiloBits, - .kind = EcsU64 - }); + if (level == -4) { + dump_backtrace(stream); + } +} + +void ecs_os_dbg( + const char *file, + int32_t line, + const char *msg) +{ + if (ecs_os_api.log_) { + ecs_os_api.log_(1, file, line, msg); + } +} + +void ecs_os_trace( + const char *file, + int32_t line, + const char *msg) +{ + if (ecs_os_api.log_) { + ecs_os_api.log_(0, file, line, msg); + } +} + +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); + } +} - EcsMegaBits = ecs_unit_init(world, &(ecs_unit_desc_t){ - .entity = ecs_entity(world, { .name = "MegaBits" }), - .quantity = EcsData, - .base = EcsBits, - .prefix = EcsMega }); - ecs_primitive_init(world, &(ecs_primitive_desc_t){ - .entity = EcsMegaBits, - .kind = EcsU64 - }); +void ecs_os_err( + const char *file, + int32_t line, + const char *msg) +{ + if (ecs_os_api.log_) { + ecs_os_api.log_(-3, file, line, msg); + } +} - EcsGigaBits = ecs_unit_init(world, &(ecs_unit_desc_t){ - .entity = ecs_entity(world, { .name = "GigaBits" }), - .quantity = EcsData, - .base = EcsBits, - .prefix = EcsGiga }); - ecs_primitive_init(world, &(ecs_primitive_desc_t){ - .entity = EcsGigaBits, - .kind = EcsU64 - }); +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); + } +} - EcsBytes = ecs_unit_init(world, &(ecs_unit_desc_t){ - .entity = ecs_entity(world, { .name = "Bytes" }), - .quantity = EcsData, - .symbol = "B", - .base = EcsBits, - .translation = { .factor = 8, .power = 1 } }); - ecs_primitive_init(world, &(ecs_primitive_desc_t){ - .entity = EcsBytes, - .kind = EcsU64 - }); +static +void ecs_os_gettime(ecs_time_t *time) { + ecs_assert(ecs_os_has_time() == true, ECS_MISSING_OS_API, NULL); + + uint64_t now = ecs_os_now(); + uint64_t sec = now / 1000000000; - EcsKiloBytes = ecs_unit_init(world, &(ecs_unit_desc_t){ - .entity = ecs_entity(world, { .name = "KiloBytes" }), - .quantity = EcsData, - .base = EcsBytes, - .prefix = EcsKilo }); - ecs_primitive_init(world, &(ecs_primitive_desc_t){ - .entity = EcsKiloBytes, - .kind = EcsU64 - }); + assert(sec < UINT32_MAX); + assert((now - sec * 1000000000) < UINT32_MAX); - EcsMegaBytes = ecs_unit_init(world, &(ecs_unit_desc_t){ - .entity = ecs_entity(world, { .name = "MegaBytes" }), - .quantity = EcsData, - .base = EcsBytes, - .prefix = EcsMega }); - ecs_primitive_init(world, &(ecs_primitive_desc_t){ - .entity = EcsMegaBytes, - .kind = EcsU64 - }); + time->sec = (uint32_t)sec; + time->nanosec = (uint32_t)(now - sec * 1000000000); +} - EcsGigaBytes = ecs_unit_init(world, &(ecs_unit_desc_t){ - .entity = ecs_entity(world, { .name = "GigaBytes" }), - .quantity = EcsData, - .base = EcsBytes, - .prefix = EcsGiga }); - ecs_primitive_init(world, &(ecs_primitive_desc_t){ - .entity = EcsGigaBytes, - .kind = EcsU64 - }); +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); +} - EcsKibiBytes = ecs_unit_init(world, &(ecs_unit_desc_t){ - .entity = ecs_entity(world, { .name = "KibiBytes" }), - .quantity = EcsData, - .base = EcsBytes, - .prefix = EcsKibi }); - ecs_primitive_init(world, &(ecs_primitive_desc_t){ - .entity = EcsKibiBytes, - .kind = EcsU64 - }); +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); +} - EcsMebiBytes = ecs_unit_init(world, &(ecs_unit_desc_t){ - .entity = ecs_entity(world, { .name = "MebiBytes" }), - .quantity = EcsData, - .base = EcsBytes, - .prefix = EcsMebi }); - ecs_primitive_init(world, &(ecs_primitive_desc_t){ - .entity = EcsMebiBytes, - .kind = EcsU64 - }); +static +void* ecs_os_api_realloc(void *ptr, ecs_size_t size) { + ecs_assert(size > 0, ECS_INVALID_PARAMETER, NULL); - EcsGibiBytes = ecs_unit_init(world, &(ecs_unit_desc_t){ - .entity = ecs_entity(world, { .name = "GibiBytes" }), - .quantity = EcsData, - .base = EcsBytes, - .prefix = EcsGibi }); - ecs_primitive_init(world, &(ecs_primitive_desc_t){ - .entity = EcsGibiBytes, - .kind = EcsU64 - }); + 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); +} - ecs_set_scope(world, prev_scope); +static +void ecs_os_api_free(void *ptr) { + if (ptr) { + ecs_os_api_free_count ++; + } + free(ptr); +} - /* DataRate units */ +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 { + return NULL; + } +} - EcsDataRate = ecs_quantity_init(world, &(ecs_entity_desc_t){ - .name = "DataRate" }); - prev_scope = ecs_set_scope(world, EcsDataRate); +/* 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; + } + } - EcsBitsPerSecond = ecs_unit_init(world, &(ecs_unit_desc_t){ - .entity = ecs_entity(world, { .name = "BitsPerSecond" }), - .quantity = EcsDataRate, - .base = EcsBits, - .over = EcsSeconds }); - ecs_primitive_init(world, &(ecs_primitive_desc_t){ - .entity = EcsBitsPerSecond, - .kind = EcsU64 - }); + return base; +} - EcsKiloBitsPerSecond = ecs_unit_init(world, &(ecs_unit_desc_t){ - .entity = ecs_entity(world, { .name = "KiloBitsPerSecond" }), - .quantity = EcsDataRate, - .base = EcsKiloBits, - .over = EcsSeconds - }); - ecs_primitive_init(world, &(ecs_primitive_desc_t){ - .entity = EcsKiloBitsPerSecond, - .kind = EcsU64 - }); +static +char* ecs_os_api_module_to_dl(const char *module) { + ecs_strbuf_t lib = ECS_STRBUF_INIT; - EcsMegaBitsPerSecond = ecs_unit_init(world, &(ecs_unit_desc_t){ - .entity = ecs_entity(world, { .name = "MegaBitsPerSecond" }), - .quantity = EcsDataRate, - .base = EcsMegaBits, - .over = EcsSeconds - }); - ecs_primitive_init(world, &(ecs_primitive_desc_t){ - .entity = EcsMegaBitsPerSecond, - .kind = EcsU64 - }); + /* Best guess, use module name with underscores + OS library extension */ + char *file_base = module_file_base(module, '_'); - EcsGigaBitsPerSecond = ecs_unit_init(world, &(ecs_unit_desc_t){ - .entity = ecs_entity(world, { .name = "GigaBitsPerSecond" }), - .quantity = EcsDataRate, - .base = EcsGigaBits, - .over = EcsSeconds - }); - ecs_primitive_init(world, &(ecs_primitive_desc_t){ - .entity = EcsGigaBitsPerSecond, - .kind = EcsU64 - }); +# if defined(ECS_TARGET_LINUX) || defined(ECS_TARGET_FREEBSD) + ecs_strbuf_appendstr(&lib, "lib"); + ecs_strbuf_appendstr(&lib, file_base); + ecs_strbuf_appendstr(&lib, ".so"); +# elif defined(ECS_TARGET_DARWIN) + ecs_strbuf_appendstr(&lib, "lib"); + ecs_strbuf_appendstr(&lib, file_base); + ecs_strbuf_appendstr(&lib, ".dylib"); +# elif defined(ECS_TARGET_WINDOWS) + ecs_strbuf_appendstr(&lib, file_base); + ecs_strbuf_appendstr(&lib, ".dll"); +# endif - EcsBytesPerSecond = ecs_unit_init(world, &(ecs_unit_desc_t){ - .entity = ecs_entity(world, { .name = "BytesPerSecond" }), - .quantity = EcsDataRate, - .base = EcsBytes, - .over = EcsSeconds }); - ecs_primitive_init(world, &(ecs_primitive_desc_t){ - .entity = EcsBytesPerSecond, - .kind = EcsU64 - }); + ecs_os_free(file_base); - EcsKiloBytesPerSecond = ecs_unit_init(world, &(ecs_unit_desc_t){ - .entity = ecs_entity(world, { .name = "KiloBytesPerSecond" }), - .quantity = EcsDataRate, - .base = EcsKiloBytes, - .over = EcsSeconds - }); - ecs_primitive_init(world, &(ecs_primitive_desc_t){ - .entity = EcsKiloBytesPerSecond, - .kind = EcsU64 - }); + return ecs_strbuf_get(&lib); +} - EcsMegaBytesPerSecond = ecs_unit_init(world, &(ecs_unit_desc_t){ - .entity = ecs_entity(world, { .name = "MegaBytesPerSecond" }), - .quantity = EcsDataRate, - .base = EcsMegaBytes, - .over = EcsSeconds - }); - ecs_primitive_init(world, &(ecs_primitive_desc_t){ - .entity = EcsMegaBytesPerSecond, - .kind = EcsU64 - }); +static +char* ecs_os_api_module_to_etc(const char *module) { + ecs_strbuf_t lib = ECS_STRBUF_INIT; - EcsGigaBytesPerSecond = ecs_unit_init(world, &(ecs_unit_desc_t){ - .entity = ecs_entity(world, { .name = "GigaBytesPerSecond" }), - .quantity = EcsDataRate, - .base = EcsGigaBytes, - .over = EcsSeconds - }); - ecs_primitive_init(world, &(ecs_primitive_desc_t){ - .entity = EcsGigaBytesPerSecond, - .kind = EcsU64 - }); + /* Best guess, use module name with dashes + /etc */ + char *file_base = module_file_base(module, '-'); - ecs_set_scope(world, prev_scope); + ecs_strbuf_appendstr(&lib, file_base); + ecs_strbuf_appendstr(&lib, "/etc"); - /* Percentage */ + ecs_os_free(file_base); - EcsPercentage = ecs_quantity_init(world, &(ecs_entity_desc_t){ - .name = "Percentage" }); - ecs_unit_init(world, &(ecs_unit_desc_t){ - .entity = EcsPercentage, - .symbol = "%" - }); - ecs_primitive_init(world, &(ecs_primitive_desc_t){ - .entity = EcsPercentage, - .kind = EcsF32 - }); + return ecs_strbuf_get(&lib); +} - /* Angles */ +void ecs_os_set_api_defaults(void) +{ + /* Don't overwrite if already initialized */ + if (ecs_os_api_initialized != 0) { + return; + } - EcsAngle = ecs_quantity_init(world, &(ecs_entity_desc_t){ - .name = "Angle" }); - prev_scope = ecs_set_scope(world, EcsAngle); - EcsRadians = ecs_unit_init(world, &(ecs_unit_desc_t){ - .entity = ecs_entity(world, { .name = "Radians" }), - .quantity = EcsAngle, - .symbol = "rad" }); - ecs_primitive_init(world, &(ecs_primitive_desc_t){ - .entity = EcsRadians, - .kind = EcsF32 - }); + if (ecs_os_api_initializing != 0) { + return; + } - EcsDegrees = ecs_unit_init(world, &(ecs_unit_desc_t){ - .entity = ecs_entity(world, { .name = "Degrees" }), - .quantity = EcsAngle, - .symbol = "°" }); - ecs_primitive_init(world, &(ecs_primitive_desc_t){ - .entity = EcsDegrees, - .kind = EcsF32 - }); - ecs_set_scope(world, prev_scope); + ecs_os_api_initializing = true; + + /* 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; - /* DeciBel */ + /* Strings */ + ecs_os_api.strdup_ = ecs_os_api_strdup; - EcsBel = ecs_unit_init(world, &(ecs_unit_desc_t){ - .entity = ecs_entity(world, { .name = "Bel" }), - .symbol = "B" }); - ecs_primitive_init(world, &(ecs_primitive_desc_t){ - .entity = EcsBel, - .kind = EcsF32 - }); - EcsDeciBel = ecs_unit_init(world, &(ecs_unit_desc_t){ - .entity = ecs_entity(world, { .name = "DeciBel" }), - .prefix = EcsDeci, - .base = EcsBel }); - ecs_primitive_init(world, &(ecs_primitive_desc_t){ - .entity = EcsDeciBel, - .kind = EcsF32 - }); + /* Time */ + ecs_os_api.get_time_ = ecs_os_gettime; - /* Documentation */ -#ifdef FLECS_DOC - ECS_IMPORT(world, FlecsDoc); + /* Logging */ + ecs_os_api.log_ = log_msg; - ecs_doc_set_brief(world, EcsDuration, - "Time amount (e.g. \"20 seconds\", \"2 hours\")"); - ecs_doc_set_brief(world, EcsSeconds, "Time amount in seconds"); - ecs_doc_set_brief(world, EcsMinutes, "60 seconds"); - ecs_doc_set_brief(world, EcsHours, "60 minutes"); - ecs_doc_set_brief(world, EcsDays, "24 hours"); + /* Modules */ + if (!ecs_os_api.module_to_dl_) { + ecs_os_api.module_to_dl_ = ecs_os_api_module_to_dl; + } - ecs_doc_set_brief(world, EcsTime, - "Time passed since an epoch (e.g. \"5pm\", \"March 3rd 2022\")"); - ecs_doc_set_brief(world, EcsDate, - "Seconds passed since January 1st 1970"); + if (!ecs_os_api.module_to_etc_) { + ecs_os_api.module_to_etc_ = ecs_os_api_module_to_etc; + } - ecs_doc_set_brief(world, EcsMass, "Units of mass (e.g. \"5 kilograms\")"); + ecs_os_api.abort_ = abort; - ecs_doc_set_brief(world, EcsElectricCurrent, - "Units of electrical current (e.g. \"2 ampere\")"); +# ifdef FLECS_OS_API_IMPL + /* Initialize defaults to OS API IMPL addon, but still allow for overriding + * by the application */ + ecs_set_os_api_impl(); + ecs_os_api_initialized = false; +# endif - ecs_doc_set_brief(world, EcsAmount, - "Units of amount of substance (e.g. \"2 mole\")"); + ecs_os_api_initializing = false; +} - ecs_doc_set_brief(world, EcsLuminousIntensity, - "Units of luminous intensity (e.g. \"1 candela\")"); +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_doc_set_brief(world, EcsForce, "Units of force (e.g. \"10 newton\")"); +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_doc_set_brief(world, EcsLength, - "Units of length (e.g. \"5 meters\", \"20 miles\")"); +bool ecs_os_has_time(void) { + return + (ecs_os_api.get_time_ != NULL) && + (ecs_os_api.sleep_ != NULL) && + (ecs_os_api.now_ != NULL); +} - ecs_doc_set_brief(world, EcsPressure, - "Units of pressure (e.g. \"1 bar\", \"1000 pascal\")"); +bool ecs_os_has_logging(void) { + return (ecs_os_api.log_ != NULL); +} - ecs_doc_set_brief(world, EcsSpeed, - "Units of movement (e.g. \"5 meters/second\")"); +bool ecs_os_has_dl(void) { + return + (ecs_os_api.dlopen_ != NULL) && + (ecs_os_api.dlproc_ != NULL) && + (ecs_os_api.dlclose_ != NULL); +} - ecs_doc_set_brief(world, EcsAcceleration, - "Unit of speed increase (e.g. \"5 meters/second/second\")"); +bool ecs_os_has_modules(void) { + return + (ecs_os_api.module_to_dl_ != NULL) && + (ecs_os_api.module_to_etc_ != NULL); +} - ecs_doc_set_brief(world, EcsTemperature, - "Units of temperature (e.g. \"5 degrees Celsius\")"); +#if defined(ECS_TARGET_WINDOWS) +static char error_str[255]; +#endif - ecs_doc_set_brief(world, EcsData, - "Units of information (e.g. \"8 bits\", \"100 megabytes\")"); +const char* ecs_os_strerror(int err) { +# if defined(ECS_TARGET_WINDOWS) + strerror_s(error_str, 255, err); + return error_str; +# else + return strerror(err); +# endif +} - ecs_doc_set_brief(world, EcsDataRate, - "Units of data transmission (e.g. \"100 megabits/second\")"); - ecs_doc_set_brief(world, EcsAngle, - "Units of rotation (e.g. \"1.2 radians\", \"180 degrees\")"); +static +void flecs_query_compute_group_id( + ecs_query_t *query, + ecs_query_table_match_t *match) +{ + ecs_assert(match != NULL, ECS_INTERNAL_ERROR, NULL); -#endif + if (query->group_by) { + ecs_table_t *table = match->table; + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + + match->group_id = query->group_by(query->world, table, + query->group_by_id, query->group_by_ctx); + } else { + match->group_id = 0; + } } -#endif +static +ecs_query_table_list_t* flecs_query_get_group( + ecs_query_t *query, + uint64_t group_id) +{ + return ecs_map_get(&query->groups, ecs_query_table_list_t, group_id); +} +static +ecs_query_table_list_t* flecs_query_ensure_group( + ecs_query_t *query, + uint64_t group_id) +{ + return ecs_map_ensure(&query->groups, ecs_query_table_list_t, group_id); +} -#ifdef FLECS_SNAPSHOT +/* Find the last node of the group after which this group should be inserted */ +static +ecs_query_table_node_t* flecs_query_find_group_insertion_node( + ecs_query_t *query, + uint64_t group_id) +{ + /* Grouping must be enabled */ + ecs_assert(query->group_by != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_map_iter_t it = ecs_map_iter(&query->groups); + ecs_query_table_list_t *list, *closest_list = NULL; + uint64_t id, closest_id = 0; -/* 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; -}; + /* Find closest smaller group id */ + while ((list = ecs_map_next(&it, ecs_query_table_list_t, &id))) { + if (id >= group_id) { + continue; + } -/** Small footprint data structure for storing data associated with a table. */ -typedef struct ecs_table_leaf_t { - ecs_table_t *table; - ecs_type_t type; - ecs_data_t *data; -} ecs_table_leaf_t; + if (!list->last) { + ecs_assert(list->first == NULL, ECS_INTERNAL_ERROR, NULL); + continue; + } -static -ecs_data_t* duplicate_data( - ecs_table_t *table, - ecs_data_t *main_data) -{ - if (!ecs_table_count(table)) { - return NULL; + if (!closest_list || ((group_id - id) < (group_id - closest_id))) { + closest_id = id; + closest_list = list; + } } - ecs_data_t *result = ecs_os_calloc(ECS_SIZEOF(ecs_data_t)); - int32_t i, column_count = table->storage_count; - result->columns = ecs_os_memdup_n( - main_data->columns, ecs_column_t, column_count); + if (closest_list) { + return closest_list->last; + } else { + return NULL; /* Group should be first in query */ + } +} - /* Copy entities and records */ - result->entities = ecs_storage_copy_t(&main_data->entities, ecs_entity_t); - result->records = ecs_storage_copy_t(&main_data->records, ecs_record_t*); +/* Initialize group with first node */ +static +void flecs_query_create_group( + ecs_query_t *query, + ecs_query_table_node_t *node) +{ + ecs_query_table_match_t *match = node->match; + uint64_t group_id = match->group_id; - /* Copy each column */ - for (i = 0; i < column_count; i ++) { - ecs_column_t *column = &result->columns[i]; - ecs_type_info_t *ti = table->type_info[i]; - int32_t size = ti->size; - ecs_copy_t copy = ti->hooks.copy; - if (copy) { - ecs_column_t dst = ecs_storage_copy(column, size); - int32_t count = ecs_storage_count(column); - void *dst_ptr = ecs_storage_first(&dst); - void *src_ptr = ecs_storage_first(column); + /* 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 = flecs_query_find_group_insertion_node( + query, group_id); - ecs_xtor_t ctor = ti->hooks.ctor; - if (ctor) { - ctor(dst_ptr, count, ti); - } + 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); - copy(dst_ptr, src_ptr, count, ti); - *column = dst; + /* 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 { - *column = ecs_storage_copy(column, size); + 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 result; +static +void flecs_query_remove_group( + ecs_query_t *query, + uint64_t group_id) +{ + ecs_map_remove(&query->groups, group_id); } +/* Find the list the node should be part of */ static -void snapshot_table( - ecs_snapshot_t *snapshot, - ecs_table_t *table) +ecs_query_table_list_t* flecs_query_get_node_list( + ecs_query_t *query, + ecs_query_table_node_t *node) { - if (table->flags & EcsTableHasBuiltins) { - return; + ecs_query_table_match_t *match = node->match; + if (query->group_by) { + return flecs_query_get_group(query, match->group_id); + } else { + return &query->list; } - - 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 = flecs_type_copy(&table->type); - l->data = duplicate_data(table, &table->data); } +/* Find or create the list the node should be part of */ 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_query_table_list_t* flecs_query_ensure_node_list( + ecs_query_t *query, + ecs_query_table_node_t *node) { - ecs_snapshot_t *result = ecs_os_calloc_t(ecs_snapshot_t); - ecs_assert(result != NULL, ECS_OUT_OF_MEMORY, NULL); + ecs_query_table_match_t *match = node->match; + if (query->group_by) { + return flecs_query_ensure_group(query, match->group_id); + } else { + return &query->list; + } +} - ecs_run_aperiodic((ecs_world_t*)world, 0); +/* Remove node from list */ +static +void flecs_query_remove_table_node( + ecs_query_t *query, + ecs_query_table_node_t *node) +{ + ecs_query_table_node_t *prev = node->prev; + ecs_query_table_node_t *next = node->next; - result->world = (ecs_world_t*)world; + 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); - /* 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_query_table_list_t *list = flecs_query_get_node_list(query, node); - /* 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); + 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; + } - /* Array may have holes, so initialize with 0 */ - ecs_os_memset_n(arr, 0, ecs_table_leaf_t, table_count); + ecs_assert(prev != NULL || query->list.first == node, + ECS_INTERNAL_ERROR, NULL); + ecs_assert(next != NULL || query->list.last == node, + ECS_INTERNAL_ERROR, NULL); - /* Iterate tables in iterator */ - if (iter) { - while (next(iter)) { - ecs_table_t *table = iter->table; - snapshot_table(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(result, table); - } + if (prev) { + prev->next = next; + } + if (next) { + next->prev = prev; } - return result; -} + ecs_assert(list->count > 0, ECS_INTERNAL_ERROR, NULL); + list->count --; -/** Create a snapshot */ -ecs_snapshot_t* ecs_snapshot_take( - ecs_world_t *stage) -{ - const ecs_world_t *world = ecs_get_world(stage); + if (query->group_by) { + ecs_query_table_match_t *match = node->match; + uint64_t group_id = match->group_id; - ecs_snapshot_t *result = snapshot_create( - world, ecs_eis(world), NULL, NULL); + /* 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; + } - result->last_id = world->info.last_id; + ecs_assert(query->list.count > 0, ECS_INTERNAL_ERROR, NULL); + query->list.count --; - return result; -} + /* 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; + } -/** Create a filtered snapshot */ -ecs_snapshot_t* ecs_snapshot_take_w_iter( - ecs_iter_t *iter) -{ - ecs_world_t *world = iter->world; - ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); + /* 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 */ + flecs_query_remove_group(query, group_id); + list = NULL; + } + } - ecs_snapshot_t *result = snapshot_create( - world, ecs_eis(world), iter, iter ? iter->next : NULL); + if (list) { + if (list->first == node) { + list->first = next; + } + if (list->last == node) { + list->last = prev; + } + } - result->last_id = world->info.last_id; + node->prev = NULL; + node->next = NULL; - return result; + query->match_count ++; } -/* Restoring an unfiltered snapshot restores the world to the exact state it was - * when the snapshot was taken. */ +/* Add node to list */ static -void restore_unfiltered( - ecs_world_t *world, - ecs_snapshot_t *snapshot) +void flecs_query_insert_table_node( + ecs_query_t *query, + ecs_query_table_node_t *node) { - flecs_sparse_restore(ecs_eis(world), snapshot->entity_index); - flecs_sparse_free(snapshot->entity_index); - - world->info.last_id = snapshot->last_id; + /* Node should not be part of an existing list */ + ecs_assert(node->prev == NULL && node->next == 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); + /* If this is the first match, activate system */ + if (!query->list.first && query->entity) { + ecs_remove_id(query->world, query->entity, EcsEmpty); + } - for (i = 0; i <= count; i ++) { - ecs_table_t *world_table = flecs_sparse_get( - &world->store.tables, ecs_table_t, (uint32_t)i); + flecs_query_compute_group_id(query, node->match); - if (world_table && (world_table->flags & EcsTableHasBuiltins)) { - continue; - } + ecs_query_table_list_t *list = flecs_query_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); - ecs_table_leaf_t *snapshot_table = NULL; - if (i < snapshot_count) { - snapshot_table = &leafs[i]; - if (!snapshot_table->table) { - snapshot_table = NULL; - } + ecs_query_table_node_t *last = list->last; + ecs_query_table_node_t *last_next = last->next; + + node->prev = last; + node->next = last_next; + last->next = node; + + if (last_next) { + last_next->prev = node; } - /* If the world table no longer exists but the snapshot table does, - * reinsert it */ - if (!world_table && snapshot_table) { - ecs_table_t *table = flecs_table_find_or_create(world, - &snapshot_table->type); - ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + list->last = node; - if (snapshot_table->data) { - flecs_table_replace_data(world, table, snapshot_table->data); + 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; } - - /* If the world table still exists, replace its data */ - } else if (world_table && snapshot_table) { - ecs_assert(snapshot_table->table == world_table, - ECS_INTERNAL_ERROR, NULL); + } + } else { + ecs_assert(list->first == NULL, ECS_INTERNAL_ERROR, NULL); - if (snapshot_table->data) { - flecs_table_replace_data( - world, world_table, snapshot_table->data); - } else { - flecs_table_clear_data( - world, world_table, &world_table->data); - flecs_table_init_data(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->data); - flecs_delete_table(world, world_table); - - /* If there is no world & snapshot table, nothing needs to be done */ - } else { } + list->first = node; + list->last = node; - if (snapshot_table) { - ecs_os_free(snapshot_table->data); - flecs_type_free(&snapshot_table->type); + if (query->group_by) { + /* Initialize group with its first node */ + flecs_query_create_group(query, node); } } - /* 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; - } + if (query->group_by) { + query->list.count ++; + } - int32_t tcount = ecs_table_count(table); - if (tcount) { - flecs_notify_on_set(world, table, 0, tcount, NULL, true); + list->count ++; + query->match_count ++; + + 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); +} + +static +ecs_query_table_match_t* flecs_query_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; + + 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; +} + +typedef struct { + ecs_table_t *table; + int32_t *dirty_state; + int32_t column; +} table_dirty_state_t; + +static +void flecs_query_get_dirty_state( + ecs_query_t *query, + ecs_query_table_match_t *match, + int32_t term, + table_dirty_state_t *out) +{ + ecs_world_t *world = query->world; + ecs_entity_t subject = match->sources[term]; + int32_t column; + + if (!subject) { + out->table = match->table; + column = match->columns[term]; + if (column == -1) { + column = 0; } + } else { + out->table = ecs_get_table(world, subject); + column = -match->columns[term]; + } + + out->dirty_state = flecs_table_get_dirty_state(out->table); + + if (column) { + out->column = ecs_table_type_to_storage_index(out->table, column - 1); + } else { + out->column = -1; } } -/* Restoring a filtered snapshots only restores the entities in the snapshot - * to their previous state. */ +/* Get match monitor. Monitors are used to keep track of whether components + * matched by the query in a table have changed. */ static -void restore_filtered( - ecs_world_t *world, - ecs_snapshot_t *snapshot) +bool flecs_query_get_match_monitor( + ecs_query_t *query, + ecs_query_table_match_t *match) { - 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 (match->monitor) { + return false; + } - for (l = 0; l < snapshot_count; l ++) { - ecs_table_leaf_t *snapshot_table = &leafs[l]; - ecs_table_t *table = snapshot_table->table; + int32_t *monitor = ecs_os_calloc_n(int32_t, query->filter.term_count + 1); - if (!table) { - continue; + /* Mark terms that don't need to be monitored. This saves time when reading + * and/or updating the monitor. */ + const ecs_filter_t *f = &query->filter; + int32_t i, t = -1, term_count = f->term_count; + table_dirty_state_t cur_dirty_state; + + for (i = 0; i < term_count; i ++) { + if (t == f->terms[i].field_index) { + if (monitor[t + 1] != -1) { + continue; + } } - ecs_data_t *data = snapshot_table->data; - if (!data) { - flecs_type_free(&snapshot_table->type); - continue; + t = f->terms[i].field_index; + monitor[t + 1] = -1; + + if (f->terms[i].inout != EcsIn && + f->terms[i].inout != EcsInOut && + f->terms[i].inout != EcsInOutDefault) { + continue; /* If term isn't read, don't monitor */ } - /* 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_storage_count(&data->entities); - ecs_entity_t *entities = ecs_storage_first( - &snapshot_table->data->entities); - for (i = 0; i < entity_count; i ++) { - ecs_entity_t e = entities[i]; - ecs_record_t *r = flecs_entities_get(world, e); - if (r && r->table) { - flecs_table_delete(world, r->table, - ECS_RECORD_TO_ROW(r->row), true); - } else { - /* Make sure that the entity has the same generation count */ - flecs_entities_set_generation(world, e); - } + int32_t column = match->columns[t]; + if (column == 0) { + continue; /* Don't track terms that aren't matched */ } - /* 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_query_get_dirty_state(query, match, t, &cur_dirty_state); + if (cur_dirty_state.column == -1) { + continue; /* Don't track terms that aren't stored */ + } - flecs_table_merge(world, table, table, &table->data, snapshot_table->data); + monitor[t + 1] = 0; + } - /* Run OnSet systems for merged entities */ - if (new_count) { - flecs_notify_on_set( - world, table, old_count, new_count, NULL, true); + match->monitor = monitor; + + query->flags |= EcsQueryHasMonitor; + + return true; +} + +/* Synchronize match monitor with table dirty state */ +static +void flecs_query_sync_match_monitor( + ecs_query_t *query, + ecs_query_table_match_t *match) +{ + ecs_assert(match != NULL, ECS_INTERNAL_ERROR, NULL); + if (!match->monitor) { + if (query->flags & EcsQueryHasMonitor) { + flecs_query_get_match_monitor(query, match); + } else { + return; } + } - ecs_os_free(snapshot_table->data->columns); - ecs_os_free(snapshot_table->data); - flecs_type_free(&snapshot_table->type); + int32_t *monitor = match->monitor; + ecs_table_t *table = match->table; + int32_t *dirty_state = flecs_table_get_dirty_state(table); + ecs_assert(dirty_state != NULL, ECS_INTERNAL_ERROR, NULL); + table_dirty_state_t cur; + + monitor[0] = dirty_state[0]; /* Did table gain/lose entities */ + + int32_t i, term_count = query->filter.term_count; + for (i = 0; i < term_count; i ++) { + int32_t t = query->filter.terms[i].field_index; + if (monitor[t + 1] == -1) { + continue; + } + + flecs_query_get_dirty_state(query, match, t, &cur); + ecs_assert(cur.column != -1, ECS_INTERNAL_ERROR, NULL); + monitor[t + 1] = cur.dirty_state[cur.column + 1]; } } -/** Restore a snapshot */ -void ecs_snapshot_restore( - ecs_world_t *world, - ecs_snapshot_t *snapshot) +/* Check if single match term has changed */ +static +bool flecs_query_check_match_monitor_term( + ecs_query_t *query, + ecs_query_table_match_t *match, + int32_t term) { - ecs_run_aperiodic(world, 0); + ecs_assert(match != NULL, ECS_INTERNAL_ERROR, NULL); + + if (flecs_query_get_match_monitor(query, match)) { + return true; + } - 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); + int32_t *monitor = match->monitor; + ecs_table_t *table = match->table; + int32_t *dirty_state = flecs_table_get_dirty_state(table); + ecs_assert(dirty_state != NULL, ECS_INTERNAL_ERROR, NULL); + table_dirty_state_t cur; + + int32_t state = monitor[term]; + if (state == -1) { + return false; } - ecs_vector_free(snapshot->tables); + if (!term) { + return monitor[0] != dirty_state[0]; + } - ecs_os_free(snapshot); + flecs_query_get_dirty_state(query, match, term - 1, &cur); + ecs_assert(cur.column != -1, ECS_INTERNAL_ERROR, NULL); + + return monitor[term] != cur.dirty_state[cur.column + 1]; } -ecs_iter_t ecs_snapshot_iter( - ecs_snapshot_t *snapshot) +/* Check if any term for match has changed */ +static +bool flecs_query_check_match_monitor( + ecs_query_t *query, + ecs_query_table_match_t *match) { - ecs_snapshot_iter_t iter = { - .tables = snapshot->tables, - .index = 0 - }; + ecs_assert(match != NULL, ECS_INTERNAL_ERROR, NULL); - return (ecs_iter_t){ - .world = snapshot->world, - .table_count = ecs_vector_count(snapshot->tables), - .priv.iter.snapshot = iter, - .next = ecs_snapshot_next - }; -} + if (flecs_query_get_match_monitor(query, match)) { + return true; + } -bool ecs_snapshot_next( - ecs_iter_t *it) -{ - ecs_snapshot_iter_t *iter = &it->priv.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; + int32_t *monitor = match->monitor; + ecs_table_t *table = match->table; + int32_t *dirty_state = flecs_table_get_dirty_state(table); + ecs_assert(dirty_state != NULL, ECS_INTERNAL_ERROR, NULL); + table_dirty_state_t cur; - for (i = iter->index; i < count; i ++) { - ecs_table_t *table = tables[i].table; - if (!table) { + if (monitor[0] != dirty_state[0]) { + return true; + } + + ecs_filter_t *f = &query->filter; + int32_t i, term_count = f->term_count; + for (i = 0; i < term_count; i ++) { + ecs_term_t *term = &f->terms[i]; + int32_t t = term->field_index; + if (monitor[t + 1] == -1) { continue; } - ecs_data_t *data = tables[i].data; + flecs_query_get_dirty_state(query, match, t, &cur); + ecs_assert(cur.column != -1, ECS_INTERNAL_ERROR, NULL); - it->table = table; - it->count = ecs_table_count(table); - if (data) { - it->entities = ecs_storage_first(&data->entities); - } else { - it->entities = NULL; + if (monitor[t + 1] != cur.dirty_state[cur.column + 1]) { + return true; } - - ECS_BIT_SET(it->flags, EcsIterIsValid); - iter->index = i + 1; - - goto yield; } - ECS_BIT_CLEAR(it->flags, EcsIterIsValid); return false; - -yield: - ECS_BIT_CLEAR(it->flags, EcsIterIsValid); - return true; } -/** Cleanup snapshot */ -void ecs_snapshot_free( - ecs_snapshot_t *snapshot) +/* Check if any term for matched table has changed */ +static +bool flecs_query_check_table_monitor( + ecs_query_t *query, + ecs_query_table_t *table, + int32_t term) { - flecs_sparse_free(snapshot->entity_index); + ecs_query_table_node_t *cur, *end = table->last->node.next; - 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); + for (cur = &table->first->node; cur != end; cur = cur->next) { + ecs_query_table_match_t *match = (ecs_query_table_match_t*)cur; + if (term == -1) { + if (flecs_query_check_match_monitor(query, match)) { + return true; } - flecs_type_free(&snapshot_table->type); + } else { + if (flecs_query_check_match_monitor_term(query, match, term)) { + return true; + } } - } + } - ecs_vector_free(snapshot->tables); - ecs_os_free(snapshot); + return false; } -#endif - - - -#ifdef FLECS_DOC - -static ECS_COPY(EcsDocDescription, dst, src, { - ecs_os_strset((char**)&dst->value, src->value); - -}) - -static ECS_MOVE(EcsDocDescription, dst, src, { - ecs_os_free((char*)dst->value); - dst->value = src->value; - src->value = NULL; -}) - -static ECS_DTOR(EcsDocDescription, ptr, { - ecs_os_free((char*)ptr->value); -}) - -void ecs_doc_set_name( - ecs_world_t *world, - ecs_entity_t entity, - const char *name) +static +bool flecs_query_check_query_monitor( + ecs_query_t *query) { - ecs_set_pair(world, entity, EcsDocDescription, EcsName, { - .value = (char*)name - }); -} + ecs_table_cache_iter_t it; + if (flecs_table_cache_iter(&query->cache, &it)) { + ecs_query_table_t *qt; + while ((qt = flecs_table_cache_next(&it, ecs_query_table_t))) { + if (flecs_query_check_table_monitor(query, qt, -1)) { + return true; + } + } + } -void ecs_doc_set_brief( - ecs_world_t *world, - ecs_entity_t entity, - const char *description) -{ - ecs_set_pair(world, entity, EcsDocDescription, EcsDocBrief, { - .value = (char*)description - }); + return false; } -void ecs_doc_set_detail( - ecs_world_t *world, - ecs_entity_t entity, - const char *description) +static +void flecs_query_init_query_monitors( + ecs_query_t *query) { - ecs_set_pair(world, entity, EcsDocDescription, EcsDocDetail, { - .value = (char*)description - }); + ecs_query_table_node_t *cur = query->list.first; + + /* Ensure each match has a monitor */ + for (; cur != NULL; cur = cur->next) { + ecs_query_table_match_t *match = (ecs_query_table_match_t*)cur; + flecs_query_get_match_monitor(query, match); + } } -void ecs_doc_set_link( +/* The group by function for cascade computes the tree depth for the table type. + * This causes tables in the query cache to be ordered by depth, which ensures + * breadth-first iteration order. */ +static +uint64_t flecs_query_group_by_cascade( ecs_world_t *world, - ecs_entity_t entity, - const char *link) + ecs_table_t *table, + ecs_id_t id, + void *ctx) { - ecs_set_pair(world, entity, EcsDocDescription, EcsDocLink, { - .value = (char*)link - }); + (void)id; + ecs_term_t *term = ctx; + ecs_entity_t rel = term->src.trav; + int32_t depth = flecs_relation_depth(world, rel, table); + return flecs_ito(uint64_t, depth); } -void ecs_doc_set_color( +static +ecs_vector_t* flecs_query_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, - const char *color) + ecs_size_t size) { - ecs_set_pair(world, entity, EcsDocDescription, EcsDocColor, { - .value = (char*)color - }); -} + ecs_assert(entity != 0, ECS_INTERNAL_ERROR, NULL); -const char* ecs_doc_get_name( - const ecs_world_t *world, - ecs_entity_t entity) -{ - const EcsDocDescription *ptr = ecs_get_pair( - world, entity, EcsDocDescription, EcsName); - if (ptr) { - return ptr->value; - } else { - return ecs_get_name(world, entity); - } -} + ecs_ref_t *ref = ecs_vector_add(&references, ecs_ref_t); + ecs_term_id_t *src = &term->src; -const char* ecs_doc_get_brief( - const ecs_world_t *world, - ecs_entity_t entity) -{ - const EcsDocDescription *ptr = ecs_get_pair( - world, entity, EcsDocDescription, EcsDocBrief); - if (ptr) { - return ptr->value; - } else { - return NULL; + if (!(src->flags & EcsCascade)) { + ecs_assert(entity != 0, ECS_INTERNAL_ERROR, NULL); } -} - -const char* ecs_doc_get_detail( - const ecs_world_t *world, - ecs_entity_t entity) -{ - const EcsDocDescription *ptr = ecs_get_pair( - world, entity, EcsDocDescription, EcsDocDetail); - if (ptr) { - return ptr->value; + + if (size) { + *ref = ecs_ref_init_id(world, entity, component); } else { - return NULL; + *ref = (ecs_ref_t){ + .entity = entity, + .id = 0 + }; } -} -const char* ecs_doc_get_link( - const ecs_world_t *world, - ecs_entity_t entity) -{ - const EcsDocDescription *ptr = ecs_get_pair( - world, entity, EcsDocDescription, EcsDocLink); - if (ptr) { - return ptr->value; - } else { - return NULL; - } -} + query->flags |= EcsQueryHasRefs; -const char* ecs_doc_get_color( - const ecs_world_t *world, - ecs_entity_t entity) -{ - const EcsDocDescription *ptr = ecs_get_pair( - world, entity, EcsDocDescription, EcsDocColor); - if (ptr) { - return ptr->value; - } else { - return NULL; - } + return references; } -void FlecsDocImport( - ecs_world_t *world) -{ - ECS_MODULE(world, FlecsDoc); - - ecs_set_name_prefix(world, "EcsDoc"); +static +ecs_query_table_match_t* flecs_query_add_table_match( + ecs_query_t *query, + ecs_query_table_t *qt, + ecs_table_t *table) +{ + ecs_filter_t *filter = &query->filter; + int32_t term_count = filter->term_count; - flecs_bootstrap_component(world, EcsDocDescription); - flecs_bootstrap_tag(world, EcsDocBrief); - flecs_bootstrap_tag(world, EcsDocDetail); - flecs_bootstrap_tag(world, EcsDocLink); - flecs_bootstrap_tag(world, EcsDocColor); + /* Add match for table. One table can have more than one match, if + * the query contains wildcards. */ + ecs_query_table_match_t *qm = flecs_query_cache_add(qt); + qm->table = table; + qm->columns = ecs_os_malloc_n(int32_t, term_count); + qm->ids = ecs_os_malloc_n(ecs_id_t, term_count); + qm->sources = ecs_os_malloc_n(ecs_entity_t, term_count); + qm->sizes = ecs_os_malloc_n(ecs_size_t, term_count); - ecs_set_hooks(world, EcsDocDescription, { - .ctor = ecs_default_ctor, - .move = ecs_move(EcsDocDescription), - .copy = ecs_copy(EcsDocDescription), - .dtor = ecs_dtor(EcsDocDescription) - }); + /* Insert match to iteration list if table is not empty */ + if (!table || ecs_table_count(table) != 0) { + flecs_query_insert_table_node(query, &qm->node); + } - ecs_add_id(world, ecs_id(EcsDocDescription), EcsDontInherit); + return qm; } -#endif - - -#ifdef FLECS_PLECS - -#include - -#define TOK_NEWLINE '\n' -#define TOK_WITH "with" -#define TOK_USING "using" - -#define STACK_MAX_SIZE (64) - -typedef struct { - const char *name; - const char *code; +static +void flecs_query_set_table_match( + ecs_world_t *world, + ecs_query_t *query, + ecs_query_table_match_t *qm, + ecs_table_t *table, + ecs_iter_t *it) +{ + ecs_filter_t *filter = &query->filter; + int32_t i, term_count = filter->term_count; + int32_t field_count = filter->field_count; + ecs_term_t *terms = filter->terms; - ecs_entity_t last_predicate; - ecs_entity_t last_subject; - ecs_entity_t last_object; + /* Reset resources in case this is an existing record */ + if (qm->sparse_columns) { + ecs_vector_free(qm->sparse_columns); + qm->sparse_columns = NULL; + } + if (qm->bitset_columns) { + ecs_vector_free(qm->bitset_columns); + qm->bitset_columns = NULL; + } + if (qm->references) { + ecs_os_free(qm->references); + qm->references = NULL; + } - ecs_id_t last_assign_id; - ecs_entity_t assign_to; + ecs_os_memcpy_n(qm->columns, it->columns, int32_t, field_count); + ecs_os_memcpy_n(qm->ids, it->ids, ecs_id_t, field_count); + ecs_os_memcpy_n(qm->sources, it->sources, ecs_entity_t, field_count); + ecs_os_memcpy_n(qm->sizes, it->sizes, ecs_size_t, field_count); - 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; + /* Look for union & disabled terms */ + if (table) { + if (table->flags & EcsTableHasUnion) { + for (i = 0; i < term_count; i ++) { + if (ecs_term_match_0(&terms[i])) { + continue; + } - char *annot[STACK_MAX_SIZE]; - int32_t annot_count; + ecs_id_t id = terms[i].id; + if (ECS_HAS_ID_FLAG(id, PAIR) && ECS_PAIR_SECOND(id) == EcsWildcard) { + continue; + } + + int32_t actual_index = terms[i].field_index; + int32_t column = it->columns[actual_index]; + if (column <= 0) { + continue; + } - bool with_stmt; - bool scope_assign_stmt; - bool using_stmt; - bool assign_stmt; - bool isa_stmt; + ecs_id_t table_id = table->type.array[column - 1]; + if (ECS_PAIR_FIRST(table_id) != EcsUnion) { + continue; + } - int32_t errors; -} plecs_state_t; + flecs_switch_term_t *sc = ecs_vector_add( + &qm->sparse_columns, flecs_switch_term_t); + sc->signature_column_index = actual_index; + sc->sw_case = ECS_PAIR_SECOND(id); + sc->sw_column = NULL; + qm->ids[actual_index] = id; + } + } + if (table->flags & EcsTableHasToggle) { + for (i = 0; i < term_count; i ++) { + if (ecs_term_match_0(&terms[i])) { + continue; + } -static -ecs_entity_t plecs_lookup( - const ecs_world_t *world, - const char *path, - plecs_state_t *state, - ecs_entity_t rel, - bool is_subject) -{ - ecs_entity_t e = 0; + int32_t actual_index = terms[i].field_index; + ecs_id_t id = it->ids[actual_index]; + ecs_id_t bs_id = ECS_TOGGLE | id; + int32_t bs_index = ecs_search(world, table, bs_id, 0); - if (!is_subject) { - ecs_entity_t oneof = 0; - if (rel) { - if (ecs_has_id(world, rel, EcsOneOf)) { - oneof = rel; - } else { - oneof = ecs_get_target(world, rel, EcsOneOf, 0); - } - if (oneof) { - return ecs_lookup_path_w_sep( - world, oneof, path, NULL, NULL, false); + if (bs_index != -1) { + flecs_bitset_term_t *bc = ecs_vector_add( + &qm->bitset_columns, flecs_bitset_term_t); + bc->column_index = bs_index; + bc->bs_column = NULL; + } } } - 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; + } + + /* Add references for substituted terms */ + ecs_vector_t *refs = NULL; + for (i = 0; i < term_count; i ++) { + ecs_term_t *term = &terms[i]; + if (!ecs_term_match_this(term)) { + /* non-This terms are set during iteration */ + continue; + } + + int32_t actual_index = terms[i].field_index; + ecs_entity_t src = it->sources[actual_index]; + ecs_size_t size = 0; + if (it->sizes) { + size = it->sizes[actual_index]; + } + if (src) { + ecs_id_t id = it->ids[actual_index]; + ecs_assert(ecs_is_valid(world, src), ECS_INTERNAL_ERROR, NULL); + + if (id) { + refs = flecs_query_add_ref(world, query, refs, term, id, src, size); + + /* Use column index to bind term and ref */ + if (qm->columns[actual_index] != 0) { + qm->columns[actual_index] = -ecs_vector_count(refs); + } } } } - - if (!e) { - e = ecs_lookup_path_w_sep(world, 0, path, NULL, NULL, !is_subject); + if (refs) { + int32_t count = ecs_vector_count(refs); + ecs_ref_t *ptr = ecs_vector_first(refs, ecs_ref_t); + qm->references = ecs_os_memdup_n(ptr, ecs_ref_t, count); + ecs_vector_free(refs); } - - return e; } -/* Lookup action used for deserializing entity refs in component values */ -#ifdef FLECS_EXPR +/** Populate query cache with tables */ static -ecs_entity_t plecs_lookup_action( - const ecs_world_t *world, - const char *path, - void *ctx) +void flecs_query_match_tables( + ecs_world_t *world, + ecs_query_t *query) { - return plecs_lookup(world, path, ctx, 0, false); + ecs_table_t *table = NULL; + ecs_query_table_t *qt = NULL; + + ecs_iter_t it = ecs_filter_iter(world, &query->filter); + ECS_BIT_SET(it.flags, EcsIterIsInstanced); + ECS_BIT_SET(it.flags, EcsIterIsFilter); + ECS_BIT_SET(it.flags, EcsIterEntityOptional); + + while (ecs_filter_next(&it)) { + if ((table != it.table) || (!it.table && !qt)) { + /* New table matched, add record to cache */ + qt = ecs_os_calloc_t(ecs_query_table_t); + ecs_table_cache_insert(&query->cache, it.table, &qt->hdr); + table = it.table; + } + + ecs_query_table_match_t *qm = flecs_query_add_table_match(query, qt, table); + flecs_query_set_table_match(world, query, qm, table, &it); + } } -#endif static -ecs_entity_t plecs_ensure_entity( +bool flecs_query_match_table( ecs_world_t *world, - plecs_state_t *state, - const char *path, - ecs_entity_t rel, - bool is_subject) + ecs_query_t *query, + ecs_table_t *table) { - if (!path) { - return 0; + if (!ecs_map_is_initialized(&query->cache.index)) { + return false; } - ecs_entity_t e = plecs_lookup(world, path, state, rel, is_subject); - if (!e) { - if (rel && flecs_get_oneof(world, rel)) { - /* If relationship has oneof and entity was not found, don't proceed - * with creating an entity as this can cause asserts later on */ - char *relstr = ecs_get_fullpath(world, rel); - ecs_parser_error(state->name, 0, 0, - "invalid identifier '%s' for relationship '%s'", path, relstr); - ecs_os_free(relstr); - return 0; - } - - 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); - } + ecs_query_table_t *qt = NULL; + int var_id = ecs_filter_find_this_var(&query->filter); + if (var_id == -1) { + /* If query doesn't match with This term, it can't match with tables */ + return false; + } - 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_iter_t it = ecs_filter_iter(world, &query->filter); + ECS_BIT_SET(it.flags, EcsIterIsInstanced); + ECS_BIT_SET(it.flags, EcsIterIsFilter); + ECS_BIT_SET(it.flags, EcsIterEntityOptional); + ecs_iter_set_var_as_table(&it, var_id, table); - ecs_entity_t with = ecs_get_with(world); - if (with) { - ecs_add_id(world, e, with); - } + while (ecs_filter_next(&it)) { + ecs_assert(it.table == table, ECS_INTERNAL_ERROR, NULL); + if (qt == NULL) { + qt = ecs_os_calloc_t(ecs_query_table_t); + ecs_table_cache_insert(&query->cache, it.table, &qt->hdr); + table = it.table; } + + ecs_query_table_match_t *qm = flecs_query_add_table_match(query, qt, table); + flecs_query_set_table_match(world, query, qm, table, &it); } - return e; + return qt != NULL; } +ECS_SORT_TABLE_WITH_COMPARE(_, flecs_query_sort_table_generic, order_by, static) + static -bool plecs_pred_is_subj( - ecs_term_t *term, - plecs_state_t *state) +void flecs_query_sort_table( + ecs_world_t *world, + ecs_table_t *table, + int32_t column_index, + ecs_order_by_action_t compare, + ecs_sort_table_action_t sort) { - if (term->src.name != NULL) { - return false; - } - if (term->second.name != NULL) { - return false; - } - if (ecs_term_match_0(term)) { - return false; - } - if (state->with_stmt) { - return false; - } - if (state->assign_stmt) { - return false; + ecs_data_t *data = &table->data; + if (!ecs_storage_count(&data->entities)) { + /* Nothing to sort */ + return; } - if (state->isa_stmt) { - return false; + + int32_t count = flecs_table_data_count(data); + if (count < 2) { + return; } - if (state->using_stmt) { - return false; + + ecs_entity_t *entities = ecs_storage_first(&data->entities); + + void *ptr = NULL; + int32_t size = 0; + if (column_index != -1) { + ecs_type_info_t *ti = table->type_info[column_index]; + ecs_column_t *column = &data->columns[column_index]; + size = ti->size; + ptr = ecs_storage_first(column); } - return true; + if (sort) { + sort(world, table, entities, ptr, size, 0, count - 1, compare); + } else { + flecs_query_sort_table_generic(world, table, entities, ptr, size, 0, count - 1, compare); + } } -/* Set masks aren't useful in plecs, so translate them back to entity names */ +/* Helper struct for building sorted table ranges */ +typedef struct sort_helper_t { + ecs_query_table_match_t *match; + ecs_entity_t *entities; + const void *ptr; + int32_t row; + int32_t elem_size; + int32_t count; + bool shared; +} sort_helper_t; + static -const char* plecs_set_mask_to_name( - ecs_flags32_t flags) +const void* ptr_from_helper( + sort_helper_t *helper) { - flags &= EcsTraverseFlags; - if (flags == EcsSelf) { - return "self"; - } else if (flags == EcsUp) { - return "up"; - } else if (flags == EcsDown) { - return "down"; - } else if (flags == EcsCascade || flags == (EcsUp|EcsCascade)) { - return "cascade"; - } else if (flags == EcsParent) { - return "parent"; + ecs_assert(helper->row < helper->count, ECS_INTERNAL_ERROR, NULL); + ecs_assert(helper->elem_size >= 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(helper->row >= 0, ECS_INTERNAL_ERROR, NULL); + if (helper->shared) { + return helper->ptr; + } else { + return ECS_ELEM(helper->ptr, helper->elem_size, helper->row); } - return NULL; } static -char* plecs_trim_annot( - char *annot) +ecs_entity_t e_from_helper( + sort_helper_t *helper) { - annot = (char*)ecs_parse_whitespace(annot); - int32_t len = ecs_os_strlen(annot) - 1; - while (isspace(annot[len]) && (len > 0)) { - annot[len] = '\0'; - len --; + if (helper->row < helper->count) { + return helper->entities[helper->row]; + } else { + return 0; } - return annot; } static -void plecs_apply_annotations( - ecs_world_t *world, - ecs_entity_t subj, - plecs_state_t *state) +void flecs_query_build_sorted_table_range( + ecs_query_t *query, + ecs_query_table_list_t *list) { - (void)world; - (void)subj; - (void)state; -#ifdef FLECS_DOC - int32_t i = 0, count = state->annot_count; - for (i = 0; i < count; i ++) { - char *annot = state->annot[i]; - if (!ecs_os_strncmp(annot, "@brief ", 7)) { - annot = plecs_trim_annot(annot + 7); - ecs_doc_set_brief(world, subj, annot); - } else if (!ecs_os_strncmp(annot, "@link ", 6)) { - annot = plecs_trim_annot(annot + 6); - ecs_doc_set_link(world, subj, annot); - } else if (!ecs_os_strncmp(annot, "@name ", 6)) { - annot = plecs_trim_annot(annot + 6); - ecs_doc_set_name(world, subj, annot); - } else if (!ecs_os_strncmp(annot, "@color ", 7)) { - annot = plecs_trim_annot(annot + 7); - ecs_doc_set_color(world, subj, annot); - } + ecs_world_t *world = query->world; + ecs_entity_t id = query->order_by_component; + ecs_order_by_action_t compare = query->order_by; + + if (!list->count) { + return; } -#else - ecs_warn("cannot apply annotations, doc addon is missing"); -#endif -} -static -int plecs_create_term( - ecs_world_t *world, - ecs_term_t *term, - const char *name, - const char *expr, - int64_t column, - plecs_state_t *state) -{ - state->last_subject = 0; - state->last_predicate = 0; - state->last_object = 0; - state->last_assign_id = 0; + int to_sort = 0; - const char *pred_name = term->first.name; - const char *subj_name = term->src.name; - const char *obj_name = term->second.name; + 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->data; - if (!subj_name) { - subj_name = plecs_set_mask_to_name(term->src.flags); - } - if (!obj_name) { - obj_name = plecs_set_mask_to_name(term->second.flags); - } + ecs_assert(ecs_table_count(table) != 0, ECS_INTERNAL_ERROR, NULL); - if (!ecs_term_id_is_set(&term->first)) { - ecs_parser_error(name, expr, column, "missing predicate in expression"); - return -1; - } + int32_t index = -1; + if (id) { + index = ecs_search(world, table->storage_table, id, 0); + } - if (state->assign_stmt && !ecs_term_match_this(term)) { - ecs_parser_error(name, expr, column, - "invalid statement in assign statement"); - return -1; - } + if (index != -1) { + ecs_type_info_t *ti = table->type_info[index]; + ecs_column_t *column = &data->columns[index]; + int32_t size = ti->size; + helper[to_sort].ptr = ecs_storage_first(column); + helper[to_sort].elem_size = size; + helper[to_sort].shared = false; + } else if (id) { + /* Find component in prefab */ + ecs_entity_t base = 0; + ecs_search_relation(world, table, 0, id, + EcsIsA, EcsUp, &base, 0, 0); - bool pred_as_subj = plecs_pred_is_subj(term, state); + /* 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); - ecs_entity_t pred = plecs_ensure_entity(world, state, pred_name, 0, pred_as_subj); - ecs_entity_t subj = plecs_ensure_entity(world, state, subj_name, pred, true); - ecs_entity_t obj = 0; + const EcsComponent *cptr = ecs_get(world, id, EcsComponent); + ecs_assert(cptr != NULL, ECS_INTERNAL_ERROR, NULL); - if (ecs_term_id_is_set(&term->second)) { - obj = plecs_ensure_entity(world, state, obj_name, pred, - state->assign_stmt == false); - if (!obj) { - return -1; + helper[to_sort].ptr = ecs_get_id(world, base, id); + 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; } - } - - if (state->assign_stmt || state->isa_stmt) { - subj = state->assign_to; - } - - if (state->isa_stmt && obj) { - ecs_parser_error(name, expr, column, - "invalid object in inheritance statement"); - return -1; - } - if (state->using_stmt && (obj || subj)) { - ecs_parser_error(name, expr, column, - "invalid predicate/object in using statement"); - return -1; + helper[to_sort].match = match; + helper[to_sort].entities = ecs_storage_first(&data->entities); + helper[to_sort].row = 0; + helper[to_sort].count = ecs_table_count(table); + to_sort ++; } - if (state->isa_stmt) { - pred = ecs_pair(EcsIsA, pred); - } + ecs_assert(to_sort != 0, 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; + bool proceed; + do { + int32_t j, min = 0; + proceed = true; - 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; + ecs_entity_t e1; + while (!(e1 = e_from_helper(&helper[min]))) { + min ++; + if (min == to_sort) { + proceed = false; + break; } - } else { - state->last_predicate = pred; - state->last_object = obj; - pred_as_subj = false; } - } - - /* 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; + if (!proceed) { + break; } - 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); + for (j = min + 1; j < to_sort; j++) { + ecs_entity_t e2 = e_from_helper(&helper[j]); + if (!e2) { + continue; + } - state->using[state->using_frame ++] = pred; - state->using_frames[state->sp] = state->using_frame; + const void *ptr1 = ptr_from_helper(&helper[min]); + const void *ptr2 = ptr_from_helper(&helper[j]); - /* 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 (compare(e1, ptr1, e2, ptr2) > 0) { + min = j; + e1 = e_from_helper(&helper[min]); } } - } - - /* 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); - } - /* If annotations preceded the statement, append */ - if (state->annot_count) { - if (!subj) { - ecs_parser_error(name, expr, column, - "missing subject for annotations"); - return -1; + 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 ++; } - plecs_apply_annotations(world, subj, state); + cur_helper->row ++; + } while (proceed); + + /* Iterate through the vector of slices to set the prev/next ptrs. This + * can't be done while building the vector, as reallocs may occur */ + int32_t i, count = ecs_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]; } - return 0; + nodes[0].prev = NULL; + nodes[i - 1].next = NULL; + + ecs_os_free(helper); } static -const char* plecs_parse_inherit_stmt( - const char *name, - const char *expr, - const char *ptr, - plecs_state_t *state) +void flecs_query_build_sorted_tables( + ecs_query_t *query) { - if (state->isa_stmt) { - ecs_parser_error(name, expr, ptr - expr, - "cannot nest inheritance"); - return NULL; - } + ecs_vector_clear(query->table_slices); - 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; + 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); - return ptr; + /* Sort tables in current group */ + flecs_query_build_sorted_table_range(query, list); + + /* Find next group to sort */ + cur = list->last->next; + } while (cur); + } + } else { + flecs_query_build_sorted_table_range(query, &query->list); + } } static -const char* plecs_parse_assign_expr( +void flecs_query_sort_tables( ecs_world_t *world, - const char *name, - const char *expr, - const char *ptr, - plecs_state_t *state) + ecs_query_t *query) { - (void)world; - - if (!state->assign_stmt) { - ecs_parser_error(name, expr, ptr - expr, - "unexpected value outside of assignment statement"); - return NULL; - } - - 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; - } - -#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; + ecs_order_by_action_t compare = query->order_by; + if (!compare) { + return; } - if (!assign_to) { - ecs_parser_error(name, expr, ptr - expr, - "missing entity to assign to"); - return NULL; - } + ecs_sort_table_action_t sort = query->sort_table; + + ecs_entity_t order_by_component = query->order_by_component; + int32_t i, order_by_term = -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; - } + /* Find term that iterates over component (must be at least one) */ + if (order_by_component) { + const ecs_filter_t *f = &query->filter; + int32_t term_count = f->term_count; + for (i = 0; i < term_count; i ++) { + ecs_term_t *term = &f->terms[i]; + if (!ecs_term_match_this(term)) { + continue; + } - void *value_ptr = ecs_get_mut_id(world, assign_to, assign_id); + if (term->id == order_by_component) { + order_by_term = i; + break; + } + } - 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_assert(order_by_term != -1, ECS_INTERNAL_ERROR, NULL); } - ecs_modified_id(world, assign_to, assign_id); -#endif - - return ptr; -} - -static -const char* plecs_parse_assign_stmt( - ecs_world_t *world, - const char *name, - const char *expr, - const char *ptr, - plecs_state_t *state) -{ - (void)world; - - 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; - } + /* Iterate over non-empty tables. Don't bother with empty tables as they + * have nothing to sort */ - if (state->assign_stmt) { - ecs_parser_error(name, expr, ptr - expr, - "invalid assign statement in assign statement"); - return NULL; - } + bool tables_sorted = false; - if (!state->scope_assign_stmt) { - state->assign_to = state->last_subject; - } + ecs_table_cache_iter_t it; + ecs_query_table_t *qt; + flecs_table_cache_iter(&query->cache, &it); - state->assign_stmt = true; - - /* Assignment without a preceding component */ - if (ptr[0] == '{') { - ecs_entity_t type = 0; + while ((qt = flecs_table_cache_next(&it, ecs_query_table_t))) { + ecs_table_t *table = qt->hdr.table; + bool dirty = false; - if (state->scope_assign_stmt) { - ecs_assert(state->assign_to == ecs_get_scope(world), - ECS_INTERNAL_ERROR, NULL); + if (flecs_query_check_table_monitor(query, qt, 0)) { + dirty = true; } - /* 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; + int32_t column = -1; + if (order_by_component) { + if (flecs_query_check_table_monitor(query, qt, order_by_term + 1)) { + dirty = true; } - } - /* 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]; - } + if (dirty) { + column = -1; - /* 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]; + ecs_table_t *storage_table = table->storage_table; + if (storage_table) { + column = ecs_search(world, storage_table, + order_by_component, NULL); + } + + if (column == -1) { + /* Component is shared, no sorting is needed */ + dirty = false; + } } } - if (!type) { - ecs_parser_error(name, expr, ptr - expr, - "missing type for assignment"); - return NULL; + if (!dirty) { + continue; } - state->last_assign_id = type; + /* Something has changed, sort the table. Prefers using flecs_query_sort_table when available */ + flecs_query_sort_table(world, table, column, compare, sort); + tables_sorted = true; } - return ptr; + if (tables_sorted || query->match_count != query->prev_match_count) { + flecs_query_build_sorted_tables(query); + query->match_count ++; /* Increase version if tables changed */ + } } static -const char* plecs_parse_using_stmt( - const char *name, - const char *expr, - const char *ptr, - plecs_state_t *state) +bool flecs_query_has_refs( + ecs_query_t *query) { - if (state->isa_stmt || state->assign_stmt) { - ecs_parser_error(name, expr, ptr - expr, - "invalid usage of using keyword"); - return NULL; + ecs_term_t *terms = query->filter.terms; + int32_t i, count = query->filter.term_count; + for (i = 0; i < count; i ++) { + if (terms[i].src.flags & (EcsUp | EcsIsEntity)) { + return true; + } } - /* Add following expressions to using list */ - state->using_stmt = true; - - return ptr + 5; + return false; } static -const char* plecs_parse_with_stmt( - const char *name, - const char *expr, - const char *ptr, - plecs_state_t *state) +void flecs_query_for_each_component_monitor( + ecs_world_t *world, + ecs_query_t *query, + void(*callback)( + ecs_world_t* world, + ecs_id_t id, + ecs_query_t *query)) { - if (state->isa_stmt) { - ecs_parser_error(name, expr, ptr - expr, - "invalid with after inheritance"); - return NULL; - } + ecs_term_t *terms = query->filter.terms; + int32_t i, count = query->filter.term_count; - if (state->assign_stmt) { - ecs_parser_error(name, expr, ptr - expr, - "invalid with in assign_stmt"); - return NULL; + for (i = 0; i < count; i++) { + ecs_term_t *term = &terms[i]; + ecs_term_id_t *src = &term->src; + + if (src->flags & EcsUp) { + callback(world, ecs_pair(src->trav, EcsWildcard), query); + if (src->trav != EcsIsA) { + callback(world, ecs_pair(EcsIsA, EcsWildcard), query); + } + callback(world, term->id, query); + + } else if (src->flags & EcsSelf && !ecs_term_match_this(term)) { + callback(world, term->id, query); + } } +} - /* Add following expressions to with list */ - state->with_stmt = true; - return ptr + 5; +static +bool flecs_query_is_term_id_supported( + ecs_term_id_t *term_id) +{ + if (!(term_id->flags & EcsIsVariable)) { + return true; + } + if (ecs_id_is_wildcard(term_id->id)) { + return true; + } + return false; } static -const char* plecs_parse_scope_open( +void flecs_query_process_signature( ecs_world_t *world, - const char *name, - const char *expr, - const char *ptr, - plecs_state_t *state) + ecs_query_t *query) { - state->isa_stmt = false; + ecs_term_t *terms = query->filter.terms; + int32_t i, count = query->filter.term_count; - if (state->assign_stmt) { - ecs_parser_error(name, expr, ptr - expr, - "invalid scope in assign_stmt"); - return NULL; - } + for (i = 0; i < count; i ++) { + ecs_term_t *term = &terms[i]; + ecs_term_id_t *first = &term->first; + ecs_term_id_t *src = &term->src; + ecs_term_id_t *second = &term->second; + ecs_inout_kind_t inout = term->inout; - state->sp ++; + bool is_src_ok = flecs_query_is_term_id_supported(src); + bool is_first_ok = flecs_query_is_term_id_supported(first); + bool is_second_ok = flecs_query_is_term_id_supported(second); - ecs_entity_t scope = 0; - ecs_entity_t default_scope_type = 0; + (void)first; + (void)second; + (void)is_src_ok; + (void)is_first_ok; + (void)is_second_ok; - if (!state->with_stmt) { - if (state->last_subject) { - scope = state->last_subject; - ecs_set_scope(world, state->last_subject); + /* Queries do not support named variables */ + ecs_check(is_src_ok || ecs_term_match_this(term), + ECS_UNSUPPORTED, NULL); + ecs_check(is_first_ok, ECS_UNSUPPORTED, NULL); + ecs_check(is_second_ok, ECS_UNSUPPORTED, NULL); - /* Check if scope has a default child component */ - ecs_entity_t def_type_src = ecs_get_target_for_id(world, scope, - 0, ecs_pair(EcsDefaultChildComponent, EcsWildcard)); + if (inout != EcsIn) { + query->flags |= EcsQueryHasOutColumns; + } - if (def_type_src) { - default_scope_type = ecs_get_target( - world, def_type_src, EcsDefaultChildComponent, 0); - } - } 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); - } - ecs_set_scope(world, state->last_predicate); - } + if (src->flags & EcsCascade) { + /* Query can only have one cascade column */ + ecs_assert(query->cascade_by == 0, ECS_INVALID_PARAMETER, NULL); + query->cascade_by = i + 1; } - 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 ((src->flags & EcsTraverseFlags) == EcsSelf) { + if (src->flags & EcsIsEntity) { + flecs_add_flag(world, term->src.id, EcsEntityObserved); + } + } } - state->using_frames[state->sp] = state->using_frame; - state->with_frames[state->sp] = state->with_frame; - state->with_stmt = false; + query->flags |= (ecs_flags32_t)(flecs_query_has_refs(query) * EcsQueryHasRefs); - return ptr; + if (!(query->flags & EcsQueryIsSubquery)) { + flecs_query_for_each_component_monitor(world, query, flecs_monitor_register); + } +error: + return; } +/** When a table becomes empty remove it from the query list, or vice versa. */ static -const char* plecs_parse_scope_close( - ecs_world_t *world, - const char *name, - const char *expr, - const char *ptr, - plecs_state_t *state) +void flecs_query_update_table( + ecs_query_t *query, + ecs_table_t *table, + bool empty) { - 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; - } - - state->scope[state->sp] = 0; - state->default_scope_type[state->sp] = 0; - state->sp --; + int32_t prev_count = ecs_query_table_count(query); + ecs_table_cache_set_empty(&query->cache, table, empty); + int32_t cur_count = ecs_query_table_count(query); - if (state->sp < 0) { - ecs_parser_error(name, expr, ptr - expr, "invalid } without a {"); - return NULL; - } + if (prev_count != cur_count) { + ecs_query_table_t *qt = ecs_table_cache_get(&query->cache, table); + ecs_assert(qt != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_query_table_match_t *cur, *next; - ecs_id_t id = state->scope[state->sp]; + for (cur = qt->first; cur != NULL; cur = next) { + next = cur->next_match; - if (!id || ECS_HAS_ID_FLAG(id, PAIR)) { - ecs_set_with(world, id); - } + if (empty) { + ecs_assert(ecs_table_count(table) == 0, + ECS_INTERNAL_ERROR, NULL); - if (!id || !ECS_HAS_ID_FLAG(id, PAIR)) { - ecs_set_scope(world, id); + flecs_query_remove_table_node(query, &cur->node); + } else { + ecs_assert(ecs_table_count(table) != 0, + ECS_INTERNAL_ERROR, NULL); + flecs_query_insert_table_node(query, &cur->node); + } + } } - 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; + ecs_assert(cur_count || query->list.first == NULL, + ECS_INTERNAL_ERROR, NULL); } static -const char *plecs_parse_plecs_term( - ecs_world_t *world, - const char *name, - const char *expr, - const char *ptr, - plecs_state_t *state) +void flecs_query_add_subquery( + ecs_world_t *world, + ecs_query_t *parent, + ecs_query_t *subquery) { - ecs_term_t term = {0}; - ecs_entity_t scope = ecs_get_scope(world); - - /* 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; - - ptr = ecs_parse_term(world, name, expr, ptr, &term); - if (!ptr) { - return NULL; - } - - if (!ecs_term_is_initialized(&term)) { - ecs_parser_error(name, expr, ptr - expr, "expected identifier"); - return NULL; /* No term found */ - } - - /* 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.first.name, state, 0, false); - ecs_entity_t obj = plecs_lookup( - world, term.second.name, state, pred, false); - ecs_id_t id = 0; - if (pred && obj) { - id = ecs_pair(pred, obj); - } else if (pred) { - id = pred; - } - - if (id && (ecs_get_typeid(world, id) != 0)) { - scope_assignment = true; - } - } - } + ecs_query_t **elem = ecs_vector_add(&parent->subqueries, ecs_query_t*); + *elem = subquery; - bool prev = state->assign_stmt; - if (scope_assignment) { - state->assign_stmt = true; - state->assign_to = scope; - } - if (plecs_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; + ecs_table_cache_t *cache = &parent->cache; + ecs_table_cache_iter_t it; + ecs_query_table_t *qt; + flecs_table_cache_all_iter(cache, &it); + while ((qt = flecs_table_cache_next(&it, ecs_query_table_t))) { + flecs_query_match_table(world, subquery, qt->hdr.table); } - state->assign_stmt = prev; +} - ecs_term_fini(&term); +static +void flecs_query_notify_subqueries( + ecs_world_t *world, + ecs_query_t *query, + ecs_query_event_t *event) +{ + if (query->subqueries) { + ecs_query_t **queries = ecs_vector_first(query->subqueries, ecs_query_t*); + int32_t i, count = ecs_vector_count(query->subqueries); - return ptr; + ecs_query_event_t sub_event = *event; + sub_event.parent_query = query; + + for (i = 0; i < count; i ++) { + ecs_query_t *sub = queries[i]; + flecs_query_notify(world, sub, &sub_event); + } + } } +/* Remove table */ static -const char* plecs_parse_annotation( - const char *name, - const char *expr, - const char *ptr, - plecs_state_t *state) +void flecs_query_table_match_free( + ecs_query_t *query, + ecs_query_table_t *elem, + ecs_query_table_match_t *first) { - do { - if(state->annot_count >= STACK_MAX_SIZE) { - ecs_parser_error(name, expr, ptr - expr, - "max number of annotations reached"); - return NULL; - } + ecs_query_table_match_t *cur, *next; - char ch; - const char *start = ptr; - for (; (ch = *ptr) && ch != '\n'; ptr ++) { } + for (cur = first; cur != NULL; cur = next) { + ecs_os_free(cur->columns); + ecs_os_free(cur->ids); + ecs_os_free(cur->sources); + ecs_os_free(cur->sizes); + ecs_os_free(cur->references); + ecs_os_free(cur->sparse_columns); + ecs_os_free(cur->bitset_columns); + ecs_os_free(cur->monitor); - int32_t len = (int32_t)(ptr - start); - char *annot = ecs_os_malloc_n(char, len + 1); - ecs_os_memcpy_n(annot, start, char, len); - annot[len] = '\0'; + if (!elem->hdr.empty) { + flecs_query_remove_table_node(query, &cur->node); + } - state->annot[state->annot_count] = annot; - state->annot_count ++; + next = cur->next_match; - ptr = ecs_parse_fluff(ptr, NULL); - } while (ptr[0] == '@'); + ecs_os_free(cur); + } +} - return ptr; +static +void flecs_query_table_free( + ecs_query_t *query, + ecs_query_table_t *elem) +{ + flecs_query_table_match_free(query, elem, elem->first); + ecs_os_free(elem); } static -void plecs_clear_annotations( - plecs_state_t *state) +void flecs_query_unmatch_table( + ecs_query_t *query, + ecs_table_t *table) { - int32_t i, count = state->annot_count; - for (i = 0; i < count; i ++) { - ecs_os_free(state->annot[i]); + ecs_query_table_t *qt = ecs_table_cache_remove( + &query->cache, table, NULL); + if (qt) { + flecs_query_table_free(query, qt); } - state->annot_count = 0; } +/* Rematch system with tables after a change happened to a watched entity */ static -const char* plecs_parse_stmt( +void flecs_query_rematch_tables( ecs_world_t *world, - const char *name, - const char *expr, - const char *ptr, - plecs_state_t *state) + ecs_query_t *query, + ecs_query_t *parent_query) { - 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; + ecs_iter_t it, parent_it; + ecs_table_t *table = NULL; + ecs_query_table_t *qt = NULL; + ecs_query_table_match_t *qm = NULL; - plecs_clear_annotations(state); + if (parent_query) { + parent_it = ecs_query_iter(world, parent_query); + it = ecs_filter_chain_iter(&parent_it, &query->filter); + } else { + it = ecs_filter_iter(world, &query->filter); + } - ptr = ecs_parse_fluff(ptr, NULL); + ECS_BIT_SET(it.flags, EcsIterIsInstanced); + ECS_BIT_SET(it.flags, EcsIterIsFilter); + ECS_BIT_SET(it.flags, EcsIterEntityOptional); - char ch = ptr[0]; + int32_t rematch_count = ++ query->rematch_count; - if (!ch) { - goto done; - } else if (ch == '{') { - ptr = ecs_parse_fluff(ptr + 1, NULL); - goto scope_open; - } else if (ch == '}') { - ptr = ecs_parse_fluff(ptr + 1, NULL); - goto scope_close; - } else if (ch == '(') { - goto term_expr; - } else if (ch == '@') { - ptr = plecs_parse_annotation(name, expr, ptr, state); - if (!ptr) goto error; - goto term_expr; - } else if (!ecs_os_strncmp(ptr, TOK_USING " ", 5)) { - ptr = plecs_parse_using_stmt(name, expr, ptr, state); - if (!ptr) goto error; - goto term_expr; - } else if (!ecs_os_strncmp(ptr, TOK_WITH " ", 5)) { - ptr = plecs_parse_with_stmt(name, expr, ptr, state); - if (!ptr) goto error; - goto term_expr; - } else { - goto term_expr; + while (ecs_iter_next(&it)) { + if ((table != it.table) || (!it.table && !qt)) { + if (qm && qm->next_match) { + flecs_query_table_match_free(query, qt, qm->next_match); + qm->next_match = NULL; + } + + table = it.table; + + qt = ecs_table_cache_get(&query->cache, table); + if (!qt) { + qt = ecs_os_calloc_t(ecs_query_table_t); + ecs_table_cache_insert(&query->cache, table, &qt->hdr); + } + + ecs_assert(qt->hdr.table == table, ECS_INTERNAL_ERROR, NULL); + qt->rematch_count = rematch_count; + qm = NULL; + } + if (!qm) { + qm = qt->first; + } else { + qm = qm->next_match; + } + if (!qm) { + qm = flecs_query_add_table_match(query, qt, table); + } + + flecs_query_set_table_match(world, query, qm, table, &it); + + if (table && ecs_table_count(table) && query->group_by) { + /* If grouping is enabled, make sure match is in the right place */ + flecs_query_remove_table_node(query, &qm->node); + flecs_query_insert_table_node(query, &qm->node); + } } -term_expr: - if (!ptr[0]) { - goto done; + if (qm && qm->next_match) { + flecs_query_table_match_free(query, qt, qm->next_match); + qm->next_match = NULL; } - if (!(ptr = plecs_parse_plecs_term(world, name, ptr, ptr, state))) { - goto error; + /* Iterate all tables in cache, remove ones that weren't just matched */ + ecs_table_cache_iter_t cache_it; + if (flecs_table_cache_all_iter(&query->cache, &cache_it)) { + while ((qt = flecs_table_cache_next(&cache_it, ecs_query_table_t))) { + if (qt->rematch_count != rematch_count) { + flecs_query_unmatch_table(query, qt->hdr.table); + } + } } +} - ptr = ecs_parse_fluff(ptr, NULL); +static +void flecs_query_remove_subquery( + ecs_query_t *parent, + ecs_query_t *sub) +{ + ecs_assert(parent != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(sub != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(parent->subqueries != NULL, ECS_INTERNAL_ERROR, NULL); - if (ptr[0] == '{' && !isspace(ptr[-1])) { - /* A '{' directly after an identifier (no whitespace) is a literal */ - goto assign_expr; + int32_t i, count = ecs_vector_count(parent->subqueries); + ecs_query_t **sq = ecs_vector_first(parent->subqueries, ecs_query_t*); + + for (i = 0; i < count; i ++) { + if (sq[i] == sub) { + break; + } } - if (!state->using_stmt) { - if (ptr[0] == ':') { - ptr = ecs_parse_fluff(ptr + 1, NULL); - goto inherit_stmt; - } else if (ptr[0] == '=') { - ptr = ecs_parse_fluff(ptr + 1, NULL); - goto assign_stmt; - } else if (ptr[0] == ',') { - ptr = ecs_parse_fluff(ptr + 1, NULL); - goto term_expr; - } else if (ptr[0] == '{') { - state->assign_stmt = false; - ptr = ecs_parse_fluff(ptr + 1, NULL); - goto scope_open; + ecs_vector_remove(parent->subqueries, ecs_query_t*, i); +} + +/* -- Private API -- */ + +void flecs_query_notify( + ecs_world_t *world, + ecs_query_t *query, + ecs_query_event_t *event) +{ + bool notify = true; + + switch(event->kind) { + case EcsQueryTableMatch: + /* Creation of new table */ + if (flecs_query_match_table(world, query, event->table)) { + if (query->subqueries) { + flecs_query_notify_subqueries(world, query, event); + } } + notify = false; + break; + case EcsQueryTableUnmatch: + /* Deletion of table */ + flecs_query_unmatch_table(query, event->table); + break; + case EcsQueryTableRematch: + /* Rematch tables of query */ + flecs_query_rematch_tables(world, query, event->parent_query); + break; + case EcsQueryOrphan: + ecs_assert(query->flags & EcsQueryIsSubquery, ECS_INTERNAL_ERROR, NULL); + query->flags |= EcsQueryIsOrphaned; + query->parent = NULL; + break; } - state->assign_stmt = false; - goto done; + if (notify) { + flecs_query_notify_subqueries(world, query, event); + } +} -inherit_stmt: - ptr = plecs_parse_inherit_stmt(name, expr, ptr, state); - if (!ptr) goto error; +static +void flecs_query_order_by( + ecs_world_t *world, + ecs_query_t *query, + ecs_entity_t order_by_component, + ecs_order_by_action_t order_by, + ecs_sort_table_action_t action) +{ + ecs_check(query != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(!(query->flags & EcsQueryIsOrphaned), ECS_INVALID_PARAMETER, NULL); - /* Expect base identifier */ - goto term_expr; + query->order_by_component = order_by_component; + query->order_by = order_by; + query->sort_table = action; -assign_stmt: - ptr = plecs_parse_assign_stmt(world, name, expr, ptr, state); - if (!ptr) goto error; + ecs_vector_free(query->table_slices); + query->table_slices = NULL; - ptr = ecs_parse_fluff(ptr, NULL); + flecs_query_sort_tables(world, query); - /* Assignment without a preceding component */ - if (ptr[0] == '{') { - goto assign_expr; + if (!query->table_slices) { + flecs_query_build_sorted_tables(query); } +error: + return; +} - /* Expect component identifiers */ - goto term_expr; +static +void flecs_query_group_by( + ecs_query_t *query, + ecs_entity_t sort_component, + ecs_group_by_action_t group_by) +{ + /* Cannot change grouping once a query has been created */ + ecs_check(query->group_by_id == 0, ECS_INVALID_OPERATION, NULL); + ecs_check(query->group_by == 0, ECS_INVALID_OPERATION, NULL); -assign_expr: - ptr = plecs_parse_assign_expr(world, name, expr, ptr, state); - if (!ptr) goto error; + query->group_by_id = sort_component; + query->group_by = group_by; + ecs_map_init(&query->groups, ecs_query_table_list_t, 16); +error: + return; +} - ptr = ecs_parse_fluff(ptr, NULL); - if (ptr[0] == ',') { - ptr ++; - goto term_expr; - } else if (ptr[0] == '{') { - state->assign_stmt = false; - ptr ++; - goto scope_open; +/* Implementation for iterable mixin */ +static +void flecs_query_iter_init( + const ecs_world_t *world, + const ecs_poly_t *poly, + ecs_iter_t *iter, + ecs_term_t *filter) +{ + ecs_poly_assert(poly, ecs_query_t); + + if (filter) { + iter[1] = ecs_query_iter(world, (ecs_query_t*)poly); + iter[0] = ecs_term_chain_iter(&iter[1], filter); } else { - state->assign_stmt = false; - goto done; + iter[0] = ecs_query_iter(world, (ecs_query_t*)poly); } +} -scope_open: - ptr = plecs_parse_scope_open(world, name, expr, ptr, state); - if (!ptr) goto error; - goto done; +static +void flecs_query_on_event( + ecs_iter_t *it) +{ + /* Because this is the observer::run callback, checking if this is event is + * already handled is not done for us. */ + ecs_world_t *world = it->world; + ecs_observer_t *o = it->ctx; + if (o->last_event_id) { + if (o->last_event_id[0] == world->event_id) { + return; + } + o->last_event_id[0] = world->event_id; + } -scope_close: - ptr = plecs_parse_scope_close(world, name, expr, ptr, state); - if (!ptr) goto error; - goto done; + ecs_query_t *query = o->ctx; + ecs_table_t *table = it->table; + ecs_assert(query != NULL, ECS_INTERNAL_ERROR, NULL); -done: - return ptr; -error: - return NULL; + /* The observer isn't doing the matching because the query can do it more + * efficiently by checking the table with the query cache. */ + if (ecs_table_cache_get(&query->cache, table) == NULL) { + return; + } + + ecs_entity_t event = it->event; + if (event == EcsOnTableEmpty) { + flecs_query_update_table(query, table, true); + } else + if (event == EcsOnTableFill) { + flecs_query_update_table(query, table, false); + } } -int ecs_plecs_from_str( - ecs_world_t *world, - const char *name, - const char *expr) +static +void flecs_query_table_cache_free( + ecs_query_t *query) { - const char *ptr = expr; - ecs_term_t term = {0}; - plecs_state_t state = {0}; + ecs_table_cache_iter_t it; + ecs_query_table_t *qt; - if (!expr) { - return 0; + if (flecs_table_cache_all_iter(&query->cache, &it)) { + while ((qt = flecs_table_cache_next(&it, ecs_query_table_t))) { + flecs_query_table_free(query, qt); + } } - state.scope[0] = 0; - ecs_entity_t prev_scope = ecs_set_scope(world, 0); - ecs_entity_t prev_with = ecs_set_with(world, 0); + ecs_table_cache_fini(&query->cache); +} - do { - expr = ptr = plecs_parse_stmt(world, name, expr, ptr, &state); - if (!ptr) { - goto error; - } +static +void flecs_query_fini( + ecs_query_t *query) +{ + ecs_world_t *world = query->world; - if (!ptr[0]) { - break; /* End of expression */ + if (query->group_by_ctx_free) { + if (query->group_by_ctx) { + query->group_by_ctx_free(query->group_by_ctx); } - } while (true); + } - ecs_set_scope(world, prev_scope); - ecs_set_with(world, prev_with); + if ((query->flags & EcsQueryIsSubquery) && + !(query->flags & EcsQueryIsOrphaned)) + { + flecs_query_remove_subquery(query->parent, query); + } - plecs_clear_annotations(&state); + flecs_query_notify_subqueries(world, query, &(ecs_query_event_t){ + .kind = EcsQueryOrphan + }); - if (state.sp != 0) { - ecs_parser_error(name, expr, 0, "missing end of scope"); - goto error; - } + flecs_query_for_each_component_monitor(world, query, + flecs_monitor_unregister); + flecs_query_table_cache_free(query); - if (state.assign_stmt) { - ecs_parser_error(name, expr, 0, "unfinished assignment"); - goto error; - } + ecs_map_fini(&query->groups); - if (state.errors) { - goto error; - } + ecs_vector_free(query->subqueries); + ecs_vector_free(query->table_slices); + ecs_filter_fini(&query->filter); - return 0; -error: - ecs_set_scope(world, state.scope[0]); - ecs_set_with(world, prev_with); - ecs_term_fini(&term); - return -1; + ecs_poly_free(query, ecs_query_t); } -int ecs_plecs_from_file( + +/* -- Public API -- */ + +ecs_query_t* ecs_query_init( ecs_world_t *world, - const char *filename) + const ecs_query_desc_t *desc) { - FILE* file; - char* content = NULL; - int32_t bytes; - size_t size; + ecs_check(world != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(desc->_canary == 0, ECS_INVALID_PARAMETER, NULL); + ecs_check(!(world->flags & EcsWorldFini), ECS_INVALID_OPERATION, NULL); - /* Open file for reading */ - ecs_os_fopen(&file, filename, "r"); - if (!file) { - ecs_err("%s (%s)", ecs_os_strerror(errno), filename); - goto error; - } + ecs_query_t *result = ecs_poly_new(ecs_query_t); + ecs_observer_desc_t observer_desc = { .filter = desc->filter }; + ecs_entity_t entity = desc->entity; - /* Determine file size */ - fseek(file, 0 , SEEK_END); - bytes = (int32_t)ftell(file); - if (bytes == -1) { - goto error; - } - rewind(file); + observer_desc.filter.flags = EcsFilterMatchEmptyTables; + observer_desc.filter.storage = &result->filter; + result->filter = ECS_FILTER_INIT; - /* 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; + if (ecs_filter_init(world, &observer_desc.filter) == NULL) { goto error; - } else { - content[size] = '\0'; } - fclose(file); - - int result = ecs_plecs_from_str(world, filename, content); - ecs_os_free(content); - return result; -error: - ecs_os_free(content); - return -1; -} + if (result->filter.term_count) { + observer_desc.entity = entity; + observer_desc.run = flecs_query_on_event; + observer_desc.ctx = result; + observer_desc.events[0] = EcsOnTableEmpty; + observer_desc.events[1] = EcsOnTableFill; + observer_desc.filter.flags |= EcsFilterIsFilter; -#endif + /* ecs_filter_init could have moved away resources from the terms array + * in the descriptor, so use the terms array from the filter. */ + observer_desc.filter.terms_buffer = result->filter.terms; + observer_desc.filter.terms_buffer_count = result->filter.term_count; + observer_desc.filter.expr = NULL; /* Already parsed */ + entity = ecs_observer_init(world, &observer_desc); + if (!entity) { + goto error; + } + } -#ifdef FLECS_PIPELINE + result->world = world; + result->iterable.init = flecs_query_iter_init; + result->dtor = (ecs_poly_dtor_t)flecs_query_fini; + result->prev_match_count = -1; -/* Worker thread */ -static -void* worker(void *arg) { - ecs_stage_t *stage = arg; - ecs_world_t *world = stage->world; + if (ecs_should_log_1()) { + char *filter_expr = ecs_filter_str(world, &result->filter); + ecs_dbg_1("#[green]query#[normal] [%s] created", + filter_expr ? filter_expr : ""); + ecs_os_free(filter_expr); + } - ecs_dbg_2("worker %d: start", stage->id); + ecs_log_push_1(); - /* Start worker, increase counter so main thread knows how many - * workers are ready */ - ecs_os_mutex_lock(world->sync_mutex); - world->workers_running ++; + flecs_query_process_signature(world, result); - if (!(world->flags & EcsWorldQuitWorkers)) { - ecs_os_cond_wait(world->worker_cond, world->sync_mutex); + /* Group before matching so we won't have to move tables around later */ + int32_t cascade_by = result->cascade_by; + if (cascade_by) { + flecs_query_group_by(result, result->filter.terms[cascade_by - 1].id, + flecs_query_group_by_cascade); + result->group_by_ctx = &result->filter.terms[cascade_by - 1]; } - ecs_os_mutex_unlock(world->sync_mutex); + 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); + flecs_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; + } - while (!(world->flags & EcsWorldQuitWorkers)) { - ecs_entity_t old_scope = ecs_set_scope((ecs_world_t*)stage, 0); + if (desc->parent != NULL) { + result->flags |= EcsQueryIsSubquery; + } - ecs_dbg_3("worker %d: run", stage->id); - - ecs_run_pipeline( - (ecs_world_t*)stage, - world->pipeline, - world->info.delta_time); + /* If the query refers to itself, add the components that were queried for + * to the query itself. */ + if (entity) { + int32_t t, term_count = result->filter.term_count; + ecs_term_t *terms = result->filter.terms; - ecs_set_scope((ecs_world_t*)stage, old_scope); + for (t = 0; t < term_count; t ++) { + ecs_term_t *term = &terms[t]; + if (term->src.id == entity) { + ecs_add_id(world, entity, term->id); + } + } } - ecs_dbg_2("worker %d: finalizing", stage->id); - - ecs_os_mutex_lock(world->sync_mutex); - world->workers_running --; - ecs_os_mutex_unlock(world->sync_mutex); + if (!entity) { + entity = ecs_new_id(world); + } - ecs_dbg_2("worker %d: stop", stage->id); + EcsPoly *poly = ecs_poly_bind(world, entity, ecs_query_t); + poly->poly = result; + result->entity = entity; - return NULL; -} + /* Ensure that while initially populating the query with tables, they are + * in the right empty/non-empty list. This ensures the query won't miss + * empty/non-empty events for tables that are currently out of sync, but + * change back to being in sync before processing pending events. */ + ecs_run_aperiodic(world, EcsAperiodicEmptyTables); -/* Start threads */ -static -void start_workers( - ecs_world_t *world, - int32_t threads) -{ - ecs_set_stage_count(world, threads); + ecs_table_cache_init(&result->cache); - ecs_assert(ecs_get_stage_count(world) == threads, ECS_INTERNAL_ERROR, NULL); + if (!desc->parent) { + flecs_query_match_tables(world, result); + } else { + flecs_query_add_subquery(world, desc->parent, result); + result->parent = desc->parent; + } - 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); - stage->thread = ecs_os_thread_new(worker, stage); - ecs_assert(stage->thread != 0, ECS_OPERATION_FAILED, NULL); + if (desc->order_by) { + flecs_query_order_by( + world, result, desc->order_by_component, desc->order_by, + desc->sort_table); } -} -/* Wait until all workers are running */ -static -void wait_for_workers( - ecs_world_t *world) -{ - int32_t stage_count = ecs_get_stage_count(world); - bool wait = true; + if (!ecs_query_table_count(result) && result->filter.term_count) { + ecs_add_id(world, entity, EcsEmpty); + } - 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); -} + ecs_poly_modified(world, entity, ecs_query_t); -/* Synchronize workers */ -static -void sync_worker( - ecs_world_t *world) -{ - int32_t stage_count = ecs_get_stage_count(world); + ecs_log_pop_1(); - /* 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 result; +error: + if (result) { + ecs_filter_fini(&result->filter); + ecs_os_free(result); } - - /* 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); + return NULL; } -/* Wait until all threads are waiting on sync point */ -static -void wait_for_sync( - ecs_world_t *world) +void ecs_query_fini( + ecs_query_t *query) { - int32_t stage_count = ecs_get_stage_count(world); - - ecs_dbg_3("#[bold]pipeline: waiting for worker sync"); - - 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); - - ecs_dbg_3("#[bold]pipeline: workers synced"); + ecs_poly_assert(query, ecs_query_t); + ecs_delete(query->world, query->entity); } -/* Signal workers that they can start/resume work */ -static -void signal_workers( - ecs_world_t *world) +const ecs_filter_t* ecs_query_get_filter( + ecs_query_t *query) { - ecs_dbg_3("#[bold]pipeline: signal workers"); - ecs_os_mutex_lock(world->sync_mutex); - ecs_os_cond_broadcast(world->worker_cond); - ecs_os_mutex_unlock(world->sync_mutex); + ecs_poly_assert(query, ecs_query_t); + return &query->filter; } -/** Stop workers */ -static -bool ecs_stop_threads( - ecs_world_t *world) +ecs_iter_t ecs_query_iter( + const ecs_world_t *stage, + ecs_query_t *query) { - bool threads_active = false; + ecs_poly_assert(query, ecs_query_t); + ecs_check(!(query->flags & EcsQueryIsOrphaned), + ECS_INVALID_PARAMETER, NULL); - /* Test if threads are created. Cannot use workers_running, since this is - * a potential race if threads haven't spun up yet. */ - ecs_stage_t *stages = world->stages; - int i, count = world->stage_count; - for (i = 0; i < count; i ++) { - ecs_stage_t *stage = &stages[i]; - if (stage->thread) { - threads_active = true; - break; - } - stage->thread = 0; - }; + ecs_world_t *world = query->world; + ecs_poly_assert(world, ecs_world_t); - /* If no threads are active, just return */ - if (!threads_active) { - return false; + /* Process table events to ensure that the list of iterated tables doesn't + * contain empty tables. */ + flecs_process_pending_tables(world); + + /* If query has order_by, apply sort */ + flecs_query_sort_tables(world, query); + + /* If monitors changed, do query rematching */ + if (!(world->flags & EcsWorldReadonly) && query->flags & EcsQueryHasRefs) { + flecs_eval_component_monitors(world); } - /* Make sure all threads are running, to ensure they catch the signal */ - wait_for_workers(world); + query->prev_match_count = query->match_count; - /* Signal threads should quit */ - world->flags |= EcsWorldQuitWorkers; - signal_workers(world); + /* Prepare iterator */ - /* Join all threads with main */ - for (i = 0; i < count; i ++) { - ecs_os_thread_join(stages[i].thread); - stages[i].thread = 0; + int32_t table_count; + if (query->table_slices) { + table_count = ecs_vector_count(query->table_slices); + } else { + table_count = ecs_query_table_count(query); } - world->flags &= ~EcsWorldQuitWorkers; - ecs_assert(world->workers_running == 0, ECS_INTERNAL_ERROR, NULL); + ecs_query_iter_t it = { + .query = query, + .node = query->list.first, + .last = NULL + }; - /* Deinitialize stages */ - ecs_set_stage_count(world, 1); + if (query->order_by && query->list.count) { + it.node = ecs_vector_first(query->table_slices, ecs_query_table_node_t); + } - return true; -} + ecs_flags32_t flags = 0; + ECS_BIT_COND(flags, EcsIterIsFilter, ECS_BIT_IS_SET(query->filter.flags, + EcsFilterIsFilter)); + ECS_BIT_COND(flags, EcsIterIsInstanced, ECS_BIT_IS_SET(query->filter.flags, + EcsFilterIsInstanced)); -/* -- Private functions -- */ + ecs_iter_t result = { + .real_world = world, + .world = (ecs_world_t*)stage, + .terms = query->filter.terms, + .field_count = query->filter.field_count, + .table_count = table_count, + .flags = flags, + .priv.iter.query = it, + .next = ecs_query_next, + }; -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 EcsPipeline *pq = ecs_get(world, pipeline, EcsPipeline); - ecs_assert(pq != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_filter_t *filter = &query->filter; + if (filter->flags & EcsFilterMatchOnlyThis) { + /* When the query only matches This terms, we can reuse the storage from + * the cache to populate the iterator */ + flecs_iter_init(&result, flecs_iter_cache_ptrs); + } else { + /* Check if non-This terms (like singleton terms) still match */ + ecs_iter_t fit = flecs_filter_iter_w_flags( + world, &query->filter, EcsIterIgnoreThis); + if (!ecs_filter_next(&fit)) { + /* No match, so return nothing */ + ecs_iter_fini(&result); + goto noresults; + } - ecs_pipeline_op_t *op = ecs_vector_first(pq->ops, ecs_pipeline_op_t); - if (!op || !op->no_staging) { - ecs_readonly_begin(world); + /* Initialize iterator with private storage for ids, ptrs, sizes and + * columns so we have a place to store the non-This data */ + flecs_iter_init(&result, flecs_iter_cache_ptrs | flecs_iter_cache_ids | + flecs_iter_cache_columns | flecs_iter_cache_sizes); + + /* Copy the data */ + int32_t term_count = filter->field_count; + if (term_count) { + if (result.ptrs) { + ecs_os_memcpy_n(result.ptrs, fit.ptrs, void*, term_count); + } + ecs_os_memcpy_n(result.ids, fit.ids, ecs_id_t, term_count); + ecs_os_memcpy_n(result.sizes, fit.sizes, ecs_size_t, term_count); + ecs_os_memcpy_n(result.columns, fit.columns, int32_t, term_count); } + + ecs_iter_fini(&fit); } + + return result; +error: +noresults: + return (ecs_iter_t) { + .flags = EcsIterNoResults, + .next = ecs_query_next + }; } -bool ecs_worker_sync( - ecs_world_t *world, - EcsPipeline *pq) +void ecs_query_set_group( + ecs_iter_t *it, + uint64_t group_id) { - ecs_assert(pq != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(pq->cur_op != NULL, ECS_INTERNAL_ERROR, NULL); - bool rebuild = false; - - int32_t stage_count = ecs_get_stage_count(world); - ecs_assert(stage_count != 0, ECS_INTERNAL_ERROR, NULL); - int32_t build_count = world->info.pipeline_build_count_total; + ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(it->next == ecs_query_next, ECS_INVALID_PARAMETER, NULL); + ecs_check(!(it->flags & EcsIterIsValid), ECS_INVALID_PARAMETER, NULL); - /* If there are no threads, merge in place */ - if (stage_count == 1) { - if (!pq->cur_op->no_staging) { - ecs_readonly_end(world); - } + ecs_query_iter_t *qit = &it->priv.iter.query; + ecs_query_t *q = qit->query; + ecs_check(q != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_pipeline_update(world, world->pipeline, false); + ecs_query_table_list_t *node = flecs_query_get_group(q, group_id); + if (!node) { + qit->node = NULL; + return; + } - /* Synchronize all workers. The last worker to reach the sync point will - * signal the main thread, which will perform the merge. */ + ecs_query_table_node_t *first = node->first; + if (first) { + qit->node = node->first; + qit->last = node->last->next; } else { - sync_worker(world); + qit->node = NULL; + qit->last = NULL; } + +error: + return; +} - if (build_count != world->info.pipeline_build_count_total) { - rebuild = true; - } +static +int find_smallest_column( + ecs_table_t *table, + ecs_query_table_match_t *table_data, + ecs_vector_t *sparse_columns) +{ + flecs_switch_term_t *sparse_column_array = + ecs_vector_first(sparse_columns, flecs_switch_term_t); + int32_t i, count = ecs_vector_count(sparse_columns); + int32_t min = INT_MAX, index = 0; - if (stage_count == 1) { - if (!pq->cur_op->no_staging) { - ecs_readonly_begin(world); - } - } + for (i = 0; i < count; i ++) { + /* The array with sparse queries for the matched table */ + flecs_switch_term_t *sparse_column = &sparse_column_array[i]; - return rebuild; -} + /* Pointer to the switch column struct of the table */ + ecs_switch_t *sw = sparse_column->sw_column; -void ecs_worker_end( - ecs_world_t *world) -{ - flecs_stage_from_world(&world); + /* If the sparse column pointer hadn't been retrieved yet, do it now */ + if (!sw) { + /* Get the table column index from the signature column index */ + int32_t table_column_index = table_data->columns[ + sparse_column->signature_column_index]; - int32_t stage_count = ecs_get_stage_count(world); - ecs_assert(stage_count != 0, ECS_INTERNAL_ERROR, NULL); + /* Translate the table column index to switch column index */ + table_column_index -= table->sw_offset; + ecs_assert(table_column_index >= 1, ECS_INTERNAL_ERROR, NULL); - /* If there are no threads, merge in place */ - if (stage_count == 1) { - if (ecs_stage_is_readonly(world)) { - ecs_readonly_end(world); + /* Get the sparse column */ + ecs_data_t *data = &table->data; + sw = sparse_column->sw_column = + &data->sw_columns[table_column_index - 1]; } - /* 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); + /* Find the smallest column */ + int32_t case_count = flecs_switch_case_count(sw, sparse_column->sw_case); + if (case_count < min) { + min = case_count; + index = i + 1; + } } + + return index; } -void ecs_workers_progress( - ecs_world_t *world, - ecs_entity_t pipeline, - ecs_ftime_t delta_time) +typedef struct { + int32_t first; + int32_t count; +} query_iter_cursor_t; + +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, + query_iter_cursor_t *cur, + bool filter) { - ecs_poly_assert(world, ecs_world_t); - ecs_assert(!ecs_is_deferred(world), ECS_INVALID_OPERATION, NULL); - int32_t stage_count = ecs_get_stage_count(world); + bool first_iteration = false; + int32_t sparse_smallest; - ecs_time_t start = {0}; - if (world->flags & EcsWorldMeasureFrameTime) { - ecs_time_measure(&start); + if (!(sparse_smallest = iter->sparse_smallest)) { + sparse_smallest = iter->sparse_smallest = find_smallest_column( + table, matched_table, sparse_columns); + first_iteration = true; } - EcsPipeline *pq = ecs_get_mut(world, pipeline, EcsPipeline); - ecs_assert(pq != NULL, ECS_INTERNAL_ERROR, NULL); + sparse_smallest -= 1; - if (stage_count != pq->iter_count) { - pq->iters = ecs_os_realloc_n(pq->iters, ecs_iter_t, stage_count); - pq->iter_count = stage_count; + flecs_switch_term_t *columns = ecs_vector_first( + sparse_columns, flecs_switch_term_t); + flecs_switch_term_t *column = &columns[sparse_smallest]; + ecs_switch_t *sw, *sw_smallest = column->sw_column; + ecs_entity_t case_smallest = column->sw_case; + + /* 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; + } + } } - ecs_pipeline_update(world, pipeline, true); - ecs_vector_t *ops = pq->ops; - ecs_pipeline_op_t *op = ecs_vector_first(ops, ecs_pipeline_op_t); - if (!op) { - return; + if (first == -1) { + goto done; } - if (stage_count == 1) { - 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_op_t *op_last = ecs_vector_last(ops, ecs_pipeline_op_t); - - /* Make sure workers are running and ready */ - wait_for_workers(world); - - /* Synchronize n times for each op in the pipeline */ - for (; op <= op_last; op ++) { - if (!op->no_staging) { - ecs_readonly_begin(world); + /* 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; } - /* Signal workers that they should start running systems */ - world->workers_waiting = 0; - signal_workers(world); - - /* Wait until all workers are waiting on sync point */ - wait_for_sync(world); - - /* Merge */ - if (!op->no_staging) { - ecs_readonly_end(world); - } + column = &columns[i]; + sw = column->sw_column; - if (ecs_pipeline_update(world, pipeline, false)) { - ecs_assert(!ecs_is_deferred(world), ECS_INVALID_OPERATION, NULL); - pq = ecs_get_mut(world, pipeline, EcsPipeline); - /* Refetch, in case pipeline itself has moved */ - op = pq->cur_op - 1; - op_last = ecs_vector_last(pq->ops, ecs_pipeline_op_t); - ecs_assert(op <= op_last, ECS_INTERNAL_ERROR, NULL); + if (flecs_switch_get(sw, first) != column->sw_case) { + first = flecs_switch_next(sw_smallest, first); + if (first == -1) { + goto done; + } } } - } + } while (i != count); - if (world->flags & EcsWorldMeasureFrameTime) { - world->info.system_time_total += (float)ecs_time_measure(&start); - } + 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; } -/* -- Public functions -- */ +#define BS_MAX ((uint64_t)0xFFFFFFFFFFFFFFFF) -void ecs_set_threads( - ecs_world_t *world, - int32_t threads) +static +int bitset_column_next( + ecs_table_t *table, + ecs_vector_t *bitset_columns, + ecs_query_iter_t *iter, + query_iter_cursor_t *cur) { - ecs_assert(threads <= 1 || ecs_os_has_threading(), ECS_MISSING_OS_API, NULL); + /* 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 + }; - int32_t stage_count = ecs_get_stage_count(world); + /* 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) + }; - 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); - } + int32_t i, count = ecs_vector_count(bitset_columns); + flecs_bitset_term_t *columns = ecs_vector_first( + bitset_columns, flecs_bitset_term_t); + int32_t bs_offset = table->bs_offset; + + int32_t first = iter->bitset_first; + int32_t last = 0; + + for (i = 0; i < count; i ++) { + flecs_bitset_term_t *column = &columns[i]; + ecs_bitset_t *bs = columns[i].bs_column; + + if (!bs) { + int32_t index = column->column_index; + ecs_assert((index - bs_offset >= 0), ECS_INTERNAL_ERROR, NULL); + bs = &table->data.bs_columns[index - bs_offset]; + columns[i].bs_column = bs; } + + int32_t bs_elem_count = bs->count; + int32_t bs_block = first >> 6; + int32_t bs_block_count = ((bs_elem_count - 1) >> 6) + 1; - /* 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); + if (bs_block >= bs_block_count) { + goto done; } - } -} -#endif + uint64_t *data = bs->data; + int32_t bs_start = first & 0x3F; + /* 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; + } -#ifdef FLECS_PIPELINE + bs_start = 0; + remain = BS_MAX; /* Test the full block */ + v = data[bs_block]; + } -static ECS_DTOR(EcsPipeline, ptr, { - ecs_vector_free(ptr->ops); - ecs_os_free(ptr->iters); -}) + /* Step 2: find the first non-empty element in the block */ + while (!(v & bitmask[bs_start])) { + bs_start ++; -typedef enum ComponentWriteState { - NotWritten = 0, - WriteToMain, - WriteToStage -} ComponentWriteState; + /* 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 ++; -typedef struct write_state_t { - ecs_map_t *components; - bool wildcard; -} write_state_t; + if (bs_block_end == bs_block_count) { + break; + } -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; - } -} + v = data[bs_block_end]; + remain = BS_MAX; /* Test the full block */ + } -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); - } -} + /* 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 reset_write_state( - write_state_t *write_state) -{ - ecs_map_clear(write_state->components); - write_state->wildcard = false; -} + /* 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 get_any_write_state( - write_state_t *write_state) -{ - if (write_state->wildcard) { - return WriteToStage; - } + /* 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; + } - 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; + /* 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; return 0; +done: + iter->sparse_smallest = 0; + iter->sparse_first = 0; + return -1; } static -bool check_term_component( - ecs_term_t *term, - bool is_active, - ecs_entity_t component, - write_state_t *write_state) +void mark_columns_dirty( + ecs_query_t *query, + ecs_query_table_match_t *table_data) { - int32_t state = get_write_state(write_state->components, component); + ecs_table_t *table = table_data->table; - ecs_term_id_t *src = &term->src; + if (table && table->dirty_state) { + 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]; + int32_t ti = term->field_index; - if ((src->flags & EcsSelf) && ecs_term_match_this(term) && term->oper != EcsNot) { - switch(term->inout) { - case EcsInOutNone: - /* 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 (term->inout == EcsIn || term->inout == EcsInOutNone) { + /* Don't mark readonly terms dirty */ + continue; } - }; - } else if (!src->id || term->oper == EcsNot) { - bool needs_merge = false; - 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; - } + if (table_data->sources[ti] != 0) { + /* Don't mark table dirty if term is not from the table */ + continue; } - break; - default: - break; - }; - switch(term->inout) { - case EcsInOutDefault: - if ((!(src->flags & EcsSelf) || (!ecs_term_match_this(term))) && (!ecs_term_match_0(term))) { - /* 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); + int32_t index = table_data->columns[ti]; + if (index <= 0) { + /* If term is not set, there's nothing to mark dirty */ + continue; } - break; - default: - break; - }; - if (needs_merge) { - return true; + /* 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] ++; + } } } - - return false; -} - -static -bool check_term( - ecs_term_t *term, - bool is_active, - write_state_t *write_state) -{ - if (term->oper != EcsOr) { - return check_term_component( - term, is_active, term->id, write_state); - } - - return false; } -static -bool check_terms( - ecs_filter_t *filter, - bool is_active, - write_state_t *ws) +bool ecs_query_next( + ecs_iter_t *it) { - bool needs_merge = false; - ecs_term_t *terms = filter->terms; - int32_t t, term_count = filter->term_count; - - /* 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 (ecs_term_match_this(term)) { - needs_merge |= check_term(term, is_active, ws); - } - } + ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(it->next == ecs_query_next, ECS_INVALID_PARAMETER, NULL); - /* Now check staged terms */ - for (t = 0; t < term_count; t ++) { - ecs_term_t *term = &terms[t]; - if (!ecs_term_match_this(term)) { - needs_merge |= check_term(term, is_active, ws); - } + if (flecs_iter_next_row(it)) { + return true; } - return needs_merge; -} - -static -bool flecs_pipeline_is_inactive( - const EcsPipeline *pq, - ecs_table_t *table) -{ - return flecs_id_record_get_table(pq->idr_inactive, table) != NULL; + return flecs_iter_next_instanced(it, ecs_query_next_instanced(it)); +error: + return false; } -static -EcsPoly* flecs_pipeline_term_system( +bool ecs_query_next_instanced( ecs_iter_t *it) { - int32_t index = ecs_search(it->world, it->table, ecs_poly_id(EcsSystem), 0); - ecs_assert(index != -1, ECS_INTERNAL_ERROR, NULL); - EcsPoly *poly = ecs_table_get_column(it->table, index); - ecs_assert(poly != NULL, ECS_INTERNAL_ERROR, NULL); - poly = &poly[it->offset]; - return poly; -} + ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(it->next == ecs_query_next, ECS_INVALID_PARAMETER, NULL); -static -bool flecs_pipeline_build( - ecs_world_t *world, - ecs_entity_t pipeline, - EcsPipeline *pq) -{ - (void)pipeline; + if (ECS_BIT_IS_SET(it->flags, EcsIterNoResults)) { + goto done; + } - ecs_query_iter(world, pq->query); + ECS_BIT_SET(it->flags, EcsIterIsValid); - if (pq->match_count == pq->query->match_count) { - /* No need to rebuild the pipeline */ - return false; - } + ecs_query_iter_t *iter = &it->priv.iter.query; + ecs_query_t *query = iter->query; + ecs_world_t *world = query->world; + ecs_flags32_t flags = query->flags; + const ecs_filter_t *filter = &query->filter; + bool only_this = filter->flags & EcsFilterMatchOnlyThis; - world->info.pipeline_build_count_total ++; - pq->rebuild_count ++; + ecs_poly_assert(world, ecs_world_t); + (void)world; - write_state_t ws = { - .components = ecs_map_new(int32_t, ECS_HI_COMPONENT_ID), - .wildcard = false - }; + query_iter_cursor_t cur; + ecs_query_table_node_t *node, *next, *prev, *last; + if ((prev = iter->prev)) { + /* Match has been iterated, update monitor for change tracking */ + if (flags & EcsQueryHasMonitor) { + flecs_query_sync_match_monitor(query, prev->match); + } + if (flags & EcsQueryHasOutColumns) { + mark_columns_dirty(query, prev->match); + } + } - ecs_pipeline_op_t *op = NULL; - ecs_vector_t *ops = NULL; - ecs_query_t *query = pq->query; + iter->skip_count = 0; - if (pq->ops) { - ecs_vector_free(pq->ops); - } + flecs_iter_validate(it); - bool multi_threaded = false; - bool no_staging = false; - bool first = true; + last = iter->last; + for (node = iter->node; node != last; node = next) { + ecs_query_table_match_t *match = node->match; + ecs_table_t *table = match->table; - /* Iterate systems in pipeline, add ops for running / merging */ - ecs_iter_t it = ecs_query_iter(world, query); - while (ecs_query_next(&it)) { - EcsPoly *poly = flecs_pipeline_term_system(&it); + next = node->next; - int i; - for (i = 0; i < it.count; i ++) { - ecs_system_t *sys = ecs_poly(poly[i].poly, ecs_system_t); - ecs_query_t *q = sys->query; - if (!q) { - continue; + if (table) { + cur.first = node->offset; + cur.count = node->count; + if (!cur.count) { + cur.count = ecs_table_count(table); + + /* List should never contain empty tables */ + ecs_assert(cur.count != 0, ECS_INTERNAL_ERROR, NULL); } - bool needs_merge = false; - bool is_active = !ecs_has_id( - world, it.entities[i], EcsEmpty); - needs_merge = check_terms(&q->filter, is_active, &ws); + ecs_vector_t *bitset_columns = match->bitset_columns; + ecs_vector_t *sparse_columns = match->sparse_columns; - if (is_active) { - if (first) { - multi_threaded = sys->multi_threaded; - no_staging = sys->no_staging; - first = false; - } + if (bitset_columns || sparse_columns) { + bool found = false; - if (sys->multi_threaded != multi_threaded) { - needs_merge = true; - multi_threaded = sys->multi_threaded; - } - if (sys->no_staging != no_staging) { - needs_merge = true; - no_staging = sys->no_staging; - } - } + do { + found = false; - if (needs_merge) { - /* After merge all components will be merged, so reset state */ - reset_write_state(&ws); - op = NULL; + if (bitset_columns) { + if (bitset_column_next(table, bitset_columns, iter, + &cur) == -1) + { + /* No more enabled components for table */ + iter->bitset_first = 0; + break; + } else { + found = true; + next = node; + } + } - /* 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); - } + 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 */ + iter->bitset_first = 0; + break; + } + } else { + found = true; + next = node; + iter->bitset_first = cur.first + cur.count; + } + } + } while (!found); - /* 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); + if (!found) { + continue; + } } - if (!op) { - op = ecs_vector_add(&ops, ecs_pipeline_op_t); - op->count = 0; - op->multi_threaded = false; - op->no_staging = false; - } + it->group_id = match->group_id; + } else { + cur.count = 0; + cur.first = 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; + if (only_this) { + /* If query has only This terms, reuse cache storage */ + it->ids = match->ids; + it->columns = match->columns; + it->sizes = match->sizes; + } else { + /* If query has non-This terms make sure not to overwrite them */ + int32_t t, term_count = filter->term_count; + for (t = 0; t < term_count; t ++) { + ecs_term_t *term = &filter->terms[t]; + if (!ecs_term_match_this(term)) { + continue; } - op->count ++; + + int32_t actual_index = term->field_index; + it->ids[actual_index] = match->ids[actual_index]; + it->columns[actual_index] = match->columns[actual_index]; + it->sizes[actual_index] = match->sizes[actual_index]; } } - } - ecs_map_free(ws.components); + it->sources = match->sources; + it->references = match->references; + it->instance_count = 0; - /* 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_iter_populate_data(world, it, match->table, cur.first, cur.count, + it->ptrs, NULL); - if (!op) { - ecs_dbg("#[green]pipeline#[reset] is empty"); - return true; - } else { - ecs_assert(op != NULL, ECS_INTERNAL_ERROR, NULL); + iter->node = next; + iter->prev = node; + goto yield; + } - /* Add schedule to debug tracing */ - ecs_dbg("#[bold]pipeline rebuild"); - ecs_log_push_1(); +done: +error: + ecs_iter_fini(it); + return false; + +yield: + return true; +} - ecs_dbg("#[green]schedule#[reset]: threading: %d, staging: %d:", - op->multi_threaded, !op->no_staging); - ecs_log_push_1(); +bool ecs_query_changed( + ecs_query_t *query, + const ecs_iter_t *it) +{ + if (it) { + ecs_check(it->next == ecs_query_next, ECS_INVALID_PARAMETER, NULL); + ecs_check(ECS_BIT_IS_SET(it->flags, EcsIterIsValid), + ECS_INVALID_PARAMETER, NULL); - it = ecs_query_iter(world, pq->query); - while (ecs_query_next(&it)) { - if (flecs_pipeline_is_inactive(pq, it.table)) { - continue; - } + ecs_query_table_match_t *qt = + (ecs_query_table_match_t*)it->priv.iter.query.prev; + ecs_assert(qt != NULL, ECS_INVALID_PARAMETER, NULL); - EcsPoly *poly = flecs_pipeline_term_system(&it); + if (!query) { + query = it->priv.iter.query.query; + } else { + ecs_check(query == it->priv.iter.query.query, + ECS_INVALID_PARAMETER, NULL); + } - for (i = 0; i < it.count; i ++) { - ecs_system_t *sys = ecs_poly(poly[i].poly, ecs_system_t); - if (ecs_should_log_1()) { - char *path = ecs_get_fullpath(world, it.entities[i]); - ecs_dbg("#[green]system#[reset] %s", path); - ecs_os_free(path); - } + ecs_check(query != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_poly_assert(query, ecs_query_t); - ran_since_merge ++; - if (ran_since_merge == op[op_index].count) { - ecs_dbg("#[magenta]merge#[reset]"); - ecs_log_pop_1(); - 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_1(); - } + flecs_process_pending_tables(it->real_world); - if (sys->last_frame == (world->info.frame_count_total + 1)) { - last_system = it.entities[i]; + return flecs_query_check_match_monitor(query, qt); + } - /* Can't break from loop yet. It's possible that previously - * inactive systems that ran before the last ran system are - * now active. */ - } - } - } + ecs_poly_assert(query, ecs_query_t); + ecs_check(!(query->flags & EcsQueryIsOrphaned), + ECS_INVALID_PARAMETER, NULL); - ecs_log_pop_1(); - ecs_log_pop_1(); + flecs_process_pending_tables(query->world); + + if (!(query->flags & EcsQueryHasMonitor)) { + query->flags |= EcsQueryHasMonitor; + flecs_query_init_query_monitors(query); + return true; /* Monitors didn't exist yet */ } - /* 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; + if (query->match_count != query->prev_match_count) { + return true; + } - return true; + return flecs_query_check_query_monitor(query); +error: + return false; } -void ecs_pipeline_reset_iter( - ecs_world_t *world, - EcsPipeline *pq) +void ecs_query_skip( + ecs_iter_t *it) { - ecs_pipeline_op_t *op = ecs_vector_first(pq->ops, ecs_pipeline_op_t); - int32_t i, ran_since_merge = 0, op_index = 0, iter_count = pq->iter_count; + ecs_assert(it->next == ecs_query_next, ECS_INVALID_PARAMETER, NULL); + ecs_assert(ECS_BIT_IS_SET(it->flags, EcsIterIsValid), + ECS_INVALID_PARAMETER, NULL); - /* Free state of existing iterators */ - for (i = 0; i < iter_count; i ++) { - ecs_iter_fini(&pq->iters[i]); + if (it->instance_count > it->count) { + it->priv.iter.query.skip_count ++; + if (it->priv.iter.query.skip_count == it->instance_count) { + /* For non-instanced queries, make sure all entities are skipped */ + it->priv.iter.query.prev = NULL; + } + } else { + it->priv.iter.query.prev = NULL; } +} - 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. */ - pq->cur_i = -1; - return; +bool ecs_query_orphaned( + ecs_query_t *query) +{ + ecs_poly_assert(query, ecs_query_t); + return query->flags & EcsQueryIsOrphaned; +} + +char* ecs_query_str( + const ecs_query_t *query) +{ + return ecs_filter_str(query->world, &query->filter); +} + +int32_t ecs_query_table_count( + const ecs_query_t *query) +{ + ecs_run_aperiodic(query->world, EcsAperiodicEmptyTables); + return query->cache.tables.count; +} + +int32_t ecs_query_empty_table_count( + const ecs_query_t *query) +{ + ecs_run_aperiodic(query->world, EcsAperiodicEmptyTables); + return query->cache.empty_tables.count; +} + +int32_t ecs_query_entity_count( + const ecs_query_t *query) +{ + ecs_run_aperiodic(query->world, EcsAperiodicEmptyTables); + + int32_t result = 0; + ecs_table_cache_hdr_t *cur, *last = query->cache.tables.last; + if (!last) { + return 0; } - /* Create new iterators */ - for (i = 0; i < iter_count; i ++) { - pq->iters[i] = ecs_query_iter(world, pq->query); + for (cur = query->cache.tables.first; cur != NULL; cur = cur->next) { + result += ecs_table_count(cur->table); } - /* Move iterator to last ran system */ - ecs_iter_t *it = &pq->iters[0]; - while (ecs_query_next(it)) { - /* Progress worker iterators */ - for (i = 1; i < iter_count; i ++) { - bool hasnext = ecs_query_next(&pq->iters[i]); - ecs_assert_var(hasnext, ECS_INTERNAL_ERROR, NULL); - ecs_assert(it->table == pq->iters[i].table, - ECS_INTERNAL_ERROR, NULL); - ecs_assert(it->count == pq->iters[i].count, - ECS_INTERNAL_ERROR, NULL); - } + return result; +} - if (flecs_pipeline_is_inactive(pq, it->table)) { - continue; - } +ecs_entity_t ecs_query_entity( + const ecs_query_t *query) +{ + return query->entity; +} - for (i = 0; i < it->count; i ++) { - ran_since_merge ++; - if (ran_since_merge == op[op_index].count) { - ran_since_merge = 0; - op_index ++; - } - if (it->entities[i] == pq->last_system) { - pq->cur_op = &op[op_index]; - pq->cur_i = i; - return; - } - } +/* Marker object used to differentiate a component vs. a tag edge */ +static ecs_table_diff_t ecs_table_edge_is_component; + +/* Id sequence (type) utilities */ + +static +uint64_t flecs_type_hash(const void *ptr) { + const ecs_type_t *type = ptr; + ecs_id_t *ids = type->array; + int32_t count = type->count; + return flecs_hash(ids, count * ECS_SIZEOF(ecs_id_t)); +} + +static +int flecs_type_compare(const void *ptr_1, const void *ptr_2) { + const ecs_type_t *type_1 = ptr_1; + const ecs_type_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); } - ecs_abort(ECS_INTERNAL_ERROR, NULL); + const ecs_id_t *ids_1 = type_1->array; + const ecs_id_t *ids_2 = type_2->array; + int result = 0; + + int32_t i; + for (i = 0; !result && (i < count_1); i ++) { + ecs_id_t id_1 = ids_1[i]; + ecs_id_t id_2 = ids_2[i]; + result = (id_1 > id_2) - (id_1 < id_2); + } + + return result; } -bool ecs_pipeline_update( - ecs_world_t *world, - ecs_entity_t pipeline, - bool start_of_frame) +void flecs_table_hashmap_init(ecs_hashmap_t *hm) { + flecs_hashmap_init(hm, ecs_type_t, ecs_table_t*, + flecs_type_hash, flecs_type_compare); +} + +/* Find location where to insert id into type */ +static +int flecs_type_find_insert( + const ecs_type_t *type, + int32_t offset, + ecs_id_t to_add) { - ecs_poly_assert(world, ecs_world_t); - ecs_assert(!(world->flags & EcsWorldReadonly), ECS_INVALID_OPERATION, NULL); - ecs_assert(pipeline != 0, ECS_INTERNAL_ERROR, NULL); + ecs_id_t *array = type->array; + int32_t i, count = type->count; - /* 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) { - ecs_run_aperiodic(world, 0); + for (i = offset; i < count; i ++) { + ecs_id_t id = array[i]; + if (id == to_add) { + return -1; + } + if (id > to_add) { + return i; + } } + return i; +} - EcsPipeline *pq = ecs_get_mut(world, pipeline, EcsPipeline); - ecs_assert(pq != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(pq->query != NULL, ECS_INTERNAL_ERROR, NULL); +/* Find location of id in type */ +static +int flecs_type_find( + const ecs_type_t *type, + ecs_id_t id) +{ + ecs_id_t *array = type->array; + int32_t i, count = type->count; - bool rebuilt = flecs_pipeline_build(world, pipeline, pq); - if (start_of_frame) { - /* Initialize iterators */ - int32_t i, count = pq->iter_count; - for (i = 0; i < count; i ++) { - pq->iters[i] = ecs_query_iter(world, pq->query); + for (i = 0; i < count; i ++) { + ecs_id_t cur = array[i]; + if (ecs_id_match(cur, id)) { + return i; + } + if (cur > id) { + return -1; } - pq->cur_op = ecs_vector_first(pq->ops, ecs_pipeline_op_t); - } else if (rebuilt) { - /* If pipeline got updated and we were mid frame, reset iterators */ - ecs_pipeline_reset_iter(world, pq); - return true; - } else { - pq->cur_op += 1; } - return false; + return -1; } -void ecs_run_pipeline( - ecs_world_t *world, - ecs_entity_t pipeline, - ecs_ftime_t delta_time) +/* Count number of matching ids */ +static +int flecs_type_count_matches( + const ecs_type_t *type, + ecs_id_t wildcard, + int32_t offset) { - ecs_assert(world != NULL, ECS_INVALID_OPERATION, NULL); + ecs_id_t *array = type->array; + int32_t i = offset, count = type->count; - if (!pipeline) { - pipeline = world->pipeline; + for (; i < count; i ++) { + ecs_id_t cur = array[i]; + if (!ecs_id_match(cur, wildcard)) { + break; + } } - ecs_assert(pipeline != 0, ECS_INVALID_PARAMETER, NULL); - - /* 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->flags & EcsWorldReadonly), - ECS_INVALID_OPERATION, NULL); - - /* Forward to worker_progress. This function handles staging, threading - * and synchronization across workers. */ - ecs_workers_progress(world, pipeline, delta_time); - return; + return i - offset; +} - /* 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); +/* Create type from source type with id */ +static +int flecs_type_new_with( + ecs_type_t *dst, + const ecs_type_t *src, + ecs_id_t with) +{ + ecs_id_t *src_array = src->array; + int32_t at = flecs_type_find_insert(src, 0, with); + if (at == -1) { + return -1; } - ecs_stage_t *stage = flecs_stage_from_world(&world); + int32_t dst_count = src->count + 1; + ecs_id_t *dst_array = ecs_os_malloc_n(ecs_id_t, dst_count); + dst->count = dst_count; + dst->array = dst_array; - EcsPipeline *pq = (EcsPipeline*)ecs_get(world, pipeline, EcsPipeline); - ecs_assert(pq != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(pq->query != NULL, ECS_INTERNAL_ERROR, NULL); + if (at) { + ecs_os_memcpy_n(dst_array, src_array, ecs_id_t, at); + } - 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; + int32_t remain = src->count - at; + if (remain) { + ecs_os_memcpy_n(&dst_array[at + 1], &src_array[at], ecs_id_t, remain); + } - int32_t stage_index = ecs_get_stage_id(stage->thread_ctx); - int32_t stage_count = ecs_get_stage_count(world); + dst_array[at] = with; - ecs_worker_begin(stage->thread_ctx); + return 0; +} - ecs_iter_t *it = &pq->iters[stage_index]; - while (ecs_query_next(it)) { - if (flecs_pipeline_is_inactive(pq, it->table)) { - continue; /* Skip inactive systems */ +/* Create type from source type without ids matching wildcard */ +static +int flecs_type_new_filtered( + ecs_type_t *dst, + const ecs_type_t *src, + ecs_id_t wildcard, + int32_t at) +{ + *dst = flecs_type_copy(src); + ecs_id_t *dst_array = dst->array; + ecs_id_t *src_array = src->array; + if (at) { + ecs_os_memcpy_n(dst_array, src_array, ecs_id_t, at); + } + + int32_t i = at + 1, w = at, count = src->count; + for (; i < count; i ++) { + ecs_id_t id = src_array[i]; + if (!ecs_id_match(id, wildcard)) { + dst_array[w] = id; + w ++; } + } - EcsPoly *poly = flecs_pipeline_term_system(it); - int32_t i, count = it->count; - for(i = 0; i < count; i ++) { - ecs_entity_t e = it->entities[i]; - ecs_system_t *sys = ecs_poly(poly[i].poly, ecs_system_t); + dst->count = w; + if (w != count) { + dst->array = ecs_os_realloc_n(dst->array, ecs_id_t, w); + } - ecs_assert(sys->entity == e, ECS_INTERNAL_ERROR, NULL); + return 0; +} - sys->last_frame = world->info.frame_count_total + 1; +/* Create type from source type without id */ +static +int flecs_type_new_without( + ecs_type_t *dst, + const ecs_type_t *src, + ecs_id_t without) +{ + ecs_id_t *src_array = src->array; + int32_t count = 1, at = flecs_type_find(src, without); + if (at == -1) { + return -1; + } - if (!stage_index || op->multi_threaded) { - ecs_stage_t *s = NULL; - if (!op->no_staging) { - s = stage; - } + int32_t src_count = src->count; + if (src_count == 1) { + dst->array = NULL; + dst->count = 0; + return 0; + } - ecs_run_intern(world, s, e, sys, stage_index, - stage_count, delta_time, 0, 0, NULL); + if (ecs_id_is_wildcard(without)) { + if (ECS_IS_PAIR(without)) { + ecs_entity_t r = ECS_PAIR_FIRST(without); + ecs_entity_t o = ECS_PAIR_SECOND(without); + if (r == EcsWildcard && o != EcsWildcard) { + return flecs_type_new_filtered(dst, src, without, at); } + } + count += flecs_type_count_matches(src, without, at + 1); + } - ran_since_merge ++; - world->info.systems_ran_frame ++; + int32_t dst_count = src_count - count; + dst->count = dst_count; + if (!dst_count) { + dst->array = NULL; + return 0; + } - if (op != op_last && ran_since_merge == op->count) { - ran_since_merge = 0; + ecs_id_t *dst_array = ecs_os_malloc_n(ecs_id_t, dst_count); + dst->array = dst_array; - /* 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. */ - bool rebuild = ecs_worker_sync(world, pq); - pq = (EcsPipeline*)ecs_get(world, pipeline, EcsPipeline); - if (rebuild) { - i = pq->cur_i; - count = it->count; - } + if (at) { + ecs_os_memcpy_n(dst_array, src_array, ecs_id_t, at); + } - poly = flecs_pipeline_term_system(it); - op = pq->cur_op; - op_last = ecs_vector_last(pq->ops, ecs_pipeline_op_t); - } - } + int32_t remain = dst_count - at; + if (remain) { + ecs_os_memcpy_n( + &dst_array[at], &src_array[at + count], ecs_id_t, remain); } - ecs_worker_end(stage->thread_ctx); + return 0; } -bool ecs_progress( - ecs_world_t *world, - ecs_ftime_t user_delta_time) +/* Copy type */ +ecs_type_t flecs_type_copy( + const ecs_type_t *src) { - ecs_ftime_t delta_time = ecs_frame_begin(world, user_delta_time); - - ecs_dbg_3("#[bold]progress#[reset](dt = %.2f)", (double)delta_time); - ecs_log_push_3(); - ecs_run_pipeline(world, 0, delta_time); - ecs_log_pop_3(); + int32_t src_count = src->count; + return (ecs_type_t) { + .array = ecs_os_memdup_n(src->array, ecs_id_t, src_count), + .count = src_count + }; +} - ecs_frame_end(world); +/* Free type */ +void flecs_type_free( + ecs_type_t *type) +{ + ecs_os_free(type->array); +} - return !ECS_BIT_IS_SET(world->flags, EcsWorldQuit); +/* Add to type */ +static +void flecs_type_add( + ecs_type_t *type, + ecs_id_t add) +{ + ecs_type_t new_type; + int res = flecs_type_new_with(&new_type, type, add); + if (res != -1) { + flecs_type_free(type); + type->array = new_type.array; + type->count = new_type.count; + } } -void ecs_set_time_scale( - ecs_world_t *world, - ecs_ftime_t scale) +/* Graph edge utilities */ + +static +void table_diff_free( + ecs_table_diff_t *diff) { - world->info.time_scale = scale; + 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); + ecs_os_free(diff); } -void ecs_reset_clock( +static +ecs_graph_edge_t* graph_edge_new( ecs_world_t *world) { - world->info.world_time_total = 0; - world->info.world_time_total_raw = 0; + ecs_graph_edge_t *result = (ecs_graph_edge_t*)world->store.first_free; + if (result) { + world->store.first_free = result->hdr.next; + ecs_os_zeromem(result); + } else { + result = ecs_os_calloc_t(ecs_graph_edge_t); + } + return result; } -void ecs_set_pipeline( +static +void graph_edge_free( ecs_world_t *world, - ecs_entity_t pipeline) + ecs_graph_edge_t *edge) { - ecs_poly_assert(world, ecs_world_t); - ecs_check( ecs_get(world, pipeline, EcsPipeline) != NULL, - ECS_INVALID_PARAMETER, "not a pipeline"); + if (world->flags & EcsWorldFini) { + ecs_os_free(edge); + } else { + edge->hdr.next = world->store.first_free; + world->store.first_free = &edge->hdr; + } +} - world->pipeline = pipeline; -error: - return; +static +ecs_graph_edge_t* ensure_hi_edge( + ecs_world_t *world, + ecs_graph_edges_t *edges, + ecs_id_t id) +{ + ecs_map_init_if(&edges->hi, ecs_graph_edge_t*, 1); + + ecs_graph_edge_t **ep = ecs_map_ensure(&edges->hi, ecs_graph_edge_t*, id); + ecs_graph_edge_t *edge = ep[0]; + if (edge) { + return edge; + } + + if (id < ECS_HI_COMPONENT_ID) { + edge = &edges->lo[id]; + } else { + edge = graph_edge_new(world); + } + + ep[0] = edge; + return edge; } -ecs_entity_t ecs_get_pipeline( - const ecs_world_t *world) +static +ecs_graph_edge_t* ensure_edge( + ecs_world_t *world, + ecs_graph_edges_t *edges, + ecs_id_t id) { - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - world = ecs_get_world(world); - return world->pipeline; -error: - return 0; + ecs_graph_edge_t *edge; + + if (id < ECS_HI_COMPONENT_ID) { + if (!edges->lo) { + edges->lo = ecs_os_calloc_n(ecs_graph_edge_t, ECS_HI_COMPONENT_ID); + } + edge = &edges->lo[id]; + } else { + ecs_map_init_if(&edges->hi, ecs_graph_edge_t*, 1); + edge = ensure_hi_edge(world, edges, id); + } + + return edge; } -ecs_entity_t ecs_pipeline_init( +static +void disconnect_edge( ecs_world_t *world, - const ecs_pipeline_desc_t *desc) + ecs_id_t id, + ecs_graph_edge_t *edge) { - ecs_poly_assert(world, ecs_world_t); - ecs_assert(desc != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(edge != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(edge->id == id, ECS_INTERNAL_ERROR, NULL); + (void)id; - ecs_entity_t result = desc->entity; - if (!result) { - result = ecs_new(world, 0); + /* Remove backref from destination table */ + ecs_graph_edge_hdr_t *next = edge->hdr.next; + ecs_graph_edge_hdr_t *prev = edge->hdr.prev; + + if (next) { + next->prev = prev; + } + if (prev) { + prev->next = next; } - ecs_query_desc_t qd = desc->query; - if (!qd.order_by) { - qd.order_by = flecs_entity_compare; + /* Remove data associated with edge */ + ecs_table_diff_t *diff = edge->diff; + if (diff && diff != &ecs_table_edge_is_component) { + table_diff_free(diff); } - qd.entity = result; - ecs_query_t *query = ecs_query_init(world, &qd); - if (!query) { - ecs_delete(world, result); - return 0; + /* If edge id is low, clear it from fast lookup array */ + if (id < ECS_HI_COMPONENT_ID) { + ecs_os_memset_t(edge, 0, ecs_graph_edge_t); + } else { + graph_edge_free(world, edge); } +} - ecs_assert(query->filter.terms[0].id == EcsSystem, - ECS_INVALID_PARAMETER, NULL); +static +void remove_edge( + ecs_world_t *world, + ecs_graph_edges_t *edges, + ecs_id_t id, + ecs_graph_edge_t *edge) +{ + ecs_assert(edges != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(ecs_map_is_initialized(&edges->hi), ECS_INTERNAL_ERROR, NULL); + disconnect_edge(world, id, edge); + ecs_map_remove(&edges->hi, id); +} - ecs_set(world, result, EcsPipeline, { - .query = query, - .match_count = -1, - .idr_inactive = flecs_id_record_ensure(world, EcsEmpty) - }); +static +void init_edges( + ecs_graph_edges_t *edges) +{ + edges->lo = NULL; + ecs_os_zeromem(&edges->hi); +} - return result; +static +void init_node( + ecs_graph_node_t *node) +{ + init_edges(&node->add); + init_edges(&node->remove); } -/* -- Module implementation -- */ +bool flecs_table_records_update_empty( + ecs_table_t *table) +{ + bool result = false; + bool is_empty = ecs_table_count(table) == 0; + + int32_t i, count = table->record_count; + for (i = 0; i < count; i ++) { + ecs_table_record_t *tr = &table->records[i]; + ecs_table_cache_t *cache = tr->hdr.cache; + result |= ecs_table_cache_set_empty(cache, table, is_empty); + } + + return result; +} static -void FlecsPipelineFini( +void init_table( ecs_world_t *world, - void *ctx) + ecs_table_t *table, + ecs_table_t *prev) { - (void)ctx; - if (ecs_get_stage_count(world)) { - ecs_set_threads(world, 0); - } + table->type_info = NULL; + table->flags = 0; + table->dirty_state = NULL; + table->lock = 0; + table->refcount = 1; + table->generation = 0; - ecs_assert(world->workers_running == 0, ECS_INTERNAL_ERROR, NULL); + init_node(&table->node); + + flecs_table_init(world, table, prev); } -#define flecs_bootstrap_phase(world, phase, depends_on)\ - flecs_bootstrap_tag(world, phase);\ - _flecs_bootstrap_phase(world, phase, depends_on) static -void _flecs_bootstrap_phase( +ecs_table_t *create_table( ecs_world_t *world, - ecs_entity_t phase, - ecs_entity_t depends_on) + ecs_type_t *type, + flecs_hashmap_result_t table_elem, + ecs_table_t *prev) { - ecs_add_id(world, phase, EcsPhase); - if (depends_on) { - ecs_add_pair(world, phase, EcsDependsOn, depends_on); + 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); + result->type = *type; + + if (ecs_should_log_2()) { + char *expr = ecs_type_str(world, &result->type); + ecs_dbg_2( + "#[green]table#[normal] [%s] #[green]created#[reset] with id %d", + expr, result->id); + ecs_os_free(expr); } -} -void FlecsPipelineImport( - ecs_world_t *world) -{ - ECS_MODULE(world, FlecsPipeline); - ECS_IMPORT(world, FlecsSystem); + ecs_log_push_2(); - ecs_set_name_prefix(world, "Ecs"); + /* Store table in table hashmap */ + *(ecs_table_t**)table_elem.value = result; - flecs_bootstrap_component(world, EcsPipeline); - flecs_bootstrap_tag(world, EcsPhase); + /* Set keyvalue to one that has the same lifecycle as the table */ + *(ecs_type_t*)table_elem.key = result->type; - flecs_bootstrap_phase(world, EcsPreFrame, 0); - flecs_bootstrap_phase(world, EcsOnLoad, EcsPreFrame); - flecs_bootstrap_phase(world, EcsPostLoad, EcsOnLoad); - flecs_bootstrap_phase(world, EcsPreUpdate, EcsPostLoad); - flecs_bootstrap_phase(world, EcsOnUpdate, EcsPreUpdate); - flecs_bootstrap_phase(world, EcsOnValidate, EcsOnUpdate); - flecs_bootstrap_phase(world, EcsPostUpdate, EcsOnValidate); - flecs_bootstrap_phase(world, EcsPreStore, EcsPostUpdate); - flecs_bootstrap_phase(world, EcsOnStore, EcsPreStore); - flecs_bootstrap_phase(world, EcsPostFrame, EcsOnStore); + init_table(world, result, prev); - ecs_set_hooks(world, EcsPipeline, { - .ctor = ecs_default_ctor, - .dtor = ecs_dtor(EcsPipeline) + flecs_notify_queries(world, &(ecs_query_event_t) { + .kind = EcsQueryTableMatch, + .table = result }); - world->pipeline = ecs_pipeline_init(world, &(ecs_pipeline_desc_t){ - .entity = ecs_entity(world, { .name = "BuiltinPipeline" }), - .query = { - .filter.terms = { - { .id = EcsSystem }, - { .id = EcsPhase, .src.flags = EcsCascade, .src.trav = EcsDependsOn } - }, - .order_by = flecs_entity_compare - } - }); + /* Update counters */ + world->info.table_count ++; + world->info.table_record_count += result->record_count; + world->info.table_storage_count += result->storage_count; + world->info.empty_table_count ++; + world->info.table_create_total ++; + + if (!result->storage_count) { + world->info.tag_table_count ++; + } else { + world->info.trivial_table_count += !(result->flags & EcsTableIsComplex); + } - /* Cleanup thread administration when world is destroyed */ - ecs_atfini(world, FlecsPipelineFini, NULL); + ecs_log_pop_2(); + + return result; } -#endif +static +ecs_table_t* find_or_create( + ecs_world_t *world, + ecs_type_t *type, + bool own_type, + ecs_table_t *prev) +{ + ecs_poly_assert(world, ecs_world_t); + int32_t id_count = type->count; + if (!id_count) { + return &world->store.root; + } -#ifdef FLECS_OS_API_IMPL -#ifdef ECS_TARGET_WINDOWS -#ifndef WIN32_LEAN_AND_MEAN -#define WIN32_LEAN_AND_MEAN -#endif -#include -#include + ecs_table_t *table; + flecs_hashmap_result_t elem = flecs_hashmap_ensure( + &world->store.table_map, type, ecs_table_t*); + if ((table = *(ecs_table_t**)elem.value)) { + if (own_type) { + flecs_type_free(type); + } + return table; + } -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; -} + /* If we get here, table needs to be created which is only allowed when the + * application is not currently in progress */ + ecs_assert(!(world->flags & EcsWorldReadonly), ECS_INTERNAL_ERROR, NULL); -static -void* win_thread_join( - ecs_os_thread_t thr) -{ - HANDLE *thread = (HANDLE*)(uintptr_t)thr; - DWORD r = WaitForSingleObject(*thread, INFINITE); - if (r == WAIT_FAILED) { - ecs_err("win_thread_join: WaitForSingleObject failed"); + /* If we get here, the table has not been found, so create it. */ + if (own_type) { + return create_table(world, type, elem, prev); } - ecs_os_free(thread); - return NULL; + + ecs_type_t copy = flecs_type_copy(type); + return create_table(world, ©, elem, prev); } static -int32_t win_ainc( - int32_t *count) +void ids_append( + ecs_type_t *ids, + ecs_id_t id) { - return InterlockedIncrement(count); + ids->array = ecs_os_realloc_n(ids->array, ecs_id_t, ids->count + 1); + ids->array[ids->count ++] = id; } static -int32_t win_adec( - int32_t *count) +void diff_insert_isa( + ecs_world_t *world, + ecs_table_t *table, + ecs_table_diff_t *base_diff, + ecs_type_t *append_to, + ecs_type_t *append_from, + ecs_id_t add) { - return InterlockedDecrement(count); -} + ecs_entity_t base = ecs_pair_second(world, add); + ecs_table_t *base_table = ecs_get_table(world, base); + if (!base_table) { + return; + } -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; -} + ecs_type_t base_type = base_table->type; + ecs_table_t *table_wo_base = base_table; -static -void win_mutex_free( - ecs_os_mutex_t m) -{ - CRITICAL_SECTION *mutex = (CRITICAL_SECTION*)(intptr_t)m; - DeleteCriticalSection(mutex); - ecs_os_free(mutex); + /* If the table does not have a component from the base, it should + * emit an OnSet event */ + ecs_id_t *ids = base_type.array; + int32_t j, i, count = base_type.count; + for (i = 0; i < count; i ++) { + ecs_id_t id = ids[i]; + + if (ECS_HAS_RELATION(id, EcsIsA)) { + /* The base has an IsA relationship. 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_search(world, table, base_id, 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_search(world, table, id, NULL) == -1) { + ids_append(append_to, id); + } + } } static -void win_mutex_lock( - ecs_os_mutex_t m) +void diff_insert_added_isa( + ecs_world_t *world, + ecs_table_t *table, + ecs_table_diff_t *diff, + ecs_id_t id) { - CRITICAL_SECTION *mutex = (CRITICAL_SECTION*)(intptr_t)m; - EnterCriticalSection(mutex); + ecs_table_diff_t base_diff; + diff_insert_isa(world, table, &base_diff, &diff->on_set, + &base_diff.un_set, id); } static -void win_mutex_unlock( - ecs_os_mutex_t m) +void diff_insert_removed_isa( + ecs_world_t *world, + ecs_table_t *table, + ecs_table_diff_t *diff, + ecs_id_t id) { - CRITICAL_SECTION *mutex = (CRITICAL_SECTION*)(intptr_t)m; - LeaveCriticalSection(mutex); + ecs_table_diff_t base_diff; + diff_insert_isa(world, table, &base_diff, &diff->un_set, + &base_diff.un_set, id); } 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 diff_insert_added( + ecs_world_t *world, + ecs_table_t *table, + ecs_table_diff_t *diff, + ecs_id_t id) { - (void)c; -} + diff->added.array[diff->added.count ++] = id; -static -void win_cond_signal( - ecs_os_cond_t c) -{ - CONDITION_VARIABLE *cond = (CONDITION_VARIABLE*)(intptr_t)c; - WakeConditionVariable(cond); + if (ECS_HAS_RELATION(id, EcsIsA)) { + diff_insert_added_isa(world, table, diff, id); + } } -static -void win_cond_broadcast( - ecs_os_cond_t c) +static +void diff_insert_removed( + ecs_world_t *world, + ecs_table_t *table, + ecs_table_diff_t *diff, + ecs_id_t id) { - CONDITION_VARIABLE *cond = (CONDITION_VARIABLE*)(intptr_t)c; - WakeAllConditionVariable(cond); -} + diff->removed.array[diff->removed.count ++] = id; -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); -} + if (ECS_HAS_RELATION(id, EcsIsA)) { + /* Removing an IsA relationship 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; + } -static bool win_time_initialized; -static double win_time_freq; -static LARGE_INTEGER win_time_start; + if (table->flags & EcsTableHasIsA) { + if (!ecs_get_typeid(world, id)) { + /* Do nothing if id is not a component */ + return; + } -static -void win_time_setup(void) { - if ( win_time_initialized) { - return; + /* 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_search_relation(world, table, 0, id, EcsIsA, + EcsUp, 0, 0, 0) != -1) + { + ids_append(&diff->on_set, id); + return; + } } - - win_time_initialized = true; - LARGE_INTEGER freq; - QueryPerformanceFrequency(&freq); - QueryPerformanceCounter(&win_time_start); - win_time_freq = (double)freq.QuadPart / 1000000000.0; + if (ecs_get_typeid(world, id) != 0) { + ids_append(&diff->un_set, id); + } } static -void win_sleep( - int32_t sec, - int32_t nanosec) +void compute_table_diff( + ecs_world_t *world, + ecs_table_t *node, + ecs_table_t *next, + ecs_graph_edge_t *edge, + ecs_id_t id) { - HANDLE timer; - LARGE_INTEGER ft; + if (node == next) { + return; + } - ft.QuadPart = -((int64_t)sec * 10000000 + (int64_t)nanosec / 100); + if (ECS_IS_PAIR(id)) { + ecs_id_record_t *idr = flecs_id_record_get(world, ecs_pair( + ECS_PAIR_FIRST(id), EcsWildcard)); + if (idr->flags & EcsIdUnion) { + id = ecs_pair(EcsUnion, ECS_PAIR_FIRST(id)); + } + } - timer = CreateWaitableTimer(NULL, TRUE, NULL); - SetWaitableTimer(timer, &ft, 0, NULL, NULL, 0); - WaitForSingleObject(timer, INFINITE); - CloseHandle(timer); -} + ecs_type_t node_type = node->type; + ecs_type_t next_type = next->type; -static double win_time_freq; -static ULONG win_current_resolution; + ecs_id_t *ids_node = node_type.array; + ecs_id_t *ids_next = next_type.array; + int32_t i_node = 0, node_count = node_type.count; + int32_t i_next = 0, next_count = next_type.count; + int32_t added_count = 0; + int32_t removed_count = 0; + bool trivial_edge = !ECS_HAS_RELATION(id, EcsIsA); -static -void win_enable_high_timer_resolution(bool enable) -{ - HMODULE hntdll = GetModuleHandle((LPCTSTR)"ntdll.dll"); - if (!hntdll) { - return; - } + /* 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]; - LONG (__stdcall *pNtSetTimerResolution)( - ULONG desired, BOOLEAN set, ULONG * current); + bool added = id_next < id_node; + bool removed = id_node < id_next; - pNtSetTimerResolution = (LONG(__stdcall*)(ULONG, BOOLEAN, ULONG*)) - GetProcAddress(hntdll, "NtSetTimerResolution"); + trivial_edge &= !added || id_next == id; + trivial_edge &= !removed || id_node == id; - if(!pNtSetTimerResolution) { - return; + added_count += added; + removed_count += removed; + + i_node += id_node <= id_next; + i_next += id_next <= id_node; } - ULONG current, resolution = 10000; /* 1 ms */ + added_count += next_count - i_next; + removed_count += node_count - i_node; - if (!enable && win_current_resolution) { - pNtSetTimerResolution(win_current_resolution, 0, ¤t); - win_current_resolution = 0; - return; - } else if (!enable) { - return; + trivial_edge &= (added_count + removed_count) <= 1 && + !ecs_id_is_wildcard(id); + + if (trivial_edge && removed_count && (node->flags & EcsTableHasIsA)) { + /* If a single component was removed from a table with an IsA, + * relationship it could reexpose an inherited component. If this is + * the case, don't treat it as a trivial edge. */ + if (ecs_search_relation(world, next, 0, id, EcsIsA, EcsUp, 0, 0, 0) != -1) { + trivial_edge = false; + } } - if (resolution == win_current_resolution) { + if (trivial_edge) { + /* If edge is trivial there's no need to create a diff element for it. + * Store whether the id is a tag or not, so that we can still tell + * whether an UnSet handler should be called or not. */ + if (node->storage_table != next->storage_table) { + edge->diff = &ecs_table_edge_is_component; + } return; } - if (win_current_resolution) { - pNtSetTimerResolution(win_current_resolution, 0, ¤t); + ecs_table_diff_t *diff = ecs_os_calloc_t(ecs_table_diff_t); + edge->diff = diff; + if (added_count) { + diff->added.array = ecs_os_malloc_n(ecs_id_t, added_count); + diff->added.count = 0; + } + if (removed_count) { + diff->removed.array = ecs_os_malloc_n(ecs_id_t, removed_count); + diff->removed.count = 0; } - if (pNtSetTimerResolution(resolution, 1, ¤t)) { - /* Try setting a lower resolution */ - resolution *= 2; - if(pNtSetTimerResolution(resolution, 1, ¤t)) return; + 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]; + + 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); + } + + i_node += id_node <= id_next; + i_next += id_next <= id_node; } - win_current_resolution = resolution; + for (; i_next < next_count; i_next ++) { + diff_insert_added(world, node, diff, ids_next[i_next]); + } + for (; i_node < node_count; i_node ++) { + diff_insert_removed(world, next, diff, ids_node[i_node]); + } + + ecs_assert(diff->added.count == added_count, ECS_INTERNAL_ERROR, NULL); + ecs_assert(diff->removed.count == removed_count, ECS_INTERNAL_ERROR, NULL); } static -uint64_t win_time_now(void) { - uint64_t now; +void flecs_add_overrides_for_base( + ecs_world_t *world, + ecs_type_t *dst_type, + ecs_id_t pair) +{ + ecs_entity_t base = ecs_pair_second(world, pair); + ecs_assert(base != 0, ECS_INTERNAL_ERROR, NULL); + ecs_table_t *base_table = ecs_get_table(world, base); + if (!base_table) { + return; + } + + ecs_id_t *ids = base_table->type.array; - LARGE_INTEGER qpc_t; - QueryPerformanceCounter(&qpc_t); - now = (uint64_t)(qpc_t.QuadPart / win_time_freq); + ecs_flags32_t flags = base_table->flags; + if (flags & EcsTableHasOverrides) { + int32_t i, count = base_table->type.count; + for (i = 0; i < count; i ++) { + ecs_id_t id = ids[i]; + if (ECS_HAS_ID_FLAG(id, OVERRIDE)) { + flecs_type_add(dst_type, id & ~ECS_OVERRIDE); + } + } + } - return now; + if (flags & EcsTableHasIsA) { + const ecs_table_record_t *tr = flecs_id_record_get_table( + world->idr_isa_wildcard, base_table); + ecs_assert(tr != NULL, ECS_INTERNAL_ERROR, NULL); + int32_t i = tr->column, end = i + tr->count; + for (; i != end; i ++) { + flecs_add_overrides_for_base(world, dst_type, ids[i]); + } + } } static -void win_fini(void) { - if (ecs_os_api.flags_ & EcsOsApiHighResolutionTimer) { - win_enable_high_timer_resolution(false); +void flecs_add_with_property( + ecs_world_t *world, + ecs_id_record_t *idr_with_wildcard, + ecs_type_t *dst_type, + ecs_entity_t r, + ecs_entity_t o) +{ + r = ecs_get_alive(world, r); + + /* Check if component/relationship has With pairs, which contain ids + * that need to be added to the table. */ + ecs_table_t *table = ecs_get_table(world, r); + if (!table) { + return; } -} + + const ecs_table_record_t *tr = flecs_id_record_get_table( + idr_with_wildcard, table); + if (tr) { + int32_t i = tr->column, end = i + tr->count; + ecs_id_t *ids = table->type.array; -void ecs_set_os_api_impl(void) { - ecs_os_set_api_defaults(); + for (; i < end; i ++) { + ecs_id_t id = ids[i]; + ecs_assert(ECS_PAIR_FIRST(id) == EcsWith, ECS_INTERNAL_ERROR, NULL); + ecs_id_t ra = ECS_PAIR_SECOND(id); + ecs_id_t a = ra; + if (o) { + a = ecs_pair(ra, o); + } - ecs_os_api_t api = ecs_os_api; + flecs_type_add(dst_type, a); - 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; - api.sleep_ = win_sleep; - api.now_ = win_time_now; - api.fini_ = win_fini; + flecs_add_with_property(world, idr_with_wildcard, dst_type, ra, o); + } + } - win_time_setup(); +} - if (ecs_os_api.flags_ & EcsOsApiHighResolutionTimer) { - win_enable_high_timer_resolution(true); +static +ecs_table_t* flecs_find_table_with( + ecs_world_t *world, + ecs_table_t *node, + ecs_id_t with) +{ + ecs_ensure_id(world, with); + + ecs_id_record_t *idr = NULL; + ecs_entity_t r = 0, o = 0; + + if (ECS_IS_PAIR(with)) { + r = ECS_PAIR_FIRST(with); + o = ECS_PAIR_SECOND(with); + idr = flecs_id_record_ensure(world, ecs_pair(r, EcsWildcard)); + if (idr->flags & EcsIdUnion) { + ecs_type_t dst_type; + ecs_id_t union_id = ecs_pair(EcsUnion, r); + int res = flecs_type_new_with(&dst_type, &node->type, union_id); + if (res == -1) { + return node; + } + + return find_or_create(world, &dst_type, true, node); + } else if (idr->flags & EcsIdExclusive) { + /* Relationship is exclusive, check if table already has it */ + const ecs_table_record_t *tr = flecs_id_record_get_table(idr, node); + if (tr) { + /* Table already has an instance of the relationship, create + * a new id sequence with the existing id replaced */ + ecs_type_t dst_type = flecs_type_copy(&node->type); + dst_type.array[tr->column] = with; + return find_or_create(world, &dst_type, true, node); + } + } + } else { + idr = flecs_id_record_ensure(world, with); + r = with; } - ecs_os_set_api(&api); -} + /* Create sequence with new id */ + ecs_type_t dst_type; + int res = flecs_type_new_with(&dst_type, &node->type, with); + if (res == -1) { + return node; /* Current table already has id */ + } -#else -#include "pthread.h" + if (r == EcsIsA) { + /* If adding a prefab, check if prefab has overrides */ + flecs_add_overrides_for_base(world, &dst_type, with); + } -#if defined(__APPLE__) && defined(__MACH__) -#include -#elif defined(__EMSCRIPTEN__) -#include -#else -#include -#endif + if (idr->flags & EcsIdWith) { + ecs_id_record_t *idr_with_wildcard = flecs_id_record_get(world, + ecs_pair(EcsWith, EcsWildcard)); + /* If id has With property, add targets to type */ + flecs_add_with_property(world, idr_with_wildcard, &dst_type, r, o); + } + + return find_or_create(world, &dst_type, true, node); +} static -ecs_os_thread_t posix_thread_new( - ecs_os_thread_callback_t callback, - void *arg) +ecs_table_t* flecs_find_table_without( + ecs_world_t *world, + ecs_table_t *node, + ecs_id_t without) { - pthread_t *thread = ecs_os_malloc(sizeof(pthread_t)); + if (ECS_IS_PAIR(without)) { + ecs_entity_t r = 0; + ecs_id_record_t *idr = NULL; + r = ECS_PAIR_FIRST(without); + idr = flecs_id_record_get(world, ecs_pair(r, EcsWildcard)); + if (idr && idr->flags & EcsIdUnion) { + without = ecs_pair(EcsUnion, r); + } + } - if (pthread_create (thread, NULL, callback, arg) != 0) { - ecs_os_abort(); + /* Create sequence with new id */ + ecs_type_t dst_type; + int res = flecs_type_new_without(&dst_type, &node->type, without); + if (res == -1) { + return node; /* Current table does not have id */ } - return (ecs_os_thread_t)(uintptr_t)thread; + return find_or_create(world, &dst_type, true, node); } static -void* posix_thread_join( - ecs_os_thread_t thread) +void init_edge( + ecs_table_t *table, + ecs_graph_edge_t *edge, + ecs_id_t id, + ecs_table_t *to) { - void *arg; - pthread_t *thr = (pthread_t*)(uintptr_t)thread; - pthread_join(*thr, &arg); - ecs_os_free(thr); - return arg; + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(edge != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(edge->id == 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(edge->hdr.next == NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(edge->hdr.prev == NULL, ECS_INTERNAL_ERROR, NULL); + + edge->from = table; + edge->to = to; + edge->id = id; } static -int32_t posix_ainc( - int32_t *count) +void flecs_init_edge_for_add( + ecs_world_t *world, + ecs_table_t *table, + ecs_graph_edge_t *edge, + ecs_id_t id, + ecs_table_t *to) { - int value; -#ifdef __GNUC__ - value = __sync_add_and_fetch (count, 1); - return value; -#else - /* Unsupported */ - abort(); -#endif + init_edge(table, edge, id, to); + + ensure_hi_edge(world, &table->node.add, id); + + if (table != to) { + /* Add edges are appended to refs.next */ + ecs_graph_edge_hdr_t *to_refs = &to->node.refs; + ecs_graph_edge_hdr_t *next = to_refs->next; + + to_refs->next = &edge->hdr; + edge->hdr.prev = to_refs; + + edge->hdr.next = next; + if (next) { + next->prev = &edge->hdr; + } + + compute_table_diff(world, table, to, edge, id); + } } static -int32_t posix_adec( - int32_t *count) +void flecs_init_edge_for_remove( + ecs_world_t *world, + ecs_table_t *table, + ecs_graph_edge_t *edge, + ecs_id_t id, + ecs_table_t *to) { - int value; -#ifdef __GNUC__ - value = __sync_sub_and_fetch (count, 1); - return value; -#else - /* Unsupported */ - abort(); -#endif -} + init_edge(table, edge, id, to); -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(); + ensure_hi_edge(world, &table->node.remove, id); + + if (table != to) { + /* Remove edges are appended to refs.prev */ + ecs_graph_edge_hdr_t *to_refs = &to->node.refs; + ecs_graph_edge_hdr_t *prev = to_refs->prev; + + to_refs->prev = &edge->hdr; + edge->hdr.next = to_refs; + + edge->hdr.prev = prev; + if (prev) { + prev->next = &edge->hdr; + } + + compute_table_diff(world, table, to, edge, id); } - return (ecs_os_mutex_t)(uintptr_t)mutex; } static -void posix_mutex_free( - ecs_os_mutex_t m) +ecs_table_t* flecs_create_edge_for_remove( + ecs_world_t *world, + ecs_table_t *node, + ecs_graph_edge_t *edge, + ecs_id_t id) { - pthread_mutex_t *mutex = (pthread_mutex_t*)(intptr_t)m; - pthread_mutex_destroy(mutex); - ecs_os_free(mutex); + ecs_table_t *to = flecs_find_table_without(world, node, id); + + flecs_init_edge_for_remove(world, node, edge, id, to); + + return to; } static -void posix_mutex_lock( - ecs_os_mutex_t m) +ecs_table_t* flecs_create_edge_for_add( + ecs_world_t *world, + ecs_table_t *node, + ecs_graph_edge_t *edge, + ecs_id_t id) { - pthread_mutex_t *mutex = (pthread_mutex_t*)(intptr_t)m; - if (pthread_mutex_lock(mutex)) { - abort(); - } + ecs_table_t *to = flecs_find_table_with(world, node, id); + + flecs_init_edge_for_add(world, node, edge, id, to); + + return to; } static -void posix_mutex_unlock( - ecs_os_mutex_t m) +void populate_diff( + ecs_graph_edge_t *edge, + ecs_id_t *add_ptr, + ecs_id_t *remove_ptr, + ecs_table_diff_t *out) { - pthread_mutex_t *mutex = (pthread_mutex_t*)(intptr_t)m; - if (pthread_mutex_unlock(mutex)) { - abort(); + if (out) { + ecs_table_diff_t *diff = edge->diff; + + if (diff && diff != &ecs_table_edge_is_component) { + *out = *diff; + } else { + out->on_set.count = 0; + + if (add_ptr) { + out->added.array = add_ptr; + out->added.count = 1; + } else { + out->added.count = 0; + } + + if (remove_ptr) { + out->removed.array = remove_ptr; + out->removed.count = 1; + if (diff == &ecs_table_edge_is_component) { + 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; + } + } } } -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(); +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); + + node = node ? node : &world->store.root; + + /* 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); + + ecs_id_t id = id_ptr[0]; + ecs_graph_edge_t *edge = ensure_edge(world, &node->node.remove, id); + ecs_table_t *to = edge->to; + + if (!to) { + to = flecs_create_edge_for_remove(world, node, edge, id); + ecs_assert(to != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(edge->to != NULL, ECS_INTERNAL_ERROR, NULL); } - return (ecs_os_cond_t)(uintptr_t)cond; + + populate_diff(edge, NULL, id_ptr, diff); + + return to; +error: + return NULL; } -static -void posix_cond_free( - ecs_os_cond_t c) +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) { - pthread_cond_t *cond = (pthread_cond_t*)(intptr_t)c; - if (pthread_cond_destroy(cond)) { - abort(); + ecs_poly_assert(world, ecs_world_t); + + node = node ? node : &world->store.root; + + /* 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); + + ecs_id_t id = id_ptr[0]; + ecs_graph_edge_t *edge = ensure_edge(world, &node->node.add, id); + ecs_table_t *to = edge->to; + + if (!to) { + to = flecs_create_edge_for_add(world, node, edge, id); + ecs_assert(to != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(edge->to != NULL, ECS_INTERNAL_ERROR, NULL); } - ecs_os_free(cond); + + populate_diff(edge, id_ptr, NULL, diff); + + return to; +error: + return NULL; } -static -void posix_cond_signal( - ecs_os_cond_t c) +ecs_table_t* flecs_table_find_or_create( + ecs_world_t *world, + ecs_type_t *type) { - pthread_cond_t *cond = (pthread_cond_t*)(intptr_t)c; - if (pthread_cond_signal(cond)) { - abort(); - } + ecs_poly_assert(world, ecs_world_t); + return find_or_create(world, type, false, NULL); } -static -void posix_cond_broadcast( - ecs_os_cond_t c) +void flecs_init_root_table( + ecs_world_t *world) { - pthread_cond_t *cond = (pthread_cond_t*)(intptr_t)c; - if (pthread_cond_broadcast(cond)) { - abort(); - } + ecs_poly_assert(world, ecs_world_t); + + world->store.root.type = (ecs_type_t){0}; + + init_table(world, &world->store.root, NULL); + + /* 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; } -static -void posix_cond_wait( - ecs_os_cond_t c, - ecs_os_mutex_t m) +void flecs_table_clear_edges( + ecs_world_t *world, + ecs_table_t *table) { - 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(); + (void)world; + ecs_poly_assert(world, ecs_world_t); + + ecs_log_push_1(); + + ecs_map_iter_t it; + ecs_graph_node_t *table_node = &table->node; + ecs_graph_edges_t *node_add = &table_node->add; + ecs_graph_edges_t *node_remove = &table_node->remove; + ecs_map_t *add_hi = &node_add->hi; + ecs_map_t *remove_hi = &node_remove->hi; + ecs_graph_edge_hdr_t *node_refs = &table_node->refs; + ecs_graph_edge_t *edge; + uint64_t key; + + /* Cleanup outgoing edges */ + it = ecs_map_iter(add_hi); + while ((edge = ecs_map_next_ptr(&it, ecs_graph_edge_t*, &key))) { + disconnect_edge(world, key, edge); } -} -static bool posix_time_initialized; + it = ecs_map_iter(remove_hi); + while ((edge = ecs_map_next_ptr(&it, ecs_graph_edge_t*, &key))) { + disconnect_edge(world, key, edge); + } -#if defined(__APPLE__) && defined(__MACH__) -static mach_timebase_info_data_t posix_osx_timebase; -static uint64_t posix_time_start; -#else -static uint64_t posix_time_start; -#endif + /* Cleanup incoming add edges */ + ecs_graph_edge_hdr_t *next, *cur = node_refs->next; + if (cur) { + do { + edge = (ecs_graph_edge_t*)cur; + ecs_assert(edge->to == table, ECS_INTERNAL_ERROR, NULL); + ecs_assert(edge->from != NULL, ECS_INTERNAL_ERROR, NULL); + next = cur->next; + remove_edge(world, &edge->from->node.add, edge->id, edge); + } while ((cur = next)); + } -static -void posix_time_setup(void) { - if (posix_time_initialized) { - return; + /* Cleanup incoming remove edges */ + cur = node_refs->prev; + if (cur) { + do { + edge = (ecs_graph_edge_t*)cur; + ecs_assert(edge->to == table, ECS_INTERNAL_ERROR, NULL); + ecs_assert(edge->from != NULL, ECS_INTERNAL_ERROR, NULL); + next = cur->prev; + remove_edge(world, &edge->from->node.remove, edge->id, edge); + } while ((cur = next)); } - - posix_time_initialized = true; - #if defined(__APPLE__) && defined(__MACH__) - mach_timebase_info(&posix_osx_timebase); - posix_time_start = mach_absolute_time(); - #else - struct timespec ts; - clock_gettime(CLOCK_MONOTONIC, &ts); - posix_time_start = (uint64_t)ts.tv_sec*1000000000 + (uint64_t)ts.tv_nsec; - #endif + ecs_os_free(node_add->lo); + ecs_os_free(node_remove->lo); + ecs_map_fini(add_hi); + ecs_map_fini(remove_hi); + table_node->add.lo = NULL; + table_node->remove.lo = NULL; + + ecs_log_pop_1(); } -static -void posix_sleep( - int32_t sec, - int32_t nanosec) +/* 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) { - 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"); - } + return flecs_table_traverse_add(world, table, &id, NULL); } -/* prevent 64-bit overflow when computing relative timestamp - see https://gist.github.com/jspohr/3dc4f00033d79ec5bdaf67bc46c813e3 -*/ -#if defined(ECS_TARGET_DARWIN) -static -int64_t posix_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; +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); } -#endif -static -uint64_t posix_time_now(void) { - ecs_assert(posix_time_initialized != 0, ECS_INTERNAL_ERROR, NULL); +#include - uint64_t now; +/* Utility macros to enforce consistency when initializing iterator fields */ - #if defined(ECS_TARGET_DARWIN) - now = (uint64_t) posix_int64_muldiv( - (int64_t)mach_absolute_time(), - (int64_t)posix_osx_timebase.numer, - (int64_t)posix_osx_timebase.denom); - #elif defined(__EMSCRIPTEN__) - now = (long long)(emscripten_get_now() * 1000.0 * 1000); - #else - struct timespec ts; - clock_gettime(CLOCK_MONOTONIC, &ts); - now = ((uint64_t)ts.tv_sec * 1000 * 1000 * 1000 + (uint64_t)ts.tv_nsec); - #endif +/* If term count is smaller than cache size, initialize with inline array, + * otherwise allocate. */ +#define INIT_CACHE(it, fields, f, count, cache_size)\ + if (!it->f && (fields & flecs_iter_cache_##f) && count) {\ + if (count <= cache_size) {\ + it->f = it->priv.cache.f;\ + it->priv.cache.used |= flecs_iter_cache_##f;\ + } else {\ + it->f = ecs_os_calloc(ECS_SIZEOF(*(it->f)) * count);\ + it->priv.cache.allocated |= flecs_iter_cache_##f;\ + }\ + } - return now; -} +/* If array is using the cache, make sure that its address is correct in case + * the iterator got moved (typically happens when returned by a function) */ +#define VALIDATE_CACHE(it, f)\ + if (it->f) {\ + if (it->priv.cache.used & flecs_iter_cache_##f) {\ + it->f = it->priv.cache.f;\ + }\ + } -void ecs_set_os_api_impl(void) { - ecs_os_set_api_defaults(); +/* If array is allocated, free it when finalizing the iterator */ +#define FINI_CACHE(it, f)\ + if (it->f) {\ + if (it->priv.cache.allocated & flecs_iter_cache_##f) {\ + ecs_os_free((void*)it->f);\ + }\ + } - ecs_os_api_t api = ecs_os_api; +void flecs_iter_init( + ecs_iter_t *it, + ecs_flags8_t fields) +{ + ecs_assert(!ECS_BIT_IS_SET(it->flags, EcsIterIsValid), + ECS_INTERNAL_ERROR, NULL); - 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; - api.sleep_ = posix_sleep; - api.now_ = posix_time_now; + it->priv.cache.used = 0; + it->priv.cache.allocated = 0; - posix_time_setup(); + INIT_CACHE(it, fields, ids, it->field_count, ECS_TERM_CACHE_SIZE); + INIT_CACHE(it, fields, sources, it->field_count, ECS_TERM_CACHE_SIZE); + INIT_CACHE(it, fields, match_indices, it->field_count, ECS_TERM_CACHE_SIZE); + INIT_CACHE(it, fields, columns, it->field_count, ECS_TERM_CACHE_SIZE); + INIT_CACHE(it, fields, variables, it->variable_count, + ECS_VARIABLE_CACHE_SIZE); + INIT_CACHE(it, fields, sizes, it->field_count, ECS_TERM_CACHE_SIZE); - ecs_os_set_api(&api); + if (!ECS_BIT_IS_SET(it->flags, EcsIterIsFilter)) { + INIT_CACHE(it, fields, ptrs, it->field_count, ECS_TERM_CACHE_SIZE); + } else { + it->ptrs = NULL; + } } -#endif -#endif - - +static +void iter_validate_cache( + ecs_iter_t *it) +{ + /* Make sure pointers to cache are up to date in case iter has moved */ + VALIDATE_CACHE(it, ids); + VALIDATE_CACHE(it, sources); + VALIDATE_CACHE(it, match_indices); + VALIDATE_CACHE(it, columns); + VALIDATE_CACHE(it, variables); + VALIDATE_CACHE(it, sizes); + VALIDATE_CACHE(it, ptrs); +} -#ifdef FLECS_COREDOC +void flecs_iter_validate( + ecs_iter_t *it) +{ + iter_validate_cache(it); + ECS_BIT_SET(it->flags, EcsIterIsValid); +} -#define URL_ROOT "https://flecs.docsforge.com/master/relationships-manual/" +void ecs_iter_fini( + ecs_iter_t *it) +{ + ECS_BIT_CLEAR(it->flags, EcsIterIsValid); -void FlecsCoreDocImport( - ecs_world_t *world) -{ - ECS_MODULE(world, FlecsCoreDoc); + if (it->fini) { + it->fini(it); + } - ECS_IMPORT(world, FlecsMeta); - ECS_IMPORT(world, FlecsDoc); + FINI_CACHE(it, ids); + FINI_CACHE(it, columns); + FINI_CACHE(it, sources); + FINI_CACHE(it, sizes); + FINI_CACHE(it, ptrs); + FINI_CACHE(it, match_indices); + FINI_CACHE(it, variables); +} - ecs_set_name_prefix(world, "Ecs"); +static +ecs_size_t iter_get_size_for_id( + ecs_world_t *world, + ecs_id_t id) +{ + ecs_id_record_t *idr = flecs_id_record_get(world, id); + if (!idr) { + return 0; + } - /* Initialize reflection data for core components */ + if (idr->flags & EcsIdUnion) { + return ECS_SIZEOF(ecs_entity_t); + } - ecs_struct_init(world, &(ecs_struct_desc_t){ - .entity = ecs_id(EcsComponent), - .members = { - {.name = (char*)"size", .type = ecs_id(ecs_i32_t)}, - {.name = (char*)"alignment", .type = ecs_id(ecs_i32_t)} - } - }); + if (idr->type_info) { + return idr->type_info->size; + } - ecs_struct_init(world, &(ecs_struct_desc_t){ - .entity = ecs_id(EcsDocDescription), - .members = { - {.name = "value", .type = ecs_id(ecs_string_t)} - } - }); + return 0; +} - /* 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"); +static +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) +{ + bool is_shared = false; + ecs_table_t *table; + void *data; + ecs_size_t size = 0; + int32_t row, u_index; - ecs_doc_set_brief(world, EcsFlecsCore, "Flecs module with builtin components"); + if (!column) { + /* Term has no data. This includes terms that have Not operators. */ + goto no_data; + } - ecs_doc_set_brief(world, EcsWorld, "Entity associated with world"); + if (!it->terms) { + goto no_data; + } - 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"); + /* Filter terms may match with data but don't return it */ + if (it->terms[t].inout == EcsInOutNone) { + if (size_out) { + size = iter_get_size_for_id(world, it->ids[t]); + } + goto no_data; + } - 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"); + if (column < 0) { + is_shared = true; - ecs_doc_set_brief(world, EcsTransitive, "Transitive relationship property"); - ecs_doc_set_brief(world, EcsReflexive, "Reflexive relationship property"); - ecs_doc_set_brief(world, EcsFinal, "Final relationship property"); - ecs_doc_set_brief(world, EcsDontInherit, "DontInherit relationship property"); - ecs_doc_set_brief(world, EcsTag, "Tag relationship property"); - ecs_doc_set_brief(world, EcsAcyclic, "Acyclic relationship property"); - ecs_doc_set_brief(world, EcsExclusive, "Exclusive relationship property"); - ecs_doc_set_brief(world, EcsSymmetric, "Symmetric relationship property"); - ecs_doc_set_brief(world, EcsWith, "With relationship property"); - ecs_doc_set_brief(world, EcsOnDelete, "OnDelete relationship cleanup property"); - ecs_doc_set_brief(world, EcsOnDeleteTarget, "OnDeleteTarget relationship cleanup property"); - ecs_doc_set_brief(world, EcsDefaultChildComponent, "Sets default component hint for children of entity"); - ecs_doc_set_brief(world, EcsRemove, "Remove relationship cleanup property"); - ecs_doc_set_brief(world, EcsDelete, "Delete relationship cleanup property"); - ecs_doc_set_brief(world, EcsPanic, "Panic relationship cleanup property"); - ecs_doc_set_brief(world, EcsIsA, "Builtin IsA relationship"); - ecs_doc_set_brief(world, EcsChildOf, "Builtin ChildOf relationship"); - ecs_doc_set_brief(world, EcsDependsOn, "Builtin DependsOn relationship"); - 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"); + /* Data is not from This */ + if (it->references) { + /* The reference array is used only for components matched on a + * table (vs. individual entities). Remaining components should be + * assigned outside of this function */ + if (ecs_term_match_this(&it->terms[t])) { - ecs_doc_set_link(world, EcsTransitive, URL_ROOT "#transitive-property"); - ecs_doc_set_link(world, EcsReflexive, URL_ROOT "#reflexive-property"); - ecs_doc_set_link(world, EcsFinal, URL_ROOT "#final-property"); - ecs_doc_set_link(world, EcsDontInherit, URL_ROOT "#dontinherit-property"); - ecs_doc_set_link(world, EcsTag, URL_ROOT "#tag-property"); - ecs_doc_set_link(world, EcsAcyclic, URL_ROOT "#acyclic-property"); - ecs_doc_set_link(world, EcsExclusive, URL_ROOT "#exclusive-property"); - ecs_doc_set_link(world, EcsSymmetric, URL_ROOT "#symmetric-property"); - ecs_doc_set_link(world, EcsWith, URL_ROOT "#with-property"); - ecs_doc_set_link(world, EcsOnDelete, URL_ROOT "#cleanup-properties"); - ecs_doc_set_link(world, EcsOnDeleteTarget, URL_ROOT "#cleanup-properties"); - ecs_doc_set_link(world, EcsRemove, URL_ROOT "#cleanup-properties"); - ecs_doc_set_link(world, EcsDelete, URL_ROOT "#cleanup-properties"); - ecs_doc_set_link(world, EcsPanic, URL_ROOT "#cleanup-properties"); - ecs_doc_set_link(world, EcsIsA, URL_ROOT "#the-isa-relationship"); - ecs_doc_set_link(world, EcsChildOf, URL_ROOT "#the-childof-relationship"); - - /* 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"); + /* Iterator provides cached references for non-This terms */ + ecs_ref_t *ref = &it->references[-column - 1]; + if (ptr_out) { + if (ref->id) { + ptr_out[0] = (void*)ecs_ref_get_id(world, ref, ref->id); + } else { + ptr_out[0] = NULL; + } + } - 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 (!ref->id) { + is_shared = false; + } - 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 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 is_shared; + } - /* 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"); + return true; + } else { + ecs_entity_t subj = it->sources[t]; + ecs_assert(subj != 0, ECS_INTERNAL_ERROR, 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"); -} + /* 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 = flecs_entities_get(world, subj); + ecs_assert(r != NULL && r->table != NULL, ECS_INTERNAL_ERROR, NULL); -#endif + row = ECS_RECORD_TO_ROW(r->row); + table = r->table; + ecs_id_t id = it->ids[t]; + ecs_table_t *s_table = table->storage_table; + ecs_table_record_t *tr; -#ifdef FLECS_PARSER + if (!s_table || !(tr = flecs_table_record_get(world, s_table, id))){ + u_index = flecs_table_column_to_union_index(table, -column - 1); + if (u_index != -1) { + goto has_union; + } + goto no_data; + } -#include + /* 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_type_info_t *ti = table->type_info[column]; + ecs_column_t *s = &table->data.columns[column]; + size = ti->size; + data = ecs_storage_first(s); + /* Fallthrough to has_data */ + } + } else { + /* Data is from This, use table from iterator */ + table = it->table; + if (!table) { + goto no_data; + } -#define ECS_ANNOTATION_LENGTH_MAX (16) + row = it->offset; -#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_BRACKET_OPEN '[' -#define TOK_BRACKET_CLOSE ']' -#define TOK_WILDCARD '*' -#define TOK_VARIABLE '$' -#define TOK_PAREN_OPEN '(' -#define TOK_PAREN_CLOSE ')' + int32_t storage_column = ecs_table_type_to_storage_index( + table, column - 1); + if (storage_column == -1) { + u_index = flecs_table_column_to_union_index(table, column - 1); + if (u_index != -1) { + goto has_union; + } + goto no_data; + } -#define TOK_SELF "self" -#define TOK_UP "up" -#define TOK_DOWN "down" -#define TOK_CASCADE "cascade" -#define TOK_PARENT "parent" + ecs_type_info_t *ti = table->type_info[storage_column]; + ecs_column_t *s = &table->data.columns[storage_column]; + size = ti->size; + data = ecs_storage_first(s); -#define TOK_OVERRIDE "OVERRIDE" + if (!table || !ecs_table_count(table)) { + goto no_data; + } -#define TOK_ROLE_AND "AND" -#define TOK_ROLE_OR "OR" -#define TOK_ROLE_NOT "NOT" -#define TOK_ROLE_TOGGLE "TOGGLE" + /* Fallthrough to has_data */ + } -#define TOK_IN "in" -#define TOK_OUT "out" -#define TOK_INOUT "inout" -#define TOK_INOUT_NONE "none" +has_data: + if (ptr_out) ptr_out[0] = ECS_ELEM(data, size, row); + if (size_out) size_out[0] = size; + return is_shared; -#define ECS_MAX_TOKEN_SIZE (256) +has_union: { + /* 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->data.sw_columns[u_index]; + data = ecs_vector_first(flecs_switch_values(sw), ecs_entity_t); + size = ECS_SIZEOF(ecs_entity_t); + goto has_data; + } -typedef char ecs_token_t[ECS_MAX_TOKEN_SIZE]; +no_data: + if (ptr_out) ptr_out[0] = NULL; + if (size_out) size_out[0] = size; + return false; +} -const char* ecs_parse_eol_and_whitespace( - const char *ptr) +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) { - while (isspace(*ptr)) { - ptr ++; + if (it->table) { + it->frame_offset += ecs_table_count(it->table); } - return ptr; -} + it->table = table; + it->offset = offset; + it->count = count; -/** Skip spaces when parsing signature */ -const char* ecs_parse_whitespace( - const char *ptr) -{ - while ((*ptr != '\n') && isspace(*ptr)) { - ptr ++; + if (table) { + if (!count) { + count = it->count = ecs_table_count(table); + } + if (count) { + it->entities = ecs_storage_get_t( + &table->data.entities, ecs_entity_t, offset); + } else { + it->entities = NULL; + } } - return ptr; -} + int t, field_count = it->field_count; -const char* ecs_parse_digit( - const char *ptr, - char *token) -{ - char *tptr = token; - char ch = ptr[0]; + if (ECS_BIT_IS_SET(it->flags, EcsIterIsFilter)) { + ECS_BIT_CLEAR(it->flags, EcsIterHasShared); - if (!isdigit(ch) && ch != '-') { - ecs_parser_error(NULL, NULL, 0, "invalid start of number '%s'", ptr); - return NULL; + if (!sizes) { + return; + } + + /* Fetch sizes, skip fetching data */ + for (t = 0; t < field_count; t ++) { + sizes[t] = iter_get_size_for_id(world, it->ids[t]); + } + return; } - tptr[0] = ch; - tptr ++; - ptr ++; + bool has_shared = false; - for (; (ch = *ptr); ptr ++) { - if (!isdigit(ch)) { - break; + if (ptrs && sizes) { + for (t = 0; t < field_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 < field_count; t ++) { + ecs_assert(it->columns != NULL, ECS_INTERNAL_ERROR, NULL); - tptr[0] = ch; - tptr ++; + 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); + } } - tptr[0] = '\0'; - - return ptr; + ECS_BIT_COND(it->flags, EcsIterHasShared, has_shared); } -static -bool is_newline_comment( - const char *ptr) +bool flecs_iter_next_row( + ecs_iter_t *it) { - if (ptr[0] == '/' && ptr[1] == '/') { - return true; - } - return false; -} + ecs_assert(it != NULL, ECS_INTERNAL_ERROR, NULL); -const char* ecs_parse_fluff( - const char *ptr, - char **last_comment) -{ - const char *last_comment_start = NULL; + bool is_instanced = ECS_BIT_IS_SET(it->flags, EcsIterIsInstanced); + if (!is_instanced) { + int32_t instance_count = it->instance_count; + int32_t count = it->count; + int32_t offset = it->offset; - do { - /* Skip whitespaces before checking for a comment */ - ptr = ecs_parse_whitespace(ptr); + if (instance_count > count && offset < (instance_count - 1)) { + ecs_assert(count == 1, ECS_INTERNAL_ERROR, NULL); + int t, field_count = it->field_count; - /* Newline comment, skip until newline character */ - if (is_newline_comment(ptr)) { - ptr += 2; - last_comment_start = ptr; + for (t = 0; t < field_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]); + } + } + } - while (ptr[0] && ptr[0] != TOK_NEWLINE) { - ptr ++; + if (it->entities) { + it->entities ++; } - } + it->offset ++; - /* If a newline character is found, skip it */ - if (ptr[0] == TOK_NEWLINE) { - ptr ++; + return true; } - - } while (isspace(ptr[0]) || is_newline_comment(ptr)); - - if (last_comment) { - *last_comment = (char*)last_comment_start; } - return ptr; + return false; } -/* -- Private functions -- */ - -static -bool valid_identifier_start_char( - char ch) +bool flecs_iter_next_instanced( + ecs_iter_t *it, + bool result) { - if (ch && (isalpha(ch) || (ch == '_') || (ch == '*') || - (ch == '0') || (ch == TOK_VARIABLE) || isdigit(ch))) - { - return true; + it->instance_count = it->count; + bool is_instanced = ECS_BIT_IS_SET(it->flags, EcsIterIsInstanced); + bool has_shared = ECS_BIT_IS_SET(it->flags, EcsIterHasShared); + if (result && !is_instanced && it->count && has_shared) { + it->count = 1; } - - return false; + return result; } -static -bool valid_token_start_char( - char ch) +/* --- Public API --- */ + +void* ecs_field_w_size( + const ecs_iter_t *it, + size_t size, + int32_t term) { - if ((ch == '"') || (ch == '{') || (ch == '}') || (ch == ',') || (ch == '-') - || (ch == '[') || (ch == ']') || valid_identifier_start_char(ch)) - { - return true; + ecs_check(it->flags & EcsIterIsValid, ECS_INVALID_PARAMETER, NULL); + ecs_check(!size || ecs_field_size(it, term) == size || + (!ecs_field_size(it, term) && (!it->ptrs || !it->ptrs[term - 1])), + ECS_INVALID_PARAMETER, NULL); + + (void)size; + + if (!term) { + return it->entities; } - return false; + if (!it->ptrs) { + return NULL; + } + + return it->ptrs[term - 1]; +error: + return NULL; } -static -bool valid_token_char( - char ch) +bool ecs_field_is_readonly( + const ecs_iter_t *it, + int32_t term_index) { - if (ch && - (isalpha(ch) || isdigit(ch) || ch == '_' || ch == '.' || ch == '"')) - { + ecs_check(it->flags & EcsIterIsValid, ECS_INVALID_PARAMETER, NULL); + ecs_check(term_index > 0, ECS_INVALID_PARAMETER, NULL); + + 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 *src = &term->src; + + if (term->inout == EcsInOutDefault) { + if (!(ecs_term_match_this(term))) { + return true; + } + + if (!(src->flags & EcsSelf)) { + return true; + } + } } +error: return false; } -static -bool valid_operator_char( - char ch) +bool ecs_field_is_writeonly( + const ecs_iter_t *it, + int32_t term_index) { - if (ch == TOK_OPTIONAL || ch == TOK_NOT) { + ecs_check(it->flags & EcsIterIsValid, ECS_INVALID_PARAMETER, NULL); + ecs_check(term_index > 0, ECS_INVALID_PARAMETER, NULL); + + ecs_term_t *term = &it->terms[term_index - 1]; + ecs_check(term != NULL, ECS_INVALID_PARAMETER, NULL); + + if (term->inout == EcsOut) { return true; } +error: return false; } -const char* ecs_parse_token( - const char *name, - const char *expr, - const char *ptr, - char *token_out) +int32_t ecs_iter_find_column( + const ecs_iter_t *it, + ecs_entity_t component) { - int64_t column = ptr - expr; + ecs_check(it->flags & EcsIterIsValid, ECS_INVALID_PARAMETER, NULL); + ecs_check(it->table != NULL, ECS_INVALID_PARAMETER, NULL); + return ecs_search(it->real_world, it->table, component, 0); +error: + return -1; +} - ptr = ecs_parse_whitespace(ptr); - char *tptr = token_out, ch = ptr[0]; +bool ecs_field_is_set( + const ecs_iter_t *it, + int32_t index) +{ + ecs_check(it->flags & EcsIterIsValid, ECS_INVALID_PARAMETER, NULL); - if (!valid_token_start_char(ch)) { - if (ch == '\0' || ch == '\n') { - ecs_parser_error(name, expr, column, - "unexpected end of expression"); + 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 { - ecs_parser_error(name, expr, column, - "invalid start of token '%s'", ptr); - } - return NULL; - } - - tptr[0] = ch; - tptr ++; - ptr ++; - - if (ch == '{' || ch == '}' || ch == '[' || ch == ']' || ch == ',') { - tptr[0] = 0; - return ptr; - } - - int tmpl_nesting = 0; - bool in_str = ch == '"'; - - 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; + return true; } - - 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; + return true; +error: + return false; } -static -const char* ecs_parse_identifier( - const char *name, - const char *expr, - const char *ptr, - char *token_out) +bool ecs_field_is_self( + const ecs_iter_t *it, + int32_t index) { - 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; + ecs_assert(it != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(index >= 0, ECS_INVALID_PARAMETER, NULL); + return it->sources == NULL || it->sources[index - 1] == 0; } -static -int parse_identifier( - const char *token, - ecs_term_id_t *out) +ecs_id_t ecs_field_id( + const ecs_iter_t *it, + int32_t index) { - const char *tptr = token; - if (tptr[0] == TOK_VARIABLE && tptr[1]) { - out->flags |= EcsIsVariable; - tptr ++; - } - - out->name = ecs_os_strdup(tptr); - - return 0; + ecs_assert(it != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(index >= 0, ECS_INVALID_PARAMETER, NULL); + return it->ids[index - 1]; } -static -ecs_entity_t parse_role( - const char *name, - const char *sig, - int64_t column, - const char *token) +ecs_entity_t ecs_field_src( + const ecs_iter_t *it, + int32_t index) { - 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_NOT)) { - return ECS_NOT; - } else if (!ecs_os_strcmp(token, TOK_OVERRIDE)) { - return ECS_OVERRIDE; - } else if (!ecs_os_strcmp(token, TOK_ROLE_TOGGLE)) { - return ECS_TOGGLE; + ecs_assert(it != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(index >= 0, ECS_INVALID_PARAMETER, NULL); + if (it->sources) { + return it->sources[index - 1]; } else { - ecs_parser_error(name, sig, column, "invalid role '%s'", token); return 0; } } -static -ecs_oper_kind_t parse_operator( - char ch) +size_t ecs_field_size( + const ecs_iter_t *it, + int32_t index) { - if (ch == TOK_OPTIONAL) { - return EcsOptional; - } else if (ch == TOK_NOT) { - return EcsNot; + ecs_assert(it != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(index >= 0, ECS_INVALID_PARAMETER, NULL); + + if (index == 0) { + return sizeof(ecs_entity_t); } else { - ecs_abort(ECS_INTERNAL_ERROR, NULL); + return (size_t)it->sizes[index - 1]; } } -static -const char* parse_annotation( - const char *name, - const char *sig, - int64_t column, - const char *ptr, - ecs_inout_kind_t *inout_kind_out) +void* ecs_iter_column_w_size( + const ecs_iter_t *it, + size_t size, + int32_t index) { - char token[ECS_MAX_TOKEN_SIZE]; - - ptr = ecs_parse_identifier(name, sig, ptr, token); - if (!ptr) { + ecs_check(it->flags & EcsIterIsValid, 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 (!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_NONE)) { - *inout_kind_out = EcsInOutNone; - } - - ptr = ecs_parse_whitespace(ptr); - - if (ptr[0] != TOK_BRACKET_CLOSE) { - ecs_parser_error(name, sig, column, "expected ]"); - return NULL; - } + ecs_type_info_t *ti = table->type_info[storage_index]; + ecs_check(!size || (ecs_size_t)size == ti->size, + ECS_INVALID_PARAMETER, NULL); + (void)ti; - return ptr + 1; + ecs_column_t *column = &table->data.columns[storage_index]; + return ecs_storage_get(column, flecs_uto(int32_t, size), it->offset); +error: + return NULL; } -static -uint8_t parse_set_token( - const char *token) +size_t ecs_iter_column_size( + const ecs_iter_t *it, + int32_t index) { - if (!ecs_os_strcmp(token, TOK_SELF)) { - return EcsSelf; - } else if (!ecs_os_strcmp(token, TOK_UP)) { - return EcsUp; - } else if (!ecs_os_strcmp(token, TOK_DOWN)) { - return EcsDown; - } else if (!ecs_os_strcmp(token, TOK_CASCADE)) { - return EcsCascade; - } else if (!ecs_os_strcmp(token, TOK_PARENT)) { - return EcsParent; - } else { + ecs_check(it->flags & EcsIterIsValid, 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; } + + ecs_type_info_t *ti = table->type_info[storage_index]; + return flecs_ito(size_t, ti->size); +error: + return 0; } -static -const char* parse_term_flags( - 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* ecs_iter_str( + const ecs_iter_t *it) { - 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->flags & tok) { - ecs_parser_error(name, expr, column, - "duplicate set token '%s'", token); - return NULL; - } - - id->flags |= tok; - - if (ptr[0] == TOK_PAREN_OPEN) { - ptr ++; - - /* 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; - } - - id->trav = ecs_lookup_fullpath(world, token); - if (!id->trav) { - ecs_parser_error(name, expr, column, - "unresolved identifier '%s'", token); - return NULL; - } - - 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; - } - } + ecs_world_t *world = it->world; + ecs_strbuf_t buf = ECS_STRBUF_INIT; + int i; - 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; - } - } + if (it->field_count) { + ecs_strbuf_list_push(&buf, "term: ", ","); + for (i = 0; i < it->field_count; i ++) { + ecs_id_t id = ecs_field_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"); - /* 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; - } - } - - /* End of set expression */ - } else if (ptr[0] == tok_end || ptr[0] == TOK_AND || !ptr[0]) { - break; + ecs_strbuf_list_push(&buf, "subj: ", ","); + for (i = 0; i < it->field_count; i ++) { + ecs_entity_t subj = ecs_field_src(it, i + 1); + char *str = ecs_get_fullpath(world, subj); + ecs_strbuf_list_appendstr(&buf, str); + ecs_os_free(str); } - } while (true); - - return ptr; -} - -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; - - 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; + ecs_strbuf_list_pop(&buf, "\n"); + } - if (arg == 0) { - term_id = &term->src; - } else if (arg == 1) { - term_id = &term->second; + 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 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_term_flags(world, name, expr, (ptr - expr), ptr, - NULL, term_id, TOK_PAREN_CLOSE); - if (!ptr) { - return NULL; - } - - /* Check for term flags */ - } else if (!ecs_os_strcmp(token, TOK_CASCADE) || - !ecs_os_strcmp(token, TOK_SELF) || - !ecs_os_strcmp(token, TOK_UP) || - !ecs_os_strcmp(token, TOK_DOWN) || - !(ecs_os_strcmp(token, TOK_PARENT))) - { - ptr = parse_term_flags(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; + ecs_var_t var = it->variables[i]; + if (!var.entity) { + /* Skip table variables */ + continue; } - if (ptr[0] == TOK_AND) { - ptr = ecs_parse_whitespace(ptr + 1); - - term->id_flags = 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; + if (!actual_count) { + ecs_strbuf_list_push(&buf, "vars: ", ","); } - } else { - ecs_parser_error(name, expr, (ptr - expr), - "expected identifier or set expression"); - return NULL; - } - - arg ++; - - } while (true); - - return ptr; -} - -static -void parser_unexpected_char( - const char *name, - const char *expr, - const char *ptr, - char ch) -{ - 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"); - } -} - -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 */ }; - - ptr = ecs_parse_whitespace(ptr); + char *str = ecs_get_fullpath(world, var.entity); + ecs_strbuf_list_append(&buf, "%s=%s", var_name, str); + ecs_os_free(str); - /* 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; + actual_count ++; + } + if (actual_count) { + ecs_strbuf_list_pop(&buf, "\n"); } - ptr = ecs_parse_whitespace(ptr); - } - - if (valid_operator_char(ptr[0])) { - term.oper = parse_operator(ptr[0]); - ptr = ecs_parse_whitespace(ptr + 1); } - /* 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 (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); } + } - /* Is token a type role? */ - if (ptr[0] == TOK_BITWISE_OR && ptr[1] != TOK_BITWISE_OR) { - ptr ++; - goto parse_role; - } + return ecs_strbuf_get(&buf); +} - /* Is token a predicate? */ - if (ptr[0] == TOK_PAREN_OPEN) { - goto parse_predicate; - } +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); +} - /* Next token must be a predicate */ - goto parse_predicate; +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; +} - /* Pair with implicit subject */ - } else if (ptr[0] == TOK_PAREN_OPEN) { - goto parse_pair; +int32_t ecs_iter_count( + ecs_iter_t *it) +{ + ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); - /* Nothing else expected here */ - } else { - parser_unexpected_char(name, expr, ptr, ptr[0]); - goto error; - } + ECS_BIT_SET(it->flags, EcsIterIsFilter); + ECS_BIT_SET(it->flags, EcsIterIsInstanced); -parse_role: - term.id_flags = parse_role(name, expr, (ptr - expr), token); - if (!term.id_flags) { - goto error; + int32_t count = 0; + while (ecs_iter_next(it)) { + count += it->count; } + return count; +error: + return 0; +} - 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; - } +bool ecs_iter_is_true( + ecs_iter_t *it) +{ + ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); - /* If not, it's a predicate */ - goto parse_predicate; + ECS_BIT_SET(it->flags, EcsIterIsFilter); + ECS_BIT_SET(it->flags, EcsIterIsInstanced); - } else if (ptr[0] == TOK_PAREN_OPEN) { - goto parse_pair; - } else { - ecs_parser_error(name, expr, (ptr - expr), - "expected identifier after role"); - goto error; + bool result = ecs_iter_next(it); + if (result) { + ecs_iter_fini(it); } + return result; +error: + return false; +} -parse_predicate: - if (parse_identifier(token, &term.first)) { - ecs_parser_error(name, expr, (ptr - expr), - "invalid identifier '%s'", token); - goto error; - } +ecs_entity_t ecs_iter_get_var( + ecs_iter_t *it, + int32_t var_id) +{ + ecs_check(var_id >= 0, ECS_INVALID_PARAMETER, NULL); + ecs_check(var_id < it->variable_count, ECS_INVALID_PARAMETER, NULL); + ecs_check(it->variables != NULL, ECS_INVALID_PARAMETER, NULL); - /* Set expression */ - if (ptr[0] == TOK_COLON) { - ptr = ecs_parse_whitespace(ptr + 1); - ptr = parse_term_flags(world, name, expr, (ptr - expr), ptr, NULL, - &term.first, TOK_COLON); - if (!ptr) { - goto error; + ecs_var_t *var = &it->variables[var_id]; + ecs_entity_t e = var->entity; + if (!e) { + ecs_table_t *table = var->range.table; + if (table) { + if ((var->range.count == 1) || (ecs_table_count(table) == 1)) { + ecs_assert(ecs_table_count(table) > var->range.offset, + ECS_INTERNAL_ERROR, NULL); + e = ecs_storage_get_t(&table->data.entities, ecs_entity_t, + var->range.offset)[0]; + } } + } else { + ecs_assert(ecs_is_valid(it->real_world, e), ECS_INTERNAL_ERROR, NULL); + } - ptr = ecs_parse_whitespace(ptr); + return e; +error: + return 0; +} - if (ptr[0] == TOK_AND || !ptr[0]) { - goto parse_done; - } +ecs_table_t* ecs_iter_get_var_as_table( + ecs_iter_t *it, + int32_t var_id) +{ + ecs_check(var_id >= 0, ECS_INVALID_PARAMETER, NULL); + ecs_check(var_id < it->variable_count, ECS_INVALID_PARAMETER, NULL); + ecs_check(it->variables != NULL, ECS_INVALID_PARAMETER, NULL); - if (ptr[0] != TOK_COLON) { - ecs_parser_error(name, expr, (ptr - expr), - "unexpected token '%c' after predicate set expression", ptr[0]); - goto error; + ecs_var_t *var = &it->variables[var_id]; + ecs_table_t *table = var->range.table; + if (!table) { + /* If table is not set, try to get table from entity */ + ecs_entity_t e = var->entity; + if (e) { + ecs_record_t *r = flecs_entities_get(it->real_world, e); + if (r) { + table = r->table; + if (ecs_table_count(table) != 1) { + /* If table contains more than the entity, make sure not to + * return a partial table. */ + return 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.src.flags = EcsIsEntity; - term.src.id = 0; - ptr ++; - ptr = ecs_parse_whitespace(ptr); - } else { - ptr = parse_arguments( - world, name, expr, (ptr - expr), ptr, token, &term); + if (table) { + if (var->range.offset) { + /* Don't return whole table if only partial table is matched */ + return NULL; } - goto parse_done; + if (!var->range.count || ecs_table_count(table) == var->range.count) { + /* Return table if count matches */ + return table; + } } - goto parse_done; +error: + return NULL; +} -parse_pair: - ptr = ecs_parse_identifier(name, expr, ptr + 1, token); - if (!ptr) { - goto error; - } +ecs_table_range_t ecs_iter_get_var_as_range( + ecs_iter_t *it, + int32_t var_id) +{ + ecs_check(var_id >= 0, ECS_INVALID_PARAMETER, NULL); + ecs_check(var_id < it->variable_count, ECS_INVALID_PARAMETER, NULL); + ecs_check(it->variables != NULL, ECS_INVALID_PARAMETER, NULL); - if (ptr[0] == TOK_AND) { - ptr ++; - term.src.id = EcsThis; - term.src.flags |= EcsIsVariable; - goto parse_pair_predicate; - } else if (ptr[0] == TOK_PAREN_CLOSE) { - term.src.id = EcsThis; - term.src.flags |= EcsIsVariable; - goto parse_pair_predicate; + ecs_table_range_t result = { 0 }; + + ecs_var_t *var = &it->variables[var_id]; + ecs_table_t *table = var->range.table; + if (!table) { + ecs_entity_t e = var->entity; + if (e) { + ecs_record_t *r = flecs_entities_get(it->real_world, e); + if (r) { + result.table = r->table; + result.offset = ECS_RECORD_TO_ROW(r->row); + result.count = 1; + } + } } else { - parser_unexpected_char(name, expr, ptr, ptr[0]); - goto error; + result.table = table; + result.offset = var->range.offset; + result.count = var->range.count; + if (!result.count) { + result.count = ecs_table_count(table); + } } -parse_pair_predicate: - if (parse_identifier(token, &term.first)) { - ecs_parser_error(name, expr, (ptr - expr), - "invalid identifier '%s'", token); - goto error; - } + return result; +error: + return (ecs_table_range_t){0}; +} - ptr = ecs_parse_whitespace(ptr); - if (valid_token_start_char(ptr[0])) { - ptr = ecs_parse_identifier(name, expr, ptr, token); - if (!ptr) { - goto error; - } +void ecs_iter_set_var( + ecs_iter_t *it, + int32_t var_id, + ecs_entity_t entity) +{ + ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(var_id >= 0, ECS_INVALID_PARAMETER, NULL); + ecs_check(var_id < ECS_VARIABLE_COUNT_MAX, ECS_INVALID_PARAMETER, NULL); + ecs_check(var_id < it->variable_count, ECS_INVALID_PARAMETER, NULL); + ecs_check(entity != 0, ECS_INVALID_PARAMETER, NULL); + /* Can't set variable while iterating */ + ecs_check(!(it->flags & EcsIterIsValid), ECS_INVALID_PARAMETER, NULL); + ecs_check(it->variables != NULL, ECS_INTERNAL_ERROR, NULL); - 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; - } + iter_validate_cache(it); -parse_pair_object: - if (parse_identifier(token, &term.second)) { - ecs_parser_error(name, expr, (ptr - expr), - "invalid identifier '%s'", token); - goto error; - } + ecs_var_t *var = &it->variables[var_id]; + var->entity = entity; - if (term.id_flags != 0) { - if (!ECS_HAS_ID_FLAG(term.id_flags, PAIR)) { - ecs_parser_error(name, expr, (ptr - expr), - "invalid combination of role '%s' with pair", - ecs_id_flag_str(term.id_flags)); - goto error; - } + ecs_record_t *r = flecs_entities_get(it->real_world, entity); + if (r) { + var->range.table = r->table; + var->range.offset = ECS_RECORD_TO_ROW(r->row); + var->range.count = 1; } else { - term.id_flags = ECS_PAIR; + var->range.table = NULL; + var->range.offset = 0; + var->range.count = 0; } - ptr = ecs_parse_whitespace(ptr); - goto parse_done; - -parse_done: - *term_out = term; - return ptr; + it->constrained_vars |= flecs_ito(uint64_t, 1 << var_id); error: - ecs_term_fini(&term); - *term_out = (ecs_term_t){0}; - return NULL; + return; } -static -bool is_valid_end_of_term( - const char *ptr) +void ecs_iter_set_var_as_table( + ecs_iter_t *it, + int32_t var_id, + const ecs_table_t *table) { - 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_table_range_t range = { .table = (ecs_table_t*)table }; + ecs_iter_set_var_as_range(it, var_id, &range); } -char* ecs_parse_term( - const ecs_world_t *world, - const char *name, - const char *expr, - const char *ptr, - ecs_term_t *term) +void ecs_iter_set_var_as_range( + ecs_iter_t *it, + int32_t var_id, + const ecs_table_range_t *range) { - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(ptr != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(term != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(var_id >= 0, ECS_INVALID_PARAMETER, NULL); + ecs_check(var_id < ECS_VARIABLE_COUNT_MAX, ECS_INVALID_PARAMETER, NULL); + ecs_check(var_id < it->variable_count, ECS_INVALID_PARAMETER, NULL); + ecs_check(range != 0, ECS_INVALID_PARAMETER, NULL); + ecs_check(range->table != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(!range->offset || range->offset < ecs_table_count(range->table), + ECS_INVALID_PARAMETER, NULL); + ecs_check((range->offset + range->count) <= ecs_table_count(range->table), + ECS_INVALID_PARAMETER, NULL); - ecs_term_id_t *src = &term->src; + /* Can't set variable while iterating */ + ecs_check(!(it->flags & EcsIterIsValid), ECS_INVALID_OPERATION, NULL); - 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"); - } - } - } - - ptr = ecs_parse_eol_and_whitespace(ptr); - if (!ptr[0]) { - *term = (ecs_term_t){0}; - return (char*)ptr; - } + iter_validate_cache(it); - if (ptr == expr && !strcmp(expr, "0")) { - return (char*)&ptr[1]; - } + ecs_var_t *var = &it->variables[var_id]; + var->range = *range; - /* Parse next element */ - ptr = parse_term(world, name, ptr, term); - if (!ptr) { - goto error; + if (range->count == 1) { + ecs_table_t *table = range->table; + var->entity = ecs_storage_get_t( + &table->data.entities, ecs_entity_t, range->offset)[0]; + } else { + var->entity = 0; } - /* Check for $() notation */ - if (!ecs_os_strcmp(term->first.name, "$")) { - if (term->src.name) { - ecs_os_free(term->first.name); - - term->first = term->src; + it->constrained_vars |= flecs_uto(uint64_t, 1 << var_id); - if (term->second.name) { - term->src = term->second; - } else { - term->src.id = EcsThis; - term->src.name = NULL; - term->src.flags |= EcsIsVariable; - } +error: + return; +} - term->second.name = ecs_os_strdup(term->first.name); - term->second.flags |= EcsIsVariable; - } - } +bool ecs_iter_var_is_constrained( + ecs_iter_t *it, + int32_t var_id) +{ + return (it->constrained_vars & (flecs_uto(uint64_t, 1 << var_id))) != 0; +} - /* Post-parse consistency checks */ +ecs_iter_t ecs_page_iter( + const ecs_iter_t *it, + int32_t offset, + int32_t limit) +{ + ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(it->next != NULL, ECS_INVALID_PARAMETER, NULL); - /* 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_iter_t result = *it; + result.priv.iter.page = (ecs_page_iter_t){ + .offset = offset, + .limit = limit, + .remaining = limit + }; + result.next = ecs_page_next; + result.chain_it = (ecs_iter_t*)it; + + return result; +error: + return (ecs_iter_t){ 0 }; +} + +static +void offset_iter( + ecs_iter_t *it, + int32_t offset) +{ + it->entities = &it->entities[offset]; + + int32_t t, field_count = it->field_count; + for (t = 0; t < field_count; t ++) { + void *ptrs = it->ptrs[t]; + if (!ptrs) { + continue; } - term->oper = EcsOr; - } + if (it->sources[t]) { + continue; + } - /* 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; + it->ptrs[t] = ECS_OFFSET(ptrs, offset * it->sizes[t]); } +} + +static +bool ecs_page_next_instanced( + ecs_iter_t *it) +{ + ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(it->chain_it != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(it->next == ecs_page_next, ECS_INVALID_PARAMETER, NULL); + + ecs_iter_t *chain_it = it->chain_it; + bool instanced = ECS_BIT_IS_SET(it->flags, EcsIterIsInstanced); - /* If the term just contained a 0, the expression has nothing. Ensure - * that after the 0 nothing else follows */ - if (!ecs_os_strcmp(term->first.name, "0")) { - if (ptr[0]) { - ecs_parser_error(name, expr, (ptr - expr), - "unexpected term after 0"); - goto error; + do { + if (!ecs_iter_next(chain_it)) { + goto depleted; } - if (src->flags != 0) { - ecs_parser_error(name, expr, (ptr - expr), - "invalid combination of 0 with non-default subject"); - goto error; + ecs_page_iter_t *iter = &it->priv.iter.page; + + /* Copy everything up to the private iterator data */ + ecs_os_memcpy(it, chain_it, offsetof(ecs_iter_t, priv)); + + /* Keep instancing setting from original iterator */ + ECS_BIT_COND(it->flags, EcsIterIsInstanced, instanced); + + if (!chain_it->table) { + goto yield; /* Task query */ } - src->flags = EcsIsEntity; - src->id = 0; - ecs_os_free(term->first.name); - term->first.name = NULL; - } + int32_t offset = iter->offset; + int32_t limit = iter->limit; + if (!(offset || limit)) { + if (it->count) { + goto yield; + } else { + goto depleted; + } + } - /* Cannot combine EcsNothing with operators other than AND */ - if (term->oper != EcsAnd && ecs_term_match_0(term)) { - ecs_parser_error(name, expr, (ptr - expr), - "invalid operator for empty source"); - goto error; - } + int32_t count = it->count; + int32_t remaining = iter->remaining; - /* Verify consistency of OR expression */ - if (prev_or && term->oper == EcsOr) { - term->oper = EcsOr; - } + if (offset) { + if (offset > count) { + /* No entities to iterate in current table */ + iter->offset -= count; + it->count = 0; + continue; + } else { + it->offset += offset; + count = it->count -= offset; + iter->offset = 0; + offset_iter(it, offset); + } + } - /* Automatically assign This if entity is not assigned and the set is - * nothing */ - if (!(src->flags & EcsIsEntity)) { - if (!src->name) { - if (!src->id) { - src->id = EcsThis; - src->flags |= EcsIsVariable; + if (remaining) { + if (remaining > count) { + iter->remaining -= count; + } else { + it->count = remaining; + iter->remaining = 0; } + } else if (limit) { + /* Limit hit: no more entities left to iterate */ + goto done; } - } + } while (it->count == 0); - if (src->name && !ecs_os_strcmp(src->name, "0")) { - src->id = 0; - src->flags = EcsIsEntity; +yield: + if (!ECS_BIT_IS_SET(it->flags, EcsIterIsInstanced)) { + it->offset = 0; } - /* Process role */ - if (term->id_flags == ECS_AND) { - term->oper = EcsAndFrom; - term->id_flags = 0; - } else if (term->id_flags == ECS_OR) { - term->oper = EcsOrFrom; - term->id_flags = 0; - } else if (term->id_flags == ECS_NOT) { - term->oper = EcsNotFrom; - term->id_flags = 0; - } + return true; +done: + /* Cleanup iterator resources if it wasn't yet depleted */ + ecs_iter_fini(chain_it); +depleted: +error: + return false; +} - ptr = ecs_parse_whitespace(ptr); +bool ecs_page_next( + ecs_iter_t *it) +{ + ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(it->next == ecs_page_next, ECS_INVALID_PARAMETER, NULL); + ecs_check(it->chain_it != NULL, ECS_INVALID_PARAMETER, NULL); - return (char*)ptr; -error: - if (term) { - ecs_term_fini(term); + ECS_BIT_SET(it->chain_it->flags, EcsIterIsInstanced); + + if (flecs_iter_next_row(it)) { + return true; } - return NULL; + + return flecs_iter_next_instanced(it, ecs_page_next_instanced(it)); +error: + return false; } -#endif +ecs_iter_t ecs_worker_iter( + const ecs_iter_t *it, + int32_t index, + int32_t count) +{ + ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(it->next != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(count > 0, ECS_INVALID_PARAMETER, NULL); + ecs_check(index >= 0, ECS_INVALID_PARAMETER, NULL); + ecs_check(index < count, ECS_INVALID_PARAMETER, NULL); + return (ecs_iter_t){ + .real_world = it->real_world, + .world = it->world, + .priv.iter.worker = { + .index = index, + .count = count + }, + .next = ecs_worker_next, + .chain_it = (ecs_iter_t*)it, + .flags = it->flags & EcsIterIsInstanced + }; -#ifdef FLECS_SYSTEM +error: + return (ecs_iter_t){ 0 }; +} +static +bool ecs_worker_next_instanced( + ecs_iter_t *it) +{ + ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(it->chain_it != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(it->next == ecs_worker_next, ECS_INVALID_PARAMETER, NULL); -ecs_mixins_t ecs_system_t_mixins = { - .type_name = "ecs_system_t", - .elems = { - [EcsMixinWorld] = offsetof(ecs_system_t, world), - [EcsMixinEntity] = offsetof(ecs_system_t, entity), - [EcsMixinDtor] = offsetof(ecs_system_t, dtor) - } -}; + bool instanced = ECS_BIT_IS_SET(it->flags, EcsIterIsInstanced); -/* -- Public API -- */ + ecs_iter_t *chain_it = it->chain_it; + ecs_worker_iter_t *iter = &it->priv.iter.worker; + int32_t res_count = iter->count, res_index = iter->index; + int32_t per_worker, instances_per_worker, first; -ecs_entity_t ecs_run_intern( - ecs_world_t *world, - ecs_stage_t *stage, - ecs_entity_t system, - ecs_system_t *system_data, - int32_t stage_index, - int32_t stage_count, - ecs_ftime_t delta_time, - int32_t offset, - int32_t limit, - void *param) -{ - ecs_ftime_t time_elapsed = delta_time; - ecs_entity_t tick_source = system_data->tick_source; + do { + if (!ecs_iter_next(chain_it)) { + return false; + } - /* Support legacy behavior */ - if (!param) { - param = system_data->ctx; - } + /* Copy everything up to the private iterator data */ + ecs_os_memcpy(it, chain_it, offsetof(ecs_iter_t, priv)); - if (tick_source) { - const EcsTickSource *tick = ecs_get( - world, tick_source, EcsTickSource); + /* Keep instancing setting from original iterator */ + ECS_BIT_COND(it->flags, EcsIterIsInstanced, instanced); - if (tick) { - time_elapsed = tick->time_elapsed; + int32_t count = it->count; + int32_t instance_count = it->instance_count; + per_worker = count / res_count; + instances_per_worker = instance_count / res_count; + first = per_worker * res_index; + count -= per_worker * res_count; - /* If timer hasn't fired we shouldn't run the system */ - if (!tick->tick) { - return 0; + if (count) { + if (res_index < count) { + per_worker ++; + first += res_index; + } else { + first += count; } - } 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; } - } - if (ecs_should_log_3()) { - char *path = ecs_get_fullpath(world, system); - ecs_dbg_3("worker %d: %s", stage_index, path); - ecs_os_free(path); - } + if (!per_worker && it->table == NULL) { + if (res_index == 0) { + return true; + } else { + return false; + } + } + } while (!per_worker); - ecs_time_t time_start; - bool measure_time = ECS_BIT_IS_SET(world->flags, EcsWorldMeasureSystemTime); - if (measure_time) { - ecs_os_get_time(&time_start); - } + it->instance_count = instances_per_worker; + it->frame_offset += first; - ecs_world_t *thread_ctx = world; - if (stage) { - thread_ctx = stage->thread_ctx; + offset_iter(it, it->offset + first); + it->count = per_worker; + + if (ECS_BIT_IS_SET(it->flags, EcsIterIsInstanced)) { + it->offset += first; + } else { + it->offset = 0; } - ecs_defer_begin(thread_ctx); + return true; +error: + return false; +} - /* Prepare the query iterator */ - ecs_iter_t pit, wit, qit = ecs_query_iter(thread_ctx, system_data->query); - ecs_iter_t *it = &qit; +bool ecs_worker_next( + ecs_iter_t *it) +{ + ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(it->next == ecs_worker_next, ECS_INVALID_PARAMETER, NULL); + ecs_check(it->chain_it != NULL, ECS_INVALID_PARAMETER, NULL); - if (offset || limit) { - pit = ecs_page_iter(it, offset, limit); - it = &pit; - } + ECS_BIT_SET(it->chain_it->flags, EcsIterIsInstanced); - if (stage_count > 1 && system_data->multi_threaded) { - wit = ecs_worker_iter(it, stage_index, stage_count); - it = &wit; + if (flecs_iter_next_row(it)) { + return true; } - qit.system = system; - qit.delta_time = delta_time; - qit.delta_system_time = time_elapsed; - qit.frame_offset = offset; - qit.param = param; - qit.ctx = system_data->ctx; - qit.binding_ctx = system_data->binding_ctx; - - ecs_iter_action_t action = system_data->action; - it->callback = action; - - ecs_run_action_t run = system_data->run; - if (run) { - run(it); - } else if (system_data->query->filter.term_count) { - if (it == &qit) { - while (ecs_query_next(&qit)) { - action(&qit); - } - } else { - while (ecs_iter_next(it)) { - action(it); - } - } - } else { - action(&qit); - } + return flecs_iter_next_instanced(it, ecs_worker_next_instanced(it)); +error: + return false; +} - if (measure_time) { - system_data->time_spent += (float)ecs_time_measure(&time_start); - } +#include - system_data->invoke_count ++; +#ifndef FLECS_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 }; - ecs_defer_end(thread_ctx); +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; - return it->interrupted_by; -} + v.u = u; -/* -- Public API -- */ + 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); + } -ecs_entity_t ecs_run_w_filter( - ecs_world_t *world, - ecs_entity_t system, - ecs_ftime_t delta_time, - int32_t offset, - int32_t limit, - void *param) -{ - ecs_stage_t *stage = flecs_stage_from_world(&world); - ecs_system_t *system_data = ecs_poly_get(world, system, ecs_system_t); - ecs_assert(system_data != NULL, ECS_INVALID_PARAMETER, NULL); - return ecs_run_intern(world, stage, system, system_data, 0, 0, delta_time, - offset, limit, param); + return u; } +#endif -ecs_entity_t ecs_run_worker( - ecs_world_t *world, - ecs_entity_t system, - int32_t stage_index, - int32_t stage_count, - ecs_ftime_t delta_time, - void *param) +int32_t flecs_next_pow_of_2( + int32_t n) { - ecs_stage_t *stage = flecs_stage_from_world(&world); - ecs_system_t *system_data = ecs_poly_get(world, system, ecs_system_t); - ecs_assert(system_data != NULL, ECS_INVALID_PARAMETER, NULL); + n --; + n |= n >> 1; + n |= n >> 2; + n |= n >> 4; + n |= n >> 8; + n |= n >> 16; + n ++; - return ecs_run_intern( - world, stage, system, system_data, stage_index, stage_count, - delta_time, 0, 0, param); + return n; } -ecs_entity_t ecs_run( - ecs_world_t *world, - ecs_entity_t system, - ecs_ftime_t delta_time, - void *param) +/** Convert time to double */ +double ecs_time_to_double( + ecs_time_t t) { - return ecs_run_w_filter(world, system, delta_time, 0, 0, param); + double result; + result = t.sec; + return result + (double)t.nanosec / (double)1000000000; } -ecs_query_t* ecs_system_get_query( - const ecs_world_t *world, - ecs_entity_t system) +ecs_time_t ecs_time_sub( + ecs_time_t t1, + ecs_time_t t2) { - const ecs_system_t *s = ecs_poly_get(world, system, ecs_system_t); - if (s) { - return s->query; + ecs_time_t result; + + if (t1.nanosec >= t2.nanosec) { + result.nanosec = t1.nanosec - t2.nanosec; + result.sec = t1.sec - t2.sec; } else { - return NULL; + result.nanosec = t1.nanosec - t2.nanosec + 1000000000; + result.sec = t1.sec - t2.sec - 1; } -} -void* ecs_get_system_ctx( - const ecs_world_t *world, - ecs_entity_t system) -{ - const ecs_system_t *s = ecs_poly_get(world, system, ecs_system_t); - if (s) { - return s->ctx; - } else { - return NULL; - } + return result; } -void* ecs_get_system_binding_ctx( - const ecs_world_t *world, - ecs_entity_t system) +void ecs_sleepf( + double t) { - const ecs_system_t *s = ecs_poly_get(world, system, ecs_system_t); - if (s) { - return s->binding_ctx; - } else { - return NULL; - } -} - -/* System deinitialization */ -static -void flecs_system_fini(ecs_system_t *sys) { - if (sys->ctx_free) { - sys->ctx_free(sys->ctx); - } - - if (sys->binding_ctx_free) { - sys->binding_ctx_free(sys->binding_ctx); + if (t > 0) { + int sec = (int)t; + int nsec = (int)((t - sec) * 1000000000); + ecs_os_sleep(sec, nsec); } - - ecs_poly_free(sys, ecs_system_t); } -ecs_entity_t ecs_system_init( - ecs_world_t *world, - const ecs_system_desc_t *desc) +double ecs_time_measure( + ecs_time_t *start) { - ecs_poly_assert(world, ecs_world_t); - ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(desc->_canary == 0, ECS_INVALID_PARAMETER, NULL); - ecs_assert(!(world->flags & EcsWorldReadonly), - ECS_INVALID_WHILE_READONLY, NULL); + 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); +} - ecs_entity_t entity = desc->entity; - if (!entity) { - entity = ecs_new(world, 0); +void* ecs_os_memdup( + const void *src, + ecs_size_t size) +{ + if (!src) { + return NULL; } - EcsPoly *poly = ecs_poly_bind(world, entity, ecs_system_t); - if (!poly->poly) { - ecs_system_t *system = ecs_poly_new(ecs_system_t); - ecs_assert(system != NULL, ECS_INTERNAL_ERROR, NULL); - poly->poly = system; - system->world = world; - system->dtor = (ecs_poly_dtor_t)flecs_system_fini; - system->entity = entity; - - ecs_query_desc_t query_desc = desc->query; - query_desc.entity = entity; - - ecs_query_t *query = ecs_query_init(world, &query_desc); - if (!query) { - ecs_delete(world, entity); - return 0; - } - - /* Prevent the system from moving while we're initializing */ - ecs_defer_begin(world); - - system->query = query; - system->query_entity = query->entity; + void *dst = ecs_os_malloc(size); + ecs_assert(dst != NULL, ECS_OUT_OF_MEMORY, NULL); + ecs_os_memcpy(dst, src, size); + return dst; +} - system->run = desc->run; - system->action = desc->callback; +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); +} - system->ctx = desc->ctx; - system->binding_ctx = desc->binding_ctx; +int flecs_entity_compare_qsort( + const void *e1, + const void *e2) +{ + ecs_entity_t v1 = *(ecs_entity_t*)e1; + ecs_entity_t v2 = *(ecs_entity_t*)e2; + return flecs_entity_compare(v1, NULL, v2, NULL); +} - system->ctx_free = desc->ctx_free; - system->binding_ctx_free = desc->binding_ctx_free; +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; +} - system->tick_source = desc->tick_source; +char* ecs_vasprintf( + const char *fmt, + va_list args) +{ + ecs_size_t size = 0; + char *result = NULL; + va_list tmpa; - system->multi_threaded = desc->multi_threaded; - system->no_staging = desc->no_staging; + va_copy(tmpa, args); - if (desc->interval != 0 || desc->rate != 0 || desc->tick_source != 0) { -#ifdef FLECS_TIMER - if (desc->interval != 0) { - ecs_set_interval(world, entity, desc->interval); - } + size = vsnprintf(result, 0, fmt, tmpa); - if (desc->rate) { - ecs_set_rate(world, entity, desc->rate, desc->tick_source); - } else if (desc->tick_source) { - ecs_set_tick_source(world, entity, desc->tick_source); - } -#else - ecs_abort(ECS_UNSUPPORTED, "timer module not available"); -#endif - } + va_end(tmpa); - if (ecs_get_name(world, entity)) { - ecs_trace("#[green]system#[reset] %s created", - ecs_get_name(world, entity)); - } + if ((int32_t)size < 0) { + return NULL; + } - ecs_defer_end(world); - } else { - ecs_system_t *system = ecs_poly(poly->poly, ecs_system_t); + result = (char *) ecs_os_malloc(size + 1); - if (desc->run) { - system->run = desc->run; - } - 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) { - ECS_BIT_SET(system->query->filter.flags, EcsFilterIsInstanced); - } - if (desc->multi_threaded) { - system->multi_threaded = desc->multi_threaded; - } - if (desc->no_staging) { - system->no_staging = desc->no_staging; - } + if (!result) { + return NULL; } - ecs_poly_modified(world, entity, ecs_system_t); + ecs_os_vsprintf(result, fmt, args); - return entity; -error: - return 0; + return result; } -void FlecsSystemImport( - ecs_world_t *world) +char* ecs_asprintf( + const char *fmt, + ...) { - ECS_MODULE(world, FlecsSystem); - - ecs_set_name_prefix(world, "Ecs"); - - flecs_bootstrap_tag(world, EcsSystem); - flecs_bootstrap_component(world, EcsTickSource); - - /* 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, EcsMonitor); - ecs_set_scope(world, old_scope); + va_list args; + va_start(args, fmt); + char *result = ecs_vasprintf(fmt, args); + va_end(args); + return result; } -#endif - - -#ifdef FLECS_MONITOR - -ECS_COMPONENT_DECLARE(FlecsMonitor); -ECS_COMPONENT_DECLARE(EcsWorldStats); -ECS_COMPONENT_DECLARE(EcsPipelineStats); - -ecs_entity_t EcsPeriod1s = 0; -ecs_entity_t EcsPeriod1m = 0; -ecs_entity_t EcsPeriod1h = 0; -ecs_entity_t EcsPeriod1d = 0; -ecs_entity_t EcsPeriod1w = 0; +/* + 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 int32_t flecs_day_interval_count = 24; -static int32_t flecs_week_interval_count = 168; -static -ECS_COPY(EcsPipelineStats, dst, src, { - (void)dst; - (void)src; - ecs_abort(ECS_INVALID_OPERATION, "cannot copy pipeline stats component"); +/* -- Identifier Component -- */ +static ECS_DTOR(EcsIdentifier, ptr, { + ecs_os_strset(&ptr->value, NULL); }) -static -ECS_MOVE(EcsPipelineStats, dst, src, { - ecs_os_memcpy_t(dst, src, EcsPipelineStats); - ecs_os_zeromem(src); +static ECS_COPY(EcsIdentifier, dst, src, { + ecs_os_strset(&dst->value, src->value); + dst->hash = src->hash; + dst->length = src->length; + dst->index_hash = src->index_hash; + dst->index = src->index; }) -static -ECS_DTOR(EcsPipelineStats, ptr, { - ecs_pipeline_stats_fini(&ptr->stats); +static ECS_MOVE(EcsIdentifier, dst, src, { + ecs_os_strset(&dst->value, NULL); + dst->value = src->value; + dst->hash = src->hash; + dst->length = src->length; + dst->index_hash = src->index_hash; + dst->index = src->index; + + src->value = NULL; + src->hash = 0; + src->index_hash = 0; + src->index = 0; + src->length = 0; }) static -void MonitorStats(ecs_iter_t *it) { +void ecs_on_set(EcsIdentifier)(ecs_iter_t *it) { + EcsIdentifier *ptr = ecs_field(it, EcsIdentifier, 1); + ecs_world_t *world = it->real_world; + ecs_entity_t evt = it->event; + ecs_id_t evt_id = it->event_id; + ecs_entity_t kind = ECS_PAIR_SECOND(evt_id); /* Name, Symbol, Alias */ + ecs_id_t pair = ecs_childof(0); + ecs_hashmap_t *index = NULL; - EcsStatsHeader *hdr = ecs_field_w_size(it, 0, 1); - ecs_id_t kind = ecs_pair_first(it->world, ecs_field_id(it, 1)); - void *stats = ECS_OFFSET_T(hdr, EcsStatsHeader); + if (kind == EcsSymbol) { + index = &world->symbols; + } else if (kind == EcsAlias) { + index = &world->aliases; + } else if (kind == EcsName) { + ecs_assert(it->table != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_search(world, it->table, ecs_childof(EcsWildcard), &pair); + ecs_assert(pair != 0, ECS_INTERNAL_ERROR, NULL); - ecs_ftime_t elapsed = hdr->elapsed; - hdr->elapsed += it->delta_time; + if (evt == EcsOnSet) { + index = flecs_id_name_index_ensure(world, pair); + } else { + index = flecs_id_name_index_get(world, pair); + } + } - int32_t t_last = (int32_t)(elapsed * 60); - int32_t t_next = (int32_t)(hdr->elapsed * 60); - int32_t i, dif = t_last - t_next; + int i, count = it->count; - ecs_world_stats_t last_world = {0}; - ecs_pipeline_stats_t last_pipeline = {0}; - void *last = NULL; + for (i = 0; i < count; i ++) { + EcsIdentifier *cur = &ptr[i]; + uint64_t hash; + ecs_size_t len; + const char *name = cur->value; - if (!dif) { - /* Copy last value so we can pass it to reduce_last */ - if (kind == ecs_id(EcsWorldStats)) { - last = &last_world; - ecs_world_stats_copy_last(&last_world, stats); - } else if (kind == ecs_id(EcsPipelineStats)) { - last = &last_pipeline; - ecs_pipeline_stats_copy_last(&last_pipeline, stats); + if (cur->index && cur->index != index) { + /* If index doesn't match up, the value must have been copied from + * another entity, so reset index & cached index hash */ + cur->index = NULL; + cur->index_hash = 0; } - } - - if (kind == ecs_id(EcsWorldStats)) { - ecs_world_stats_get(world, stats); - } else if (kind == ecs_id(EcsPipelineStats)) { - ecs_pipeline_stats_get(world, ecs_get_pipeline(world), stats); - } - if (!dif) { - /* Still in same interval, combine with last measurement */ - hdr->reduce_count ++; - if (kind == ecs_id(EcsWorldStats)) { - ecs_world_stats_reduce_last(stats, last, hdr->reduce_count); - } else if (kind == ecs_id(EcsPipelineStats)) { - ecs_pipeline_stats_reduce_last(stats, last, hdr->reduce_count); + if (cur->value && (evt == EcsOnSet)) { + len = cur->length = ecs_os_strlen(name); + hash = cur->hash = flecs_hash(name, len); + } else { + len = cur->length = 0; + hash = cur->hash = 0; + cur->index = NULL; } - } else if (dif > 1) { - /* More than 16ms has passed, backfill */ - for (i = 1; i < dif; i ++) { - if (kind == ecs_id(EcsWorldStats)) { - ecs_world_stats_repeat_last(stats); - } else if (kind == ecs_id(EcsPipelineStats)) { - ecs_world_stats_repeat_last(stats); + + if (index) { + uint64_t index_hash = cur->index_hash; + ecs_entity_t e = it->entities[i]; + + if (hash != index_hash) { + if (index_hash) { + flecs_name_index_remove(index, e, index_hash); + } + if (hash) { + flecs_name_index_ensure(index, e, name, len, hash); + cur->index_hash = hash; + cur->index = index; + } + } else { + /* Name didn't change, but the string could have been + * reallocated. Make sure name index points to correct string */ + flecs_name_index_update_name(index, e, hash, name); } } - hdr->reduce_count = 0; - } - - if (last && kind == ecs_id(EcsPipelineStats)) { - ecs_pipeline_stats_fini(last); } } -static -void ReduceStats(ecs_iter_t *it) { - void *dst = ecs_field_w_size(it, 0, 1); - void *src = ecs_field_w_size(it, 0, 2); - ecs_id_t kind = ecs_pair_first(it->world, ecs_field_id(it, 1)); +/* -- Poly component -- */ - dst = ECS_OFFSET_T(dst, EcsStatsHeader); - src = ECS_OFFSET_T(src, EcsStatsHeader); +static ECS_COPY(EcsPoly, dst, src, { + (void)dst; + (void)src; + ecs_abort(ECS_INVALID_OPERATION, "poly component cannot be copied"); +}) - if (kind == ecs_id(EcsWorldStats)) { - ecs_world_stats_reduce(dst, src); - } else if (kind == ecs_id(EcsPipelineStats)) { - ecs_pipeline_stats_reduce(dst, src); +static ECS_MOVE(EcsPoly, dst, src, { + if (dst->poly && (dst->poly != src->poly)) { + ecs_poly_dtor_t *dtor = ecs_get_dtor(dst->poly); + ecs_assert(dtor != NULL, ECS_INTERNAL_ERROR, NULL); + dtor[0](dst->poly); } -} - -static -void AggregateStats(ecs_iter_t *it) { - int32_t interval = *(int32_t*)it->ctx; - - EcsStatsHeader *dst_hdr = ecs_field_w_size(it, 0, 1); - EcsStatsHeader *src_hdr = ecs_field_w_size(it, 0, 2); - void *dst = ECS_OFFSET_T(dst_hdr, EcsStatsHeader); - void *src = ECS_OFFSET_T(src_hdr, EcsStatsHeader); + dst->poly = src->poly; + src->poly = NULL; +}) - ecs_id_t kind = ecs_pair_first(it->world, ecs_field_id(it, 1)); +static ECS_DTOR(EcsPoly, ptr, { + if (ptr->poly) { + ecs_poly_dtor_t *dtor = ecs_get_dtor(ptr->poly); + ecs_assert(dtor != NULL, ECS_INTERNAL_ERROR, NULL); + dtor[0](ptr->poly); + } +}) - ecs_world_stats_t last_world = {0}; - ecs_pipeline_stats_t last_pipeline = {0}; - void *last = NULL; - if (dst_hdr->reduce_count != 0) { - /* Copy last value so we can pass it to reduce_last */ - if (kind == ecs_id(EcsWorldStats)) { - last_world.t = 0; - ecs_world_stats_copy_last(&last_world, dst); - last = &last_world; - } else if (kind == ecs_id(EcsPipelineStats)) { - last_pipeline.t = 0; - ecs_pipeline_stats_copy_last(&last_pipeline, dst); - last = &last_pipeline; - } - } +/* -- Builtin triggers -- */ - /* Reduce from minutes to the current day */ - if (kind == ecs_id(EcsWorldStats)) { - ecs_world_stats_reduce(dst, src); - } else if (kind == ecs_id(EcsPipelineStats)) { - ecs_pipeline_stats_reduce(dst, src); +static +void assert_relation_unused( + ecs_world_t *world, + ecs_entity_t rel, + ecs_entity_t property) +{ + if (world->flags & EcsWorldFini) { + return; } - if (dst_hdr->reduce_count != 0) { - if (kind == ecs_id(EcsWorldStats)) { - ecs_world_stats_reduce_last(dst, last, dst_hdr->reduce_count); - } else if (kind == ecs_id(EcsPipelineStats)) { - ecs_pipeline_stats_reduce_last(dst, last, dst_hdr->reduce_count); + ecs_vector_t *marked_ids = world->store.marked_ids; + int32_t i, count = ecs_vector_count(marked_ids); + for (i = 0; i < count; i ++) { + ecs_marked_id_t *mid = ecs_vector_get(marked_ids, ecs_marked_id_t, i); + if (mid->id == ecs_pair(rel, EcsWildcard)) { + /* If id is being cleaned up, no need to throw error as tables will + * be cleaned up */ + return; } } - /* A day has 60 24 minute intervals */ - dst_hdr->reduce_count ++; - if (dst_hdr->reduce_count >= interval) { - dst_hdr->reduce_count = 0; - } + if (ecs_id_in_use(world, ecs_pair(rel, EcsWildcard))) { + char *r_str = ecs_get_fullpath(world, rel); + char *p_str = ecs_get_fullpath(world, property); - if (last && kind == ecs_id(EcsPipelineStats)) { - ecs_pipeline_stats_fini(last); + ecs_throw(ECS_ID_IN_USE, + "cannot change property '%s' for relationship '%s': already in use", + p_str, r_str); + + ecs_os_free(r_str); + ecs_os_free(p_str); } -} - -static -void flecs_stats_monitor_import( - ecs_world_t *world, - ecs_id_t kind, - size_t size) -{ - ecs_entity_t prev = ecs_set_scope(world, kind); - - // Called each frame, collects 60 measurements per second - ecs_system_init(world, &(ecs_system_desc_t){ - .entity = ecs_entity(world, { .name = "Monitor1s", .add = {ecs_dependson(EcsPreFrame)} }), - .query.filter.terms = {{ - .id = ecs_pair(kind, EcsPeriod1s), - .src.id = EcsWorld - }}, - .callback = MonitorStats - }); - - // Called each second, reduces into 60 measurements per minute - ecs_entity_t mw1m = ecs_system_init(world, &(ecs_system_desc_t){ - .entity = ecs_entity(world, { .name = "Monitor1m", .add = {ecs_dependson(EcsPreFrame)} }), - .query.filter.terms = {{ - .id = ecs_pair(kind, EcsPeriod1m), - .src.id = EcsWorld - }, { - .id = ecs_pair(kind, EcsPeriod1s), - .src.id = EcsWorld - }}, - .callback = ReduceStats, - .interval = 1.0 - }); - - // Called each minute, reduces into 60 measurements per hour - ecs_system_init(world, &(ecs_system_desc_t){ - .entity = ecs_entity(world, { .name = "Monitor1h", .add = {ecs_dependson(EcsPreFrame)} }), - .query.filter.terms = {{ - .id = ecs_pair(kind, EcsPeriod1h), - .src.id = EcsWorld - }, { - .id = ecs_pair(kind, EcsPeriod1m), - .src.id = EcsWorld - }}, - .callback = ReduceStats, - .rate = 60, - .tick_source = mw1m - }); - - // Called each minute, reduces into 60 measurements per day - ecs_system_init(world, &(ecs_system_desc_t){ - .entity = ecs_entity(world, { .name = "Monitor1d", .add = {ecs_dependson(EcsPreFrame)} }), - .query.filter.terms = {{ - .id = ecs_pair(kind, EcsPeriod1d), - .src.id = EcsWorld - }, { - .id = ecs_pair(kind, EcsPeriod1m), - .src.id = EcsWorld - }}, - .callback = AggregateStats, - .rate = 60, - .tick_source = mw1m, - .ctx = &flecs_day_interval_count - }); - - // Called each hour, reduces into 60 measurements per week - ecs_system_init(world, &(ecs_system_desc_t){ - .entity = ecs_entity(world, { .name = "Monitor1w", .add = {ecs_dependson(EcsPreFrame)} }), - .query.filter.terms = {{ - .id = ecs_pair(kind, EcsPeriod1w), - .src.id = EcsWorld - }, { - .id = ecs_pair(kind, EcsPeriod1h), - .src.id = EcsWorld - }}, - .callback = AggregateStats, - .rate = 60, - .tick_source = mw1m, - .ctx = &flecs_week_interval_count - }); - - ecs_set_scope(world, prev); - - ecs_set_id(world, EcsWorld, ecs_pair(kind, EcsPeriod1s), size, NULL); - ecs_set_id(world, EcsWorld, ecs_pair(kind, EcsPeriod1m), size, NULL); - ecs_set_id(world, EcsWorld, ecs_pair(kind, EcsPeriod1h), size, NULL); - ecs_set_id(world, EcsWorld, ecs_pair(kind, EcsPeriod1d), size, NULL); - ecs_set_id(world, EcsWorld, ecs_pair(kind, EcsPeriod1w), size, NULL); -} - -static -void flecs_world_monitor_import( - ecs_world_t *world) -{ - ECS_COMPONENT_DEFINE(world, EcsWorldStats); - flecs_stats_monitor_import(world, ecs_id(EcsWorldStats), - sizeof(EcsWorldStats)); +error: + return; } static -void flecs_pipeline_monitor_import( - ecs_world_t *world) +bool set_id_flag( + ecs_id_record_t *idr, + ecs_flags32_t flag) { - ECS_COMPONENT_DEFINE(world, EcsPipelineStats); - - ecs_set_hooks(world, EcsPipelineStats, { - .ctor = ecs_default_ctor, - .copy = ecs_copy(EcsPipelineStats), - .move = ecs_move(EcsPipelineStats), - .dtor = ecs_dtor(EcsPipelineStats) - }); - - flecs_stats_monitor_import(world, ecs_id(EcsPipelineStats), - sizeof(EcsPipelineStats)); + if (!(idr->flags & flag)) { + idr->flags |= flag; + return true; + } + return false; } -void FlecsMonitorImport( - ecs_world_t *world) +static +bool unset_id_flag( + ecs_id_record_t *idr, + ecs_flags32_t flag) { - ECS_MODULE_DEFINE(world, FlecsMonitor); - - ecs_set_name_prefix(world, "Ecs"); - - EcsPeriod1s = ecs_new_entity(world, "EcsPeriod1s"); - EcsPeriod1m = ecs_new_entity(world, "EcsPeriod1m"); - EcsPeriod1h = ecs_new_entity(world, "EcsPeriod1h"); - EcsPeriod1d = ecs_new_entity(world, "EcsPeriod1d"); - EcsPeriod1w = ecs_new_entity(world, "EcsPeriod1w"); - - flecs_world_monitor_import(world); - flecs_pipeline_monitor_import(world); - - if (ecs_os_has_time()) { - ecs_measure_frame_time(world, true); - ecs_measure_system_time(world, true); + if ((idr->flags & flag)) { + idr->flags &= ~flag; + return true; } + return false; } -#endif - -#include - -#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 register_id_flag_for_relation( + ecs_iter_t *it, + ecs_entity_t prop, + ecs_flags32_t flag, + ecs_flags32_t not_flag, + ecs_flags32_t entity_flag) { - ecs_poly_assert(world, ecs_world_t); + ecs_world_t *world = it->world; + ecs_entity_t event = it->event; - ecs_entity_t cur = 0; - char buff[22]; - const char *name; + int i, count = it->count; + for (i = 0; i < count; i ++) { + ecs_entity_t e = it->entities[i]; + bool changed = false; - if (ecs_is_valid(world, child)) { - cur = ecs_get_target(world, child, EcsChildOf, 0); - if (cur) { - ecs_assert(cur != child, ECS_CYCLE_DETECTED, NULL); - if (cur != parent && (cur != EcsFlecsCore || prefix != NULL)) { - path_append(world, parent, cur, sep, prefix, buf); - ecs_strbuf_appendstr(buf, sep); + if (event == EcsOnAdd) { + ecs_id_record_t *idr = flecs_id_record_ensure(world, e); + changed |= set_id_flag(idr, flag); + idr = flecs_id_record_ensure(world, ecs_pair(e, EcsWildcard)); + do { + changed |= set_id_flag(idr, flag); + } while ((idr = idr->first.next)); + if (entity_flag) flecs_add_flag(world, e, entity_flag); + } else if (event == EcsOnRemove) { + ecs_id_record_t *idr = flecs_id_record_get(world, e); + if (idr) changed |= unset_id_flag(idr, not_flag); + idr = flecs_id_record_get(world, ecs_pair(e, EcsWildcard)); + if (idr) { + do { + changed |= unset_id_flag(idr, not_flag); + } while ((idr = idr->first.next)); } - } else if (prefix) { - ecs_strbuf_appendstr(buf, prefix); } - 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; + if (changed) { + assert_relation_unused(world, e, prop); + } } - - ecs_strbuf_appendstr(buf, name); - - return cur != 0; } static -bool is_number( - const char *name) -{ - ecs_assert(name != NULL, ECS_INTERNAL_ERROR, NULL); +void register_final(ecs_iter_t *it) { + ecs_world_t *world = it->world; - if (!isdigit(name[0])) { - return false; - } - - ecs_size_t i, length = ecs_os_strlen(name); - for (i = 1; i < length; i ++) { - char ch = name[i]; - - if (!isdigit(ch)) { - break; + int i, count = it->count; + for (i = 0; i < count; i ++) { + ecs_entity_t e = it->entities[i]; + if (flecs_id_record_get(world, ecs_pair(EcsIsA, e)) != NULL) { + char *e_str = ecs_get_fullpath(world, e); + ecs_throw(ECS_ID_IN_USE, + "cannot change property 'Final' for '%s': already inherited from", + e_str); + ecs_os_free(e_str); + error: + continue; } } - - return i >= length; } -static -ecs_entity_t name_to_id( - const ecs_world_t *world, - const char *name) -{ - 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 register_on_delete(ecs_iter_t *it) { + ecs_id_t id = ecs_field_id(it, 1); + register_id_flag_for_relation(it, EcsOnDelete, + ECS_ID_ON_DELETE_FLAG(ECS_PAIR_SECOND(id)), + EcsIdOnDeleteMask, + EcsEntityObservedId); } static -ecs_entity_t get_builtin( - const char *name) -{ - if (name[0] == '.' && name[1] == '\0') { - return EcsThis; - } else if (name[0] == '*' && name[1] == '\0') { - return EcsWildcard; - } else if (name[0] == '_' && name[1] == '\0') { - return EcsAny; - } +void register_on_delete_object(ecs_iter_t *it) { + ecs_id_t id = ecs_field_id(it, 1); + register_id_flag_for_relation(it, EcsOnDeleteTarget, + ECS_ID_ON_DELETE_OBJECT_FLAG(ECS_PAIR_SECOND(id)), + EcsIdOnDeleteObjectMask, + EcsEntityObservedId); +} - return 0; +static +void register_acyclic(ecs_iter_t *it) { + register_id_flag_for_relation(it, EcsAcyclic, EcsIdAcyclic, + EcsIdAcyclic, 0); } static -bool is_sep( - const char **ptr, - const char *sep) -{ - ecs_size_t len = ecs_os_strlen(sep); +void register_tag(ecs_iter_t *it) { + register_id_flag_for_relation(it, EcsTag, EcsIdTag, ~EcsIdTag, 0); - if (!ecs_os_strncmp(*ptr, sep, len)) { - *ptr += len; - return true; - } else { - return false; + /* Ensure that all id records for tag have type info set to NULL */ + ecs_world_t *world = it->real_world; + int i, count = it->count; + for (i = 0; i < count; i ++) { + ecs_entity_t e = it->entities[i]; + + if (it->event == EcsOnAdd) { + ecs_id_record_t *idr = flecs_id_record_get(world, + ecs_pair(e, EcsWildcard)); + ecs_assert(idr != NULL, ECS_INTERNAL_ERROR, NULL); + do { + if (idr->type_info != NULL) { + assert_relation_unused(world, e, EcsTag); + } + idr->type_info = NULL; + } while ((idr = idr->first.next)); + } } } static -const char* path_elem( - const char *path, - const char *sep, - int32_t *len) -{ - const char *ptr; - char ch; - int32_t template_nesting = 0; - int32_t count = 0; +void register_exclusive(ecs_iter_t *it) { + register_id_flag_for_relation(it, EcsExclusive, EcsIdExclusive, + EcsIdExclusive, 0); +} - for (ptr = path; (ch = *ptr); ptr ++) { - if (ch == '<') { - template_nesting ++; - } else if (ch == '>') { - template_nesting --; - } +static +void register_dont_inherit(ecs_iter_t *it) { + register_id_flag_for_relation(it, EcsDontInherit, + EcsIdDontInherit, EcsIdDontInherit, 0); +} - ecs_check(template_nesting >= 0, ECS_INVALID_PARAMETER, path); +static +void register_with(ecs_iter_t *it) { + register_id_flag_for_relation(it, EcsWith, EcsIdWith, 0, 0); +} - if (!template_nesting && is_sep(&ptr, sep)) { - break; - } +static +void register_union(ecs_iter_t *it) { + register_id_flag_for_relation(it, EcsUnion, EcsIdUnion, 0, 0); +} - count ++; +static +void register_slot_of(ecs_iter_t *it) { + int i, count = it->count; + for (i = 0; i < count; i ++) { + ecs_add_id(it->world, it->entities[i], EcsUnion); } +} - if (len) { - *len = count; - } +static +void on_symmetric_add_remove(ecs_iter_t *it) { + ecs_entity_t pair = ecs_field_id(it, 1); - if (count) { - return ptr; - } else { - return NULL; + if (!ECS_HAS_ID_FLAG(pair, PAIR)) { + /* If relationship was not added as a pair, there's nothing to do */ + return; } -error: - return NULL; -} -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; + ecs_entity_t rel = ECS_PAIR_FIRST(pair); + ecs_entity_t obj = ECS_PAIR_SECOND(pair); + ecs_entity_t event = it->event; + + int i, count = it->count; + for (i = 0; i < count; i ++) { + ecs_entity_t subj = it->entities[i]; + if (event == EcsOnAdd) { + if (!ecs_has_id(it->real_world, obj, ecs_pair(rel, subj))) { + ecs_add_pair(it->world, obj, rel, subj); + } + } else { + if (ecs_has_id(it->real_world, obj, ecs_pair(rel, subj))) { + ecs_remove_pair(it->world, obj, rel, subj); + } } } +} - if (!start_from_root && !parent && new_entity) { - parent = ecs_get_scope(world); - } +static +void register_symmetric(ecs_iter_t *it) { + ecs_world_t *world = it->real_world; - *path_ptr = path; + int i, count = it->count; + for (i = 0; i < count; i ++) { + ecs_entity_t r = it->entities[i]; + assert_relation_unused(world, r, EcsSymmetric); - return parent; + /* Create observer that adds the reverse relationship when R(X, Y) is + * added, or remove the reverse relationship when R(X, Y) is removed. */ + ecs_observer_init(world, &(ecs_observer_desc_t){ + .entity = ecs_entity(world, {.add = {ecs_childof(EcsFlecsInternals)}}), + .filter.terms[0] = { .id = ecs_pair(r, EcsWildcard) }, + .callback = on_symmetric_add_remove, + .events = {EcsOnAdd, EcsOnRemove} + }); + } } static -void on_set_symbol(ecs_iter_t *it) { - EcsIdentifier *n = ecs_field(it, EcsIdentifier, 1); +void on_component(ecs_iter_t *it) { ecs_world_t *world = it->world; + EcsComponent *c = ecs_field(it, EcsComponent, 1); - int i; - for (i = 0; i < it->count; i ++) { + int i, count = it->count; + for (i = 0; i < count; i ++) { ecs_entity_t e = it->entities[i]; - flecs_name_index_ensure( - &world->symbols, e, n[i].value, n[i].length, n[i].hash); + if (it->event == EcsOnSet) { + if (flecs_type_info_init_id( + world, e, c[i].size, c[i].alignment, NULL)) + { + assert_relation_unused(world, e, ecs_id(EcsComponent)); + } + } else if (it->event == EcsOnRemove) { + flecs_type_info_free(world, e); + } } } -void flecs_bootstrap_hierarchy(ecs_world_t *world) { - ecs_observer_init(world, &(ecs_observer_desc_t){ - .entity = ecs_entity(world, {.add = {ecs_childof(EcsFlecsInternals)}}), - .filter.terms[0] = {.id = ecs_pair(ecs_id(EcsIdentifier), EcsSymbol), .src.flags = EcsSelf }, - .callback = on_set_symbol, - .events = {EcsOnSet}, - .yield_existing = true - }); -} +static +void ensure_module_tag(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_target(world, e, EcsChildOf, 0); + if (parent) { + ecs_add_id(world, parent, EcsModule); + } + } +} -/* Public functions */ +/* -- Triggers for keeping hashed ids in sync -- */ -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); +static +void on_parent_change(ecs_iter_t *it) { + ecs_world_t *world = it->world; + ecs_table_t *other_table = it->other_table, *table = it->table; - world = ecs_get_world(world); + int32_t col = ecs_search(it->real_world, table, + ecs_pair(ecs_id(EcsIdentifier), EcsName), 0); + bool has_name = col != -1; + bool other_has_name = ecs_search(it->real_world, other_table, + ecs_pair(ecs_id(EcsIdentifier), EcsName), 0) != -1; - if (child == EcsWildcard) { - ecs_strbuf_appendstr(buf, "*"); - return; - } - if (child == EcsAny) { - ecs_strbuf_appendstr(buf, "_"); + if (!has_name && !other_has_name) { + /* If tables don't have names, index does not need to be updated */ return; } - if (!sep) { - sep = "."; + ecs_id_t to_pair = it->event_id; + ecs_id_t from_pair = ecs_childof(0); + + /* Find the other ChildOf relationship */ + ecs_search(it->real_world, other_table, + ecs_pair(EcsChildOf, EcsWildcard), &from_pair); + + bool to_has_name = has_name, from_has_name = other_has_name; + if (it->event == EcsOnRemove) { + if (from_pair != ecs_childof(0)) { + /* Because ChildOf is an exclusive relationship, events always come + * in OnAdd/OnRemove pairs (add for the new, remove for the old + * parent). We only need one of those events, so filter out the + * OnRemove events except for the case where a parent is removed and + * not replaced with another parent. */ + return; + } + + ecs_id_t temp = from_pair; + from_pair = to_pair; + to_pair = temp; + + to_has_name = other_has_name; + from_has_name = has_name; } - if (!child || parent != child) { - path_append(world, parent, child, sep, prefix, buf); - } else { - ecs_strbuf_appendstr(buf, ""); + /* Get the table column with names */ + const EcsIdentifier *names = ecs_iter_column(it, EcsIdentifier, col); + + ecs_hashmap_t *from_index = 0; + if (from_has_name) { + from_index = flecs_id_name_index_get(world, from_pair); + } + ecs_hashmap_t *to_index = NULL; + if (to_has_name) { + to_index = flecs_id_name_index_ensure(world, to_pair); } -error: - return; + int32_t i, count = it->count; + for (i = 0; i < count; i ++) { + ecs_entity_t e = it->entities[i]; + const EcsIdentifier *name = &names[i]; + + uint64_t index_hash = name->index_hash; + if (from_index && index_hash) { + flecs_name_index_remove(from_index, e, index_hash); + } + const char *name_str = name->value; + if (to_index && name_str) { + ecs_assert(name->hash != 0, ECS_INTERNAL_ERROR, NULL); + flecs_name_index_ensure( + to_index, e, name_str, name->length, name->hash); + } + } } -char* ecs_get_path_w_sep( + +/* -- Iterable mixins -- */ + +static +void on_event_iterable_init( const ecs_world_t *world, - ecs_entity_t parent, - ecs_entity_t child, - const char *sep, - const char *prefix) + const ecs_poly_t *poly, /* Observable */ + ecs_iter_t *it, + ecs_term_t *filter) { - 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_iter_poly(world, poly, it, filter); + it->event_id = filter->id; } -ecs_entity_t ecs_lookup_child( - const ecs_world_t *world, - ecs_entity_t parent, - const char *name) +/* -- Bootstrapping -- */ + +#define bootstrap_component(world, table, name)\ + _bootstrap_component(world, table, ecs_id(name), #name, sizeof(name),\ + ECS_ALIGNOF(name)) + +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_column_t *columns = table->data.columns; + ecs_assert(columns != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_record_t *record = flecs_entities_ensure(world, entity); + record->table = table; + + int32_t index = flecs_table_append(world, table, entity, record, false, false); + record->row = ECS_ROW_TO_RECORD(index, 0); + + EcsComponent *component = ecs_storage_first(&columns[0]); + component[index].size = size; + component[index].alignment = alignment; + + const char *name = &symbol[3]; /* Strip 'Ecs' */ + ecs_size_t symbol_length = ecs_os_strlen(symbol); + ecs_size_t name_length = symbol_length - 3; + + EcsIdentifier *name_col = ecs_storage_first(&columns[1]); + name_col[index].value = ecs_os_strdup(name); + name_col[index].length = name_length; + name_col[index].hash = flecs_hash(name, name_length); + name_col[index].index_hash = 0; + name_col[index].index = NULL; + + EcsIdentifier *symbol_col = ecs_storage_first(&columns[2]); + symbol_col[index].value = ecs_os_strdup(symbol); + symbol_col[index].length = symbol_length; + symbol_col[index].hash = flecs_hash(symbol, symbol_length); + symbol_col[index].index_hash = 0; + symbol_col[index].index = NULL; +} + +/** 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_check(world != NULL, ECS_INTERNAL_ERROR, NULL); - world = ecs_get_world(world); - - if (is_number(name)) { - return name_to_id(world, name); - } + /* Before creating table, manually set flags for ChildOf/Identifier, as this + * can no longer be done after they are in use. */ + ecs_id_record_t *idr = flecs_id_record_ensure(world, EcsChildOf); + idr->flags |= EcsIdOnDeleteObjectDelete | EcsIdDontInherit | + EcsIdAcyclic | EcsIdTag; + idr = flecs_id_record_ensure(world, ecs_pair(EcsChildOf, EcsWildcard)); + idr->flags |= EcsIdOnDeleteObjectDelete | EcsIdDontInherit | + EcsIdAcyclic | EcsIdTag | EcsIdExclusive; - ecs_id_t pair = ecs_childof(parent); - ecs_hashmap_t *index = flecs_id_name_index_get(world, pair); - if (index) { - return flecs_name_index_find(index, name, 0, 0); - } else { - return 0; - } -error: - return 0; -} + idr = flecs_id_record_ensure( + world, ecs_pair(ecs_id(EcsIdentifier), EcsWildcard)); + idr->flags |= EcsIdDontInherit; -ecs_entity_t ecs_lookup( - const ecs_world_t *world, - const char *name) -{ - if (!name) { - return 0; - } + world->idr_childof_0 = flecs_id_record_ensure(world, + ecs_pair(EcsChildOf, 0)); - ecs_check(world != NULL, ECS_INTERNAL_ERROR, NULL); - world = ecs_get_world(world); + ecs_id_t ids[] = { + ecs_id(EcsComponent), + EcsFinal, + ecs_pair(ecs_id(EcsIdentifier), EcsName), + ecs_pair(ecs_id(EcsIdentifier), EcsSymbol), + ecs_pair(EcsChildOf, EcsFlecsCore), + ecs_pair(EcsOnDelete, EcsPanic) + }; + + ecs_type_t array = { + .array = ids, + .count = 6 + }; - ecs_entity_t e = get_builtin(name); - if (e) { - return e; - } + ecs_table_t *result = flecs_table_find_or_create(world, &array); + ecs_data_t *data = &result->data; - if (is_number(name)) { - return name_to_id(world, name); - } + /* Preallocate enough memory for initial components */ + ecs_storage_init_t(&data->entities, ecs_entity_t, EcsFirstUserComponentId); + ecs_storage_init_t(&data->records, ecs_record_t, EcsFirstUserComponentId); - e = flecs_name_index_find(&world->aliases, name, 0, 0); - if (e) { - return e; - } + ecs_storage_init_t(&data->columns[0], EcsComponent, EcsFirstUserComponentId); + ecs_storage_init_t(&data->columns[1], EcsIdentifier, EcsFirstUserComponentId); + ecs_storage_init_t(&data->columns[2], EcsIdentifier, EcsFirstUserComponentId); - return ecs_lookup_child(world, 0, name); -error: - return 0; + return result; } -ecs_entity_t ecs_lookup_symbol( - const ecs_world_t *world, +static +void bootstrap_entity( + ecs_world_t *world, + ecs_entity_t id, const char *name, - bool lookup_as_path) -{ - if (!name) { - return 0; - } - - ecs_check(world != NULL, ECS_INTERNAL_ERROR, NULL); - world = ecs_get_world(world); + ecs_entity_t parent) +{ + char symbol[256]; + ecs_os_strcpy(symbol, "flecs.core."); + ecs_os_strcat(symbol, name); + + ecs_add_pair(world, id, EcsChildOf, parent); + ecs_set_name(world, id, name); + ecs_set_symbol(world, id, symbol); - ecs_entity_t e = flecs_name_index_find(&world->symbols, name, 0, 0); - if (e) { - return e; - } + ecs_assert(ecs_get_name(world, id) != NULL, ECS_INTERNAL_ERROR, NULL); - if (lookup_as_path) { - return ecs_lookup_fullpath(world, name); + if (!parent || parent == EcsFlecsCore) { + ecs_assert(ecs_lookup_fullpath(world, name) == id, + ECS_INTERNAL_ERROR, NULL); } - -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) +void flecs_bootstrap( + ecs_world_t *world) { - if (!path) { - return 0; - } + ecs_log_push(); - if (!sep) { - sep = "."; - } + ecs_set_name_prefix(world, "Ecs"); - ecs_check(world != NULL, ECS_INTERNAL_ERROR, NULL); - const ecs_world_t *stage = world; - world = ecs_get_world(world); + /* Ensure builtin ids are alive */ + ecs_ensure(world, ecs_id(EcsComponent)); + ecs_ensure(world, EcsFinal); + ecs_ensure(world, ecs_id(EcsIdentifier)); + ecs_ensure(world, EcsName); + ecs_ensure(world, EcsSymbol); + ecs_ensure(world, EcsAlias); + ecs_ensure(world, EcsChildOf); + ecs_ensure(world, EcsFlecsCore); + ecs_ensure(world, EcsOnDelete); + ecs_ensure(world, EcsPanic); + ecs_ensure(world, EcsFlag); + ecs_ensure(world, EcsWildcard); + ecs_ensure(world, EcsAny); + ecs_ensure(world, EcsTag); - ecs_entity_t e = get_builtin(path); - if (e) { - return e; - } + /* Bootstrap builtin components */ + flecs_type_info_init(world, EcsComponent, { + .ctor = ecs_default_ctor, + .on_set = on_component, + .on_remove = on_component + }); - e = flecs_name_index_find(&world->aliases, path, 0, 0); - if (e) { - return e; - } + flecs_type_info_init(world, EcsIdentifier, { + .ctor = ecs_default_ctor, + .dtor = ecs_dtor(EcsIdentifier), + .copy = ecs_copy(EcsIdentifier), + .move = ecs_move(EcsIdentifier), + .on_set = ecs_on_set(EcsIdentifier), + .on_remove = ecs_on_set(EcsIdentifier) + }); - 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 lookup_path_search = false; + flecs_type_info_init(world, EcsPoly, { + .ctor = ecs_default_ctor, + .copy = ecs_copy(EcsPoly), + .move = ecs_move(EcsPoly), + .dtor = ecs_dtor(EcsPoly) + }); - ecs_entity_t *lookup_path = ecs_get_lookup_path(stage); - ecs_entity_t *lookup_path_cur = lookup_path; - while (lookup_path_cur && *lookup_path_cur) { - lookup_path_cur ++; - } + flecs_type_info_init(world, EcsIterable, { 0 }); - if (!sep) { - sep = "."; - } + /* Cache often used id records on world */ + world->idr_wildcard = flecs_id_record_ensure(world, EcsWildcard); + world->idr_wildcard_wildcard = flecs_id_record_ensure(world, + ecs_pair(EcsWildcard, EcsWildcard)); + world->idr_any = flecs_id_record_ensure(world, EcsAny); - parent = get_parent_from_path(stage, parent, &path, prefix, true); + /* Create table for initial components */ + ecs_table_t *table = bootstrap_component_table(world); + assert(table != NULL); -retry: - cur = parent; - ptr_start = ptr = path; + bootstrap_component(world, table, EcsIdentifier); + bootstrap_component(world, table, EcsComponent); + bootstrap_component(world, table, EcsIterable); + bootstrap_component(world, table, EcsPoly); - 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; - } + world->info.last_component_id = EcsFirstUserComponentId; + world->info.last_id = EcsFirstUserEntityId; + world->info.min_id = 0; + world->info.max_id = 0; - elem = ecs_os_realloc(elem, len + 1); - ecs_os_memcpy(elem, ptr_start, len); - size = len + 1; - } + /* 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 }); + + ecs_observer_init(world, &(ecs_observer_desc_t){ + .entity = ecs_entity(world, {.add = { ecs_childof(EcsFlecsInternals)}}), + .filter.terms[0] = { .id = EcsTag, .src.flags = EcsSelf }, + .events = {EcsOnAdd, EcsOnRemove}, + .callback = register_tag, + .yield_existing = true + }); - elem[len] = '\0'; - ptr_start = ptr; + /* Populate core module */ + ecs_set_scope(world, EcsFlecsCore); - cur = ecs_lookup_child(world, cur, elem); - if (!cur) { - goto tail; - } - } + flecs_bootstrap_tag(world, EcsName); + flecs_bootstrap_tag(world, EcsSymbol); + flecs_bootstrap_tag(world, EcsAlias); -tail: - if (!cur && recursive) { - if (!lookup_path_search) { - if (parent) { - parent = ecs_get_target(world, parent, EcsChildOf, 0); - goto retry; - } else { - lookup_path_search = true; - } - } + flecs_bootstrap_tag(world, EcsQuery); + flecs_bootstrap_tag(world, EcsObserver); - if (lookup_path_search) { - if (lookup_path_cur != lookup_path) { - lookup_path_cur --; - parent = lookup_path_cur[0]; - goto retry; - } - } - } + flecs_bootstrap_tag(world, EcsModule); + flecs_bootstrap_tag(world, EcsPrivate); + flecs_bootstrap_tag(world, EcsPrefab); + flecs_bootstrap_tag(world, EcsSlotOf); + flecs_bootstrap_tag(world, EcsDisabled); + flecs_bootstrap_tag(world, EcsEmpty); - if (elem != buff) { - ecs_os_free(elem); - } + /* Initialize builtin modules */ + ecs_set_name(world, EcsFlecs, "flecs"); + ecs_add_id(world, EcsFlecs, EcsModule); + ecs_add_pair(world, EcsFlecs, EcsOnDelete, EcsPanic); - return cur; -error: - return 0; -} + ecs_add_pair(world, EcsFlecsCore, EcsChildOf, EcsFlecs); + ecs_set_name(world, EcsFlecsCore, "core"); + ecs_add_id(world, EcsFlecsCore, EcsModule); -ecs_entity_t ecs_set_scope( - ecs_world_t *world, - ecs_entity_t scope) -{ - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_stage_t *stage = flecs_stage_from_world(&world); + ecs_add_pair(world, EcsFlecsInternals, EcsChildOf, EcsFlecsCore); + ecs_set_name(world, EcsFlecsInternals, "internals"); + ecs_add_id(world, EcsFlecsInternals, EcsModule); - ecs_entity_t cur = stage->scope; - stage->scope = scope; + /* Initialize builtin entities */ + bootstrap_entity(world, EcsWorld, "World", EcsFlecsCore); + bootstrap_entity(world, EcsWildcard, "*", EcsFlecsCore); + bootstrap_entity(world, EcsAny, "_", EcsFlecsCore); + bootstrap_entity(world, EcsThis, "This", EcsFlecsCore); + bootstrap_entity(world, EcsVariable, "$", EcsFlecsCore); + bootstrap_entity(world, EcsFlag, "Flag", EcsFlecsCore); - return cur; -error: - return 0; -} + /* Component/relationship properties */ + flecs_bootstrap_tag(world, EcsTransitive); + flecs_bootstrap_tag(world, EcsReflexive); + flecs_bootstrap_tag(world, EcsSymmetric); + flecs_bootstrap_tag(world, EcsFinal); + flecs_bootstrap_tag(world, EcsDontInherit); + flecs_bootstrap_tag(world, EcsTag); + flecs_bootstrap_tag(world, EcsUnion); + flecs_bootstrap_tag(world, EcsExclusive); + flecs_bootstrap_tag(world, EcsAcyclic); + flecs_bootstrap_tag(world, EcsWith); + flecs_bootstrap_tag(world, EcsOneOf); -ecs_entity_t ecs_get_scope( - 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->scope; -error: - return 0; -} + flecs_bootstrap_tag(world, EcsOnDelete); + flecs_bootstrap_tag(world, EcsOnDeleteTarget); + flecs_bootstrap_tag(world, EcsRemove); + flecs_bootstrap_tag(world, EcsDelete); + flecs_bootstrap_tag(world, EcsPanic); -ecs_entity_t* ecs_set_lookup_path( - ecs_world_t *world, - const ecs_entity_t *lookup_path) -{ - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_stage_t *stage = flecs_stage_from_world(&world); + flecs_bootstrap_tag(world, EcsDefaultChildComponent); - ecs_entity_t *cur = stage->lookup_path; - stage->lookup_path = (ecs_entity_t*)lookup_path; + /* Builtin relationships */ + flecs_bootstrap_tag(world, EcsIsA); + flecs_bootstrap_tag(world, EcsChildOf); + flecs_bootstrap_tag(world, EcsDependsOn); - return cur; -error: - return NULL; -} + /* 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, EcsOnTableFill, "OnTableFilled", EcsFlecsCore); -ecs_entity_t* ecs_get_lookup_path( - 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->lookup_path; -error: - return NULL; -} + /* Tag relationships (relationships that should never have data) */ + ecs_add_id(world, EcsIsA, EcsTag); + ecs_add_id(world, EcsChildOf, EcsTag); + ecs_add_id(world, EcsSlotOf, EcsTag); + ecs_add_id(world, EcsDependsOn, EcsTag); + ecs_add_id(world, EcsDefaultChildComponent, EcsTag); + ecs_add_id(world, EcsUnion, EcsTag); + ecs_add_id(world, EcsFlag, EcsTag); -const char* ecs_set_name_prefix( - ecs_world_t *world, - const char *prefix) -{ - ecs_poly_assert(world, ecs_world_t); - const char *old_prefix = world->info.name_prefix; - world->info.name_prefix = prefix; - return old_prefix; -} + /* Exclusive properties */ + ecs_add_id(world, EcsChildOf, EcsExclusive); + ecs_add_id(world, EcsOnDelete, EcsExclusive); + ecs_add_id(world, EcsOnDeleteTarget, EcsExclusive); + ecs_add_id(world, EcsDefaultChildComponent, EcsExclusive); -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); + /* Sync properties of ChildOf and Identifier with bootstrapped flags */ + ecs_add_pair(world, EcsChildOf, EcsOnDeleteTarget, EcsDelete); + ecs_add_id(world, EcsChildOf, EcsAcyclic); + ecs_add_id(world, EcsChildOf, EcsDontInherit); + ecs_add_id(world, ecs_id(EcsIdentifier), EcsDontInherit); - if (!sep) { - sep = "."; - } + /* The (IsA, *) id record is used often in searches, so cache it */ + world->idr_isa_wildcard = flecs_id_record_ensure(world, + ecs_pair(EcsIsA, EcsWildcard)); - if (!path) { - if (!entity) { - entity = ecs_new_id(world); - } + /* Create triggers in internals scope */ + ecs_set_scope(world, EcsFlecsInternals); - if (parent) { - ecs_add_pair(world, entity, EcsChildOf, entity); - } + /* Term used to also match prefabs */ + ecs_term_t match_prefab = { + .id = EcsPrefab, + .oper = EcsOptional, + .src.flags = EcsSelf + }; - return entity; - } + ecs_observer_init(world, &(ecs_observer_desc_t){ + .filter.terms = { + { .id = ecs_pair(EcsChildOf, EcsWildcard), .src.flags = EcsSelf }, + match_prefab + }, + .events = { EcsOnAdd, EcsOnRemove }, + .yield_existing = true, + .callback = on_parent_change + }); - parent = get_parent_from_path(world, parent, &path, prefix, entity == 0); + ecs_observer_init(world, &(ecs_observer_desc_t){ + .filter.terms = {{ .id = EcsFinal, .src.flags = EcsSelf }, match_prefab }, + .events = {EcsOnAdd}, + .callback = register_final + }); - 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_observer_init(world, &(ecs_observer_desc_t){ + .filter.terms = { + { .id = ecs_pair(EcsOnDelete, EcsWildcard), .src.flags = EcsSelf }, + match_prefab + }, + .events = {EcsOnAdd, EcsOnRemove}, + .callback = register_on_delete + }); - ecs_entity_t cur = parent; + ecs_observer_init(world, &(ecs_observer_desc_t){ + .filter.terms = { + { .id = ecs_pair(EcsOnDeleteTarget, EcsWildcard), .src.flags = EcsSelf }, + match_prefab + }, + .events = {EcsOnAdd, EcsOnRemove}, + .callback = register_on_delete_object + }); - char *name = NULL; + ecs_observer_init(world, &(ecs_observer_desc_t){ + .filter.terms = { + { .id = EcsAcyclic, .src.flags = EcsSelf }, + match_prefab + }, + .events = {EcsOnAdd, EcsOnRemove}, + .callback = register_acyclic + }); - 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; - } + ecs_observer_init(world, &(ecs_observer_desc_t){ + .filter.terms = {{ .id = EcsExclusive, .src.flags = EcsSelf }, match_prefab }, + .events = {EcsOnAdd}, + .callback = register_exclusive + }); - elem = ecs_os_realloc(elem, len + 1); - ecs_os_memcpy(elem, ptr_start, len); - size = len + 1; - } + ecs_observer_init(world, &(ecs_observer_desc_t){ + .filter.terms = {{ .id = EcsSymmetric, .src.flags = EcsSelf }, match_prefab }, + .events = {EcsOnAdd}, + .callback = register_symmetric + }); - elem[len] = '\0'; - ptr_start = ptr; + ecs_observer_init(world, &(ecs_observer_desc_t){ + .filter.terms = {{ .id = EcsDontInherit, .src.flags = EcsSelf }, match_prefab }, + .events = {EcsOnAdd}, + .callback = register_dont_inherit + }); - ecs_entity_t e = ecs_lookup_child(world, cur, elem); - if (!e) { - if (name) { - ecs_os_free(name); - } + ecs_observer_init(world, &(ecs_observer_desc_t){ + .filter.terms = { + { .id = ecs_pair(EcsWith, EcsWildcard), .src.flags = EcsSelf }, + match_prefab + }, + .events = {EcsOnAdd}, + .callback = register_with + }); - name = ecs_os_strdup(elem); + ecs_observer_init(world, &(ecs_observer_desc_t){ + .filter.terms = {{ .id = EcsUnion, .src.flags = EcsSelf }, match_prefab }, + .events = {EcsOnAdd}, + .callback = register_union + }); - /* 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; - } + /* Entities used as slot are marked as exclusive to ensure a slot can always + * only point to a single entity. */ + ecs_observer_init(world, &(ecs_observer_desc_t){ + .filter.terms = { + { .id = ecs_pair(EcsSlotOf, EcsWildcard), .src.flags = EcsSelf }, + match_prefab + }, + .events = {EcsOnAdd}, + .callback = register_slot_of + }); - 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); - } - } + /* Define observer to make sure that adding a module to a child entity also + * adds it to the parent. */ + ecs_observer_init(world, &(ecs_observer_desc_t){ + .filter.terms = {{ .id = EcsModule, .src.flags = EcsSelf }, match_prefab}, + .events = {EcsOnAdd}, + .callback = ensure_module_tag + }); - if (cur) { - ecs_add_pair(world, e, EcsChildOf, cur); - } + /* Set scope back to flecs core */ + ecs_set_scope(world, EcsFlecsCore); - ecs_set_name(world, e, name); - } + /* Acyclic components */ + ecs_add_id(world, EcsIsA, EcsAcyclic); + ecs_add_id(world, EcsDependsOn, EcsAcyclic); + ecs_add_id(world, EcsWith, EcsAcyclic); - cur = e; - } + /* DontInherit components */ + ecs_add_id(world, EcsDisabled, EcsDontInherit); + ecs_add_id(world, EcsPrefab, EcsDontInherit); - if (entity && (cur != entity)) { - ecs_throw(ECS_ALREADY_DEFINED, name); - } + /* Transitive relationships are always Acyclic */ + ecs_add_pair(world, EcsTransitive, EcsWith, EcsAcyclic); - if (name) { - ecs_os_free(name); - } + /* Transitive relationships */ + ecs_add_id(world, EcsIsA, EcsTransitive); + ecs_add_id(world, EcsIsA, EcsReflexive); - if (elem != buff) { - ecs_os_free(elem); - } + /* Exclusive properties */ + ecs_add_id(world, EcsSlotOf, EcsExclusive); + ecs_add_id(world, EcsOneOf, EcsExclusive); + + /* Run bootstrap functions for other parts of the code */ + flecs_bootstrap_hierarchy(world); - return cur; -error: - return 0; -} + ecs_set_scope(world, 0); -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) -{ - if (!sep) { - sep = "."; - } + ecs_set_name_prefix(world, NULL); - return ecs_add_path_w_sep(world, 0, parent, path, sep, prefix); + ecs_log_pop(); } +#include -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; -} +#define ECS_NAME_BUFFER_LENGTH (64) static -void merge_stages( - ecs_world_t *world, - bool force_merge) +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) { - bool is_stage = ecs_poly_is(world, ecs_stage_t); - ecs_stage_t *stage = flecs_stage_from_world(&world); - - bool measure_frame_time = ECS_BIT_IS_SET(world->flags, - EcsWorldMeasureFrameTime); - - ecs_time_t t_start; - if (measure_frame_time) { - ecs_os_get_time(&t_start); - } + ecs_poly_assert(world, ecs_world_t); - ecs_dbg_3("#[magenta]merge"); + ecs_entity_t cur = 0; + char buff[22]; + const char *name; - 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_assert(stage->defer == 1, ECS_INVALID_OPERATION, - "mismatching defer_begin/defer_end detected"); - 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) { - flecs_defer_end(world, s); + if (ecs_is_valid(world, child)) { + cur = ecs_get_target(world, child, EcsChildOf, 0); + if (cur) { + ecs_assert(cur != child, ECS_CYCLE_DETECTED, NULL); + if (cur != parent && (cur != EcsFlecsCore || prefix != NULL)) { + path_append(world, parent, cur, sep, prefix, buf); + ecs_strbuf_appendstr(buf, sep); } + } else if (prefix) { + ecs_strbuf_appendstr(buf, prefix); } - } - - flecs_eval_component_monitors(world); - - if (measure_frame_time) { - world->info.merge_time_total += (float)ecs_time_measure(&t_start); - } - world->info.merge_count_total ++; - - /* If stage is asynchronous, deferring is always enabled */ - if (stage->async) { - ecs_defer_begin((ecs_world_t*)stage); + 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; } -} - -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); -} + ecs_strbuf_appendstr(buf, name); -bool flecs_defer_begin( - ecs_world_t *world, - ecs_stage_t *stage) -{ - (void)world; - if (stage->defer_suspend) return false; - return (++ stage->defer) == 1; + return cur != 0; } static -bool flecs_defer_op( - ecs_world_t *world, - ecs_stage_t *stage) +bool is_number( + const char *name) { - (void)world; - - /* If deferring is suspended, do operation as usual */ - if (stage->defer_suspend) return false; - - if (stage->defer) { - /* If deferring is enabled, defer operation */ - return true; + ecs_assert(name != NULL, ECS_INTERNAL_ERROR, NULL); + + if (!isdigit(name[0])) { + return false; } - /* Deferring is disabled, defer while operation is executed */ - stage->defer ++; - return false; -} - -static -bool flecs_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 (flecs_defer_op(world, stage)) { - if (!id) { - return true; - } - - ecs_defer_op_t *op = new_defer_op(stage); - op->kind = op_kind; - op->id = id; - op->is._1.entity = entity; + ecs_size_t i, length = ecs_os_strlen(name); + for (i = 1; i < length; i ++) { + char ch = name[i]; - if (op_kind == EcsOpNew) { - world->info.new_count ++; - } else if (op_kind == EcsOpAdd) { - world->info.add_count ++; - } else if (op_kind == EcsOpRemove) { - world->info.remove_count ++; + if (!isdigit(ch)) { + break; } - - return true; - } - return false; -} - -bool flecs_defer_modified( - ecs_world_t *world, - ecs_stage_t *stage, - ecs_entity_t entity, - ecs_id_t id) -{ - if (flecs_defer_op(world, stage)) { - ecs_defer_op_t *op = new_defer_op(stage); - op->kind = EcsOpModified; - op->id = id; - op->is._1.entity = entity; - return true; - } - 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) -{ - if (flecs_defer_op(world, stage)) { - 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; } - return false; -} -bool flecs_defer_delete( - ecs_world_t *world, - ecs_stage_t *stage, - ecs_entity_t entity) -{ - if (flecs_defer_op(world, stage)) { - ecs_defer_op_t *op = new_defer_op(stage); - op->kind = EcsOpDelete; - op->is._1.entity = entity; - world->info.delete_count ++; - return true; - } - return false; + return i >= length; } -bool flecs_defer_clear( - ecs_world_t *world, - ecs_stage_t *stage, - ecs_entity_t entity) +static +ecs_entity_t name_to_id( + const ecs_world_t *world, + const char *name) { - if (flecs_defer_op(world, stage)) { - ecs_defer_op_t *op = new_defer_op(stage); - op->kind = EcsOpClear; - op->is._1.entity = entity; - world->info.clear_count ++; - return true; + 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; } - return false; } -bool flecs_defer_on_delete_action( - ecs_world_t *world, - ecs_stage_t *stage, - ecs_id_t id, - ecs_entity_t action) +static +ecs_entity_t get_builtin( + const char *name) { - if (flecs_defer_op(world, stage)) { - ecs_defer_op_t *op = new_defer_op(stage); - op->kind = EcsOpOnDeleteAction; - op->id = id; - op->is._1.entity = action; - world->info.clear_count ++; - return true; + if (name[0] == '.' && name[1] == '\0') { + return EcsThis; + } else if (name[0] == '*' && name[1] == '\0') { + return EcsWildcard; + } else if (name[0] == '_' && name[1] == '\0') { + return EcsAny; } - return false; -} -bool flecs_defer_enable( - ecs_world_t *world, - ecs_stage_t *stage, - ecs_entity_t entity, - ecs_id_t id, - bool enable) -{ - if (flecs_defer_op(world, stage)) { - ecs_defer_op_t *op = new_defer_op(stage); - op->kind = enable ? EcsOpEnable : EcsOpDisable; - op->is._1.entity = entity; - op->id = id; - return true; - } - return false; + return 0; } -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) +static +bool is_sep( + const char **ptr, + const char *sep) { - if (flecs_defer_op(world, stage)) { - ecs_entity_t *ids = ecs_os_malloc(count * ECS_SIZEOF(ecs_entity_t)); - world->info.bulk_new_count ++; - - /* Use ecs_new_id as this is thread safe */ - int i; - for (i = 0; i < count; i ++) { - ids[i] = ecs_new_id(world); - } - - *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; + ecs_size_t len = ecs_os_strlen(sep); + if (!ecs_os_strncmp(*ptr, sep, len)) { + *ptr += len; return true; + } else { + return false; } - return false; -} - -bool flecs_defer_new( - ecs_world_t *world, - ecs_stage_t *stage, - ecs_entity_t entity, - ecs_id_t id) -{ - return flecs_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 flecs_defer_add_remove(world, stage, EcsOpAdd, entity, id); -} - -bool flecs_defer_remove( - ecs_world_t *world, - ecs_stage_t *stage, - ecs_entity_t entity, - ecs_id_t id) -{ - return flecs_defer_add_remove(world, stage, EcsOpRemove, entity, id); } -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 emplace) +static +const char* path_elem( + const char *path, + const char *sep, + int32_t *len) { - if (flecs_defer_op(world, stage)) { - world->info.set_count ++; + const char *ptr; + char ch; + int32_t template_nesting = 0; + int32_t count = 0; - const ecs_type_info_t *ti = NULL; - { - ecs_id_record_t *idr = flecs_id_record_get(world, id); - if (!idr) { - /* If idr doesn't exist yet, create it but only if the - * application is not multithreaded. */ - if (stage->async || (world->flags & EcsWorldMultiThreaded)) { - ti = ecs_get_type_info(world, id); - ecs_assert(ti != NULL, ECS_INVALID_PARAMETER, NULL); - } else { - /* When not in multi threaded mode, it's safe to find or - * create the id record. */ - idr = flecs_id_record_ensure(world, id); - ecs_assert(idr != NULL, ECS_INTERNAL_ERROR, NULL); - - /* Get type_info from id record. We could have called - * ecs_get_type_info directly, but since this function can be - * expensive for pairs, creating the id record ensures we can - * find the type_info quickly for subsequent operations. */ - ti = idr->type_info; - } - } else { - ti = idr->type_info; - } + for (ptr = path; (ch = *ptr); ptr ++) { + if (ch == '<') { + template_nesting ++; + } else if (ch == '>') { + template_nesting --; } - /* If the id isn't associated with a type, we can't set anything */ - ecs_check(ti != NULL, ECS_INVALID_PARAMETER, NULL); - - /* Make sure the size of the value equals the type size */ - ecs_assert(!size || size == ti->size, ECS_INVALID_PARAMETER, NULL); - size = ti->size; - - ecs_stack_t *stack = &stage->defer_stack; - 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 = flecs_stack_alloc(stack, size, ti->alignment); + ecs_check(template_nesting >= 0, ECS_INVALID_PARAMETER, path); - if (!value) { - value = ecs_get_id(world, entity, id); + if (!template_nesting && is_sep(&ptr, sep)) { + break; } - if (value) { - ecs_copy_t copy; - if ((copy = ti->hooks.copy_ctor)) { - copy(op->is._1.value, value, 1, ti); - } else { - ecs_os_memcpy(op->is._1.value, value, size); - } - } else if (!emplace) { - ecs_xtor_t ctor; - if ((ctor = ti->hooks.ctor)) { - ctor(op->is._1.value, 1, ti); - } - } + count ++; + } - if (value_out) { - *value_out = op->is._1.value; - } + if (len) { + *len = count; + } - return true; + if (count) { + return ptr; + } else { + return NULL; } error: - return false; + return NULL; } -void flecs_stage_merge_post_frame( - ecs_world_t *world, - ecs_stage_t *stage) +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) { - /* Execute post frame actions */ - ecs_vector_each(stage->post_frame_actions, ecs_action_elem_t, action, { - action->action(world, action->ctx); - }); + 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; + } + } - ecs_vector_free(stage->post_frame_actions); - stage->post_frame_actions = NULL; -} + if (!start_from_root && !parent && new_entity) { + parent = ecs_get_scope(world); + } -void flecs_stage_init( - ecs_world_t *world, - ecs_stage_t *stage) -{ - ecs_poly_assert(world, ecs_world_t); - ecs_poly_init(stage, ecs_stage_t); + *path_ptr = path; - stage->world = world; - stage->thread_ctx = world; - stage->auto_merge = true; - stage->async = false; - - flecs_stack_init(&stage->defer_stack); + return parent; } -void flecs_stage_deinit( - ecs_world_t *world, - ecs_stage_t *stage) -{ - (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); +static +void on_set_symbol(ecs_iter_t *it) { + EcsIdentifier *n = ecs_field(it, EcsIdentifier, 1); + ecs_world_t *world = it->world; - ecs_vector_free(stage->defer_queue); - flecs_stack_fini(&stage->defer_stack); + int i; + for (i = 0; i < it->count; i ++) { + ecs_entity_t e = it->entities[i]; + flecs_name_index_ensure( + &world->symbols, e, n[i].value, n[i].length, n[i].hash); + } } -void ecs_set_stage_count( - ecs_world_t *world, - int32_t stage_count) -{ - ecs_poly_assert(world, ecs_world_t); +void flecs_bootstrap_hierarchy(ecs_world_t *world) { + ecs_observer_init(world, &(ecs_observer_desc_t){ + .entity = ecs_entity(world, {.add = {ecs_childof(EcsFlecsInternals)}}), + .filter.terms[0] = {.id = ecs_pair(ecs_id(EcsIdentifier), EcsSymbol), .src.flags = EcsSelf }, + .callback = on_set_symbol, + .events = {EcsOnSet}, + .yield_existing = true + }); +} - /* World must have at least one default stage */ - ecs_assert(stage_count >= 1 || (world->flags & EcsWorldFini), - ECS_INTERNAL_ERROR, NULL); - bool auto_merge = true; - ecs_entity_t *lookup_path = NULL; - ecs_entity_t scope = 0; - ecs_entity_t with = 0; - if (world->stage_count >= 1) { - auto_merge = world->stages[0].auto_merge; - lookup_path = world->stages[0].lookup_path; - scope = world->stages[0].scope; - with = world->stages[0].with; - } +/* Public functions */ - int32_t i, count = world->stage_count; - if (count && count != stage_count) { - ecs_stage_t *stages = world->stages; +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); - 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_stage_count 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]); - } + world = ecs_get_world(world); - ecs_os_free(world->stages); + if (child == EcsWildcard) { + ecs_strbuf_appendstr(buf, "*"); + return; + } + if (child == EcsAny) { + ecs_strbuf_appendstr(buf, "_"); + return; } - if (stage_count) { - world->stages = ecs_os_malloc_n(ecs_stage_t, stage_count); - - for (i = 0; i < stage_count; i ++) { - ecs_stage_t *stage = &world->stages[i]; - flecs_stage_init(world, stage); - stage->id = i; - - /* 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->stages = NULL; + if (!sep) { + sep = "."; } - /* Regardless of whether the stage was just initialized or not, when the - * ecs_set_stage_count function is called, all stages inherit the auto_merge - * property from the world */ - for (i = 0; i < stage_count; i ++) { - world->stages[i].auto_merge = auto_merge; - world->stages[i].lookup_path = lookup_path; - world->stages[0].scope = scope; - world->stages[0].with = with; + if (!child || parent != child) { + path_append(world, parent, child, sep, prefix, buf); + } else { + ecs_strbuf_appendstr(buf, ""); } - world->stage_count = stage_count; error: return; } -int32_t ecs_get_stage_count( - const ecs_world_t *world) +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) { - world = ecs_get_world(world); - return world->stage_count; + ecs_strbuf_t buf = ECS_STRBUF_INIT; + ecs_get_path_w_sep_buf(world, parent, child, sep, prefix, &buf); + return ecs_strbuf_get(&buf); } -int32_t ecs_get_stage_id( - const ecs_world_t *world) +ecs_entity_t ecs_lookup_child( + const ecs_world_t *world, + ecs_entity_t parent, + const char *name) { - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(world != NULL, ECS_INTERNAL_ERROR, NULL); + world = ecs_get_world(world); - if (ecs_poly_is(world, ecs_stage_t)) { - ecs_stage_t *stage = (ecs_stage_t*)world; + if (is_number(name)) { + return name_to_id(world, name); + } - /* Index 0 is reserved for main stage */ - return stage->id; - } else if (ecs_poly_is(world, ecs_world_t)) { - return 0; + ecs_id_t pair = ecs_childof(parent); + ecs_hashmap_t *index = flecs_id_name_index_get(world, pair); + if (index) { + return flecs_name_index_find(index, name, 0, 0); } else { - ecs_throw(ECS_INTERNAL_ERROR, NULL); + return 0; } error: return 0; } -ecs_world_t* ecs_get_stage( +ecs_entity_t ecs_lookup( const ecs_world_t *world, - int32_t stage_id) -{ - ecs_poly_assert(world, ecs_world_t); - ecs_check(world->stage_count > stage_id, ECS_INVALID_PARAMETER, NULL); - return (ecs_world_t*)&world->stages[stage_id]; + 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 = flecs_name_index_find(&world->aliases, name, 0, 0); + if (e) { + return e; + } + + return ecs_lookup_child(world, 0, name); error: - return NULL; + return 0; } -bool ecs_readonly_begin( - ecs_world_t *world) +ecs_entity_t ecs_lookup_symbol( + const ecs_world_t *world, + const char *name, + bool lookup_as_path) +{ + if (!name) { + return 0; + } + + ecs_check(world != NULL, ECS_INTERNAL_ERROR, NULL); + world = ecs_get_world(world); + + ecs_entity_t e = flecs_name_index_find(&world->symbols, name, 0, 0); + if (e) { + return e; + } + + if (lookup_as_path) { + return ecs_lookup_fullpath(world, name); + } + +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) { - ecs_poly_assert(world, ecs_world_t); + if (!path) { + return 0; + } - flecs_process_pending_tables(world); + if (!sep) { + sep = "."; + } - ecs_dbg_3("#[bold]readonly"); - ecs_log_push_3(); + ecs_check(world != NULL, ECS_INTERNAL_ERROR, NULL); + const ecs_world_t *stage = world; + world = ecs_get_world(world); - int32_t i, count = ecs_get_stage_count(world); - for (i = 0; i < count; i ++) { - ecs_stage_t *stage = &world->stages[i]; - stage->lookup_path = world->stages[0].lookup_path; - ecs_assert(stage->defer == 0, ECS_INVALID_OPERATION, - "deferred mode cannot be enabled when entering readonly mode"); - ecs_defer_begin((ecs_world_t*)stage); + ecs_entity_t e = get_builtin(path); + if (e) { + return e; } - bool is_readonly = ECS_BIT_IS_SET(world->flags, EcsWorldReadonly); + e = flecs_name_index_find(&world->aliases, path, 0, 0); + if (e) { + return e; + } - /* From this point on, the world is "locked" for mutations, and it is only - * allowed to enqueue commands from stages */ - ECS_BIT_SET(world->flags, EcsWorldReadonly); + 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 lookup_path_search = false; - /* If world has more than one stage, signal we might be running on multiple - * threads. This is a stricter version of readonly mode: while some - * mutations like implicit component registration are still allowed in plain - * readonly mode, no mutations are allowed when multithreaded. */ - if (count > 1) { - ECS_BIT_SET(world->flags, EcsWorldMultiThreaded); + ecs_entity_t *lookup_path = ecs_get_lookup_path(stage); + ecs_entity_t *lookup_path_cur = lookup_path; + while (lookup_path_cur && *lookup_path_cur) { + lookup_path_cur ++; } - return is_readonly; + if (!sep) { + sep = "."; + } + + parent = get_parent_from_path(stage, parent, &path, prefix, true); + +retry: + cur = parent; + ptr_start = ptr = path; + + 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; + } + + elem = ecs_os_realloc(elem, len + 1); + ecs_os_memcpy(elem, ptr_start, len); + size = len + 1; + } + + elem[len] = '\0'; + ptr_start = ptr; + + cur = ecs_lookup_child(world, cur, elem); + if (!cur) { + goto tail; + } + } + +tail: + if (!cur && recursive) { + if (!lookup_path_search) { + if (parent) { + parent = ecs_get_target(world, parent, EcsChildOf, 0); + goto retry; + } else { + lookup_path_search = true; + } + } + + if (lookup_path_search) { + if (lookup_path_cur != lookup_path) { + lookup_path_cur --; + parent = lookup_path_cur[0]; + goto retry; + } + } + } + + if (elem != buff) { + ecs_os_free(elem); + } + + return cur; +error: + return 0; } -void ecs_readonly_end( - ecs_world_t *world) +ecs_entity_t ecs_set_scope( + ecs_world_t *world, + ecs_entity_t scope) { - ecs_poly_assert(world, ecs_world_t); - ecs_check(world->flags & EcsWorldReadonly, ECS_INVALID_OPERATION, NULL); - - /* After this it is safe again to mutate the world directly */ - ECS_BIT_CLEAR(world->flags, EcsWorldReadonly); - ECS_BIT_CLEAR(world->flags, EcsWorldMultiThreaded); + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_stage_t *stage = flecs_stage_from_world(&world); - ecs_log_pop_3(); + ecs_entity_t cur = stage->scope; + stage->scope = scope; - do_auto_merge(world); + return cur; error: - return; + return 0; } -void ecs_merge( - ecs_world_t *world) +ecs_entity_t ecs_get_scope( + const 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); + const ecs_stage_t *stage = flecs_stage_from_readonly_world(world); + return stage->scope; error: - return; + return 0; } -void ecs_set_automerge( +ecs_entity_t* ecs_set_lookup_path( ecs_world_t *world, - bool auto_merge) + const ecs_entity_t *lookup_path) { - /* 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->stages[0].auto_merge = auto_merge; + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_stage_t *stage = flecs_stage_from_world(&world); - /* 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_entity_t *cur = stage->lookup_path; + stage->lookup_path = (ecs_entity_t*)lookup_path; - /* 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; - } + return cur; +error: + return NULL; } -bool ecs_stage_is_readonly( - const ecs_world_t *stage) +ecs_entity_t* ecs_get_lookup_path( + const ecs_world_t *world) { - const ecs_world_t *world = ecs_get_world(stage); + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + const ecs_stage_t *stage = flecs_stage_from_readonly_world(world); + return stage->lookup_path; +error: + return NULL; +} - if (ecs_poly_is(stage, ecs_stage_t)) { - if (((ecs_stage_t*)stage)->async) { - return false; +const char* ecs_set_name_prefix( + ecs_world_t *world, + const char *prefix) +{ + ecs_poly_assert(world, ecs_world_t); + const char *old_prefix = world->info.name_prefix; + world->info.name_prefix = prefix; + return old_prefix; +} + +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); + + if (!sep) { + sep = "."; + } + + if (!path) { + if (!entity) { + entity = ecs_new_id(world); } - } - if (world->flags & EcsWorldReadonly) { - if (ecs_poly_is(stage, ecs_world_t)) { - return true; + if (parent) { + ecs_add_pair(world, entity, EcsChildOf, entity); } - } else { - if (ecs_poly_is(stage, ecs_stage_t)) { - return true; + + return entity; + } + + parent = get_parent_from_path(world, parent, &path, prefix, entity == 0); + + 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_entity_t cur = parent; + + char *name = 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; + } + + elem = ecs_os_realloc(elem, len + 1); + ecs_os_memcpy(elem, ptr_start, len); + size = len + 1; } - } - return false; -} + elem[len] = '\0'; + ptr_start = ptr; -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); + ecs_entity_t e = ecs_lookup_child(world, cur, elem); + if (!e) { + if (name) { + ecs_os_free(name); + } - stage->id = -1; - stage->auto_merge = false; - stage->async = true; + name = ecs_os_strdup(elem); - ecs_defer_begin((ecs_world_t*)stage); + /* 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; + } - return (ecs_world_t*)stage; -} + 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); + } + } -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->async == true, ECS_INVALID_PARAMETER, NULL); - flecs_stage_deinit(stage->world, stage); - ecs_os_free(stage); -error: - return; -} + if (cur) { + ecs_add_pair(world, e, EcsChildOf, cur); + } -bool ecs_stage_is_async( - ecs_world_t *stage) -{ - if (!stage) { - return false; + ecs_set_name(world, e, name); + } + + cur = e; } - - if (!ecs_poly_is(stage, ecs_stage_t)) { - return false; + + if (entity && (cur != entity)) { + ecs_throw(ECS_ALREADY_DEFINED, name); } - return ((ecs_stage_t*)stage)->async; -} + if (name) { + ecs_os_free(name); + } -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; + if (elem != buff) { + ecs_os_free(elem); + } + + return cur; error: - return false; + return 0; } - -void ecs_storage_init( - ecs_column_t *storage, - ecs_size_t size, - int32_t elem_count) +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) { - ecs_assert(size != 0, ECS_INVALID_PARAMETER, NULL); - storage->array = NULL; - storage->count = 0; - if (elem_count) { - storage->array = ecs_os_malloc(size * elem_count); + if (!sep) { + sep = "."; } - storage->size = elem_count; -} - -void ecs_storage_fini( - ecs_column_t *storage) -{ - ecs_os_free(storage->array); - storage->array = NULL; - storage->count = 0; - storage->size = 0; -} -int32_t ecs_storage_count( - ecs_column_t *storage) -{ - return storage->count; + return ecs_add_path_w_sep(world, 0, parent, path, sep, prefix); } -int32_t ecs_storage_size( - ecs_column_t *storage) -{ - return storage->size; -} -void* ecs_storage_get( - ecs_column_t *storage, - ecs_size_t size, - int32_t index) +static +ecs_id_record_elem_t* id_record_elem( + ecs_id_record_t *head, + ecs_id_record_elem_t *list, + ecs_id_record_t *idr) { - ecs_assert(index < storage->count, ECS_OUT_OF_RANGE, NULL); - return ECS_ELEM(storage->array, size, index); + return ECS_OFFSET(idr, (uintptr_t)list - (uintptr_t)head); } -void* ecs_storage_last( - ecs_column_t *storage, - ecs_size_t size) +static +void id_record_elem_insert( + ecs_id_record_t *head, + ecs_id_record_t *idr, + ecs_id_record_elem_t *elem) { - return ECS_ELEM(storage->array, size, storage->count - 1); + ecs_id_record_elem_t *head_elem = id_record_elem(idr, elem, head); + ecs_id_record_t *cur = head_elem->next; + elem->next = cur; + elem->prev = head; + if (cur) { + ecs_id_record_elem_t *cur_elem = id_record_elem(idr, elem, cur); + cur_elem->prev = idr; + } + head_elem->next = idr; } -void* ecs_storage_first( - ecs_column_t *storage) +static +void id_record_elem_remove( + ecs_id_record_t *idr, + ecs_id_record_elem_t *elem) { - return storage->array; -} + ecs_id_record_t *prev = elem->prev; + ecs_id_record_t *next = elem->next; + ecs_assert(prev != NULL, ECS_INTERNAL_ERROR, NULL); -void* ecs_storage_append( - ecs_column_t *storage, - ecs_size_t size) -{ - int32_t count = storage->count; - if (storage->size == count) { - ecs_storage_set_size(storage, size, count + 1); + ecs_id_record_elem_t *prev_elem = id_record_elem(idr, elem, prev); + prev_elem->next = next; + if (next) { + ecs_id_record_elem_t *next_elem = id_record_elem(idr, elem, next); + next_elem->prev = prev; } - storage->count = count + 1; - return ECS_ELEM(storage->array, size, count); } -void ecs_storage_remove( - ecs_column_t *storage, - ecs_size_t size, - int32_t index) +static +void insert_id_elem( + ecs_world_t *world, + ecs_id_record_t *idr, + ecs_id_t wildcard, + ecs_id_record_t *widr) { - ecs_assert(index < storage->count, ECS_OUT_OF_RANGE, NULL); - if (index == --storage->count) { - return; + ecs_assert(ecs_id_is_wildcard(wildcard), ECS_INTERNAL_ERROR, NULL); + if (!widr) { + widr = flecs_id_record_ensure(world, wildcard); } + ecs_assert(widr != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_os_memcpy( - ECS_ELEM(storage->array, size, index), - ECS_ELEM(storage->array, size, storage->count), - size); -} - -void ecs_storage_remove_last( - ecs_column_t *storage) -{ - storage->count --; -} - -ecs_column_t ecs_storage_copy( - ecs_column_t *storage, - ecs_size_t size) -{ - return (ecs_column_t) { - .count = storage->count, - .size = storage->size, - .array = ecs_os_memdup(storage->array, storage->size * size) - }; -} + if (ECS_PAIR_SECOND(wildcard) == EcsWildcard) { + ecs_assert(ECS_PAIR_FIRST(wildcard) != EcsWildcard, + ECS_INTERNAL_ERROR, NULL); + id_record_elem_insert(widr, idr, &idr->first); + } else { + ecs_assert(ECS_PAIR_FIRST(wildcard) == EcsWildcard, + ECS_INTERNAL_ERROR, NULL); + id_record_elem_insert(widr, idr, &idr->second); -void ecs_storage_reclaim( - ecs_column_t *storage, - ecs_size_t size) -{ - int32_t count = storage->count; - if (count < storage->size) { - if (count) { - storage->array = ecs_os_realloc(storage->array, size * count); - storage->size = count; - } else { - ecs_storage_fini(storage); + if (idr->flags & EcsIdAcyclic) { + id_record_elem_insert(widr, idr, &idr->acyclic); } } } -void ecs_storage_set_size( - ecs_column_t *storage, - ecs_size_t size, - int32_t elem_count) +static +void remove_id_elem( + ecs_id_record_t *idr, + ecs_id_t wildcard) { - if (storage->size != elem_count) { - if (elem_count < storage->count) { - elem_count = storage->count; - } + ecs_assert(ecs_id_is_wildcard(wildcard), ECS_INTERNAL_ERROR, NULL); - elem_count = flecs_next_pow_of_2(elem_count); - if (elem_count < 2) { - elem_count = 2; - } - if (elem_count != storage->size) { - storage->array = ecs_os_realloc(storage->array, size * elem_count); - storage->size = elem_count; + if (ECS_PAIR_SECOND(wildcard) == EcsWildcard) { + ecs_assert(ECS_PAIR_FIRST(wildcard) != EcsWildcard, + ECS_INTERNAL_ERROR, NULL); + id_record_elem_remove(idr, &idr->first); + } else { + ecs_assert(ECS_PAIR_FIRST(wildcard) == EcsWildcard, + ECS_INTERNAL_ERROR, NULL); + id_record_elem_remove(idr, &idr->second); + + if (idr->flags & EcsIdAcyclic) { + id_record_elem_remove(idr, &idr->acyclic); } } } -void ecs_storage_set_count( - ecs_column_t *storage, - ecs_size_t size, - int32_t elem_count) +static +ecs_id_t flecs_id_record_id( + ecs_id_t id) { - if (storage->count != elem_count) { - if (storage->size < elem_count) { - ecs_storage_set_size(storage, size, elem_count); + id = ecs_strip_generation(id); + if (ECS_IS_PAIR(id)) { + ecs_entity_t r = ECS_PAIR_FIRST(id); + ecs_entity_t o = ECS_PAIR_SECOND(id); + if (r == EcsAny) { + r = EcsWildcard; } - - storage->count = elem_count; + if (o == EcsAny) { + o = EcsWildcard; + } + id = ecs_pair(r, o); } + return id; } -void* ecs_storage_grow( - ecs_column_t *storage, - ecs_size_t size, - int32_t elem_count) +static +ecs_id_record_t* flecs_id_record_new( + ecs_world_t *world, + ecs_id_t id) { - ecs_assert(elem_count > 0, ECS_INTERNAL_ERROR, NULL); - int32_t count = storage->count; - ecs_storage_set_count(storage, size, count + elem_count); - return ECS_ELEM(storage->array, size, count); -} + ecs_id_record_t *idr = ecs_os_calloc_t(ecs_id_record_t); + ecs_table_cache_init(&idr->cache); -#include -#include + idr->id = id; + idr->refcount = 1; -void ecs_os_api_impl(ecs_os_api_t *api); + bool is_wildcard = ecs_id_is_wildcard(id); -static bool ecs_os_api_initialized = false; -static bool ecs_os_api_initializing = false; -static int ecs_os_api_init_count = 0; + ecs_entity_t rel = 0, obj = 0, role = id & ECS_ID_FLAGS_MASK; + if (ECS_HAS_ID_FLAG(id, PAIR)) { + rel = ecs_pair_first(world, id); + ecs_assert(rel != 0, ECS_INTERNAL_ERROR, NULL); -#ifndef __EMSCRIPTEN__ -ecs_os_api_t ecs_os_api = { - .flags_ = EcsOsApiHighResolutionTimer | EcsOsApiLogWithColors, - .log_level_ = -1 /* Disable tracing by default, but log warnings/errors */ -}; -#else -/* Disable colors by default for emscripten */ -ecs_os_api_t ecs_os_api = { - .flags_ = EcsOsApiHighResolutionTimer, - .log_level_ = -1 /* Disable tracing by default, but log warnings/errors */ -}; -#endif + /* Relationship object can be 0, as tables without a ChildOf relationship are + * added to the (ChildOf, 0) id record */ + obj = ECS_PAIR_SECOND(id); + if (obj) { + obj = ecs_get_alive(world, obj); + ecs_assert(obj != 0, ECS_INTERNAL_ERROR, NULL); + } -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; + /* Check constraints */ + if (obj && !ecs_id_is_wildcard(obj)) { + ecs_entity_t oneof = flecs_get_oneof(world, rel); + ecs_check( !oneof || ecs_has_pair(world, obj, EcsChildOf, oneof), + ECS_CONSTRAINT_VIOLATED, NULL); + (void)oneof; + } -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 (!is_wildcard && ECS_IS_PAIR(id) && (rel != EcsFlag)) { + /* Inherit flags from (relationship, *) record */ + ecs_id_record_t *idr_r = flecs_id_record_ensure( + world, ecs_pair(rel, EcsWildcard)); + idr->parent = idr_r; + idr->flags = idr_r->flags; -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_(); + /* If pair is not a wildcard, append it to wildcard lists. These + * allow for quickly enumerating all relationships for an object, or all + * objecs for a relationship. */ + insert_id_elem(world, idr, ecs_pair(rel, EcsWildcard), idr_r); + insert_id_elem(world, idr, ecs_pair(EcsWildcard, obj), NULL); + + if (rel == EcsUnion) { + idr->flags |= EcsIdUnion; + } } + } else { + rel = id & ECS_COMPONENT_MASK; + rel = ecs_get_alive(world, rel); + ecs_assert(rel != 0, ECS_INTERNAL_ERROR, NULL); } -} -void ecs_os_fini(void) { - if (!--ecs_os_api_init_count) { - if (ecs_os_api.fini_) { - ecs_os_api.fini_(); + /* Initialize type info if id is not a tag */ + if (!is_wildcard && (!role || ECS_IS_PAIR(id))) { + if (!(idr->flags & EcsIdTag)) { + const ecs_type_info_t *ti = flecs_type_info_get(world, rel); + if (!ti && obj) { + ti = flecs_type_info_get(world, obj); + } + idr->type_info = ti; } } -} -#if !defined(ECS_TARGET_WINDOWS) && !defined(ECS_TARGET_EM) && !defined(ECS_TARGET_ANDROID) -#include -#define ECS_BT_BUF_SIZE 100 -static -void dump_backtrace( - FILE *stream) -{ - int nptrs; - void *buffer[ECS_BT_BUF_SIZE]; - char **strings; - - nptrs = backtrace(buffer, ECS_BT_BUF_SIZE); + /* Mark entities that are used as component/pair ids. When a tracked + * entity is deleted, cleanup policies are applied so that the store + * won't contain any tables with deleted ids. */ - strings = backtrace_symbols(buffer, nptrs); - if (strings == NULL) { - return; + /* Flag for OnDelete policies */ + flecs_add_flag(world, rel, EcsEntityObservedId); + if (obj) { + /* Flag for OnDeleteTarget policies */ + flecs_add_flag(world, obj, EcsEntityObservedTarget); + if (ecs_has_id(world, rel, EcsAcyclic)) { + /* Flag used to determine if object should be traversed when + * propagating events or with super/subset queries */ + flecs_add_flag(world, obj, EcsEntityObservedAcyclic); + } } - for (int j = 3; j < nptrs; j++) { - fprintf(stream, "%s\n", strings[j]); + /* Flags for events */ + if (flecs_check_observers_for_event(world, id, EcsOnAdd)) { + idr->flags |= EcsIdHasOnAdd; } - - free(strings); -} -#else -static -void dump_backtrace( - FILE *stream) -{ - (void)stream; -} -#endif - -static -void log_msg( - int32_t level, - const char *file, - int32_t line, - const char *msg) -{ - FILE *stream; - if (level >= 0) { - stream = stdout; - } else { - stream = stderr; + if (flecs_check_observers_for_event(world, id, EcsOnRemove)) { + idr->flags |= EcsIdHasOnRemove; } - - bool use_colors = ecs_os_api.flags_ & EcsOsApiLogWithColors; - bool timestamp = ecs_os_api.flags_ & EcsOsApiLogWithTimeStamp; - bool deltatime = ecs_os_api.flags_ & EcsOsApiLogWithTimeDelta; - - time_t now = 0; - - if (deltatime) { - now = time(NULL); - time_t delta = 0; - if (ecs_os_api.log_last_timestamp_) { - delta = now - ecs_os_api.log_last_timestamp_; - } - ecs_os_api.log_last_timestamp_ = (int64_t)now; - - if (delta) { - if (delta < 10) { - fputs(" ", stream); - } - if (delta < 100) { - fputs(" ", stream); - } - char time_buf[20]; - ecs_os_sprintf(time_buf, "%u", (uint32_t)delta); - fputs("+", stream); - fputs(time_buf, stream); - fputs(" ", stream); - } else { - fputs(" ", stream); - } + if (flecs_check_observers_for_event(world, id, EcsOnSet)) { + idr->flags |= EcsIdHasOnSet; + } + if (flecs_check_observers_for_event(world, id, EcsUnSet)) { + idr->flags |= EcsIdHasUnSet; } - if (timestamp) { - if (!now) { - now = time(NULL); - } - char time_buf[20]; - ecs_os_sprintf(time_buf, "%u", (uint32_t)now); - fputs(time_buf, stream); - fputs(" ", stream); + if (ecs_should_log_1()) { + char *id_str = ecs_id_str(world, id); + ecs_dbg_1("#[green]id#[normal] %s #[green]created", id_str); + ecs_os_free(id_str); } - if (level >= 0) { - if (level == 0) { - if (use_colors) fputs(ECS_MAGENTA, stream); + /* Update counters */ + world->info.id_create_total ++; + + if (!is_wildcard) { + world->info.id_count ++; + + if (idr->type_info) { + world->info.component_id_count ++; } else { - if (use_colors) fputs(ECS_GREY, stream); + world->info.tag_id_count ++; } - fputs("info", stream); - } else if (level == -2) { - if (use_colors) fputs(ECS_YELLOW, stream); - fputs("warning", stream); - } else if (level == -3) { - if (use_colors) fputs(ECS_RED, stream); - fputs("error", stream); - } else if (level == -4) { - if (use_colors) fputs(ECS_RED, stream); - fputs("fatal", stream); + + if (ECS_IS_PAIR(id)) { + world->info.pair_id_count ++; + } + } else { + world->info.wildcard_id_count ++; } - if (use_colors) fputs(ECS_NORMAL, stream); - fputs(": ", stream); + return idr; +error: + return NULL; +} - if (level >= 0) { - if (ecs_os_api.log_indent_) { - char indent[32]; - int i, indent_count = ecs_os_api.log_indent_; - if (indent_count > 15) indent_count = 15; +static +void flecs_id_record_assert_empty( + ecs_id_record_t *idr) +{ + (void)idr; + ecs_assert(flecs_table_cache_count(&idr->cache) == 0, + ECS_INTERNAL_ERROR, NULL); + ecs_assert(flecs_table_cache_empty_count(&idr->cache) == 0, + ECS_INTERNAL_ERROR, NULL); +} - for (i = 0; i < indent_count; i ++) { - indent[i * 2] = '|'; - indent[i * 2 + 1] = ' '; - } +static +void flecs_id_record_free( + ecs_world_t *world, + ecs_id_record_t *idr) +{ + ecs_poly_assert(world, ecs_world_t); + ecs_assert(idr != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_id_t id = idr->id; - if (ecs_os_api.log_indent_ != indent_count) { - indent[i * 2 - 2] = '+'; + flecs_id_record_assert_empty(idr); + + if (ECS_IS_PAIR(id)) { + ecs_entity_t rel = ecs_pair_first(world, id); + ecs_entity_t obj = ECS_PAIR_SECOND(id); + if (!ecs_id_is_wildcard(id)) { + if (ECS_PAIR_FIRST(id) != EcsFlag) { + /* If id is not a wildcard, remove it from the wildcard lists */ + remove_id_elem(idr, ecs_pair(rel, EcsWildcard)); + remove_id_elem(idr, ecs_pair(EcsWildcard, obj)); } + } else { + ecs_log_push_2(); - indent[i * 2] = '\0'; + /* If id is a wildcard, it means that all id records that match the + * wildcard are also empty, so release them */ + if (ECS_PAIR_FIRST(id) == EcsWildcard) { + /* Iterate (*, Target) list */ + ecs_id_record_t *cur, *next = idr->second.next; + while ((cur = next)) { + flecs_id_record_assert_empty(cur); + next = cur->second.next; + flecs_id_record_release(world, cur); + } + } else { + /* Iterate (Relationship, *) list */ + ecs_assert(ECS_PAIR_SECOND(id) == EcsWildcard, + ECS_INTERNAL_ERROR, NULL); + ecs_id_record_t *cur, *next = idr->first.next; + while ((cur = next)) { + flecs_id_record_assert_empty(cur); + next = cur->first.next; + flecs_id_record_release(world, cur); + } + } - fputs(indent, stream); + ecs_log_pop_2(); } } - if (level < 0) { - if (file) { - const char *file_ptr = strrchr(file, '/'); - if (!file_ptr) { - file_ptr = strrchr(file, '\\'); - } + /* Update counters */ + world->info.id_delete_total ++; - if (file_ptr) { - file = file_ptr + 1; - } + if (!ecs_id_is_wildcard(id)) { + world->info.id_count --; - fputs(file, stream); - fputs(": ", stream); + if (ECS_IS_PAIR(id)) { + world->info.pair_id_count --; } - if (line) { - fprintf(stream, "%d: ", line); + if (idr->type_info) { + world->info.component_id_count --; + } else { + world->info.tag_id_count --; } + } else { + world->info.wildcard_id_count --; } - fputs(msg, stream); + /* Unregister the id record from the world */ + ecs_map_remove(&world->id_index, flecs_id_record_id(id)); - fputs("\n", stream); + /* Free resources */ + ecs_table_cache_fini(&idr->cache); + flecs_name_index_free(idr->name_index); + ecs_os_free(idr); - if (level == -4) { - dump_backtrace(stream); + if (ecs_should_log_1()) { + char *id_str = ecs_id_str(world, id); + ecs_dbg_1("#[green]id#[normal] %s #[red]deleted", id_str); + ecs_os_free(id_str); } } -void ecs_os_dbg( - const char *file, - int32_t line, - const char *msg) +ecs_id_record_t* flecs_id_record_ensure( + ecs_world_t *world, + ecs_id_t id) { - if (ecs_os_api.log_) { - ecs_os_api.log_(1, file, line, msg); - } -} + ecs_poly_assert(world, ecs_world_t); -void ecs_os_trace( - const char *file, - int32_t line, - const char *msg) -{ - if (ecs_os_api.log_) { - ecs_os_api.log_(0, file, line, msg); + ecs_id_record_t **idr_ptr = ecs_map_ensure(&world->id_index, + ecs_id_record_t*, ecs_strip_generation(id)); + ecs_id_record_t *idr = idr_ptr[0]; + if (!idr) { + idr = flecs_id_record_new(world, id); + idr_ptr = ecs_map_get(&world->id_index, + ecs_id_record_t*, flecs_id_record_id(id)); + ecs_assert(idr_ptr != NULL, ECS_INTERNAL_ERROR, NULL); + idr_ptr[0] = idr; } + + return idr; } -void ecs_os_warn( - const char *file, - int32_t line, - const char *msg) +ecs_id_record_t* flecs_id_record_get( + const ecs_world_t *world, + ecs_id_t id) { - if (ecs_os_api.log_) { - ecs_os_api.log_(-2, file, line, msg); - } + ecs_poly_assert(world, ecs_world_t); + return ecs_map_get_ptr(&world->id_index, ecs_id_record_t*, + flecs_id_record_id(id)); } -void ecs_os_err( - const char *file, - int32_t line, - const char *msg) +ecs_id_record_t* flecs_query_id_record_get( + const ecs_world_t *world, + ecs_id_t id) { - if (ecs_os_api.log_) { - ecs_os_api.log_(-3, file, line, msg); + ecs_id_record_t *idr = flecs_id_record_get(world, id); + if (!idr) { + if (ECS_IS_PAIR(id)) { + idr = flecs_id_record_get(world, + ecs_pair(EcsUnion, ECS_PAIR_FIRST(id))); + } + return idr; } + if (ECS_IS_PAIR(id) && + ECS_PAIR_SECOND(id) == EcsWildcard && + (idr->flags & EcsIdUnion)) + { + idr = flecs_id_record_get(world, + ecs_pair(EcsUnion, ECS_PAIR_FIRST(id))); + } + + return idr; } -void ecs_os_fatal( - const char *file, - int32_t line, - const char *msg) +void flecs_id_record_claim( + ecs_world_t *world, + ecs_id_record_t *idr) { - if (ecs_os_api.log_) { - ecs_os_api.log_(-4, file, line, msg); - } + (void)world; + idr->refcount ++; } -static -void ecs_os_gettime(ecs_time_t *time) { - ecs_assert(ecs_os_has_time() == true, ECS_MISSING_OS_API, NULL); - - uint64_t now = ecs_os_now(); - uint64_t sec = now / 1000000000; - - assert(sec < UINT32_MAX); - assert((now - sec * 1000000000) < UINT32_MAX); +int32_t flecs_id_record_release( + ecs_world_t *world, + ecs_id_record_t *idr) +{ + int32_t rc = -- idr->refcount; + ecs_assert(rc >= 0, ECS_INTERNAL_ERROR, NULL); - time->sec = (uint32_t)sec; - time->nanosec = (uint32_t)(now - sec * 1000000000); -} + if (!rc) { + flecs_id_record_free(world, idr); + } -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); + return rc; } -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 flecs_id_record_release_tables( + ecs_world_t *world, + ecs_id_record_t *idr) +{ + /* Cache should not contain tables that aren't empty */ + ecs_assert(flecs_table_cache_count(&idr->cache) == 0, + ECS_INTERNAL_ERROR, NULL); -static -void* ecs_os_api_realloc(void *ptr, ecs_size_t size) { - ecs_assert(size > 0, ECS_INVALID_PARAMETER, NULL); + ecs_table_cache_iter_t it; + if (flecs_table_cache_empty_iter(&idr->cache, &it)) { + ecs_table_record_t *tr; + while ((tr = flecs_table_cache_next(&it, ecs_table_record_t))) { + /* Tables can hold claims on each other, so releasing a table can + * cause the next element to get invalidated. Claim the next table + * so that we can safely iterate. */ + ecs_table_t *next = NULL; + if (it.next) { + next = it.next->table; + flecs_table_claim(world, next); + } - 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); -} + /* Release current table */ + flecs_table_release(world, tr->hdr.table); -static -void ecs_os_api_free(void *ptr) { - if (ptr) { - ecs_os_api_free_count ++; - } - free(ptr); -} + /* Check if the only thing keeping the next table alive is our + * claim. If so, move to the next record before releasing */ + if (next) { + if (next->refcount == 1) { + it.next = it.next->next; + } -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 { - return NULL; + flecs_table_release(world, next); + } + } } } -/* 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; +bool flecs_id_record_set_type_info( + ecs_world_t *world, + ecs_id_record_t *idr, + const ecs_type_info_t *ti) +{ + if (!ecs_id_is_wildcard(idr->id)) { + if (ti) { + if (!idr->type_info) { + world->info.tag_id_count --; + world->info.component_id_count ++; + } + } else { + if (idr->type_info) { + world->info.tag_id_count ++; + world->info.component_id_count --; + } } } - return base; -} - -static -char* ecs_os_api_module_to_dl(const char *module) { - ecs_strbuf_t lib = ECS_STRBUF_INIT; - - /* Best guess, use module name with underscores + OS library extension */ - char *file_base = module_file_base(module, '_'); - -# if defined(ECS_TARGET_LINUX) || defined(ECS_TARGET_FREEBSD) - ecs_strbuf_appendstr(&lib, "lib"); - ecs_strbuf_appendstr(&lib, file_base); - ecs_strbuf_appendstr(&lib, ".so"); -# elif defined(ECS_TARGET_DARWIN) - ecs_strbuf_appendstr(&lib, "lib"); - ecs_strbuf_appendstr(&lib, file_base); - ecs_strbuf_appendstr(&lib, ".dylib"); -# elif defined(ECS_TARGET_WINDOWS) - ecs_strbuf_appendstr(&lib, file_base); - ecs_strbuf_appendstr(&lib, ".dll"); -# endif - - ecs_os_free(file_base); - - return ecs_strbuf_get(&lib); + bool changed = idr->type_info != ti; + idr->type_info = ti; + return changed; } -static -char* ecs_os_api_module_to_etc(const char *module) { - ecs_strbuf_t lib = ECS_STRBUF_INIT; - - /* Best guess, use module name with dashes + /etc */ - char *file_base = module_file_base(module, '-'); +ecs_hashmap_t* flecs_id_name_index_ensure( + ecs_world_t *world, + ecs_id_t id) +{ + ecs_poly_assert(world, ecs_world_t); - ecs_strbuf_appendstr(&lib, file_base); - ecs_strbuf_appendstr(&lib, "/etc"); + ecs_id_record_t *idr = flecs_id_record_get(world, id); + ecs_assert(idr != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_os_free(file_base); + ecs_hashmap_t *map = idr->name_index; + if (!map) { + map = idr->name_index = flecs_name_index_new(); + } - return ecs_strbuf_get(&lib); + return map; } -void ecs_os_set_api_defaults(void) +ecs_hashmap_t* flecs_id_name_index_get( + const ecs_world_t *world, + ecs_id_t id) { - /* Don't overwrite if already initialized */ - if (ecs_os_api_initialized != 0) { - return; - } - - if (ecs_os_api_initializing != 0) { - return; - } - - ecs_os_api_initializing = true; - - /* 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.get_time_ = ecs_os_gettime; - - /* Logging */ - ecs_os_api.log_ = log_msg; - - /* Modules */ - if (!ecs_os_api.module_to_dl_) { - ecs_os_api.module_to_dl_ = ecs_os_api_module_to_dl; - } + ecs_poly_assert(world, ecs_world_t); - if (!ecs_os_api.module_to_etc_) { - ecs_os_api.module_to_etc_ = ecs_os_api_module_to_etc; + ecs_id_record_t *idr = flecs_id_record_get(world, id); + if (!idr) { + return NULL; } - ecs_os_api.abort_ = abort; - -# ifdef FLECS_OS_API_IMPL - /* Initialize defaults to OS API IMPL addon, but still allow for overriding - * by the application */ - ecs_set_os_api_impl(); - ecs_os_api_initialized = false; -# endif - - ecs_os_api_initializing = false; -} - -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); + return idr->name_index; } -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_table_record_t* flecs_table_record_get( + const ecs_world_t *world, + const ecs_table_t *table, + ecs_id_t id) +{ + ecs_poly_assert(world, ecs_world_t); -bool ecs_os_has_time(void) { - return - (ecs_os_api.get_time_ != NULL) && - (ecs_os_api.sleep_ != NULL) && - (ecs_os_api.now_ != NULL); -} + ecs_id_record_t* idr = flecs_id_record_get(world, id); + if (!idr) { + return NULL; + } -bool ecs_os_has_logging(void) { - return (ecs_os_api.log_ != NULL); + return (ecs_table_record_t*)ecs_table_cache_get(&idr->cache, table); } -bool ecs_os_has_dl(void) { - return - (ecs_os_api.dlopen_ != NULL) && - (ecs_os_api.dlproc_ != NULL) && - (ecs_os_api.dlclose_ != NULL); +const ecs_table_record_t* flecs_id_record_get_table( + const ecs_id_record_t *idr, + const ecs_table_t *table) +{ + if (!idr) { + return NULL; + } + return (ecs_table_record_t*)ecs_table_cache_get(&idr->cache, table); } -bool ecs_os_has_modules(void) { - return - (ecs_os_api.module_to_dl_ != NULL) && - (ecs_os_api.module_to_etc_ != NULL); -} +void flecs_fini_id_records( + 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_ptr(&it, ecs_id_record_t*, NULL))) { + flecs_id_record_release(world, idr); + } -#if defined(ECS_TARGET_WINDOWS) -static char error_str[255]; -#endif + ecs_assert(ecs_map_count(&world->id_index) == 0, ECS_INTERNAL_ERROR, NULL); -const char* ecs_os_strerror(int err) { -# if defined(ECS_TARGET_WINDOWS) - strerror_s(error_str, 255, err); - return error_str; -# else - return strerror(err); -# endif + ecs_map_fini(&world->id_index); + flecs_sparse_free(world->pending_tables); + flecs_sparse_free(world->pending_buffer); } diff --git a/src/entity.c b/src/entity.c index e3eca0aef1..41300eb241 100644 --- a/src/entity.c +++ b/src/entity.c @@ -3501,17 +3501,24 @@ const char* ecs_get_symbol( } static -ecs_entity_t set_identifier( +ecs_entity_t flecs_set_identifier( ecs_world_t *world, ecs_entity_t entity, ecs_entity_t tag, const char *name) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(entity != 0 || name != NULL, ECS_INVALID_PARAMETER, NULL); + if (!entity) { entity = ecs_new_id(world); } + if (!name) { + ecs_remove_pair(world, entity, ecs_id(EcsIdentifier), tag); + return entity; + } + EcsIdentifier *ptr = ecs_get_mut_pair(world, entity, EcsIdentifier, tag); ecs_assert(ptr != NULL, ECS_INTERNAL_ERROR, NULL); ecs_os_strset(&ptr->value, name); @@ -3533,7 +3540,7 @@ ecs_entity_t ecs_set_name( .name = name }); } - return set_identifier(world, entity, EcsName, name); + return flecs_set_identifier(world, entity, EcsName, name); } ecs_entity_t ecs_set_symbol( @@ -3541,7 +3548,7 @@ ecs_entity_t ecs_set_symbol( ecs_entity_t entity, const char *name) { - return set_identifier(world, entity, EcsSymbol, name); + return flecs_set_identifier(world, entity, EcsSymbol, name); } void ecs_set_alias( @@ -3549,7 +3556,7 @@ void ecs_set_alias( ecs_entity_t entity, const char *name) { - set_identifier(world, entity, EcsAlias, name); + flecs_set_identifier(world, entity, EcsAlias, name); } ecs_id_t ecs_make_pair( diff --git a/test/api/project.json b/test/api/project.json index f648314de5..dd52778ac7 100644 --- a/test/api/project.json +++ b/test/api/project.json @@ -717,6 +717,7 @@ "lookup_not_found", "lookup_child", "lookup_w_null_name", + "lookup_after_name_reset", "get_name", "get_name_no_name", "get_name_from_empty", diff --git a/test/api/src/Lookup.c b/test/api/src/Lookup.c index 25161f0976..5d8939993b 100644 --- a/test/api/src/Lookup.c +++ b/test/api/src/Lookup.c @@ -32,6 +32,23 @@ void Lookup_lookup_w_null_name() { ecs_fini(world); } +void Lookup_lookup_after_name_reset() { + ecs_world_t *world = ecs_mini(); + + ecs_entity_t e = ecs_new_entity(world, "foo"); + ecs_entity_t lookup = ecs_lookup(world, "foo"); + test_assert(e == lookup); + + ecs_set_name(world, e, NULL); + test_assert(ecs_lookup(world, "foo") == 0); + + ecs_set_name(world, e, "foo"); + lookup = ecs_lookup(world, "foo"); + test_assert(e == lookup); + + ecs_fini(world); +} + void Lookup_lookup_component() { ecs_world_t *world = ecs_mini(); diff --git a/test/api/src/main.c b/test/api/src/main.c index acdca4256c..fc8869d96b 100644 --- a/test/api/src/main.c +++ b/test/api/src/main.c @@ -672,6 +672,7 @@ void Lookup_lookup_component(void); void Lookup_lookup_not_found(void); void Lookup_lookup_child(void); void Lookup_lookup_w_null_name(void); +void Lookup_lookup_after_name_reset(void); void Lookup_get_name(void); void Lookup_get_name_no_name(void); void Lookup_get_name_from_empty(void); @@ -4697,6 +4698,10 @@ bake_test_case Lookup_testcases[] = { "lookup_w_null_name", Lookup_lookup_w_null_name }, + { + "lookup_after_name_reset", + Lookup_lookup_after_name_reset + }, { "get_name", Lookup_get_name @@ -10653,7 +10658,7 @@ static bake_test_suite suites[] = { "Lookup", Lookup_setup, NULL, - 38, + 39, Lookup_testcases }, {