query a value?

is there a way to query the value of a component? for example to foreach over entities that are nearby - query over tile # or query matching hash value

You can filter by shared component data of a specific value

cool, what’s the code for that?
and what’s a shared component data vs component data? everything looks like a big bag of data to me

This might be help

https://gametorrahod.com/the-chunk-data-structure/

Thanks for reminding me, I have updated that article to reflect more current version. Also more about SCD regarding to chunk is in a separated article : https://gametorrahod.com/everything-about-isharedcomponentdata/

After getting your EntityQuery with SCD type in it, the query will currently returns all chunks with that SCD type regardless of SCD index received (just archetype matching). After eq.SetSharedComponentFilter then it is reduced to just chunks that received SCD index of the SCD value specified.

Sorry was meant to post the SCD article

I don’t think using SCD for constant changing stuff is a good idea (like position) cause this will lead to a constant structural changes.
@laurentlavigne in my opinion:

  1. try to use chunk CD instead to access their values from jobs
  2. in my case i divided my map to regions and i added a region chunk CD to my concerned entities.
  3. I have 2 contant values minimum distance to be part of a region and maximum distance to go out from a region. They are used to prevent dynamic entities like players from being constantly changing regions when playing around the limites.
  4. now i can directly access my entities under chunks with specific Region CD. If I need super precision I just iterate over them to get the in range entities from a specific position.

This is preventing me from iterating over all entities. It can be usefull if you have a huge amount of entities.

1 Like

I want to know about how chunk CD works, so for example if previously I have attached a chunk CD to the only chunk I have, and later I add more to that chunk that it is over capacity and it allocates a new chunk transparently for the same archetype, that new chunk will not have the chunk CD or inherited? Would the chunk CD share over all chunks in the same archetype? Because if not, I would really have to think much more to keep my sanity about when chunks are allocated or removed and I afraid it would be error prone. Did you use chunk locking or any other tricks?

1 Like

in my case by setting a new value to chunk CD a new chunk with the same archetype will be created if not already existing, but the frequency of that change is related to the Min/Max values that are used to move an entity from a chunk to another which reduces a lot the structural changes.

Ok I was confused by the article, what you guys are saying is if I want to query against value like a position hash I need to manually move the stuff to chunks… That the only way? No Entities.QueryValue of some sort that won’t have me use chunks? I don’t even know how to use those.

That is the fast way that plays with API’s strength. If ECS automatically index or preprocess all your data in some way and lay them down differently than chunks of archetype, maybe it could achieve more flexible query like SQL, Firebase Cloud Firestore, Redis, etc. but then there will be compromise on other things that make it not suitable for running games on player’s device. (on putting new data in, on dumbly getting data out without any value criteria, need more explicit setup etc.)

So the API design decided that it wants to focus on collecting chunks by C# type the fastest, everything else followed then we have as much as SCD value filter to not alienate the design too much. Since SCD value index sticks on the chunk, they are trying to realize value query as much as possible while not stepping over the line of chunk based design.

You can make your own Entities.QueryValue but that would be just getting related chunks then for loop over all of them. This maybe faster than you thought, if you have a component type of just a single position inside then when you loop it you will get much more positions on your cache while looping.

In my game I have used SCD as a unit bigger than chunk. There is a system that query position with changed chunk filter. Any chunk detected with a change in position will have all members iterated and test dividing position by 1000 to see where in the bigger 1000x1000 grid they are right now. If the position is 9334.44 then it get SCD value = 9.

After processing, when selecting these units the cursor knows where they are (e.g. the cursor at 9823 then it is at grid #9) so on clicking down I could put on SCD filter of value 9 to narrow down related chunks before a simple for loop to see which units were hit. The compromise is that if unit moves around then these SCD calculation and chunk migration may outweight the gain you get for a few frames before they would be recalculated their grid again (in that case just iterate with position alone in the component maybe the best solution) but since my unit are buildings that won’t move after the map is loaded, this SCD optimization could be done at loading screen and I only get the gains afterwards.

3 Likes

i see entitymanager all over. are all shareddatacomponent operations main thread?

Any operation with just SCD index could be on thread bc thats whats really on the chunk, like filtering via index compare in IJobChunk. Involving values like Add Set or Get then main thread only. That said you can use EntityQuery overload to perform SCD operation in chunks so it wont cost much on main thread.

So the idea to fill a NativeArray with all the entities matching SCD querry in main thread so it can be used in a job for expensive operation like closest distance or boid stuff?
Maybe the boid code is a good example uh, did they refresh it to the latest pattern?

I didn’t check example for very long time. But the key would be GetAllUniqueSharedComponentData with 2 parameters overload. You will get the indexes that you could bring into the job after migrated to native array. If your SCD contains no ref values (which NativeArray will allow you to put the type in its ) then I think you can even bring actual values into the IJobChunk. (Though what you could get out of chunks would be limited to just SCD indexes)

While I See what you are saying, it seems that the API should be able to provide at least a sort of wrapper or convenience method to avoid having to handle what I see as a very common case ourself.

For example, if I want to filter a list of entities to only those with a value >= X, then as I understand it now I would need to write a system that iterated through all of those entities, found the ones matching the value, then I guess returned those in some other list, and that work with them? Why can’t that be wrapped up inside the API to do that on-demand for me? Entitas had something like that where I could just in my entity query, specify the filters I needed.

To be clear I’m not asking for a change in how the system works, just that a very common bit of boiler plate code essentially be provided to us for use rather than having to write it ourselves.

3 Likes

Amen to that.

ECS should be as easy as

Entities.WithValue<Health>(Value < -100).Foreach((Enemy enemy) ...
2 Likes

That’s a clever way to leverage how the system is currently designed but I really wish Unity would provide us with a better way to organize, group, and query related entities and components, especially by position. This post by @AriaBonczek in 2018 mentioned something along those lines:

As someone who’s mostly concerned with procedural generation this would be pretty huge for me. Probably the thing I want most from ECS right now aside from a better renderer. I’m curious if this is still a priority for Unity right now.

2 Likes
Entities.Foreach((in Enemy enemy, in Health health)
{
    if (health.Value < -100)
        return;

    DoStuff
    ...   
}

Why not just put an early out in the Entities.ForEach? While i generally agree that making queries be more fine grained would be useful. In practice this would result in exactly the same code as what i pasted above being generated.
If the data is not structured via shared components or tag components to allow skipping whole chunks then there are no gains to be had with something builtin.

Also of note. The above pattern is very fast, even for most checks hitting the early out. It would simply load tightly packed arrays of Health structs and very quickly reject many of them.

2 Likes

First off, it isn’t always about the gains. I just wanted to take a moment here and say that. ECS right now has a huge focus on performance, or performance by default, and while that is GREAT, I also feel like it has partially lost site of the poor soul writing the code. Don’t get me wrong, MASSIVE improvements have been made, but I think its something to continue to keep in mind. Just because offering an API to do something may not give benefits from a performance perspective doesn’t mean it isn’t worth doing, especially if it provides benefits from the coding perspective.

That said, and please bear in mind I’m still trying to wade into the shallower side of the pool that is ECS right now, here is a concrete example of something I am trying to do right now.

I have a bunch of entities that represent “candidate” points in space for use in map generation. My end goal is to gather up the positions of all those points into a NativeArray for further processing. Now which is simpler from a code perspective?

var queryDesc = new EntityQueryDesc
{
    All = new ComponentType[]{ComponentType.ReadOnly<VoronoiPositionData>(), typeof(VoronoiRelaxationData)},
    None = new ComponentType[]{typeof(VoronoiEdgePointElement)}
};
query = GetEntityQuery(queryDesc);
var positionArray = new NativeArray<VoronoiPositionData>(query.CalculateEntityCount(), Allocator.TempJob);
var ecb = endSimulationCommandBufferSystem.CreateCommandBuffer().ToConcurrent();

JobHandle getPositions = Entities.WithNone<VoronoiEdgePointElement>()
                .ForEach((Entity entity, int entityInQueryIndex, in VoronoiPositionData position, in VoronoiRelaxationData relaxations) =>
            {
                if (relaxations == 0) return;
                positionArray[entityInQueryIndex] = position;
            }).Schedule(inputDeps);

or

            EntityQuery query = GetEntityQuery(ComponentType.ReadOnly<VoronoiPositionData>());
            var array = query.ToComponentDataArray<VoronoiPositionData>(Allocator.TempJob);

Now granyted it isn’t a 1:1 comparison because the EntityQuery doesn’t elegantly let me let me handle the WithNone, so I would actually have to use EntityQueryDesc, but I think my point is clear. Not to mention in the first case I’m allocating memory for an array that won’t be fully utilized.

It would be much simpler and compact if I could just tell my EntityQuery to only include those entities with a VoronoiRelaxationData value > 0

1 Like