Files
PixelDefense/engine/libs/flecs/src/storage/id_index.c

615 lines
18 KiB
C

/**
* @file id_index.c
* @brief Index for looking up tables by (component) id.
*
* An id record stores the administration for an in use (component) id, that is
* an id that has been used in tables.
*
* An id record contains a table cache, which stores the list of tables that
* have the id. Each entry in the cache (a table record) stores the first
* occurrence of the id in the table and the number of occurrences of the id in
* the table (in the case of wildcard ids).
*
* Id records are used in lots of scenarios, like uncached queries, or for
* getting a component array/component for an entity.
*/
#include "../private_api.h"
static
ecs_id_record_elem_t* flecs_id_record_elem(
ecs_id_record_t *head,
ecs_id_record_elem_t *list,
ecs_id_record_t *idr)
{
return ECS_OFFSET(idr, (uintptr_t)list - (uintptr_t)head);
}
static
void flecs_id_record_elem_insert(
ecs_id_record_t *head,
ecs_id_record_t *idr,
ecs_id_record_elem_t *elem)
{
ecs_id_record_elem_t *head_elem = flecs_id_record_elem(idr, elem, head);
ecs_id_record_t *cur = head_elem->next;
elem->next = cur;
elem->prev = head;
if (cur) {
ecs_id_record_elem_t *cur_elem = flecs_id_record_elem(idr, elem, cur);
cur_elem->prev = idr;
}
head_elem->next = idr;
}
static
void flecs_id_record_elem_remove(
ecs_id_record_t *idr,
ecs_id_record_elem_t *elem)
{
ecs_id_record_t *prev = elem->prev;
ecs_id_record_t *next = elem->next;
ecs_assert(prev != NULL, ECS_INTERNAL_ERROR, NULL);
ecs_id_record_elem_t *prev_elem = flecs_id_record_elem(idr, elem, prev);
prev_elem->next = next;
if (next) {
ecs_id_record_elem_t *next_elem = flecs_id_record_elem(idr, elem, next);
next_elem->prev = prev;
}
}
static
void flecs_insert_id_elem(
ecs_world_t *world,
ecs_id_record_t *idr,
ecs_id_t wildcard,
ecs_id_record_t *widr)
{
ecs_assert(ecs_id_is_wildcard(wildcard), ECS_INTERNAL_ERROR, NULL);
if (!widr) {
widr = flecs_id_record_ensure(world, wildcard);
}
ecs_assert(widr != NULL, ECS_INTERNAL_ERROR, NULL);
if (ECS_PAIR_SECOND(wildcard) == EcsWildcard) {
ecs_assert(ECS_PAIR_FIRST(wildcard) != EcsWildcard,
ECS_INTERNAL_ERROR, NULL);
flecs_id_record_elem_insert(widr, idr, &idr->first);
} else {
ecs_assert(ECS_PAIR_FIRST(wildcard) == EcsWildcard,
ECS_INTERNAL_ERROR, NULL);
flecs_id_record_elem_insert(widr, idr, &idr->second);
if (idr->flags & EcsIdTraversable) {
flecs_id_record_elem_insert(widr, idr, &idr->trav);
}
}
}
static
void flecs_remove_id_elem(
ecs_id_record_t *idr,
ecs_id_t wildcard)
{
ecs_assert(ecs_id_is_wildcard(wildcard), ECS_INTERNAL_ERROR, NULL);
if (ECS_PAIR_SECOND(wildcard) == EcsWildcard) {
ecs_assert(ECS_PAIR_FIRST(wildcard) != EcsWildcard,
ECS_INTERNAL_ERROR, NULL);
flecs_id_record_elem_remove(idr, &idr->first);
} else {
ecs_assert(ECS_PAIR_FIRST(wildcard) == EcsWildcard,
ECS_INTERNAL_ERROR, NULL);
flecs_id_record_elem_remove(idr, &idr->second);
if (idr->flags & EcsIdTraversable) {
flecs_id_record_elem_remove(idr, &idr->trav);
}
}
}
static
ecs_id_t flecs_id_record_hash(
ecs_id_t id)
{
id = ecs_strip_generation(id);
if (ECS_IS_PAIR(id)) {
ecs_entity_t r = ECS_PAIR_FIRST(id);
ecs_entity_t o = ECS_PAIR_SECOND(id);
if (r == EcsAny) {
r = EcsWildcard;
}
if (o == EcsAny) {
o = EcsWildcard;
}
id = ecs_pair(r, o);
}
return id;
}
static
ecs_id_record_t* flecs_id_record_new(
ecs_world_t *world,
ecs_id_t id)
{
ecs_id_record_t *idr, *idr_t = NULL;
ecs_id_t hash = flecs_id_record_hash(id);
if (hash >= FLECS_HI_ID_RECORD_ID) {
idr = flecs_bcalloc(&world->allocators.id_record);
ecs_map_insert_ptr(&world->id_index_hi, hash, idr);
} else {
idr = &world->id_index_lo[hash];
ecs_os_zeromem(idr);
}
ecs_table_cache_init(world, &idr->cache);
idr->id = id;
idr->refcount = 1;
idr->reachable.current = -1;
bool is_wildcard = ecs_id_is_wildcard(id);
bool is_pair = ECS_IS_PAIR(id);
ecs_entity_t rel = 0, tgt = 0, role = id & ECS_ID_FLAGS_MASK;
if (is_pair) {
// rel = ecs_pair_first(world, id);
rel = ECS_PAIR_FIRST(id);
ecs_assert(rel != 0, ECS_INTERNAL_ERROR, NULL);
/* Relationship object can be 0, as tables without a ChildOf
* relationship are added to the (ChildOf, 0) id record */
tgt = ECS_PAIR_SECOND(id);
#ifdef FLECS_DEBUG
/* Check constraints */
if (tgt) {
tgt = ecs_get_alive(world, tgt);
ecs_assert(tgt != 0, ECS_INTERNAL_ERROR, NULL);
}
if (tgt && !ecs_id_is_wildcard(tgt)) {
/* Check if target of relationship satisfies OneOf property */
ecs_entity_t oneof = flecs_get_oneof(world, rel);
ecs_check( !oneof || ecs_has_pair(world, tgt, EcsChildOf, oneof),
ECS_CONSTRAINT_VIOLATED, NULL);
(void)oneof;
/* Check if we're not trying to inherit from a final target */
if (rel == EcsIsA) {
bool is_final = ecs_has_id(world, tgt, EcsFinal);
ecs_check(!is_final, ECS_CONSTRAINT_VIOLATED,
"cannot inherit from final entity");
(void)is_final;
}
}
#endif
if (!is_wildcard && (rel != EcsFlag)) {
/* Inherit flags from (relationship, *) record */
ecs_id_record_t *idr_r = flecs_id_record_ensure(
world, ecs_pair(rel, EcsWildcard));
idr->parent = idr_r;
idr->flags = idr_r->flags;
/* If pair is not a wildcard, append it to wildcard lists. These
* allow for quickly enumerating all relationships for an object,
* or all objecs for a relationship. */
flecs_insert_id_elem(world, idr, ecs_pair(rel, EcsWildcard), idr_r);
idr_t = flecs_id_record_ensure(world, ecs_pair(EcsWildcard, tgt));
flecs_insert_id_elem(world, idr, ecs_pair(EcsWildcard, tgt), idr_t);
if (rel == EcsUnion) {
idr->flags |= EcsIdUnion;
}
}
} else {
rel = id & ECS_COMPONENT_MASK;
ecs_assert(rel != 0, ECS_INTERNAL_ERROR, NULL);
}
/* Initialize type info if id is not a tag */
if (!is_wildcard && (!role || is_pair)) {
if (!(idr->flags & EcsIdTag)) {
const ecs_type_info_t *ti = flecs_type_info_get(world, rel);
if (!ti && tgt) {
ti = flecs_type_info_get(world, tgt);
}
idr->type_info = ti;
}
}
/* Mark entities that are used as component/pair ids. When a tracked
* entity is deleted, cleanup policies are applied so that the store
* won't contain any tables with deleted ids. */
/* Flag for OnDelete policies */
flecs_add_flag(world, rel, EcsEntityIsId);
if (tgt) {
/* Flag for OnDeleteTarget policies */
ecs_record_t *tgt_r = flecs_entities_get_any(world, tgt);
ecs_assert(tgt_r != NULL, ECS_INTERNAL_ERROR, NULL);
flecs_record_add_flag(tgt_r, EcsEntityIsTarget);
if (idr->flags & EcsIdTraversable) {
/* Flag used to determine if object should be traversed when
* propagating events or with super/subset queries */
flecs_record_add_flag(tgt_r, EcsEntityIsTraversable);
/* Add reference to (*, tgt) id record to entity record */
tgt_r->idr = idr_t;
}
}
ecs_observable_t *o = &world->observable;
idr->flags |= flecs_observers_exist(o, id, EcsOnAdd) * EcsIdHasOnAdd;
idr->flags |= flecs_observers_exist(o, id, EcsOnRemove) * EcsIdHasOnRemove;
idr->flags |= flecs_observers_exist(o, id, EcsOnSet) * EcsIdHasOnSet;
idr->flags |= flecs_observers_exist(o, id, EcsUnSet) * EcsIdHasUnSet;
idr->flags |= flecs_observers_exist(o, id, EcsOnTableFill) * EcsIdHasOnTableFill;
idr->flags |= flecs_observers_exist(o, id, EcsOnTableEmpty) * EcsIdHasOnTableEmpty;
idr->flags |= flecs_observers_exist(o, id, EcsOnTableCreate) * EcsIdHasOnTableCreate;
idr->flags |= flecs_observers_exist(o, id, EcsOnTableDelete) * EcsIdHasOnTableDelete;
if (ecs_should_log_1()) {
char *id_str = ecs_id_str(world, id);
ecs_dbg_1("#[green]id#[normal] %s #[green]created", id_str);
ecs_os_free(id_str);
}
/* Update counters */
world->info.id_create_total ++;
if (!is_wildcard) {
world->info.id_count ++;
if (idr->type_info) {
world->info.component_id_count ++;
} else {
world->info.tag_id_count ++;
}
if (is_pair) {
world->info.pair_id_count ++;
}
} else {
world->info.wildcard_id_count ++;
}
return idr;
#ifdef FLECS_DEBUG
error:
return NULL;
#endif
}
static
void flecs_id_record_assert_empty(
ecs_id_record_t *idr)
{
(void)idr;
ecs_assert(flecs_table_cache_count(&idr->cache) == 0,
ECS_INTERNAL_ERROR, NULL);
ecs_assert(flecs_table_cache_empty_count(&idr->cache) == 0,
ECS_INTERNAL_ERROR, NULL);
}
static
void flecs_id_record_free(
ecs_world_t *world,
ecs_id_record_t *idr)
{
ecs_poly_assert(world, ecs_world_t);
ecs_assert(idr != NULL, ECS_INTERNAL_ERROR, NULL);
ecs_id_t id = idr->id;
flecs_id_record_assert_empty(idr);
/* Id is still in use by a filter, query, rule or observer */
ecs_assert((world->flags & EcsWorldQuit) || (idr->keep_alive == 0),
ECS_ID_IN_USE, "cannot delete id that is queried for");
if (ECS_IS_PAIR(id)) {
ecs_entity_t rel = ECS_PAIR_FIRST(id);
ecs_entity_t tgt = ECS_PAIR_SECOND(id);
if (!ecs_id_is_wildcard(id)) {
if (ECS_PAIR_FIRST(id) != EcsFlag) {
/* If id is not a wildcard, remove it from the wildcard lists */
flecs_remove_id_elem(idr, ecs_pair(rel, EcsWildcard));
flecs_remove_id_elem(idr, ecs_pair(EcsWildcard, tgt));
}
} else {
ecs_log_push_2();
/* If id is a wildcard, it means that all id records that match the
* wildcard are also empty, so release them */
if (ECS_PAIR_FIRST(id) == EcsWildcard) {
/* Iterate (*, Target) list */
ecs_id_record_t *cur, *next = idr->second.next;
while ((cur = next)) {
flecs_id_record_assert_empty(cur);
next = cur->second.next;
flecs_id_record_release(world, cur);
}
} else {
/* Iterate (Relationship, *) list */
ecs_assert(ECS_PAIR_SECOND(id) == EcsWildcard,
ECS_INTERNAL_ERROR, NULL);
ecs_id_record_t *cur, *next = idr->first.next;
while ((cur = next)) {
flecs_id_record_assert_empty(cur);
next = cur->first.next;
flecs_id_record_release(world, cur);
}
}
ecs_log_pop_2();
}
}
/* Update counters */
world->info.id_delete_total ++;
if (!ecs_id_is_wildcard(id)) {
world->info.id_count --;
if (ECS_IS_PAIR(id)) {
world->info.pair_id_count --;
}
if (idr->type_info) {
world->info.component_id_count --;
} else {
world->info.tag_id_count --;
}
} else {
world->info.wildcard_id_count --;
}
/* Unregister the id record from the world & free resources */
ecs_table_cache_fini(&idr->cache);
flecs_name_index_free(idr->name_index);
ecs_vec_fini_t(&world->allocator, &idr->reachable.ids, ecs_reachable_elem_t);
ecs_id_t hash = flecs_id_record_hash(id);
if (hash >= FLECS_HI_ID_RECORD_ID) {
ecs_map_remove(&world->id_index_hi, hash);
flecs_bfree(&world->allocators.id_record, idr);
} else {
idr->id = 0; /* Tombstone */
}
if (ecs_should_log_1()) {
char *id_str = ecs_id_str(world, id);
ecs_dbg_1("#[green]id#[normal] %s #[red]deleted", id_str);
ecs_os_free(id_str);
}
}
ecs_id_record_t* flecs_id_record_ensure(
ecs_world_t *world,
ecs_id_t id)
{
ecs_id_record_t *idr = flecs_id_record_get(world, id);
if (!idr) {
idr = flecs_id_record_new(world, id);
}
return idr;
}
ecs_id_record_t* flecs_id_record_get(
const ecs_world_t *world,
ecs_id_t id)
{
ecs_poly_assert(world, ecs_world_t);
if (id == ecs_pair(EcsIsA, EcsWildcard)) {
return world->idr_isa_wildcard;
} else if (id == ecs_pair(EcsChildOf, EcsWildcard)) {
return world->idr_childof_wildcard;
} else if (id == ecs_pair_t(EcsIdentifier, EcsName)) {
return world->idr_identifier_name;
}
ecs_id_t hash = flecs_id_record_hash(id);
ecs_id_record_t *idr = NULL;
if (hash >= FLECS_HI_ID_RECORD_ID) {
idr = ecs_map_get_deref(&world->id_index_hi, ecs_id_record_t, hash);
} else {
idr = &world->id_index_lo[hash];
if (!idr->id) {
idr = NULL;
}
}
return idr;
}
ecs_id_record_t* flecs_query_id_record_get(
const ecs_world_t *world,
ecs_id_t id)
{
ecs_id_record_t *idr = flecs_id_record_get(world, id);
if (!idr) {
ecs_entity_t first = ECS_PAIR_FIRST(id);
if (ECS_IS_PAIR(id) && (first != EcsWildcard)) {
idr = flecs_id_record_get(world, ecs_pair(EcsUnion, first));
}
return idr;
}
if (ECS_IS_PAIR(id) &&
ECS_PAIR_SECOND(id) == EcsWildcard &&
(idr->flags & EcsIdUnion))
{
idr = flecs_id_record_get(world,
ecs_pair(EcsUnion, ECS_PAIR_FIRST(id)));
}
return idr;
}
void flecs_id_record_claim(
ecs_world_t *world,
ecs_id_record_t *idr)
{
(void)world;
idr->refcount ++;
}
int32_t flecs_id_record_release(
ecs_world_t *world,
ecs_id_record_t *idr)
{
int32_t rc = -- idr->refcount;
ecs_assert(rc >= 0, ECS_INTERNAL_ERROR, NULL);
if (!rc) {
flecs_id_record_free(world, idr);
}
return rc;
}
void flecs_id_record_release_tables(
ecs_world_t *world,
ecs_id_record_t *idr)
{
/* Cache should not contain tables that aren't empty */
ecs_assert(flecs_table_cache_count(&idr->cache) == 0,
ECS_INTERNAL_ERROR, NULL);
ecs_table_cache_iter_t it;
if (flecs_table_cache_empty_iter(&idr->cache, &it)) {
ecs_table_record_t *tr;
while ((tr = flecs_table_cache_next(&it, ecs_table_record_t))) {
/* Release current table */
flecs_table_free(world, tr->hdr.table);
}
}
}
bool flecs_id_record_set_type_info(
ecs_world_t *world,
ecs_id_record_t *idr,
const ecs_type_info_t *ti)
{
bool is_wildcard = ecs_id_is_wildcard(idr->id);
if (!is_wildcard) {
if (ti) {
if (!idr->type_info) {
world->info.tag_id_count --;
world->info.component_id_count ++;
}
} else {
if (idr->type_info) {
world->info.tag_id_count ++;
world->info.component_id_count --;
}
}
}
bool changed = idr->type_info != ti;
idr->type_info = ti;
return changed;
}
ecs_hashmap_t* flecs_id_record_name_index_ensure(
ecs_world_t *world,
ecs_id_record_t *idr)
{
ecs_hashmap_t *map = idr->name_index;
if (!map) {
map = idr->name_index = flecs_name_index_new(world, &world->allocator);
}
return map;
}
ecs_hashmap_t* flecs_id_name_index_ensure(
ecs_world_t *world,
ecs_id_t id)
{
ecs_poly_assert(world, ecs_world_t);
ecs_id_record_t *idr = flecs_id_record_get(world, id);
ecs_assert(idr != NULL, ECS_INTERNAL_ERROR, NULL);
return flecs_id_record_name_index_ensure(world, idr);
}
ecs_hashmap_t* flecs_id_name_index_get(
const ecs_world_t *world,
ecs_id_t id)
{
ecs_poly_assert(world, ecs_world_t);
ecs_id_record_t *idr = flecs_id_record_get(world, id);
if (!idr) {
return NULL;
}
return idr->name_index;
}
ecs_table_record_t* flecs_table_record_get(
const ecs_world_t *world,
const ecs_table_t *table,
ecs_id_t id)
{
ecs_poly_assert(world, ecs_world_t);
ecs_id_record_t* idr = flecs_id_record_get(world, id);
if (!idr) {
return NULL;
}
return (ecs_table_record_t*)ecs_table_cache_get(&idr->cache, table);
}
ecs_table_record_t* flecs_id_record_get_table(
const ecs_id_record_t *idr,
const ecs_table_t *table)
{
ecs_assert(idr != NULL, ECS_INTERNAL_ERROR, NULL);
return (ecs_table_record_t*)ecs_table_cache_get(&idr->cache, table);
}
void flecs_init_id_records(
ecs_world_t *world)
{
/* Cache often used id records on world */
world->idr_wildcard = flecs_id_record_ensure(world, EcsWildcard);
world->idr_wildcard_wildcard = flecs_id_record_ensure(world,
ecs_pair(EcsWildcard, EcsWildcard));
world->idr_any = flecs_id_record_ensure(world, EcsAny);
world->idr_isa_wildcard = flecs_id_record_ensure(world,
ecs_pair(EcsIsA, EcsWildcard));
}
void flecs_fini_id_records(
ecs_world_t *world)
{
/* Loop & delete first element until there are no elements left. Id records
* can recursively delete each other, this ensures we always have a
* valid iterator. */
while (ecs_map_count(&world->id_index_hi) > 0) {
ecs_map_iter_t it = ecs_map_iter(&world->id_index_hi);
ecs_map_next(&it);
flecs_id_record_release(world, ecs_map_ptr(&it));
}
int32_t i;
for (i = 0; i < FLECS_HI_ID_RECORD_ID; i ++) {
ecs_id_record_t *idr = &world->id_index_lo[i];
if (idr->id) {
flecs_id_record_release(world, idr);
}
}
ecs_assert(ecs_map_count(&world->id_index_hi) == 0,
ECS_INTERNAL_ERROR, NULL);
ecs_map_fini(&world->id_index_hi);
ecs_os_free(world->id_index_lo);
}