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 Deleting a whole hierarchy of entities?

Discussion in 'Entity Component System' started by burningmime, Jun 20, 2020.

  1. burningmime

    burningmime

    Joined:
    Jan 25, 2014
    Posts:
    845
    Is there a way to delete an entity, and all its children (likely converted from GameObjects)?

    Here's the code running the job (roughly). Only the root is destroyed. If I add and remove props, there will end up being hundreds of entities:

    Code (CSharp):
    1.             private void addProp(WallProp prop)
    2.             {
    3.                 float3 pos = new float3(prop.position.x, prop.position.y, prop.position.z);
    4.                 quaternion rot = prop.isX ? quaternion.identity : quaternion.AxisAngle(new float3(0, 1, 0), -math.PI/2);
    5.              
    6.                 Entity e = ecb.Instantiate(prefabs[prop.propId]);
    7.                 ecb.AddComponent(e, new Translation { Value = pos });
    8.                 ecb.AddComponent(e, new Rotation { Value = rot });
    9.                 ecb.AddComponent(e, new EntityAndProp { entity = e, prop = prop });
    10.             }
    11.  
    12.             private void removeProp(EntityAndProp eap)
    13.             {
    14.                 ecb.DestroyEntity(eap.entity);
    15.             }

    Note that this is running from a MonoBehaviour callback, not a System, if that matters. The full, full code is:

    Code (CSharp):
    1.  
    2. using System;
    3. using System.Diagnostics;
    4. using Unity.Burst;
    5. using Unity.Collections;
    6. using Unity.Entities;
    7. using Unity.Jobs;
    8. using Unity.Mathematics;
    9. using Unity.Transforms;
    10. using UnityEngine;
    11. using UnityDebug = UnityEngine.Debug;
    12.  
    13. namespace burningmime.unity.building.jobs
    14. {
    15.     internal class PropBuilder : IDisposable
    16.     {
    17.         private static readonly ComponentType _eapType = new ComponentType(typeof(EntityAndProp), ComponentType.AccessMode.ReadOnly);
    18.        
    19.         private readonly Building _map;
    20.         private readonly Entity[] _prefabs;
    21.         private readonly BlobAssetStore _bas;
    22.         private EntityManager _em;
    23.         private EntityQuery _query;
    24.        
    25.         public PropBuilder(Building map)
    26.         {
    27.             // TODO handle switching from editor to play mode without domain reload
    28.             _map = map;
    29.             _em = getWorld().EntityManager;
    30.             _query = _em.CreateEntityQuery(_eapType);
    31.             _bas = new BlobAssetStore();
    32.            
    33.             // TODO build time...
    34.             var settings = GameObjectConversionSettings.FromWorld(_em.World, _bas);
    35.             GameObject[] propObjects = _map.propSet.propObjects;
    36.             _prefabs = new Entity[propObjects.Length];
    37.             for(int i = 0; i < propObjects.Length; ++i)
    38.             {
    39.                 if(propObjects[i] == null)
    40.                     continue;
    41.                 _prefabs[i] = GameObjectConversionUtility.ConvertGameObjectHierarchy(propObjects[i], settings);
    42.             }
    43.         }
    44.  
    45.         public void update()
    46.         {
    47.             NativeArray<Entity> prefabs = new NativeArray<Entity>(_prefabs, Allocator.TempJob);
    48.             var ecbSystem = _em.World.GetOrCreateSystem<BeginPresentationEntityCommandBufferSystem>();
    49.             EntityCommandBuffer ecb = ecbSystem.CreateCommandBuffer();
    50.             NativeArray<EntityAndProp> existing = _query.ToComponentDataArray<EntityAndProp>(Allocator.TempJob);
    51.             JobHandle handle = new BuildPropsJob {
    52.                 map = _map.getNativeData(),
    53.                 view = _map.getViewState(),
    54.                 existing = existing,
    55.                 ecb = ecb,
    56.                 prefabs = prefabs
    57.             }.Schedule();
    58.             ecbSystem.AddJobHandleForProducer(handle);
    59.             prefabs.Dispose(handle);
    60.             existing.Dispose(handle);
    61.         }
    62.  
    63.         private static World getWorld()
    64.         {
    65.             #if UNITY_EDITOR
    66.             if(!Application.isPlaying)
    67.                 DefaultWorldInitialization.DefaultLazyEditModeInitialize();
    68.             #endif
    69.             return World.DefaultGameObjectInjectionWorld;
    70.         }
    71.        
    72.         public void Dispose()
    73.         {
    74.             _query.Dispose();
    75.             _bas.Dispose();
    76.         }
    77.        
    78.         [BurstCompile]
    79.         private struct BuildPropsJob : IJob
    80.         {
    81.             [ReadOnly] public Building.Native map;
    82.             [ReadOnly] public Building.ViewState view;
    83.             [ReadOnly] public NativeArray<Entity> prefabs;
    84.             public NativeArray<EntityAndProp> existing;
    85.             public EntityCommandBuffer ecb;
    86.  
    87.             public void Execute()
    88.             {
    89.                 NativeList<WallProp> expected = new NativeList<WallProp>(math.max(64, existing.Length + 10), Allocator.Temp);
    90.                 getExpectedProps(map.wallsX.elements, true, expected);
    91.                 getExpectedProps(map.wallsZ.elements, false, expected);
    92.                 expected.Sort();
    93.                 existing.Sort();
    94.                 sync(expected);
    95.                 expected.Dispose();
    96.             }
    97.  
    98.             private void getExpectedProps(NativeArray<int> walls, bool isX, NativeList<WallProp> results)
    99.             {
    100.                 for(int i = 0; i < walls.Length; i += 2)
    101.                 {
    102.                     WallSegment w = WallSegment.fromPackedValue(walls[i + 1]);
    103.                     if(w.propId > 0 && w.propId < prefabs.Length && prefabs[w.propId] != Entity.Null)
    104.                     {
    105.                         PackedPoint p = PackedPoint.fromPackedValue(walls[i]);
    106.                         RoomMask roomMask = getRoomMask(p, isX);
    107.                         if((roomMask & view.roomMask).any)
    108.                             results.Add(new WallProp(p, isX, w.propId, roomMask));
    109.                     }
    110.                 }
    111.             }
    112.  
    113.             private void sync(NativeList<WallProp> expected)
    114.             {
    115.                 int ie = 0, ia = 0, ne = expected.Length, na = existing.Length;
    116.                 while(ie < ne || ia < na)
    117.                 {
    118.                     if(ia >= na) { addProp(expected[ie]); ++ie; }
    119.                     else if(ie >= ne) { removeProp(existing[ia]); ++ia; }
    120.                     else if(existing[ia].prop < expected[ie]) { removeProp(existing[ia]); ++ia; }
    121.                     else if(expected[ie] < existing[ia].prop) { addProp(expected[ie]); ++ie; }
    122.                     else { ++ia; ++ie; }
    123.                 }
    124.             }
    125.  
    126.             private void addProp(WallProp prop)
    127.             {
    128.                 float3 pos = new float3(prop.position.x, prop.position.y, prop.position.z);
    129.                 quaternion rot = prop.isX ? quaternion.identity : quaternion.AxisAngle(new float3(0, 1, 0), -math.PI/2);
    130.                
    131.                 Entity e = ecb.Instantiate(prefabs[prop.propId]);
    132.                 ecb.AddComponent(e, new Translation { Value = pos });
    133.                 ecb.AddComponent(e, new Rotation { Value = rot });
    134.                 ecb.AddComponent(e, new EntityAndProp { entity = e, prop = prop });
    135.             }
    136.  
    137.             private void removeProp(EntityAndProp eap)
    138.             {
    139.                 ecb.DestroyEntity(eap.entity);
    140.             }
    141.  
    142.             private RoomMask getRoomMask(PackedPoint p, bool isX)
    143.             {
    144.                 int x = p.x, y = p.y, z = p.z;
    145.                 if(isX)
    146.                 {
    147.                     int northId = z == 0 ? 0 : map.roomIdAt(x, y, z - 1);
    148.                     int southId = z >= map.sizeZ ? 0 : map.roomIdAt(x, y, z);
    149.                     if(northId > 63 || northId < 0) UnityDebug.Log($"{x}, {y}, {z}, north) = {northId}");
    150.                     if(southId > 63 || southId < 0) UnityDebug.Log($"{x}, {y}, {z}, south) = {southId}");
    151.                     return RoomMask.one(northId) | RoomMask.one(southId);
    152.                 }
    153.                 else
    154.                 {
    155.                     int westId = x == 0 ? 0 : map.roomIdAt(x - 1, y, z);
    156.                     int eastId = x >= map.sizeX ? 0 : map.roomIdAt(x, y, z);
    157.                     if(westId > 63 || westId < 0) UnityDebug.Log($"{x}, {y}, {z}, westId) = {westId}");
    158.                     if(eastId > 63 || eastId < 0) UnityDebug.Log($"{x}, {y}, {z}, eastId) = {eastId}");
    159.                     return RoomMask.one(westId) | RoomMask.one(eastId);
    160.                 }
    161.             }
    162.         }
    163.        
    164.         private struct EntityAndProp : IComparable<EntityAndProp>, IComponentData
    165.         {
    166.             internal Entity entity;
    167.             internal WallProp prop;
    168.             public int CompareTo(EntityAndProp other) => prop.CompareTo(other.prop);
    169.         }
    170.     }
    171. }
    172.  
    The version with MonoBehaviour/GameObject works fine: https://gitlab.com/burningmime/easybuilding/-/blob/master/Packages/building/src/PropManager.cs
     
    Last edited: Jun 20, 2020
  2. Kmsxkuse

    Kmsxkuse

    Joined:
    Feb 15, 2019
    Posts:
    303
    1. I personally never used the hybrid GameObject -> Entity workflow but the linkers between parent and child should be either a simple IComponent or, more likely, a Dynamic Buffer with wrapper structs of child entities.

    Simply go down that chain and call EntityCommandBuffer.concurrent.DestroyEntity(index, entity) on each one.

    Here's the thing, if you can do it in EntityManager, you can also do it in EntityCommandBuffer. It's literally the same syntax except ECBs are used in parallel and EMs are used in the main thread.

    If you're deleting a lot of entities in parallel, you should use ECBs. Otherwise, stick with a .WithStructrualChanges.Run() Entities / Job call with EMs in the logic.

    In fact, I recommend not using ECBs or EMs at runtime in the first place. Either way, they're incredibly slow and expensive.

    Try manually overwriting the entity data to correspond to new entity of the same archetype rather than deleting.

    2. I'll leave this question for someone more experienced than me in Hybrid ECS.
     
  3. burningmime

    burningmime

    Joined:
    Jan 25, 2014
    Posts:
    845
    Any samples of how to do this? I don't actually know how to do it from the main thread efficiently. I could get all entities in the scene with a Parent component and do a mark and sweep garbage collection, but I really would rather not do that.

    I'm running it from a MonoBehaviour, not a System. Does that change anything?

    Well, I could use the [DisableRendering] component, but adding/removing that would cause it to change archetype. I could also maybe use some degenerate transform so it's always culled, but that doesn't seem like it would be much faster than deleting the entities entirely
     
    Last edited: Jun 20, 2020
  4. timothy_j

    timothy_j

    Joined:
    Apr 11, 2020
    Posts:
    12
    I'n not certain this is what you are asking for, but the following component allows a prefab to be converted without appearing in game:
    Code (CSharp):
    1. using Unity.Entities;
    2.  
    3. [GenerateAuthoringComponent]
    4. public struct PrefabEntityComponent : IComponentData
    5. {
    6.     public Entity PrefabEntity;
    7. }
    And then you can use a spawner system to instantiate it:
    Code (CSharp):
    1. using TJUtility;
    2. using Unity.Entities;
    3. using Unity.Transforms;
    4. using UnityEngine;
    5.  
    6. public class EntitySpawnerSystem : SystemBase
    7. {
    8.     private float m_rate;
    9.     EndSimulationEntityCommandBufferSystem m_EndSimulationEcbSystem;
    10.  
    11.     protected override void OnCreate()
    12.     {
    13.         base.OnCreate();
    14.         // Find the ECB system once and store it for later usage
    15.         m_EndSimulationEcbSystem = World
    16.             .GetOrCreateSystem<EndSimulationEntityCommandBufferSystem>();
    17.     }
    18.  
    19.     protected override void OnUpdate()
    20.     {
    21.         m_rate += Time.DeltaTime;
    22.  
    23.         var ecb = m_EndSimulationEcbSystem.CreateCommandBuffer();
    24.  
    25.         if (/*m_rate > 0.1f)*/ Input.GetKeyDown(KeyCode.Return))
    26.         {
    27.  
    28.             Entities.WithStructuralChanges().ForEach((Entity spawner, ref PrefabEntityComponent prefabComponent, in Translation trans, in Rotation rot) =>
    29.             {
    30.                 Utility.Teleport(EntityManager, prefabComponent.PrefabEntity, this, trans.Value, rot.Value);
    31.  
    32.                 Entity spawnedEntity = EntityManager.Instantiate(prefabComponent.PrefabEntity);
    33.  
    34.                 ecb.AddComponent(spawnedEntity, new IsNPCTag());
    35.                 ecb.AddComponent(spawnedEntity, new GoalComponent());
    36.  
    37.             }).Run();
    38.  
    39.             m_rate = 0f;
    40.         }
    41.  
    42.         // TODO: separate out into another system?
    43.         Entities.WithAll<IsNPCTag, DieTag>().WithStructuralChanges().ForEach((Entity npc) =>
    44.         {
    45.             ecb.DestroyEntity(npc);
    46.         }).Run();
    47.     }
    48. }
    Sorry for the unneccessary code, hopefully you can work out the gist.
    In the above, when you add a DieTag component the whole prefab is destroyed, not just the base entity. I think this might well happen with entity hierarchies constructed in other ways too, but I haven't tried.
     
  5. burningmime

    burningmime

    Joined:
    Jan 25, 2014
    Posts:
    845
    That does not work. I end up with hundreds of entities left over.
     
  6. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    3,993
    The correct way to do this is to ensure your root entities have LinkedEntityGroup attached to them. EntityManager will delete all entities in that buffer when the root entity is deleted. Search for "LinkedEntityGroup" on this page to get lots of details. https://gametorrahod.com/game-object-conversion-and-subscene/
     
    burningmime and florianhanke like this.
  7. burningmime

    burningmime

    Joined:
    Jan 25, 2014
    Posts:
    845
    OK; thanks! I think I saw that in the debugger, but maybe there were issues with it.