Query in a query?

Hi. I’m still new to entities and currently trying to understand the design and general “style” of writing gameplay code with them, but so far having a bit of trouble. In particular, I struggle to understand what’s the proper and most efficient way to communicate between entities.

For example, I have a landmine component. If I want to make it functional, I’d need to run a system that checks whether it’s been triggered. But how do I know if it has and who did it? Wouldn’t I need to query for every landmine, then in this query query for every enemy with “position” component and compare the position against landmine position to determine whether it has been tripped?

People keep saying that “no you’re misunderstanding how you’re supposed to use ecs” and I get it that my approach is probably “with oop in mind” but still, how are you supposed to communicate between entities, the right way?

It is a two-step process.

First, you query every enemy and store data about it into some kind of spatial structure.

Second, you query each landmine and then have it ask the spatial structure for enemies nearby, from which you can then perform more exact tests to see if the landmine has been tripped.

1 Like

Nothing in your post suggests you don’t use ECS in the right way.

What you are describing is effectively a collision system. ECS has a physics solver or you can write your own. I never used the built in one.

Yours seems easy to build and the process you describe is OK. Its the way you implement it which will answer the question if you use ECS right or wrong… best to share code or pseudo code

Is there a benefit of doing two consecutive queries and storing results instead of doing two nested queries? If I’m processing a very high amount of enemies, won’t allocating space for all this data be more expensive than simply checking every enemy for every landmine?

Nested queries hasn’t been supported properly ever since I started using ECS in 2018. I don’t know if this is already supported in SystemAPI for loop but I doubt that this would work without gotchas. I stick to what I know of using IJobFor or IJobChunk. I use multiple queries and storing results all the time and there has been no problem of allocating memory for this. You’re going to dispose this memory in the same frame anyway so I don’t see the problem of maintaining this throughout the game.

My suggestion is just try it and see if it causes problems. Most of the time, it won’t and you’re worrying about nothing.

2 Likes

Let’s say you have N enemies and L landmines. The cost of a new allocation and population of your spatial structure will either be O(N) or O(N log N) depending on which structure you use. Then your checks against landmines might be O(L * N / k) where k is some reduction factor (such as sparsity of a spatial hash) or it might be O(k * log N) where k is the number of elements nearby. You can do the math for these using your actual unit counts and compare that to O( L * N) which is what nested queries would give you. In practice, performing a single allocation to reduce the amount of work you are doing is generally very beneficial, as I describe in this article, specifically the "Crossing Out Tests part: Latios-Framework-Documentation/Optimization Adventures/Part 12 - Find Pairs 5.md at main · Dreaming381/Latios-Framework-Documentation · GitHub

It just works, actually. There’s also a way to do nested queries in a job, but it is very non-trivial and bypasses some parallelism safety currently. At some point, I might write a safer abstraction for it. But I’ve yet to come across a use case for it where there wasn’t a better or good enough alternative to justify the effort.

2 Likes