ECS - how do I instantiate & delete entities in the same parallel job?

trying to pick up ECS. I am having a real hard time with instantiating & destroy entities at the same job.
in this example, i have a lot of drones on screen. they will move towards a destination trivially(no collision/path find). once at the destination, a new random destination gets assigned, and the item the drone carries changes. the item being carried is a simple prefab, child under a transform under the prefab.

so i can get the object to instantiate just fine, but i couldn’t figure out how to delete the old item when a change of item happens. here are the core code:

// in create
var ecbSingleton = SystemAPI.GetSingleton<BeginSimulationEntityCommandBufferSystem.Singleton>();
        var ecb = ecbSingleton.CreateCommandBuffer(state.WorldUnmanaged);
        var stateUpdateJob = new StateUpdateJob 
        {
            randomArray = randomArray,
            ecb = ecb.AsParallelWriter(),
            itemDisplayBuffer = state.GetBufferLookup<ItemDisplayPrefabElement>(true),
            spawnerEntity = beltDroneSpawnerEntity
        };
        state.Dependency = stateUpdateJob.ScheduleParallel(state.Dependency);

// in job
                if (carryItem.item != Entity.Null) {
                    ecb.DestroyEntity(entityInQueryIndex, carryItem.item);
                    carryItem.item = Entity.Null;
                }
                var itemDisplayBufferEntity = spawnerEntity;
                var prefabsBuffer = itemDisplayBuffer[itemDisplayBufferEntity];
                var newItemPrefab = prefabsBuffer[(int)droneState.itemType].PrefabEntity;
                if (newItemPrefab != Entity.Null) {
                    // Instantiate the new item and assign it to carryItem.item
                    var newItem = ecb.Instantiate(entityInQueryIndex, newItemPrefab);
                    ecb.AddComponent<Parent>(entityInQueryIndex, newItem, new Parent {Value = carryItem.container});
                    carryItem.item = newItem;
                }

this is my code:

using Unity.Collections;
using Unity.Entities;
using Unity.Mathematics;
using Unity.Transforms;

[BurstCompile]
public partial struct SimpleMoverSystem : ISystem {
    private NativeArray<Unity.Mathematics.Random> randomArray;
    ComponentLookup<PreviousPosition> previousPositionLookup;
    [BurstCompile]
    public void OnCreate(ref SystemState state)
    {
        previousPositionLookup = state.GetComponentLookup<PreviousPosition>(true);
        // Create a random array for each thread in parallel jobs
        int workerCount = Unity.Jobs.LowLevel.Unsafe.JobsUtility.MaxJobThreadCount;
        randomArray = new NativeArray<Unity.Mathematics.Random>(workerCount, Allocator.Persistent);

        uint seed = (uint)UnityEngine.Random.Range(1, uint.MaxValue);
        for (int i = 0; i < workerCount; i++)
        {
            randomArray[i] = new Unity.Mathematics.Random(seed + (uint)i);
        }
        
        state.RequireForUpdate<BeginSimulationEntityCommandBufferSystem.Singleton>();
    }


    [BurstCompile]
    public void OnDestroy(ref SystemState state)
    {
        if (randomArray.IsCreated)
        {
            randomArray.Dispose();
        }
    }

    [BurstCompile]
    public void OnUpdate(ref SystemState state) {
        previousPositionLookup.Update(ref state);
        float deltaTime = SystemAPI.Time.DeltaTime;
            
        NativeArray<Unity.Mathematics.Random> randoms = randomArray;
        var simpleMoveJob = new SimpleMove 
        {
            deltaTime = deltaTime,
        };
        var handle = simpleMoveJob.ScheduleParallel(state.Dependency);

        state.Dependency = handle;  // Correctly assign the dependency

        var beltMoveJob = new BeltMove 
        {
            deltaTime = deltaTime,
            previousPositionFromEntity = previousPositionLookup
        };
        
        handle = beltMoveJob.ScheduleParallel(handle);
        state.Dependency = handle;        
        var beltDroneSpawnerEntity = SystemAPI.GetSingletonEntity<BeltDroneSpawner>();
        // var ecb = new EntityCommandBuffer(Allocator.TempJob);
        var ecbSingleton = SystemAPI.GetSingleton<BeginSimulationEntityCommandBufferSystem.Singleton>();
        var ecb = ecbSingleton.CreateCommandBuffer(state.WorldUnmanaged);
        var stateUpdateJob = new StateUpdateJob 
        {
            randomArray = randomArray,
            ecb = ecb.AsParallelWriter(),
            itemDisplayBuffer = state.GetBufferLookup<ItemDisplayPrefabElement>(true),
            spawnerEntity = beltDroneSpawnerEntity
        };
        state.Dependency = stateUpdateJob.ScheduleParallel(state.Dependency);
    }
}

// [BurstCompile]
public partial struct StateUpdateJob : IJobEntity {
    
    [NativeDisableParallelForRestriction]
    public NativeArray<Unity.Mathematics.Random> randomArray;
    public EntityCommandBuffer.ParallelWriter ecb; // To queue up entity commands
    [ReadOnly] public BufferLookup<ItemDisplayPrefabElement> itemDisplayBuffer; // BufferLookup instead of BufferFromEntity
    [ReadOnly] public Entity spawnerEntity;
    public void Execute(in LocalTransform transform, ref SimpleMover simpleMover, ref DroneState droneState, ref CarryItem carryItem, [EntityIndexInQuery] int entityInQueryIndex) {
        if (math.lengthsq(transform.Position - simpleMover.destination) < 0.0001f) {
            // Use thread-specific Random object
            var random = randomArray[entityInQueryIndex % randomArray.Length];
            simpleMover.destination = random.NextFloat3(new float3(-50f, 0, -50f), new float3(50f, 0, 50f));
            var oldItemType = droneState.itemType;
            droneState.itemType = (DOTS_ItemType)random.NextInt(0, (int)DOTS_ItemType.LastValue + 1);
            if (oldItemType != droneState.itemType) {
                // delete entity carryItem.item
                if (carryItem.item != Entity.Null) {
                    ecb.DestroyEntity(entityInQueryIndex, carryItem.item);
                    carryItem.item = Entity.Null;
                }
                var itemDisplayBufferEntity = spawnerEntity;
                var prefabsBuffer = itemDisplayBuffer[itemDisplayBufferEntity];
                var newItemPrefab = prefabsBuffer[(int)droneState.itemType].PrefabEntity;
                if (newItemPrefab != Entity.Null) {
                    // Instantiate the new item and assign it to carryItem.item
                    var newItem = ecb.Instantiate(entityInQueryIndex, newItemPrefab);
                    ecb.AddComponent<Parent>(entityInQueryIndex, newItem, new Parent {Value = carryItem.container});
                    carryItem.item = newItem;
                }
            }
            // Update the Random in the array after use
            randomArray[entityInQueryIndex % randomArray.Length] = random;
        }
    }
}

[BurstCompile]
public partial struct SimpleMove : IJobEntity
{
    public float deltaTime;

    public void Execute(ref LocalTransform transform, ref SimpleMover simpleMover, ref PreviousPosition previousPosition, [EntityIndexInQuery] int entityInQueryIndex)
    {
        var position = transform.Position;
        var destination = simpleMover.destination;
        var speed = simpleMover.speed;
        var diff = destination - position;
        var distanceSq = math.lengthsq(diff);
        var travelDistance = speed * deltaTime;

        if (distanceSq < travelDistance * travelDistance)
        {
            transform.Position = destination;
        }
        else
        {
            var direction = math.normalize(diff);
            var newPosition = position + direction * travelDistance;
            transform.Position = newPosition;
        }
        previousPosition.delta = transform.Position - position;
    }
}

[BurstCompile]
public partial struct BeltMove : IJobEntity {    
    public float deltaTime;
    
    
    [ReadOnly]  // Mark this as ReadOnly as well
    public ComponentLookup<PreviousPosition> previousPositionFromEntity;
    
    public void Execute(in BeltMover beltMover, ref LocalTransform transform, in Parent parent) {
        // Retrieve LocalTransform and PreviousPosition from parent entity
        var parentPreviousPosition = previousPositionFromEntity[parent.Value];
        
        var flatDelta = new float3(parentPreviousPosition.delta.x, 0, parentPreviousPosition.delta.z);
        var distanceSq = math.lengthsq(flatDelta);
        
        if (distanceSq > 0) {
            // Turn towards the direction we are moving
            var beltFlatDir = math.normalize(flatDelta);
            var beltForwardFlat = transform.Forward();
            beltForwardFlat.y = 0;
            beltForwardFlat = math.normalize(beltForwardFlat);
            // Calculate the angle difference in radians
            // Calculate the angle difference between the current forward direction and the target direction
            var up = new float3(0, 1, 0); // Rotate around the Y-axis
            float angleDifference = CFNUtils.SignedAngle(beltForwardFlat, beltFlatDir, up);

            // Determine if the belt should rotate towards the target or reverse
            float rotationAngle = angleDifference > 90 ? angleDifference - 180 : angleDifference < -90 ? angleDifference + 180 : angleDifference;
            
            // Apply the rotation towards the target
            var rotationStep = math.mul(transform.Rotation, quaternion.AxisAngle(up, rotationAngle * deltaTime * beltMover.speed * math.TORADIANS));
            transform.Rotation = rotationStep;
        }
    }
}


public partial struct PreviousPosition : IComponentData {
    public float3 delta;
}

public partial struct SimpleMover : IComponentData {
    public float speed;
    public float3 destination;
}

public partial struct BeltMover : IComponentData {
    public float speed;
}

public partial struct DroneState : IComponentData {
    public DOTS_ItemType itemType;
}```

newItem is a fake proxy entity that has a negative index. In order for your code to work, you need to assign carryItem.item using ecb.SetComponent().

1 Like

right because the entity wasn’t instantiated at this point. thanks!
this is the updated code.

public void Execute(Entity entity, in LocalTransform transform, ref SimpleMover simpleMover, ref DroneState droneState, ref CarryItem carryItem, [EntityIndexInQuery] int entityInQueryIndex) {

...
if (newItemPrefab != Entity.Null) {
                    // Instantiate the new item and assign it to carryItem.item
                    var newItem = ecb.Instantiate(entityInQueryIndex, newItemPrefab);
                    ecb.AddComponent<Parent>(entityInQueryIndex, newItem, new Parent {Value = carryItem.container});
                    ecb.SetComponent<CarryItem>(entityInQueryIndex, entity, new CarryItem {item = newItem, container = carryItem.container});
                }