Search Unity

Accumulating values in child entities

Discussion in 'Entity Component System' started by JooleanLogic, Feb 1, 2019.

  1. JooleanLogic

    JooleanLogic

    Joined:
    Mar 1, 2018
    Posts:
    447
    What is the best/simplest way to accumulate values over child entities and write them back to the parent in a job?
    Code (CSharp):
    1. ParentValue : IComponentData { int value; }    // On parent Entity
    2. ChildValue : IComponentData { int value; }    // On child Entity
    3.  
    4. // Can link child to parent by either a non grouping ParentRef or grouped by ParentRefShared.
    5. ParentRef : IComponentData { Entity parent; }
    6. ParentRefShared : ISharedComponentData { Entity parent; }
    I want to sum ChildValue.value into ParentValue.value

    This is related to my previous post here but that's a very confusing post so I thought I'd try simplify this problem that I keep getting stuck on.
     
  2. BrendonSmuts

    BrendonSmuts

    Joined:
    Jun 12, 2017
    Posts:
    86
    This is similar to an issue I've been trying to solve at the moment. My approach is to have an IJobChunk (or IJobParallelFor depending on your data) to query the results of the children on a per parent basis which. This means that your references should be pointing from your parent to your children.

    Your code/job might look something like this:

    Code (CSharp):
    1. public struct ParentValue : IComponentData
    2. {
    3.     public int Value;
    4. }
    5.  
    6. public struct ChildValue : IComponentData
    7. {
    8.     public int Value;
    9. }
    10.  
    11. //Note that when you have more child references than buffer capacity the buffer will move outside of the chunk cache line.
    12. //Plan your buffer size accordingly.
    13. [InternalBufferCapacity(4)]
    14. public struct ChildReferenceElement : IBufferElementData
    15. {
    16.     public Entity Value;
    17.     public static implicit operator Entity(ChildReferenceElement e) { return e.Value; }
    18.     public static implicit operator ChildReferenceElement(Entity e) { return new ChildReferenceElement { Value = e }; }
    19. }
    20.  
    21. public struct AccumulateChildResults : IJobChunk
    22. {
    23.     [ReadOnly] public ArchetypeChunkBufferType<ChildReferenceElement> ChildReferenceBufferType;
    24.     [ReadOnly] public ComponentDataFromEntity<ChildValue> ChildValue;
    25.     public ArchetypeChunkComponentType<ParentValue> ParentValueType;
    26.  
    27.  
    28.     public void Execute(ArchetypeChunk chunk, int chunkIndex, int firstEntityIndex)
    29.     {
    30.         var chunkParentValues = chunk.GetNativeArray(ParentValueType);
    31.         var chunkChildReferenceBuffers = chunk.GetBufferAccessor(ChildReferenceBufferType);
    32.  
    33.         for (int i = 0; i < chunk.Count; i++)
    34.         {
    35.             NativeArray<ChildReferenceElement> childReferences = chunkChildReferenceBuffers[i].AsNativeArray();
    36.          
    37.             int total = 0;
    38.  
    39.             for (int j = 0; j < childReferences.Length; j++)
    40.             {
    41.                 ChildReferenceElement childReference = childReferences[j];
    42.  
    43.                 total += ChildValue[childReference.Value].Value;
    44.             }
    45.          
    46.             chunkParentValues[i] = new ParentValue { Value = total };
    47.         }
    48.     }
    49. }
     
  3. JooleanLogic

    JooleanLogic

    Joined:
    Mar 1, 2018
    Posts:
    447
    Yeah I thought of that option as well but the main downside against it is the non-contiguous lookups of children. Also if you need to access multiple components of the child, say Position and Rotation as well, then you're now doing three random lookups for each child via CDFE's.
    In my case, there are many parents with many more children so it makes sense to iterate children contiguously and lookup the parent.
    However buffer is still a good approach and is probably better for one off collections than the alternatives.

    I've come to the conclusion that there's 4 options
    1. Buffer like you mentioned
    2. Group children by SharedComponent
    3. Group children by Component
    4. Brute force iterate all children

    They all have pros/cons and how you code them is very different so it's not easy to switch between approaches.
    It all comes down to what properties you want to chunk your data on which is not an easy question when you have lots of different groupings.
     
  4. BrendonSmuts

    BrendonSmuts

    Joined:
    Jun 12, 2017
    Posts:
    86
    I think this is just the nature of working with ECS. The whole idea is that we are considering our usage/access patterns and choosing the solution the best fits our specific case.

    1) Might work well where you may have children that are in a sufficiently small enough number where additional ArchetypeChunk slicing via SharedComponent division means potentially wasting large amounts of space in your cache line. Maybe this isn't true though, maybe the lookup cost of the CDFE outweighs mostly empty lines? I don't have experience with this yet.

    2) I think if you have a large enough number of children to fill up your cache lines (sounds like you do?) your SharedComponent approach would probably be the best fit.

    3) Not sure what you mean by this one. A component with a reference to the parent that is just a regular IComponentData? Without doing some sort of processing to group your components together your only option is going to be iterating through all children with a single thread. Even if you want to group your components I'm not sure how you do this multi-threaded.

    4) Probably not the best idea if we are trying to design systems with very wide threading in mind. :)

    It's tough to make decisions about these things at the moment when we have so little experience/understanding of the underlying implications. Give it your best guess and come back to it when you've had more time to learn. :)
     
  5. JooleanLogic

    JooleanLogic

    Joined:
    Mar 1, 2018
    Posts:
    447
    Yes very good advice. Not enough experience with dod is giving me decision paralysis. The approach I take on this is going to affect how at least a dozen other systems are written though so it's a lot to refactor if I change it down the track.
    I'll take your advice though and let future me worry about it. :)