Search Unity

Rapidly adding and removing components to an entity with a huge amount of components

Discussion in 'Entity Component System' started by Abbrew, Sep 26, 2019.

  1. Abbrew

    Abbrew

    Joined:
    Jan 1, 2018
    Posts:
    417
    IIRC this is a performance issue. Said rapidly added and removed components tend to be "message" or "status" components. For example, a soldier entity with a ton of components can acquire a GetReadyToShoot component that only lasts 1 frame. Or maybe a soldier holds on to a Suppressed component for only a few dozen frames. In any case these constantly changing archetypes would waste chunk space and induce latency. Would the solution be to have a sort of "status entity" where the parent entity is comprised of rarely changed components, and the status entity is comprised of fewer but transient components? A StatusEntity component for the parent entity and AffectedEntity component for the status entity would be used to link the two.
     
    NotaNaN and PublicEnumE like this.
  2. PublicEnumE

    PublicEnumE

    Joined:
    Feb 3, 2019
    Posts:
    729
    You’re right, that can cause poor performance. Best to avoid archetype changes entirely, when possible.

    This is something I struggled to know how best to approach. But after a few weeks of testing different approaches, I decided to commit to the following rules:

    1. Entities never change archetypes. Once I create an entity, it will never change its archetype until it’s destroyed. That means no adding or removing components.

    P.S. This also means to always create entities from prefabs, instantiation, or by using EntityArchetype variables. Creating an entity from scratch and then adding components to it one by one causes a lot of archetype thrash.

    2. Use separate, tiny entities for events or messages. These generally have one component, and are destroyed within a single frame (usually later in that same update loop).

    3. Avoid #2 as much as possible. Creating an event entity still requires you to use an EntityCommandBuffer, which requires a sync point. In many cases when you might want to create a message or entity component, there end up being other ways to do what you’d like that avoid creating new entities.

    - - -

    A tip that’s helped me reach #1 on that list:

    I tend to define behaviors (like “GetReadyToShoot”) as IBufferElementData-s, and store them in DynamicBuffers on my entities. That way, I can write an EntityQuery with a change filter for that Buffer type, and early out if the DB is empty. If it’s not, then I process all the behaviors inside.

    - - -

    Anyhow, at the end of the day, avoiding constant thrash from archetype changes and chunk allocation is more performant. and there are usually ways to achieve it. :)
     
    PhilSA and Abbrew like this.
  3. tertle

    tertle

    Joined:
    Jan 25, 2011
    Posts:
    3,759
    Great advice, I have a couple of things to add.

    #1 I try to strive for this, but do allow archetypes to change if it's only at max every few seconds.

    #2/3 I wrote a somewhat popular event system to do this without EntityCommandBuffer and instead using batch operations while managing the life spam automatically.

    https://forum.unity.com/threads/batch-entitycommandbuffer.593569/#post-4929749

    Taking my old benchmark it's about 10x faster.



    That's 100k events/frame on a really old cpu.

    This solution isn't for everyone, but does go to show the benefits of avoiding ECB and moving to batch operations instead if possible.
     
    NotaNaN, PublicEnumE and Abbrew like this.
  4. Abbrew

    Abbrew

    Joined:
    Jan 1, 2018
    Posts:
    417
    Your idea of representing messages as IBufferElementData is interesting. I'll definitely use messages this way. Thanks!
     
    PublicEnumE likes this.
  5. Abbrew

    Abbrew

    Joined:
    Jan 1, 2018
    Posts:
    417
    I remember this from January! I'd consider using your Event system. Fortunately I've only been coding constantly active systems so far and can now evaluate different ways of triggering temporary systems
     
  6. RoughSpaghetti3211

    RoughSpaghetti3211

    Joined:
    Aug 11, 2015
    Posts:
    1,705
    Interesting, How would you handle CreateAdditionalEntity() inside a GameObjectConversionSystem. Im not sure of a way to get around creating an entity from scratch and then adding components to it one by one. Or is ok to take the one off hit ?
     
    NotaNaN and PublicEnumE like this.
  7. PublicEnumE

    PublicEnumE

    Joined:
    Feb 3, 2019
    Posts:
    729
    Good catch. I have made exceptions before for one-off entities created during game start up. Certainly for the Game Object Conversion System, but also for some cases where I’ve created highly custom, persistent entities in a System’s Create() method.

    but in truth, that’s me being lazy. My first case could usually be avoided using SubScenes to avoid runtime GameObject conversion. And to avoid my second case, one could use EntityManager.CreateArchetype(), and then use the resulting archetype to spawn the custom entity.
     
    Last edited: Sep 26, 2019
    NotaNaN likes this.
  8. RoughSpaghetti3211

    RoughSpaghetti3211

    Joined:
    Aug 11, 2015
    Posts:
    1,705
    Got it thanks for the reply
     
    NotaNaN and PublicEnumE like this.
  9. Singtaa

    Singtaa

    Joined:
    Dec 14, 2010
    Posts:
    492
    For the OP, you can also use Tags to minimize the cost of chunk data movement:

    Code (CSharp):
    1. public struct IsOnCooldown : IComponentData { }
    2.  
    3. public struct CooldownInfo : IComponentData {
    4.     public float timeRemaining;
    5. }
    So instead of adding/removing
    CooldownInfo
    , you use an extra tag
    IsOnCooldown
    , and only add/remove/query that.
     
    MintTree117 and PublicEnumE like this.
  10. Abbrew

    Abbrew

    Joined:
    Jan 1, 2018
    Posts:
    417
    @PublicEnumE @tertle I've settled on having a IComponentData containing only a bool isActive, and having relevant entities fetched through an EntityQuery with a SetFilterChanged on that component, processing only when isActive is true. However, this still fetches entities with isActive set to false. This is easily avoidable through an early exit, but would a cleaner/more flexible way be to have each "message" component be an ISharedComponentData with a bool isActive? In this case, only entities with isActive set to true would be processed.
     
    PublicEnumE likes this.
  11. PublicEnumE

    PublicEnumE

    Joined:
    Feb 3, 2019
    Posts:
    729
    Hear you loud and clear. It’s difficult to speak to your specific case without being more familiar, but:

    One thing about ECS: Because of the way CPUs work, what ends up being most performant in the end isn’t always immediately intuitive. We’re traditionally trained to look for a mathematically most performant solution which seems to eliminate all possible waste (like ”only iterate over the specific components which have changed.”).

    But in ECS this isn’t always the most performant thing to do. Since all component data is stored in chunks, and since an entire chunk is loaded into the cache to edit any of its component data, and since a CPU reading a bool from its own cache is blazingly fast: iterating over several false bools in a chunk to find the one bool that’s true might end up being the fastest of all possible options we have available (and damned fast, at that).

    Since the ins and out of ECS performance are still being worked out, a lot of this community has taken this approach: When starting out, try a few different approaches, and crank them up to a large scale to do performance tests. Don’t assume that just because a solution contains *some* waste that it’s not going to end up being the fastest *available* solution. Test and see. And learn what’s out there about the inner workings of ECS. The new rules about what’s likely to be most performant will become easier to predict.
     
  12. Abbrew

    Abbrew

    Joined:
    Jan 1, 2018
    Posts:
    417
    Thanks! I forgot that premature optimization/assumptions are the root of all evil. I'll try running some tests and maybe share what I find
     
    PublicEnumE likes this.
  13. davenirline

    davenirline

    Joined:
    Jul 7, 2010
    Posts:
    982
    This is my struggle as well. Adding components so that they can be filtered in some systems is the most idiomatic way in ECS, but because of the way the data is handled, this may not be the best course.

    What is Unity devs advice on this?
     
  14. Radu392

    Radu392

    Joined:
    Jan 6, 2016
    Posts:
    210
    I’ve settled on having a single component with a single bool named isDisabled and just work with that, a bit like @Abbrew s solution.

    I tried different approaches myself as I like tinkering with stuff, and that is by far the best solution in terms of both performance and ease of coding together. However, it does go against ecs style so it would indeed be nice to have a unity dev let us know if this should be the ‘correct’ way of doing things or not.
     
    PublicEnumE likes this.