Search Unity

Question How does EntityQuery/Foreach handle entities destroyed in another job

Discussion in 'Entity Component System' started by libra34567, May 20, 2022.

  1. libra34567

    libra34567

    Joined:
    Apr 5, 2014
    Posts:
    62
    A few questions come in mind, and would really appreciated if somebody could point me to the right doc as I can't find it anywhere.

    Suppose I do everything via ecb and schedule the Entities.Foreach().Schedule()/ScheduleParallel()

    Here is a few of my assumptions
    1. Does ForEach construction of enittyQuery happens on mainthread? And then based on the matching entities it finds it schedule jobs in worker threads right?
    2. If that's the case, what would happen if: when the forEach is schduling jobs on mainthread, the entity still exists, but when the job is actually executed, the entity is destroyed in another job. How should I handle that situation?

    Thanks in advanced :)
     
  2. Anthiese

    Anthiese

    Joined:
    Oct 13, 2013
    Posts:
    73
    Using an EntityCommandBuffer is a fancy way of saying "make these changes at some point on the main thread." One does not simply "destroy an entity in another job" (unless it's a
    .Run
    job). Any pending changes from EntityCommandBuffers aren't actually executed until code explicitly does so on the main thread (e.g. by an EntityCommandBufferSystem's OnUpdate executing), which then triggers a sync point where queued jobs are all run to completion to ensure it's safe to make structural changes. Once a job is scheduled, it's always going to run to completion before the next sync point happens, either by nature or by the sync point requiring all pending jobs to complete.

    In other words, if an entity is used by a job, the job always completes before the entity can be deleted, have component adds/removes, have shared component changes, etc.

    Relevant documentation links:
    Sync points
    Entity Command Buffers
     
    Last edited: May 20, 2022
    hippocoder likes this.
  3. libra34567

    libra34567

    Joined:
    Apr 5, 2014
    Posts:
    62
    @cyriaca Thank you for the reply and sorry for the late reply.

    I have a few confusions as I’m not sure if you have answered my question.

    Here assume a situation where there’s two Entities.ForEach.SchduleParallel in a system. The first one destroy all the entities that went thro it. The second system operates on the same sets of entities, but try to add components on them. What i worried is that the second foreach job won’t be able to find those entities when its trying to add component onto those as they are already destroyed in another job.

    I’m trying to discuss this assumed situation because I’m meeting issues in dots where It throws a entitiy could not be found exception in one of my Entities.ForEach.SchduleParallel job. (Even if there’s no operation on that entity at all in that job).

    What I assumed could have happened is: (Correct me if I’m wrong), that during the scheduling of the job. The entity in Entities.Foreach((Entity entity, in SomeComponent)) still exist, but when the job is actually being executed, it was destroyed by some other jobs. Otherwise I would realy have difficulty understanding why is the ForEach loop giving me a non-existing entity.

    Looking forward for your reply!, Thank you :)
     
  4. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    4,271
    Think of EntityCommandBuffer.DestroyEntity as an Add To Cart button. Your money doesn't disappear until checkout (EntityCommandBuffer.Playback).

    If you are seeing actual issues, you should share error messages, stack traces, and relevant code referenced by those stack traces (your code, specifically).
     
    libra34567, Anthiese and hippocoder like this.
  5. hippocoder

    hippocoder

    Digital Ape

    Joined:
    Apr 11, 2010
    Posts:
    29,723
    I would add it's a good time to look up what Sync Points are and why code goes wide and narrow a few times per frame, so you can see how an EntityCommandBuffer is utilised. You'd write to a parallel version of that, and all these changes can only take place at a narrow sync point later. So if you kicked off two jobs in the same system, they would still not have these structural changes or creation / destruction til the relevant sync point.

    Hope I got that right :p
     
    libra34567 and Anthiese like this.
  6. libra34567

    libra34567

    Joined:
    Apr 5, 2014
    Posts:
    62
    @hippocoder @DreamingImLatios

    Thank you guys for the reply, I had read thro those docs before I posted the questions. I think that I expressed myself badly. I'm sorry for that.

    What I'm asking is exactly regarding the way they handle the above mentioned situation in sync point (in my case EndSimulationEntityCommandBufferSystem). How does unity deal with the case where, during the playback of recorded commands, a modify entity command is playbacked after the a destroy entity command?.

    I'm asking the question due to the following entity does not exist exception I'm getting.
    Would be lovely if anybody could point me the mistake I made in my code :)

    Here is a screenshot of the code I'm having issue with:
    upload_2022-5-22_15-9-23.png

    And here is the error message:
    upload_2022-5-22_15-9-10.png

    I hope this better explained my quetion :), thx in advanced for helping out!

    p.s. I'm running on entities 0.17. Unity 2021.3.2f1
     
    Last edited: May 22, 2022
  7. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    4,271
    It doesn't. Don't do this.
     
    hippocoder and Anthiese like this.
  8. mk1987

    mk1987

    Joined:
    Mar 18, 2019
    Posts:
    53
    Slight deviation but still relevant.

    I had produced a decal system where when a laser impacts a ship, i instantiate a prefab entity where the ship is set as the parent entity, the decal would last about 2s and is essentially a glow animation that is destroyed once its finished. This works pretty well except in rare instances where im creating this decal entity in the same frame that the ship is destroyed and it comes up with an error message (i'll edit this post with the error message once ive reimplemented the system but im not 'working' today).

    I am 99% sure its to do with how the parents child and linkedentity buffers are updated as its not instantaneous and seems to happen in the next frame (since its the frame after the ship is destroyed the error crops up). Does anyone know where these buffers are updated? Failing that is there a system where i can just destroy this decal entity in the event that the parent no longer exists when its sorting these buffers out? Perhaps a entity command buffer .assignParentEntity(entity) and .assignParentEntitySafe(entity, bool) where the latter checks parentage exists? Potentially there is also a scenario where the decal might be killed in the ship destroy and decal destroy systems within the same frame, in this scenario one of the destroyentity command buffers will throw an error. Is there some mechanism to destroy an entity that will not cause an error if its already been destroyed?
     
  9. xVergilx

    xVergilx

    Joined:
    Dec 22, 2014
    Posts:
    3,296
    This kind of issues are solved by introducing a separate ECBSystem. Insert it before ECBSystem which destroys entity.
    Use this new ECBSystem to perform all kind of modifications to the entity. And in next ECBSystem that is executed - destroy entities.

    This will ensure entity survives during modification playback.

    It looks like this:
    - NextFrameECBSystem (performs modifications done to entities queued up to the next frame, no creation / destruction);
    - BeginFrameECBSystem (performs destruction & creation of entities)
    - ... default systems ...
    - SimulationGroup
    - ...
    - EndSimECBSystem (modifications that should be propagated to the MonoBehaviour side)
    - "MonobehaviourSide"SystemGroup (to sync up correctly results, MonoBehaviour update is performed here)

    Alternatively, use EndSimECBSystem for modifications, and BeginSimECBSystem for destroying entities.
    Should do the same, but with less readability and execution order flexability.
     
    hippocoder and Anthiese like this.
  10. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    4,271
    I've ran into this too. Here is my solution: https://github.com/Dreaming381/lsss.../DestroyUninitializedOrphanedEffectsSystem.cs
    This system runs in my extended sync point period after command buffer playback but before the TransformSystemGroup.
     
  11. Razmot

    Razmot

    Joined:
    Apr 27, 2013
    Posts:
    346
    to solve these kind of problems :

    I tag my entities with a ToDestroy component

    I have a DestroySystem :
    - it runs asap in the frame [UpdateInGroup(typeof(InitializationSystemGroup))]
    - it collects the entites to destroy in a list and then destroys them on main thread with EntityManager.DestroyEntity(_entitiesToDestroy);
     
  12. xVergilx

    xVergilx

    Joined:
    Dec 22, 2014
    Posts:
    3,296
    This is also valid strategy. Although, structural changes to perform structural changes is slower than performing them "directly" via ECB.

    Note that EntityManager.DestroyEntity supports EntityQuery as a parameter.

    So you could just pass query "WithAll" <ToDestroy> component.
    This is faster than manually iterating, fetching & storing them in a list / NativeArray.
    Also, uses less code.
     
  13. Razmot

    Razmot

    Joined:
    Apr 27, 2013
    Posts:
    346
    yes a query is possibly faster, in my case I have a some main thread managed stuff to do in the foreach loop before actually destroying the entities (eg Releasing a custom renderer from a pool)