docs.unity3d.com
    Show / Hide Table of Contents

    Using IJobEntity jobs

    IJobEntity, is a way to iterate across ComponentData, similar to Entities.ForEach. Use this, when you have a data transformation that you want in multiple systems, with different invocations. It creates the IJobEntityBatch for you, so all you have to think about is what data you need to transform.

    You write a struct using the IJobEntity interface, and implement your own custom Execute function. Remember the partial keyword, as source generation will create a struct implementing IJobEntityBatch in a separate file found inside project/Temp/GeneratedCode/..... Here's a simple example, that adds one to every translation component every frame.

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

    Specifying a Query

    There are two ways to specify a query for IJobEntity:

    1. Doing it manually, to specify different invocation requirements.
    2. Having the implemented IJobEntity do it for you, based on its given Execute parameters.

    An example of both can be seen here:

    partial struct QueryJob : IJobEntity
    {
        // Iterates over all translation components and increments upwards movement by one.
        public void Execute(ref Translation translation)
        {
            translation.Value += math.up();
        }
    }
    
    public partial class QuerySystem : SystemBase
    {
        // Query that matches QueryJob, specified for `BoidTarget`
        EntityQuery m_QueryBoidTarget;
    
        // Query that matches QueryJob, specified for `BoidObstacle`
        EntityQuery m_QueryBoidObstacle;
        protected override void OnCreate()
        {
            // Query that contains all of Execute params found in `QueryJob` - as well as additional user specified component `BoidTarget`.
            m_QueryBoidTarget = GetEntityQuery(ComponentType.ReadWrite<Translation>(),ComponentType.ReadOnly<BoidTarget>());
    
            // Query that contains all of Execute params found in `QueryJob` - as well as additional user specified component `BoidObstacle`.
            m_QueryBoidObstacle = GetEntityQuery(ComponentType.ReadWrite<Translation>(),ComponentType.ReadOnly<BoidObstacle>());
        }
    
        protected override void OnUpdate()
        {
            // Uses the BoidTarget query
            new QueryJob().ScheduleParallel(m_QueryBoidTarget);
    
            // Uses the BoidObstacle query
            new QueryJob().ScheduleParallel(m_QueryBoidObstacle);
    
            // Uses query created automatically that matches parameters found in `QueryJob`.
            new QueryJob().ScheduleParallel();
        }
    }
    

    Attributes

    Since this resembles a job, all attributes that work on a job also work:

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

    However, IJobEntity also has additional attributes:

    • Unity.Entities.EntityInQueryIndex Set on int parameter in Execute to get the current index in query, for the current entity iteration. It is the equivalent to the entityInQueryIndex found in Entities.ForEach.

    A sample of EntityInQueryIndex can be read as follows:

    [BurstCompile]
    partial struct CopyPositionsJob : IJobEntity
    {
        public NativeArray<float3> CopyPositions;
    
        // Iterates over all `LocalToWorld` and stores their position inside `copyPositions`.
        public void Execute([EntityInQueryIndex] int entityInQueryIndex, in LocalToWorld localToWorld)
        {
            CopyPositions[entityInQueryIndex] = localToWorld.Position;
        }
    }
    
    public partial class EntityInQuerySystem : SystemBase
    {
        // This query should match `CopyPositionsJob` parameters
        EntityQuery m_Query;
        protected override void OnCreate()
        {
            // Get query that matches `CopyPositionsJob` parameters
            m_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>(m_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);
        }
    }
    

    IJobEntity vs Entities.ForEach

    The core advantage of IJobEntity over Entities.ForEach is that it enables you to write code once which can be used throughout many systems, instead of only once.

    Here's an example taken from boids. This is Entities.ForEach:

    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();
        }
    }
    

    It can be rewritten as (remark, CopyPositionsJob can be found above):

    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));;
        }
    }
    
    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