Search Unity

What is the most efficient way to access one entity?

Discussion in 'Data Oriented Technology Stack' started by mnarimani, Aug 13, 2019.

  1. mnarimani

    mnarimani

    Joined:
    Mar 27, 2017
    Posts:
    186
    I have many archetypes in my game that only one of them can exist at a time but it is not guaranteed that they exist all the time. (Example: Player.)
    What is the best way to query for these kind of entities?
    I can't cache the entity since it's not guaranteed that they exist when system starts.
     
  2. tertle

    tertle

    Joined:
    Jan 25, 2011
    Posts:
    1,564
    You can just use the singleton methods

    RequireSingletonForUpdate<>() (or you can use HasSingleton<T>() if system needs to run even if singleton doesn't exist)
    GetSingleton<T>()
    GetSingletonEntity<T>();

    Entity query also has

    query.GetSingleton<>()
    query.GetSingletonEntity()

    if you need a more complex query
     
  3. mnarimani

    mnarimani

    Joined:
    Mar 27, 2017
    Posts:
    186
    I think singletons require entities to have exactly one component (The singleton component). Am I right? Docs Link
     
  4. tertle

    tertle

    Joined:
    Jan 25, 2011
    Posts:
    1,564
    The GetSingleton<T> api requires just 1 of a component in the entire world for example Player.
    It does not mean the entity that the player component is attached to can't also have other components such as Input and this can also exist elsewhere.

    The reason I linked the query version as well is it allows multiple of a component as long as the combination is unique

    Maybe the player and the monsters both have a Health component, but there is only ever 1 player.
    This would work

    GetEntityQuery(typeof(Player), typeof(Health)).GetSingleton<Health>()

    Even if there are hundreds of other objects in the world with Health as long as there is only 1 player with health.

    Disclaimer: been a while since I used this but I'm pretty sure this is how it works from memory.
     
    mnarimani likes this.
  5. Creepgin

    Creepgin

    Joined:
    Dec 14, 2010
    Posts:
    250
    It's easy to get tripped on
    ComponentSystem.GetSingleton()
    vs
    EntityQuery.GetSingleton()
    . The biggest difference is that the former will increase the
    m_EntityQueries
    count which will affect how the system runs (see source code of
    ShouldRunSystem()
    ). Sometimes you'll get really weird side effects if you are not careful of which version you are using.
     
    mnarimani likes this.
  6. mnarimani

    mnarimani

    Joined:
    Mar 27, 2017
    Posts:
    186
    Another question, What is the best way to update a singleton?

    For example, player archetype is this:
    PlayerTag, Health, Translation, Rotation


    I can have this query:
    var query = GetEntityQuery(typeof(PlayerTag), typeof(Health), typeof(Translation), typeof(Rotation))


    And for example read health like this:
    Health health = query.GetSingleton<Health>();


    But when I want to set the health, I cannot use SetSingleton. The line below gives me error:
    query.SetSingleton(new Health(20))
     
  7. Creepgin

    Creepgin

    Joined:
    Dec 14, 2010
    Posts:
    250
    What error are you getting? You should make sure the entity exist (with count of 1). This following test passes:

    Code (CSharp):
    1. [Test]
    2. public void GetSetSingleton() {
    3.     var entity = m_Manager.CreateEntity(typeof(EcsTestData), typeof(EcsTestData2));
    4.     var query = EmptySystem.GetEntityQuery(typeof(EcsTestData), typeof(EcsTestData2));
    5.  
    6.     query.SetSingleton(new EcsTestData(10));
    7.     Assert.AreEqual(10, EmptySystem.GetSingleton<EcsTestData>().value);
    8. }
     
  8. mnarimani

    mnarimani

    Joined:
    Mar 27, 2017
    Posts:
    186
    I think the problem with that test is that there is only one entity.
    Here is my code. This is a simple camera system that moves the camera above the player:
    Code (CSharp):
    1. using Unity.Entities;
    2. using Unity.Mathematics;
    3. using Unity.Transforms;
    4. using UnityEngine;
    5.  
    6. namespace BlackSand
    7. {
    8.     public class PlayerCameraSystem : ComponentSystem
    9.     {
    10.         private EntityQuery playerQuery, cameraQuery;
    11.  
    12.         protected override void OnCreate()
    13.         {
    14.             cameraQuery = GetEntityQuery(
    15.                 ComponentType.ReadOnly<Camera>(),
    16.                 ComponentType.ReadOnly<CameraOffset>(),
    17.                 typeof(Translation),
    18.                 typeof(LookAtPoint));
    19.             playerQuery = GetEntityQuery(ComponentType.ReadOnly<PlayerTag>(), ComponentType.ReadOnly<Translation>());
    20.  
    21.             RequireForUpdate(cameraQuery);
    22.             RequireForUpdate(playerQuery);
    23.         }
    24.  
    25.         protected override void OnUpdate()
    26.         {
    27.             float3 playerPosition = playerQuery.GetSingleton<Translation>().Value;
    28.             float3 cameraOffset = cameraQuery.GetSingleton<CameraOffset>().Value;
    29.             float3 cameraPosition = playerPosition + cameraOffset;
    30.  
    31.             // Entity camera = cameraQuery.GetSingletonEntity();
    32.             // EntityManager.SetComponentData(camera, new Translation { Value = cameraPosition });
    33.             // EntityManager.SetComponentData(camera, new LookAtPoint(playerPosition));
    34.             cameraQuery.SetSingleton(new Translation { Value = cameraPosition });
    35.             cameraQuery.SetSingleton(new LookAtPoint(playerPosition));
    36.         }
    37.     }
    38. }
    And here is the error:

    Code (CSharp):
    1. InvalidOperationException: GetSingleton<Unity.Transforms.Translation>() requires that Unity.Transforms.Translation is the only component type in its archetype.
    2. Unity.Entities.EntityQuery.SetSingleton[T] (T value) (at Library/PackageCache/com.unity.entities@0.1.1-preview/Unity.Entities/Iterators/EntityQuery.cs:700)
    3. BlackSand.PlayerCameraSystem.OnUpdate () (at Assets/Scripts/Systems/PlayerCameraSystem.cs:34)
    4. Unity.Entities.ComponentSystem.InternalUpdate () (at Library/PackageCache/com.unity.entities@0.1.1-preview/Unity.Entities/ComponentSystem.cs:800)
    5. Unity.Entities.ComponentSystemBase.Update () (at Library/PackageCache/com.unity.entities@0.1.1-preview/Unity.Entities/ComponentSystem.cs:284)
    6. Unity.Entities.ComponentSystemGroup.OnUpdate () (at Library/PackageCache/com.unity.entities@0.1.1-preview/Unity.Entities/ComponentSystemGroup.cs:602)
    7. UnityEngine.Debug:LogException(Exception)
    8. Unity.Debug:LogException(Exception) (at Library/PackageCache/com.unity.entities@0.1.1-preview/Unity.Entities/Stubs/Unity/Debug.cs:25)
    9. Unity.Entities.ComponentSystemGroup:OnUpdate() (at Library/PackageCache/com.unity.entities@0.1.1-preview/Unity.Entities/ComponentSystemGroup.cs:606)
    10. Unity.Entities.ComponentSystem:InternalUpdate() (at Library/PackageCache/com.unity.entities@0.1.1-preview/Unity.Entities/ComponentSystem.cs:800)
    11. Unity.Entities.ComponentSystemBase:Update() (at Library/PackageCache/com.unity.entities@0.1.1-preview/Unity.Entities/ComponentSystem.cs:284)
    12. Unity.Entities.DummyDelegateWrapper:TriggerUpdate() (at Library/PackageCache/com.unity.entities@0.1.1-preview/Unity.Entities/ScriptBehaviourUpdateOrder.cs:144)
     
  9. Creepgin

    Creepgin

    Joined:
    Dec 14, 2010
    Posts:
    250
    Okay so this is a bit of oversight by Unity on implementing the checks on GetSingleton/SetSingleton. It's trying to enforce the rule that there's only one component type in the archetype, but the actual check only look at the index:

    Code (CSharp):
    1. if(GetIndexInEntityQuery(TypeManager.GetTypeIndex<T>()) != 1)
    2.     throw new System.InvalidOperationException($"GetSingleton<{typeof(T)}>() requires that {typeof(T)} is the only component type in its archetype.");
    This leads to the confusing scenario that sometimes GetSingleton/SetSingleton works if the comp index is conveniently at 1 (even though it's not the only comp type in the archetype). Take the following tests for example.

    Code (CSharp):
    1. [Test]
    2. public void GetSetSingleton1() {
    3.     var entity1 = m_Manager.CreateEntity(typeof(EcsTestData), typeof(EcsTestData2));
    4.     var query = EmptySystem.GetEntityQuery(typeof(EcsTestData), typeof(EcsTestData2));
    5.     query.SetSingleton(new EcsTestData(10));
    6.     Assert.AreEqual(10, query.GetSingleton<EcsTestData>().value);
    7. }
    8.  
    9.  
    10. [Test]
    11. public void GetSetSingleton2() {
    12.     var entity1 = m_Manager.CreateEntity(typeof(EcsTestData), typeof(EcsTestData2));
    13.     var query = EmptySystem.GetEntityQuery(typeof(EcsTestData), typeof(EcsTestData2));
    14.     query.SetSingleton(new EcsTestData2(10));
    15.     Assert.AreEqual(10, query.GetSingleton<EcsTestData2>().value1);
    16. }
    First test passes, and the second fails with the above mentioned error. I think Unity's intention is for both of them to fail. So in your use case, it's best to just work with
    query.GetSingletonEntity()
    .
     
  10. calabi

    calabi

    Joined:
    Oct 29, 2009
    Posts:
    84
    I have a question I'll ask here instead of starting a new thread. How do I get this to work in a job? I cant seem to pass a singleton into a job and write to it.
     
  11. mnarimani

    mnarimani

    Joined:
    Mar 27, 2017
    Posts:
    186
    There are a lot of ways.
    You can write an IJobForeach and only run it on the specified query.
    Or you can use GetComponentDataFromEntity method inside the system and store the return value inside a job.

    Also keep in mind that starting a job also has a cost. Don't write a job for trivial tasks.
     
  12. calabi

    calabi

    Joined:
    Oct 29, 2009
    Posts:
    84
    Thanks, I'm just iterating through a load of entities adding and totalling their variable amounts into a single variable. Is there a way to get a public variable out of a jobcomponent without using an entity, basically have it return the variable.
     
  13. calabi

    calabi

    Joined:
    Oct 29, 2009
    Posts:
    84
    So is this really the best way of doing it.

    Code (CSharp):
    1. public class CountAllMoneyHeld : JobComponentSystem
    2. {
    3.     //public int AllMoneyHeld;
    4.     public EntityQuery CityInforquery;
    5.  
    6.     protected override void OnCreate()
    7.     {
    8.         CityInforquery = GetEntityQuery(typeof(GeneralCityInfo));
    9.     }
    10.  
    11.     struct CountAllMoneyJob : IJobForEach<MoneyEntityInfo>
    12.     {        
    13.         public NativeArray<GeneralCityInfo> currentinfo;
    14.      
    15.         public void Execute(ref MoneyEntityInfo currmoney)
    16.         {
    17.  
    18.             var currentcount = currentinfo[0];
    19.             currentcount.CountAllMoney += currmoney.MoneyHeld;
    20.  
    21.             currentinfo[0] = currentcount;    
    22.          
    23.         }
    24.     }  
    25.  
    26.     protected override JobHandle OnUpdate(JobHandle inputDeps)
    27.     {    
    28.  
    29.         var deedledumps = CityInforquery.ToComponentDataArray<GeneralCityInfo>(Allocator.TempJob, out JobHandle deedlehandle);
    30.  
    31.         var CountMoneyJob = new CountAllMoneyJob
    32.         {            
    33.             currentinfo = deedledumps          
    34.          
    35.         };
    36.         inputDeps = CountMoneyJob.ScheduleSingle(this, deedlehandle);
    37.        
    38.       //CityInforquery.SetSingleton<GeneralCityInfo>(new GeneralCityInfo { CountAllMoney = currentinfo });
    39.    
    40.  
    41.         inputDeps.Complete();
    42.  
    43.         CityInforquery.CopyFromComponentDataArray(deedledumps);
    44.  
    45.         deedledumps.Dispose();
    46.  
    47.         return inputDeps;
    48.     }
    49. }

    I have 3 jobs just for this one entity that seems not very efficient. Is there not a way I could write to a single variable and then return it from a job and then write that to the entity component outside the job.
     
  14. calabi

    calabi

    Joined:
    Oct 29, 2009
    Posts:
    84
    Ok figured out a better way of doing it if anyone is curious(I forgot nativearrays work as references).
    Code (CSharp):
    1.  
    2. public class CountAllMoneyHeld : JobComponentSystem
    3. {
    4.  
    5.     public EntityQuery CityInforquery;
    6.     public NativeArray<int> countmoney;
    7.  
    8.     protected override void OnCreate()
    9.     {
    10.         CityInforquery = GetEntityQuery(typeof(GeneralCityInfo));
    11.         this.Enabled = false;
    12.         //countmoney = new NativeArray<int>(1, Allocator.TempJob);
    13.     }
    14.  
    15.     struct CountAllMoneyJob : IJobForEach<MoneyEntityInfo>
    16.     {
    17.         //[NativeDisableParallelForRestriction]
    18.         public NativeArray<int> jobcountmoney;
    19.  
    20.         public void Execute(ref MoneyEntityInfo currmoney)
    21.         {
    22.          
    23.             jobcountmoney[0] += currmoney.MoneyHeld;
    24.          
    25.         }
    26.  
    27.     }
    28.  
    29.  
    30.     protected override JobHandle OnUpdate(JobHandle inputDeps)
    31.     {
    32.         countmoney = new NativeArray<int>(1, Allocator.TempJob);
    33.  
    34.         var CountMoneyJob = new CountAllMoneyJob
    35.         {
    36.             //currentinfo = CityInforquery.GetSingleton<GeneralCityInfo>(),
    37.             //currentinfo = deedledumps
    38.             jobcountmoney = countmoney
    39.          
    40.             //currentinfo = GetSingleton<GeneralCityInfo>()
    41.          
    42.  
    43.         };
    44.         inputDeps = CountMoneyJob.ScheduleSingle(this, inputDeps);    
    45.  
    46.         inputDeps.Complete();
    47.  
    48.         CityInforquery.SetSingleton<GeneralCityInfo>(new GeneralCityInfo { CountAllMoney = countmoney[0] });
    49.  
    50.         if (inputDeps.IsCompleted)
    51.         {
    52.             this.Enabled = false;
    53.  
    54.  
    55.         }
    56.      
    57.         countmoney.Dispose();
    58.  
    59.         return inputDeps;
    60.     }
    61. }

    It doesn't work multithreaded I just get random answers, I'm curious why that happens but it works and is probably better than 3 jobs.