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):
partial 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 entities 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 that Entities.ForEach creates, use [WithStoreEntityQueryInField(ref query)] with the ref parameter modifier. This function assigns a reference to the query to the field you provide.
Note
The EntityQuery is created in OnCreate. This method gives a copy of that query that can be used at any time (even before the Entities.ForEach is invoked). Also, this EntityQuery does not have any of the filters that the Entities.ForEach invocation sets up.
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];
})
.WithDisposeOnCompletion(dataSquared)
.Schedule();
}
Optional components
You can't 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 Component of that Entity has changed since the last time the current SystemBase instance has run, you can use WithChangeFilter<T>
to enable change filtering. 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 partial 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) => { /* .. */})
By default, you can pass up to eight parameters to an Entities.ForEach lambda function. (If you need to pass more parameters, you can define a custom delegate.) When using the standard delegates, you must group the parameters in the following order:
- Parameters passed-by-value first (no parameter modifiers)
- Writable parameters second (
ref
parameter modifier) - 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 and you have not created a suitable delegate, 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.)
Custom delegates
You can use more than 8 arguments in a ForEach lambda function. by declaring your own delegate type and ForEach overload. This allows you to use as many arguments as you want and to put the ref/in/value parameters in any order you want.
You can declare the three special, named parameters entity
, entityInQueryIndex
, and nativeThreadIndex
anywhere in your parameter list. Do not use ref
or in
modifiers for these parameters.
static class BringYourOwnDelegate
{
// Declare the delegate that takes 12 parameters. T0 is used for the Entity argument
[Unity.Entities.CodeGeneratedJobForEach.EntitiesForEachCompatible]
public delegate void CustomForEachDelegate<T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11>
(T0 t0, in T1 t1, in T2 t2, in T3 t3, in T4 t4, in T5 t5,
in T6 t6, in T7 t7, in T8 t8, in T9 t9, in T10 t10, in T11 t11);
// Declare the function overload
public static TDescription ForEach<TDescription, T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11>
(this TDescription description, CustomForEachDelegate<T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11> codeToRun)
where TDescription : struct, Unity.Entities.CodeGeneratedJobForEach.ISupportForEachWithUniversalDelegate =>
LambdaForEachDescriptionConstructionMethods.ThrowCodeGenException<TDescription>();
}
// A system that uses the custom delegate and overload
public partial class MayParamsSystem : SystemBase
{
protected override void OnUpdate()
{
Entities.ForEach(
(Entity entity0,
in Data1 d1,
in Data2 d2,
in Data3 d3,
in Data4 d4,
in Data5 d5,
in Data6 d6,
in Data7 d7,
in Data8 d8,
in Data9 d9,
in Data10 d10,
in Data11 d11
) => {/* .. */})
.Run();
}
}
Note
The default limit of eight arguments for a ForEach lambda function was chosen because declaring too many delegates and overloads has a negative effect on IDE performance. A unique delegate type and ForEach overload is required for each combination of ref/in/value and number of arguments.
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 partial 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);
})
.WithDisposeOnCompletion(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 thesortKey
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 thesortKey
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 NativeDisableParallelForRestriction
and others. Entities.ForEach provides these as functions because the C# language doesn't allow attibutes on local variables.
You can also indicate that you want captured NativeContainers or types that contain NativeContainers to be Disposed of after your Entities.ForEach runs by using WithDisposeOnCompletion(variable)
. This will either Dispose of the types immediately after the lambda runs (in the case of Run()
) or schedule them to be Diposed later with a Job and return the JobHandle (in the case of Schedule()
/ScheduleParallel()
).
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.
Supported Features
You can execute the lambda function on the main thread using Run()
, as a single job using Schedule()
, or as a parallel job using ScheduleParallel()
. These different execution methods have different constraints on how you access data. In addition, Burst uses a restricted subset of the C# language, so you need to specify WithoutBurst()
when using C# features outside this subset (including accessing managed types).
The following table shows which features are currently supported in Entities.ForEach for the different methods of scheduling available in SystemBase:
Supported Feature | Run | Schedule | ScheduleParallel |
---|---|---|---|
Capture local value type | x | x | x |
Capture local reference type | x (only WithoutBurst and not in ISystem) | ||
Writing to captured variables | x | ||
Use field on the system class | x (only WithoutBurst) | ||
Methods on reference types | x (only WithoutBurst and not in ISystem) | ||
Shared Components | x (only WithoutBurst and not in ISystem) | ||
Managed Components | x (only WithoutBurst and not in ISystem) | ||
Structural changes | x (only WithStructuralChanges and not in ISystem) | ||
SystemBase.GetComponent | x | x | x |
SystemBase.SetComponent | x | x | |
GetComponentDataFromEntity | x | x | x (only as ReadOnly) |
HasComponent | x | x | x |
WithDisposeOnCompletion | x | x | x |
WithScheduleGranularity | x |
Note: WithStructuralChanges()
will disable Burst. Do not use this option if you want to achieve high levels of performance in your Entities.ForEach (instead use an EntityCommandBuffer).
An Entities.ForEach construction uses Roslyn source generators to translate the code you write for the construction into correct ECS code. This translation allows you to express the intent of your algorithm without having to include complex, boilerplate code. However, it can mean that some common ways of writing code are not allowed.
The following features are not currently supported:
Unsupported Feature |
---|
Dynamic code in .With invocations |
SharedComponent parameters by ref |
Nested Entities.ForEach lambda expressions |
Calling with delegate stored in variable, field or by method |
SetComponent with lambda parameter type |
GetComponent with writable lambda parameter |
Generic parameters in lambdas |
In systems with generic parameters |
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.