Search Unity

  1. The Unity Pro & Visual Studio Professional Bundle gives you the tools you need to develop faster & collaborate more efficiently. Learn more.
    Dismiss Notice
  2. Improved Prefab workflow (includes Nested Prefabs!), 2D isometric Tilemap and more! Get the 2018.3 Beta now.
    Dismiss Notice
  3. Want more efficiency in your development work? Sign up to receive weekly tech and creative know-how from Unity experts.
    Dismiss Notice
  4. Participate with students all over the world and build projects to teach people. Join now!
    Dismiss Notice
  5. Build games and experiences that can load instantly and without install. Explore the Project Tiny Preview today!
    Dismiss Notice
  6. Improve your Unity skills with a certified instructor in a private, interactive classroom. Watch the overview now.
    Dismiss Notice
  7. Want to see the most recent patch releases? Take a peek at the patch release page.
    Dismiss Notice

How to think about setting up large number of entities

Discussion in 'Entity Component System and C# Job system' started by tbriley, Dec 7, 2018 at 8:56 AM.

  1. tbriley

    tbriley

    Joined:
    Sep 10, 2013
    Posts:
    40
    I've been using ECS frameworks for a while yet but am struggling a bit getting started with the Unity system - It's great, but there are many solutions to the problem ;)

    I have an entity and want to give it a component that represents some kind of Hash (ie, fixed number of bytes and characters A-Z), and another component that references that Hash. I've ended up with something like this:

    Code (CSharp):
    1.  
    2. public struct Hash : IBufferElementData
    3. {
    4.     public byte Value;
    5. }
    6.  
    7. public struct SomeParentHash : IBufferElementData
    8. {
    9.     public byte Value;
    10. }
    11.  
    Assigning SomeParentHash can potentially be a costly operation as I may have to cycle through a large number of entities to find a good match. What's the "correct" way to think about this? At first I thought a reactive system, then IJobProcessComponentData (but these are IBufferElementDatas, not IComponentDatas)... etc etc... I'm lost in a sea of options. Can I somehow ditch the IBufferElementDatas and use IComponentDatas with fixed arrays without having to sprinkle `unsafe`everywhere? Or continue with one of these strategies? Or is there some other path I'm not recognizing? I feel like I'm barely scratching the surface here.
     
  2. meanmonkey

    meanmonkey

    Joined:
    Nov 13, 2014
    Posts:
    76
  3. eizenhorn

    eizenhorn

    Joined:
    Oct 17, 2016
    Posts:
    649
    You can use IJobProcessComponentData\IJobProcessComponentDataWithEntity with BufferFromEntity<YourBufferType> for get buffer from entity and you can use BufferFromEntity.Exists(Entity) for check, exist buffer on entity or not, also, if you want iterate only entities with YourBufferType, you can use [RequireComponentTag(typeof(YourBufferType))]
     
    meanmonkey likes this.
  4. tbriley

    tbriley

    Joined:
    Sep 10, 2013
    Posts:
    40
    i've found their posts very helpful ;)

    this is basically what i ended up on, it just feels a bit clunky to me, like creating an empty IComponentData just to tag these with, and not even consuming it in Execute(). it would be nice to have at least IJobProcessBufferElementData and IJobProcessBufferElementDataWithEntity, or ideally some way to mix the two (or way to stuff some kind of fixed array structure into an IComponentData).

    Code (CSharp):
    1.  
    2.         //[BurstCompile]
    3.         [RequireComponentTag(typeof(BufferA))]
    4.         [RequireComponentTag(typeof(BufferB))]
    5.         [RequireComponentTag(typeof(BufferC))]
    6.         struct Job : IJobProcessComponentDataWithEntity<SomeTag>
    7.         {
    8.             [ReadOnly] public BufferFromEntity<BufferA> bufferA;
    9.             [ReadOnly] public BufferFromEntity<BufferB> bufferB;
    10.             [ReadOnly] public BufferFromEntity<BufferC> bufferC;
    11.  
    12.             public void Execute(Entity entity, int index, ref SomeTag data)
    13.             {
    14.                 var someBytes = Encoding.UTF8.GetBytes("HELLO");
    15.                 var someArray = new BufferA[someBytes.Length];
    16.                 for (var i = 0; i < someBytes.Length; i++)
    17.                 {
    18.                     someArray[i].Value = someBytes[i];
    19.                 }
    20.                 bufferA[entity].CopyFrom(someArray);
    21.             }
    22.         }
    23.  
    24.         protected override JobHandle OnUpdate(JobHandle inputDeps)
    25.         {
    26.             var job = new Job()
    27.             {
    28.                 bufferA = GetBufferFromEntity<BufferA>(false)
    29.                 bufferB = GetBufferFromEntity<BufferB>(false)
    30.                 bufferC = GetBufferFromEntity<BufferC>(false)
    31.             };
    32.             return job.Schedule(this, inputDeps);
    33.         }
    34.  
    Does this look "about right"? Also, I'm curious why this doesn't seem to work with Burst. Cheers!
     
  5. eizenhorn

    eizenhorn

    Joined:
    Oct 17, 2016
    Posts:
    649
    You can't use static fields\properties inside Burst. In your case is Encoding.UTF8 <- it's static property
     
  6. recursive

    recursive

    Joined:
    Jul 12, 2012
    Posts:
    401
    To add to what @eizenhorn said, you could store the bytes in a NativeArray<byte> and pass that into the job. Not only will this not access a static property reference, if you mark the passed array as [ReadOnly] then this is a net performance benefit as all jobs can safely read from it.

    You also cannot make a managed array
    new BufferA[someBytes.Length]
    in a job. But with BufferFromEntity you can use it like a NativeList<T>:
    Code (CSharp):
    1. [ReadOnly] public BufferFromEntity<BufferA> bufferAs;
    2. [ReadOnly] public NativeArray<byte> SomeBytes;
    3.  
    4. public void Execute(Entity entity, int index, [ReadOnly] ref SomeTag data)
    5. {
    6.     // if you know the type has the desired dynamic buffer, you don't need to use the Exists(entity) check on the BufferFromEntity<>
    7.     var buffer = bufferAs[entity];
    8.     buffer.Clear();
    9.     buffer.ReInterpret<byte>().AddRange(someBytes); // sizes must match but is otherwise a plain old reinterpret_cast. Use wisely.
    10. }
    It can also be "shadow-copied" and masquarade as a normal array if you are not simultaneously changing it's size via ToArray();
     
  7. tbriley

    tbriley

    Joined:
    Sep 10, 2013
    Posts:
    40
    informative, thanks! though i was doing
    new BufferA[someBytes.Length]
    in a job...

    the issue with statics now also clarifies why i was unable to use entity command buffer. much learning still to do.
     
  8. eizenhorn

    eizenhorn

    Joined:
    Oct 17, 2016
    Posts:
    649
    You can use ECB in bursted job, but only Destroy operation.
     
  9. recursive

    recursive

    Joined:
    Jul 12, 2012
    Posts:
    401
    I'll add that ECB RemoveComponent works with burst, but only the overload that takes a ComponentType struct and not the generics version (since that calls into a static type lookup manager).
     
    tbriley likes this.