Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

Question Best practices during scripting your ecs architecture for performance purposes

Discussion in 'Entity Component System' started by rubcc95, May 15, 2023.

  1. rubcc95

    rubcc95

    Joined:
    Dec 27, 2019
    Posts:
    222
    1.- Singletons and their purpose: The entity pack contains numerous functions to build and access singletons, but to what extent is it necessary and convenient to use? If I don't use multiple worlds in my program, why do I want to add a single instance and search for it in each frame among the pile of EntityManager components instead of not making the component a singleton and keeping it as an unmanaged static structure?

    2.-
    Collections within components: CommandBuffers, NativeContainers, pointers...
    Imagine a simple inventory system. The inventory has a fixed capacity and always has one selected. I assume several implementations for this and I am not clear which is the optimal or the most correct.
    Code (CSharp):
    1.     [InternalBufferCapacity(5)]
    2.     struct InventoryItem : IBufferElementData
    3.     {
    4.         public Entity reference;
    5.     }
    6.  
    7.     struct SelectedInventoryItem : IComponentData
    8.     {
    9.         public int index;
    10.     }
    Option A: Use two components, a fixed-size CommandBuffer, and a ComponentData which indicates the selected object. But if the objects in the inventory have a read-only purpose and the only thing that can be altered is the selected object? Why to use a CommandBuffer?
    Code (CSharp):
    1.     struct Inventory : IComponentData
    2.     {
    3.         public NativeArray<Entity> entities;
    4.         public int index;
    5.  
    6.         public Entity this[int index]
    7.         {
    8.             get
    9.             {
    10.                 if (index < entities.Length)
    11.                     return entities[index];
    12.                 throw new System.IndexOutOfRangeException();
    13.             }
    14.         }
    15.  
    16.         public Entity Current => this[index];      
    17.     }
    18.  
    Now, using a NativeArray gives problems when inserting it inside another, we can use an UnsafeList instead, but why not use pointers directly? My profiler tests give a higher speed by letting the garbage collector do the job of destroying these allocations when necessary instead of creating unsafeArrays inside pointers or using buffers.
    Code (CSharp):
    1.     unsafe struct Inventory : IComponentData
    2.     {      
    3.         public Entity* references;
    4.         public int length;
    5.         public int index;
    6.  
    7.         public Entity this[int index]
    8.         {
    9.             get
    10.             {
    11.                 if (index < length)
    12.                     return references[index];
    13.                 throw new System.IndexOutOfRangeException();
    14.             }
    15.         }
    16.  
    17.         public Entity Current => this[index];
    18.     }
     
  2. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    3,976
    xVergilx likes this.
  3. xVergilx

    xVergilx

    Joined:
    Dec 22, 2014
    Posts:
    3,292
    If you're willing to manually manage dependencies to exclude potential race conditions - then sure, statics could work. There's another issue with statics however - they have to be reset manually if you disable domain reload.

    If its a single variable, SharedStatic<T> may work.
    Otherwise, builtin singletons are better.

    Also, you don't have to use EntityManager in order to get them.
    Queries and Lookups exist.

    Same as 1. If you go wihout safety - it may work.
    It may break. It may leak and cause a hard crash. Also - no thread safety.

    Note that safety checks are not included in the release version.
    So performance gains may be about 0 compared to the editor / no safety variation.

    In terms of case 2 - if you want a specific native container - you can build it.
    That's a more robust option than going full unsafe no-checks mode.

    Is it worth it overall? That's up to you to decide.

    In reality, if its only 5 elements, you could:
    - Put a switch for an indexer;
    - Layout data inside a struct;
    - Done;

    Probably going to be faster and more reliable, no unsafe required.


    Right now Entities lack in native container binding to components.
    That's an annoyance but it can be worked around.
    However, overusing unsafe / pointers will make your life 10 times harder than it should be.
     
    Last edited: May 15, 2023
  4. Razmot

    Razmot

    Joined:
    Apr 27, 2013
    Posts:
    345
    >But if the objects in the inventory have a read-only purpose and the only thing that can be altered is the selected object

    I would keep the "mostly read" NativeArray as a field in the System and just pass it around with .AsReadonly().

    Outside of components you can nest NativeContainers as much as you want:

    Crazy nesting example from my code that works fine and fast with a bursted Isystem.OnUpdate on mainthread
    (I do unions / intersections on a bunch of hashsets for a procedural voxel terrain ) :

    Code (CSharp):
    1.  public partial struct LocationSystem : ISystem
    2.     {
    3.         private LocatorList _locatorList;
    4.  
    Code (CSharp):
    1.   public struct LocatorList
    2.     {
    3.         public NativeArray<Locator> _entityLoc;
    4.         public NativeArray<NativeHashSet<int3>> _scan;
    5.         public NativeArray<NativeHashSet<int3>> _scanCopy;
    6.         public NativeArray<NativeList<Entity>> _freshEntities;
    7.  
    Code (CSharp):
    1.  public struct Locator
    2.     {
    3.         private NativeHashSet<int3> _keySet;
    4.         private NativeHashSet<int3> _keySetCopy;
    5.  
    6.         private NativeHashMap<int3, Entity> _map;
    7.  
    8.         public NativeHashSet<int3>.ReadOnly KeySet => _keySet.AsReadOnly();
    9.  
    ( I wish I could have the keys of a hashmap as an hashset but it's implemented as an array)
     
  5. n3b

    n3b

    Joined:
    Nov 16, 2014
    Posts:
    56
    I use dummy RW/RO singletons to explicitly define dependency chain between systems. If I'm not wrong there is no API for that.
    Along with World-wide available repository of native collections and custom allocator (to dispose all collections at once) it covers all my needs honestly.

    A side note - when using nested collection with unsafe lists - keep in mind that lists grow up by the next power of two, it can eat memory quite fast if not controlled.