docs.unity3d.com
Search Results for

    Show / Hide Table of Contents

    Iterate over component data with IJobEntity

    You can use IJobEntity to iterate across ComponentData when you have a data transformation that you want to use in multiple systems, with different invocations. It creates an IJobChunk job, so you only have to consider what data you want to transform.

    Create an IJobEntity job

    To create an IJobEntity job, write a struct that uses the IJobEntity interface, and implement your own custom Execute method.

    Use the partial keyword because source generation creates a struct that implements IJobChunk in a separate file in project/Temp/GeneratedCode/.....

    The following example adds one to every SampleComponent every frame.

    public struct SampleComponent : IComponentData { public float Value; }
    public partial struct ASampleJob : IJobEntity
    {
        // Adds one to every SampleComponent value
        void Execute(ref SampleComponent sample)
        {
            sample.Value += 1f;
        }
    }
    
    public partial class ASample : SystemBase
    {
        protected override void OnUpdate()
        {
            // Schedules the job
            new ASampleJob().ScheduleParallel();
        }
    }
    

    Specify a query

    You can specify a query for IJobEntity in the following ways:

    • Create a query manually, to specify different invocation requirements.
    • Use the IJobEntity attributes to create a query based on its given Execute parameters, and specifications on the job struct.

    The following example shows both options:

    partial struct QueryJob : IJobEntity
    {
        // Iterates over all SampleComponents and increments their value
        public void Execute(ref SampleComponent sample)
        {
            sample.Value += 1;
        }
    }
    
    [RequireMatchingQueriesForUpdate]
    public partial class QuerySystem : SystemBase
    {
        // Query that matches QueryJob, specified for `BoidTarget`
        EntityQuery query_boidtarget;
    
        // Query that matches QueryJob, specified for `BoidObstacle`
        EntityQuery query_boidobstacle;
        protected override void OnCreate()
        {
            // Query that contains all of Execute params found in `QueryJob` - as well as additional user specified component `BoidTarget`.
            query_boidtarget = GetEntityQuery(ComponentType.ReadWrite<SampleComponent>(),ComponentType.ReadOnly<BoidTarget>());
    
            // Query that contains all of Execute params found in `QueryJob` - as well as additional user specified component `BoidObstacle`.
            query_boidobstacle = GetEntityQuery(ComponentType.ReadWrite<SampleComponent>(),ComponentType.ReadOnly<BoidObstacle>());
        }
    
        protected override void OnUpdate()
        {
            // Uses the BoidTarget query
            new QueryJob().ScheduleParallel(query_boidtarget);
    
            // Uses the BoidObstacle query
            new QueryJob().ScheduleParallel(query_boidobstacle);
    
            // Uses query created automatically that matches parameters found in `QueryJob`.
            new QueryJob().ScheduleParallel();
        }
    }
    

    Attributes

    IJobEntity has the following built-in attributes:

    Attribute Description
    Unity.Entities.WithAll(params Type[]) Set on the job struct. Narrows the query so that the entities have to match all the types provided.
    Unity.Entities.WithAny(params Type[]) Set on the job struct. Narrows the query so that the entities have to match any of the types provided.
    Unity.Entities.WithNone(params Type[]) Set on the job struct. Narrows the query so that the entities have to match none of the types provided.
    Unity.Entities.WithChangeFilter(params Type[]) Set on the job struct or attach to an argument in Execute. Narrows the query so that the entities have to have had changes in the archetype chunk for the given components.
    Unity.Entities.WithOptions(params EntityQueryOptions[]) Set on the job struct. Changes the scope of the query to use the EntityQueryOptions described.
    Unity.Entities.EntityIndexInQuery Set on the int parameter in Execute to get the current index in query, for the current entity iteration. This is the same as entityInQueryIndex in Entities.ForEach.

    The following is an example of EntityIndexInQuery:

    [RequireMatchingQueriesForUpdate]
    public partial class EntityInQuerySystem : SystemBase
    {
        // This query should match `CopyPositionsJob` parameters
        EntityQuery query;
        protected override void OnCreate()
        {
            // Get query that matches `CopyPositionsJob` parameters
            query = GetEntityQuery(ComponentType.ReadOnly<LocalToWorld>());
        }
    
        protected override void OnUpdate()
        {
            // Get a native array equal to the size of the amount of entities found by the query.
            var positions = new NativeArray<float3>(query.CalculateEntityCount(), World.UpdateAllocator.ToAllocator);
    
            // Schedule job on parallel threads for this array.
            new CopyPositionsJob{copyPositions = positions}.ScheduleParallel();
    
            // Dispose the array of positions found by the job.
            positions.Dispose(Dependency);
        }
    }
    

    Because IJobEntity resembles a job, you can also use all attributes that work on a job:

    • Unity.Burst.BurstCompile
    • Unity.Collections.DeallocateOnJobCompletion
    • Unity.Collections.NativeDisableParallelForRestriction
    • Unity.Burst.BurstDiscard
    • Unity.Collections.LowLevel.Unsafe.NativeSetThreadIndex
    • Unity.Collections.NativeDisableParallelForRestriction
    • Unity.Burst.NoAlias

    Execute parameters

    The following is a list of all the supported Execute parameters you can use in IJobEntity:

    Parameter Description
    IComponentData Mark as ref for read-write access, or in for read-only access to the ComponentData.
    ICleanupComponentData Mark as ref for read-write access, or in for read-only access to the ComponentData.
    ISharedComponent Mark in for read-only access to a SharedComponentData. If this is managed you can't Burst compile or schedule it. Use .Run instead.
    Managed components Use a value-copy for read-write access or mark with in for read-only access of managed components. For example, UnityEngine.Transform. Marking a managed component as ref is an error, and you can't Burst-compile or schedule it. Use .Run instead.
    Entity Gets the current entity. This is a value copy only, so don't mark with ref or in.
    DynamicBuffer<T> Gets the DynamicBuffer. Mark with ref for read-write access and in for read-only access.
    IAspect Gets the Aspect. Aspects act as references so you can't assign them. However, you can use ref and value-copy to mark it as read-write, and in for read-only access.
    int There are three supported ints:
    Mark the int with the attribute [Unity.Entities.ChunkIndexInQuery] to get the current archetype chunk index in a query.
    Mark the int with the attribute [Unity.Entities.EntityIndexInChunk] to get the current entity index in the current archetype chunk. You can add EntityIndexInChunk and ChunkIndexInQuery to get a unique identifier per entity.
    Mark the int with the attribute [Unity.Entities.EntityIndexInQuery] to get the packed index of the query. This parameter internally uses EntityQuery.CalculateBaseEntityIndexArray[Async] which negatively affects performance.

    Comparison between IJobEntity and Entities.ForEach

    IJobEntity is similar to Entities.ForEach, however you can reuse IJobEntity throughout several systems, so you should use it over Entities.ForEach where possible. For example, this is an Entities.ForEach example:

    [RequireMatchingQueriesForUpdate]
    public partial class BoidForEachSystem : SystemBase
    {
        EntityQuery m_BoidQuery;
        EntityQuery m_ObstacleQuery;
        EntityQuery m_TargetQuery;
        protected override void OnUpdate()
        {
            // Calculate amount of entities in respective queries.
            var boidCount = m_BoidQuery.CalculateEntityCount();
            var obstacleCount = m_ObstacleQuery.CalculateEntityCount();
            var targetCount = m_TargetQuery.CalculateEntityCount();
    
            // Allocate arrays to store data equal to the amount of entities matching respective queries.
            var cellSeparation = CollectionHelper.CreateNativeArray<float3, RewindableAllocator>(boidCount, ref World.UpdateAllocator);
            var copyTargetPositions = CollectionHelper.CreateNativeArray<float3, RewindableAllocator>(targetCount, ref World.UpdateAllocator);
            var copyObstaclePositions = CollectionHelper.CreateNativeArray<float3, RewindableAllocator>(obstacleCount, ref World.UpdateAllocator);
    
            // Schedule job for respective arrays to be stored with respective queries.
            Entities
                .WithSharedComponentFilter(new BoidSetting{num=1})
                .ForEach((int entityInQueryIndex, in LocalToWorld localToWorld) =>
                {
                    cellSeparation[entityInQueryIndex] = localToWorld.Position;
                })
                .ScheduleParallel();
    
            Entities
                .WithAll<BoidTarget>()
                .WithStoreEntityQueryInField(ref m_TargetQuery)
                .ForEach((int entityInQueryIndex, in LocalToWorld localToWorld) =>
                {
                    copyTargetPositions[entityInQueryIndex] = localToWorld.Position;
                })
                .ScheduleParallel();
    
            Entities
                .WithAll<BoidObstacle>()
                .WithStoreEntityQueryInField(ref m_ObstacleQuery)
                .ForEach((int entityInQueryIndex, in LocalToWorld localToWorld) =>
                {
                    copyObstaclePositions[entityInQueryIndex] = localToWorld.Position;
                })
                .ScheduleParallel();
        }
    }
    

    You can rewrite it as the following with IJobEntity:

    [BurstCompile]
    partial struct CopyPositionsJob : IJobEntity
    {
        public NativeArray<float3> copyPositions;
    
        // Iterates over all `LocalToWorld` and stores their position inside `copyPositions`.
        public void Execute([EntityIndexInQuery] int entityIndexInQuery, in LocalToWorld localToWorld)
        {
            copyPositions[entityIndexInQuery] = localToWorld.Position;
        }
    }
    
    [RequireMatchingQueriesForUpdate]
    public partial class BoidJobEntitySystem : SystemBase
    {
        EntityQuery m_BoidQuery;
        EntityQuery m_ObstacleQuery;
        EntityQuery m_TargetQuery;
    
        protected override void OnUpdate()
        {
            // Calculate amount of entities in respective queries.
            var boidCount = m_BoidQuery.CalculateEntityCount();
            var obstacleCount = m_ObstacleQuery.CalculateEntityCount();
            var targetCount = m_TargetQuery.CalculateEntityCount();
    
            // Allocate arrays to store data equal to the amount of entities matching respective queries.
            var cellSeparation = CollectionHelper.CreateNativeArray<float3, RewindableAllocator>(boidCount, ref World.UpdateAllocator);
            var copyTargetPositions = CollectionHelper.CreateNativeArray<float3, RewindableAllocator>(targetCount, ref World.UpdateAllocator);
            var copyObstaclePositions = CollectionHelper.CreateNativeArray<float3, RewindableAllocator>(obstacleCount, ref World.UpdateAllocator);
    
            // Schedule job for respective arrays to be stored with respective queries.
            new CopyPositionsJob { copyPositions = cellSeparation}.ScheduleParallel(m_BoidQuery);
            new CopyPositionsJob { copyPositions = copyTargetPositions}.ScheduleParallel(m_TargetQuery);
            new CopyPositionsJob { copyPositions = copyObstaclePositions}.ScheduleParallel(m_ObstacleQuery);
        }
    
        protected override void OnCreate()
        {
            // Get respective queries, that includes components required by `CopyPositionsJob` described earlier.
            m_BoidQuery = GetEntityQuery(typeof(LocalToWorld));
            m_BoidQuery.SetSharedComponentFilter(new BoidSetting{num=1});
            m_ObstacleQuery = GetEntityQuery(typeof(LocalToWorld), typeof(BoidObstacle));
            m_TargetQuery = GetEntityQuery(typeof(LocalToWorld), typeof(BoidTarget));;
        }
    }
    

    Additional resources

    • Entities.ForEach documentation
    • IJobEntity API documentation
    In This Article
    Back to top
    Copyright © 2024 Unity Technologies — Trademarks and terms of use
    • Legal
    • Privacy Policy
    • Cookie Policy
    • Do Not Sell or Share My Personal Information
    • Your Privacy Choices (Cookie Settings)