Skip to content

Commit

Permalink
Fixed: Invalidate query cache when changing inheritance aka (Is,X). S…
Browse files Browse the repository at this point in the history
…econd part of 05cb495
  • Loading branch information
richardbiely committed Oct 10, 2024
1 parent 16ca404 commit 1c73225
Show file tree
Hide file tree
Showing 2 changed files with 110 additions and 16 deletions.
63 changes: 55 additions & 8 deletions include/gaia/ecs/world.h
Original file line number Diff line number Diff line change
Expand Up @@ -572,11 +572,7 @@ namespace gaia {
// m_world.add_entity_archetype_pair(m_entity, ec.pArchetype);

// Cached queries might need to be invalidated.
// TODO: We still need to handle invalidation "down-the-tree".
// E.g., if [wolf, (Is,carnivore)] and [carnivore, (Is,animal)],
// and there is a query (Is,animal) and we remove {Is,carnivore}
// from wolf, the (Is,animal) query won't be invalidated.
m_world.m_queryCache.invalidate_queries_for_entity(EntityLookupKey(entity));
m_world.invalidate_queries_for_entity({Is, e});
}

m_pArchetype = m_world.foc_archetype_add(m_pArchetype, entity);
Expand Down Expand Up @@ -607,6 +603,9 @@ namespace gaia {
EntityLookupKey entityKey(m_entity);
EntityLookupKey eKey(e);

// Cached queries might need to be invalidated.
m_world.invalidate_queries_for_entity({Is, e});

// m_entity -> {..., e}
{
const auto it = m_world.m_entityToAsTargets.find(entityKey);
Expand All @@ -632,9 +631,6 @@ namespace gaia {
if (set.empty())
m_world.m_entityToAsRelations.erase(it);
}

// Cached queries might need to be invalidated.
m_world.m_queryCache.invalidate_queries_for_entity(EntityLookupKey(entity));
}

m_pArchetype = m_world.foc_archetype_del(m_pArchetype, entity);
Expand Down Expand Up @@ -3555,6 +3551,36 @@ namespace gaia {
return false;
}

//! Traverse the (Is, X) relationships all the way to their source
template <bool CheckIn, typename Func>
GAIA_NODISCARD void as_up_trav(Entity entity, Func func) {
GAIA_ASSERT(valid_entity(entity));

// Pairs are not supported
if (entity.pair())
return;

if constexpr (!CheckIn) {
func(entity);
}

const auto& ec = m_recs.entities[entity.id()];
const auto* pArchetype = ec.pArchetype;

// Early exit if there are no Is relationship pairs on the archetype
if (pArchetype->pairs_is() == 0)
return;

for (uint32_t i = 0; i < pArchetype->pairs_is(); ++i) {
auto e = pArchetype->entity_from_pairs_as_idx(i);
const auto& ecTarget = m_recs.entities[e.gen()];
auto target = ecTarget.pChunk->entity_view()[ecTarget.row];
func(target);

as_up_trav<CheckIn>(target, func);
}
}

template <typename T>
const ComponentCacheItem& reg_core_entity(Entity id, Archetype* pArchetype) {
auto comp = add(*pArchetype, id.entity(), id.pair(), id.kind());
Expand Down Expand Up @@ -3750,6 +3776,27 @@ namespace gaia {
m_querySerMap.erase(it);
serId = QueryIdBad;
}

void invalidate_queries_for_entity(Pair is_pair) {
GAIA_ASSERT(is_pair.first() == Is);

// We still need to handle invalidation "down-the-tree".
// E.g. following setup:
// q = w.query().all({Is,animal});
// w.as(wolf, carnivore);
// w.as(carnivore, animal);
// q.each() ...; // animal, carnivore, wolf
// w.del(wolf, {Is,carnivore}) // wolf no longer a carnivore and thus no longer an animal
// After this deletion, we need to invalidate "q" because wolf is no longer an animal
// and we don't want q to include it.
// q.each() ...; // animal

auto e = is_pair.second();
as_up_trav<false>(e, [&](Entity target) {
// Invalidate all queries that contain everything in our path.
m_queryCache.invalidate_queries_for_entity(EntityLookupKey(Pair{Is, target}));
});
}
};

GAIA_NODISCARD inline QuerySerBuffer& query_buffer(World& world, QueryId& serId) {
Expand Down
63 changes: 55 additions & 8 deletions single_include/gaia.h
Original file line number Diff line number Diff line change
Expand Up @@ -24395,11 +24395,7 @@ namespace gaia {
// m_world.add_entity_archetype_pair(m_entity, ec.pArchetype);

// Cached queries might need to be invalidated.
// TODO: We still need to handle invalidation "down-the-tree".
// E.g., if [wolf, (Is,carnivore)] and [carnivore, (Is,animal)],
// and there is a query (Is,animal) and we remove {Is,carnivore}
// from wolf, the (Is,animal) query won't be invalidated.
m_world.m_queryCache.invalidate_queries_for_entity(EntityLookupKey(entity));
m_world.invalidate_queries_for_entity({Is, e});
}

m_pArchetype = m_world.foc_archetype_add(m_pArchetype, entity);
Expand Down Expand Up @@ -24430,6 +24426,9 @@ namespace gaia {
EntityLookupKey entityKey(m_entity);
EntityLookupKey eKey(e);

// Cached queries might need to be invalidated.
m_world.invalidate_queries_for_entity({Is, e});

// m_entity -> {..., e}
{
const auto it = m_world.m_entityToAsTargets.find(entityKey);
Expand All @@ -24455,9 +24454,6 @@ namespace gaia {
if (set.empty())
m_world.m_entityToAsRelations.erase(it);
}

// Cached queries might need to be invalidated.
m_world.m_queryCache.invalidate_queries_for_entity(EntityLookupKey(entity));
}

m_pArchetype = m_world.foc_archetype_del(m_pArchetype, entity);
Expand Down Expand Up @@ -27378,6 +27374,36 @@ namespace gaia {
return false;
}

//! Traverse the (Is, X) relationships all the way to their source
template <bool CheckIn, typename Func>
GAIA_NODISCARD void as_up_trav(Entity entity, Func func) {
GAIA_ASSERT(valid_entity(entity));

// Pairs are not supported
if (entity.pair())
return;

if constexpr (!CheckIn) {
func(entity);
}

const auto& ec = m_recs.entities[entity.id()];
const auto* pArchetype = ec.pArchetype;

// Early exit if there are no Is relationship pairs on the archetype
if (pArchetype->pairs_is() == 0)
return;

for (uint32_t i = 0; i < pArchetype->pairs_is(); ++i) {
auto e = pArchetype->entity_from_pairs_as_idx(i);
const auto& ecTarget = m_recs.entities[e.gen()];
auto target = ecTarget.pChunk->entity_view()[ecTarget.row];
func(target);

as_up_trav<CheckIn>(target, func);
}
}

template <typename T>
const ComponentCacheItem& reg_core_entity(Entity id, Archetype* pArchetype) {
auto comp = add(*pArchetype, id.entity(), id.pair(), id.kind());
Expand Down Expand Up @@ -27573,6 +27599,27 @@ namespace gaia {
m_querySerMap.erase(it);
serId = QueryIdBad;
}

void invalidate_queries_for_entity(Pair is_pair) {
GAIA_ASSERT(is_pair.first() == Is);

// We still need to handle invalidation "down-the-tree".
// E.g. following setup:
// q = w.query().all({Is,animal});
// w.as(wolf, carnivore);
// w.as(carnivore, animal);
// q.each() ...; // animal, carnivore, wolf
// w.del(wolf, {Is,carnivore}) // wolf no longer a carnivore and thus no longer an animal
// After this deletion, we need to invalidate "q" because wolf is no longer an animal
// and we don't want q to include it.
// q.each() ...; // animal

auto e = is_pair.second();
as_up_trav<false>(e, [&](Entity target) {
// Invalidate all queries that contain everything in our path.
m_queryCache.invalidate_queries_for_entity(EntityLookupKey(Pair{Is, target}));
});
}
};

GAIA_NODISCARD inline QuerySerBuffer& query_buffer(World& world, QueryId& serId) {
Expand Down

0 comments on commit 1c73225

Please sign in to comment.