Search Unity

Best way to implement singleton Entity?

Discussion in 'Entity Component System' started by fholm, Nov 16, 2018.

  1. fholm

    fholm

    Joined:
    Aug 20, 2011
    Posts:
    2,052
    I have need for a couple of singleton entities in my project, which holds global settings for various things. Some stuff like the 'World' component is modified before each simulation tick:

    Code (csharp):
    1.  
    2.   public unsafe struct World : IComponentData {
    3.     public Int32 Tick;
    4.     // etc ...
    5.   }
    6.  
    And some stuff, like the settings:

    Code (csharp):
    1.  
    2.   public unsafe struct Settings : IComponentData {
    3.     public Int32 MaxConnections;
    4.     // etc ...
    5.   }
    6.  
    Is set once at startup and doesn't change after that. I need access to these in all kinds of various places, both in jobs and not. What's the 'recommended' way of doing this? If there is one. Do you just store the entity reference in some globally accessible place?
     
  2. meanmonkey

    meanmonkey

    Joined:
    Nov 13, 2014
    Posts:
    148
    I thought about that too but I'm not sure if an ecs is meant to be used like this. I personally ended up creating a static helper class holding an object reference. This object contains all my global data inculding nativecontainers for system to system passing.

    But if you want to store it in a singleton entity, why not assigning your components as shown above to an entity, store the entityID somehwere and access the entity via ComponentDataFromEntity? You will have store the entityid somehwere globally at least.

    https://github.com/Unity-Technologi...ation/reference/component_data_from_entity.md

    You only can use that in the mainthread though, for jobs there is a injection variant but that will be deprecated, so I dunno what future releases will bring up in this case.
     
  3. fholm

    fholm

    Joined:
    Aug 20, 2011
    Posts:
    2,052
    I feel like putting as much data as possible on entities is the way to go, this for example what the overwatch devs did. They basically came to the conclusion that everything should be an entity, as it simplifies a lot of other interactions, helps dealing with multiple worlds, etc.

    I can't do static/global data easily, as i need to be able to have several almost identical worlds going at once.
     
  4. meanmonkey

    meanmonkey

    Joined:
    Nov 13, 2014
    Posts:
    148
    I see...another idea which came up my mind, and I'm just brainstorming now, is SharedComponentData. Zero memory cost on per entity basis. Only thing to consider is, that entities sharing the same sharedcomponentdata ar grouped together in chunks, depends on your systems if that could be a problem or kinda overhead.

    So you could access you global data on per entity basis..
     
  5. fholm

    fholm

    Joined:
    Aug 20, 2011
    Posts:
    2,052
    I'm... interesting idea, but honestly feel just having a few singleton entities laying around is the way to go. Potentially with SharedComponentData on themselves, since I might need to store some reference objects and such
     
  6. dartriminis

    dartriminis

    Joined:
    Feb 3, 2017
    Posts:
    157
    I, personally, never assume their will only ever be one entity with a given component type. Even if I am sure I'm only ever going to create one (such as a player component) I always treat my scripts as if there were many. This helps make the scripting consistent and less error prone.

    If I just have some global setting data that needs shared between systems, I generally stick this in another system and use World.GetManager or InjectAttribute to reference this data elsewhere. Usually this system pulls it's data in either from a MonoBehaviour (if I have scene references) or JSON.

    Just my $0.02. :)
     
    meanmonkey likes this.
  7. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    4,271
    I'm building my game such that no data is stored in systems (other than maybe caches that could be safely deleted at any time). This makes things way easier to support modding without releasing my systems code and will hopefully lead to a beautiful save state system.

    For singleton entities, I have a base class method that looks like this:
    Code (CSharp):
    1. public Entity GetFirstEntity(ComponentGroup group)
    2.         {
    3.             if (group.CalculateLength() <= 0)
    4.             {
    5.                 throw new InvalidOperationException($"Failed to find an entity in {group}");
    6.             }
    7.  
    8.             var entityType = GetArchetypeChunkEntityType();
    9.             var chunks = group.CreateArchetypeChunkArray(Allocator.Temp);
    10.             var entities = chunks[0].GetNativeArray(entityType);
    11.             var entity = entities[0];
    12.             chunks.Dispose();
    13.             return entity;
    14.         }
    I can then pass the returned entity and ComponentDataFromEntity to jobs, especially IJobProcessComponentDataWithEntity. This works beautifully. If you expect the singleton entity to stay forever, you can lazy cache it with a property on a system. However, I allow for singleton entities to be destroyed and reinstantiated from prefabs at the end of every frame, so I'm hoping Unity will provide a more efficient way to get the first entity from the first chunk only.

    As for ISharedComponentData on entities, I haven't tested this yet due to other priorities, but I have this code typed up which may or may not work as expected:
    Code (CSharp):
    1. public struct SharedComponentDataHandle<T> : IComponentData where T : ISharedComponentData
    2. {
    3.     public int version;
    4. }
    5.  
    6. public T GetSharedComponentData<T>(Entity entity, bool readOnly = false) where T : struct, ISharedComponentData
    7.         {
    8.             GetComponentDataFromEntity<SharedComponentDataHandle<T>>(readOnly);
    9.             return EntityManager.GetSharedComponentData<T>(entity);
    10.         }
    11.  
    12.         public void SetSharedComponentData<T>(Entity entity, T data) where T : struct, ISharedComponentData
    13.         {
    14.             EntityManager.SetSharedComponentData(entity, data);
    15.         }
    The goal of the above is to set up automatic dependency tracking of SharedComponentData since automatic dependency tracking is based on type, yet input dependencies are based on jobs scheduled. So if a non-tracked type is always paired with a tracked type, then the tracked type should enforce the correct dependencies of the non-tracked type.
     
    fholm likes this.
  8. illinar

    illinar

    Joined:
    Apr 6, 2011
    Posts:
    863
    I'm using this:

    Code (CSharp):
    1. public struct SingletonInjection<T> where T : struct, IComponentData
    2. {
    3.     private ComponentDataArray<T> _dataArray;
    4.     private EntityArray _entities;
    5.     readonly private int Length;
    6.  
    7.     public T data
    8.     {
    9.         get
    10.         {
    11.             if (Length > 1)
    12.             {
    13.                 throw new System.Exception("Multiple copies of a singleton data were found");
    14.             }
    15.             return _dataArray[0];
    16.         }
    17.         set
    18.         {
    19.             _dataArray[0] = value;
    20.         }
    21.     }
    22.  
    23.     public Entity entity
    24.     {
    25.         get
    26.         {
    27.             return _entities[0];
    28.         }
    29.     }
    30.  
    31.     public static implicit operator bool(SingletonInjection<T> a) => a.Length > 0;
    32. }
    Code (CSharp):
    1. [Inject] SingletonInjection<Settings> _settings;
    But Injections are going away in the future. Also, some official way of having singletons will arrive at some point.
     
  9. fholm

    fholm

    Joined:
    Aug 20, 2011
    Posts:
    2,052
    Does this work for jobs?
     
  10. illinar

    illinar

    Joined:
    Apr 6, 2011
    Posts:
    863
    Yes. It's a normal injection just in a generic wrapper.

    Also, if you want top performance or more ECS purity, you can also split settings into smaller components so only the stuff that is used is fetched into the cache, although if it's a singleton it might not matter.

    I'm doing it for decoupling sometimes. Like ScreenResolution : IComponentData { int width, height; }