Looking up data
The most efficient way to access and modify your ECS data is to use a system with an entity query and job. This provides the best utilization of CPU resources with the fewest memory cache misses. In fact, one of the goals of your data design should be to perform the bulk of your data transformation using the most efficient, fastest path. However, sometimes you need to access an arbitrary component of an arbitrary entity at an arbitrary point in your program.
Given an Entity object, you can look up data in its IComponentData and dynamic buffers. The method varies depending on whether your code executes in a system using Entities.ForEach or using an IJobChunk
job, or elsewhere on the main thread.
Looking up entity data in a system
Use GetComponent<T>(Entity) to look up data stored in a component of an arbitrary entity from inside a system's Entities.ForEach or Job.WithCode
function.
For example, if you have Target components with an Entity field identifying the entity to target, you can use the following code to rotate the tracking entities toward their target:
public partial class TrackingSystem : SystemBase
{
protected override void OnUpdate()
{
float deltaTime = this.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 (!HasComponent<LocalToWorld>(target.entity))
return;
// Look up the entity data
LocalToWorld targetTransform
= 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();
}
}
Accessing data stored in dynamic buffers requires an extra step. You must declare a local variable of type BufferFromEntity in your OnUpdate() method. You can then "capture" the local variable in your lambda function.
public struct BufferData : IBufferElementData
{
public float Value;
}
public partial class BufferLookupSystem : SystemBase
{
protected override void OnUpdate()
{
BufferFromEntity<BufferData> buffersOfAllEntities
= this.GetBufferFromEntity<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.HasComponent(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();
}
}
Looking up entity data in IJobEntityBatch
To randomly access component data in an IJobEntityBatch or other job struct, use one of the following types to get an array-like interface to component, indexed by Entity object:
Declare a field of type ComponentDataFromEntity or BufferFromEntity, and set the value of the field before scheduling the job.
For example, if you had Target components with an Entity field identifying the entities to target, you could add the following field to your job struct to look up the world position of the targets:
[ReadOnly]
public ComponentDataFromEntity<LocalToWorld> EntityPositions;
Note that this declaration uses the ReadOnly attribute. You should always declare ComponentDataFromEntity
You can set this field when scheduling the job as follows:
var job = new ChaserSystemJob();
job.EntityPositions = this.GetComponentDataFromEntity<LocalToWorld>(true);
Inside the job's Execute()
function, you can lookup the value of a component using an Entity object:
float3 targetPosition = EntityPositions[targetEntity].Position;
The following, full example shows a system that moves entities that have a Target field containing the Entity object of their target towards the current location of the target:
public partial class MoveTowardsEntitySystem : SystemBase
{
private EntityQuery query;
[BurstCompile]
private struct MoveTowardsJob : IJobEntityBatch
{
// Read-write data in the current chunk
public ComponentTypeHandle<Translation> PositionTypeHandleAccessor;
// Read-only data in the current chunk
[ReadOnly]
public ComponentTypeHandle<Target> TargetTypeHandleAccessor;
// Read-only data stored (potentially) in other chunks
[ReadOnly]
public ComponentDataFromEntity<LocalToWorld> EntityPositions;
// Non-entity data
public float deltaTime;
public void Execute(ArchetypeChunk batchInChunk, int batchIndex)
{
// Get arrays of the components in chunk
NativeArray<Translation> positions
= batchInChunk.GetNativeArray<Translation>(PositionTypeHandleAccessor);
NativeArray<Target> targets
= batchInChunk.GetNativeArray<Target>(TargetTypeHandleAccessor);
for (int i = 0; i < positions.Length; i++)
{
// Get the target Entity object
Entity targetEntity = targets[i].entity;
// Check that the target still exists
if (!EntityPositions.HasComponent(targetEntity))
continue;
// Update translation to move the chasing enitity toward the target
float3 targetPosition = EntityPositions[targetEntity].Position;
float3 chaserPosition = positions[i].Value;
float3 displacement = targetPosition - chaserPosition;
positions[i] = new Translation
{
Value = chaserPosition + displacement * deltaTime
};
}
}
}
protected override void OnCreate()
{
// Select all entities that have Translation and Target Componentx
query = this.GetEntityQuery
(
typeof(Translation),
ComponentType.ReadOnly<Target>()
);
}
protected override void OnUpdate()
{
// Create the job
var job = new MoveTowardsJob();
// Set the chunk data accessors
job.PositionTypeHandleAccessor =
this.GetComponentTypeHandle<Translation>(false);
job.TargetTypeHandleAccessor =
this.GetComponentTypeHandle<Target>(true);
// Set the component data lookup field
job.EntityPositions = this.GetComponentDataFromEntity<LocalToWorld>(true);
// Set non-ECS data fields
job.deltaTime = this.Time.DeltaTime;
// Schedule the job using Dependency property
this.Dependency = job.ScheduleParallel(query, this.Dependency);
}
}
Data access errors
If the data you are looking up overlaps the data you are directly reading and writing in the job, then random access can lead to race conditions and subtle bugs. When you are sure that there is no overlap between the specific entity data you are reading or writing directly in the job and the specific entity data you are reading or writing randomly, then you can mark the accessor object with the NativeDisableParallelForRestriction attribute.