/** * @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" #ifdef FLECS_QUERY_DSL #include "addons/query_dsl/query_dsl.h" #endif static flecs_component_ptr_t flecs_table_get_component( ecs_table_t *table, int32_t column_index, int32_t row) { ecs_assert(column_index < table->column_count, ECS_INTERNAL_ERROR, NULL); ecs_column_t *column = &table->data.columns[column_index]; return (flecs_component_ptr_t){ .ti = column->ti, .ptr = ECS_ELEM(column->data, column->ti->size, row) }; } flecs_component_ptr_t flecs_get_component_ptr( const ecs_world_t *world, ecs_table_t *table, int32_t row, ecs_component_record_t *cr) { ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); if (!cr) { return (flecs_component_ptr_t){0}; } if (cr->flags & (EcsIdSparse|EcsIdDontFragment)) { ecs_entity_t entity = ecs_table_entities(table)[row]; return (flecs_component_ptr_t){ .ti = cr->type_info, .ptr = flecs_component_sparse_get(world, cr, table, entity) }; } const ecs_table_record_t *tr = flecs_component_get_table(cr, table); if (!tr || (tr->column == -1)) { return (flecs_component_ptr_t){0}; } return flecs_table_get_component(table, tr->column, row); } void* flecs_get_component( const ecs_world_t *world, ecs_table_t *table, int32_t row, ecs_component_record_t *cr) { return flecs_get_component_ptr(world, table, row, cr).ptr; } void* flecs_get_base_component( const ecs_world_t *world, ecs_table_t *table, ecs_id_t component, ecs_component_record_t *cr, int32_t recur_depth) { ecs_check(recur_depth < ECS_MAX_RECURSION, ECS_INVALID_OPERATION, "cycle detected in IsA relationship"); /* Table (and thus entity) does not have component, look for base */ if (!(table->flags & EcsTableHasIsA)) { return NULL; } if (!(cr->flags & EcsIdOnInstantiateInherit)) { return NULL; } /* Exclude Name */ if (component == 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 */ const ecs_table_record_t *tr_isa = flecs_component_get_table( world->cr_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; ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); const ecs_table_record_t *tr = flecs_component_get_table(cr, table); if (!tr) { if (cr->flags & EcsIdDontFragment) { ptr = flecs_component_sparse_get(world, cr, table, base); } if (!ptr) { ptr = flecs_get_base_component(world, table, component, cr, recur_depth + 1); } } else { if (cr->flags & EcsIdSparse) { return flecs_component_sparse_get(world, cr, table, base); } else { int32_t row = ECS_RECORD_TO_ROW(r->row); return flecs_table_get_component(table, tr->column, row).ptr; } } } while (!ptr && (i < end)); return ptr; error: return NULL; } ecs_entity_t flecs_new_id( const ecs_world_t *world) { flecs_poly_assert(world, ecs_world_t); flecs_check_exclusive_world_access_write(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*, world); ecs_assert(!(unsafe_world->flags & EcsWorldMultiThreaded), ECS_INVALID_OPERATION, "cannot create entities in multithreaded mode"); ecs_entity_t 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); return entity; } 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_table_count(table) > row, ECS_INTERNAL_ERROR, NULL); flecs_notify_on_add(world, table, NULL, row, 1, diff, evt_flags, ctor, true); ecs_assert(table == record->table, ECS_INTERNAL_ERROR, NULL); 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_table_count(src_table) > src_row, ECS_INTERNAL_ERROR, NULL); ecs_assert(record != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(record == flecs_entities_get(world, entity), ECS_INTERNAL_ERROR, NULL); ecs_assert(record->table == src_table, 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); /* Copy entity & components from src_table to dst_table */ flecs_table_move(world, entity, entity, dst_table, dst_row, src_table, src_row, ctor); ecs_assert(record->table == src_table, ECS_INTERNAL_ERROR, NULL); /* Update entity index & delete old data after running remove actions */ record->table = dst_table; record->row = ECS_ROW_TO_RECORD(dst_row, record->row & ECS_ROW_FLAGS_MASK); flecs_table_delete(world, src_table, src_row, false); flecs_notify_on_add(world, dst_table, src_table, dst_row, 1, diff, evt_flags, ctor, true); ecs_assert(record->table == dst_table, ECS_INTERNAL_ERROR, NULL); } 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_assert(record != NULL, ECS_INTERNAL_ERROR, NULL); ecs_table_t *src_table = record->table; int is_trav = (record->row & EcsEntityIsTraversable) != 0; ecs_assert(src_table != NULL, ECS_INTERNAL_ERROR, NULL); 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. */ ecs_flags32_t non_fragment_flags = src_table->flags & EcsTableHasDontFragment; if (non_fragment_flags) { diff->added_flags |= non_fragment_flags; diff->removed_flags |= non_fragment_flags; flecs_notify_on_add(world, src_table, src_table, ECS_RECORD_TO_ROW(record->row), 1, diff, evt_flags, construct, true); flecs_notify_on_remove(world, src_table, src_table, ECS_RECORD_TO_ROW(record->row), 1, diff); } flecs_journal_end(); return; } ecs_os_perf_trace_push("flecs.commit"); ecs_assert(dst_table != NULL, ECS_INTERNAL_ERROR, NULL); flecs_table_traversable_add(dst_table, is_trav); flecs_move_entity(world, entity, record, dst_table, diff, construct, evt_flags); flecs_table_traversable_add(src_table, -is_trav); /* 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->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); } ecs_os_perf_trace_pop("flecs.commit"); error: flecs_journal_end(); return; } 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); } ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); flecs_defer_begin(world, world->stages[0]); int32_t row = flecs_table_appendn(world, table, count, entities); ecs_type_t type = table->type; if (!type.count && !component_data) { flecs_defer_end(world, world->stages[0]); 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; } flecs_notify_on_add(world, table, NULL, row, count, diff, (component_data == NULL) ? 0 : EcsEventNoOnSet, true, true); 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]; ecs_component_record_t *cr = flecs_components_get(world, id); ecs_assert(cr != NULL, ECS_INTERNAL_ERROR, NULL); const ecs_type_info_t *ti = cr->type_info; if (!ti) { ecs_assert(ti != NULL, ECS_INVALID_PARAMETER, "component '%s' passed to to bulk_new() at index %d is a " "tag/zero sized", flecs_errstr(ecs_id_str(world, id)), c_i); } int32_t size = ti->size; void *ptr; if (cr->flags & EcsIdSparse) { int32_t e; for (e = 0; e < count; e ++) { ptr = flecs_component_sparse_get( world, cr, table, entities[e]); ecs_copy_t copy; ecs_move_t move; if (is_move && (move = ti->hooks.move)) { move(ptr, src_ptr, 1, ti); } else if (!is_move && (copy = ti->hooks.copy)) { copy(ptr, src_ptr, 1, ti); } else { ecs_os_memcpy(ptr, src_ptr, size); } flecs_notify_on_set(world, table, row + e, id, true); src_ptr = ECS_OFFSET(src_ptr, size); } } else { const ecs_table_record_t *tr = flecs_component_get_table(cr, table); ecs_assert(tr != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(tr->column != -1, ECS_INVALID_PARAMETER, "component '%s' passed to bulk_new() at index %d is a " "tag/zero sized", flecs_errstr(ecs_id_str(world, id))); ecs_assert(tr->count == 1, ECS_INVALID_PARAMETER, "component passed to bulk_new() at index %d is " "invalid/a wildcard", flecs_errstr(ecs_id_str(world, id))); int32_t index = tr->column; ecs_column_t *column = &table->data.columns[index]; ecs_assert(size != 0, ECS_INTERNAL_ERROR, NULL); ptr = ECS_ELEM(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_id_t component = flecs_column_id(table, j); ecs_type_t set_type = { .array = &component, .count = 1 }; flecs_notify_on_set_ids(world, table, row, count, &set_type); } } 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 component, 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, &component, &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. */ } void flecs_add_id( ecs_world_t *world, ecs_entity_t entity, ecs_id_t component) { ecs_stage_t *stage = flecs_stage_from_world(&world); if (flecs_defer_add(stage, entity, component)) { 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, &component, &diff); flecs_commit(world, entity, r, dst_table, &diff, true, 0); flecs_defer_end(world, stage); } void flecs_remove_id( ecs_world_t *world, ecs_entity_t entity, ecs_id_t component) { ecs_stage_t *stage = flecs_stage_from_world(&world); if (flecs_defer_remove(stage, entity, component)) { 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, &component, &diff); flecs_commit(world, entity, r, dst_table, &diff, true, 0); flecs_defer_end(world, stage); } void flecs_add_ids( ecs_world_t *world, ecs_entity_t entity, ecs_id_t *ids, int32_t count) { ecs_record_t *r = flecs_entities_get(world, entity); ecs_table_diff_builder_t diff = ECS_TABLE_DIFF_INIT; flecs_table_diff_builder_init(world, &diff); ecs_table_t *table = ecs_get_table(world, entity); int32_t i; for (i = 0; i < count; i ++) { ecs_id_t component = ids[i]; table = flecs_find_table_add(world, table, component, &diff); } ecs_table_diff_t table_diff; flecs_table_diff_build_noalloc(&diff, &table_diff); flecs_commit(world, entity, r, table, &table_diff, true, 0); flecs_table_diff_builder_fini(world, &diff); } flecs_component_ptr_t flecs_ensure( ecs_world_t *world, ecs_entity_t entity, ecs_id_t component, ecs_record_t *r, ecs_size_t size) { flecs_component_ptr_t dst = {0}; flecs_poly_assert(world, ecs_world_t); ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); ecs_component_record_t *cr = NULL; ecs_table_t *table = r->table; ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); if (component < FLECS_HI_COMPONENT_ID) { int16_t column_index = table->component_map[component]; if (column_index > 0) { ecs_column_t *column = &table->data.columns[column_index - 1]; ecs_assert(column->ti->size == size, ECS_INTERNAL_ERROR, NULL); dst.ptr = ECS_ELEM(column->data, size, ECS_RECORD_TO_ROW(r->row)); dst.ti = column->ti; return dst; } else if (column_index < 0) { column_index = flecs_ito(int16_t, -column_index - 1); const ecs_table_record_t *tr = &table->_->records[column_index]; cr = tr->hdr.cr; if (cr->flags & EcsIdSparse) { dst.ptr = flecs_component_sparse_get( world, cr, r->table, entity); dst.ti = cr->type_info; ecs_assert(dst.ti->size == size, ECS_INTERNAL_ERROR, NULL); return dst; } } } else { cr = flecs_components_get(world, component); dst = flecs_get_component_ptr( world, table, ECS_RECORD_TO_ROW(r->row), cr); if (dst.ptr) { ecs_assert(dst.ti->size == size, ECS_INTERNAL_ERROR, NULL); return dst; } } /* If entity didn't have component yet, add it */ flecs_add_id_w_record(world, entity, r, component, 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]); if (!cr) { cr = flecs_components_get(world, component); ecs_assert(cr != NULL, ECS_INTERNAL_ERROR, NULL); } ecs_assert(r->table != NULL, ECS_INTERNAL_ERROR, NULL); return flecs_get_component_ptr( world, r->table, ECS_RECORD_TO_ROW(r->row), cr); } flecs_component_ptr_t flecs_get_mut( const ecs_world_t *world, ecs_entity_t entity, ecs_entity_t id, ecs_record_t *r, ecs_size_t size) { ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(size != 0, ECS_INTERNAL_ERROR, NULL); (void)entity; world = ecs_get_world(world); flecs_check_exclusive_world_access_write(world); flecs_component_ptr_t result; if (id < FLECS_HI_COMPONENT_ID) { if (!world->non_trivial_lookup[id]) { ecs_table_t *table = r->table; ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(table->component_map != NULL, ECS_INTERNAL_ERROR, NULL); int16_t column_index = table->component_map[id]; if (column_index > 0) { ecs_column_t *column = &table->data.columns[column_index - 1]; ecs_check(column->ti->size == size, ECS_INVALID_PARAMETER, "invalid component size"); result.ptr = ECS_ELEM(column->data, size, ECS_RECORD_TO_ROW(r->row)); result.ti = column->ti; return result; } return (flecs_component_ptr_t){0}; } } ecs_component_record_t *cr = flecs_components_get(world, id); int32_t row = ECS_RECORD_TO_ROW(r->row); return flecs_get_component_ptr(world, r->table, row, cr); error: return (flecs_component_ptr_t){0}; } 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; ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); 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); } void flecs_add_to_root_table( ecs_world_t *world, ecs_entity_t e) { flecs_poly_assert(world, ecs_world_t); ecs_assert(!(world->flags & EcsWorldMultiThreaded), ECS_INTERNAL_ERROR, NULL); ecs_record_t *r = flecs_entities_get(world, e); ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(r->table == NULL, ECS_INTERNAL_ERROR, NULL); int32_t row = flecs_table_append(world, &world->store.root, e, false, false); r->table = &world->store.root; r->row = ECS_ROW_TO_RECORD(row, r->row & ECS_ROW_FLAGS_MASK); flecs_journal(world, EcsJournalNew, e, 0, 0); } const char* flecs_entity_invalid_reason( const ecs_world_t *world, ecs_entity_t entity) { if (!entity) { return "entity id cannot be 0"; } if (entity & ECS_PAIR) { return "cannot use a pair as an entity"; } if (entity & ECS_ID_FLAGS_MASK) { return "entity id contains flag bits (TOGGLE or AUTO_OVERRIDE)"; } /* Entities should not contain data in dead zone bits */ if (entity & ~0xFF00FFFFFFFFFFFF) { return "entity id is not a valid bit pattern for an entity"; } if (!ecs_is_alive(world, entity)) { return "entity is not alive"; } return NULL; } #define flecs_assert_entity_valid(world, entity, function) \ ecs_check(entity && ecs_is_alive(world, entity), ECS_INVALID_PARAMETER, \ "invalid entity '%s' passed to %s(): %s", \ flecs_errstr(ecs_id_str(world, entity)),\ function,\ flecs_entity_invalid_reason(world, entity)); #define flecs_assert_component_valid(world, entity, component, function)\ ecs_check(ecs_id_is_valid(world, component), ECS_INVALID_PARAMETER, \ "invalid component '%s' passed to %s() for entity '%s': %s", \ flecs_errstr(ecs_id_str(world, component)), \ function,\ flecs_errstr_1(ecs_get_path(world, entity)), \ flecs_id_invalid_reason(world, component)) /* -- 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); flecs_assert_entity_valid(world, entity, "commit"); ecs_check(!ecs_is_deferred(world), ECS_INVALID_OPERATION, "commit cannot be called on stage or while world is deferred"); 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; diff.added_flags = table->flags & EcsTableAddEdgeFlags; } if (removed) { diff.removed = *removed; if (src_table) { diff.removed_flags = src_table->flags & EcsTableRemoveEdgeFlags; } } 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_new( ecs_world_t *world) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); flecs_stage_from_world(&world); ecs_entity_t e = flecs_new_id(world); flecs_add_to_root_table(world, e); return e; error: return 0; } ecs_entity_t ecs_new_low_id( ecs_world_t *world) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(!(world->flags & EcsWorldMultiThreaded), ECS_INVALID_OPERATION, "cannot create entities in multithreaded mode"); flecs_stage_from_world(&world); flecs_check_exclusive_world_access_write(world); ecs_entity_t e = 0; if (world->info.last_component_id < FLECS_HI_COMPONENT_ID) { do { e = world->info.last_component_id ++; } while (ecs_exists(world, e) && e <= FLECS_HI_COMPONENT_ID); } if (!e || e >= FLECS_HI_COMPONENT_ID) { /* If the low component ids are depleted, return a regular entity id */ e = ecs_new(world); } else { flecs_entities_ensure(world, e); flecs_add_to_root_table(world, e); } return e; 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); ecs_check(table != NULL, ECS_INVALID_PARAMETER, NULL); flecs_stage_from_world(&world); ecs_entity_t entity = flecs_new_id(world); ecs_record_t *r = flecs_entities_get(world, entity); ecs_flags32_t flags = table->flags & EcsTableAddEdgeFlags; if (table->flags & EcsTableHasIsA) { flags |= EcsTableHasOnAdd; } ecs_table_diff_t table_diff = { .added = table->type, .added_flags = flags }; flecs_new_entity(world, entity, r, table, &table_diff, true, 0); return entity; error: return 0; } ecs_entity_t ecs_new_w_id( ecs_world_t *world, ecs_id_t component) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(ecs_id_is_valid(world, component), ECS_INVALID_PARAMETER, "invalid component '%s' passed to new_w(): %s", flecs_errstr(ecs_id_str(world, component)), flecs_id_invalid_reason(world, component)) ecs_stage_t *stage = flecs_stage_from_world(&world); if (flecs_defer_cmd(stage)) { ecs_entity_t e = ecs_new(world); ecs_add_id(world, e, component); return e; } ecs_table_diff_t table_diff = ECS_TABLE_DIFF_INIT; ecs_table_t *table = flecs_table_traverse_add( world, &world->store.root, &component, &table_diff); ecs_entity_t entity = flecs_new_id(world); ecs_record_t *r = flecs_entities_get(world, entity); flecs_new_entity(world, entity, r, table, &table_diff, true, 0); flecs_defer_end(world, stage); return entity; error: return 0; } static void flecs_copy_id( ecs_world_t *world, ecs_entity_t entity, ecs_record_t *r, ecs_id_t component, size_t size, void *dst_ptr, const void *src_ptr, const ecs_type_info_t *ti) { ecs_assert(dst_ptr != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(src_ptr != NULL, ECS_INTERNAL_ERROR, NULL); if (ti->hooks.on_replace) { flecs_invoke_replace_hook( world, r->table, entity, component, dst_ptr, src_ptr, ti); } ecs_copy_t copy = ti->hooks.copy; if (copy) { copy(dst_ptr, src_ptr, 1, ti); } else { ecs_os_memcpy(dst_ptr, src_ptr, flecs_utosize(size)); } flecs_table_mark_dirty(world, r->table, component); ecs_table_t *table = r->table; ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); flecs_notify_on_set( world, table, ECS_RECORD_TO_ROW(r->row), component, true); } /* Traverse table graph by either adding or removing identifiers parsed from the * passed in expression. */ static int flecs_traverse_from_expr( ecs_world_t *world, const char *name, const char *expr, ecs_vec_t *ids) { #ifdef FLECS_QUERY_DSL const char *ptr = expr; if (ptr) { ecs_id_t component = 0; while (ptr[0] && (ptr = flecs_id_parse(world, name, ptr, &component))) { if (!component) { break; } if (!ecs_id_is_valid(world, component)) { char *idstr = ecs_id_str(world, component); ecs_parser_error(name, expr, (ptr - expr), "'%s' is invalid for ecs_entity_desc_t::add_expr", idstr); ecs_os_free(idstr); goto error; } ecs_vec_append_t(&world->allocator, ids, ecs_id_t)[0] = component; } if (!ptr) { goto error; } } return 0; #else (void)world; (void)name; (void)expr; (void)ids; ecs_err("cannot parse component expression: script addon required"); goto error; #endif error: return -1; } /* 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) { #ifdef FLECS_QUERY_DSL const char *ptr = expr; if (ptr) { ecs_id_t component = 0; while (ptr[0] && (ptr = flecs_id_parse(world, name, ptr, &component))) { if (!component) { break; } ecs_add_id(world, entity, component); } } #else (void)world; (void)entity; (void)name; (void)expr; ecs_err("cannot parse component expression: script addon required"); #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 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); ecs_vec_t ids; /* Add components from the 'add_expr' expression. Look up before naming * entity, so that expression can't resolve to self. */ ecs_vec_init_t(&world->allocator, &ids, ecs_id_t, 0); if (desc->add_expr && ecs_os_strcmp(desc->add_expr, "0")) { if (flecs_traverse_from_expr(world, name, desc->add_expr, &ids)) { goto error; } } /* Set symbol */ 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, "entity symbol inconsistent: %s (provided) vs. %s (existing)", desc->symbol, sym); } else { ecs_set_symbol(world, result, desc->symbol); } } /* If a name is provided but not yet assigned, add the Name component */ if (name && !name_assigned) { if (!ecs_add_path_w_sep(world, result, scope, name, sep, root_sep)) { if (name[0] == '#') { /* Numerical ids should always return, unless it's invalid */ goto error; } } } else if (new_entity && scope) { ecs_add_pair(world, result, EcsChildOf, scope); } /* Find existing table */ ecs_table_t *src_table = NULL, *table = NULL; ecs_record_t *r = flecs_entities_get(world, result); table = r->table; /* Add components from the 'add' array */ if (desc->add) { int32_t i = 0; ecs_id_t component; while ((component = desc->add[i ++])) { table = flecs_find_table_add(world, table, component, &diff); } } /* Add components from the 'set' array */ if (desc->set) { int32_t i = 0; ecs_id_t component; while ((component = desc->set[i ++].type)) { table = flecs_find_table_add(world, table, component, &diff); } } /* Add ids from .expr */ { int32_t i, count = ecs_vec_count(&ids); ecs_id_t *expr_ids = ecs_vec_first(&ids); for (i = 0; i < count; i ++) { table = flecs_find_table_add(world, table, expr_ids[i], &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 (new_entity) { if (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); } } /* 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 component values */ if (desc->set) { table = r->table; ecs_assert(r->table != NULL, ECS_INTERNAL_ERROR, NULL); int32_t i = 0, row = ECS_RECORD_TO_ROW(r->row); const ecs_value_t *v; flecs_defer_begin(world, world->stages[0]); while ((void)(v = &desc->set[i ++]), v->type) { if (!v->ptr) { continue; } ecs_assert(ECS_RECORD_TO_ROW(r->row) == row, ECS_INTERNAL_ERROR, NULL); ecs_component_record_t *cr = flecs_components_get(world, v->type); flecs_component_ptr_t ptr = flecs_get_component_ptr( world, table, row, cr); ecs_check(ptr.ptr != NULL, ECS_INVALID_OPERATION, "component '%s' added to entity '%s' was removed during the " "operation, make sure not to remove the component in hooks/observers", flecs_errstr(ecs_id_str(world, v->type)), flecs_errstr_2(ecs_get_path(world, result))); const ecs_type_info_t *ti = cr->type_info; flecs_copy_id(world, result, r, v->type, flecs_itosize(ti->size), ptr.ptr, v->ptr, ti); } flecs_defer_end(world, world->stages[0]); } flecs_table_diff_builder_fini(world, &diff); ecs_vec_fini_t(&world->allocator, &ids, ecs_id_t); return 0; error: flecs_table_diff_builder_fini(world, &diff); ecs_vec_fini_t(&world->allocator, &ids, ecs_id_t); return -1; } /* 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 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 (new_entity) { if (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 */ if (desc->add) { int32_t i = 0; ecs_id_t component; while ((component = desc->add[i ++])) { bool defer = true; if (ECS_HAS_ID_FLAG(component, PAIR) && ECS_PAIR_FIRST(component) == EcsChildOf) { scope = ECS_PAIR_SECOND(component); 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, component); } } } /* Set component values */ if (desc->set) { int32_t i = 0; const ecs_value_t *v; while ((void)(v = &desc->set[i ++]), v->type) { if (v->ptr) { ecs_check(v->type != 0, ECS_INVALID_PARAMETER, "0 passed for component to ecs_entity_desc_t::set[%d]", i); const ecs_type_info_t *ti = ecs_get_type_info(world, v->type); ecs_check(ti != NULL, ECS_INVALID_PARAMETER, "component '%s' passed to ecs_entity_desc_t::set[%d] is a " "tag/zero sized", flecs_errstr(ecs_id_str(world, v->type)), i); ecs_set_id(world, entity, v->type, flecs_ito(size_t, ti->size), v->ptr); } else { ecs_add_id(world, entity, v->type); } } } /* Add components from the 'add_expr' expression */ if (desc->add_expr) { flecs_defer_from_expr(world, entity, name, desc->add_expr); } int32_t thread_count = ecs_get_stage_count(world); /* 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); } } } /* Set name */ if (name && !name_assigned) { ecs_add_path_w_sep(world, entity, scope, name, sep, root_sep); } error: return; } 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, "ecs_entity_desc_t is uninitialized, initialize to {0} before using"); 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; #ifdef FLECS_DEBUG if (desc->add) { ecs_id_t component; int32_t i = 0; while ((component = desc->add[i ++])) { if (ECS_HAS_ID_FLAG(component, PAIR) && (ECS_PAIR_FIRST(component) == EcsChildOf)) { if (desc->name) { ecs_check(false, ECS_INVALID_PARAMETER, "%s: cannot set parent in " "ecs_entity_desc_t::add, use ecs_entity_desc_t::parent", desc->name); } else { ecs_check(false, ECS_INVALID_PARAMETER, "cannot set parent in " "ecs_entity_desc_t::add, use ecs_entity_desc_t::parent"); } } } } #endif 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(name); if (!id) { return 0; } if (result && (id != result)) { ecs_err( "the '#xxx' string provided to ecs_entity_desc_t::name " "does not match the id provided to ecs_entity_desc_t::id"); return 0; } name = NULL; result = id; } } const char *root_sep = desc->root_sep; bool 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; } } } /* Parent field takes precedence over scope */ if (desc->parent) { scope = desc->parent; ecs_check(ecs_is_valid(world, desc->parent), ECS_INVALID_PARAMETER, "the entity provided in ecs_entity_desc_t::parent is not valid"); } /* Find or create entity */ if (!result) { if (name) { /* If add array contains a ChildOf pair, use it as scope instead */ 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(world); } new_entity = true; ecs_assert(ecs_get_type(world, result) != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(ecs_get_type(world, result)->count == 0, ECS_INTERNAL_ERROR, NULL); } } else { /* Make sure provided id is either alive or revivable */ ecs_make_alive(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 (ecs_is_deferred(world)) { flecs_deferred_add_remove((ecs_world_t*)stage, result, name, desc, scope, with, new_entity, name_assigned); } else { if (flecs_traverse_add(world, result, name, desc, scope, with, 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) { flecs_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, "ecs_bulk_desc_t is uninitialized, set to {0} before using"); flecs_check_exclusive_world_access_write(world); 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_assert(!ecs_is_alive(world, entities[i]), ECS_INVALID_PARAMETER, "cannot pass alive entities to ecs_bulk_init()"); flecs_entities_ensure(world, entities[i]); } } ecs_type_t ids; ecs_table_t *table = desc->table; if (!table) { table = &world->store.root; } if (!table->type.count) { ecs_table_diff_builder_t diff = ECS_TABLE_DIFF_INIT; flecs_table_diff_builder_init(world, &diff); int32_t i = 0; ecs_id_t component; while ((component = desc->ids[i])) { table = flecs_find_table_add(world, table, component, &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 }; int32_t i = 0; while ((desc->ids[i])) { i ++; } ids.array = ECS_CONST_CAST(ecs_id_t*, desc->ids); ids.count = i; 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; } const ecs_entity_t* ecs_bulk_new_w_id( ecs_world_t *world, ecs_id_t component, 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, component, &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 (component) { table = flecs_find_table_add(world, table, component, &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; } 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_path(world, result); ecs_abort(ECS_INVALID_COMPONENT_SIZE, path); ecs_os_free(path); } if (ptr->alignment != alignment) { char *path = ecs_get_path(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, "ecs_component_desc_t is uninitialized, set to {0} before using"); /* 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_make_alive(world, result); new_component = ecs_has(world, result, EcsComponent); } EcsComponent *ptr = ecs_ensure(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 registered", ecs_get_name(world, result)); } else { ecs_trace("#[green]component#[reset] %s registered", ecs_get_name(world, result)); } } } else { flecs_check_component(world, result, ptr, desc->type.size, desc->type.alignment); } if (desc->type.name && new_component) { ecs_entity(world, { .id = result, .name = desc->type.name }); } 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; } 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; } 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; ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); if (table->type.count) { ecs_table_diff_t diff = { .removed = table->type, .removed_flags = table->flags & EcsTableRemoveEdgeFlags }; flecs_commit(world, entity, r, &world->store.root, &diff, false, 0); } flecs_entity_remove_non_fragmenting(world, entity, NULL); flecs_defer_end(world, stage); error: return; } 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_os_perf_trace_push("flecs.delete"); ecs_record_t *r = flecs_entities_try(world, entity); if (r) { ecs_check(!ecs_has_pair(world, entity, EcsOnDelete, EcsPanic), ECS_CONSTRAINT_VIOLATED, "cannot delete entity '%s' with (OnDelete, Panic) trait", flecs_errstr(ecs_get_path(world, entity))); 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); } 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) { flecs_table_traversable_add(r->table, -1); } /* Merge operations before deleting entity */ flecs_defer_end(world, stage); flecs_defer_begin(world, stage); } /* Entity is still in use by a query */ ecs_assert((world->flags & EcsWorldQuit) || !flecs_component_is_delete_locked(world, entity), ECS_INVALID_OPERATION, "cannot delete '%s' as it is still in use by queries", flecs_errstr(ecs_id_str(world, entity))); table = r->table; if (table) { /* NULL if entity got cleaned up as result of cycle */ ecs_table_diff_t diff = { .removed = table->type, .removed_flags = table->flags & EcsTableRemoveEdgeFlags }; int32_t row = ECS_RECORD_TO_ROW(r->row); flecs_notify_on_remove( world, table, &world->store.root, row, 1, &diff); flecs_entity_remove_non_fragmenting(world, entity, r); flecs_table_delete(world, table, row, true); } flecs_entities_remove(world, entity); flecs_journal_end(); } flecs_defer_end(world, stage); error: ecs_os_perf_trace_pop("flecs.delete"); return; } void ecs_add_id( ecs_world_t *world, ecs_entity_t entity, ecs_id_t component) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); flecs_assert_entity_valid(world, entity, "add"); flecs_assert_component_valid(world, entity, component, "add"); flecs_add_id(world, entity, component); error: return; } void ecs_remove_id( ecs_world_t *world, ecs_entity_t entity, ecs_id_t component) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); flecs_assert_entity_valid(world, entity, "remove"); /* Component validity check is slightly different for remove() because it is * allowed to remove wildcards, but not allowed to add wildcards. */ ecs_check(ecs_id_is_valid(world, component) || ecs_id_is_wildcard(component), ECS_INVALID_PARAMETER, "invalid component '%s' passed to remove() for entity '%s': %s", flecs_errstr(ecs_id_str(world, component)), flecs_errstr_1(ecs_get_path(world, entity)), flecs_id_invalid_reason(world, component)); flecs_remove_id(world, entity, component); error: return; } void ecs_auto_override_id( ecs_world_t *world, ecs_entity_t entity, ecs_id_t component) { flecs_assert_component_valid(world, entity, component, "auto_override"); ecs_add_id(world, entity, ECS_AUTO_OVERRIDE | component); 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(ecs_is_alive(world, src), ECS_INVALID_PARAMETER, NULL); ecs_check(!dst || (ecs_get_table(world, dst)->type.count == 0), ECS_INVALID_PARAMETER, "target entity for clone() cannot have components"); ecs_stage_t *stage = flecs_stage_from_world(&world); if (!dst) { dst = ecs_new(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; ecs_assert(src_table != NULL, ECS_INTERNAL_ERROR, NULL); ecs_table_t *dst_table = src_table; if (src_table->flags & EcsTableHasName) { dst_table = ecs_table_remove_id(world, src_table, ecs_pair_t(EcsIdentifier, EcsName)); } ecs_type_t dst_type = dst_table->type; ecs_table_diff_t diff = { .added = dst_type, .added_flags = dst_table->flags & EcsTableAddEdgeFlags }; ecs_record_t *dst_r = flecs_entities_get(world, dst); if (dst_table != dst_r->table) { flecs_move_entity(world, dst, dst_r, dst_table, &diff, true, 0); } if (copy_value) { int32_t row = ECS_RECORD_TO_ROW(dst_r->row); int32_t i, count = src_table->column_count; for (i = 0; i < count; i ++) { int32_t type_id = ecs_table_column_to_type_index(src_table, i); ecs_id_t component = src_table->type.array[type_id]; void *dst_ptr = ecs_get_mut_id(world, dst, component); if (!dst_ptr) { continue; } const void *src_ptr = ecs_get_id(world, src, component); const ecs_type_info_t *ti = src_table->data.columns[i].ti; if (ti->hooks.copy) { ti->hooks.copy(dst_ptr, src_ptr, 1, ti); } else { ecs_os_memcpy(dst_ptr, src_ptr, ti->size); } flecs_notify_on_set(world, dst_table, row, component, true); } if (dst_table->flags & EcsTableHasSparse) { count = dst_table->type.count; for (i = 0; i < count; i ++) { const ecs_table_record_t *tr = &dst_table->_->records[i]; ecs_component_record_t *cr = tr->hdr.cr; if (cr->sparse) { void *src_ptr = flecs_component_sparse_get( world, cr, src_table, src); if (src_ptr) { ecs_set_id(world, dst, cr->id, flecs_ito(size_t, cr->type_info->size), src_ptr); } } } } } if (src_r->row & EcsEntityHasDontFragment) { ecs_component_record_t *cur = world->cr_non_fragmenting_head; while (cur) { if (!ecs_id_is_wildcard(cur->id)) { ecs_assert(cur->flags & EcsIdSparse, ECS_INTERNAL_ERROR, NULL); if (cur->sparse) { if (cur->type_info) { void *src_ptr = flecs_sparse_get(cur->sparse, 0, src); if (src_ptr) { ecs_set_id(world, dst, cur->id, flecs_ito(size_t, cur->type_info->size), src_ptr); } } else { if (flecs_sparse_has(cur->sparse, src)) { ecs_add_id(world, dst, cur->id); } } } } cur = cur->non_fragmenting.next; } } 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 component) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); flecs_assert_entity_valid(world, entity, "get"); ecs_check(ecs_id_is_valid(world, component) || ecs_id_is_wildcard(component), 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; ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); if (component < FLECS_HI_COMPONENT_ID) { if (!world->non_trivial_lookup[component]) { ecs_get_low_id(table, r, component); return NULL; } } ecs_component_record_t *cr = flecs_components_get(world, component); if (!cr) { return NULL; } if (cr->flags & EcsIdDontFragment) { void *ptr = flecs_component_sparse_get(world, cr, table, entity); if (ptr) { return ptr; } } const ecs_table_record_t *tr = flecs_component_get_table(cr, table); if (!tr) { return flecs_get_base_component(world, table, component, cr, 0); } else { if (cr->flags & EcsIdSparse) { return flecs_component_sparse_get(world, cr, table, entity); } ecs_check(tr->column != -1, ECS_INVALID_PARAMETER, "component '%s' passed to get() is a tag/zero sized", flecs_errstr(ecs_id_str(world, component))); } int32_t row = ECS_RECORD_TO_ROW(r->row); return flecs_table_get_component(table, tr->column, row).ptr; error: return NULL; } #ifdef FLECS_DEBUG static bool flecs_component_has_on_replace( const ecs_world_t *world, ecs_id_t component, const char *funcname) { const ecs_type_info_t *ti = ecs_get_type_info(world, component); ecs_check(ti != NULL, ECS_INVALID_PARAMETER, "invalid component '%s' for %s(): component cannot be a tag/zero sized", flecs_errstr(ecs_id_str(world, component)), funcname); return ti->hooks.on_replace != NULL; error: return false; } #endif void* ecs_get_mut_id( const ecs_world_t *world, ecs_entity_t entity, ecs_id_t component) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); flecs_assert_entity_valid(world, entity, "get_mut"); flecs_assert_component_valid(world, entity, component, "get_mut"); ecs_dbg_assert(!flecs_component_has_on_replace(world, component, "get_mut"), ECS_INVALID_PARAMETER, "cannot call get_mut() for component '%s' which has an on_replace hook " "(use set()/assign())", flecs_errstr(ecs_id_str(world, component))); world = ecs_get_world(world); flecs_check_exclusive_world_access_write(world); ecs_record_t *r = flecs_entities_get(world, entity); ecs_assert(r != NULL, ECS_INVALID_PARAMETER, NULL); if (component < FLECS_HI_COMPONENT_ID) { if (!world->non_trivial_lookup[component]) { ecs_get_low_id(r->table, r, component); return NULL; } } ecs_component_record_t *cr = flecs_components_get(world, component); int32_t row = ECS_RECORD_TO_ROW(r->row); return flecs_get_component_ptr(world, r->table, row, cr).ptr; error: return NULL; } void* ecs_ensure_id( ecs_world_t *world, ecs_entity_t entity, ecs_id_t component, size_t size) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); flecs_assert_entity_valid(world, entity, "ensure"); flecs_assert_component_valid(world, entity, component, "ensure"); ecs_dbg_assert(!flecs_component_has_on_replace(world, component, "ensure"), ECS_INVALID_PARAMETER, "cannot call ensure() for component '%s' which has an on_replace hook " "(use set()/assign())", flecs_errstr(ecs_id_str(world, component))); ecs_stage_t *stage = flecs_stage_from_world(&world); if (flecs_defer_cmd(stage)) { return flecs_defer_ensure( world, stage, entity, component, flecs_uto(int32_t, size)); } ecs_record_t *r = flecs_entities_get(world, entity); ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); void *result = flecs_ensure(world, entity, component, r, flecs_uto(int32_t, size)).ptr; ecs_check(result != NULL, ECS_INVALID_OPERATION, "component '%s' ensured on entity '%s' was removed during the " "operation, make sure not to remove the component in hooks/observers", flecs_errstr(ecs_id_str(world, component)), flecs_errstr_2(ecs_get_path(world, entity))); flecs_defer_end(world, stage); return result; error: return NULL; } void* ecs_emplace_id( ecs_world_t *world, ecs_entity_t entity, ecs_id_t component, size_t size, bool *is_new) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); flecs_assert_entity_valid(world, entity, "emplace"); flecs_assert_component_valid(world, entity, component, "emplace"); ecs_dbg_assert(!flecs_component_has_on_replace(world, component, "emplace"), ECS_INVALID_PARAMETER, "cannot call emplace() for component '%s' which has an on_replace hook " "(use set()/entity::replace())", flecs_errstr(ecs_id_str(world, component))); ecs_stage_t *stage = flecs_stage_from_world(&world); if (flecs_defer_cmd(stage)) { return flecs_defer_emplace( world, stage, entity, component, flecs_uto(int32_t, size), is_new); } ecs_check(is_new || !ecs_has_id(world, entity, component), ECS_INVALID_PARAMETER, "cannot emplace() existing component '%s' for entity '%s' unless " "'is_new' argument is provided", flecs_errstr(ecs_id_str(world, component)), flecs_errstr_2(ecs_get_path(world, entity))); ecs_record_t *r = flecs_entities_get(world, entity); ecs_table_t *table = r->table; ecs_component_record_t *cr = flecs_components_ensure(world, component); if (cr->flags & EcsIdDontFragment) { void *ptr = flecs_component_sparse_get(world, cr, table, entity); if (ptr) { if (is_new) { *is_new = false; } flecs_defer_end(world, stage); return ptr; } if (is_new) { *is_new = true; } is_new = NULL; } flecs_add_id_w_record(world, entity, r, component, false /* No ctor */); flecs_defer_end(world, stage); void *ptr = flecs_get_component( world, r->table, ECS_RECORD_TO_ROW(r->row), cr); ecs_check(ptr != NULL, ECS_INVALID_OPERATION, "component '%s' emplaced on entity '%s' was removed during the " "operation, make sure not to remove the component in hooks/observers", flecs_errstr(ecs_id_str(world, component)), flecs_errstr_2(ecs_get_path(world, entity))); if (is_new) { *is_new = table != r->table; } return ptr; 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 = r->table; ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); int32_t count = ecs_os_ainc(&table->_->lock); (void)count; if (write) { ecs_check(count == 1, ECS_ACCESS_VIOLATION, "invalid concurrent access to table for entity '%s'", flecs_errstr(ecs_get_path(world, entity))); } 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; ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); return table->data.entities[ECS_RECORD_TO_ROW(record->row)]; error: return 0; } const void* ecs_record_get_id( const ecs_world_t *stage, const ecs_record_t *r, ecs_id_t component) { const ecs_world_t *world = ecs_get_world(stage); ecs_component_record_t *cr = flecs_components_get(world, component); return flecs_get_component( world, r->table, ECS_RECORD_TO_ROW(r->row), cr); } bool ecs_record_has_id( ecs_world_t *stage, const ecs_record_t *r, ecs_id_t component) { const ecs_world_t *world = ecs_get_world(stage); if (r->table) { return ecs_table_has_id(world, r->table, component); } return false; } void* ecs_record_ensure_id( ecs_world_t *stage, ecs_record_t *r, ecs_id_t component) { const ecs_world_t *world = ecs_get_world(stage); ecs_component_record_t *cr = flecs_components_get(world, component); return flecs_get_component( world, r->table, ECS_RECORD_TO_ROW(r->row), cr); } void flecs_modified_id_if( ecs_world_t *world, ecs_entity_t entity, ecs_id_t component, bool invoke_hook) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); flecs_assert_entity_valid(world, entity, "modified"); flecs_assert_component_valid(world, entity, component, "modified"); ecs_stage_t *stage = flecs_stage_from_world(&world); if (flecs_defer_modified(stage, entity, component)) { return; } ecs_record_t *r = flecs_entities_get(world, entity); ecs_table_t *table = r->table; ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); ecs_component_record_t *cr = flecs_components_get(world, component); if (!cr || !flecs_component_get_table(cr, table)) { flecs_defer_end(world, stage); return; } flecs_notify_on_set( world, table, ECS_RECORD_TO_ROW(r->row), component, invoke_hook); flecs_table_mark_dirty(world, table, component); flecs_defer_end(world, stage); error: return; } void ecs_modified_id( ecs_world_t *world, ecs_entity_t entity, ecs_id_t component) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); flecs_assert_entity_valid(world, entity, "modified"); flecs_assert_component_valid(world, entity, component, "modified"); ecs_stage_t *stage = flecs_stage_from_world(&world); if (component < FLECS_HI_COMPONENT_ID) { if (!world->non_trivial_set[component]) { return; } } if (flecs_defer_modified(stage, entity, component)) { 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, component), ECS_INVALID_PARAMETER, "invalid call to modified(), entity '%s' does not have component '%s'", flecs_errstr(ecs_get_path(world, entity)), flecs_errstr_2(ecs_id_str(world, component))); ecs_record_t *r = flecs_entities_get(world, entity); ecs_table_t *table = r->table; flecs_notify_on_set( world, table, ECS_RECORD_TO_ROW(r->row), component, true); flecs_table_mark_dirty(world, table, component); flecs_defer_end(world, stage); error: return; } void flecs_set_id_move( ecs_world_t *world, ecs_stage_t *stage, ecs_entity_t entity, ecs_id_t component, size_t size, void *ptr, ecs_cmd_kind_t cmd_kind) { if (flecs_defer_cmd(stage)) { ecs_throw(ECS_INVALID_OPERATION, "cannot flush a command queue to a deferred stage. This happens " "when a stage is explicitly merged into the world/another stage " "that is deferred"); } ecs_record_t *r = flecs_entities_get(world, entity); flecs_component_ptr_t dst = flecs_ensure( world, entity, component, r, flecs_uto(int32_t, size)); 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); if (ti->hooks.on_replace) { flecs_invoke_replace_hook( world, r->table, entity, component, dst.ptr, ptr, ti); } ecs_move_t move; if (cmd_kind != EcsCmdEmplace) { /* ctor will have happened by ensure */ 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, component); if (cmd_kind == EcsCmdSet) { ecs_table_t *table = r->table; if (table->flags & EcsTableHasOnSet || ti->hooks.on_set) { ecs_type_t ids = { .array = &component, .count = 1 }; flecs_notify_on_set_ids( world, table, ECS_RECORD_TO_ROW(r->row), 1, &ids); } } flecs_defer_end(world, stage); error: return; } void ecs_set_id( ecs_world_t *world, ecs_entity_t entity, ecs_id_t component, size_t size, const void *ptr) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); flecs_assert_entity_valid(world, entity, "set"); flecs_assert_component_valid(world, entity, component, "set"); ecs_check(size != 0, ECS_INVALID_PARAMETER, NULL); ecs_check(ptr != NULL, ECS_INVALID_PARAMETER, "invalid call to set() for component '%s' and entity '%s': no " "component value provided", flecs_errstr(ecs_id_str(world, component)), flecs_errstr_1(ecs_id_str(world, entity))); ecs_stage_t *stage = flecs_stage_from_world(&world); if (flecs_defer_cmd(stage)) { flecs_defer_set(world, stage, entity, component, flecs_utosize(size), ECS_CONST_CAST(void*, ptr)); return; } ecs_record_t *r = flecs_entities_get(world, entity); flecs_component_ptr_t dst = flecs_ensure(world, entity, component, r, flecs_uto(int32_t, size)); if (component < FLECS_HI_COMPONENT_ID) { if (!world->non_trivial_set[component]) { ecs_os_memcpy(dst.ptr, ptr, size); goto done; } } flecs_copy_id(world, entity, r, component, size, dst.ptr, ptr, dst.ti); done: flecs_defer_end(world, stage); error: return; } #if defined(FLECS_DEBUG) || defined(FLECS_KEEP_ASSERT) static bool flecs_can_toggle( ecs_world_t *world, ecs_id_t component) { ecs_component_record_t *cr = flecs_components_get(world, component); if (!cr) { return ecs_has_id(world, component, EcsCanToggle); } return (cr->flags & EcsIdCanToggle) != 0; } #endif void ecs_enable_id( ecs_world_t *world, ecs_entity_t entity, ecs_id_t component, bool enable) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_stage_t *stage = flecs_stage_from_world(&world); ecs_check(ecs_is_valid(world, entity), ECS_INVALID_PARAMETER, NULL); flecs_assert_component_valid(world, entity, component, "enable_component"); ecs_check(flecs_can_toggle(world, component), ECS_INVALID_OPERATION, "cannot enable/disable component '%s' as it does not have the CanToggle trait", flecs_errstr(ecs_id_str(world, component))); ecs_entity_t bs_id = component | ECS_TOGGLE; ecs_add_id((ecs_world_t*)stage, entity, bs_id); if (flecs_defer_enable(stage, entity, component, enable)) { return; } ecs_record_t *r = flecs_entities_get(world, entity); ecs_table_t *table = r->table; int32_t index = ecs_table_get_type_index(world, table, bs_id); ecs_assert(index != -1, ECS_INTERNAL_ERROR, NULL); 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); flecs_defer_end(world, stage); error: return; } bool ecs_is_enabled_id( const ecs_world_t *world, ecs_entity_t entity, ecs_id_t component) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); flecs_assert_entity_valid(world, entity, "is_enabled"); flecs_assert_component_valid(world, entity, component, "is_enabled"); /* 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; ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); ecs_entity_t bs_id = component | 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, component); } 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; } void ecs_set_child_order( ecs_world_t *world, ecs_entity_t parent, const ecs_entity_t *children, int32_t child_count) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(ecs_is_alive(world, parent), ECS_INVALID_PARAMETER, NULL); ecs_check(children == NULL || child_count, ECS_INVALID_PARAMETER, "children array passed to set_child_order() cannot be NULL if " "child_count is not 0"); ecs_check(children != NULL || !child_count, ECS_INVALID_PARAMETER, "children array passed to set_child_order() cannot be not-NULL if " "child_count is 0"); ecs_check(!(world->flags & EcsWorldMultiThreaded), ECS_INVALID_OPERATION, "cannot call set_child_oderder() while in multithreaded mode"); flecs_stage_from_world(&world); flecs_check_exclusive_world_access_write(world); flecs_ordered_children_reorder(world, parent, children, child_count); error: return; } ecs_entities_t ecs_get_ordered_children( const ecs_world_t *world, ecs_entity_t parent) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(ecs_is_alive(world, parent), ECS_INVALID_PARAMETER, NULL); world = ecs_get_world(world); flecs_check_exclusive_world_access_read(world); ecs_component_record_t *cr = flecs_components_get( world, ecs_childof(parent)); ecs_check(cr != NULL && (cr->flags & EcsIdOrderedChildren), ECS_INVALID_PARAMETER, "invalid call to get_ordered_children(): parent '%s' does not have " "the OrderedChildren trait", flecs_errstr(ecs_get_path(world, parent))); ecs_assert(cr->pair != NULL, ECS_INTERNAL_ERROR, NULL); return (ecs_entities_t){ .count = ecs_vec_count(&cr->pair->ordered_children), .alive_count = ecs_vec_count(&cr->pair->ordered_children), .ids = ecs_vec_first(&cr->pair->ordered_children), }; error: return (ecs_entities_t){0}; } bool ecs_has_id( const ecs_world_t *world, ecs_entity_t entity, ecs_id_t component) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); flecs_assert_entity_valid(world, entity, "has"); ecs_check(component != 0, ECS_INVALID_PARAMETER, "invalid component passed to has(): component cannot be 0"); /* 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; ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); if (component < FLECS_HI_COMPONENT_ID) { if (!world->non_trivial_lookup[component]) { return table->component_map[component] != 0; } } ecs_component_record_t *cr = flecs_components_get(world, component); bool can_inherit = false; if (cr) { const ecs_table_record_t *tr = flecs_component_get_table(cr, table); if (tr) { return true; } if (cr->flags & (EcsIdDontFragment|EcsIdMatchDontFragment)) { if (flecs_component_sparse_has(cr, entity)) { return true; } else { return flecs_get_base_component( world, table, component, cr, 0) != NULL; } } can_inherit = cr->flags & EcsIdOnInstantiateInherit; } if (!(table->flags & EcsTableHasIsA)) { return false; } if (!can_inherit) { return false; } ecs_table_record_t *tr; int32_t column = ecs_search_relation(world, table, 0, component, EcsIsA, 0, 0, 0, &tr); if (column == -1) { return false; } return true; error: return false; } bool ecs_owns_id( const ecs_world_t *world, ecs_entity_t entity, ecs_id_t component) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); flecs_assert_entity_valid(world, entity, "owns"); ecs_check(component != 0, ECS_INVALID_PARAMETER, "invalid component passed to has(): component cannot be 0"); /* 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; ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); if (component < FLECS_HI_COMPONENT_ID) { if (!world->non_trivial_lookup[component]) { return table->component_map[component]; } } ecs_component_record_t *cr = flecs_components_get(world, component); if (cr) { const ecs_table_record_t *tr = flecs_component_get_table(cr, table); if (tr) { return true; } if (cr->flags & (EcsIdDontFragment|EcsIdMatchDontFragment)) { return flecs_component_sparse_has(cr, entity); } } error: return false; } 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); flecs_assert_entity_valid(world, entity, "get_target"); 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; ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); ecs_id_t wc = ecs_pair(rel, EcsWildcard); ecs_component_record_t *cr = flecs_components_get(world, wc); if (!cr) { return 0; } const ecs_table_record_t *tr = flecs_component_get_table(cr, table);; if (!tr) { if (cr->flags & EcsIdDontFragment) { if (cr->flags & EcsIdExclusive) { if (index > 0) { return 0; } ecs_entity_t *tgt = flecs_sparse_get(cr->sparse, 0, entity); if (tgt) { return *tgt; } } else { ecs_type_t *type = flecs_sparse_get(cr->sparse, 0, entity); if (type && (index < type->count)) { return type->array[index]; } } } if (cr->flags & EcsIdOnInstantiateInherit) { goto look_in_base; } return 0; } 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) { const ecs_table_record_t *tr_isa = flecs_component_get_table( world->cr_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; } } } error: return 0; } ecs_entity_t ecs_get_parent( const ecs_world_t *world, ecs_entity_t entity) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); flecs_assert_entity_valid(world, entity, "get_parent"); 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; ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); ecs_component_record_t *cr = world->cr_childof_wildcard; ecs_assert(cr != NULL, ECS_INTERNAL_ERROR, NULL); const ecs_table_record_t *tr = flecs_component_get_table(cr, table); if (!tr) { return 0; } ecs_entity_t id = table->type.array[tr->index]; return flecs_entities_get_alive(world, ECS_PAIR_SECOND(id)); error: return 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 component) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); flecs_assert_entity_valid(world, entity, "get_target_for_id"); if (!component) { 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, component, 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, component)) { 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); flecs_assert_entity_valid(world, entity, "get_depth"); ecs_check(ecs_has_id(world, rel, EcsAcyclic), ECS_INVALID_PARAMETER, "cannot determine depth for non-acyclic relationship '%s' " "(add Acyclic trait to relationship)", flecs_errstr(ecs_get_path(world, rel))); ecs_table_t *table = ecs_get_table(world, entity); if (table) { return ecs_table_get_depth(world, table, rel); } return 0; error: return -1; } bool ecs_is_valid( const ecs_world_t *world, ecs_entity_t entity) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); #ifdef FLECS_DEBUG world = ecs_get_world(world); flecs_check_exclusive_world_access_read(world); #endif /* 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 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); flecs_check_exclusive_world_access_read(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); flecs_check_exclusive_world_access_read(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 an alive one. */ if ((uint32_t)entity != entity) { return 0; } ecs_entity_t current = flecs_entities_get_alive(world, entity); if (!current || !flecs_entities_is_alive(world, current)) { return 0; } return current; error: return 0; } void ecs_make_alive( 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)); flecs_check_exclusive_world_access_write(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, "cannot call make_alive() while world is in multithreaded mode"); /* Check if a version of the provided id is alive */ ecs_entity_t current = ecs_get_alive(world, (uint32_t)entity); if (current == 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(!current, ECS_INVALID_OPERATION, "invalid call to make_alive(): entity %u is alive with different " "generation (%u vs %u)", (uint32_t)entity, (uint32_t)(current >> 32), (uint32_t)(entity >> 32)); /* 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_make_alive(world, entity); /* Ensure id exists. The underlying data structure will verify that the * generation count matches the provided one. */ ecs_record_t *r = flecs_entities_ensure(world, entity); ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(r->table == NULL, ECS_INTERNAL_ERROR, NULL); int32_t row = flecs_table_append( world, &world->store.root, entity, false, false); r->table = &world->store.root; r->row = ECS_ROW_TO_RECORD(row, r->row & ECS_ROW_FLAGS_MASK); error: return; } void ecs_make_alive_id( ecs_world_t *world, ecs_id_t component) { flecs_poly_assert(world, ecs_world_t); if (ECS_HAS_ID_FLAG(component, PAIR)) { ecs_entity_t r = ECS_PAIR_FIRST(component); ecs_entity_t t = ECS_PAIR_SECOND(component); ecs_check(r != 0, ECS_INVALID_PARAMETER, NULL); ecs_check(t != 0, ECS_INVALID_PARAMETER, NULL); if (flecs_entities_get_alive(world, r) == 0) { ecs_assert(!ecs_exists(world, r), ECS_INVALID_PARAMETER, "first element of pair is not alive"); ecs_make_alive(world, r); } if (flecs_entities_get_alive(world, t) == 0) { ecs_assert(!ecs_exists(world, t), ECS_INVALID_PARAMETER, "second element of pair is not alive"); ecs_make_alive(world, t); } } else { ecs_make_alive(world, component & 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); flecs_check_exclusive_world_access_read(world); return flecs_entities_exists(world, entity); error: return false; } void ecs_set_version( ecs_world_t *world, ecs_entity_t entity_with_generation) { flecs_poly_assert(world, ecs_world_t); ecs_assert(!(world->flags & EcsWorldReadonly), ECS_INVALID_OPERATION, "cannot change generation for entity %u when world is in readonly mode", (uint32_t)entity_with_generation); ecs_assert(!(ecs_is_deferred(world)), ECS_INVALID_OPERATION, "cannot change generation for entity %u while world is deferred", (uint32_t)entity_with_generation); flecs_check_exclusive_world_access_write(world); flecs_entities_make_alive(world, entity_with_generation); if (flecs_entities_is_alive(world, entity_with_generation)) { ecs_record_t *r = flecs_entities_get(world, entity_with_generation); if (r && r->table) { int32_t row = ECS_RECORD_TO_ROW(r->row); ecs_entity_t *entities = r->table->data.entities; entities[row] = entity_with_generation; } } } uint32_t ecs_get_version( ecs_entity_t entity) { return flecs_uto(uint32_t, ECS_GENERATION(entity)); } ecs_table_t* ecs_get_table( const ecs_world_t *world, ecs_entity_t entity) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); flecs_assert_entity_valid(world, entity, "get_table"); ecs_check(ecs_is_valid(world, entity), ECS_INVALID_PARAMETER, NULL); world = ecs_get_world(world); flecs_check_exclusive_world_access_read(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_check(world != NULL, ECS_INVALID_PARAMETER, NULL); flecs_assert_entity_valid(world, entity, "get_type"); ecs_table_t *table = ecs_get_table(world, entity); if (table) { return &table->type; } error: return NULL; } void ecs_enable( ecs_world_t *world, ecs_entity_t entity, bool enabled) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); flecs_assert_entity_valid(world, entity, "enable"); 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 component = ids[i]; ecs_flags32_t flags = ecs_id_get_flags(world, component); if (!(flags & EcsIdOnInstantiateDontInherit)){ ecs_enable(world, component, enabled); } } } else { if (enabled) { ecs_remove_id(world, entity, EcsDisabled); } else { ecs_add_id(world, entity, EcsDisabled); } } error: return; } 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_entity_str( const ecs_world_t *world, ecs_entity_t entity) { ecs_strbuf_t buf = ECS_STRBUF_INIT; flecs_assert_entity_valid(world, entity, "entity_str"); ecs_get_path_w_sep_buf(world, 0, entity, 0, "", &buf, false); 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; } ecs_table_range_t flecs_range_from_entity( const ecs_world_t *world, ecs_entity_t e) { ecs_record_t *r = flecs_entities_get(world, e); if (!r) { return (ecs_table_range_t){ 0 }; } return (ecs_table_range_t){ .table = r->table, .offset = ECS_RECORD_TO_ROW(r->row), .count = 1 }; }