Multiple Entities.ForEach

I would like to have a system that records various game statistics on how the player is progressing and records these in a native array. The plan is these statistics could be viewed by the player within some UI menu. These statistics are triggered by many different event components.

Initially I was going to write a single Entities.WithAny<>() and then use several HasComponent<>() calls to check what type of statistic (which native array index) needs updating.

Instead of this I’m wondering if it would be more performant using several Entities.ForEach calls within the same OnUpdate() function, each one only querying the relevant component, avoiding any HasComponent<>() calls. The downside is that you would have a few Entities.ForEach’s that would not iterate any Entities on some of the update calls. So there would be some overhead for that, but presumably less work than several HasComponent<>() calls?

These statistic updates will be quite infrequent. Does the multiple Entities.ForEach seem reasonable?

You can profile it, and validate, if your concern is justified. If you loose tiny fraction of ms on one job, just because is idle, while other iterates through thousands of entities, costing i.e. 1 ms, then overhead is really insignificant.

Also, if system has no matching queries of entities, then it is pretty much skipped.
So it is perfectly fine, to have “idle” systems.

1 Like

Entities.WithAny<>() Has no effect at all unless you use WriteGroup.
WriteGroup is described here:
https://docs.unity3d.com/Packages/com.unity.entities@0.16/manual/ecs_write_groups.html
When filtering with WriteGroup, all types in the same WriteGroup not included in the WithAll types will be put into WithNone types. WithAny will remove those types marked in WithAny from the query’s WithNone types.

If you are not using WriteGroup, Entities.WithAny<>() literally means give me all chunks with or without these types, which means that you are not filtering any chunk/entity out and could be going through every single entity in the world and checking if it has some component with HasComponent.

So separate Entities.Foreach() is better for sure.
And an alternative is to use IJobChunk to filter at chunk level, with chunk.Has(someTypeHandle)

Also, you’d better have a tag type like StatsTag:IComponentData{}. To mark that this entity(Archetype chunk in fact) has stats on it. so it will narrow down your query to exactly what you what to dig into.

This statement is not true, the “Any” filter will surely pick all entities with Any of those components (even if they only have one of those), but will require at least one of those components to exist.

From the docs: https://i.imgur.com/5Nfk85z.png

3 Likes

My bad you are right

        //@TODO: All this could be much faster by having all ComponentType pre-sorted to perform a single search loop instead two nested for loops...
        static bool IsMatchingArchetype(Archetype* archetype, EntityQueryData* query)
        {
            for (int i = 0; i != query->ArchetypeQueryCount; i++)
            {
                if (IsMatchingArchetype(archetype, query->ArchetypeQuery + i))
                    return true;
            }

            return false;
        }

        static bool IsMatchingArchetype(Archetype* archetype, ArchetypeQuery* query)
        {
            if (!TestMatchingArchetypeAll(archetype, query->All, query->AllCount, query->Options))
                return false;
            if (!TestMatchingArchetypeNone(archetype, query->None, query->NoneCount))
                return false;
            if (!TestMatchingArchetypeAny(archetype, query->Any, query->AnyCount))
                return false;

            return true;
        }

        static bool TestMatchingArchetypeAny(Archetype* archetype, int* anyTypes, int anyCount)
        {
            if (anyCount == 0) return true;

            var componentTypes = archetype->Types;
            var componentTypesCount = archetype->TypesCount;
            for (var i = 0; i < componentTypesCount; i++)
            {
                var componentTypeIndex = componentTypes[i].TypeIndex;
                for (var j = 0; j < anyCount; j++)
                {
                    var anyTypeIndex = anyTypes[j];
                    if (componentTypeIndex == anyTypeIndex)
                        return true;
                }
            }

            return false;
        }

Last Time I check ECS source I did not find this code block. But instead found that And EntityQuery holds a list call required components and it does not contain Any types. And this RequiredComponents is used in Chunk iteration. So I thought that Query’s Type Matching is done on the fly.

But that’s not the case! This time I found this part

        public void EndArchetypeChangeTracking(ArchetypeChanges changes, EntityQueryManager* queries)
        {
            Assert.AreEqual(m_ArchetypeTrackingVersion, changes.ArchetypeTrackingVersion);
            if (m_Archetypes.Length - changes.StartIndex == 0)
                return;

            var changeList = new UnsafeArchetypePtrList(m_Archetypes.Ptr + changes.StartIndex, m_Archetypes.Length - changes.StartIndex);
            queries->AddAdditionalArchetypes(changeList);
        }

This function is only called when there’s a structural change. And all EntityQuery’s internal data pointer is pointing to internal storage Managed by EntityComponetStore.
So every EntityQuery’s matching ArchyType is updated when there is a structural change. And kept in a linear list for reuse.
So EntityQuery iteration is just walking through a linear list of ArchyType’s linear list of chunks, and test for Change/SCD/Order filter if needed.
That’s fast!

2 Likes