1241 lines
33 KiB
C
1241 lines
33 KiB
C
/**
|
|
* @file table_graph.c
|
|
* @brief Data structure to speed up table transitions.
|
|
*
|
|
* The table graph is used to speed up finding tables in add/remove operations.
|
|
* For example, if component C is added to an entity in table [A, B], the entity
|
|
* must be moved to table [A, B, C]. The graph speeds this process up with an
|
|
* edge for component C that connects [A, B] to [A, B, C].
|
|
*/
|
|
|
|
#include "../private_api.h"
|
|
|
|
/* Id sequence (type) utilities */
|
|
|
|
static
|
|
uint64_t flecs_type_hash(const void *ptr) {
|
|
const ecs_type_t *type = ptr;
|
|
ecs_id_t *ids = type->array;
|
|
int32_t count = type->count;
|
|
return flecs_hash(ids, count * ECS_SIZEOF(ecs_id_t));
|
|
}
|
|
|
|
static
|
|
int flecs_type_compare(const void *ptr_1, const void *ptr_2) {
|
|
const ecs_type_t *type_1 = ptr_1;
|
|
const ecs_type_t *type_2 = ptr_2;
|
|
|
|
int32_t count_1 = type_1->count;
|
|
int32_t count_2 = type_2->count;
|
|
|
|
if (count_1 != count_2) {
|
|
return (count_1 > count_2) - (count_1 < count_2);
|
|
}
|
|
|
|
const ecs_id_t *ids_1 = type_1->array;
|
|
const ecs_id_t *ids_2 = type_2->array;
|
|
int result = 0;
|
|
|
|
int32_t i;
|
|
for (i = 0; !result && (i < count_1); i ++) {
|
|
ecs_id_t id_1 = ids_1[i];
|
|
ecs_id_t id_2 = ids_2[i];
|
|
result = (id_1 > id_2) - (id_1 < id_2);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
void flecs_table_hashmap_init(
|
|
ecs_world_t *world,
|
|
ecs_hashmap_t *hm)
|
|
{
|
|
flecs_hashmap_init(hm, ecs_type_t, ecs_table_t*,
|
|
flecs_type_hash, flecs_type_compare, &world->allocator);
|
|
}
|
|
|
|
/* Find location where to insert id into type */
|
|
static
|
|
int flecs_type_find_insert(
|
|
const ecs_type_t *type,
|
|
int32_t offset,
|
|
ecs_id_t to_add)
|
|
{
|
|
ecs_id_t *array = type->array;
|
|
int32_t i, count = type->count;
|
|
|
|
for (i = offset; i < count; i ++) {
|
|
ecs_id_t id = array[i];
|
|
if (id == to_add) {
|
|
return -1;
|
|
}
|
|
if (id > to_add) {
|
|
return i;
|
|
}
|
|
}
|
|
return i;
|
|
}
|
|
|
|
/* Find location of id in type */
|
|
static
|
|
int flecs_type_find(
|
|
const ecs_type_t *type,
|
|
ecs_id_t id)
|
|
{
|
|
ecs_id_t *array = type->array;
|
|
int32_t i, count = type->count;
|
|
|
|
for (i = 0; i < count; i ++) {
|
|
ecs_id_t cur = array[i];
|
|
if (ecs_id_match(cur, id)) {
|
|
return i;
|
|
}
|
|
if (cur > id) {
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
/* Count number of matching ids */
|
|
static
|
|
int flecs_type_count_matches(
|
|
const ecs_type_t *type,
|
|
ecs_id_t wildcard,
|
|
int32_t offset)
|
|
{
|
|
ecs_id_t *array = type->array;
|
|
int32_t i = offset, count = type->count;
|
|
|
|
for (; i < count; i ++) {
|
|
ecs_id_t cur = array[i];
|
|
if (!ecs_id_match(cur, wildcard)) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
return i - offset;
|
|
}
|
|
|
|
/* Create type from source type with id */
|
|
static
|
|
int flecs_type_new_with(
|
|
ecs_world_t *world,
|
|
ecs_type_t *dst,
|
|
const ecs_type_t *src,
|
|
ecs_id_t with)
|
|
{
|
|
ecs_id_t *src_array = src->array;
|
|
int32_t at = flecs_type_find_insert(src, 0, with);
|
|
if (at == -1) {
|
|
return -1;
|
|
}
|
|
|
|
int32_t dst_count = src->count + 1;
|
|
ecs_id_t *dst_array = flecs_walloc_n(world, ecs_id_t, dst_count);
|
|
dst->count = dst_count;
|
|
dst->array = dst_array;
|
|
|
|
if (at) {
|
|
ecs_os_memcpy_n(dst_array, src_array, ecs_id_t, at);
|
|
}
|
|
|
|
int32_t remain = src->count - at;
|
|
if (remain) {
|
|
ecs_os_memcpy_n(&dst_array[at + 1], &src_array[at], ecs_id_t, remain);
|
|
}
|
|
|
|
dst_array[at] = with;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Create type from source type without ids matching wildcard */
|
|
static
|
|
int flecs_type_new_filtered(
|
|
ecs_world_t *world,
|
|
ecs_type_t *dst,
|
|
const ecs_type_t *src,
|
|
ecs_id_t wildcard,
|
|
int32_t at)
|
|
{
|
|
*dst = flecs_type_copy(world, src);
|
|
ecs_id_t *dst_array = dst->array;
|
|
ecs_id_t *src_array = src->array;
|
|
if (at) {
|
|
ecs_assert(dst_array != NULL, ECS_INTERNAL_ERROR, NULL);
|
|
ecs_os_memcpy_n(dst_array, src_array, ecs_id_t, at);
|
|
}
|
|
|
|
int32_t i = at + 1, w = at, count = src->count;
|
|
for (; i < count; i ++) {
|
|
ecs_id_t id = src_array[i];
|
|
if (!ecs_id_match(id, wildcard)) {
|
|
dst_array[w] = id;
|
|
w ++;
|
|
}
|
|
}
|
|
|
|
dst->count = w;
|
|
if (w != count) {
|
|
dst->array = flecs_wrealloc_n(world, ecs_id_t, w, count, dst->array);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Create type from source type without id */
|
|
static
|
|
int flecs_type_new_without(
|
|
ecs_world_t *world,
|
|
ecs_type_t *dst,
|
|
const ecs_type_t *src,
|
|
ecs_id_t without)
|
|
{
|
|
ecs_id_t *src_array = src->array;
|
|
int32_t count = 1, at = flecs_type_find(src, without);
|
|
if (at == -1) {
|
|
return -1;
|
|
}
|
|
|
|
int32_t src_count = src->count;
|
|
if (src_count == 1) {
|
|
dst->array = NULL;
|
|
dst->count = 0;
|
|
return 0;
|
|
}
|
|
|
|
if (ecs_id_is_wildcard(without)) {
|
|
if (ECS_IS_PAIR(without)) {
|
|
ecs_entity_t r = ECS_PAIR_FIRST(without);
|
|
ecs_entity_t o = ECS_PAIR_SECOND(without);
|
|
if (r == EcsWildcard && o != EcsWildcard) {
|
|
return flecs_type_new_filtered(world, dst, src, without, at);
|
|
}
|
|
}
|
|
count += flecs_type_count_matches(src, without, at + 1);
|
|
}
|
|
|
|
int32_t dst_count = src_count - count;
|
|
dst->count = dst_count;
|
|
if (!dst_count) {
|
|
dst->array = NULL;
|
|
return 0;
|
|
}
|
|
|
|
ecs_id_t *dst_array = flecs_walloc_n(world, ecs_id_t, dst_count);
|
|
dst->array = dst_array;
|
|
|
|
if (at) {
|
|
ecs_os_memcpy_n(dst_array, src_array, ecs_id_t, at);
|
|
}
|
|
|
|
int32_t remain = dst_count - at;
|
|
if (remain) {
|
|
ecs_os_memcpy_n(
|
|
&dst_array[at], &src_array[at + count], ecs_id_t, remain);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Copy type */
|
|
ecs_type_t flecs_type_copy(
|
|
ecs_world_t *world,
|
|
const ecs_type_t *src)
|
|
{
|
|
int32_t src_count = src->count;
|
|
if (!src_count) {
|
|
return (ecs_type_t){ 0 };
|
|
}
|
|
|
|
ecs_id_t *ids = flecs_walloc_n(world, ecs_id_t, src_count);
|
|
ecs_os_memcpy_n(ids, src->array, ecs_id_t, src_count);
|
|
return (ecs_type_t) {
|
|
.array = ids,
|
|
.count = src_count
|
|
};
|
|
}
|
|
|
|
/* Free type */
|
|
void flecs_type_free(
|
|
ecs_world_t *world,
|
|
ecs_type_t *type)
|
|
{
|
|
int32_t count = type->count;
|
|
if (count) {
|
|
flecs_wfree_n(world, ecs_id_t, type->count, type->array);
|
|
}
|
|
}
|
|
|
|
/* Add to type */
|
|
static
|
|
void flecs_type_add(
|
|
ecs_world_t *world,
|
|
ecs_type_t *type,
|
|
ecs_id_t add)
|
|
{
|
|
ecs_type_t new_type;
|
|
int res = flecs_type_new_with(world, &new_type, type, add);
|
|
if (res != -1) {
|
|
flecs_type_free(world, type);
|
|
type->array = new_type.array;
|
|
type->count = new_type.count;
|
|
}
|
|
}
|
|
|
|
/* Graph edge utilities */
|
|
|
|
void flecs_table_diff_builder_init(
|
|
ecs_world_t *world,
|
|
ecs_table_diff_builder_t *builder)
|
|
{
|
|
ecs_allocator_t *a = &world->allocator;
|
|
ecs_vec_init_t(a, &builder->added, ecs_id_t, 256);
|
|
ecs_vec_init_t(a, &builder->removed, ecs_id_t, 256);
|
|
}
|
|
|
|
void flecs_table_diff_builder_fini(
|
|
ecs_world_t *world,
|
|
ecs_table_diff_builder_t *builder)
|
|
{
|
|
ecs_allocator_t *a = &world->allocator;
|
|
ecs_vec_fini_t(a, &builder->added, ecs_id_t);
|
|
ecs_vec_fini_t(a, &builder->removed, ecs_id_t);
|
|
}
|
|
|
|
void flecs_table_diff_builder_clear(
|
|
ecs_table_diff_builder_t *builder)
|
|
{
|
|
ecs_vec_clear(&builder->added);
|
|
ecs_vec_clear(&builder->removed);
|
|
}
|
|
|
|
static
|
|
void flecs_table_diff_build_type(
|
|
ecs_world_t *world,
|
|
ecs_vec_t *vec,
|
|
ecs_type_t *type,
|
|
int32_t offset)
|
|
{
|
|
int32_t count = vec->count - offset;
|
|
ecs_assert(count >= 0, ECS_INTERNAL_ERROR, NULL);
|
|
if (count) {
|
|
type->array = flecs_wdup_n(world, ecs_id_t, count,
|
|
ECS_ELEM_T(vec->array, ecs_id_t, offset));
|
|
type->count = count;
|
|
ecs_vec_set_count_t(&world->allocator, vec, ecs_id_t, offset);
|
|
}
|
|
}
|
|
|
|
void flecs_table_diff_build(
|
|
ecs_world_t *world,
|
|
ecs_table_diff_builder_t *builder,
|
|
ecs_table_diff_t *diff,
|
|
int32_t added_offset,
|
|
int32_t removed_offset)
|
|
{
|
|
flecs_table_diff_build_type(world, &builder->added, &diff->added,
|
|
added_offset);
|
|
flecs_table_diff_build_type(world, &builder->removed, &diff->removed,
|
|
removed_offset);
|
|
}
|
|
|
|
void flecs_table_diff_build_noalloc(
|
|
ecs_table_diff_builder_t *builder,
|
|
ecs_table_diff_t *diff)
|
|
{
|
|
diff->added = (ecs_type_t){
|
|
.array = builder->added.array, .count = builder->added.count };
|
|
diff->removed = (ecs_type_t){
|
|
.array = builder->removed.array, .count = builder->removed.count };
|
|
}
|
|
|
|
static
|
|
void flecs_table_diff_build_add_type_to_vec(
|
|
ecs_world_t *world,
|
|
ecs_vec_t *vec,
|
|
ecs_type_t *add)
|
|
{
|
|
if (!add || !add->count) {
|
|
return;
|
|
}
|
|
|
|
int32_t offset = vec->count;
|
|
ecs_vec_grow_t(&world->allocator, vec, ecs_id_t, add->count);
|
|
ecs_os_memcpy_n(ecs_vec_get_t(vec, ecs_id_t, offset),
|
|
add->array, ecs_id_t, add->count);
|
|
}
|
|
|
|
void flecs_table_diff_build_append_table(
|
|
ecs_world_t *world,
|
|
ecs_table_diff_builder_t *dst,
|
|
ecs_table_diff_t *src)
|
|
{
|
|
flecs_table_diff_build_add_type_to_vec(world, &dst->added, &src->added);
|
|
flecs_table_diff_build_add_type_to_vec(world, &dst->removed, &src->removed);
|
|
}
|
|
|
|
static
|
|
void flecs_table_diff_free(
|
|
ecs_world_t *world,
|
|
ecs_table_diff_t *diff)
|
|
{
|
|
flecs_wfree_n(world, ecs_id_t, diff->added.count, diff->added.array);
|
|
flecs_wfree_n(world, ecs_id_t, diff->removed.count, diff->removed.array);
|
|
flecs_bfree(&world->allocators.table_diff, diff);
|
|
}
|
|
|
|
static
|
|
ecs_graph_edge_t* flecs_table_ensure_hi_edge(
|
|
ecs_world_t *world,
|
|
ecs_graph_edges_t *edges,
|
|
ecs_id_t id)
|
|
{
|
|
if (!edges->hi) {
|
|
edges->hi = flecs_alloc_t(&world->allocator, ecs_map_t);
|
|
ecs_map_init_w_params(edges->hi, &world->allocators.ptr);
|
|
}
|
|
|
|
ecs_graph_edge_t **r = ecs_map_ensure_ref(edges->hi, ecs_graph_edge_t, id);
|
|
ecs_graph_edge_t *edge = r[0];
|
|
if (edge) {
|
|
return edge;
|
|
}
|
|
|
|
if (id < FLECS_HI_COMPONENT_ID) {
|
|
edge = &edges->lo[id];
|
|
} else {
|
|
edge = flecs_bcalloc(&world->allocators.graph_edge);
|
|
}
|
|
|
|
r[0] = edge;
|
|
return edge;
|
|
}
|
|
|
|
static
|
|
ecs_graph_edge_t* flecs_table_ensure_edge(
|
|
ecs_world_t *world,
|
|
ecs_graph_edges_t *edges,
|
|
ecs_id_t id)
|
|
{
|
|
ecs_graph_edge_t *edge;
|
|
|
|
if (id < FLECS_HI_COMPONENT_ID) {
|
|
if (!edges->lo) {
|
|
edges->lo = flecs_bcalloc(&world->allocators.graph_edge_lo);
|
|
}
|
|
edge = &edges->lo[id];
|
|
} else {
|
|
edge = flecs_table_ensure_hi_edge(world, edges, id);
|
|
}
|
|
|
|
return edge;
|
|
}
|
|
|
|
static
|
|
void flecs_table_disconnect_edge(
|
|
ecs_world_t *world,
|
|
ecs_id_t id,
|
|
ecs_graph_edge_t *edge)
|
|
{
|
|
ecs_assert(edge != NULL, ECS_INTERNAL_ERROR, NULL);
|
|
ecs_assert(edge->id == id, ECS_INTERNAL_ERROR, NULL);
|
|
(void)id;
|
|
|
|
/* Remove backref from destination table */
|
|
ecs_graph_edge_hdr_t *next = edge->hdr.next;
|
|
ecs_graph_edge_hdr_t *prev = edge->hdr.prev;
|
|
|
|
if (next) {
|
|
next->prev = prev;
|
|
}
|
|
if (prev) {
|
|
prev->next = next;
|
|
}
|
|
|
|
/* Remove data associated with edge */
|
|
ecs_table_diff_t *diff = edge->diff;
|
|
if (diff) {
|
|
flecs_table_diff_free(world, diff);
|
|
}
|
|
|
|
/* If edge id is low, clear it from fast lookup array */
|
|
if (id < FLECS_HI_COMPONENT_ID) {
|
|
ecs_os_memset_t(edge, 0, ecs_graph_edge_t);
|
|
} else {
|
|
flecs_bfree(&world->allocators.graph_edge, edge);
|
|
}
|
|
}
|
|
|
|
static
|
|
void flecs_table_remove_edge(
|
|
ecs_world_t *world,
|
|
ecs_graph_edges_t *edges,
|
|
ecs_id_t id,
|
|
ecs_graph_edge_t *edge)
|
|
{
|
|
ecs_assert(edges != NULL, ECS_INTERNAL_ERROR, NULL);
|
|
ecs_assert(edges->hi != NULL, ECS_INTERNAL_ERROR, NULL);
|
|
flecs_table_disconnect_edge(world, id, edge);
|
|
ecs_map_remove(edges->hi, id);
|
|
}
|
|
|
|
static
|
|
void flecs_table_init_edges(
|
|
ecs_graph_edges_t *edges)
|
|
{
|
|
edges->lo = NULL;
|
|
edges->hi = NULL;
|
|
}
|
|
|
|
static
|
|
void flecs_table_init_node(
|
|
ecs_graph_node_t *node)
|
|
{
|
|
flecs_table_init_edges(&node->add);
|
|
flecs_table_init_edges(&node->remove);
|
|
}
|
|
|
|
bool flecs_table_records_update_empty(
|
|
ecs_table_t *table)
|
|
{
|
|
bool result = false;
|
|
bool is_empty = ecs_table_count(table) == 0;
|
|
|
|
int32_t i, count = table->_->record_count;
|
|
for (i = 0; i < count; i ++) {
|
|
ecs_table_record_t *tr = &table->_->records[i];
|
|
ecs_table_cache_t *cache = tr->hdr.cache;
|
|
result |= ecs_table_cache_set_empty(cache, table, is_empty);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
static
|
|
void flecs_init_table(
|
|
ecs_world_t *world,
|
|
ecs_table_t *table,
|
|
ecs_table_t *prev)
|
|
{
|
|
table->flags = 0;
|
|
table->dirty_state = NULL;
|
|
table->_->lock = 0;
|
|
table->_->generation = 0;
|
|
|
|
flecs_table_init_node(&table->node);
|
|
|
|
flecs_table_init(world, table, prev);
|
|
}
|
|
|
|
static
|
|
ecs_table_t *flecs_create_table(
|
|
ecs_world_t *world,
|
|
ecs_type_t *type,
|
|
flecs_hashmap_result_t table_elem,
|
|
ecs_table_t *prev)
|
|
{
|
|
ecs_table_t *result = flecs_sparse_add_t(&world->store.tables, ecs_table_t);
|
|
ecs_assert(result != NULL, ECS_INTERNAL_ERROR, NULL);
|
|
result->_ = flecs_calloc_t(&world->allocator, ecs_table__t);
|
|
ecs_assert(result->_ != NULL, ECS_INTERNAL_ERROR, NULL);
|
|
|
|
result->id = flecs_sparse_last_id(&world->store.tables);
|
|
result->type = *type;
|
|
|
|
if (ecs_should_log_2()) {
|
|
char *expr = ecs_type_str(world, &result->type);
|
|
ecs_dbg_2(
|
|
"#[green]table#[normal] [%s] #[green]created#[reset] with id %d",
|
|
expr, result->id);
|
|
ecs_os_free(expr);
|
|
}
|
|
|
|
ecs_log_push_2();
|
|
|
|
/* Store table in table hashmap */
|
|
*(ecs_table_t**)table_elem.value = result;
|
|
|
|
/* Set keyvalue to one that has the same lifecycle as the table */
|
|
*(ecs_type_t*)table_elem.key = result->type;
|
|
result->_->hash = table_elem.hash;
|
|
|
|
flecs_init_table(world, result, prev);
|
|
|
|
/* Update counters */
|
|
world->info.table_count ++;
|
|
world->info.table_record_count += result->_->record_count;
|
|
world->info.table_storage_count += result->column_count;
|
|
world->info.empty_table_count ++;
|
|
world->info.table_create_total ++;
|
|
|
|
if (!result->column_count) {
|
|
world->info.tag_table_count ++;
|
|
} else {
|
|
world->info.trivial_table_count += !(result->flags & EcsTableIsComplex);
|
|
}
|
|
|
|
ecs_log_pop_2();
|
|
|
|
return result;
|
|
}
|
|
|
|
static
|
|
ecs_table_t* flecs_table_ensure(
|
|
ecs_world_t *world,
|
|
ecs_type_t *type,
|
|
bool own_type,
|
|
ecs_table_t *prev)
|
|
{
|
|
ecs_poly_assert(world, ecs_world_t);
|
|
|
|
int32_t id_count = type->count;
|
|
if (!id_count) {
|
|
return &world->store.root;
|
|
}
|
|
|
|
ecs_table_t *table;
|
|
flecs_hashmap_result_t elem = flecs_hashmap_ensure(
|
|
&world->store.table_map, type, ecs_table_t*);
|
|
if ((table = *(ecs_table_t**)elem.value)) {
|
|
if (own_type) {
|
|
flecs_type_free(world, type);
|
|
}
|
|
return table;
|
|
}
|
|
|
|
/* If we get here, table needs to be created which is only allowed when the
|
|
* application is not currently in progress */
|
|
ecs_assert(!(world->flags & EcsWorldReadonly), ECS_INTERNAL_ERROR, NULL);
|
|
|
|
/* If we get here, the table has not been found, so create it. */
|
|
if (own_type) {
|
|
return flecs_create_table(world, type, elem, prev);
|
|
}
|
|
|
|
ecs_type_t copy = flecs_type_copy(world, type);
|
|
return flecs_create_table(world, ©, elem, prev);
|
|
}
|
|
|
|
static
|
|
void flecs_diff_insert_added(
|
|
ecs_world_t *world,
|
|
ecs_table_diff_builder_t *diff,
|
|
ecs_id_t id)
|
|
{
|
|
ecs_vec_append_t(&world->allocator, &diff->added, ecs_id_t)[0] = id;
|
|
}
|
|
|
|
static
|
|
void flecs_diff_insert_removed(
|
|
ecs_world_t *world,
|
|
ecs_table_diff_builder_t *diff,
|
|
ecs_id_t id)
|
|
{
|
|
ecs_allocator_t *a = &world->allocator;
|
|
ecs_vec_append_t(a, &diff->removed, ecs_id_t)[0] = id;
|
|
}
|
|
|
|
static
|
|
void flecs_compute_table_diff(
|
|
ecs_world_t *world,
|
|
ecs_table_t *node,
|
|
ecs_table_t *next,
|
|
ecs_graph_edge_t *edge,
|
|
ecs_id_t id)
|
|
{
|
|
if (ECS_IS_PAIR(id)) {
|
|
ecs_id_record_t *idr = flecs_id_record_get(world, ecs_pair(
|
|
ECS_PAIR_FIRST(id), EcsWildcard));
|
|
if (idr->flags & EcsIdUnion) {
|
|
if (node != next) {
|
|
id = ecs_pair(EcsUnion, ECS_PAIR_FIRST(id));
|
|
} else {
|
|
ecs_table_diff_t *diff = flecs_bcalloc(
|
|
&world->allocators.table_diff);
|
|
diff->added.count = 1;
|
|
diff->added.array = flecs_wdup_n(world, ecs_id_t, 1, &id);
|
|
edge->diff = diff;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
ecs_type_t node_type = node->type;
|
|
ecs_type_t next_type = next->type;
|
|
|
|
ecs_id_t *ids_node = node_type.array;
|
|
ecs_id_t *ids_next = next_type.array;
|
|
int32_t i_node = 0, node_count = node_type.count;
|
|
int32_t i_next = 0, next_count = next_type.count;
|
|
int32_t added_count = 0;
|
|
int32_t removed_count = 0;
|
|
bool trivial_edge = !ECS_HAS_RELATION(id, EcsIsA);
|
|
|
|
/* First do a scan to see how big the diff is, so we don't have to realloc
|
|
* or alloc more memory than required. */
|
|
for (; i_node < node_count && i_next < next_count; ) {
|
|
ecs_id_t id_node = ids_node[i_node];
|
|
ecs_id_t id_next = ids_next[i_next];
|
|
|
|
bool added = id_next < id_node;
|
|
bool removed = id_node < id_next;
|
|
|
|
trivial_edge &= !added || id_next == id;
|
|
trivial_edge &= !removed || id_node == id;
|
|
|
|
added_count += added;
|
|
removed_count += removed;
|
|
|
|
i_node += id_node <= id_next;
|
|
i_next += id_next <= id_node;
|
|
}
|
|
|
|
added_count += next_count - i_next;
|
|
removed_count += node_count - i_node;
|
|
|
|
trivial_edge &= (added_count + removed_count) <= 1 &&
|
|
!ecs_id_is_wildcard(id);
|
|
|
|
if (trivial_edge) {
|
|
/* If edge is trivial there's no need to create a diff element for it */
|
|
return;
|
|
}
|
|
|
|
ecs_table_diff_builder_t *builder = &world->allocators.diff_builder;
|
|
int32_t added_offset = builder->added.count;
|
|
int32_t removed_offset = builder->removed.count;
|
|
|
|
for (i_node = 0, i_next = 0; i_node < node_count && i_next < next_count; ) {
|
|
ecs_id_t id_node = ids_node[i_node];
|
|
ecs_id_t id_next = ids_next[i_next];
|
|
|
|
if (id_next < id_node) {
|
|
flecs_diff_insert_added(world, builder, id_next);
|
|
} else if (id_node < id_next) {
|
|
flecs_diff_insert_removed(world, builder, id_node);
|
|
}
|
|
|
|
i_node += id_node <= id_next;
|
|
i_next += id_next <= id_node;
|
|
}
|
|
|
|
for (; i_next < next_count; i_next ++) {
|
|
flecs_diff_insert_added(world, builder, ids_next[i_next]);
|
|
}
|
|
for (; i_node < node_count; i_node ++) {
|
|
flecs_diff_insert_removed(world, builder, ids_node[i_node]);
|
|
}
|
|
|
|
ecs_table_diff_t *diff = flecs_bcalloc(&world->allocators.table_diff);
|
|
edge->diff = diff;
|
|
flecs_table_diff_build(world, builder, diff, added_offset, removed_offset);
|
|
|
|
ecs_assert(diff->added.count == added_count, ECS_INTERNAL_ERROR, NULL);
|
|
ecs_assert(diff->removed.count == removed_count, ECS_INTERNAL_ERROR, NULL);
|
|
}
|
|
|
|
static
|
|
void flecs_add_overrides_for_base(
|
|
ecs_world_t *world,
|
|
ecs_type_t *dst_type,
|
|
ecs_id_t pair)
|
|
{
|
|
ecs_entity_t base = ecs_pair_second(world, pair);
|
|
ecs_assert(base != 0, ECS_INTERNAL_ERROR, NULL);
|
|
ecs_table_t *base_table = ecs_get_table(world, base);
|
|
if (!base_table) {
|
|
return;
|
|
}
|
|
|
|
ecs_id_t *ids = base_table->type.array;
|
|
|
|
ecs_flags32_t flags = base_table->flags;
|
|
if (flags & EcsTableHasOverrides) {
|
|
int32_t i, count = base_table->type.count;
|
|
for (i = 0; i < count; i ++) {
|
|
ecs_id_t id = ids[i];
|
|
ecs_id_t to_add = 0;
|
|
if (ECS_HAS_ID_FLAG(id, OVERRIDE)) {
|
|
to_add = id & ~ECS_OVERRIDE;
|
|
} else {
|
|
ecs_table_record_t *tr = &base_table->_->records[i];
|
|
ecs_id_record_t *idr = (ecs_id_record_t*)tr->hdr.cache;
|
|
if (idr->flags & EcsIdAlwaysOverride) {
|
|
to_add = id;
|
|
}
|
|
}
|
|
if (to_add) {
|
|
ecs_id_t wc = ecs_pair(ECS_PAIR_FIRST(to_add), EcsWildcard);
|
|
bool exclusive = false;
|
|
if (ECS_IS_PAIR(to_add)) {
|
|
ecs_id_record_t *idr = flecs_id_record_get(world, wc);
|
|
if (idr) {
|
|
exclusive = (idr->flags & EcsIdExclusive) != 0;
|
|
}
|
|
}
|
|
if (!exclusive) {
|
|
flecs_type_add(world, dst_type, to_add);
|
|
} else {
|
|
int32_t column = flecs_type_find(dst_type, wc);
|
|
if (column == -1) {
|
|
flecs_type_add(world, dst_type, to_add);
|
|
} else {
|
|
dst_type->array[column] = to_add;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (flags & EcsTableHasIsA) {
|
|
ecs_table_record_t *tr = flecs_id_record_get_table(
|
|
world->idr_isa_wildcard, base_table);
|
|
ecs_assert(tr != NULL, ECS_INTERNAL_ERROR, NULL);
|
|
int32_t i = tr->index, end = i + tr->count;
|
|
for (; i != end; i ++) {
|
|
flecs_add_overrides_for_base(world, dst_type, ids[i]);
|
|
}
|
|
}
|
|
}
|
|
|
|
static
|
|
void flecs_add_with_property(
|
|
ecs_world_t *world,
|
|
ecs_id_record_t *idr_with_wildcard,
|
|
ecs_type_t *dst_type,
|
|
ecs_entity_t r,
|
|
ecs_entity_t o)
|
|
{
|
|
r = ecs_get_alive(world, r);
|
|
|
|
/* Check if component/relationship has With pairs, which contain ids
|
|
* that need to be added to the table. */
|
|
ecs_table_t *table = ecs_get_table(world, r);
|
|
if (!table) {
|
|
return;
|
|
}
|
|
|
|
ecs_table_record_t *tr = flecs_id_record_get_table(
|
|
idr_with_wildcard, table);
|
|
if (tr) {
|
|
int32_t i = tr->index, end = i + tr->count;
|
|
ecs_id_t *ids = table->type.array;
|
|
|
|
for (; i < end; i ++) {
|
|
ecs_id_t id = ids[i];
|
|
ecs_assert(ECS_PAIR_FIRST(id) == EcsWith, ECS_INTERNAL_ERROR, NULL);
|
|
ecs_id_t ra = ECS_PAIR_SECOND(id);
|
|
ecs_id_t a = ra;
|
|
if (o) {
|
|
a = ecs_pair(ra, o);
|
|
}
|
|
|
|
flecs_type_add(world, dst_type, a);
|
|
flecs_add_with_property(world, idr_with_wildcard, dst_type, ra, o);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
static
|
|
ecs_table_t* flecs_find_table_with(
|
|
ecs_world_t *world,
|
|
ecs_table_t *node,
|
|
ecs_id_t with)
|
|
{
|
|
ecs_ensure_id(world, with);
|
|
|
|
ecs_id_record_t *idr = NULL;
|
|
ecs_entity_t r = 0, o = 0;
|
|
|
|
if (ECS_IS_PAIR(with)) {
|
|
r = ECS_PAIR_FIRST(with);
|
|
o = ECS_PAIR_SECOND(with);
|
|
idr = flecs_id_record_ensure(world, ecs_pair(r, EcsWildcard));
|
|
if (idr->flags & EcsIdUnion) {
|
|
ecs_type_t dst_type;
|
|
ecs_id_t union_id = ecs_pair(EcsUnion, r);
|
|
int res = flecs_type_new_with(
|
|
world, &dst_type, &node->type, union_id);
|
|
if (res == -1) {
|
|
return node;
|
|
}
|
|
|
|
return flecs_table_ensure(world, &dst_type, true, node);
|
|
} else if (idr->flags & EcsIdExclusive) {
|
|
/* Relationship is exclusive, check if table already has it */
|
|
ecs_table_record_t *tr = flecs_id_record_get_table(idr, node);
|
|
if (tr) {
|
|
/* Table already has an instance of the relationship, create
|
|
* a new id sequence with the existing id replaced */
|
|
ecs_type_t dst_type = flecs_type_copy(world, &node->type);
|
|
ecs_assert(dst_type.array != NULL, ECS_INTERNAL_ERROR, NULL);
|
|
dst_type.array[tr->index] = with;
|
|
return flecs_table_ensure(world, &dst_type, true, node);
|
|
}
|
|
}
|
|
} else {
|
|
idr = flecs_id_record_ensure(world, with);
|
|
r = with;
|
|
}
|
|
|
|
/* Create sequence with new id */
|
|
ecs_type_t dst_type;
|
|
int res = flecs_type_new_with(world, &dst_type, &node->type, with);
|
|
if (res == -1) {
|
|
return node; /* Current table already has id */
|
|
}
|
|
|
|
if (r == EcsIsA) {
|
|
/* If adding a prefab, check if prefab has overrides */
|
|
flecs_add_overrides_for_base(world, &dst_type, with);
|
|
} else if (r == EcsChildOf) {
|
|
o = ecs_get_alive(world, o);
|
|
if (ecs_has_id(world, o, EcsPrefab)) {
|
|
flecs_type_add(world, &dst_type, EcsPrefab);
|
|
}
|
|
}
|
|
|
|
if (idr->flags & EcsIdWith) {
|
|
ecs_id_record_t *idr_with_wildcard = flecs_id_record_get(world,
|
|
ecs_pair(EcsWith, EcsWildcard));
|
|
/* If id has With property, add targets to type */
|
|
flecs_add_with_property(world, idr_with_wildcard, &dst_type, r, o);
|
|
}
|
|
|
|
return flecs_table_ensure(world, &dst_type, true, node);
|
|
}
|
|
|
|
static
|
|
ecs_table_t* flecs_find_table_without(
|
|
ecs_world_t *world,
|
|
ecs_table_t *node,
|
|
ecs_id_t without)
|
|
{
|
|
if (ECS_IS_PAIR(without)) {
|
|
ecs_entity_t r = 0;
|
|
ecs_id_record_t *idr = NULL;
|
|
r = ECS_PAIR_FIRST(without);
|
|
idr = flecs_id_record_get(world, ecs_pair(r, EcsWildcard));
|
|
if (idr && idr->flags & EcsIdUnion) {
|
|
without = ecs_pair(EcsUnion, r);
|
|
}
|
|
}
|
|
|
|
/* Create sequence with new id */
|
|
ecs_type_t dst_type;
|
|
int res = flecs_type_new_without(world, &dst_type, &node->type, without);
|
|
if (res == -1) {
|
|
return node; /* Current table does not have id */
|
|
}
|
|
|
|
return flecs_table_ensure(world, &dst_type, true, node);
|
|
}
|
|
|
|
static
|
|
void flecs_table_init_edge(
|
|
ecs_table_t *table,
|
|
ecs_graph_edge_t *edge,
|
|
ecs_id_t id,
|
|
ecs_table_t *to)
|
|
{
|
|
ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL);
|
|
ecs_assert(edge != NULL, ECS_INTERNAL_ERROR, NULL);
|
|
ecs_assert(edge->id == 0, ECS_INTERNAL_ERROR, NULL);
|
|
ecs_assert(edge->hdr.next == NULL, ECS_INTERNAL_ERROR, NULL);
|
|
ecs_assert(edge->hdr.prev == NULL, ECS_INTERNAL_ERROR, NULL);
|
|
|
|
edge->from = table;
|
|
edge->to = to;
|
|
edge->id = id;
|
|
}
|
|
|
|
static
|
|
void flecs_init_edge_for_add(
|
|
ecs_world_t *world,
|
|
ecs_table_t *table,
|
|
ecs_graph_edge_t *edge,
|
|
ecs_id_t id,
|
|
ecs_table_t *to)
|
|
{
|
|
flecs_table_init_edge(table, edge, id, to);
|
|
|
|
flecs_table_ensure_hi_edge(world, &table->node.add, id);
|
|
|
|
if (table != to || table->flags & EcsTableHasUnion) {
|
|
/* Add edges are appended to refs.next */
|
|
ecs_graph_edge_hdr_t *to_refs = &to->node.refs;
|
|
ecs_graph_edge_hdr_t *next = to_refs->next;
|
|
|
|
to_refs->next = &edge->hdr;
|
|
edge->hdr.prev = to_refs;
|
|
|
|
edge->hdr.next = next;
|
|
if (next) {
|
|
next->prev = &edge->hdr;
|
|
}
|
|
|
|
flecs_compute_table_diff(world, table, to, edge, id);
|
|
}
|
|
}
|
|
|
|
static
|
|
void flecs_init_edge_for_remove(
|
|
ecs_world_t *world,
|
|
ecs_table_t *table,
|
|
ecs_graph_edge_t *edge,
|
|
ecs_id_t id,
|
|
ecs_table_t *to)
|
|
{
|
|
flecs_table_init_edge(table, edge, id, to);
|
|
|
|
flecs_table_ensure_hi_edge(world, &table->node.remove, id);
|
|
|
|
if (table != to) {
|
|
/* Remove edges are appended to refs.prev */
|
|
ecs_graph_edge_hdr_t *to_refs = &to->node.refs;
|
|
ecs_graph_edge_hdr_t *prev = to_refs->prev;
|
|
|
|
to_refs->prev = &edge->hdr;
|
|
edge->hdr.next = to_refs;
|
|
|
|
edge->hdr.prev = prev;
|
|
if (prev) {
|
|
prev->next = &edge->hdr;
|
|
}
|
|
|
|
flecs_compute_table_diff(world, table, to, edge, id);
|
|
}
|
|
}
|
|
|
|
static
|
|
ecs_table_t* flecs_create_edge_for_remove(
|
|
ecs_world_t *world,
|
|
ecs_table_t *node,
|
|
ecs_graph_edge_t *edge,
|
|
ecs_id_t id)
|
|
{
|
|
ecs_table_t *to = flecs_find_table_without(world, node, id);
|
|
flecs_init_edge_for_remove(world, node, edge, id, to);
|
|
return to;
|
|
}
|
|
|
|
static
|
|
ecs_table_t* flecs_create_edge_for_add(
|
|
ecs_world_t *world,
|
|
ecs_table_t *node,
|
|
ecs_graph_edge_t *edge,
|
|
ecs_id_t id)
|
|
{
|
|
ecs_table_t *to = flecs_find_table_with(world, node, id);
|
|
flecs_init_edge_for_add(world, node, edge, id, to);
|
|
return to;
|
|
}
|
|
|
|
ecs_table_t* flecs_table_traverse_remove(
|
|
ecs_world_t *world,
|
|
ecs_table_t *node,
|
|
ecs_id_t *id_ptr,
|
|
ecs_table_diff_t *diff)
|
|
{
|
|
ecs_poly_assert(world, ecs_world_t);
|
|
|
|
node = node ? node : &world->store.root;
|
|
|
|
/* Removing 0 from an entity is not valid */
|
|
ecs_check(id_ptr != NULL, ECS_INVALID_PARAMETER, NULL);
|
|
ecs_check(id_ptr[0] != 0, ECS_INVALID_PARAMETER, NULL);
|
|
|
|
ecs_id_t id = id_ptr[0];
|
|
ecs_graph_edge_t *edge = flecs_table_ensure_edge(world, &node->node.remove, id);
|
|
ecs_table_t *to = edge->to;
|
|
|
|
if (!to) {
|
|
to = flecs_create_edge_for_remove(world, node, edge, id);
|
|
ecs_assert(to != NULL, ECS_INTERNAL_ERROR, NULL);
|
|
ecs_assert(edge->to != NULL, ECS_INTERNAL_ERROR, NULL);
|
|
}
|
|
|
|
if (node != to) {
|
|
if (edge->diff) {
|
|
*diff = *edge->diff;
|
|
} else {
|
|
diff->added.count = 0;
|
|
diff->removed.array = id_ptr;
|
|
diff->removed.count = 1;
|
|
}
|
|
}
|
|
|
|
return to;
|
|
error:
|
|
return NULL;
|
|
}
|
|
|
|
ecs_table_t* flecs_table_traverse_add(
|
|
ecs_world_t *world,
|
|
ecs_table_t *node,
|
|
ecs_id_t *id_ptr,
|
|
ecs_table_diff_t *diff)
|
|
{
|
|
ecs_poly_assert(world, ecs_world_t);
|
|
ecs_assert(diff != NULL, ECS_INTERNAL_ERROR, NULL);
|
|
|
|
node = node ? node : &world->store.root;
|
|
|
|
/* Adding 0 to an entity is not valid */
|
|
ecs_check(id_ptr != NULL, ECS_INVALID_PARAMETER, NULL);
|
|
ecs_check(id_ptr[0] != 0, ECS_INVALID_PARAMETER, NULL);
|
|
|
|
ecs_id_t id = id_ptr[0];
|
|
ecs_graph_edge_t *edge = flecs_table_ensure_edge(world, &node->node.add, id);
|
|
ecs_table_t *to = edge->to;
|
|
|
|
if (!to) {
|
|
to = flecs_create_edge_for_add(world, node, edge, id);
|
|
ecs_assert(to != NULL, ECS_INTERNAL_ERROR, NULL);
|
|
ecs_assert(edge->to != NULL, ECS_INTERNAL_ERROR, NULL);
|
|
}
|
|
|
|
if (node != to || edge->diff) {
|
|
if (edge->diff) {
|
|
*diff = *edge->diff;
|
|
} else {
|
|
diff->added.array = id_ptr;
|
|
diff->added.count = 1;
|
|
diff->removed.count = 0;
|
|
}
|
|
}
|
|
|
|
return to;
|
|
error:
|
|
return NULL;
|
|
}
|
|
|
|
ecs_table_t* flecs_table_find_or_create(
|
|
ecs_world_t *world,
|
|
ecs_type_t *type)
|
|
{
|
|
ecs_poly_assert(world, ecs_world_t);
|
|
return flecs_table_ensure(world, type, false, NULL);
|
|
}
|
|
|
|
void flecs_init_root_table(
|
|
ecs_world_t *world)
|
|
{
|
|
ecs_poly_assert(world, ecs_world_t);
|
|
|
|
world->store.root.type = (ecs_type_t){0};
|
|
world->store.root._ = flecs_calloc_t(&world->allocator, ecs_table__t);
|
|
flecs_init_table(world, &world->store.root, NULL);
|
|
|
|
/* Ensure table indices start at 1, as 0 is reserved for the root */
|
|
uint64_t new_id = flecs_sparse_new_id(&world->store.tables);
|
|
ecs_assert(new_id == 0, ECS_INTERNAL_ERROR, NULL);
|
|
(void)new_id;
|
|
}
|
|
|
|
void flecs_table_clear_edges(
|
|
ecs_world_t *world,
|
|
ecs_table_t *table)
|
|
{
|
|
(void)world;
|
|
ecs_poly_assert(world, ecs_world_t);
|
|
|
|
ecs_log_push_1();
|
|
|
|
ecs_map_iter_t it;
|
|
ecs_graph_node_t *table_node = &table->node;
|
|
ecs_graph_edges_t *node_add = &table_node->add;
|
|
ecs_graph_edges_t *node_remove = &table_node->remove;
|
|
ecs_map_t *add_hi = node_add->hi;
|
|
ecs_map_t *remove_hi = node_remove->hi;
|
|
ecs_graph_edge_hdr_t *node_refs = &table_node->refs;
|
|
|
|
/* Cleanup outgoing edges */
|
|
it = ecs_map_iter(add_hi);
|
|
while (ecs_map_next(&it)) {
|
|
flecs_table_disconnect_edge(world, ecs_map_key(&it), ecs_map_ptr(&it));
|
|
}
|
|
|
|
it = ecs_map_iter(remove_hi);
|
|
while (ecs_map_next(&it)) {
|
|
flecs_table_disconnect_edge(world, ecs_map_key(&it), ecs_map_ptr(&it));
|
|
}
|
|
|
|
/* Cleanup incoming add edges */
|
|
ecs_graph_edge_hdr_t *next, *cur = node_refs->next;
|
|
if (cur) {
|
|
do {
|
|
ecs_graph_edge_t *edge = (ecs_graph_edge_t*)cur;
|
|
ecs_assert(edge->to == table, ECS_INTERNAL_ERROR, NULL);
|
|
ecs_assert(edge->from != NULL, ECS_INTERNAL_ERROR, NULL);
|
|
next = cur->next;
|
|
flecs_table_remove_edge(world, &edge->from->node.add, edge->id, edge);
|
|
} while ((cur = next));
|
|
}
|
|
|
|
/* Cleanup incoming remove edges */
|
|
cur = node_refs->prev;
|
|
if (cur) {
|
|
do {
|
|
ecs_graph_edge_t *edge = (ecs_graph_edge_t*)cur;
|
|
ecs_assert(edge->to == table, ECS_INTERNAL_ERROR, NULL);
|
|
ecs_assert(edge->from != NULL, ECS_INTERNAL_ERROR, NULL);
|
|
next = cur->prev;
|
|
flecs_table_remove_edge(world, &edge->from->node.remove, edge->id, edge);
|
|
} while ((cur = next));
|
|
}
|
|
|
|
if (node_add->lo) {
|
|
flecs_bfree(&world->allocators.graph_edge_lo, node_add->lo);
|
|
}
|
|
if (node_remove->lo) {
|
|
flecs_bfree(&world->allocators.graph_edge_lo, node_remove->lo);
|
|
}
|
|
|
|
ecs_map_fini(add_hi);
|
|
ecs_map_fini(remove_hi);
|
|
flecs_free_t(&world->allocator, ecs_map_t, add_hi);
|
|
flecs_free_t(&world->allocator, ecs_map_t, remove_hi);
|
|
table_node->add.lo = NULL;
|
|
table_node->remove.lo = NULL;
|
|
table_node->add.hi = NULL;
|
|
table_node->remove.hi = NULL;
|
|
|
|
ecs_log_pop_1();
|
|
}
|
|
|
|
/* Public convenience functions for traversing table graph */
|
|
ecs_table_t* ecs_table_add_id(
|
|
ecs_world_t *world,
|
|
ecs_table_t *table,
|
|
ecs_id_t id)
|
|
{
|
|
ecs_table_diff_t diff;
|
|
return flecs_table_traverse_add(world, table, &id, &diff);
|
|
}
|
|
|
|
ecs_table_t* ecs_table_remove_id(
|
|
ecs_world_t *world,
|
|
ecs_table_t *table,
|
|
ecs_id_t id)
|
|
{
|
|
ecs_table_diff_t diff;
|
|
return flecs_table_traverse_remove(world, table, &id, &diff);
|
|
}
|
|
|
|
ecs_table_t* ecs_table_find(
|
|
ecs_world_t *world,
|
|
const ecs_id_t *ids,
|
|
int32_t id_count)
|
|
{
|
|
ecs_type_t type = {
|
|
.array = ECS_CONST_CAST(ecs_id_t*, ids),
|
|
.count = id_count
|
|
};
|
|
return flecs_table_ensure(world, &type, false, NULL);
|
|
}
|