docs.unity3d.com
    Show / Hide Table of Contents

    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:

    • ComponentDataFromEntity
    • BufferFromEntity

    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 objects as read-only unless you do write to the components you access.

    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.

    Back to top
    Terms of use
    Copyright © 2023 Unity Technologies — Terms of use
    • Legal
    • Privacy Policy
    • Cookies
    • Do Not Sell or Share My Personal Information
    • Your Privacy Choices (Cookie Settings)
    "Unity", Unity logos, and other Unity trademarks are trademarks or registered trademarks of Unity Technologies or its affiliates in the U.S. and elsewhere (more info here). Other names or brands are trademarks of their respective owners.
    Generated by DocFX on 18 October 2023