Look up arbitrary data
The most efficient way to access and change data is to use a system with an entity query and a job. This utilizes the CPU resources in the most efficient way, with minimal memory cache misses. It's best practice to use the most efficient, fastest path to perform the bulk of data transformations. However, there are times when you might need to access an arbitrary component of an arbitrary entity at an arbitrary point in your program.
You can look up data in an entity's IComponentData
and its dynamic buffers. The way you look up data depends on whether your code uses Entities.ForEach
, or an IJobChunk
job, or some other method on the main thread to execute in a system.
Look up entity data in a system
To look up data stored in a component of an arbitrary entity from inside a system's Entities.ForEach
or Job.WithCode
method, use GetComponent<T>(Entity)
For example, the following code uses GetComponent<T>(Entity)
to get a Target
component, which has an entity field that identifies the entity to target. It then rotates the tracking entities towards their target:
[RequireMatchingQueriesForUpdate]
public partial class TrackingSystem : SystemBase
{
protected override void OnUpdate()
{
float deltaTime = SystemAPI.Time.DeltaTime;
Entities
.ForEach((ref Rotation orientation,
in LocalToWorld transform,
in Target target) =>
{
// Check to make sure the target Entity still exists and has
// the needed component
if (!SystemAPI.HasComponent<LocalToWorld>(target.entity))
return;
// Look up the entity data
LocalToWorld targetTransform
= SystemAPI.GetComponent<LocalToWorld>(target.entity);
float3 targetPosition = targetTransform.Position;
// Calculate the rotation
float3 displacement = targetPosition - transform.Position;
float3 upReference = new float3(0, 1, 0);
quaternion lookRotation =
quaternion.LookRotationSafe(displacement, upReference);
orientation.Value =
math.slerp(orientation.Value, lookRotation, deltaTime);
})
.ScheduleParallel();
}
}
If you want to access data stored in a dynamic buffer, you also need to declare a local variable of type BufferLookup
in the SystemBase
OnUpdate
method. You can then capture the local variable in a lambda expression. For example:
public struct BufferData : IBufferElementData
{
public float Value;
}
[RequireMatchingQueriesForUpdate]
public partial class BufferLookupSystem : SystemBase
{
protected override void OnUpdate()
{
BufferLookup<BufferData> buffersOfAllEntities
= this.GetBufferLookup<BufferData>(true);
Entities
.ForEach((ref Rotation orientation,
in LocalToWorld transform,
in Target target) =>
{
// Check to make sure the target Entity with this buffer type still exists
if (!buffersOfAllEntities.HasBuffer(target.entity))
return;
// Get a reference to the buffer
DynamicBuffer<BufferData> bufferOfOneEntity =
buffersOfAllEntities[target.entity];
// Use the data in the buffer
float avg = 0;
for (var i = 0; i < bufferOfOneEntity.Length; i++)
{
avg += bufferOfOneEntity[i].Value;
}
if (bufferOfOneEntity.Length > 0)
avg /= bufferOfOneEntity.Length;
})
.ScheduleParallel();
}
}
Look up entity data in a job
To access component data at random in a job struct such as IJobChunk
, use one of the following types:
These types get an array-like interface to component, indexed by Entity
object. You can also use ComponentLookup
to determine whether an entity's enableable components are enabled or disabled, or to toggle the state of these components.
To use them, declare a field of type ComponentLookup
or BufferLookup
, set the value of the field, and then schedule the job.
For example, you can use the ComponentLookup
field to look up the world position of entities:
[ReadOnly]
public ComponentLookup<LocalToWorld> EntityPositions;
Note
This declaration uses the ReadOnly
attribute. Always declare ComponentLookup
objects as read-only unless you want to write to the components you access.
The following example illustrates how to set the data fields and schedule the job:
protected override void OnUpdate()
{
var job = new ChaserSystemJob();
// Set non-ECS data fields
job.deltaTime = SystemAPI.Time.DeltaTime;
// Schedule the job using Dependency property
Dependency = job.ScheduleParallel(query, this.Dependency);
}
To look up the value of a component, use an entity object inside the job's Execute
method:
float3 targetPosition = entityPosition.Position;
float3 chaserPosition = transform.Position;
float3 displacement = targetPosition - chaserPosition;
float3 newPosition = chaserPosition + displacement * deltaTime;
transform.Position = newPosition;
The following, full example shows a system that moves entities that have a Target
field that contains the entity object of their target towards the current location of the target:
[RequireMatchingQueriesForUpdate]
public partial class MoveTowardsEntitySystem : SystemBase
{
private EntityQuery query;
[BurstCompile]
private partial struct MoveTowardsJob : IJobEntity
{
// Read-only data stored (potentially) in other chunks
[ReadOnly]
public ComponentLookup<LocalToWorld> EntityPositions;
// Non-entity data
public float deltaTime;
public void Execute(ref LocalTransform transform, in Target target, in LocalToWorld entityPosition)
{
// Get the target Entity object
Entity targetEntity = target.entity;
// Check that the target still exists
if (!EntityPositions.HasComponent(targetEntity))
return;
// Update translation to move the chasing entity toward the target
float3 targetPosition = entityPosition.Position;
float3 chaserPosition = transform.Position;
float3 displacement = targetPosition - chaserPosition;
transform.Position = chaserPosition + displacement * deltaTime;
}
}
protected override void OnCreate()
{
// Select all entities that have Translation and Target Component
query = this.GetEntityQuery
(
typeof(LocalTransform),
ComponentType.ReadOnly<Target>()
);
}
protected override void OnUpdate()
{
// Create the job
var job = new MoveTowardsJob();
// Set the component data lookup field
job.EntityPositions = GetComponentLookup<LocalToWorld>(true);
// Set non-ECS data fields
job.deltaTime = SystemAPI.Time.DeltaTime;
// Schedule the job using Dependency property
Dependency = job.ScheduleParallel(query, Dependency);
}
}
Data access errors
If the data you look up overlaps the data you want to read and write to in the job, then random access might lead to race conditions.
You can mark an accessor object with the NativeDisableParallelForRestriction
attribute, if you're sure that there's no overlap between the entity data you want to read or write to directly, and the specific entity data you want to read or write to at random.