4928 lines
148 KiB
C
4928 lines
148 KiB
C
/**
|
|
* @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 <ctype.h>
|
|
|
|
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;
|
|
}
|