Automatically Destroy childs?

Hi,

I have a “root” entity which places the childs correctly in the world. There exist 100 different childs (different Combination of Datacomponents & number of “sub childs” …)

The problem now is the cleanup of the entites. I have a job which iterates over each root object and decides if it should be removed or not. The thing is that if i destroy a root entity, the childs still exist and are not removed.

I could write a job for each component combination which will cleanup the childs correctly, but this will take alot of time and i wonder if there is an easier way to just say "CommandBuffer.DestroyEntityWithChilds(entity); or not.

EntityManager.DestroyEntity() has an overload that takes a query. You might want to use this with a suitable query for your needs.

@GilCat i can only use this from the mainthread right? Is this possible from inside a job as I need to cleanup depending on the data of each entity.

Basically: ForEach → If(root.Value.Equals(XXX)) → DestroyWithChildren(Root)

If you have linked entity group on root it should destroy root entity with childs when you destroy root

EntityCommandBuffer also has this overload but you can’t use it in a job because jobs can’t take reference types (EntityQuery).
If you want to destroy a hierarchy in a job I would go for traversing the hierarchy recursively from root to all the children and destroying them recursively. Heck you can even use bust with that.

I’ve took some time and wrote a Parallel job that will destroy and transform hierarchy as I might also need this in the future.

  [BurstCompile]
  struct DestroyHierarchyJob<T> : IJobForEachWithEntity<T>
    where T: struct, IComponentData {

    public EntityCommandBuffer.Concurrent CmdBuffer;
    [ReadOnly]
    public BufferFromEntity<Child> ChildrenFromEntity;

    public void Execute(Entity entity, int index, [ReadOnly]ref T _) {
      DestroyHierarchy(CmdBuffer, entity, index, ChildrenFromEntity);
    }

    void DestroyHierarchy(EntityCommandBuffer.Concurrent cmdBuffer, Entity entity, int index, BufferFromEntity<Child> childrenFromEntity) {
      if (!childrenFromEntity.Exists(entity))
        return;
      var children = childrenFromEntity[entity];
      for (var i = 0; i < children.Length; ++i) {
        var childEntity = children[i].Value;
        cmdBuffer.DestroyEntity(index, childEntity);
        DestroyHierarchy(cmdBuffer, childEntity, index, childrenFromEntity);
      }
    }
  }

Now when you want to destroy a hierarch just ForEach → If(root.Value.Equals(XXX)) → AddComponentData(root.Value, DestroyRoot)
DestroyRoot is a IComponentData that you will add to your root entities to trigger the destroy system.

Destroy system will looks something like this:

  public struct DestroyHierarchy : IComponentData { }

  class DestroyRootSystem : JobComponentSystem {

    EndSimulationEntityCommandBufferSystem m_endSimulationCmdBuffer;
    EntityQuery m_query;

    protected override void OnCreate() {
      base.OnCreate();
      m_endSimulationCmdBuffer = World.Active.GetOrCreateSystem<EndSimulationEntityCommandBufferSystem>();
      m_query = GetEntityQuery(typeof(DestroyHierarchy));
    }

    protected override JobHandle OnUpdate(JobHandle inputDeps) {
      inputDeps = new DestroyHierarchyJob<DestroyHierarchy> {
        CmdBuffer = m_endSimulationCmdBuffer.CreateCommandBuffer().ToConcurrent(),
        ChildrenFromEntity = GetBufferFromEntity<Child>(true)
      }.Schedule(m_query, inputDeps);
      m_endSimulationCmdBuffer.CreateCommandBuffer().RemoveComponent(m_query, typeof(DestroyHierarchy));
      m_endSimulationCmdBuffer.AddJobHandleForProducer(inputDeps);
      return inputDeps;
    }
  }
}

EDIT: Added RemoveComponent for avoiding the hierarchy to auto destroy next time children are added

2 Likes

Thank you for your code examples! Can you please talk a little bit about BufferFromEntity ? I have never seen something like that in the ECS examples and i can guess what it does, but a little explanation would be good

EDIT: i played around with your code and found that it doesnt work on my system. My guess is because i never specify any Buffer anywhere so my “Child Buffer” is always empty and doesnt iterate over it.

I create my Entites via code like this:

var parent = CommandBuffer.Instantiate(Entity);
var child= CommandBuffer.Instantiate(Entity);

CommandBuffer.AddComponent(child, new Parent{Value = parent});

EDIT2: I struggle to add something to my buffer here (i guess?):

var buffer = CommandBuffer.AddBuffer(index, entity);
buffer.Add(new Child(){Value = child1});
buffer.Add(new Child(){Value = child2});
buffer.Add(new Child(){Value = child3});

When you add a children into your transform hierarchy an internal state is added to the parent entity as ISystemStateBufferElementData. Read more about the TransformSystem here.
BufferFromEntity is how you access dynamic buffers inside jobs.

To create your entities you should try to use the entity conversion workflow , but if you really must do it from code you must add LocalToWorld and LocalToParent component after CommandBuffer.AddComponent(child, new Parent{Value = parent});
[quote=“Cell-i-Zenit, post:6, topic: 745296, username:Cell-i-Zenit”]
EDIT2: I struggle to add something to my buffer here (i guess?):
var buffer = CommandBuffer.AddBuffer(index, entity);
buffer.Add(new Child(){Value = child1});
buffer.Add(new Child(){Value = child2});
buffer.Add(new Child(){Value = child3});
*[/quote]
*
You can’t do that (and you don’t need to) because it is the internal system state of TransformSystem.

Btw, what version of Entities are you using?

1 Like

Thank you for your help, it looks like i had it working before (like you now explained it), but like always the error was on a different part of the code :slight_smile: The error was that i used OnStartRunning not as it was intended. I thought its kind of like Start (and OnCreate is awake), but it gets executed every frame(?).

Is there an explanation where i can get information about the overrides? When they are executed etc?

.12-Preview 33 is the version iam using.

This is my solution to this:

public struct EntityReference : IComponentData {
    public readonly Entity owner;

    public EntityReference(Entity owner) {
        this.owner = owner;
    }

    public static void Create(Entity owner, Entity referred, EntityManager entityManager) {
        entityManager.AddComponentData(referred, new EntityReference(owner));
    }

    public static void Create(Entity owner, Entity referred, EntityCommandBuffer commandBuffer) {
        commandBuffer.AddComponent(referred, new EntityReference(owner));
    }
}

// Not converted to job yet
public class DestroyUnownedEntityReferencesSystem : ComponentSystem {
    private EntityQuery query;
   
    private ArchetypeChunkEntityType entityType;
    private ArchetypeChunkComponentType<EntityReference> referenceType;

    private EndPresentationEntityCommandBufferSystem barrier;

    protected override void OnCreateManager() {
        this.query = GetEntityQuery(typeof(EntityReference));
        this.barrier = this.World.GetOrCreateSystem<EndPresentationEntityCommandBufferSystem>();
    }

    protected override void OnUpdate() {
        this.entityType = GetArchetypeChunkEntityType();
        this.referenceType = GetArchetypeChunkComponentType<EntityReference>(true);

        NativeArray<ArchetypeChunk> chunks = this.query.CreateArchetypeChunkArray(Allocator.TempJob);
       
        // Collection of referenced entities
        NativeHashMap<Entity, byte> referencedEntities = new NativeHashMap<Entity, byte>(10, Allocator.TempJob);

        // Note here that we store the referenced entities here so that we can check if they
        // are still referenced by another entities before destroying them
        StoreReferencedEntities(ref chunks, ref referencedEntities);
        DestroyUnownedReferences(ref chunks, ref referencedEntities);
       
        referencedEntities.Dispose();
        chunks.Dispose();
    }

    private void StoreReferencedEntities(ref NativeArray<ArchetypeChunk> chunks, ref NativeHashMap<Entity, byte> referencedEntities) {
        for (int i = 0; i < chunks.Length; ++i) {
            StoreReferencedEntities(chunks[i], ref referencedEntities);
        }
    }

    private void StoreReferencedEntities(ArchetypeChunk chunk,
        ref NativeHashMap<Entity, byte> referencedEntities) {
        NativeArray<Entity> entities = chunk.GetNativeArray(this.entityType);
        NativeArray<EntityReference> references = chunk.GetNativeArray(this.referenceType);
        for (int i = 0; i < references.Length; ++i) {
            EntityReference entityReference = references[i];
           
            if (this.EntityManager.Exists(entityReference.owner)) {
                referencedEntities.AddOrReplace(entities[i], (byte)0);
            }
        }
    }

    private void DestroyUnownedReferences(ref NativeArray<ArchetypeChunk> chunks,
        ref NativeHashMap<Entity, byte> referencedEntities) {
        for (int i = 0; i < chunks.Length; ++i) {
            DestroyUnownedReferences(chunks[i], ref referencedEntities);
        }
    }

    private void DestroyUnownedReferences(ArchetypeChunk chunk, ref NativeHashMap<Entity, byte> referencedEntities) {
        NativeArray<Entity> entities = chunk.GetNativeArray(this.entityType);
        NativeArray<EntityReference> references = chunk.GetNativeArray(this.referenceType);
        for (int i = 0; i < references.Length; ++i) {
            EntityReference entityReference = references[i];
           
            if (!this.EntityManager.Exists(entityReference.owner) && !referencedEntities.TryGetValue(entities[i], out _)) {
                // Owner no longer exists and the referenced Entity is not referenced by anyone.
                // We destroy the reference as it is no longer being pointed to
                this.PostUpdateCommands.DestroyEntity(entities[i]);
            }
        }
    }
}

The only drawback is that I have to call EntityReference.Create() every time an entity reference is established. It’s easy to forget.