Search Unity

Approaches to storing Entity references created from EntityCommandBuffer.CreateEntity/Instantiate?

Discussion in 'Entity Component System' started by sient, Apr 27, 2019.

  1. sient

    sient

    Joined:
    Aug 9, 2013
    Posts:
    602
    Has anyone come up with a nice solution to storing Entity instances from EntityCommandBuffer.CreateEntity/Instantiate and then referencing that specific Entity instance N frames in the future?

    Code (csharp):
    1. struct Effect : IComponentData {
    2.   public int FramesToShow;
    3.   public Entity Visualization;
    4. };
    5.  
    6. struct Job : IJobForEach<Effect> {
    7.   public EntityCommandBuffer Commands;
    8.   void Update(ref Effect e) {
    9.     if (e.FramesToShow == 0)
    10.       e.Visualization = Commands.CreateEntity();
    11.     if (e.FramesToShow > 10)
    12.       Commands.Destroy(e.Visualization); // This will throw an exception.
    13.     e.FramesToShow++;
    14.   }
    15. }
    The only general-purpose solution I've been able to think of so far is to increment an atomic counter when instantiating the entity, and then build around the generated ID so a system can look up the reference using that generated ID during the next N frames.

    Code (csharp):
    1. struct DeferredEntity : IComponentData {
    2.   public int Id; // Generated via an atomic counter.
    3. }
    4.  
    5. struct GatherIds : IJobForEachWithEntity<DeferredEntity> {
    6.   public NativeHashMap<int, Entity> Output;
    7.   void Update(Entity entity, int index, [ReadOnly] ref DeferredEntity deferred) {
    8.     Output[deferred.Id] = entity;
    9.   }
    10. }
    11.  
    12. // Use GatherIds.Output in dependent jobs to resolve an id to an entity.
    If anyone has solved this differently I'd be very keen on the approach - thanks!
     
    June1111 likes this.
  2. BrendonSmuts

    BrendonSmuts

    Joined:
    Jun 12, 2017
    Posts:
    86
    I think we've all ran into the issue of storing entity IDs that were coming out of a command buffer at some point. I came up with a whole bunch of elaborate solutions that would, similarly to yours, create specialized entities that would later resolve the entity ID for the component concerned. Only after days and days of work did I realize the command buffer presented a built in solution all along.

    As you may or may not already know, the entity you get out of the command buffer is pretty much useless outside the context of that specific buffer's commands, essentially being just a special negatively indexed counter. The command buffer later uses this to detect whether additional add/set/remove commands should apply to an existing entity or one that was just now created through the current buffer and requires a remap. Additionally the buffer is also aware of component data types that contain entity values and will remap any of these fields when processing their set/add commands.

    The answer to our problem then is instead of directly storing the nonsense entity counter that comes out of the buffer, let the command buffer set your storing component using the counter entity value and perform the remap on your behalf.

    In this case your instantiation job should look something like this:

    Code (CSharp):
    1. struct Job : IJobForEachWithEntity<Effect>
    2. {
    3.     public EntityCommandBuffer Commands;
    4.  
    5.     public void Execute(Entity entity, int index, ref Effect effect)
    6.     {
    7.         effect.FramesToShow++;
    8.  
    9.         if (effect.FramesToShow == 0)
    10.         {
    11.             effect.Visualization = Commands.CreateEntity();
    12.             Commands.SetComponent(entity, effect);
    13.         }
    14.         else if (effect.FramesToShow > 10)
    15.         {
    16.             Commands.DestroyEntity(effect.Visualization);
    17.         }
    18.     }
    19. }
    The side effect of this is that because there is a buffered command to explicitly set your Effect component to a fixed value at a point when the command buffer resolves, any additional write values to other fields, such as FramesToShow, will be overwritten by the presently passed value. If you had a scenario where this was an issue you would need to split the Effect component data into additional components that can have their data written to separately.

    Note that there are a couple of other solutions to resolve entity ID that may scale better but are significantly more complicated.
     
    Last edited: Apr 27, 2019
  3. davenirline

    davenirline

    Joined:
    Jul 7, 2010
    Posts:
    982
    Does this work with storing entities on dynamic buffers?
     
  4. julian-moschuering

    julian-moschuering

    Joined:
    Apr 15, 2014
    Posts:
    529
    Yes, all modifications done by the ECB will have the final Entity including SetBuffer (since preview ~29).

    You could create event objects using the ECB which store the two entities and get resolved by another system to solve the problem of ECBs not supporting adding to a dynamic buffer.
     
    Last edited: Apr 29, 2019
  5. sient

    sient

    Joined:
    Aug 9, 2013
    Posts:
    602
    Ooh, thanks! That's a really nice feature in EntityCommandBuffer. I was able to mostly get what I wanted by working with that.
     
  6. BrendonSmuts

    BrendonSmuts

    Joined:
    Jun 12, 2017
    Posts:
    86
    Yes you can use a similar approach to regular IComponentData.

    Trying to keep the example as similar to the original as possible, even though it doesn't make much sense, using a buffer might look something like this:

    Code (CSharp):
    1. struct Effect : IComponentData
    2. {
    3.     public int FramesToShow;
    4. };
    5.  
    6. struct EffectElement : IBufferElementData
    7. {
    8.     public Entity Visualization;
    9. }
    10.  
    11. struct Job : IJobForEachWithEntity<Effect>
    12. {
    13.     public EntityCommandBuffer Commands;
    14.     public BufferFromEntity<EffectElement> VisualizationBuffer;
    15.  
    16.     public void Execute(Entity entity, int index, ref Effect effect)
    17.     {
    18.         var currentBuffer = VisualizationBuffer[entity];
    19.  
    20.         effect.FramesToShow++;
    21.  
    22.         if (effect.FramesToShow == 0)
    23.         {
    24.             var e = Commands.CreateEntity();
    25.             var setBuffer = Commands.SetBuffer<EffectElement>(entity);
    26.             setBuffer.CopyFrom(currentBuffer.AsNativeArray());
    27.             setBuffer.Add(new EffectElement { Visualization = e});
    28.         }
    29.         else if (effect.FramesToShow > 10)
    30.         {
    31.             for (int i = 0; i < currentBuffer.Length; i++)
    32.             {
    33.                 Commands.DestroyEntity(currentBuffer[i].Visualization);
    34.             }
    35.  
    36.             currentBuffer.Clear();
    37.         }
    38.     }
    39. }
    Things to note here is that the SetBuffer method on the command buffer creates a brand new DynamicBuffer that will overwrite any existing buffer. If there are any existing values in the original buffer we might be interested in keeping we need to copy them over, as shown above.
     
    rsodre likes this.
  7. PublicEnumE

    PublicEnumE

    Joined:
    Feb 3, 2019
    Posts:
    729
    Is there any way to assign the temp entity reference to an existing DynamicBuffer, without the SetBuffer() and CopyFrom() calls? Those could end up being costly.

    If you assign a temporary entity value to a struct in an existing DynamicBuffer, will the ECB be able to remap it? Or will this only work if you add it to a new DB which you’ve just created using the same ECB (like in the example)?

    Also: any way to force remapping in native collection types?
     
    Last edited: Apr 29, 2020
  8. tertle

    tertle

    Joined:
    Jan 25, 2011
    Posts:
    3,759
    No you need to use set buffer, really not that expensive. And no to native containers.

    Only the ECB knows which fake entity maps to what, so you have to go through it.