Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Newbie question, how to write to DynamicBufferElement

Discussion in 'Entity Component System' started by abob101, Feb 17, 2020.

  1. abob101

    abob101

    Joined:
    Oct 28, 2017
    Posts:
    26
    Hi all,

    I'm very new to ECS, and struggling to figure how to work with DynamicBuffers (amongst other things). It's easy enough to create a DynmicBuffer, add elements and iterate through them accessing the data. However how can I modify the value of an element?

    For example, I have a series of "Stats" stored against a Character/Monster/etc. So an Archetype with a DynamicBuffer something like this:

    public struct StatBufferElement : IBufferElementData
    {
    public int StatType;
    public float Value;
    }

    monsterArchetype = entityManager.CreateArchetype(typeof(Monster), typeof(StatBufferElement));


    With this I can get the Stat Value something like this:

    private float GetStatValue(Entity fromEntity, int statType)
    {
    DynamicBuffer<StatBufferElement> stats = entityManager.GetBuffer<StatBufferElement>(fromEntity);
    for (int i = 0; i < stats.Length; i++)
    {
    if (stats[i].StatType == statType)
    {
    return stats[i].Value;
    }
    }
    return 0;
    }

    But what if I want to have a function to get the actual StatBufferElement itself, so that I can modify the Value? Can I somehow get a reference to the StatBufferElement so I can modify it?

    I feel like I want to be able to do something like this:

    private StatBufferElement GetStat(Entity fromEntity, int statType)
    {
    DynamicBuffer<StatBufferElement> stats = entityManager.GetBuffer<StatBufferElement>(fromEntity);
    for (int i = 0; i < stats.Length; i++)
    {
    if (stats[i].StatType == statType)
    {
    return stats[i];
    }
    }
    return some sort of null struct??;
    }

    StatBufferElement stat = GetStat(aMonsterEntity, healthStatType);
    stat.Value = 50;


    I think my problem is I've not really worked with structs much before. Do I need to return a ref type? Can I do that with a DynamicBufferElement?

    I may be way off track here.

    Thanks.
     
  2. Vacummus

    Vacummus

    Joined:
    Dec 18, 2013
    Posts:
    191
    @abob101 In C#, structs are value types, and value types can not be null. So you can't return null there. What I would recommend is returning the index of the buffer element instead of the buffer element itself. You can have a generic extension function for the finding the index, like this one:

    Code (CSharp):
    1. static public int FindIndex<T>(this DynamicBuffer<T> buffer, Func<T, bool> match) where T: struct, IBufferElementData
    2. {
    3.     for (var i = 0; i < buffer.Length; i++)
    4.     {
    5.         if (match(buffer[i]))
    6.         {
    7.             return i;
    8.         }
    9.     }
    10.  
    11.     return -1;
    12. }
    And then with your stats buffer (or any other buffer), you can just call that function to get the index:

    Code (CSharp):
    1. var index = stats.FindIndex((x) => x.StatType == healthStatType);
    If the index is -1 then that means the element does not exist. And finally, to update the element in the buffer you have to reset it like so:

    Code (CSharp):
    1. var index = stats.FindIndex((x) => x.StatType == healthStatType);
    2. StatBufferElement stat = stats[index];
    3. stat.Value = 50;
    4. stats[index] = stat;
    ^ This is because structs are value types and not reference types (like objects). So making changes to stat.Value does not update value in the array, so you have to reset it.
     
  3. abob101

    abob101

    Joined:
    Oct 28, 2017
    Posts:
    26
    Thanks @Vacummus. I've been looking at this further, I had considered storing indexs but I generally don't do that because the # elements in the list (DynamicBuffer in this case) can change.

    The design challenge here is that really I want to store a reference to a Stat (StatBufferElement).

    When I do this in OOP, I would have the Stat as an object (class).

    Code (CSharp):
    1. public class Stat
    2. {
    3.     public int StatType;
    4.     public float Value;
    5. }
    Code (CSharp):
    1. public class Monster
    2. {
    3.      public Dictionary<StatType, Stat> Stats;
    4.  
    5.      public GetStat(int statType)
    6.      {
    7.          return Stats[statType];
    8.      }
    9. }
    Then a spell/item etc can point straight to the Stat that it applies to, eg....

    Code (CSharp):
    1. public class Effect
    2. {
    3.      public Stat AffectedStat;
    4.      public float Amount;
    5. }
    This is oversimplified, but the point being with OOP I can use references between a Stat, Effect etc to track how they are related. I'm struggling to translate this to ECS... I can't stored a reference to an element in a DynamicBuffer. Index is an option but if the elements are added/removed from the buffer the index will be incorrect.

    I guess each Stat/Effect could be an Entity and the DynamicBuffer could be a list of "references" to Entities which are the Stat, Effect etc but that seems a bit nuts having every Stat being it's own Entity.

    I feel like I'm not thinking about it in a proper "data oriented design" way, rather trying to translate my OOP approach and making a mess of things.
     
  4. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    4,220
    It's a pretty easy trap. The issue is you are focused on relationships between things that aren't data. Effect doesn't need a Stat. An Effect is just data. It can't do anything with it. What really matters is the algorithms that need the data. Who needs it? How do they need it? Which parts do they need? How often do they need it?

    Once you figure that out, it becomes a lot easier to analyze the numerous options at your disposal. You could use a NativeHashMap with the Entity + Type as key. Or you could bank on looking up stats in the dynamic buffer without direct knowledge of its location to be seldom enough where it isn't a performance problem. You could optimize this further by splitting the Stat and the StatType into two dynamic buffers so that you can use SIMD to find the correct type much faster. Or if you require the Stats to be sorted in the dynamic buffer, you can use a bitmask with pop count to lookup the stat even faster. Or maybe you go a different direction and make your stats buffer an ISystemStateBufferElement buffer which holds entities with the stats (and tags for the stat types).
     
  5. abob101

    abob101

    Joined:
    Oct 28, 2017
    Posts:
    26
    My (very limited) understanding is that I can't stored a NativeHashMap in a component though right? I've seen mention on using NativeHashMap before but I don't get how it can be used with a JobSystem.

    This means each Stat becomes it's own Entity right? I think this is what makes sense to me. With the example above, if an Effect (such as a spell) is applied to a Monster the Stat will need to be updated. With this approach (each Stat is it's own Entity) I could keep track of which stats needs to recalculated by adding en empty component tag "Dirty". Then a Job could be used to recalculate all the "Dirty" Stats. ??? I might go try that approach and see how it goes.

    ECS makes me feel like such a newb this is embarrassing :(
     
  6. RoughSpaghetti3211

    RoughSpaghetti3211

    Joined:
    Aug 11, 2015
    Posts:
    1,697
    Stick with it it can be challenging to train your mind to think Data.
     
  7. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    4,220
    You can store it in a class component.

    It may not be the most performant, but it may be the most intuitive to you which is why I suggested it as an option. It'll still be more performant than MonoBehaviours though.