Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice

Question How to destroy entities along with disabling monobehaviour component.

Discussion in 'Entity Component System' started by Jericho90220, Feb 14, 2023.

  1. Jericho90220

    Jericho90220

    Joined:
    Feb 18, 2014
    Posts:
    6
    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:

    Code (CSharp):
    1. [UpdateInGroup(typeof(FixedStepSimulationSystemGroup))]
    2. [RequireMatchingQueriesForUpdate]
    3. public partial class DestroySystem : SystemBase
    4. {
    5.     protected override void OnCreate()
    6.     {
    7.         base.OnCreate();
    8.     }
    9.  
    10.     protected override void OnUpdate()
    11.     {
    12.         Entities
    13.             .WithDeferredPlaybackSystem<BeginFixedStepSimulationEntityCommandBufferSystem>()
    14.             .ForEach((DestroyUnitAspect aspect, EntityCommandBuffer buffer) =>
    15.             {
    16.                 Debug.Log("1");
    17.                 NamePlateManager.Instance.EntityDestroyed(aspect.Entity);
    18.                 EnemyCounterUI.Instance.AddEnemyCount(-1);
    19.                 buffer.DestroyEntity(aspect.Entity);
    20.             }).WithoutBurst().Run();
    21.        
    22.         Entities
    23.             .WithDeferredPlaybackSystem<BeginFixedStepSimulationEntityCommandBufferSystem>()
    24.             .ForEach((DestroyAspect aspect ,EntityCommandBuffer buffer) =>
    25.             {
    26.                 Debug.Log("2");
    27.                 buffer.DestroyEntity(aspect.Entity);
    28.             }).Run();
    29.     }
    30. }
     
  2. xVergilx

    xVergilx

    Joined:
    Dec 22, 2014
    Posts:
    3,294
    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.
     
    Last edited: Feb 15, 2023
    Jericho90220 likes this.
  3. Jericho90220

    Jericho90220

    Joined:
    Feb 18, 2014
    Posts:
    6
    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.

    Code (CSharp):
    1. [UpdateInGroup(typeof(FixedStepSimulationSystemGroup))]
    2. [RequireMatchingQueriesForUpdate]
    3. public partial class DestroySystem : SystemBase
    4. {
    5.     private EntityQuery _query;
    6.  
    7.     protected override void OnCreate()
    8.     {
    9.         base.OnCreate();
    10.         _query= new EntityQueryBuilder(Allocator.Temp).WithAll<DestroyTag>().Build(this);
    11.     }
    12.  
    13.     protected override void OnUpdate()
    14.     {
    15.         var ecb = SystemAPI.GetSingleton<EndFixedStepSimulationEntityCommandBufferSystem.Singleton>().CreateCommandBuffer(World.Unmanaged);
    16.        
    17.         Entities.ForEach((DestroyUnitAspect aspect) =>
    18.             {
    19.                 NamePlateManager.Instance.EntityDestroyed(aspect.Entity);
    20.             }).WithoutBurst().Run();
    21.        
    22.         ecb.DestroyEntity(_query);
    23.     }
    24. }
     
  4. xVergilx

    xVergilx

    Joined:
    Dec 22, 2014
    Posts:
    3,294
    SystemHandle can be used to obtain an actual system via World.Unmanaged.GetUnsafeSystemRef<T>(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<T>(). 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<T>(). Though its a tiny bit slower.


    You don't need SystemAPI for that.
     
    Jericho90220 likes this.
  5. Jericho90220

    Jericho90220

    Joined:
    Feb 18, 2014
    Posts:
    6
    Thanks, here is updated code:


    Code (CSharp):
    1. [UpdateInGroup(typeof(FixedStepSimulationSystemGroup))]
    2. [RequireMatchingQueriesForUpdate]
    3. public partial class DestroySystem : SystemBase
    4. {
    5.     private EntityQuery _query;
    6.     private EndFixedStepSimulationEntityCommandBufferSystem _ecbSystem;
    7.  
    8.     protected override void OnCreate()
    9.     {
    10.         base.OnCreate();
    11.         _query= new EntityQueryBuilder(Allocator.Temp).WithAll<DestroyTag>().Build(this);
    12.         _ecbSystem = World.GetExistingSystemManaged<EndFixedStepSimulationEntityCommandBufferSystem>();
    13.     }
    14.  
    15.     protected override void OnUpdate()
    16.     {
    17.         var ecb = _ecbSystem.CreateCommandBuffer();
    18.        
    19.         Entities.ForEach((DestroyUnitAspect aspect) =>
    20.             {
    21.                 NamePlateManager.Instance.EntityDestroyed(aspect.Entity);
    22.             }).WithoutBurst().Run();
    23.        
    24.         ecb.DestroyEntity(_query);
    25.     }
    26. }