Skip to content

Commit 2edca0d

Browse files
committed
Make component inheritance queries cacheable
1 parent f0787c0 commit 2edca0d

11 files changed

Lines changed: 289 additions & 2638 deletions

File tree

distr/flecs.c

Lines changed: 99 additions & 1519 deletions
Large diffs are not rendered by default.

distr/flecs.h

Lines changed: 0 additions & 1092 deletions
Large diffs are not rendered by default.

examples/c/queries/component_inheritance/src/main.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,9 +57,9 @@ int main(int argc, char *argv[]) {
5757
void *bufs = ecs_base_field(&it, Buf, 0);
5858
size_t stride = ecs_field_stride(&it, 0);
5959
for (int i = 0; i < it.count; i ++) {
60-
Buf *buf = ECS_OFFSET(bufs, stride * i);
60+
Buf *buf = ECS_OFFSET(bufs, stride * (size_t)i);
6161
printf("%s has buf value %.0f\n",
62-
ecs_get_name(ecs, it.entities[i]), buf->value);
62+
ecs_get_name(ecs, it.entities[i]), (double)buf->value);
6363
}
6464
}
6565

src/query/cache/cache.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -625,6 +625,7 @@ ecs_query_cache_t* flecs_query_cache_init(
625625

626626
if ((t == count) && (q->flags & EcsQueryMatchOnlySelf) &&
627627
!(q->flags & EcsQueryMatchWildcards) &&
628+
!(q->flags & EcsQueryHasComponentInheritance) &&
628629
!(q->flags & EcsQueryCacheWithFilter))
629630
{
630631
if (!const_desc->order_by && !const_desc->group_by &&

src/query/validator.c

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -978,7 +978,6 @@ int flecs_term_finalize(
978978

979979
if (term->flags_ & EcsTermIdInherited) {
980980
trivial_term = false;
981-
cacheable_term = false;
982981
}
983982

984983
if (term->flags_ & EcsTermReflexive) {
@@ -1714,6 +1713,7 @@ bool flecs_query_finalize_simple(
17141713
/* Populate terms */
17151714
bool has_this = false, has_only_this = true;
17161715
int8_t cacheable_count = 0, trivial_count = 0, up_count = 0;
1716+
int8_t inherited_count = 0;
17171717
for (i = 0; i < term_count; i ++) {
17181718
ecs_term_t *term = &q->terms[i];
17191719
ecs_id_t id = term->id;
@@ -1829,7 +1829,8 @@ bool flecs_query_finalize_simple(
18291829
if (flecs_components_get(world, ecs_pair(EcsIsA, first)) != NULL) {
18301830
term->flags_ |= EcsTermIdInherited;
18311831
q->flags |= EcsQueryHasComponentInheritance;
1832-
cacheable = false; trivial = false;
1832+
trivial = false;
1833+
inherited_count ++;
18331834
}
18341835

18351836
if (cacheable) {
@@ -1868,8 +1869,14 @@ bool flecs_query_finalize_simple(
18681869
q->flags |= EcsQueryHasCacheable;
18691870
}
18701871

1871-
if (cacheable_count == term_count && trivial_count == term_count) {
1872-
q->flags |= EcsQueryIsCacheable|EcsQueryIsTrivial;
1872+
if (cacheable_count == term_count &&
1873+
(trivial_count + inherited_count) == term_count)
1874+
{
1875+
q->flags |= EcsQueryIsCacheable;
1876+
}
1877+
1878+
if (trivial_count == term_count) {
1879+
q->flags |= EcsQueryIsTrivial;
18731880
}
18741881

18751882
if (!up_count) {

src/storage/table.c

Lines changed: 83 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -530,9 +530,14 @@ void flecs_table_emit(
530530
ecs_table_t *table,
531531
ecs_entity_t event)
532532
{
533+
ecs_type_t *ids = &table->type;
534+
if (table->_->extended_type) {
535+
ids = table->_->extended_type;
536+
}
537+
533538
ecs_defer_begin(world);
534539
flecs_emit(world, world, &(ecs_event_desc_t) {
535-
.ids = &table->type,
540+
.ids = ids,
536541
.event = event,
537542
.table = table,
538543
.flags = EcsEventTableOnly,
@@ -548,7 +553,8 @@ bool flecs_table_register_inherited_for_base(
548553
int32_t type_index,
549554
int16_t column,
550555
ecs_id_t base_id,
551-
ecs_vec_t *inherited)
556+
ecs_vec_t *inherited,
557+
ecs_vec_t *inherited_wild)
552558
{
553559
ecs_component_record_t *cr = flecs_components_ensure(world, base_id);
554560

@@ -576,7 +582,8 @@ bool flecs_table_register_inherited_for_base(
576582
ecs_table_cache_insert(&cr->cache, table, &tr->hdr);
577583
flecs_component_claim(world, cr);
578584

579-
*ecs_vec_append_t(&world->allocator, inherited, ecs_table_record_t*) = tr;
585+
ecs_vec_t *dst = ecs_id_is_wildcard(base_id) ? inherited_wild : inherited;
586+
*ecs_vec_append_t(&world->allocator, dst, ecs_table_record_t*) = tr;
580587

581588
if (base_id < FLECS_HI_COMPONENT_ID) {
582589
world->non_trivial_lookup[base_id] |= EcsNonTrivialIdInherit;
@@ -593,7 +600,8 @@ void flecs_table_register_inherited_bases(
593600
int16_t column,
594601
ecs_entity_t rel,
595602
ecs_entity_t tgt,
596-
ecs_vec_t *inherited)
603+
ecs_vec_t *inherited,
604+
ecs_vec_t *inherited_wild)
597605
{
598606
ecs_record_t *r = flecs_entities_get(world, rel);
599607
if (!r) {
@@ -621,19 +629,21 @@ void flecs_table_register_inherited_bases(
621629

622630
ecs_id_t base_id = tgt ? ecs_pair(base, tgt) : base;
623631
bool recurse = flecs_table_register_inherited_for_base(
624-
world, table, type_index, column, base_id, inherited);
632+
world, table, type_index, column, base_id, inherited,
633+
inherited_wild);
625634

626635
if (tgt) {
627636
if (flecs_table_register_inherited_for_base(world, table,
628-
type_index, column, ecs_pair(base, EcsWildcard), inherited))
637+
type_index, column, ecs_pair(base, EcsWildcard), inherited,
638+
inherited_wild))
629639
{
630640
recurse = true;
631641
}
632642
}
633643

634644
if (recurse) {
635645
flecs_table_register_inherited_bases(world, table, type_index,
636-
column, base, tgt, inherited);
646+
column, base, tgt, inherited, inherited_wild);
637647
}
638648
}
639649
}
@@ -648,8 +658,9 @@ void flecs_table_register_inherited(
648658
return;
649659
}
650660

651-
ecs_vec_t inherited;
661+
ecs_vec_t inherited, inherited_wild;
652662
ecs_vec_init_t(&world->allocator, &inherited, ecs_table_record_t*, 0);
663+
ecs_vec_init_t(&world->allocator, &inherited_wild, ecs_table_record_t*, 0);
653664

654665
ecs_type_t type = table->type;
655666
int32_t ti, count = type.count;
@@ -670,21 +681,42 @@ void flecs_table_register_inherited(
670681

671682
int16_t column = table->_->records[ti].column;
672683
flecs_table_register_inherited_bases(
673-
world, table, ti, column, rel, tgt, &inherited);
684+
world, table, ti, column, rel, tgt, &inherited, &inherited_wild);
674685
}
675686

676-
int32_t n = ecs_vec_count(&inherited);
687+
int32_t nw = ecs_vec_count(&inherited);
688+
int32_t ww = ecs_vec_count(&inherited_wild);
689+
int32_t n = nw + ww;
677690
if (n) {
678-
int32_t i, old = table->_->record_count;
691+
/* Inherited records are inserted right after the table's own id records
692+
* so that the wildcard records remain at the end. Non-wildcard
693+
* inherited records (the base component ids) are placed first, followed
694+
* by the existing wildcard records and the inherited wildcard records.
695+
* This ensures the extended_type array (component ids + base ids)
696+
* mirrors the order of the records array and excludes wildcards. */
697+
int32_t i, type_count = type.count;
698+
int32_t old = table->_->record_count;
699+
int32_t aux = old - type_count;
679700
ecs_table_record_t *old_records = table->_->records;
680701
ecs_table_record_t *records = flecs_walloc_n(
681702
world, ecs_table_record_t, old + n);
682-
ecs_os_memcpy_n(records, old_records, ecs_table_record_t, old);
683703

684-
ecs_table_record_t **inherited_records = ecs_vec_first_t(
704+
ecs_table_record_t **nw_records = ecs_vec_first_t(
685705
&inherited, ecs_table_record_t*);
686-
for (i = 0; i < n; i ++) {
687-
records[old + i] = *inherited_records[i];
706+
ecs_table_record_t **ww_records = ecs_vec_first_t(
707+
&inherited_wild, ecs_table_record_t*);
708+
709+
ecs_os_memcpy_n(records, old_records, ecs_table_record_t, type_count);
710+
711+
for (i = 0; i < nw; i ++) {
712+
records[type_count + i] = *nw_records[i];
713+
}
714+
715+
ecs_os_memcpy_n(&records[type_count + nw], &old_records[type_count],
716+
ecs_table_record_t, aux);
717+
718+
for (i = 0; i < ww; i ++) {
719+
records[type_count + nw + aux + i] = *ww_records[i];
688720
}
689721

690722
table->_->records = records;
@@ -693,16 +725,44 @@ void flecs_table_register_inherited(
693725
for (i = 0; i < old + n; i ++) {
694726
ecs_table_record_t *tr = &records[i];
695727
ecs_table_cache_replace(&tr->hdr.cr->cache, table, &tr->hdr);
728+
729+
/* Propagate table event flags for inherited base components, so
730+
* that OnTableCreate/OnTableDelete events are emitted for tables
731+
* with derived components (used to notify query caches). */
732+
table->flags |= tr->hdr.cr->flags &
733+
(EcsIdHasOnTableCreate | EcsIdHasOnTableDelete);
734+
}
735+
736+
/* Build extended type with component ids + non-wildcard base ids */
737+
if (nw) {
738+
int32_t et_count = type_count + nw;
739+
ecs_type_t *et = flecs_walloc_t(world, ecs_type_t);
740+
et->count = et_count;
741+
et->array = flecs_walloc_n(world, ecs_id_t, et_count);
742+
for (i = 0; i < type_count; i ++) {
743+
et->array[i] = type.array[i];
744+
}
745+
for (i = 0; i < nw; i ++) {
746+
ecs_id_t base_id = records[type_count + i].hdr.cr->id;
747+
et->array[type_count + i] = base_id;
748+
table->bloom_filter = flecs_table_bloom_filter_add(
749+
table->bloom_filter, base_id);
750+
}
751+
table->_->extended_type = et;
696752
}
697753

698-
for (i = 0; i < n; i ++) {
699-
flecs_wfree_t(world, ecs_table_record_t, inherited_records[i]);
754+
for (i = 0; i < nw; i ++) {
755+
flecs_wfree_t(world, ecs_table_record_t, nw_records[i]);
756+
}
757+
for (i = 0; i < ww; i ++) {
758+
flecs_wfree_t(world, ecs_table_record_t, ww_records[i]);
700759
}
701760

702761
flecs_wfree_n(world, ecs_table_record_t, old, old_records);
703762
}
704763

705764
ecs_vec_fini_t(&world->allocator, &inherited, ecs_table_record_t*);
765+
ecs_vec_fini_t(&world->allocator, &inherited_wild, ecs_table_record_t*);
706766
}
707767

708768
/* Main table initialization function */
@@ -1479,6 +1539,12 @@ void flecs_table_fini(
14791539
ecs_os_free(table->component_map);
14801540
flecs_table_records_unregister(world, table);
14811541

1542+
if (table->_->extended_type) {
1543+
flecs_wfree_n(world, ecs_id_t, table->_->extended_type->count,
1544+
table->_->extended_type->array);
1545+
flecs_wfree_t(world, ecs_type_t, table->_->extended_type);
1546+
}
1547+
14821548
/* Update counters */
14831549
world->info.table_count --;
14841550
world->info.table_delete_total ++;

src/storage/table.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,10 @@ typedef struct ecs_table__t {
109109

110110
struct ecs_table_record_t *records; /* Array with table records */
111111

112+
ecs_type_t *extended_type; /* Component ids + inherited base ids, used
113+
* to emit OnTableCreate/OnTableDelete events
114+
* for tables with derived components */
115+
112116
#ifdef FLECS_DEBUG_INFO
113117
/* Fields used for debug visualization */
114118
struct {

test/query/project.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1413,7 +1413,9 @@
14131413
"pair_multi",
14141414
"pair_different_target",
14151415
"pair_wildcard",
1416-
"pair_wildcard_multi_rel"
1416+
"pair_wildcard_multi_rel",
1417+
"cached_match_derived_table_added_after",
1418+
"cached_unmatch_derived_table_on_delete"
14171419
]
14181420
}, {
14191421
"id": "BuiltinPredicates",

test/query/src/ComponentInheritance.c

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4744,3 +4744,75 @@ void ComponentInheritance_1_var_pair_written(void) {
47444744
ecs_query_fini(r);
47454745
ecs_fini(world);
47464746
}
4747+
4748+
void ComponentInheritance_cached_match_derived_table_added_after(void) {
4749+
ecs_world_t *world = ecs_mini();
4750+
4751+
ecs_entity_t Unit = ecs_entity(world, { .name = "Unit" });
4752+
ecs_entity_t Warrior = ecs_entity(world, { .name = "Warrior" });
4753+
ecs_add_pair(world, Warrior, EcsIsA, Unit);
4754+
4755+
ecs_query_t *q = ecs_query(world, {
4756+
.cache_kind = cache_kind, .expr = "Unit" });
4757+
test_assert(q != NULL);
4758+
4759+
if (cache_kind == EcsQueryCacheAuto) {
4760+
test_assert(q->flags & EcsQueryIsCacheable);
4761+
}
4762+
4763+
ecs_entity_t e1 = ecs_new_w_id(world, Warrior);
4764+
ecs_entity_t e2 = ecs_new_w_id(world, Unit);
4765+
4766+
ecs_iter_t it = ecs_query_iter(world, q);
4767+
test_bool(true, ecs_query_next(&it));
4768+
test_uint(1, it.count);
4769+
test_uint(e1, it.entities[0]);
4770+
test_uint(Warrior, ecs_field_id(&it, 0));
4771+
4772+
test_bool(true, ecs_query_next(&it));
4773+
test_uint(1, it.count);
4774+
test_uint(e2, it.entities[0]);
4775+
test_uint(Unit, ecs_field_id(&it, 0));
4776+
4777+
test_bool(false, ecs_query_next(&it));
4778+
4779+
ecs_query_fini(q);
4780+
ecs_fini(world);
4781+
}
4782+
4783+
void ComponentInheritance_cached_unmatch_derived_table_on_delete(void) {
4784+
ecs_world_t *world = ecs_mini();
4785+
4786+
ecs_entity_t Unit = ecs_entity(world, { .name = "Unit" });
4787+
ecs_entity_t Warrior = ecs_entity(world, { .name = "Warrior" });
4788+
ecs_add_pair(world, Warrior, EcsIsA, Unit);
4789+
4790+
ecs_query_t *q = ecs_query(world, {
4791+
.cache_kind = cache_kind, .expr = "Unit" });
4792+
test_assert(q != NULL);
4793+
4794+
ecs_entity_t e1 = ecs_new_w_id(world, Warrior);
4795+
4796+
{
4797+
ecs_iter_t it = ecs_query_iter(world, q);
4798+
test_bool(true, ecs_query_next(&it));
4799+
test_uint(1, it.count);
4800+
test_uint(e1, it.entities[0]);
4801+
test_uint(Warrior, ecs_field_id(&it, 0));
4802+
test_bool(false, ecs_query_next(&it));
4803+
}
4804+
4805+
ecs_delete(world, e1);
4806+
4807+
ecs_delete_empty_tables(world, &(ecs_delete_empty_tables_desc_t){
4808+
.delete_generation = 1
4809+
});
4810+
4811+
{
4812+
ecs_iter_t it = ecs_query_iter(world, q);
4813+
test_bool(false, ecs_query_next(&it));
4814+
}
4815+
4816+
ecs_query_fini(q);
4817+
ecs_fini(world);
4818+
}

test/query/src/Validator.c

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3582,13 +3582,14 @@ void Validator_validate_simple_w_inherited_component(void) {
35823582
test_int(q->terms[0].field_index, 0);
35833583
test_uint(q->terms[0].first.id, Unit|EcsSelf|EcsIsEntity);
35843584
test_uint(q->terms[0].src.id, EcsThis|EcsSelf|EcsIsVariable);
3585-
test_uint(q->terms[0].flags_, EcsTermIdInherited);
3585+
test_uint(q->terms[0].flags_, EcsTermIdInherited|EcsTermIsCacheable);
35863586

35873587
test_assert(!(q->data_fields & (1 << 0)));
35883588

35893589
test_query_flags(EcsQueryMatchOnlyThis|EcsQueryMatchThis|
35903590
EcsQueryMatchOnlySelf|EcsQueryHasTableThisVar|
3591-
EcsQueryHasComponentInheritance,
3591+
EcsQueryHasComponentInheritance|EcsQueryHasCacheable|
3592+
EcsQueryIsCacheable,
35923593
q->flags);
35933594

35943595
ecs_query_fini(q);

0 commit comments

Comments
 (0)