/** * @file entity.c * @brief Entity API. * * This file contains the implementation for the entity API, which includes * creating/deleting entities, adding/removing/setting components, instantiating * prefabs, and several other APIs for retrieving entity data. * * The file also contains the implementation of the command buffer, which is * located here so it can call functions private to the compilation unit. */ #include "private_api.h" #include static const ecs_entity_t* flecs_bulk_new( 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); typedef struct { const ecs_type_info_t *ti; void *ptr; } flecs_component_ptr_t; static flecs_component_ptr_t flecs_get_component_w_index( ecs_table_t *table, int32_t column_index, int32_t row) { ecs_check(column_index < table->column_count, ECS_NOT_A_COMPONENT, NULL); ecs_column_t *column = &table->data.columns[column_index]; return (flecs_component_ptr_t){ .ti = column->ti, .ptr = ecs_vec_get(&column->data, column->size, row) }; error: return (flecs_component_ptr_t){0}; } static flecs_component_ptr_t flecs_get_component_ptr( 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); ecs_table_record_t *tr = flecs_table_record_get(world, table, id); if (!tr || (tr->column == -1)) { ecs_check(tr == NULL, ECS_NOT_A_COMPONENT, NULL); return (flecs_component_ptr_t){0}; } return flecs_get_component_w_index(table, tr->column, row); error: return (flecs_component_ptr_t){0}; } static void* flecs_get_component( const ecs_world_t *world, ecs_table_t *table, int32_t row, ecs_id_t id) { return flecs_get_component_ptr(world, table, row, id).ptr; } void* flecs_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) { /* Cycle detected in IsA relationship */ ecs_check(recur_depth < ECS_MAX_RECURSION, ECS_INVALID_PARAMETER, NULL); /* Table (and thus entity) does not have component, look for base */ if (!(table->flags & EcsTableHasIsA)) { return NULL; } /* Exclude Name */ if (id == ecs_pair(ecs_id(EcsIdentifier), EcsName)) { return NULL; } /* Table should always be in the table index for (IsA, *), otherwise the * HasBase flag should not have been set */ 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_type_t type = table->type; ecs_id_t *ids = type.array; int32_t i = tr_isa->index, end = tr_isa->count + tr_isa->index; void *ptr = NULL; do { ecs_id_t pair = ids[i ++]; ecs_entity_t base = ecs_pair_second(world, pair); ecs_record_t *r = flecs_entities_get(world, base); ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); table = r->table; if (!table) { continue; } const ecs_table_record_t *tr = flecs_id_record_get_table(table_index, table); if (!tr || tr->column == -1) { ptr = flecs_get_base_component(world, table, id, table_index, recur_depth + 1); } else { int32_t row = ECS_RECORD_TO_ROW(r->row); ptr = flecs_get_component_w_index(table, tr->column, row).ptr; } } while (!ptr && (i < end)); return ptr; error: return NULL; } 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; } /* 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); } ecs_add_pair(world, parent, slot, child); break; } 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; } static ecs_table_t* flecs_find_table_add( ecs_world_t *world, ecs_table_t *table, ecs_id_t id, ecs_table_diff_builder_t *diff) { ecs_table_diff_t temp_diff = ECS_TABLE_DIFF_INIT; table = flecs_table_traverse_add(world, table, &id, &temp_diff); ecs_check(table != NULL, ECS_INVALID_PARAMETER, NULL); flecs_table_diff_build_append_table(world, diff, &temp_diff); return table; error: return NULL; } static ecs_table_t* flecs_find_table_remove( ecs_world_t *world, ecs_table_t *table, ecs_id_t id, ecs_table_diff_builder_t *diff) { ecs_table_diff_t temp_diff = ECS_TABLE_DIFF_INIT; table = flecs_table_traverse_remove(world, table, &id, &temp_diff); ecs_check(table != NULL, ECS_INVALID_PARAMETER, NULL); flecs_table_diff_build_append_table(world, diff, &temp_diff); return table; error: return NULL; } static void flecs_instantiate_children( 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; ecs_entity_t slot_of = 0; ecs_entity_t *ids = type.array; int32_t type_count = type.count; /* Instantiate child table for each instance */ /* 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 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 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) == (uint32_t)base)) { childof_base_index = pos; } int32_t storage_index = ecs_table_type_to_column_index(child_table, i); if (storage_index != -1) { ecs_vec_t *column = &child_data->columns[storage_index].data; component_data[pos] = ecs_vec_first(column); } else { component_data[pos] = NULL; } components.array[pos] = id; pos ++; } /* 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 ++; } components.count = pos; /* Instantiate the prefab child table for each new instance */ ecs_entity_t *instances = ecs_vec_first(&table->data.entities); int32_t child_count = ecs_vec_count(&child_data->entities); bool has_union = child_table->flags & EcsTableHasUnion; for (i = row; i < count + row; i ++) { ecs_entity_t instance = instances[i]; ecs_table_diff_builder_t diff = ECS_TABLE_DIFF_INIT; flecs_table_diff_builder_init(world, &diff); 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); /* Find or create table */ for (j = 0; j < components.count; j ++) { i_table = flecs_find_table_add( world, i_table, components.array[j], &diff); } ecs_assert(i_table != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(i_table->type.count == components.count, ECS_INTERNAL_ERROR, NULL); /* 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_vec_first(&child_data->entities); #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 /* Create children */ int32_t child_row; ecs_table_diff_t table_diff; flecs_table_diff_build_noalloc(&diff, &table_diff); const ecs_entity_t *i_children = flecs_bulk_new(world, i_table, NULL, &components, child_count, component_data, false, &child_row, &table_diff); flecs_table_diff_builder_fini(world, &diff); /* If children have union relationships, initialize */ if (has_union) { ecs_table__t *meta = child_table->_; ecs_assert(meta != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(i_table->_ != NULL, ECS_INTERNAL_ERROR, NULL); int32_t u, u_count = meta->sw_count; for (u = 0; u < u_count; u ++) { ecs_switch_t *src_sw = &meta->sw_columns[i]; ecs_switch_t *dst_sw = &i_table->_->sw_columns[i]; ecs_vec_t *v_src_values = flecs_switch_values(src_sw); ecs_vec_t *v_dst_values = flecs_switch_values(dst_sw); uint64_t *src_values = ecs_vec_first(v_src_values); uint64_t *dst_values = ecs_vec_first(v_dst_values); for (j = 0; j < child_count; j ++) { dst_values[j] = src_values[j]; } } } /* 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; } void flecs_instantiate( ecs_world_t *world, ecs_entity_t base, ecs_table_t *table, int32_t row, int32_t count) { ecs_record_t *record = flecs_entities_get_any(world, base); ecs_table_t *base_table = record->table; if (!base_table || !(base_table->flags & EcsTableIsPrefab)) { /* Don't instantiate children from base entities that aren't prefabs */ return; } ecs_id_record_t *idr = flecs_id_record_get(world, ecs_childof(base)); ecs_table_cache_iter_t it; if (idr && flecs_table_cache_all_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); } } } static void flecs_set_union( ecs_world_t *world, ecs_table_t *table, int32_t row, int32_t count, const ecs_type_t *ids) { 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; } ecs_table_record_t *tr = flecs_id_record_get_table(idr, table); ecs_assert(tr != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(table->_ != NULL, ECS_INTERNAL_ERROR, NULL); int32_t column = tr->index - table->_->sw_offset; ecs_switch_t *sw = &table->_->sw_columns[column]; ecs_entity_t union_case = 0; union_case = ecs_pair_second(world, id); int32_t r; for (r = 0; r < count; r ++) { flecs_switch_set(sw, row + r, union_case); } } } } 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, const ecs_type_t *added, ecs_flags32_t flags) { ecs_assert(added != NULL, ECS_INTERNAL_ERROR, NULL); if (added->count) { ecs_flags32_t table_flags = table->flags; if (table_flags & EcsTableHasUnion) { flecs_set_union(world, table, row, count, added); } if (table_flags & (EcsTableHasOnAdd|EcsTableHasIsA|EcsTableHasTraversable)) { flecs_emit(world, world, &(ecs_event_desc_t){ .event = EcsOnAdd, .ids = added, .table = table, .other_table = other_table, .offset = row, .count = count, .observable = world, .flags = flags }); } } } void flecs_notify_on_remove( ecs_world_t *world, ecs_table_t *table, ecs_table_t *other_table, int32_t row, int32_t count, const ecs_type_t *removed) { ecs_assert(removed != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(count != 0, ECS_INTERNAL_ERROR, NULL); if (removed->count && (table->flags & (EcsTableHasOnRemove|EcsTableHasUnSet|EcsTableHasIsA|EcsTableHasTraversable))) { flecs_emit(world, world, &(ecs_event_desc_t) { .event = EcsOnRemove, .ids = removed, .table = table, .other_table = other_table, .offset = row, .count = count, .observable = world }); } } static void flecs_update_name_index( ecs_world_t *world, ecs_table_t *src, ecs_table_t *dst, int32_t offset, int32_t count) { ecs_assert(src != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(dst != NULL, ECS_INTERNAL_ERROR, NULL); if (!(dst->flags & EcsTableHasName)) { /* If destination table doesn't have a name, we don't need to update the * name index. Even if the src table had a name, the on_remove hook for * EcsIdentifier will remove the entity from the index. */ return; } ecs_hashmap_t *src_index = src->_->name_index; ecs_hashmap_t *dst_index = dst->_->name_index; if ((src_index == dst_index) || (!src_index && !dst_index)) { /* If the name index didn't change, the entity still has the same parent * so nothing needs to be done. */ return; } EcsIdentifier *names = ecs_table_get_pair(world, dst, EcsIdentifier, EcsName, offset); ecs_assert(names != NULL, ECS_INTERNAL_ERROR, NULL); int32_t i; ecs_entity_t *entities = ecs_vec_get_t( &dst->data.entities, ecs_entity_t, offset); for (i = 0; i < count; i ++) { ecs_entity_t e = entities[i]; EcsIdentifier *name = &names[i]; uint64_t index_hash = name->index_hash; if (index_hash) { flecs_name_index_remove(src_index, e, index_hash); } const char *name_str = name->value; if (name_str) { ecs_assert(name->hash != 0, ECS_INTERNAL_ERROR, NULL); flecs_name_index_ensure( dst_index, e, name_str, name->length, name->hash); name->index = dst_index; } } } static ecs_record_t* flecs_new_entity( ecs_world_t *world, ecs_entity_t entity, ecs_record_t *record, ecs_table_t *table, ecs_table_diff_t *diff, bool ctor, ecs_flags32_t evt_flags) { ecs_assert(record != NULL, ECS_INTERNAL_ERROR, NULL); int32_t row = flecs_table_append(world, table, entity, ctor, true); record->table = table; record->row = ECS_ROW_TO_RECORD(row, record->row & ECS_ROW_FLAGS_MASK); ecs_assert(ecs_vec_count(&table->data.entities) > row, ECS_INTERNAL_ERROR, NULL); flecs_notify_on_add(world, table, NULL, row, 1, &diff->added, evt_flags); return record; } static void flecs_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 ctor, ecs_flags32_t evt_flags) { 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_table->type.count > 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(src_row >= 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(ecs_vec_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); /* Append new row to destination table */ int32_t dst_row = flecs_table_append(world, dst_table, entity, false, false); /* Invoke remove actions for removed components */ flecs_notify_on_remove( world, src_table, dst_table, src_row, 1, &diff->removed); /* Copy entity & components from src_table to dst_table */ flecs_table_move(world, entity, entity, dst_table, dst_row, src_table, src_row, ctor); /* 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); flecs_notify_on_add( world, dst_table, src_table, dst_row, 1, &diff->added, evt_flags); flecs_update_name_index(world, src_table, dst_table, dst_row, 1); error: return; } static void flecs_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); /* Invoke remove actions before deleting */ flecs_notify_on_remove(world, table, NULL, row, 1, &diff->removed); 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_update_component_monitor_w_array( ecs_world_t *world, ecs_type_t *ids) { 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); } } static void flecs_update_component_monitors( ecs_world_t *world, ecs_type_t *added, ecs_type_t *removed) { flecs_update_component_monitor_w_array(world, added); flecs_update_component_monitor_w_array(world, removed); } 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, ecs_flags32_t evt_flags) { ecs_assert(!(world->flags & EcsWorldReadonly), ECS_INTERNAL_ERROR, NULL); flecs_journal_begin(world, EcsJournalMove, entity, &diff->added, &diff->removed); ecs_table_t *src_table = NULL; int is_trav = 0; if (record) { src_table = record->table; is_trav = (record->row & EcsEntityIsTraversable) != 0; } 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 union relationship could have changed. */ if (src_table) { flecs_notify_on_add(world, src_table, src_table, ECS_RECORD_TO_ROW(record->row), 1, &diff->added, evt_flags); } flecs_journal_end(); return; } if (src_table) { ecs_assert(dst_table != NULL, ECS_INTERNAL_ERROR, NULL); flecs_table_traversable_add(dst_table, is_trav); if (dst_table->type.count) { flecs_move_entity(world, entity, record, dst_table, diff, construct, evt_flags); } else { flecs_delete_entity(world, record, diff); record->table = NULL; } flecs_table_traversable_add(src_table, -is_trav); } else { flecs_table_traversable_add(dst_table, is_trav); if (dst_table->type.count) { flecs_new_entity(world, entity, record, dst_table, diff, construct, evt_flags); } } /* 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 (is_trav) { flecs_update_component_monitors(world, &diff->added, &diff->removed); } 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: flecs_journal_end(); return; } static const ecs_entity_t* flecs_bulk_new( 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_entities_new_ids(world, count); } if (!table) { return entities; } ecs_type_t type = table->type; if (!type.count) { return entities; } ecs_type_t component_array = { 0 }; if (!component_ids) { component_ids = &component_array; component_array.array = type.array; component_array.count = type.count; } ecs_data_t *data = &table->data; int32_t row = flecs_table_appendn(world, table, data, count, entities); /* Update entity index. */ int i; for (i = 0; i < count; i ++) { ecs_record_t *r = flecs_entities_get(world, entities[i]); r->table = table; r->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->added, (component_data == NULL) ? 0 : EcsEventNoOnSet); if (component_data) { int32_t c_i; for (c_i = 0; c_i < component_ids->count; c_i ++) { void *src_ptr = component_data[c_i]; if (!src_ptr) { continue; } /* 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, table, id); ecs_assert(tr != NULL, ECS_INVALID_PARAMETER, "id is not a component"); ecs_assert(tr->column != -1, ECS_INVALID_PARAMETER, "id is not a component"); ecs_assert(tr->count == 1, ECS_INVALID_PARAMETER, "ids cannot be wildcards"); int32_t index = tr->column; ecs_column_t *column = &table->data.columns[index]; ecs_type_info_t *ti = column->ti; int32_t size = column->size; ecs_assert(size != 0, ECS_INTERNAL_ERROR, NULL); void *ptr = ecs_vec_get(&column->data, size, row); 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); } }; int32_t j, storage_count = table->column_count; for (j = 0; j < storage_count; j ++) { ecs_type_t set_type = { .array = &table->data.columns[j].id, .count = 1 }; flecs_notify_on_set(world, table, row, count, &set_type, true); } } flecs_defer_end(world, &world->stages[0]); if (row_out) { *row_out = row; } if (sparse_count) { entities = flecs_entities_ids(world); return &entities[sparse_count]; } else { return entities; } } static void flecs_add_id_w_record( ecs_world_t *world, ecs_entity_t entity, ecs_record_t *record, ecs_id_t id, bool construct) { ecs_assert(record != NULL, ECS_INTERNAL_ERROR, NULL); ecs_table_t *src_table = record->table; ecs_table_diff_t diff = ECS_TABLE_DIFF_INIT; ecs_table_t *dst_table = flecs_table_traverse_add( world, src_table, &id, &diff); flecs_commit(world, entity, record, dst_table, &diff, construct, EcsEventNoOnSet); /* No OnSet, this function is only called from * functions that are about to set the component. */ } static void flecs_add_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_add(stage, entity, id)) { return; } ecs_record_t *r = flecs_entities_get(world, entity); ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); ecs_table_diff_t diff = ECS_TABLE_DIFF_INIT; 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, 0); flecs_defer_end(world, stage); } static void flecs_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(stage, entity, id)) { return; } ecs_record_t *r = flecs_entities_get(world, entity); ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); ecs_table_t *src_table = r->table; ecs_table_diff_t diff = ECS_TABLE_DIFF_INIT; ecs_table_t *dst_table = flecs_table_traverse_remove( world, src_table, &id, &diff); flecs_commit(world, entity, r, dst_table, &diff, true, 0); flecs_defer_end(world, stage); } static flecs_component_ptr_t flecs_get_mut( ecs_world_t *world, ecs_entity_t entity, ecs_entity_t id, ecs_record_t *r) { flecs_component_ptr_t dst = {0}; ecs_poly_assert(world, ecs_world_t); 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 (r->table) { dst = flecs_get_component_ptr( world, r->table, ECS_RECORD_TO_ROW(r->row), id); if (dst.ptr) { return dst; } } /* If entity didn't have component yet, add it */ flecs_add_id_w_record(world, entity, r, id, true); /* Flush commands so the pointer we're fetching is stable */ flecs_defer_end(world, &world->stages[0]); flecs_defer_begin(world, &world->stages[0]); ecs_assert(r->table != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(r->table->column_count != 0, ECS_INTERNAL_ERROR, NULL); dst = flecs_get_component_ptr( world, r->table, ECS_RECORD_TO_ROW(r->row), id); error: return dst; } void flecs_invoke_hook( ecs_world_t *world, ecs_table_t *table, int32_t count, int32_t row, ecs_entity_t *entities, void *ptr, ecs_id_t id, const ecs_type_info_t *ti, ecs_entity_t event, ecs_iter_action_t hook) { int32_t defer = world->stages[0].defer; if (defer < 0) { world->stages[0].defer *= -1; } ecs_iter_t it = { .field_count = 1}; it.entities = entities; flecs_iter_init(world, &it, flecs_iter_cache_all); it.world = world; it.real_world = world; it.table = table; it.ptrs[0] = ptr; it.sizes = ECS_CONST_CAST(ecs_size_t*, &ti->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; it.offset = row; flecs_iter_validate(&it); hook(&it); ecs_iter_fini(&it); world->stages[0].defer = defer; } 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_assert(ids != NULL, ECS_INTERNAL_ERROR, NULL); ecs_data_t *data = &table->data; ecs_entity_t *entities = ecs_vec_get_t( &data->entities, ecs_entity_t, row); ecs_assert(entities != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert((row + count) <= ecs_vec_count(&data->entities), ECS_INTERNAL_ERROR, NULL); if (owned) { 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, table, id); ecs_assert(tr != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(tr->column != -1, ECS_INTERNAL_ERROR, NULL); ecs_assert(tr->count == 1, ECS_INTERNAL_ERROR, NULL); int32_t index = tr->column; ecs_column_t *column = &table->data.columns[index]; const ecs_type_info_t *ti = column->ti; ecs_iter_action_t on_set = ti->hooks.on_set; if (on_set) { ecs_vec_t *c = &column->data; void *ptr = ecs_vec_get(c, column->size, row); flecs_invoke_hook(world, table, count, row, entities, ptr, id, ti, EcsOnSet, on_set); } } } /* Run OnSet notifications */ if (table->flags & EcsTableHasOnSet && ids->count) { flecs_emit(world, world, &(ecs_event_desc_t) { .event = EcsOnSet, .ids = ids, .table = table, .offset = row, .count = count, .observable = world }); } } void flecs_record_add_flag( ecs_record_t *record, uint32_t flag) { if (flag == EcsEntityIsTraversable) { if (!(record->row & flag)) { ecs_table_t *table = record->table; if (table) { flecs_table_traversable_add(table, 1); } } } record->row |= flag; } void flecs_add_flag( ecs_world_t *world, ecs_entity_t entity, uint32_t flag) { ecs_record_t *record = flecs_entities_get_any(world, entity); ecs_assert(record != NULL, ECS_INTERNAL_ERROR, NULL); flecs_record_add_flag(record, 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) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(!ecs_is_deferred(world), ECS_INVALID_OPERATION, NULL); ecs_table_t *src_table = NULL; if (!record) { record = flecs_entities_get(world, entity); src_table = record->table; } ecs_table_diff_t diff = ECS_TABLE_DIFF_INIT; if (added) { diff.added = *added; } if (removed) { diff.removed = *removed; } ecs_defer_begin(world); flecs_commit(world, entity, record, table, &diff, true, 0); ecs_defer_end(world); return src_table != table; error: return false; } ecs_entity_t ecs_set_with( ecs_world_t *world, ecs_id_t id) { 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_id_t ecs_get_with( 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->with; error: return 0; } ecs_entity_t ecs_new_id( ecs_world_t *world) { 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_CONST_CAST(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); /* Can't atomically increase number above max int */ ecs_assert(flecs_entities_max_id(unsafe_world) < UINT_MAX, ECS_INVALID_OPERATION, NULL); entity = (ecs_entity_t)ecs_os_ainc( (int32_t*)&flecs_entities_max_id(unsafe_world)); } else { entity = flecs_entities_new_id(unsafe_world); } ecs_assert(!unsafe_world->info.max_id || ecs_entity_t_lo(entity) <= unsafe_world->info.max_id, ECS_OUT_OF_RANGE, NULL); flecs_journal(world, EcsJournalNew, entity, 0, 0); return entity; error: return 0; } ecs_entity_t ecs_new_low_id( ecs_world_t *world) { 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_CONST_CAST(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); } ecs_entity_t id = 0; if (unsafe_world->info.last_component_id < FLECS_HI_COMPONENT_ID) { do { id = unsafe_world->info.last_component_id ++; } while (ecs_exists(unsafe_world, id) && id <= FLECS_HI_COMPONENT_ID); } if (!id || id >= FLECS_HI_COMPONENT_ID) { /* If the low component ids are depleted, return a regular entity id */ id = ecs_new_id(unsafe_world); } else { flecs_entities_ensure(world, id); } ecs_assert(ecs_get_type(world, id) == NULL, ECS_INTERNAL_ERROR, NULL); return id; error: return 0; } 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_stage_t *stage = flecs_stage_from_world(&world); ecs_entity_t entity = ecs_new_id(world); ecs_id_t ids[3]; ecs_type_t to_add = { .array = ids, .count = 0 }; if (id) { ids[to_add.count ++] = id; } ecs_id_t with = stage->with; if (with) { ids[to_add.count ++] = with; } 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_add(stage, entity, to_add.array[0])) { int i; for (i = 1; i < to_add.count; i ++) { flecs_defer_add(stage, entity, to_add.array[i]); } return entity; } int32_t i, count = to_add.count; ecs_table_t *table = &world->store.root; ecs_table_diff_builder_t diff = ECS_TABLE_DIFF_INIT; flecs_table_diff_builder_init(world, &diff); for (i = 0; i < count; i ++) { table = flecs_find_table_add( world, table, to_add.array[i], &diff); } ecs_table_diff_t table_diff; flecs_table_diff_build_noalloc(&diff, &table_diff); ecs_record_t *r = flecs_entities_get(world, entity); flecs_new_entity(world, entity, r, table, &table_diff, true, true); flecs_table_diff_builder_fini(world, &diff); } else { if (flecs_defer_cmd(stage)) { return entity; } flecs_entities_ensure(world, entity); } flecs_defer_end(world, stage); return entity; error: return 0; } ecs_entity_t ecs_new_w_table( ecs_world_t *world, ecs_table_t *table) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); flecs_stage_from_world(&world); ecs_entity_t entity = ecs_new_id(world); ecs_record_t *r = flecs_entities_get(world, entity); ecs_table_diff_t table_diff = { .added = table->type }; flecs_new_entity(world, entity, r, table, &table_diff, true, true); return entity; error: return 0; } #ifdef FLECS_PARSER /* Traverse table graph by either adding or removing identifiers parsed from the * passed in expression. */ static ecs_table_t *flecs_traverse_from_expr( ecs_world_t *world, ecs_table_t *table, const char *name, const char *expr, ecs_table_diff_builder_t *diff, bool replace_and, bool *error) { const char *ptr = expr; if (ptr) { ecs_term_t term = {0}; while (ptr[0] && (ptr = ecs_parse_term(world, name, expr, ptr, &term, NULL))){ if (!ecs_term_is_initialized(&term)) { break; } 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; } if (ecs_term_finalize(world, &term)) { ecs_term_fini(&term); if (error) { *error = true; } return NULL; } 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; } if (term.oper == EcsAnd || !replace_and) { /* Regular AND expression */ table = flecs_find_table_add(world, table, term.id, diff); } ecs_term_fini(&term); } if (!ptr) { if (error) { *error = true; } return NULL; } } return table; } /* Add/remove components based on the parsed expression. This operation is * slower than flecs_traverse_from_expr, but safe to use from a deferred context. */ static void flecs_defer_from_expr( ecs_world_t *world, ecs_entity_t entity, const char *name, const char *expr, bool is_add, bool replace_and) { const char *ptr = expr; if (ptr) { ecs_term_t term = {0}; while (ptr[0] && (ptr = ecs_parse_term(world, name, expr, ptr, &term, NULL))) { if (!ecs_term_is_initialized(&term)) { break; } if (ecs_term_finalize(world, &term)) { return; } 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; } 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); } } ecs_term_fini(&term); } } } #endif /* If operation is not deferred, add components by finding the target * table and moving the entity towards it. */ static int flecs_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 flecs_new_entity, bool name_assigned) { const char *sep = desc->sep; const char *root_sep = desc->root_sep; ecs_table_diff_builder_t diff = ECS_TABLE_DIFF_INIT; flecs_table_diff_builder_init(world, &diff); /* Find existing table */ ecs_table_t *src_table = NULL, *table = NULL; ecs_record_t *r = flecs_entities_get(world, result); table = r->table; /* If a name is provided but not yet assigned, add the Name component */ if (name && !name_assigned) { table = flecs_find_table_add(world, table, ecs_pair(ecs_id(EcsIdentifier), EcsName), &diff); } /* Add components from the 'add' id array */ int32_t i = 0; ecs_id_t id; const ecs_id_t *ids = desc->add; while ((i < FLECS_ID_DESC_MAX) && (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 = flecs_find_table_add(world, table, id, &diff); } } /* Find destination table */ /* 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 (flecs_new_entity) { if (flecs_new_entity && scope && !name && !name_assigned) { table = flecs_find_table_add( world, table, ecs_pair(EcsChildOf, scope), &diff); } if (with) { table = flecs_find_table_add(world, table, with, &diff); } } /* 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 = flecs_traverse_from_expr( world, table, name, desc->add_expr, &diff, true, &error); if (error) { flecs_table_diff_builder_fini(world, &diff); return -1; } #else ecs_abort(ECS_UNSUPPORTED, "parser addon is not available"); #endif } /* Commit entity to destination table */ if (src_table != table) { flecs_defer_begin(world, &world->stages[0]); ecs_table_diff_t table_diff; flecs_table_diff_build_noalloc(&diff, &table_diff); flecs_commit(world, result, r, table, &table_diff, true, 0); flecs_table_diff_builder_fini(world, &diff); flecs_defer_end(world, &world->stages[0]); } /* 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); } 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_table_diff_builder_fini(world, &diff); return 0; } /* When in deferred mode, we need to add/remove components one by one using * the regular operations. */ static void flecs_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 flecs_new_entity, bool name_assigned) { const char *sep = desc->sep; const char *root_sep = desc->root_sep; /* 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 (flecs_new_entity) { if (flecs_new_entity && scope && !name && !name_assigned) { ecs_add_id(world, entity, ecs_pair(EcsChildOf, scope)); } if (with) { ecs_add_id(world, entity, with); } } /* Add components from the 'add' id array */ int32_t i = 0; ecs_id_t id; const ecs_id_t *ids = desc->add; while ((i < FLECS_ID_DESC_MAX) && (id = ids[i ++])) { bool defer = true; if (ECS_HAS_ID_FLAG(id, PAIR) && ECS_PAIR_FIRST(id) == EcsChildOf) { scope = ECS_PAIR_SECOND(id); if (name && (!desc->id || !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 flecs_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) { 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); } } } } ecs_entity_t ecs_entity_init( ecs_world_t *world, 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); ecs_entity_t result = desc->id; const char *name = desc->name; const char *sep = desc->sep; if (!sep) { sep = "."; } if (name) { if (!name[0]) { name = NULL; } else if (flecs_name_is_id(name)){ ecs_entity_t id = flecs_name_to_id(world, name); if (!id) { return 0; } if (result && (id != result)) { ecs_err("name id conflicts with provided id"); return 0; } name = NULL; result = id; } } const char *root_sep = desc->root_sep; bool flecs_new_entity = false; bool name_assigned = false; /* 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; } } } /* Find or create entity */ 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 < FLECS_ID_DESC_MAX) && (id = ids[i ++])) { if (ECS_HAS_ID_FLAG(id, PAIR) && (ECS_PAIR_FIRST(id) == EcsChildOf)) { scope = ECS_PAIR_SECOND(id); } } result = ecs_lookup_path_w_sep( world, scope, name, sep, root_sep, false); if (result) { name_assigned = true; } } if (!result) { if (desc->use_low_id) { result = ecs_new_low_id(world); } else { result = ecs_new_id(world); } flecs_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); 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 */ if (!sep || sep[0]) { path = ecs_get_path_w_sep(world, scope, result, sep, ""); } else { /* Safe, only freed when sep is valid */ path = ECS_CONST_CAST(char*, ecs_get_name(world, result)); } } if (path) { if (ecs_os_strcmp(path, name)) { /* Mismatching name */ ecs_err("existing entity '%s' is initialized with " "conflicting name '%s'", path, name); if (!sep || sep[0]) { ecs_os_free(path); } return 0; } if (!sep || sep[0]) { ecs_os_free(path); } } } } ecs_assert(name_assigned == ecs_has_pair( world, result, ecs_id(EcsIdentifier), EcsName), ECS_INTERNAL_ERROR, NULL); if (stage->defer) { flecs_deferred_add_remove((ecs_world_t*)stage, result, name, desc, scope, with, flecs_new_entity, name_assigned); } else { if (flecs_traverse_add(world, result, name, desc, scope, with, flecs_new_entity, name_assigned)) { return 0; } } return result; error: return 0; } const ecs_entity_t* ecs_bulk_init( ecs_world_t *world, const ecs_bulk_desc_t *desc) { ecs_poly_assert(world, ecs_world_t); ecs_assert(!(world->flags & EcsWorldReadonly), ECS_INTERNAL_ERROR, NULL); ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(desc->_canary == 0, ECS_INVALID_PARAMETER, NULL); const ecs_entity_t *entities = desc->entities; int32_t count = desc->count; int32_t sparse_count = 0; if (!entities) { sparse_count = flecs_entities_count(world); entities = flecs_entities_new_ids(world, count); ecs_assert(entities != NULL, ECS_INTERNAL_ERROR, NULL); } else { int i; for (i = 0; i < count; i ++) { ecs_ensure(world, entities[i]); } } ecs_type_t ids; ecs_table_t *table = desc->table; if (!table) { ecs_table_diff_builder_t diff = ECS_TABLE_DIFF_INIT; flecs_table_diff_builder_init(world, &diff); int32_t i = 0; ecs_id_t id; while ((id = desc->ids[i])) { table = flecs_find_table_add(world, table, id, &diff); i ++; } ids.array = ECS_CONST_CAST(ecs_id_t*, desc->ids); ids.count = i; ecs_table_diff_t table_diff; flecs_table_diff_build_noalloc(&diff, &table_diff); flecs_bulk_new(world, table, entities, &ids, count, desc->data, true, NULL, &table_diff); flecs_table_diff_builder_fini(world, &diff); } else { ecs_table_diff_t diff = { .added.array = table->type.array, .added.count = table->type.count }; ids = (ecs_type_t){.array = diff.added.array, .count = diff.added.count}; flecs_bulk_new(world, table, entities, &ids, count, desc->data, true, NULL, &diff); } if (!sparse_count) { return entities; } else { /* Refetch entity ids, in case the underlying array was reallocated */ entities = flecs_entities_ids(world); return &entities[sparse_count]; } error: return NULL; } static void flecs_check_component( ecs_world_t *world, ecs_entity_t result, const EcsComponent *ptr, ecs_size_t size, ecs_size_t alignment) { if (ptr->size != size) { char *path = ecs_get_fullpath(world, result); ecs_abort(ECS_INVALID_COMPONENT_SIZE, path); ecs_os_free(path); } if (ptr->alignment != alignment) { char *path = ecs_get_fullpath(world, result); ecs_abort(ECS_INVALID_COMPONENT_ALIGNMENT, path); ecs_os_free(path); } } 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(desc != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(desc->_canary == 0, ECS_INVALID_PARAMETER, NULL); /* If existing entity is provided, check if it is already registered as a * component and matches the size/alignment. This can prevent having to * suspend readonly mode, and increases the number of scenarios in which * this function can be called in multithreaded mode. */ ecs_entity_t result = desc->entity; if (result && ecs_is_alive(world, result)) { const EcsComponent *const_ptr = ecs_get(world, result, EcsComponent); if (const_ptr) { flecs_check_component(world, result, const_ptr, desc->type.size, desc->type.alignment); return result; } } ecs_suspend_readonly_state_t readonly_state; world = flecs_suspend_readonly(world, &readonly_state); bool new_component = true; if (!result) { result = ecs_new_low_id(world); } else { ecs_ensure(world, result); new_component = ecs_has(world, result, EcsComponent); } 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 (!new_component || ptr->size != desc->type.size) { 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 { flecs_check_component(world, result, ptr, desc->type.size, desc->type.alignment); } ecs_modified(world, result, EcsComponent); 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 < FLECS_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 0; } 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_stage_t *stage = flecs_stage_from_world(&world); const ecs_entity_t *ids; if (flecs_defer_bulk_new(world, stage, count, id, &ids)) { return ids; } ecs_table_t *table = &world->store.root; ecs_table_diff_builder_t diff = ECS_TABLE_DIFF_INIT; flecs_table_diff_builder_init(world, &diff); if (id) { table = flecs_find_table_add(world, table, id, &diff); } ecs_table_diff_t td; flecs_table_diff_build_noalloc(&diff, &td); ids = flecs_bulk_new(world, table, NULL, NULL, count, NULL, false, NULL, &td); flecs_table_diff_builder_fini(world, &diff); flecs_defer_end(world, stage); return ids; error: return NULL; } void ecs_clear( ecs_world_t *world, ecs_entity_t entity) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(ecs_is_valid(world, entity), ECS_INVALID_PARAMETER, NULL); ecs_stage_t *stage = flecs_stage_from_world(&world); if (flecs_defer_clear(stage, entity)) { return; } ecs_record_t *r = flecs_entities_get(world, entity); ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); ecs_table_t *table = r->table; if (table) { ecs_table_diff_t diff = { .removed = table->type }; flecs_delete_entity(world, r, &diff); r->table = NULL; if (r->row & EcsEntityIsTraversable) { flecs_table_traversable_add(table, -1); } } flecs_defer_end(world, stage); error: return; } static void flecs_throw_invalid_delete( 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); } error: ecs_os_free(id_str); } static void flecs_marked_id_push( ecs_world_t *world, ecs_id_record_t* idr, ecs_entity_t action, bool delete_id) { ecs_marked_id_t *m = ecs_vec_append_t(&world->allocator, &world->store.marked_ids, ecs_marked_id_t); m->idr = idr; m->id = idr->id; m->action = action; m->delete_id = delete_id; flecs_id_record_claim(world, idr); } static void flecs_id_mark_for_delete( ecs_world_t *world, ecs_id_record_t *idr, ecs_entity_t action, bool delete_id); static void flecs_targets_mark_for_delete( ecs_world_t *world, ecs_table_t *table) { ecs_id_record_t *idr; ecs_entity_t *entities = ecs_vec_first(&table->data.entities); int32_t i, count = ecs_vec_count(&table->data.entities); for (i = 0; i < count; i ++) { ecs_record_t *r = flecs_entities_get(world, entities[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 & (EcsEntityIsId|EcsEntityIsTarget))) { continue; } ecs_entity_t e = entities[i]; if (flags & EcsEntityIsId) { if ((idr = flecs_id_record_get(world, e))) { flecs_id_mark_for_delete(world, idr, ECS_ID_ON_DELETE(idr->flags), true); } if ((idr = flecs_id_record_get(world, ecs_pair(e, EcsWildcard)))) { flecs_id_mark_for_delete(world, idr, ECS_ID_ON_DELETE(idr->flags), true); } } if (flags & EcsEntityIsTarget) { if ((idr = flecs_id_record_get(world, ecs_pair(EcsWildcard, e)))) { flecs_id_mark_for_delete(world, idr, ECS_ID_ON_DELETE_TARGET(idr->flags), true); } if ((idr = flecs_id_record_get(world, ecs_pair(EcsFlag, e)))) { flecs_id_mark_for_delete(world, idr, ECS_ID_ON_DELETE_TARGET(idr->flags), true); } } } } 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; } 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) { ecs_id_record_t *idr = (ecs_id_record_t*)tr->hdr.cache; ecs_id_t id = idr->id; /* If action is not specified and we're deleting a relationship target, * derive the action from the current record */ int32_t i = tr->index, count = tr->count; do { ecs_type_t *type = &table->type; ecs_table_record_t *trr = &table->_->records[i]; ecs_id_record_t *idrr = (ecs_id_record_t*)trr->hdr.cache; result = ECS_ID_ON_DELETE_TARGET(idrr->flags); if (result == EcsDelete) { /* Delete takes precedence over Remove */ break; } if (count > 1) { /* If table contains multiple pairs for target they are not * guaranteed to occupy consecutive elements in the table's type * vector, so a linear search is needed to find matches. */ for (++ i; i < type->count; i ++) { if (ecs_id_match(type->array[i], id)) { break; } } /* We should always have as many matching ids as tr->count */ ecs_assert(i < type->count, ECS_INTERNAL_ERROR, NULL); } } while (--count); } return result; } static void flecs_update_monitors_for_delete( ecs_world_t *world, ecs_id_t id) { flecs_update_component_monitors(world, NULL, &(ecs_type_t){ .array = (ecs_id_t[]){id}, .count = 1 }); } static void flecs_id_mark_for_delete( ecs_world_t *world, ecs_id_record_t *idr, ecs_entity_t action, bool delete_id) { if (idr->flags & EcsIdMarkedForDelete) { return; } idr->flags |= EcsIdMarkedForDelete; flecs_marked_id_push(world, idr, action, delete_id); ecs_id_t id = idr->id; bool delete_target = flecs_id_is_delete_target(id, action); /* 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; } 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); } } } /* 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; } } /* 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); } } } } static bool flecs_on_delete_mark( ecs_world_t *world, ecs_id_t id, ecs_entity_t action, bool delete_id) { 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); } } if (action == EcsPanic) { /* This id is protected from deletion */ flecs_throw_invalid_delete(world, id); return false; } flecs_id_mark_for_delete(world, idr, action, delete_id); return true; } static void flecs_remove_from_table( ecs_world_t *world, ecs_table_t *table) { ecs_table_diff_t temp_diff; ecs_table_diff_builder_t diff = ECS_TABLE_DIFF_INIT; flecs_table_diff_builder_init(world, &diff); ecs_table_t *dst_table = table; /* To find the dst table, remove all ids that are marked for deletion */ int32_t i, t, count = ecs_vec_count(&world->store.marked_ids); ecs_marked_id_t *ids = ecs_vec_first(&world->store.marked_ids); const ecs_table_record_t *tr; for (i = 0; i < count; i ++) { const ecs_id_record_t *idr = ids[i].idr; if (!(tr = flecs_id_record_get_table(idr, dst_table))) { continue; } t = tr->index; do { ecs_id_t id = dst_table->type.array[t]; dst_table = flecs_table_traverse_remove( world, dst_table, &id, &temp_diff); flecs_table_diff_build_append_table(world, &diff, &temp_diff); } while (dst_table->type.count && (t = ecs_search_offset( world, dst_table, t, idr->id, NULL)) != -1); } ecs_assert(dst_table != NULL, ECS_INTERNAL_ERROR, NULL); if (!dst_table->type.count) { /* If this removes all components, clear table */ flecs_table_clear_entities(world, table); } else { /* Otherwise, merge table into dst_table */ if (dst_table != table) { int32_t table_count = ecs_table_count(table); if (diff.removed.count && table_count) { ecs_log_push_3(); ecs_table_diff_t td; flecs_table_diff_build_noalloc(&diff, &td); flecs_notify_on_remove(world, table, NULL, 0, table_count, &td.removed); ecs_log_pop_3(); } flecs_table_merge(world, dst_table, table, &dst_table->data, &table->data); } } flecs_table_diff_builder_fini(world, &diff); } static bool flecs_on_delete_clear_tables( ecs_world_t *world) { /* Iterate in reverse order so that DAGs get deleted bottom to top */ int32_t i, last = ecs_vec_count(&world->store.marked_ids), first = 0; ecs_marked_id_t *ids = ecs_vec_first(&world->store.marked_ids); 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 ((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 */ if (world->stages[0].defer) { flecs_defer_end(world, &world->stages[0]); flecs_defer_begin(world, &world->stages[0]); } /* User code (from triggers) could have enqueued more ids to delete, * reobtain the array in case it got reallocated */ ids = ecs_vec_first(&world->store.marked_ids); } /* Check if new ids were marked since we started */ int32_t new_last = ecs_vec_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); return true; } static bool flecs_on_delete_clear_ids( ecs_world_t *world) { int32_t i, count = ecs_vec_count(&world->store.marked_ids); ecs_marked_id_t *ids = ecs_vec_first(&world->store.marked_ids); int twice = 2; do { for (i = 0; i < count; i ++) { /* Release normal ids before wildcard ids */ if (ecs_id_is_wildcard(ids[i].id)) { if (twice == 2) { continue; } } else { if (twice == 1) { continue; } } ecs_id_record_t *idr = ids[i].idr; bool delete_id = ids[i].delete_id; 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. */ int32_t rc = flecs_id_record_release(world, idr); ecs_assert(rc >= 0, ECS_INTERNAL_ERROR, NULL); (void)rc; /* If rc is 0, the id was likely deleted by a nested delete_with call * made by an on_remove handler/OnRemove observer */ if (rc) { if (delete_id) { /* If id should be deleted, release initial claim. This happens when * a component, tag, or part of a pair is deleted. */ flecs_id_record_release(world, idr); } else { /* If id should not be deleted, unmark id record for deletion. This * happens when all instances *of* an id are deleted, for example * when calling ecs_remove_all or ecs_delete_with. */ idr->flags &= ~EcsIdMarkedForDelete; } } } } while (-- twice); return true; } static void flecs_on_delete( ecs_world_t *world, ecs_id_t id, ecs_entity_t action, bool delete_id) { /* 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_vec_count(&world->store.marked_ids); /* Make sure we're evaluating a consistent list of non-empty tables */ ecs_run_aperiodic(world, EcsAperiodicEmptyTables); /* Collect all ids that need to be deleted */ flecs_on_delete_mark(world, id, action, delete_id); /* Only perform cleanup if we're the first stack frame doing it */ if (!count && ecs_vec_count(&world->store.marked_ids)) { ecs_dbg_2("#[red]delete#[reset]"); ecs_log_push_2(); /* Empty tables with all the to be deleted ids */ flecs_on_delete_clear_tables(world); /* All marked tables are empty, ensure they're in the right list */ ecs_run_aperiodic(world, EcsAperiodicEmptyTables); /* Release remaining references to the ids */ flecs_on_delete_clear_ids(world); /* Verify deleted ids are no longer in use */ #ifdef FLECS_DEBUG ecs_marked_id_t *ids = ecs_vec_first(&world->store.marked_ids); int32_t i; count = ecs_vec_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); /* Ids are deleted, clear stack */ ecs_vec_clear(&world->store.marked_ids); ecs_log_pop_2(); } } void ecs_delete_with( ecs_world_t *world, ecs_id_t id) { flecs_journal_begin(world, EcsJournalDeleteWith, id, NULL, NULL); ecs_stage_t *stage = flecs_stage_from_world(&world); if (flecs_defer_on_delete_action(stage, id, EcsDelete)) { return; } flecs_on_delete(world, id, EcsDelete, false); flecs_defer_end(world, stage); flecs_journal_end(); } void ecs_remove_all( ecs_world_t *world, ecs_id_t id) { flecs_journal_begin(world, EcsJournalRemoveAll, id, NULL, NULL); ecs_stage_t *stage = flecs_stage_from_world(&world); if (flecs_defer_on_delete_action(stage, id, EcsRemove)) { return; } flecs_on_delete(world, id, EcsRemove, false); flecs_defer_end(world, stage); flecs_journal_end(); } 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); ecs_stage_t *stage = flecs_stage_from_world(&world); if (flecs_defer_delete(stage, entity)) { return; } ecs_record_t *r = flecs_entities_try(world, entity); if (r) { flecs_journal_begin(world, EcsJournalDelete, entity, NULL, NULL); ecs_flags32_t row_flags = ECS_RECORD_TO_ROW_FLAGS(r->row); ecs_table_t *table; if (row_flags) { if (row_flags & EcsEntityIsTarget) { flecs_on_delete(world, ecs_pair(EcsFlag, entity), 0, true); flecs_on_delete(world, ecs_pair(EcsWildcard, entity), 0, true); r->idr = NULL; } if (row_flags & EcsEntityIsId) { flecs_on_delete(world, entity, 0, true); flecs_on_delete(world, ecs_pair(entity, EcsWildcard), 0, true); } if (row_flags & EcsEntityIsTraversable) { table = r->table; if (table) { flecs_table_traversable_add(table, -1); } } /* Merge operations before deleting entity */ flecs_defer_end(world, stage); flecs_defer_begin(world, stage); } table = r->table; if (table) { ecs_table_diff_t diff = { .removed = table->type }; flecs_delete_entity(world, r, &diff); r->row = 0; r->table = NULL; } flecs_entities_remove(world, entity); flecs_journal_end(); } flecs_defer_end(world, stage); error: return; } 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_check(ecs_is_alive(world, entity), ECS_INVALID_PARAMETER, NULL); ecs_check(ecs_id_is_valid(world, id), ECS_INVALID_PARAMETER, NULL); flecs_add_id(world, entity, id); error: return; } 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_alive(world, entity), ECS_INVALID_PARAMETER, NULL); ecs_check(ecs_id_is_valid(world, id) || ecs_id_is_wildcard(id), ECS_INVALID_PARAMETER, NULL); flecs_remove_id(world, entity, id); error: return; } void ecs_override_id( ecs_world_t *world, ecs_entity_t entity, ecs_id_t id) { ecs_check(ecs_id_is_valid(world, id), ECS_INVALID_PARAMETER, NULL); ecs_add_id(world, entity, ECS_OVERRIDE | id); error: return; } 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_alive(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); } if (flecs_defer_clone(stage, dst, src, copy_value)) { return dst; } ecs_record_t *src_r = flecs_entities_get(world, src); ecs_assert(src_r != NULL, ECS_INTERNAL_ERROR, NULL); ecs_table_t *src_table = src_r->table; if (!src_table) { goto done; } ecs_type_t src_type = src_table->type; ecs_table_diff_t diff = { .added = src_type }; ecs_record_t *dst_r = flecs_entities_get(world, dst); flecs_new_entity(world, dst, dst_r, 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); int32_t i, count = src_table->column_count; for (i = 0; i < count; i ++) { ecs_type_t type = { .array = &src_table->data.columns[i].id, .count = 1 }; flecs_notify_on_set(world, src_table, row, 1, &type, true); } } done: flecs_defer_end(world, stage); return dst; error: return 0; } const void* ecs_get_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_alive(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); ecs_assert(r != NULL, ECS_INVALID_PARAMETER, 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; } const ecs_table_record_t *tr = flecs_id_record_get_table(idr, table); if (!tr) { return flecs_get_base_component(world, table, id, idr, 0); } else { ecs_check(tr->column != -1, ECS_NOT_A_COMPONENT, NULL); } int32_t row = ECS_RECORD_TO_ROW(r->row); return flecs_get_component_w_index(table, tr->column, row).ptr; error: return NULL; } void* ecs_get_mut_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_alive(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_cmd(stage)) { return flecs_defer_set( world, stage, EcsOpMut, entity, id, 0, NULL, true); } ecs_record_t *r = flecs_entities_get(world, entity); ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); void *result = flecs_get_mut(world, entity, id, r).ptr; ecs_check(result != NULL, ECS_INVALID_PARAMETER, NULL); flecs_defer_end(world, stage); return result; error: return NULL; } void* ecs_get_mut_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_alive(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); ecs_check(flecs_defer_cmd(stage), ECS_INVALID_PARAMETER, NULL); return flecs_defer_set( world, stage, EcsOpSet, entity, id, 0, NULL, true); error: return NULL; } 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); const ecs_world_t *world = ecs_get_world(stage); ecs_record_t *r = flecs_entities_get(world, entity); ecs_assert(r != NULL, ECS_INTERNAL_ERROR, 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_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); } ecs_check(count >= 0, ECS_ACCESS_VIOLATION, NULL); error: return; } ecs_record_t* ecs_write_begin( ecs_world_t *world, ecs_entity_t entity) { return flecs_access_begin(world, entity, true); } 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_entity_t entity) { return flecs_access_begin(world, entity, false); } void ecs_read_end( const ecs_record_t *r) { flecs_access_end(r, false); } ecs_entity_t ecs_record_get_entity( const ecs_record_t *record) { ecs_check(record != NULL, ECS_INVALID_PARAMETER, NULL); ecs_table_t *table = record->table; if (!table) { return 0; } return ecs_vec_get_t(&table->data.entities, ecs_entity_t, ECS_RECORD_TO_ROW(record->row))[0]; error: return 0; } 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 flecs_get_component(world, r->table, ECS_RECORD_TO_ROW(r->row), id); } bool ecs_record_has_id( ecs_world_t *stage, const ecs_record_t *r, ecs_id_t id) { const ecs_world_t *world = ecs_get_world(stage); if (r->table) { return ecs_table_has_id(world, r->table, id); } return false; } 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 flecs_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_alive(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, id); } return result; error: return (ecs_ref_t){0}; } void ecs_ref_update( const ecs_world_t *world, ecs_ref_t *ref) { 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_record_t *r = ref->record; ecs_table_t *table = r->table; if (!table) { return; } ecs_table_record_t *tr = ref->tr; if (!tr || tr->hdr.table != table) { tr = ref->tr = flecs_table_record_get(world, table, ref->id); if (!tr) { return; } ecs_assert(tr->hdr.table == r->table, ECS_INTERNAL_ERROR, NULL); } error: return; } 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(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); ecs_record_t *r = ref->record; ecs_table_t *table = r->table; if (!table) { return NULL; } int32_t row = ECS_RECORD_TO_ROW(r->row); ecs_check(row < ecs_table_count(table), ECS_INTERNAL_ERROR, NULL); ecs_table_record_t *tr = ref->tr; if (!tr || tr->hdr.table != table) { tr = ref->tr = flecs_table_record_get(world, table, id); if (!tr) { return NULL; } ecs_assert(tr->hdr.table == r->table, ECS_INTERNAL_ERROR, NULL); } int32_t column = tr->column; ecs_assert(column != -1, ECS_INTERNAL_ERROR, NULL); return flecs_get_component_w_index(table, column, row).ptr; error: return NULL; } 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_alive(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, "cannot emplace a component the entity already has"); ecs_stage_t *stage = flecs_stage_from_world(&world); if (flecs_defer_cmd(stage)) { return flecs_defer_set(world, stage, EcsOpEmplace, entity, id, 0, NULL, true); } ecs_record_t *r = flecs_entities_get(world, entity); flecs_add_id_w_record(world, entity, r, id, false /* Add without ctor */); flecs_defer_end(world, stage); void *ptr = flecs_get_component(world, r->table, ECS_RECORD_TO_ROW(r->row), id); ecs_check(ptr != NULL, ECS_INVALID_PARAMETER, NULL); return ptr; error: return NULL; } static void flecs_modified_id_if( ecs_world_t *world, ecs_entity_t entity, ecs_id_t id) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(ecs_is_alive(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(stage, entity, id)) { return; } ecs_record_t *r = flecs_entities_get(world, entity); ecs_table_t *table = r->table; if (!flecs_table_record_get(world, table, id)) { flecs_defer_end(world, stage); return; } 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; } 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_alive(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(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; } static void flecs_copy_ptr_w_id( ecs_world_t *world, ecs_stage_t *stage, ecs_entity_t entity, ecs_id_t id, size_t size, void *ptr) { if (flecs_defer_cmd(stage)) { flecs_defer_set(world, stage, EcsOpSet, entity, id, flecs_utosize(size), ptr, false); return; } ecs_record_t *r = flecs_entities_get(world, entity); flecs_component_ptr_t dst = flecs_get_mut(world, entity, id, r); const ecs_type_info_t *ti = dst.ti; ecs_check(dst.ptr != NULL, ECS_INVALID_PARAMETER, NULL); if (ptr) { ecs_copy_t copy = ti->hooks.copy; if (copy) { copy(dst.ptr, ptr, 1, ti); } else { ecs_os_memcpy(dst.ptr, ptr, flecs_utosize(size)); } } else { ecs_os_memset(dst.ptr, 0, size); } flecs_table_mark_dirty(world, r->table, id); ecs_table_t *table = r->table; if (table->flags & EcsTableHasOnSet || ti->hooks.on_set) { ecs_type_t ids = { .array = &id, .count = 1 }; flecs_notify_on_set( world, table, ECS_RECORD_TO_ROW(r->row), 1, &ids, true); } flecs_defer_end(world, stage); error: return; } static void flecs_move_ptr_w_id( ecs_world_t *world, ecs_stage_t *stage, ecs_entity_t entity, ecs_id_t id, size_t size, void *ptr, ecs_cmd_kind_t cmd_kind) { if (flecs_defer_cmd(stage)) { flecs_defer_set(world, stage, cmd_kind, entity, id, flecs_utosize(size), ptr, false); return; } ecs_record_t *r = flecs_entities_get(world, entity); flecs_component_ptr_t dst = flecs_get_mut(world, entity, id, r); ecs_check(dst.ptr != NULL, ECS_INVALID_PARAMETER, NULL); const ecs_type_info_t *ti = dst.ti; ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); ecs_move_t move; if (cmd_kind != EcsOpEmplace) { /* ctor will have happened by get_mut */ move = ti->hooks.move_dtor; } else { move = ti->hooks.ctor_move_dtor; } if (move) { move(dst.ptr, ptr, 1, ti); } else { ecs_os_memcpy(dst.ptr, ptr, flecs_utosize(size)); } flecs_table_mark_dirty(world, r->table, id); if (cmd_kind == EcsOpSet) { ecs_table_t *table = r->table; if (table->flags & EcsTableHasOnSet || ti->hooks.on_set) { ecs_type_t ids = { .array = &id, .count = 1 }; flecs_notify_on_set( world, table, ECS_RECORD_TO_ROW(r->row), 1, &ids, true); } } flecs_defer_end(world, stage); error: return; } 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_alive(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 (!entity) { entity = ecs_new_id(world); ecs_entity_t scope = stage->scope; if (scope) { ecs_add_pair(world, entity, EcsChildOf, scope); } } /* Safe to cast away const: function won't modify if move arg is false */ flecs_copy_ptr_w_id(world, stage, entity, id, size, ECS_CONST_CAST(void*, ptr)); return entity; error: return 0; } 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); ecs_stage_t *stage = flecs_stage_from_world(&world); if (flecs_defer_enable(stage, entity, id, enable)) { return; } else { /* Operations invoked by enable/disable should not be deferred */ stage->defer --; } ecs_record_t *r = flecs_entities_get(world, entity); ecs_entity_t bs_id = id | ECS_TOGGLE; ecs_table_t *table = r->table; int32_t index = -1; if (table) { index = ecs_table_get_type_index(world, table, bs_id); } if (index == -1) { ecs_add_id(world, entity, bs_id); ecs_enable_id(world, entity, id, enable); return; } ecs_assert(table->_ != NULL, ECS_INTERNAL_ERROR, NULL); 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->_->bs_columns[index]; ecs_assert(bs != NULL, ECS_INTERNAL_ERROR, NULL); flecs_bitset_set(bs, ECS_RECORD_TO_ROW(r->row), enable); error: return; } bool ecs_is_enabled_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_alive(world, entity), ECS_INVALID_PARAMETER, NULL); ecs_check(ecs_id_is_valid(world, id), ECS_INVALID_PARAMETER, NULL); /* Make sure we're not working with a stage */ world = ecs_get_world(world); ecs_record_t *r = flecs_entities_get(world, entity); ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); ecs_table_t *table = r->table; if (!table) { return false; } ecs_entity_t bs_id = id | ECS_TOGGLE; int32_t index = ecs_table_get_type_index(world, table, bs_id); 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); } ecs_assert(table->_ != NULL, ECS_INTERNAL_ERROR, NULL); index -= table->_->bs_offset; ecs_assert(index >= 0, ECS_INTERNAL_ERROR, NULL); ecs_bitset_t *bs = &table->_->bs_columns[index]; return flecs_bitset_get(bs, ECS_RECORD_TO_ROW(r->row)); error: return false; } 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_alive(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_record_t *r = flecs_entities_get_any(world, entity); ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); ecs_table_t *table = r->table; if (!table) { return false; } ecs_id_record_t *idr = flecs_id_record_get(world, id); int32_t column; if (idr) { ecs_table_record_t *tr = flecs_id_record_get_table(idr, table); if (tr) { return true; } } if (!(table->flags & (EcsTableHasUnion|EcsTableHasIsA))) { return false; } ecs_table_record_t *tr; column = ecs_search_relation(world, table, 0, id, EcsIsA, 0, 0, 0, &tr); if (column == -1) { return false; } 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_assert(table->_ != NULL, ECS_INTERNAL_ERROR, NULL); ecs_switch_t *sw = &table->_->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(world, id); } } return true; error: return false; } bool ecs_owns_id( const ecs_world_t *world, ecs_entity_t entity, ecs_id_t id) { return (ecs_search(world, ecs_get_table(world, entity), id, 0) != -1); } ecs_entity_t ecs_get_target( const ecs_world_t *world, ecs_entity_t entity, ecs_entity_t rel, int32_t index) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(ecs_is_alive(world, entity), ECS_INVALID_PARAMETER, NULL); ecs_check(rel != 0, ECS_INVALID_PARAMETER, NULL); world = ecs_get_world(world); ecs_record_t *r = flecs_entities_get(world, entity); ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); ecs_table_t *table = r->table; if (!table) { goto not_found; } ecs_id_t wc = ecs_pair(rel, EcsWildcard); ecs_id_record_t *idr = flecs_id_record_get(world, wc); const ecs_table_record_t *tr = NULL; if (idr) { tr = flecs_id_record_get_table(idr, table); } if (!tr) { if (table->flags & EcsTableHasUnion) { wc = ecs_pair(EcsUnion, rel); tr = flecs_table_record_get(world, table, wc); if (tr) { ecs_assert(table->_ != NULL, ECS_INTERNAL_ERROR, NULL); ecs_switch_t *sw = &table->_->sw_columns[ tr->index - table->_->sw_offset]; int32_t row = ECS_RECORD_TO_ROW(r->row); return flecs_switch_get(sw, row); } } if (!idr || !(idr->flags & EcsIdDontInherit)) { goto look_in_base; } else { return 0; } } else if (table->flags & EcsTableHasTarget) { EcsTarget *tf = ecs_table_get_id(world, table, ecs_pair_t(EcsTarget, rel), ECS_RECORD_TO_ROW(r->row)); if (tf) { return ecs_record_get_entity(tf->target); } } if (index >= tr->count) { index -= tr->count; goto look_in_base; } return ecs_pair_second(world, table->type.array[tr->index + index]); look_in_base: if (table->flags & EcsTableHasIsA) { ecs_table_record_t *tr_isa = flecs_id_record_get_table( world->idr_isa_wildcard, table); ecs_assert(tr_isa != NULL, ECS_INTERNAL_ERROR, NULL); ecs_id_t *ids = table->type.array; int32_t i = tr_isa->index, end = (i + tr_isa->count); for (; i < end; i ++) { ecs_id_t isa_pair = ids[i]; ecs_entity_t base = ecs_pair_second(world, isa_pair); ecs_assert(base != 0, ECS_INTERNAL_ERROR, NULL); ecs_entity_t t = ecs_get_target(world, base, rel, index); if (t) { return t; } } } not_found: error: return 0; } ecs_entity_t ecs_get_parent( const ecs_world_t *world, ecs_entity_t entity) { return ecs_get_target(world, entity, EcsChildOf, 0); } 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_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(ecs_is_alive(world, entity), ECS_INVALID_PARAMETER, NULL); if (!id) { return ecs_get_target(world, entity, rel, 0); } world = ecs_get_world(world); 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 { entity = 0; /* Don't return entity if id was not found */ if (table) { 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; } error: return 0; } int32_t ecs_get_depth( const ecs_world_t *world, ecs_entity_t entity, ecs_entity_t rel) { ecs_check(ecs_is_valid(world, rel), ECS_INVALID_PARAMETER, NULL); ecs_check(ecs_has_id(world, rel, EcsAcyclic), ECS_INVALID_PARAMETER, NULL); ecs_table_t *table = ecs_get_table(world, entity); if (table) { return ecs_table_get_depth(world, table, rel); } return 0; error: return -1; } static ecs_entity_t flecs_id_for_depth( ecs_world_t *world, int32_t depth) { ecs_vec_t *depth_ids = &world->store.depth_ids; int32_t i, count = ecs_vec_count(depth_ids); for (i = count; i <= depth; i ++) { ecs_entity_t *el = ecs_vec_append_t( &world->allocator, depth_ids, ecs_entity_t); el[0] = ecs_new_w_pair(world, EcsChildOf, EcsFlecsInternals); ecs_map_val_t *v = ecs_map_ensure(&world->store.entity_to_depth, el[0]); v[0] = flecs_ito(uint64_t, i); } return ecs_vec_get_t(&world->store.depth_ids, ecs_entity_t, depth)[0]; } static int32_t flecs_depth_for_id( ecs_world_t *world, ecs_entity_t id) { ecs_map_val_t *v = ecs_map_get(&world->store.entity_to_depth, id); ecs_assert(v != NULL, ECS_INTERNAL_ERROR, NULL); return flecs_uto(int32_t, v[0]); } static int32_t flecs_depth_for_flat_table( ecs_world_t *world, ecs_table_t *table) { ecs_assert(table->flags & EcsTableHasTarget, ECS_INTERNAL_ERROR, NULL); ecs_id_t id; int32_t col = ecs_search(world, table, ecs_pair(EcsFlatten, EcsWildcard), &id); ecs_assert(col != -1, ECS_INTERNAL_ERROR, NULL); (void)col; return flecs_depth_for_id(world, ECS_PAIR_SECOND(id)); } static void flecs_flatten( ecs_world_t *world, ecs_entity_t root, ecs_id_t pair, int32_t depth, const ecs_flatten_desc_t *desc) { ecs_id_record_t *idr = flecs_id_record_get(world, pair); if (!idr) { return; } ecs_entity_t depth_id = flecs_id_for_depth(world, depth); ecs_id_t root_pair = ecs_pair(EcsChildOf, root); ecs_id_t tgt_pair = ecs_pair_t(EcsTarget, EcsChildOf); ecs_id_t depth_pair = ecs_pair(EcsFlatten, depth_id); ecs_id_t name_pair = ecs_pair_t(EcsIdentifier, EcsName); ecs_entity_t rel = ECS_PAIR_FIRST(pair); 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))) { ecs_table_t *table = tr->hdr.table; int32_t i, count = ecs_table_count(table); bool has_tgt = table->flags & EcsTableHasTarget; flecs_emit_propagate_invalidate(world, table, 0, count); ecs_entity_t *entities = table->data.entities.array; for (i = 0; i < count; i ++) { ecs_record_t *record = flecs_entities_get(world, entities[i]); ecs_flags32_t flags = ECS_RECORD_TO_ROW_FLAGS(record->row); if (flags & EcsEntityIsTarget) { flecs_flatten(world, root, ecs_pair(rel, entities[i]), depth + 1, desc); } } ecs_table_diff_t tmpdiff; ecs_table_diff_builder_t diff = ECS_TABLE_DIFF_INIT; flecs_table_diff_builder_init(world, &diff); ecs_table_t *dst; dst = flecs_table_traverse_add(world, table, &root_pair, &tmpdiff); flecs_table_diff_build_append_table(world, &diff, &tmpdiff); dst = flecs_table_traverse_add(world, dst, &tgt_pair, &tmpdiff); flecs_table_diff_build_append_table(world, &diff, &tmpdiff); if (!desc->lose_depth) { if (!has_tgt) { dst = flecs_table_traverse_add(world, dst, &depth_pair, &tmpdiff); flecs_table_diff_build_append_table(world, &diff, &tmpdiff); } else { int32_t cur_depth = flecs_depth_for_flat_table(world, table); cur_depth += depth; ecs_entity_t e_depth = flecs_id_for_depth(world, cur_depth); ecs_id_t p_depth = ecs_pair(EcsFlatten, e_depth); dst = flecs_table_traverse_add(world, dst, &p_depth, &tmpdiff); flecs_table_diff_build_append_table(world, &diff, &tmpdiff); } } if (!desc->keep_names) { dst = flecs_table_traverse_remove(world, dst, &name_pair, &tmpdiff); flecs_table_diff_build_append_table(world, &diff, &tmpdiff); } int32_t dst_count = ecs_table_count(dst); ecs_table_diff_t td; flecs_table_diff_build_noalloc(&diff, &td); flecs_notify_on_remove(world, table, NULL, 0, count, &td.removed); flecs_table_merge(world, dst, table, &dst->data, &table->data); flecs_notify_on_add(world, dst, NULL, dst_count, count, &td.added, 0); flecs_table_diff_builder_fini(world, &diff); EcsTarget *fh = ecs_table_get_id(world, dst, tgt_pair, 0); ecs_assert(fh != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(count != 0, ECS_INTERNAL_ERROR, NULL); int32_t remain = count; for (i = dst_count; i < (dst_count + count); i ++) { if (!has_tgt) { fh[i].target = flecs_entities_get_any(world, ECS_PAIR_SECOND(pair)); fh[i].count = remain; remain --; } ecs_assert(fh[i].target != NULL, ECS_INTERNAL_ERROR, NULL); } } } ecs_delete_with(world, pair); flecs_id_record_release(world, idr); } void ecs_flatten( ecs_world_t *world, ecs_id_t pair, const ecs_flatten_desc_t *desc) { ecs_poly_assert(world, ecs_world_t); ecs_entity_t rel = ECS_PAIR_FIRST(pair); ecs_entity_t root = ecs_pair_second(world, pair); ecs_flatten_desc_t private_desc = {0}; if (desc) { private_desc = *desc; } ecs_run_aperiodic(world, 0); ecs_defer_begin(world); ecs_id_record_t *idr = flecs_id_record_get(world, pair); 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))) { ecs_table_t *table = tr->hdr.table; if (!table->_->traversable_count) { continue; } if (table->flags & EcsTableIsPrefab) { continue; } int32_t i, count = ecs_table_count(table); ecs_entity_t *entities = table->data.entities.array; for (i = 0; i < count; i ++) { ecs_record_t *record = flecs_entities_get(world, entities[i]); ecs_flags32_t flags = ECS_RECORD_TO_ROW_FLAGS(record->row); if (flags & EcsEntityIsTarget) { flecs_flatten(world, root, ecs_pair(rel, entities[i]), 1, &private_desc); } } } } ecs_defer_end(world); } static const char* flecs_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_alive(world, entity), ECS_INVALID_PARAMETER, NULL); const EcsIdentifier *ptr = ecs_get_pair( world, entity, EcsIdentifier, tag); if (ptr) { return ptr->value; } else { return NULL; } error: return NULL; } const char* ecs_get_name( const ecs_world_t *world, ecs_entity_t entity) { return flecs_get_identifier(world, entity, EcsName); } const char* ecs_get_symbol( const ecs_world_t *world, ecs_entity_t entity) { world = ecs_get_world(world); if (ecs_owns_pair(world, entity, ecs_id(EcsIdentifier), EcsSymbol)) { return flecs_get_identifier(world, entity, EcsSymbol); } else { return NULL; } } static ecs_entity_t flecs_set_identifier( ecs_world_t *world, ecs_stage_t *stage, 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); if (tag == EcsName) { /* Insert command after get_mut, but before the name is potentially * freed. Even though the name is a const char*, it is possible that the * application passed in the existing name of the entity which could * still cause it to be freed. */ flecs_defer_path(stage, 0, entity, name); } ecs_os_strset(&ptr->value, name); ecs_modified_pair(world, entity, ecs_id(EcsIdentifier), tag); return entity; error: return 0; } ecs_entity_t ecs_set_name( ecs_world_t *world, ecs_entity_t entity, const char *name) { if (!entity) { return ecs_entity(world, { .name = name }); } ecs_stage_t *stage = flecs_stage_from_world(&world); flecs_set_identifier(world, stage, entity, EcsName, name); return entity; } ecs_entity_t ecs_set_symbol( ecs_world_t *world, ecs_entity_t entity, const char *name) { return flecs_set_identifier(world, NULL, entity, EcsSymbol, name); } void ecs_set_alias( ecs_world_t *world, ecs_entity_t entity, const char *name) { flecs_set_identifier(world, NULL, entity, EcsAlias, name); } ecs_id_t ecs_make_pair( ecs_entity_t relationship, ecs_entity_t target) { ecs_assert(!ECS_IS_PAIR(relationship) && !ECS_IS_PAIR(target), ECS_INVALID_PARAMETER, "cannot create nested pairs"); return ecs_pair(relationship, target); } bool ecs_is_valid( const ecs_world_t *world, ecs_entity_t entity) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); /* 0 is not a valid entity id */ if (!entity) { return false; } /* Entity identifiers should not contain flag bits */ if (entity & ECS_ID_FLAGS_MASK) { return false; } /* Entities should not contain data in dead zone bits */ if (entity & ~0xFF00FFFFFFFFFFFF) { return false; } /* 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; } ecs_id_t ecs_strip_generation( ecs_entity_t e) { /* If this is not a pair, erase the generation bits */ if (!(e & ECS_ID_FLAGS_MASK)) { e &= ~ECS_GENERATION_MASK; } return e; } 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); world = ecs_get_world(world); return flecs_entities_is_alive(world, entity); error: return false; } 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; } /* Make sure we're not working with a stage */ world = ecs_get_world(world); if (flecs_entities_is_alive(world, entity)) { return entity; } /* Make sure id does not have generation. This guards against accidentally * "upcasting" a not alive identifier to a alive one. */ if ((uint32_t)entity != entity) { return 0; } ecs_entity_t current = flecs_entities_get_generation(world, entity); if (!current || !flecs_entities_is_alive(world, current)) { return 0; } return current; error: return 0; } void ecs_ensure( ecs_world_t *world, ecs_entity_t entity) { ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); ecs_check(entity != 0, ECS_INVALID_PARAMETER, NULL); /* Const cast is safe, function checks for threading */ world = ECS_CONST_CAST(ecs_world_t*, ecs_get_world(world)); /* The entity index can be mutated while in staged/readonly mode, as long as * the world is not multithreaded. */ ecs_assert(!(world->flags & EcsWorldMultiThreaded), ECS_INVALID_OPERATION, NULL); /* Check if a version of the provided id is alive */ ecs_entity_t any = ecs_get_alive(world, (uint32_t)entity); if (any == entity) { /* If alive and equal to the argument, there's nothing left to do */ return; } /* 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; } void ecs_ensure_id( ecs_world_t *world, ecs_id_t id) { ecs_poly_assert(world, ecs_world_t); if (ECS_HAS_ID_FLAG(id, PAIR)) { ecs_entity_t r = ECS_PAIR_FIRST(id); ecs_entity_t o = ECS_PAIR_SECOND(id); ecs_check(r != 0, ECS_INVALID_PARAMETER, NULL); ecs_check(o != 0, ECS_INVALID_PARAMETER, NULL); if (flecs_entities_get_generation(world, r) == 0) { ecs_assert(!ecs_exists(world, r), ECS_INVALID_PARAMETER, "first element of pair is not alive"); flecs_entities_ensure(world, r); } if (flecs_entities_get_generation(world, o) == 0) { ecs_assert(!ecs_exists(world, o), ECS_INVALID_PARAMETER, "second element of pair is not alive"); flecs_entities_ensure(world, o); } } else { flecs_entities_ensure(world, id & ECS_COMPONENT_MASK); } error: return; } bool ecs_exists( 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); world = ecs_get_world(world); return flecs_entities_exists(world, entity); error: return false; } ecs_table_t* ecs_get_table( const ecs_world_t *world, ecs_entity_t entity) { 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_assert(record != NULL, ECS_INTERNAL_ERROR, NULL); return record->table; error: return NULL; } 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; } return NULL; } 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); world = ecs_get_world(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; } } } } if (idr) { return idr->type_info; } else if (!(id & ECS_ID_FLAGS_MASK)) { return flecs_type_info_get(world, id); } error: return NULL; } ecs_entity_t ecs_get_typeid( const 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; } error: return 0; } bool ecs_id_is_tag( const 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; } } return false; } bool ecs_id_is_union( const ecs_world_t *world, ecs_id_t id) { if (!ECS_IS_PAIR(id)) { return false; } else if (ECS_PAIR_FIRST(id) == EcsUnion) { return true; } else { ecs_entity_t first = ecs_pair_first(world, id); if (ecs_has_id(world, first, EcsUnion)) { return true; } } return false; } int32_t ecs_count_id( const ecs_world_t *world, ecs_entity_t id) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); if (!id) { return 0; } int32_t count = 0; ecs_iter_t it = ecs_term_iter(world, &(ecs_term_t) { .id = id, .src.flags = EcsSelf, .flags = EcsTermMatchDisabled|EcsTermMatchPrefab }); it.flags |= EcsIterNoData; it.flags |= EcsIterEvalTables; while (ecs_term_next(&it)) { count += it.count; } return count; error: return 0; } 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); } } } 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); error: return false; } bool ecs_defer_end( ecs_world_t *world) { 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 ecs_defer_suspend( ecs_world_t *world) { 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 > 0, ECS_INVALID_OPERATION, NULL); stage->defer = -stage->defer; error: return; } void ecs_defer_resume( ecs_world_t *world) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_stage_t *stage = flecs_stage_from_world(&world); ecs_check(stage->defer < 0, ECS_INVALID_OPERATION, NULL); stage->defer = -stage->defer; 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, AND)) { return "AND"; } 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_check(world != NULL, 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, '|'); } if (ECS_HAS_ID_FLAG(id, PAIR)) { ecs_entity_t rel = ECS_PAIR_FIRST(id); ecs_entity_t obj = ECS_PAIR_SECOND(id); ecs_entity_t e; if ((e = ecs_get_alive(world, rel))) { rel = e; } if ((e = ecs_get_alive(world, obj))) { obj = e; } 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; } char* ecs_id_str( const ecs_world_t *world, 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]; if (i) { ecs_strbuf_appendch(buf, ','); ecs_strbuf_appendch(buf, ' '); } if (id == 1) { ecs_strbuf_appendlit(buf, "Component"); } else { ecs_id_str_buf(world, id, buf); } } } char* ecs_type_str( const ecs_world_t *world, const ecs_type_t *type) { if (!type) { return ecs_os_strdup(""); } 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 { return NULL; } } char* ecs_entity_str( const ecs_world_t *world, ecs_entity_t entity) { ecs_strbuf_t buf = ECS_STRBUF_INIT; ecs_check(ecs_is_alive(world, entity), ECS_INVALID_PARAMETER, NULL); ecs_get_path_w_sep_buf(world, 0, entity, 0, "", &buf); ecs_strbuf_appendlit(&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); error: return NULL; } static void flecs_flush_bulk_new( ecs_world_t *world, ecs_cmd_t *cmd) { ecs_entity_t *entities = cmd->is._n.entities; if (cmd->id) { int i, count = cmd->is._n.count; for (i = 0; i < count; i ++) { flecs_entities_ensure(world, entities[i]); flecs_add_id(world, entities[i], cmd->id); } } ecs_os_free(entities); } static void flecs_dtor_value( ecs_world_t *world, ecs_id_t id, void *value, int32_t count) { 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); } } } static void flecs_discard_cmd( ecs_world_t *world, ecs_cmd_t *cmd) { if (cmd->kind != EcsOpBulkNew) { void *value = cmd->is._1.value; if (value) { flecs_dtor_value(world, cmd->id, value, 1); flecs_stack_free(value, cmd->is._1.size); } } else { ecs_os_free(cmd->is._n.entities); } } static bool flecs_remove_invalid( ecs_world_t *world, ecs_id_t id, ecs_id_t *id_out) { if (ECS_HAS_ID_FLAG(id, PAIR)) { ecs_entity_t rel = ECS_PAIR_FIRST(id); if (!flecs_entities_is_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(id); if (!flecs_entities_is_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_TARGET(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_entities_is_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; } } return true; } static void flecs_cmd_batch_for_entity( ecs_world_t *world, ecs_table_diff_builder_t *diff, ecs_entity_t entity, ecs_cmd_t *cmds, int32_t start) { ecs_record_t *r = flecs_entities_get(world, entity); ecs_table_t *table = NULL; if (r) { table = r->table; } world->info.cmd.batched_entity_count ++; ecs_table_t *start_table = table; ecs_cmd_t *cmd; int32_t next_for_entity; ecs_table_diff_t table_diff; /* Keep track of diff for observers/hooks */ int32_t cur = start; ecs_id_t id; bool has_set = false; do { cmd = &cmds[cur]; id = cmd->id; next_for_entity = cmd->next_for_entity; if (next_for_entity < 0) { /* First command for an entity has a negative index, flip sign */ next_for_entity *= -1; } /* Check if added id is still valid (like is the parent of a ChildOf * pair still alive), if not run cleanup actions for entity */ if (id) { if (flecs_remove_invalid(world, id, &id)) { if (!id) { /* Entity should remain alive but id should not be added */ cmd->kind = EcsOpSkip; continue; } /* Entity should remain alive and id is still valid */ } else { /* Id was no longer valid and had a Delete policy */ cmd->kind = EcsOpSkip; ecs_delete(world, entity); flecs_table_diff_builder_clear(diff); return; } } ecs_cmd_kind_t kind = cmd->kind; switch(kind) { case EcsOpAddModified: /* Add is batched, but keep Modified */ cmd->kind = EcsOpModified; /* fall through */ case EcsOpAdd: table = flecs_find_table_add(world, table, id, diff); world->info.cmd.batched_command_count ++; break; case EcsOpModified: if (start_table) { /* If a modified was inserted for an existing component, the value * of the component could have been changed. If this is the case, * call on_set hooks before the OnAdd/OnRemove observers are invoked * when moving the entity to a different table. * This ensures that if OnAdd/OnRemove observers access the modified * component value, the on_set hook has had the opportunity to * run first to set any computed values of the component. */ int32_t row = ECS_RECORD_TO_ROW(r->row); flecs_component_ptr_t ptr = flecs_get_component_ptr( world, start_table, row, cmd->id); if (ptr.ptr) { const ecs_type_info_t *ti = ptr.ti; ecs_iter_action_t on_set; if ((on_set = ti->hooks.on_set)) { flecs_invoke_hook(world, start_table, 1, row, &entity, ptr.ptr, cmd->id, ptr.ti, EcsOnSet, on_set); } } } break; case EcsOpSet: case EcsOpMut: table = flecs_find_table_add(world, table, id, diff); world->info.cmd.batched_command_count ++; has_set = true; break; case EcsOpEmplace: /* Don't add for emplace, as this requires a special call to ensure * the constructor is not invoked for the component */ break; case EcsOpRemove: table = flecs_find_table_remove(world, table, id, diff); world->info.cmd.batched_command_count ++; break; case EcsOpClear: if (table) { ecs_id_t *ids = ecs_vec_grow_t(&world->allocator, &diff->removed, ecs_id_t, table->type.count); ecs_os_memcpy_n(ids, table->type.array, ecs_id_t, table->type.count); } table = &world->store.root; world->info.cmd.batched_command_count ++; break; case EcsOpClone: case EcsOpBulkNew: case EcsOpPath: case EcsOpDelete: case EcsOpOnDeleteAction: case EcsOpEnable: case EcsOpDisable: case EcsOpSkip: break; } /* Add, remove and clear operations can be skipped since they have no * side effects besides adding/removing components */ if (kind == EcsOpAdd || kind == EcsOpRemove || kind == EcsOpClear) { cmd->kind = EcsOpSkip; } } while ((cur = next_for_entity)); /* Move entity to destination table in single operation */ flecs_table_diff_build_noalloc(diff, &table_diff); flecs_defer_begin(world, &world->stages[0]); flecs_commit(world, entity, r, table, &table_diff, true, 0); flecs_defer_end(world, &world->stages[0]); flecs_table_diff_builder_clear(diff); /* If the batch contains set commands, copy the component value from the * temporary command storage to the actual component storage before OnSet * observers are invoked. This ensures that for multi-component OnSet * observers all components are assigned a valid value before the observer * is invoked. * This only happens for entities that didn't have the assigned component * yet, as for entities that did have the component already the value will * have been assigned directly to the component storage. */ if (has_set) { cur = start; do { cmd = &cmds[cur]; next_for_entity = cmd->next_for_entity; if (next_for_entity < 0) { next_for_entity *= -1; } switch(cmd->kind) { case EcsOpSet: case EcsOpMut: { flecs_component_ptr_t ptr = {0}; if (r->table) { ptr = flecs_get_component_ptr(world, r->table, ECS_RECORD_TO_ROW(r->row), cmd->id); } /* It's possible that even though the component was set, the * command queue also contained a remove command, so before we * do anything ensure the entity actually has the component. */ if (ptr.ptr) { const ecs_type_info_t *ti = ptr.ti; ecs_move_t move = ti->hooks.move; if (move) { move(ptr.ptr, cmd->is._1.value, 1, ti); } else { ecs_os_memcpy(ptr.ptr, cmd->is._1.value, ti->size); } if (cmd->kind == EcsOpSet) { /* A set operation is add + copy + modified. We just did * the add the copy, so the only thing that's left is a * modified command, which will call the OnSet * observers. */ cmd->kind = EcsOpModified; } else { /* If this was a get_mut, nothing's left to be done */ cmd->kind = EcsOpSkip; } } else { /* The entity no longer has the component which means that * there was a remove command for the component in the * command queue. In that case skip the command. */ cmd->kind = EcsOpSkip; } break; } case EcsOpClone: case EcsOpBulkNew: case EcsOpAdd: case EcsOpRemove: case EcsOpEmplace: case EcsOpModified: case EcsOpAddModified: case EcsOpPath: case EcsOpDelete: case EcsOpClear: case EcsOpOnDeleteAction: case EcsOpEnable: case EcsOpDisable: case EcsOpSkip: break; } } while ((cur = next_for_entity)); } } /* Leave safe section. Run all deferred commands. */ bool flecs_defer_end( ecs_world_t *world, ecs_stage_t *stage) { ecs_poly_assert(world, ecs_world_t); ecs_poly_assert(stage, ecs_stage_t); if (stage->defer < 0) { /* Defer suspending makes it possible to do operations on the storage * without flushing the commands in the queue */ return false; } ecs_assert(stage->defer > 0, ECS_INTERNAL_ERROR, NULL); if (!--stage->defer) { /* Test whether we're flushing to another queue or whether we're * flushing to the storage */ bool merge_to_world = false; if (ecs_poly_is(world, ecs_world_t)) { merge_to_world = world->stages[0].defer == 0; } ecs_stage_t *dst_stage = flecs_stage_from_world(&world); ecs_commands_t *commands = stage->cmd; ecs_vec_t *queue = &commands->queue; if (ecs_vec_count(queue)) { ecs_cmd_t *cmds = ecs_vec_first(queue); int32_t i, count = ecs_vec_count(queue); ecs_table_diff_builder_t diff; flecs_table_diff_builder_init(world, &diff); flecs_commands_push(stage); for (i = 0; i < count; i ++) { ecs_cmd_t *cmd = &cmds[i]; ecs_entity_t e = cmd->entity; bool is_alive = flecs_entities_is_alive(world, e); /* A negative index indicates the first command for an entity */ if (merge_to_world && (cmd->next_for_entity < 0)) { /* Batch commands for entity to limit archetype moves */ if (is_alive) { flecs_cmd_batch_for_entity(world, &diff, e, cmds, i); } else { world->info.cmd.discard_count ++; } } /* Invalidate entry */ if (cmd->entry) { cmd->entry->first = -1; } /* 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. */ ecs_cmd_kind_t kind = cmd->kind; if ((kind != EcsOpPath) && ((kind == EcsOpSkip) || (e && !is_alive))) { world->info.cmd.discard_count ++; flecs_discard_cmd(world, cmd); continue; } ecs_id_t id = cmd->id; switch(kind) { case EcsOpAdd: ecs_assert(id != 0, ECS_INTERNAL_ERROR, NULL); if (flecs_remove_invalid(world, id, &id)) { if (id) { world->info.cmd.add_count ++; flecs_add_id(world, e, id); } else { world->info.cmd.discard_count ++; } } else { world->info.cmd.discard_count ++; ecs_delete(world, e); } break; case EcsOpRemove: flecs_remove_id(world, e, id); world->info.cmd.remove_count ++; break; case EcsOpClone: ecs_clone(world, e, id, cmd->is._1.clone_value); break; case EcsOpSet: flecs_move_ptr_w_id(world, dst_stage, e, cmd->id, flecs_itosize(cmd->is._1.size), cmd->is._1.value, kind); world->info.cmd.set_count ++; break; case EcsOpEmplace: if (merge_to_world) { ecs_emplace_id(world, e, id); } flecs_move_ptr_w_id(world, dst_stage, e, cmd->id, flecs_itosize(cmd->is._1.size), cmd->is._1.value, kind); world->info.cmd.get_mut_count ++; break; case EcsOpMut: flecs_move_ptr_w_id(world, dst_stage, e, cmd->id, flecs_itosize(cmd->is._1.size), cmd->is._1.value, kind); world->info.cmd.get_mut_count ++; break; case EcsOpModified: flecs_modified_id_if(world, e, id); world->info.cmd.modified_count ++; break; case EcsOpAddModified: flecs_add_id(world, e, id); flecs_modified_id_if(world, e, id); world->info.cmd.add_count ++; world->info.cmd.modified_count ++; break; case EcsOpDelete: { ecs_delete(world, e); world->info.cmd.delete_count ++; break; } case EcsOpClear: ecs_clear(world, e); world->info.cmd.clear_count ++; break; case EcsOpOnDeleteAction: ecs_defer_begin(world); flecs_on_delete(world, id, e, false); ecs_defer_end(world); world->info.cmd.other_count ++; break; case EcsOpEnable: ecs_enable_id(world, e, id, true); world->info.cmd.other_count ++; break; case EcsOpDisable: ecs_enable_id(world, e, id, false); world->info.cmd.other_count ++; break; case EcsOpBulkNew: flecs_flush_bulk_new(world, cmd); world->info.cmd.other_count ++; continue; case EcsOpPath: ecs_ensure(world, e); if (cmd->id) { ecs_add_pair(world, e, EcsChildOf, cmd->id); } ecs_set_name(world, e, cmd->is._1.value); ecs_os_free(cmd->is._1.value); cmd->is._1.value = NULL; break; case EcsOpSkip: break; } if (cmd->is._1.value) { flecs_stack_free(cmd->is._1.value, cmd->is._1.size); } } flecs_stack_reset(&commands->stack); ecs_vec_clear(queue); flecs_commands_pop(stage); flecs_table_diff_builder_fini(world, &diff); } return true; } return false; } /* Delete operations from queue without executing them. */ bool flecs_defer_purge( ecs_world_t *world, ecs_stage_t *stage) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(stage != NULL, ECS_INVALID_PARAMETER, NULL); if (!--stage->defer) { ecs_vec_t commands = stage->cmd->queue; if (ecs_vec_count(&commands)) { ecs_cmd_t *cmds = ecs_vec_first(&commands); int32_t i, count = ecs_vec_count(&commands); for (i = 0; i < count; i ++) { flecs_discard_cmd(world, &cmds[i]); } ecs_vec_fini_t(&stage->allocator, &stage->cmd->queue, ecs_cmd_t); ecs_vec_clear(&commands); flecs_stack_reset(&stage->cmd->stack); flecs_sparse_clear(&stage->cmd->entries); } return true; } error: return false; }