Suppose I want to iterate over only a certain subset of entities in my scene each frame, and cycle through the subsets such that every entity gets its turn exactly once every N frames. In single-threaded code, I might do something like this:
for (int i = frame % step; i < entities.Length; i += step)
{
// code goes here
}
Where frame is the current frame and step is the number of frames it will take to loop over all available entities.
Is there an easy way to achieve this in a job or an Entities.ForEach? I assume it would work to define an IJobChunk and do a similar staggered for loop over the chunks (with a smaller step size) inside of that job, but is there a simpler or better way?
I do have a static set of entities. Using ToEntityArray() makes perfect sense and I had plain forgotten that method existed, but I’m not 100% clear on where iterating over the entity array would come in, since the syntax for ForEach assumes I’m iterating over every entity and the documentation for IJobChunk and IJobParallelFor is all about chunks. Would I have to create an IJobParallelFor, pass in the entity array in addition to the ArchetypeChunks array, and then for each chunk iterate over only the appropriate entities as indexed in the entities array, or am I way overthinking this?
I’m looking at the manual entry for IJobParallelFor to try to make sense of exactly what I would have to change, and here’s where my disconnect is: the Execute function takes an index as its argument and then iterates in parallel over the entire range of that index. If I just pass in the array of all my components, I don’t see where I can tell it to iterate over only this or that fraction of them. Am I just missing something obvious?
I suppose I could give all my entities a SharedComponent that acts as their index from 0 to N and then every frame filter the query to only those entities which have a certain index and pass those entities’ components into the job, but I don’t think that’s what you meant. I am also not sure how repeated filtering like that works out performance-wise.
You can also apply your execute condition to entityInQueryIndex and early out if it doesn’t meet your condition. You’d have to check the Burst inspector to make sure it optimizes it correctly to use strides, but Burst is usually pretty good with such optimizations.
Not sure if you can do it in your use case but you could always use a shared component to split them up. Then you can use SetSharedComponentFilter on your query and only get the entities that match.
Thank you for the help, everyone. I reviewed the manual on using ForEach today and tested out giving my entities an index component that simply holds an integer value from 0 to N. Then in the system, I set
indices = new List<Index>();
EntityManager.GetAllUniqueSharedComponentData<Index>(indices);
step = indices.Count;
And added .WithSharedComponentFilter(indices[frame % step]) to my Entities.ForEach. As far as I can tell, this works as intended and performs quite well.
Is the above what you mean by this, or are you thinking more of essentially an if clause in the loop that just ignores a given entity if its index doesn’t match the current one? I would think that would cause more of a load, since the system would still be checking every entity every frame at least for whether its index matches.
I’m thinking the “if” check per entity. Again, your concern should be optimized out by Burst, but you would have to check to make sure. Personally though, if you really care about that level of performance, I would use IJobChunk with chunk components or IJobFor manual chunk iteration and perform the cycling at chunk granularity rather than entity granularity. Right now you are thrashing your cache pretty hard with that modulus.
You’re right; I just tried the if clause and the performance seems similar to shared component filtering. I will go ahead and try cycling by chunk when I get the time if only for my edification.