Define and execute an Entities.ForEach lambda expression
To use Entities.ForEach
, you must pass it a lambda expression, which Unity uses to generate an entity query based on the lambda parameter types. When the generated job runs, Unity calls the lambda expression once for each entity that matches the query. ForEachLambdaJobDescription
represents this generated job.
When you define the Entities.ForEach
lambda expression, you can declare parameters that the SystemBase
class uses to pass in information about the current entity when it executes the method.
A typical lambda expression looks like this:
Entities.ForEach(
(Entity entity,
int entityInQueryIndex,
ref ObjectPosition translation,
in Movement move) => { /* .. */})
You can pass up to eight parameters to an Entities.ForEach
lambda expression. If you need to pass more parameters, you can define a custom delegate. For more information, refer to the section on Custom delegates in this document.
When you use the standard delegates, you must group the parameters in the following order:
- Parameters passed-by-value (no parameter modifiers)
- Writable parameters (
ref
parameter modifier) - Read-only parameters (
in
parameter modifier)
You must use the ref
or in
parameter modify keyword on all components. If you don't, the component struct that Unity passes to your method is a copy instead of a reference. This means that it takes up extra memory for the read-ony parameters, and Unity silently throws any changes you make to the components when the copied struct goes out of scope after the function returns.
If the lambda expression doesn't follow this order, and you haven't 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
This error message cites the number of arguments as the issue even when though the problem is the parameter order.
Custom delegates
If you want to use more than eight arguments in a ForEach
lambda expression, you must declare your own delegate type and ForEach
overload. Declaring your own type means you can use an unlimited amount of arguments and put the ref
, in
, and value
parameters in any order you want.
You can also declare the three named parameters entity
, entityInQueryIndex
, and nativeThreadIndex
anywhere in your parameter list. Don't use ref
or in
modifiers for these parameters.
The following example shows 12 arguments, and uses the entity
parameter within the lambda expression:
static class BringYourOwnDelegate
{
// Declare the delegate that takes 12 parameters. T0 is used for the Entity argument
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
[RequireMatchingQueriesForUpdate]
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();
}
}
Component parameters
To access a component associated with an entity, you must pass a parameter of that component type to the lambda expression. The compiler automatically adds all components passed to the lambda expression to the entity query as required components.
To update a component value, you must use the ref
keyword in the parameter list to pass it to the lambda expression. Without the ref
keyword, Unity makes any modifications to a temporary copy of the component.
To declare a read-only component passed to the lambda expression use the in
keyword in the parameter list.
When you use ref
, Unity marks the components in the current chunk as changed, even if the lambda expression doesn't actually change them. For efficiency, always use the in
keyword to declare components that your lambda expression doesn't change as read only.
The following example passes a Source
component parameter to a job as read-only, and a Destination
component parameter as writable:
Entities.ForEach(
(ref Destination outputData,
in Source inputData) =>
{
outputData.Value = inputData.Value;
})
.ScheduleParallel();
Important
You can't pass chunk components to the Entities.ForEach
lambda expression.
For dynamic buffers, use DynamicBuffer<T>
rather than the component type stored in the buffer:
[RequireMatchingQueriesForUpdate]
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
}
}
Named parameters
You can also pass the following named parameters to the Entities.ForEach
lambda expression, which Unity assigns values based on the entity the job is processing.
Parameter | Description |
---|---|
Entity entity |
The entity instance of the current entity. You can name the parameter anything as long as the type is Entity . |
int entityInQueryIndex |
The index of the entity in the list of all entities that the query selected. 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. Use entityInQueryIndex as the sortKey to add commands to a concurrent entity command buffer. |
int nativeThreadIndex |
A unique index of the thread executing the current iteration of the lambda expression. When you use Run to execute the lambda expression, nativeThreadIndex is always zero. Don't use nativeThreadIndex as the sortKey of a concurrent entity command buffer; use entityInQueryIndex instead. |
EntityCommands commands |
You can name this parameter anything as long as the type is EntityCommands . Use this parameter only in conjunction with either WithDeferredPlaybackSystem<T> or WithImmediatePlayback . The EntityCommands type has several methods that mirror their counterparts in the EntityCommandBuffer type. If you use an EntityCommands instance inside Entities.ForEach , the compiler creates extra code where appropriate to handle the creation, scheduling, playback, and disposal of entity command buffers, on which counterparts to EntityCommands methods are invoked. |
Execute an Entities.ForEach lambda expression
You can execute a job lambda expression in the following ways:
- Use
Schedule
andScheduleParallel
to schedule the job - Use
Run
to execute the job immediately on the main thread.
The following example illustrates a SystemBase
implementation that uses Entities.ForEach
to read the Velocity
component and write to the ObjectPosition
component:
[RequireMatchingQueriesForUpdate]
partial class ApplyVelocitySystem : SystemBase
{
protected override void OnUpdate()
{
Entities
.ForEach((ref ObjectPosition translation,
in Velocity velocity) =>
{
translation.Value += velocity.Value;
})
.Schedule();
}
}
Capture variables
You can capture local variables for the Entities.ForEach
lambda expression. When you call one of the Schedule
methods instead of Run
to use a job to execute the lambda expression, there are some restrictions on the captured variables and how you use them:
- You can only capture native containers and blittable types.
- 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 use WithReadOnly(variable)
to specify read-only access. For more information about setting attributes for captured variables, see SystemBase.Entities
. Entities.ForEach
provides these as methods because the C# language doesn't allow attributes on local variables.
To dispose of captured native containers or types that contain native containers after Entities.ForEach
runs, use WithDisposeOnCompletion(variable)
. If you call this in Run
, this disposes of the types immediately after the lambda expression runs. If you call this in Schedule
andScheduleParallel
, it schedules them to be disposed of later with a job, and returns the JobHandle.
Note
When you execute the method with Run
you can write to captured variables that aren't native containers. However, you should still use blittable types where possible so that the method can be compiled with Burst.