Search Unity

Question Confusion with EntityCommandBuffer usage

Discussion in 'Entity Component System' started by seona13, Dec 19, 2022.

  1. seona13

    seona13

    Joined:
    Nov 28, 2019
    Posts:
    24
    I have a spawning ISystem that has the following code in the Burst-compiled OnUpdate method:

    Code (CSharp):
    1. var pondEntity = SystemAPI.GetSingletonEntity<PondProperties>();
    2. var pond = SystemAPI.GetAspectRW<PondAspect>(pondEntity);
    3.  
    4. var ecb = new EntityCommandBuffer(Allocator.Temp);
    5.  
    6. for (int i = 0; i < pond.NumberMotesToSpawn; i++)
    7. {
    8.     var newMote = ecb.Instantiate(pond.MotePrefab);
    9.     var newMoteTransform = pond.GetRandomMoteTransform();
    10.     ecb.SetComponent(newMote, new LocalToWorldTransform { Value = newMoteTransform });
    11. }
    12.  
    13. ecb.Playback(state.EntityManager);
    14.  
    This is working fine - all my motes spawn with a random facing, and the movement system moves them off in those directions.

    However I also want to set a value on each mote's aspect so that I can have the movement system change its direction after a random number of seconds. (Each mote needs it's own random number so they don't all change at once like a flock of birds!) I tried getting the aspect for a given mote in the for-loop as such:

    Code (CSharp):
    1. var pondEntity = SystemAPI.GetSingletonEntity<PondProperties>();
    2. var pond = SystemAPI.GetAspectRW<PondAspect>(pondEntity);
    3.  
    4. var ecb = new EntityCommandBuffer(Allocator.Temp);
    5.  
    6. for (int i = 0; i < pond.NumberMotesToSpawn; i++)
    7. {
    8.     var newMote = ecb.Instantiate(pond.MotePrefab);
    9.     var newMoteTransform = pond.GetRandomMoteTransform();
    10.     ecb.SetComponent(newMote, new LocalToWorldTransform { Value = newMoteTransform });
    11.  
    12.     var aspect = SystemAPI.GetAspectRW<MoteMoveAspect>(newMote);
    13.     aspect.DirectionTimer = pond.GetRandomFloat;
    14. }
    15.  
    16. ecb.Playback(state.EntityManager);
    17.  
    Now the motes don't spawn at all and I get the following ultra-helpful error message in Unity:
    ArgumentException: System.ArgumentException: All entities created using EntityCommandBuffer.CreateEntity must be realized via playback(). One of the entities is still deferred (Index: {0}).
    This Exception was thrown from a function compiled with Burst, which has limited exception support.


    I have no idea what this means, web searching is not giving me anything useful, and I don't understand why this new code is even bothering the EntityCommandBuffer.

    So far I have tried:
    1/ Moving the two new lines above the one before them - maybe the ecb call had to be the last thing that happened? Nope, apparently not.
    2/ Commenting out the second of the two new lines to try and narrow down what is causing the problem. Even just getting the aspect without doing anything with it causes the error and the non-spawning.

    I can't think of anything else useful I could try here, so I'm hoping someone will have an idea of why this is happening and how to fix it.
     
  2. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    4,266
    Prior to playback, you can't read any component on an entity created by an ECB. When you call GetAspect, it tries to read the components, fails, and spits out the error message you see. Unfortunately, there's not a good way to set an aspect for an entity created in an ECB. You will need to create a custom method to populate the individual components based on what aspect parameter you want to set.
     
    leoncorrl likes this.
  3. seona13

    seona13

    Joined:
    Nov 28, 2019
    Posts:
    24
    Ohhhh, okay, that makes more sense now. Thanks.

    Is it possible to set the initial value in the Authoring/Baker somehow? If not where else would you hang that custom method you're suggesting?
     
  4. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    4,266
    I would put the method in a static class and make it take a ref to an ECB or an IBaker or EntityManager or whatever you need.
     
  5. seona13

    seona13

    Joined:
    Nov 28, 2019
    Posts:
    24
    Sorry, I'm still feeling my way through the whole ECS thing, and I'm not entirely sure I follow you.

    Do I keep the current spawn for-loop as it is, and then call a static method to modify each entity's aspect after I've done the ecb.Playback? That feel horribly inefficient, so maybe I've misunderstood how it all hangs together.
     
  6. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    4,266
    Alright. Forget that aspects exist temporarily, because you can't use them when setting up new entities in a job. Figure out the underlying components the aspect is working with and write code to set up those components correctly using ECB.
     
  7. seona13

    seona13

    Joined:
    Nov 28, 2019
    Posts:
    24
    Oh, so I can get a component on an entity after I create it via the ECB, but I can't get an aspect. Am I understanding that correctly?
     
  8. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    4,266
    You can't get anything until after ECB playback. But at least with a component, you can get the component value from the original prefab you instantiated from. So you can get a copy of that component, modify it, and then set it on the new entity.