Using Entities.ForEach
Use the Entities.ForEach construction provided by the SystemBase class as a concise way to define and execute your algorithms over entities and their components. Entities.ForEach executes a lambda function you define over all the entities selected by an entity query.
To execute a job lambda function, you either schedule the job using Schedule()
and ScheduleParallel()
, or execute it immediately (on the main thread) with Run()
. You can use additional methods defined on Entities.ForEach to set the entity query as well as various job options.
The following example illustrates a simple SystemBase implementation that uses Entities.ForEach to read one component (Velocity in this case) and write to another (Translation):
class ApplyVelocitySystem : SystemBase
{
protected override void OnUpdate()
{
Entities
.ForEach((ref Translation translation,
in Velocity velocity) =>
{
translation.Value += velocity.Value;
})
.Schedule();
}
}
Note the use of the keywords ref
and in
on the parameters of the ForEach lambda function. Use ref
for components that you write to, and in
for components that you only read. Marking components as read-only helps the job scheduler execute your jobs more efficiently.
Selecting entities
Entities.ForEach provides its own mechanism for defining the entity query used to select the entites to process. The query automatically includes any components you use as parameters of your lambda function. You can also use the WithAll
, WithAny
, and WithNone
clauses to further refine which entities are selected. See SystemBase.Entities for the complete list of query options.
The following example selects entities that have the components, Destination, Source, and LocalToWorld; and have at least one of the components, Rotation, Translation, or Scale; but which do not have a LocalToParent component.
Entities.WithAll<LocalToWorld>()
.WithAny<Rotation, Translation, Scale>()
.WithNone<LocalToParent>()
.ForEach((ref Destination outputData, in Source inputData) =>
{
/* do some work */
})
.Schedule();
In this example, only the Destination and Source components can be accessed inside the lambda function since they are the only components in the parameter list.
Accessing the EntityQuery object
To access the EntityQuery object created by Entities.ForEach, use [WithStoreEntityQueryInField(ref query)] with the ref parameter modifier. This function assigns a reference to the query to the field you provide.
The following example illustrates how to access the EntityQuery object implicitly created for an Entities.ForEach construction. In this case, the example uses the EntityQuery object to invoke the CalculateEntityCount() method. The example uses this count to create a native array with enough space to store one value per entity selected by the query:
private EntityQuery query;
protected override void OnUpdate()
{
int dataCount = query.CalculateEntityCount();
NativeArray<float> dataSquared
= new NativeArray<float>(dataCount, Allocator.Temp);
Entities
.WithStoreEntityQueryInField(ref query)
.ForEach((int entityInQueryIndex, in Data data) =>
{
dataSquared[entityInQueryIndex] = data.Value * data.Value;
})
.ScheduleParallel();
Job
.WithCode(() =>
{
//Use dataSquared array...
var v = dataSquared[dataSquared.Length -1];
})
.WithDeallocateOnJobCompletion(dataSquared)
.Schedule();
}
Optional components
You cannot create a query specifying optional components (using WithAny<T,U>) and also access those components in the lambda function. If you need to read or write to a component that is optional, you can split the Entities.ForEach construction into multiple jobs, one for each combination of the optional components. For example, if you had two optional components, you would need three ForEach constructions: one including the first optional component, one including the second, and one including both components. Another alternative is to iterate by chunk using IJobChunk.
Change filtering
In cases where you only want to process an entity component when another entity of that component has changed since the last time the current SystemBase instance has run, you can enable change filtering using WithChangeFilter<T>. The component type used in the change filter must either be in the lambda function parameter list or part of a WithAll<T> statement.
Entities
.WithChangeFilter<Source>()
.ForEach((ref Destination outputData,
in Source inputData) =>
{
/* Do work */
})
.ScheduleParallel();
An entity query supports change filtering on up to two component types.
Note that change filtering is applied at the chunk level. If any code accesses a component in a chunk with write access, then that component type in that chunk is marked as changed -- even if the code didn’t actually change any data.
Shared component filtering
Entities with shared components are grouped into chunks with other entities having the same value for their shared components. You can select groups of entities that have specific shared component values using the WithSharedComponentFilter() function.
The following example selects entities grouped by a Cohort ISharedComponentData. The lambda function in this example sets a DisplayColor IComponentData component based on the entity’s cohort:
public class ColorCycleJob : SystemBase
{
protected override void OnUpdate()
{
List<Cohort> cohorts = new List<Cohort>();
EntityManager.GetAllUniqueSharedComponentData<Cohort>(cohorts);
foreach (Cohort cohort in cohorts)
{
DisplayColor newColor = ColorTable.GetNextColor(cohort.Value);
Entities.WithSharedComponentFilter(cohort)
.ForEach((ref DisplayColor color) => { color = newColor; })
.ScheduleParallel();
}
}
}
The example uses the EntityManager to get all the unique cohort values. It then schedules a lambda job for each cohort, passing the new color to the lambda function as a captured variable.
Defining the ForEach function
When you define the lambda function to use with Entities.ForEach, you can declare parameters that the SystemBase class uses to pass in information about the current entity when it executes the function.
A typical lambda function looks like:
Entities.ForEach(
(Entity entity,
int entityInQueryIndex,
ref Translation translation,
in Movement move) => {/* .. */})
You can pass up to eight parameters to an Entities.ForEach lambda function. The parameters must be grouped in the following order:
1. Parameters passed-by-value first (no parameter modifiers)
2. Writable parameters second (`ref` parameter modifier)
3. Read-only parameters last (`in` parameter modifier)
All components should use either the ref
or the in
parameter modifier keywords. Otherwise, the component struct passed to your function is a copy instead of a reference. This means an extra memory copy for read-only parameters and means that any changes to components you intended to update are silently thrown when the copied struct goes out of scope after the function returns.
If your function does not obey these rules, the compiler provides an error similar to:
error CS1593: Delegate 'Invalid_ForEach_Signature_See_ForEach_Documentation_For_Rules_And_Restrictions' does not take N arguments
(Note that the error message cites the number of arguments as the issue even when the problem is the parameter order.)
Component parameters
To access a component associated with an entity, you must pass a parameter of that component type to the lambda function. The compiler automatically adds all components passed to the function to the entity query as required components.
To update a component value, you must pass it to the lambda function by reference using the ref
keyword in the parameter list. (Without the ref
keyword, any modifications would be made to a temporary copy of the component since it would be passed by value.)
To designate a component passed to the lambda function as read-only, use the in
keyword in the parameter list.
Note: Using ref
means that the components in the current chunk are marked as changed, even if the lambda function does not actually modify them. For efficiency, always designate components that your lambda function does not modify as read only using the in
keyword.
The following example passes a Source component parameter to the job as read-only, and a Destination component parameter as writable:
Entities.ForEach(
(ref Destination outputData,
in Source inputData) =>
{
outputData.Value = inputData.Value;
})
.ScheduleParallel();
Note: Currently, you cannot pass chunk components to the Entities.ForEach lambda function.
For dynamic buffers, use DynamicBuffer<T> rather than the Component type stored in the buffer:
public class BufferSum : SystemBase
{
private EntityQuery query;
//Schedules the two jobs with a dependency between them
protected override void OnUpdate()
{
//The query variable can be accessed here because we are
//using WithStoreEntityQueryInField(query) in the entities.ForEach below
int entitiesInQuery = query.CalculateEntityCount();
//Create a native array to hold the intermediate sums
//(one element per entity)
NativeArray<int> intermediateSums
= new NativeArray<int>(entitiesInQuery, Allocator.TempJob);
//Schedule the first job to add all the buffer elements
Entities
.ForEach((int entityInQueryIndex, in DynamicBuffer<IntBufferData> buffer) =>
{
for (int i = 0; i < buffer.Length; i++)
{
intermediateSums[entityInQueryIndex] += buffer[i].Value;
}
})
.WithStoreEntityQueryInField(ref query)
.WithName("IntermediateSums")
.ScheduleParallel(); // Execute in parallel for each chunk of entities
//Schedule the second job, which depends on the first
Job
.WithCode(() =>
{
int result = 0;
for (int i = 0; i < intermediateSums.Length; i++)
{
result += intermediateSums[i];
}
//Not burst compatible:
Debug.Log("Final sum is " + result);
})
.WithDeallocateOnJobCompletion(intermediateSums)
.WithoutBurst()
.WithName("FinalSum")
.Schedule(); // Execute on a single, background thread
}
}
Special, named parameters
In addition to components, you can pass the following special, named parameters to the Entities.ForEach lambda function, which are assigned values based on the entity the job is currently processing:
Entity entity
— the Entity instance of the current entity. (The parameter can be named anything as long as the type is Entity.)int entityInQueryIndex
— the index of the entity in the list of all entities selected by the query. Use the entity index value when you have a native array that you need to fill with a unique value for each entity. You can use the entityInQueryIndex as the index in that array. The entityInQueryIndex should also be used as the jobIndex for adding commands to a concurrent EntityCommandBuffer.int nativeThreadIndex
— a unique index for the thread executing the current iteration of the lambda function. When you execute the lambda function using Run(), nativeThreadIndex is always zero. (Do not usenativeThreadIndex
as thejobIndex
of a concurrent EntityCommandBuffer; useentityInQueryIndex
instead.)
Capturing variables
You can capture local variables for Entities.ForEach lambda functions. When you execute the function using a job (by calling one of the Schedule functions instead of Run) there are some restrictions on the captured variables and how you use them:
- Only native containers and blittable types can be captured.
- A job can only write to captured variables that are native containers. (To “return” a single value, create a native array with one element.)
If you read a [native container], but don't write to it, always specify read-only access using WithReadOnly(variable)
.
See SystemBase.Entities for more information about setting attributes for captured variables. The attributes you can specify include, DeallocateOnJobCompletion
, NativeDisableParallelForRestriction
, and others. Entities.ForEach provides these as functions because the C# language doesn't allow attibutes on local variables.
Note: When executing the function with Run()
you can write to captured variables that are not native containers. However, you should still use blittable types where possible so that the function can be compiled with Burst.
Dependencies
By default, a system manages its ECS-related dependencies using its Dependency property. By default, the system adds each job created with Entities.ForEach and [Job.WithCode] to the Dependency job handle in the order that they appear in the OnUpdate() function. You can also manage job dependencies manually by passing a [JobHandle] to your Schedule
functions, which then return the resulting dependency. See Dependency for more information.
See Job dependencies for more general information about job dependencies.