Issue with DestroyEntity in ECS

I’m trying to make a system in ECS, that will show an outline around objects. My plan is to show this with two additional materials (mask and fill) for an entity. My starting point is the asset “Quick Outline”

As far as I have understood I have to make new entities to show additional materials for an entity so the way I have implemented it is to have a component like this:

public struct OutlineComponent : IComponentData
{ 
    public Entity MaskEntity;
    public Entity FillEntity;
    public bool Show;
}

Then I have a system where I do this in OnUpdate:

var ecb = this.ecbSystem.CreateCommandBuffer();
Entities
.WithoutBurst()
.ForEach((Entity entity, int entityInQueryIndex, ref OutlineComponent outline, in RenderMesh renderMesh, in RenderBounds renderBounds) =>
{

    if (outline.Show && outline.FillEntity == Entity.Null)
    {
        outline.FillEntity = CreateChildEntity(entity, renderMesh, archetype, materialFill, ref ecb);
    }
    else if (!outline.Show && outline.FillEntity != Entity.Null)
    {
        ecb.DestroyEntity(outline.FillEntity);
        outline.FillEntity = Entity.Null;
    }

    if (outline.Show && outline.MaskEntity == Entity.Null)
    {
        outline.MaskEntity = CreateChildEntity(entity, renderMesh, archetype, materialMask, ref ecb);
    }
    else if (!outline.Show && outline.MaskEntity != Entity.Null)
    {
        ecb.DestroyEntity(outline.MaskEntity);
        outline.MaskEntity = Entity.Null;
    }
}).Run();

Where this.ecbSystem is EndSimulationEntityCommandBufferSystem
CreateChildEntity(…) calls ecb.CreateEntity() and adds components


Flipping the OutlineComponent.Show bool to true works and the outline is displayed, but when set to false I get this error:

ArgumentException: System.InvalidOperationException: playbackState.CreateEntityBatch passed to SelectEntity is null

Anyone who can help me to solve this issue?

Would also welcome all feedback on the approach in general, since I’m new to ECS.


EDIT
Everything works if I use EntityQuery, a normal foreach loop and the EntityManager directly instead of the command buffer.

EDIT: (Long story short) Entity e = ecb.CreateEntity() returns a reference to deferred entity which won’t be automagically recognized outside scope of a single job run. Here is a solution to make ECB fix (remap) those deferred Entity field references:

    compData.entity = ecb.CreateEntity( archetype );
    ecb.SetComponent( entity , compData );

Theory put to test:

using Debug = UnityEngine.Debug;
using Unity.Entities;
using Unity.Rendering;
  
public class IssuewithDestroyEntityInECS : SystemBase
{
	EndSimulationEntityCommandBufferSystem ecbSystem;
	EntityArchetype archetype;
  
	protected override void OnCreate ()
	{
		ecbSystem = World.GetOrCreateSystem<EndSimulationEntityCommandBufferSystem>();
		archetype = EntityManager.CreateArchetype(
			typeof(OutlineComponent) ,
			typeof(RenderMesh) ,
			typeof(RenderBounds)
		);
  
		var ENTITY_1 = EntityManager.CreateEntity( archetype );
		EntityManager.SetComponentData( ENTITY_1 , new OutlineComponent{
			Show = true ,
			MaskEntity = Entity.Null ,
			FillEntity = Entity.Null ,
		} );
		EntityManager.SetName( ENTITY_1 , nameof(ENTITY_1) );
  
		var ENTITY_2 = EntityManager.CreateEntity( archetype );
		EntityManager.SetComponentData( ENTITY_2 , new OutlineComponent{
			Show = false ,
			MaskEntity = Entity.Null ,
			FillEntity = EntityManager.CreateEntity() ,
		} );
		EntityManager.SetName( ENTITY_2 , nameof(ENTITY_2) );
  
		var ENTITY_3 = EntityManager.CreateEntity( archetype );
		EntityManager.SetComponentData( ENTITY_3 , new OutlineComponent{
			Show = false ,
			MaskEntity = EntityManager.CreateEntity() ,
			FillEntity = Entity.Null ,
		} );
		EntityManager.SetName( ENTITY_3 , nameof(ENTITY_3) );
	}
	
	protected override void OnUpdate ()
	{
		var ecb = this.ecbSystem.CreateCommandBuffer();
		var entityManager = EntityManager;
  
		Entities
			.WithChangeFilter<OutlineComponent>()
			.ForEach( ( Entity entity , in OutlineComponent IN_outline ) =>
			{
				OutlineComponent outline = IN_outline;
				bool isDirty = false;
  
				// TEST: force to switch every frame
				outline.Show = !outline.Show; isDirty = true;
  
				if( outline.Show && outline.FillEntity==Entity.Null )
				{
					outline.FillEntity = ecb.CreateEntity();
					isDirty = true;
				}
				else if( !outline.Show && outline.FillEntity!=Entity.Null )
				{
					ecb.DestroyEntity( outline.FillEntity );
					outline.FillEntity = Entity.Null;
					isDirty = true;
				}
  
				if( outline.Show && outline.MaskEntity==Entity.Null )
				{
					outline.MaskEntity = ecb.CreateEntity();
					isDirty = true;
				}
				else if( !outline.Show && outline.MaskEntity!=Entity.Null )
				{
					ecb.DestroyEntity( outline.MaskEntity );
					outline.MaskEntity = Entity.Null;
					isDirty = true;
				}
  
				if( isDirty )
					ecb.SetComponent( entity , outline );
			})
			.WithoutBurst().Run();
  
		ecbSystem.AddJobHandleForProducer( this.Dependency );
	}
}
  
public struct OutlineComponent : IComponentData
{
	public Entity MaskEntity;
	public Entity FillEntity;
	public bool Show;
}

Unity 2020.2.1f1

Entities 0.16.0-preview.21