Search Unity

Collections in Components

Discussion in 'Entity Component System' started by orionburcham, Dec 19, 2018.

  1. orionburcham

    orionburcham

    Joined:
    Jan 31, 2010
    Posts:
    488
    I imagine this ground has been well covered in the past. But times change, and I'm having trouble telling which ECS info is current and not. So:

    What's the best way to approach this problem?:

    - - -

    I'd like to create a Component which holds a collection of elements. For example: "SpawnPointLocationsComponent", which would hold a collection of 2D vectors.

    - - -

    I'm under the impression that Components can't contain collections like this. Is that still true? And if so, how would you approach this problem?

    Thanks, and Cheers!
     
  2. tertle

    tertle

    Joined:
    Jan 25, 2011
    Posts:
    3,761
  3. orionburcham

    orionburcham

    Joined:
    Jan 31, 2010
    Posts:
    488
    So rather than something like:

    Code (CSharp):
    1. public struct MyComponent : IComponentData
    2. {
    3.     public int value;
    4.     public MyBlitableType01[] collection01;
    5.     public MyBlitableType02[] collection02;
    6. }
    ...you would have:

    Code (CSharp):
    1. public struct MyComponent : IComponentData
    2. {
    3.     public int value;
    4. }
    5.  
    6. [InternalBufferCapacity(10)]
    7. public struct MyBlitableType01Element : IBufferElementData
    8. {
    9.    public MyBlitableType01 value;
    10. }
    11.  
    12. [InternalBufferCapacity(10)]
    13. public struct MyBlitableType02Element : IBufferElementData
    14. {
    15.    public MyBlitableType02 value;
    16. }
    ?
     
  4. tertle

    tertle

    Joined:
    Jan 25, 2011
    Posts:
    3,761
  5. orionburcham

    orionburcham

    Joined:
    Jan 31, 2010
    Posts:
    488
    Is that how you would do it?
     
  6. tertle

    tertle

    Joined:
    Jan 25, 2011
    Posts:
    3,761
    Depends on your use case.

    I might consider making every spawn point its own entity and use a ComponentGroup as a pseudo collection.

    But for dynamic buffers and when you want a collection actually attached to an entity, that's exactly how to do it.
     
    orionburcham likes this.
  7. orionburcham

    orionburcham

    Joined:
    Jan 31, 2010
    Posts:
    488
    Thank you for the info!

    Hmm...I guess I'm probably out of luck if I wanted the equivalent of a jagged array (MyType[][]), yes?
     
  8. orionburcham

    orionburcham

    Joined:
    Jan 31, 2010
    Posts:
    488
    I suppose I could turn this:

    Code (CSharp):
    1. public struct MyComponent
    2. {
    3.     public MyType[][] myTypes;
    4. }
    Into this:

    Code (CSharp):
    1. public struct MyComponent : IComponentData
    2. {
    3.     public int subType01StartingIndex;
    4.     public int length = 10;
    5. }
    6.  
    7. [InternalBufferCapacity(10)]
    8. public struct MySubType01Element : IBufferElementData
    9. {
    10.    public int subType02StartingIndex;
    11.     public int length = 10;
    12. }
    13.  
    14. [InternalBufferCapacity(100)]
    15. public struct MySubType02Element : IBufferElementData
    16. {
    17.     public int value;
    18. }
    ...It's not pretty. But I'm not sure why it wouldn't be supported.
     
  9. recursive

    recursive

    Joined:
    Jul 12, 2012
    Posts:
    669
    Another alternative, assuming the jagged array elements all have the same type, is only two DynamicBuffer types, one of which can be reused.

    An IBufferElementData that contains an entity reference, and an IBufferElementData that contains your MyType element.

    The first DynamicBuffer has the list of entities that form the sub-array.

    The secondary DynamicBuffer(s) has the list of MyType buffer elements.

    This scales nicely if you have a single array of MyType buffer elements since you don't need the entity redirect lookups.

    You can then use 2 BufferFromEntity structs to do lookup:


    Code (CSharp):
    1. [InternalBufferCapacity(16)]
    2. struct EntityReference : IBufferElementData
    3. {
    4.     public Entity Value;
    5. }
    6.  
    7. [InternalBufferCapacity(16)]
    8. struct MyTypeElement : IBufferElementData
    9. {
    10.     public MyType Value;
    11. }
    12.  
    13. struct MyJaggedArrayJob : IJob
    14. {
    15.     public Entity MainArray;
    16.     [ReadOnly] public BufferFromEntity<EntityReference> MainArrayLookup;
    17.     public BufferFromEntity<MyTypeElement> SubArrayLookup;
    18.  
    19.     public void Execute()
    20.     {
    21.         NativeArray<Entity> mainArray = MainArrayLookup[MainArray].ReInterpret<Entity>().ToNativeArray();
    22.  
    23.         int mainArrayLen = mainArray.Length;
    24.         for(i = 0; i < mainArrayLen; ++i)
    25.         {
    26.             Entity subArrayEntity = mainArray[i];
    27.             NativeArray<int> subArray = SubArrayValues[subArrayEntity].ReInterpret<MyType>().ToNativeArray();
    28.             int subArrayLen = subArray.Length;
    29.             for(int j = 0; j < subArrayLen; ++j)
    30.             {
    31.                 MyType myType = subArray[j];
    32.                 // do something here...
    33.             }
    34.         }
    35.     }
    36. }
     
    Djayp likes this.
  10. Djayp

    Djayp

    Joined:
    Feb 16, 2015
    Posts:
    114
    Thank you so much @recursive !

    I first used a ConcurrentDictionary stored in the system but I thought creating a custom NativeContainer would be a better way. Now I'm confused and need to compare performances between NativeContainer and DynamicBuffer, but thank you anyway =)
     
    recursive likes this.
  11. recursive

    recursive

    Joined:
    Jul 12, 2012
    Posts:
    669
    Ah, you may want to look at NativeMultiHashmap<T0,T1> as well. It has a concurrent write support struct and you could have an index as the key. You can also remove individual values from a bucket as well. Only issue would be iteration is more complicated and fragmented than the other setup I described as you must iterate manually and stable order isnt guaranteed with concurrent writes.