Use EntityCommandBuffer in Multiple IJobEntity within ISystem

(Sorry for the inconvenience, My english is poor, the following content is translated by AI)

I am currently working with Unity DOTS, and I have encountered an issue as follows:

Error: InvalidOperationException: The previously scheduled job DestroyJob writes to the Unity.Entities.EntityCommandBuffer DestroyJob.JobData.ECB. You must call JobHandle.Complete() on the job DestroyJob, before you can write to the Unity.Entities.EntityCommandBuffer safely.

The functionality of the code is simple, it first destroys all planes (Entities with the TagPlane component), and then recreates some new planes.

DestroyJob.cs

namespace Systems.Jobs
{
    [BurstCompile]
    public partial struct DestroyJob : IJobEntity
    {
        public EntityCommandBuffer.ParallelWriter ECB;

        [BurstCompile]
        public void Execute(in TagPlane tag, in Entity entity)
        {
            ECB.DestroyEntity(entity.Index, entity);
        }
    }
}

SpawnJob.cs

namespace Systems.Jobs
{
    public partial struct SpawnJob : IJobEntity
    {
        public EntityCommandBuffer.ParallelWriter ECB;
        public void Execute([ChunkIndexInQuery] int chunkIndex, SpawnerAspect spawnerAspect)
        {
            for (int i = 0; i < SpawnerAspect.SpawnerComponent.ValueRO.NumberToSpawn; i++)
            {
                var spawnerComponent = SpawnerAspect.SpawnerComponent.ValueRO;
                var plane = ECB.Instantiate(chunkIndex, spawnerComponent.Prefab);
                var localTransform = new LocalTransform
                {
                    Position = // randomPosition,
                    Rotation = quaternion.identity,
                    Scale = 0.1f
                };
                ECB.SetComponent(chunkIndex, plane, localTransform);
                ECB.AddComponent(chunkIndex, plane, new TagPlane());
            }
        }
    }
}

SpawnerSystem.cs

public void OnUpdate(ref SystemState state){
    var singleton = SystemAPI.GetSingleton<EndSimulationEntityCommandBufferSystem.Singleton>();
    var ecb = singleton.CreateCommandBuffer(state.WorldUnmanaged);
    new DestroyJob()
    {
        ECB = ecb.AsParallelWriter()
    }.Schedule();

    // state.Dependency.Complete(); this works

    new SpawnJob()
    {
        ECB = ecb.AsParallelWriter(),
    }.Schedule();

}

I tried adding state.Dependency.Complete() before new SpawnJob, and the issue was resolved, as the error no longer occurs. However, I am unsure if Complete() is a blocking operation, and I feel that this is not the optimal solution.

I have searched for some information, but I am not sure if it is correct:

  1. I am unable to obtain an instance of SystemBase (EndSimulationEntityCommandBufferSystem) within an ISystem, and thus cannot call the AddJobHandleForProducer method. Additionally, some discussions mentioned that Singleton already handles dependencies by Unity, so there is no need to call AddJobHandleForProducer. Therefore, I do not believe this is the cause of the issue.

  2. Some suggest manually specifying the dependencies of the Job. After modifying the code as follows, the issue remains unresolved:

    var destroyJob = new DestroyJob()
    {
    	ECB = ecb.AsParallelWriter()
    }.Schedule(state.Dependency);
    
    // destroyJob.Complete(); without this line, cause same error
    
    var spawnJob = new SpawnJob()
    {
    	ECB = ecb.AsParallelWriter(),
    }.Schedule(destroyJob);
    
    state.Dependency = spawnJob;
    

I am now seeking to understand how to use the EntityCommandBuffer in multiple IJobEntity within an ISystem. Although state.Dependency.Complete() can resolve the issue, I consider it a temporary solution.

Many thanks!

Calling AsParallelWriter after including a previously created parallel writer in a scheduled job is problematic. Instead, store the parallel writer as a local variable and pass it to both jobs, instead of calling AsParallelWriter twice.

var ecb = singleton
    .CreateCommandBuffer(state.WorldUnmanaged)
    .AsParallelWriter();
new XXXJob() { ECB = ecb }.Schedule();
new YYYJob() { ECB = ecb }.Schedule();

Alternatively, you can just create two EntityCommandBuffer instances and handle them separately, you don’t have to use just one.

1 Like

Thank you so much, it works well