How to destroy entities along with disabling monobehaviour component.

Hello ecs fans, I work on my first unity ecs (1.0) project and this is the issue I struggle with at the moment.
I have units which have UI nameplates. Upon destroying unit entity, nameplate is disabled.

In my current implementation system destroys entities along with disabling nameplate in monobehaviour world and then it destroys the rest of entities which are tagged with DestroyTag.

Problem in my implementation is that the unit entity is destroyed in both queries. So destroy entity is called twice.
I guess it’s problem of the buffer? That it’s playback changes but in second query the changes are still not updated?

I tried to resolve this in jobs but could not figure out how.

Below is my implementation so far:

[UpdateInGroup(typeof(FixedStepSimulationSystemGroup))]
[RequireMatchingQueriesForUpdate]
public partial class DestroySystem : SystemBase
{
    protected override void OnCreate()
    {
        base.OnCreate();
    }

    protected override void OnUpdate()
    {
        Entities
            .WithDeferredPlaybackSystem<BeginFixedStepSimulationEntityCommandBufferSystem>()
            .ForEach((DestroyUnitAspect aspect, EntityCommandBuffer buffer) =>
            {
                Debug.Log("1");
                NamePlateManager.Instance.EntityDestroyed(aspect.Entity);
                EnemyCounterUI.Instance.AddEnemyCount(-1);
                buffer.DestroyEntity(aspect.Entity);
            }).WithoutBurst().Run();
        
        Entities
            .WithDeferredPlaybackSystem<BeginFixedStepSimulationEntityCommandBufferSystem>()
            .ForEach((DestroyAspect aspect ,EntityCommandBuffer buffer) =>
            {
                Debug.Log("2");
                buffer.DestroyEntity(aspect.Entity);
            }).Run();
    }
}

It is.
Both queries run will add entity to be destroyed [twice]. Playback occurs later on & it does not check if entity exist.

When playback occurs is decided by the ECB system you create buffer from.
(see execution order of systems)

You can just use EntityQuery as a parameter for the buffer to destroy entities outside of ForEach:

  • Grab & store an ECBSystem in OnCreate from the World;
  • Define & store EntityQuery for the entity cleanup (based on tags or otherwise);
  • Use _ecbSystem.CreateCommandBuffer() to create buffer separately from the ForEach;
  • Use ecb.DestroyEntity(query);
  • UI cleanup queries can be performed as is, just remove destroy entity command.

That way entity will get destroyed only once for all entities stored by the query upon buffer playback.

See EntityCommandBuffer.DestroyEntity(EntityQuery entityQuery)

In general, if your entity state is based on data structure (see tags) - its more efficient (both code & performance wise) to use operations on queries directly. Same applies to AddComponent[ForEntityQuery], RemoveComponent[ForEntityQuery], etc. In 1.0 there are respective AddComponent & RemoveComponent overloads.

1 Like

Haven’t notice that it is possible to use an EntityQuery as a parameter for a buffer. Learned something new. Thanks!

I updated code. Though I haven’t managed to store ECBSystem in OnCreate. I tried to use “older” way to get system from World, but now it returns SystemHandle which does not have CreateCommandBuffer method. And if try to use APISystem.GetSingleton… I got exception on play that there must exist 1 system.

[UpdateInGroup(typeof(FixedStepSimulationSystemGroup))]
[RequireMatchingQueriesForUpdate]
public partial class DestroySystem : SystemBase
{
    private EntityQuery _query;

    protected override void OnCreate()
    {
        base.OnCreate();
        _query= new EntityQueryBuilder(Allocator.Temp).WithAll<DestroyTag>().Build(this);
    }

    protected override void OnUpdate()
    {
        var ecb = SystemAPI.GetSingleton<EndFixedStepSimulationEntityCommandBufferSystem.Singleton>().CreateCommandBuffer(World.Unmanaged);
       
        Entities.ForEach((DestroyUnitAspect aspect) =>
            {
                NamePlateManager.Instance.EntityDestroyed(aspect.Entity);
            }).WithoutBurst().Run();
       
        ecb.DestroyEntity(_query);
    }
}

SystemHandle can be used to obtain an actual system via World.Unmanaged.GetUnsafeSystemRef(handle);
However, that is for unmanaged systems.

EndFixedStepSimulationEntityCommandBufferSystem is a managed system. (or at least was as of 1.0.0-pre15);
To grab it, use World.GetExistingSystemManaged(). It returns actual SystemBase.

ECBSystems are always created with automatic bootstrap, so no need to check if they exist.
If its not an ECBSystem, or something you didn’t declared & created via bootstrap, then its better to use World.GetOrCreateSystemManaged(). Though its a tiny bit slower.

You don’t need SystemAPI for that.

1 Like

Thanks, here is updated code:

[UpdateInGroup(typeof(FixedStepSimulationSystemGroup))]
[RequireMatchingQueriesForUpdate]
public partial class DestroySystem : SystemBase
{
    private EntityQuery _query;
    private EndFixedStepSimulationEntityCommandBufferSystem _ecbSystem;

    protected override void OnCreate()
    {
        base.OnCreate();
        _query= new EntityQueryBuilder(Allocator.Temp).WithAll<DestroyTag>().Build(this);
        _ecbSystem = World.GetExistingSystemManaged<EndFixedStepSimulationEntityCommandBufferSystem>();
    }

    protected override void OnUpdate()
    {
        var ecb = _ecbSystem.CreateCommandBuffer();
       
        Entities.ForEach((DestroyUnitAspect aspect) =>
            {
                NamePlateManager.Instance.EntityDestroyed(aspect.Entity);
            }).WithoutBurst().Run();
       
        ecb.DestroyEntity(_query);
    }
}