- Home /
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.
if( thatEntity!=Entity.Null ) ecb.DestroyEntity( thatEntity );
else Debug("oh noes, that entity is totally null! O:");
?
Thank you for the reply, but I'm not sure I understand what you mean. outline.FillEntity
and outline.$$anonymous$$askEntity
is by design null when outline.Show
is false. The idea here is that when Show is flipped to false, they will not be null and therefore destroyed and at least we know that the DestroyEntity method is called since it is producing the error.
Oh sorry, I somehow didn't noticed you was testing for that already
Answer by andrew-lukasik · Jan 18, 2021 at 11:04 AM
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
I did not get any errors running your code, but I did if I tried to switch the Show On/Off during runtime.
I used this in a $$anonymous$$onoBehaviour.LateUpdate
if(UnityEngine.Input.GetKeyDown(UnityEngine.KeyCode.O))
{
var query = entity$$anonymous$$anager.CreateEntityQuery(typeof(OutlineComponent));
using(var entities = query.ToEntityArray(Unity.Collections.Allocator.Temp))
{
foreach(var entity in entities)
{
var outline = entity$$anonymous$$anager.GetComponentData<OutlineComponent>(entity);
outline.Show = !outline.Show;
UnityEngine.Debug.Log($"Toggle outline to {outline.Show} for {entity$$anonymous$$anager.GetName(entity)}");
entity$$anonymous$$anager.SetComponentData<OutlineComponent>(entity, outline);
}
}
}
Working around dependency graph (JobHandles) like this will cause such errors. Especially when command buffer is involved, sooner or later, it creates race conditions.
You need to introduce dependency back into this workflow to fix the issue ie.: to stop modifying chunks after commands were created but not executed yet.
Thank you for helping me. It makes sense. One more test. I tried to add outline.Show = !outline.Show; to the beginning of the ForEach (right before the first if statement). At least then there are no dependency issues was my thinking, but it fails immediately with: ArgumentException: Invalid Entity version Do you understand what is going on here?