Class SystemBase
Implement SystemBase to create a systems in ECS.
Inherited Members
Namespace: Unity.Entities
Syntax
public abstract class SystemBase : ComponentSystemBase
Remarks
Systems in ECS
A typical system operates on a set of entities that have specific components. The system identifies the components of interest, reading and writing data, and performing other entity operations as appropriate.
The following example shows a basic system that iterates over entities using a Entities.ForEach construction. In this example, the system iterates over all entities with both a Displacement and a Velocity component and updates the Displacement based on the delta time elapsed since the last frame.
public struct Position : IComponentData
{
public float3 Value;
}
public struct Velocity : IComponentData
{
public float3 Value;
}
public class ECSSystem : SystemBase
{
protected override void OnUpdate()
{
// Local variable captured in ForEach
float dT = Time.DeltaTime;
Entities
.WithName("Update_Displacement")
.ForEach(
(ref Position position, in Velocity velocity) =>
{
position = new Position()
{
Value = position.Value + velocity.Value * dT
};
}
)
.ScheduleParallel();
}
}
System lifecycle callbacks
You can define a set of system lifecycle event functions when you implement a system. The runtime invokes these functions in the following order:
- OnCreate() -- called when the system is created.
- OnStartRunning() -- before the first OnUpdate and whenever the system resumes running.
- OnUpdate() -- every frame as long as the system has work to do (see ShouldRunSystem()) and the system is Enabled.
- OnStopRunning() -- whenever the system stops updating because it finds no entities matching its queries. Also called before OnDestroy.
- OnDestroy() -- when the system is destroyed.
All of these functions are executed on the main thread. To perform work on background threads, you can schedule jobs from the OnUpdate() function.
System update order
The runtime executes systems in the order determined by their ComponentSystemGroup. Place a system in a group using UpdateInGroupAttribute. Use UpdateBeforeAttribute and UpdateAfterAttribute to specify the execution order within a group.
If you do not explicitly place a system in a specific group, the runtime places it in the default World SimulationSystemGroup. By default, all systems are discovered, instantiated, and added to the default World. You can use the DisableAutoCreationAttribute to prevent a system from being created automatically.
Entity queries
A system caches all queries created through an Entities.ForEach construction, through
ComponentSystemBase.GetEntityQuery, or through ComponentSystemBase.RequireForUpdate. By default,
the runtime only calls a system's OnUpdate()
function when one of these cached queries finds entities. You
can use the AlwaysUpdateSystemAttribute to have the system always update. Note that a system
with no queries is also updated every frame.
Entities.ForEach and Job.WithCode constructions
The Entities property provides a convenient mechanism for iterating over entity data. Using an Entities.ForEach construction, you can define your entity query, specify a lambda function to run for each entity, and either schedule the work to be done on a background thread or execute the work immediately on the main thread.
The Entities.ForEach construction uses a C# compiler extension to take a data query syntax that describes your intent and translate it into efficient (optionally) job-based code.
The Job property provides a similar mechanism for defining a C# Job. You can only use
Schedule()
to run a Job.WithCode construction, which executes the lambda function as a single job.
System attributes
You can use a number of attributes on your SystemBase implementation to control when it updates:
- UpdateInGroupAttribute -- place the system in a ComponentSystemGroup.
- UpdateBeforeAttribute -- always update the system before another system in the same group.
- UpdateAfterAttribute -- always update the system after another system in the same group.
- AlwaysUpdateSystemAttribute -- invoke
OnUpdate
every frame. - DisableAutoCreationAttribute -- do not create the system automatically.
- AlwaysSynchronizeSystemAttribute -- force a sync point before invoking
OnUpdate
.
Properties
Dependency
The ECS-related data dependencies of the system.
Declaration
protected JobHandle Dependency { get; set; }
Property Value
Type | Description |
---|---|
JobHandle |
Remarks
Before OnUpdate(), the Dependency property represents the combined job handles of any job that writes to the same components that the current system reads -- or reads the same components that the current system writes to. When you use Entities.ForEach or Job.WithCode, the system uses the Dependency property to specify a job’s dependencies when scheduling it. The system also combines the new job's JobHandle with Dependency so that any subsequent job scheduled in the system depends on the earlier jobs (in sequence).
The following example illustrates an OnUpdate()
implementation that relies on implicit dependency
management. The function schedules three jobs, each depending on the previous one:
protected override void OnUpdate()
{
Entities
.WithName("ForEach_Job_One")
.ForEach((ref AComponent c) =>
{
/*...*/
})
.ScheduleParallel();
Entities
.WithName("ForEach_Job_Two")
.ForEach((ref AnotherComponent c) =>
{
/*...*/
})
.ScheduleParallel();
Job
.WithName("Job_Three")
.WithCode(() =>
{
/*...*/
})
.Schedule();
}
You can opt out of this default dependency management by explicitly passing a JobHandle to Entities.ForEach or Job.WithCode. When you pass in a JobHandle, these constructions also return a JobHandle representing the input dependencies combined with the new job. The JobHandle objects of any jobs scheduled with explicit dependencies are not combined with the system’s Dependency property. You must set the Dependency property manually to make sure that later systems receive the correct job dependencies.
The following OnUpdate() function illustrates manual dependency management. The function uses two [Entity.ForEach] constructions that schedule jobs which do not depend upon each other, only the incoming dependencies of the system. Then a Job.WithCode construction schedules a job that depends on both of the prior jobs, who’s dependencies are combined using [JobHandle.CombineDependencies]. Finally, the JobHandle of the last job is assigned to the Dependency property so that the ECS safety manager can propagate the dependencies to subsequent systems.
protected override void OnUpdate()
{
JobHandle One = Entities
.WithName("ForEach_Job_One")
.ForEach((ref AComponent c) =>
{
/*...*/
})
.ScheduleParallel(this.Dependency);
JobHandle Two = Entities
.WithName("ForEach_Job_Two")
.ForEach((ref AnotherComponent c) =>
{
/*...*/
})
.ScheduleParallel(this.Dependency);
JobHandle intermediateDependencies =
JobHandle.CombineDependencies(One, Two);
JobHandle finalDependency = Job
.WithName("Job_Three")
.WithCode(() =>
{
/*...*/
})
.Schedule(intermediateDependencies);
this.Dependency = finalDependency;
}
You can combine implicit and explicit dependency management (by using [JobHandle.CombineDependencies]); however, doing so can be error prone. When you set the Dependency property, the assigned JobHandle replaces any existing dependency, it is not combined with them.
Note that the default, implicit dependency management does not include IJobChunk jobs. You must manage the dependencies for IJobChunk explicitly.
Entities
Provides a mechanism for defining an entity query and invoking a lambda expression on each entity selected by that query.
Declaration
protected ForEachLambdaJobDescription Entities { get; }
Property Value
Type | Description |
---|---|
Unity.Entities.CodeGeneratedJobForEach.ForEachLambdaJobDescription |
Remarks
The Entities property provides a convenient mechanism for implementing the most common operation performed by systems in ECS, namely, iterating over a set of entities to read and update component data. Entities provides a LINQ method-style syntax that you use to describe the work to be performed. Unity uses a compiler extension to convert the description into efficient, (optionally) multi-threaded executable code.
Entities
.WithName("Update_Position") // Shown in error messages and profiler
.WithAll<LocalToWorld>() // Require the LocalToWorld component
.ForEach(
// Write to Displacement (ref), read Velocity (in)
(ref Position position, in Velocity velocity) =>
{
//Execute for each selected entity
position = new Position()
{
// dT is a captured variable
Value = position.Value + velocity.Value * dT
};
}
)
.ScheduleParallel(); // Schedule as a parallel job
Describing the entity query
The components that you specify as parameters for your lambda function are automatically added to the entity query created for an Entities.Foreach construction. You can also add a number of "With" clauses to identify which entities that you want to process These clauses include:
WithAll
-- An entity must have all of these component types (in addition to having all the component types found in the lambda parameter list).WithAny
-- An entity must have one or more of these component types.WithNone
-- An entity must not have any of these component types.WithChangeFilter()
-- Only selects entities in chunks in which the specified component might have changed since the last time this system instance updated.WithSharedComponentFilter(ISharedComponentData)
-- Only select chunks that have a specified value for a shared component.WithEntityQueryOptions(EntityQueryOptions)
-- Specify additonal options defined in a EntityQueryOptions object.WithStoreEntityQueryInField(EntityQuery)
-- Stores the EntityQuery object generated by the Entities.ForEach in an EntityQuery field on your system. You can use this EntityQuery object for such purposes as getting the number of entities that will be selected by the query. Note that this function assigns the EntityQuery instance to your field when the system is created. This means that you can use the query before the first execution of the lambda function.
Defining the lambda function
Define the lambda function inside the ForEach()
method of the entities property. When the system invokes the
lambda function, it assigns values to the function parameters based on the current entity. You can pass ECS
component types as parameters as well as a set of special, named parameters.
- 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.
You can pass up to eight parameters to the lambda function. In addition to ECS component types, you can use the following:
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.
.ForEach((Entity entity,
int entityInQueryIndex,
ref WritableComponent aReadwriteComponent,
in ReadonlyComponent aReadonlyComponent) =>
{
/*..*/
})
Capturing variables
You can capture local variables in the lambda function. When you execute the function using a job (by
calling ScheduleParallel()
or ScheduleSingle()
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.)
You can use the following functions to apply modifiers and attributes to the captured native container variables, including native arrays. See Job.WithCode for a list of these modifiers and attributes.
Executing the lambda function
To execute a ForEach construction, you have three options:
ScheduleParallel()
-- schedules the work to be done in parallel using the C# Job system. Each parallel job instance processes at least one chunk of entities at a time. In other words, if all the selected entities are in the same chunk, then only one job instance is spawned.Schedule()
-- schedules the work to be done in a single job (no matter how many entities are selected).Run()
-- evaluates the entity query and invokes the lambda function for each selected entity immediately on the main thread. CallingRun()
completes the system Dependency JobHandle before running, blocking the main thread, if necessary, while it waits for those jobs to finish.
When you call Schedule() or ScheduleParallel() without parameters, then the scheduled jobs use the current value of Dependency. You can also pass a JobHandle to these functions to define the dependencies of the scheduled job. In this case, the Entities.forEach construction returns a new JobHandle that adds the scheduled job to the passed in JobHandle. See Dependency for more information.
Additional options
WithName(string)
-— assigns the specified string as the name of the generated job class. Assigning a name is optional, but can help identify the function when debugging and profiling.WithStructuralChanges()
-— executes the lambda function on the main thread and disables Burst so that you can make structural changes to your entity data within the function. For better performance, use an EntityCommandBuffer instead.WithoutBurst()
—- disables Burst compilation. Use this function when your lambda function contains code not supported by Burst or while debugging.WithBurst(FloatMode, FloatPrecision, bool)
— sets options for the Burst compiler:- floatMode —- sets the floating point math optimization mode.Fast mode executes faster, but produces larger floating point error than Strict mode.Defaults to Strict. See Burst FloatMode.
- floatPrecision —- sets the floating point math precision. See Burst FloatPrecision.
- synchronousCompilation —- compiles the function immediately instead of scheduling the function for compilation later.
Job
Provides a mechanism for defining and executing an [IJob].
Declaration
protected LambdaSingleJobDescription Job { get; }
Property Value
Type | Description |
---|---|
Unity.Entities.CodeGeneratedJobForEach.LambdaSingleJobDescription |
Remarks
The Jobs property provides a convenient mechanism for implementing single jobs. Unity uses a compiler extension to convert the job description you create with Job.WithCode into efficient, executable code that (optionally) runs in a background thread.
public class RandomSumJob : SystemBase
{
private uint seed = 1;
protected override void OnUpdate()
{
Random randomGen = new Random(seed++);
NativeArray<float> randomNumbers
= new NativeArray<float>(500, Allocator.TempJob);
Job.WithCode(() =>
{
for (int i = 0; i < randomNumbers.Length; i++)
{
randomNumbers[i] = randomGen.NextFloat();
}
}).Schedule();
// To get data out of a job, you must use a NativeArray
// even if there is only one value
NativeArray<float> result
= new NativeArray<float>(1, Allocator.TempJob);
Job.WithCode(() =>
{
for (int i = 0; i < randomNumbers.Length; i++)
{
result[0] += randomNumbers[i];
}
}).Schedule();
// This completes the scheduled jobs to get the result immediately, but for
// better efficiency you should schedule jobs early in the frame with one
// system and get the results late in the frame with a different system.
this.CompleteDependency();
UnityEngine.Debug.Log("The sum of "
+ randomNumbers.Length + " numbers is " + result[0]);
randomNumbers.Dispose();
result.Dispose();
}
}
Implement your lambda function inside the Job.WithCode(lambda)
function. The lambda function cannot
take any parameters. You can capture local variables.
Schedule()
-- executes the lambda function as a single job.Run()
-- executes immediately on the main thread. Immediately before it invokesRun()
the system completes all jobs with a JobHandle in the system Dependency property as well as any jobs with a JobHandle passed as a dependency toRun()
as an (optional) parameter.
When scheduling a job, you can pass a JobHandle to set the job's dependencies explicitly and the construction returns the updated JobHandle combining the earlier dependencies with the new job. If you do not provide a JobHandle, the system uses Dependency when scheduling the job, and updates the property to include the new job automatically.
You can use the additional options listed for Entities.ForEach with a Job.WithCode
construction.
Capturing variables
You can capture local variables in the lambda function. When you execute the function using a job (by
calling Schedule()
, ScheduleParallel()
or ScheduleSingle()
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.)
You can use the following functions to apply modifiers and attributes to the captured [native container] variables, including [native arrays]:
WithReadOnly(myvar)
— restricts access to the variable as read-only.WithDeallocateOnJobCompletion(myvar)
— deallocates the native container after the job is complete. See DeallocateOnJobCompletionAttribute.WithNativeDisableParallelForRestriction(myvar)
— permits multiple threads to access the same writable native container. Parallel access is only safe when each thread only accesses its own, unique range of elements in the container. If more than one thread accesses the same element a race condition is created in which the timing of the access changes the result. See NativeDisableParallelForRestriction.WithNativeDisableContainerSafetyRestriction(myvar)
— disables normal safety restrictions that prevent dangerous access to the native container. Disabling safety restrictions unwisely can lead to race conditions, subtle bugs, and crashes in your application. See NativeDisableContainerSafetyRestrictionAttribute.WithNativeDisableUnsafePtrRestrictionAttribute(myvar)
— Allows you to use unsafe pointers provided by the native container. Incorrect pointer use can lead to subtle bugs, instability, and crashes in your application. See NativeDisableUnsafePtrRestrictionAttribute.
Methods
CompleteDependency()
Declaration
protected void CompleteDependency()
GetComponent<T>(Entity)
Look up the value of a component for an entity.
Declaration
protected T GetComponent<T>(Entity entity)
where T : struct, IComponentData
Parameters
Type | Name | Description |
---|---|---|
Entity | entity | The entity. |
Returns
Type | Description |
---|---|
T | A struct of type T containing the component value. |
Type Parameters
Name | Description |
---|---|
T | The type of component to retrieve. |
Remarks
Use this method to look up data in another entity using its Entity object. For example, if you have a component that contains an Entity field, you can look up the component data for the referenced entity using this method.
When iterating over a set of entities via Entities.ForEach, do not use this method to access data of the current entity in the set. This function is much slower than accessing the data directly (by passing the component containing the data to your lambda iteration function as a parameter).
When you call this method on the main thread, it invokes GetComponentData<T>(Entity).
(An Entities.ForEach function invoked with Run()
executes on the main thread.) When you call this method
inside a job scheduled using Entities.ForEach, this method gets replaced with component access methods
through ComponentDataFromEntity<T>.
In both cases, this lookup method results in a slower, indirect memory access. When possible, organize your data to minimize the need for indirect lookups.
Exceptions
Type | Condition |
---|---|
ArgumentException | Thrown if the component type has no fields. |
GetComponentDataFromEntity<T>(Boolean)
Gets an dictionary-like container containing all components of type T, keyed by Entity.
Declaration
public ComponentDataFromEntity<T> GetComponentDataFromEntity<T>(bool isReadOnly = false)
where T : struct, IComponentData
Parameters
Type | Name | Description |
---|---|---|
Boolean | isReadOnly | Whether the data is only read, not written. Access data as read-only whenever possible. |
Returns
Type | Description |
---|---|
ComponentDataFromEntity<T> | All component data of type T. |
Type Parameters
Name | Description |
---|---|
T | A struct that implements IComponentData. |
Remarks
When you call this method on the main thread, it invokes GetComponentDataFromEntity<T>(Boolean).
(An Entities.ForEach function invoked with Run()
executes on the main thread.) When you call this method
inside a job scheduled using Entities.ForEach, this method gets replaced direct access to
ComponentDataFromEntity<T>.
HasComponent<T>(Entity)
Checks whether an entity has a specific type of component.
Declaration
protected bool HasComponent<T>(Entity entity)
where T : struct, IComponentData
Parameters
Type | Name | Description |
---|---|---|
Entity | entity | The Entity object. |
Returns
Type | Description |
---|---|
Boolean | True, if the specified entity has the component. |
Type Parameters
Name | Description |
---|---|
T | The data type of the component. |
Remarks
Always returns false for an entity that has been destroyed.
Use this method to check if another entity has a given type of component using its Entity object. For example, if you have a component that contains an Entity field, you can check whether the referenced entity has a specific type of component using this method. (Entities in the set always have required components, so you don’t need to check for them.)
When iterating over a set of entities via Entities.ForEach, avoid using this method with the current entity in the set. It is generally faster to change your entity query methods to avoid optional components; this may require a different Entities.ForEach construction to handle each combination of optional and non-optional components.
When you call this method on the main thread, it invokes HasComponent<T>(Entity).
(An Entities.ForEach function invoked with Run()
executes on the main thread.) When you call this method
inside a job scheduled using Entities.ForEach, this method gets replaced with component access methods
through ComponentDataFromEntity<T>.
In both cases, this lookup method results in a slower, indirect memory access. When possible, organize your data to minimize the need for indirect lookups.
OnUpdate()
Implement OnUpdate()
to perform the major work of this system.
Declaration
protected abstract void OnUpdate()
Remarks
The system invokes OnUpdate()
once per frame on the main thread when any of this system's
EntityQueries match existing entities, the system has the [AlwaysUpdateSystem]
attribute, or the system has no queries at all. OnUpdate()
is triggered by the system's
parent system group, which calls the Update() method of all its child
systems in its own OnUpdate()
function. The Update()
function evaluates whether a system
should, in fact, update before calling OnUpdate()
.
The Entities.ForEach and Job.WithCode constructions provide convenient mechanisms for defining jobs. You can also instantiate and schedule an IJobChunk instance; you can use the [C# JobSystem] or you can perform work on the main thread. If you call EntityManager methods that perform structural changes on the main thread, be sure to arrange the system order to minimize the performance impact of the resulting sync points.
SetComponent<T>(Entity, T)
Sets the value of a component of an entity.
Declaration
protected void SetComponent<T>(Entity entity, T component)
where T : struct, IComponentData
Parameters
Type | Name | Description |
---|---|---|
Entity | entity | The entity. |
T | component | The data to set. |
Type Parameters
Name | Description |
---|---|
T | The component type. |
Remarks
Use this method to look up and set data in another entity using its Entity object. For example, if you have a component that contains an Entity field, you can update the component data for the referenced entity using this method.
When iterating over a set of entities via Entities.ForEach, do not use this method to update data of the current entity in the set. This function is much slower than accessing the data directly (by passing the component containing the data to your lambda iteration function as a parameter).
When you call this method on the main thread, it invokes SetComponentData<T>(Entity, T).
(An Entities.ForEach function invoked with Run()
executes on the main thread.) When you call this method
inside a job scheduled using Entities.ForEach, this method gets replaced with component access methods
through ComponentDataFromEntity<T>.
In both cases, this lookup method results in a slower, indirect memory access. When possible, organize your data to minimize the need for indirect lookups.
Exceptions
Type | Condition |
---|---|
ArgumentException | Thrown if the component type has no fields. |
Update()
Update the system manually.
Declaration
public override sealed void Update()
Overrides
Remarks
Most programs should not update systems manually. SystemBase implementations
cannot override Update()
. Instead, implement system behavior in OnUpdate().