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 Dumb basic question

Discussion in 'Entity Component System' started by ronJohnJr, Aug 24, 2022.

  1. ronJohnJr

    ronJohnJr

    Joined:
    Mar 5, 2015
    Posts:
    106
    Hi, I'm going through a few tutorials to get the hang of DOTS, but I have a question. Every tutorial or resource I can find instantiates multiple objects in OnUpdate, which makes sense, but how and where would I put the code to just instantiate ONE prefab and stop. Would that still go into update with some sort of condition to spawn one and then stop? But then would update always be running and checking if that condition is true or false? Is there a better way? Thanks
     
  2. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    3,983
    Most likely yes
    To some degree, yes. Either it will test the query every update, or it will run your code in OnUpdate() where you poll, depending on how you write your system.
    To start with, no. When writing data-oriented code, you write code very specific to the problem and poll things often. Then as things progress, you find ways to batch things together and generalize.
     
    ronJohnJr likes this.
  3. ronJohnJr

    ronJohnJr

    Joined:
    Mar 5, 2015
    Posts:
    106
    Ok thanks, so I've created a "spawned" bool and wrapped my spawn / instantiate logic in it, and then when I instantiate my entity I set the bool to true so unity no longer tries to read the logic. That sound good?
     
  4. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    3,983
    What owns the "spawned" bool?
     
  5. ronJohnJr

    ronJohnJr

    Joined:
    Mar 5, 2015
    Posts:
    106
    Sorry not 100% sure what you mean, I'm still trying to learn.

    It's being used in the PlayerSpawnSystem with World.GetOrCreateSystem<BeginSimulationEntityCommandBufferSystem>();
     
  6. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    3,983
    That's just another way of asking where is that bool stored. Whatever type has the bool as a member "owns" it. And really, I was wondering if you were storing it in an IComponentData or a SystemBase. The reason I asked is because in builds, systems can run before the scenes get loaded, which can sometimes break startup spawning logic like this depending on how you acquire the prefab to spawn and where the bool gating the logic is stored.
     
  7. ronJohnJr

    ronJohnJr

    Joined:
    Mar 5, 2015
    Posts:
    106

    Oh ok, it's in systemBase. I wouldn't really know how to do it any other way right now lol. I'm at the point where I just understand how to edit the tutorials to fit what I need, not make something from scratch
     
  8. vectorized-runner

    vectorized-runner

    Joined:
    Jan 22, 2018
    Posts:
    383
    There's a trick to run systems once, on the OnCreate method of your system, you use RequireSingletonForUpdate<T> (T is a empty tag component here), and whenever you want this System to run, you create a Single Entity with that component. At the end of the OnUpdate of the System, you use GetSingleton<T> and Destroy this entity, so the System no longer runs on each update, until it's required to run again
     
    Rupture13 likes this.
  9. xVergilx

    xVergilx

    Joined:
    Dec 22, 2014
    Posts:
    3,292
    Couple of options. Systems will not run in cases like:
    - No entities available for the EntityQueries set to the system -> No entities to process:

    1. Use GetEntityQuery in OnCreate. This will register query as one that should be processed by the system. If any of these queries match -> then the system will run, unless overriden by RequireForUpdate / RequireSingletonForUpdate.
    Basically "if <ANY> query match -> process"

    2. If RequireForUpdate is used, then all queries set for the GetEntityQuery will be ignored. Queries passed via RequireForUpdate will be used instead. This is useful, because any ForEach generated job will codegen a query and add it automatically via GetEntityQuery. This way you can override behaviour of the system to run when:
    "if <ALL> queries match -> process".

    3. EntityQueryDesc can be used to set query matching to "None".
    So if no specific component is present -> then system will run.

    Alternatively, you can always fast opt-out:
    4. Check if stored EntityQuery is empty (e.g. cached from OnCreate).
    "if (_query.IsEmpty) return;". Its relatively fast, although overhead is non-zero;

    And lastly:
    5. You can disable system completely with .Enabled property. Yes, its kinda non-DOD alike, but works like a charm for systems that should only run once per application lifetime cycle.
     
    Last edited: Aug 25, 2022
    Rupture13 and bb8_1 like this.
  10. ronJohnJr

    ronJohnJr

    Joined:
    Mar 5, 2015
    Posts:
    106

    So I tried to do this with my limited knowledge and I can't get it to work. I made a RunUpdateOnceTag component, put it on my player, then I have RequireSingletonForUpdate<RunUpdateOnceTag>(); in my playerSpawnSystem, but it wont reach the update part, it's like it can't find it. I don't know enough about the ECS system to say why
     
  11. ronJohnJr

    ronJohnJr

    Joined:
    Mar 5, 2015
    Posts:
    106

    Number 5 worked for me, thanks. Not sure if it did it "right" though. I put

    World.GetExistingSystem<PlayerSpawnSystem>().Enabled = false;

    At the end of OnUpdate
     
  12. Rupture13

    Rupture13

    Joined:
    Apr 12, 2016
    Posts:
    129
    The OnUpdate is part of the PlayerSpawnSystem, right?
    If so, you don't have to do
    World.GetExistingSystem<PlayerSpawnSystem>().Enabled = false;

    but can simply do
    Enabled = false;

    since "Enabled" is a member of the system you're working in.
     
    ronJohnJr and StickyKevin like this.
  13. ronJohnJr

    ronJohnJr

    Joined:
    Mar 5, 2015
    Posts:
    106
    cool thanks
     
  14. xVergilx

    xVergilx

    Joined:
    Dec 22, 2014
    Posts:
    3,292
    Either component is not added to the entity, other queries don't match, or RequireSingletonForUpdate is bugged (there are some bugged versions). Usually RequireForUpdate is better.

    Use Entity Debugger, or new Window -> Analysis -> DOTS/Systems window. You can select system to see what queries available (and what entity in the queries are). "Hierarchy" window also provides info on which entities exist. Selecting one will show in inspector what components are present.

    Its extremely important to learn how to use those tools, otherwise you'll get stuck constantly trying to figure out whats going on.
     
    Last edited: Aug 25, 2022
    Anthiese likes this.
  15. ronJohnJr

    ronJohnJr

    Joined:
    Mar 5, 2015
    Posts:
    106
    Not sure what I'm doing wrong, looks like everything is in the right place?

    Code (CSharp):
    1. [UpdateInGroup(typeof(LateSimulationSystemGroup))]
    2. public partial class PlayerSpawnSystem : SystemBase
    3. {
    4.     private EntityQuery m_UpdateTagQuery;
    5.  
    6.     private EndSimulationEntityCommandBufferSystem m_EndSimEcb;
    7.  
    8.     private Entity m_Prefab;
    9.  
    10.     protected override void OnCreate()
    11.     {
    12.         m_UpdateTagQuery = GetEntityQuery(ComponentType.ReadWrite<RunUpdateOnceTag>());
    13.  
    14.         m_EndSimEcb = World.GetOrCreateSystem<EndSimulationEntityCommandBufferSystem>();
    15.  
    16.         RequireForUpdate(m_UpdateTagQuery);
    17.     }
    18.  
    19.     protected override void OnUpdate()
    20.     {
    21.         var commandBuffer = m_EndSimEcb.CreateCommandBuffer().AsParallelWriter();
    22.      
    23.         var updateOnce = GetSingleton<RunUpdateOnceTag>();
    24.      
    25.         if (m_Prefab == Entity.Null)
    26.         {
    27.             m_Prefab = GetSingleton<PlayerAuthoringComponent>().Prefab;
    28.             return;
    29.         }
    30.         EntityManager.Instantiate(m_Prefab);
    31.  
    32.         //Enabled = false;
    33.  
    34.         Entities
    35.         .WithAll<RunUpdateOnceTag>()
    36.         .ForEach((Entity entity, int entityInQueryIndex) =>
    37.         {
    38.             commandBuffer.RemoveComponent<RunUpdateOnceTag>(entityInQueryIndex, entity);
    39.         }).ScheduleParallel();
    40.  
    41.         m_EndSimEcb.AddJobHandleForProducer(Dependency);
    42.     }
    43. }




     
  16. Arnold_2013

    Arnold_2013

    Joined:
    Nov 24, 2013
    Posts:
    262
    I can't really follow what the problem is. But is the system not running and is this a problem?

    Should this be the case then I can imagine its because you are making the EntityManager.Instantiate(m_Prefab); in the OnUpdate()... before this instantiate there is no entity with RunUpdateOnceTag

    So when DOTS looks at your system it decides not to run it because the Query in the Entities.ForEach is not returning any entities. Maybe add an [AlwaysUpdateSystem] above your system to kick it to run.
     
  17. ronJohnJr

    ronJohnJr

    Joined:
    Mar 5, 2015
    Posts:
    106

    OnUpdate is not being ran at all when I'm using "RequireForUpdate(m_UpdateTagQuery);" I can put a debug.log in there and it wont print anything, also backed up by my player not being instantiated. So obviously I'm doing something wrong lol My player isn't in the scene, but it's looking for the runUpdateOnceTag that's attached to the player, maybe that's it?
     
  18. Rupture13

    Rupture13

    Joined:
    Apr 12, 2016
    Posts:
    129
    Since the RunUpdateOnceTag is a tag and thus has no data, I'm not sure if ComponentType.ReadWrite works with it (since there's no data to read or write). Try using just
    GetEntityQuery(typeof(RunUpdateOnceTag));


    Furthermore:
    • You don't have to
      return
      after finding your prefab entity
    • You can instantiate the prefab with the EntityCommandBuffer too, to avoid a sync point.
    • You don't have to run a job to remove the RunUpdateOnceTag from the singleton.

    Code (CSharp):
    1.  
    2. [UpdateInGroup(typeof(LateSimulationSystemGroup))]
    3. public partial class PlayerSpawnSystem : SystemBase
    4. {
    5.     private EntityQuery m_UpdateTagQuery;
    6.  
    7.     private EndSimulationEntityCommandBufferSystem m_EndSimEcb;
    8.  
    9.     private Entity m_Prefab;
    10.  
    11.     protected override void OnCreate()
    12.     {
    13.         m_UpdateTagQuery = GetEntityQuery(typeof(RunUpdateOnceTag));
    14.  
    15.         m_EndSimEcb = World.GetOrCreateSystem<EndSimulationEntityCommandBufferSystem>();
    16.  
    17.         RequireForUpdate(m_UpdateTagQuery);
    18.     }
    19.  
    20.     protected override void OnUpdate()
    21.     {
    22.         var commandBuffer = m_EndSimEcb.CreateCommandBuffer();
    23.    
    24.         var updateOnce = m_UpdateTagQuery.GetSingletonEntity();
    25.  
    26.         if (m_Prefab == Entity.Null)
    27.         {
    28.             m_Prefab = GetSingleton<PlayerAuthoringComponent>().Prefab;
    29.         }
    30.  
    31.         commandBuffer.Instantiate(m_Prefab);
    32.         commandBuffer.RemoveComponent<RunUpdateOnceTag>(updateOnce);
    33.     }
    34. }
    35.  
     
  19. Rupture13

    Rupture13

    Joined:
    Apr 12, 2016
    Posts:
    129
    Actually, I think I know what the issue is:

    The entity that the RunUpdateOnceTag exists on, is a prefab itself (it has the Prefab tag).
    EntityQueries don't find Prefabs (or Disabled entities) unless explicitly stated that they should.
    So what you can do in your case is:
    Code (CSharp):
    1. m_UpdateTagQuery = GetEntityQuery(new EntityQueryDesc
    2. {
    3.     All = new ComponentType[] { typeof(RunUpdateOnceTag) },
    4.     Options = EntityQueryOptions.IncludePrefab
    5. });
    And I think then everything should work.
    The other optimisations in my previous comment still apply though, but they aren't essential in making it work ;)
     
    ronJohnJr likes this.
  20. ronJohnJr

    ronJohnJr

    Joined:
    Mar 5, 2015
    Posts:
    106

    Thanks for the code clean up! and it ALMOST works now. It instantiates 2 players before it stops running the OnUpdate.
     
  21. Rupture13

    Rupture13

    Joined:
    Apr 12, 2016
    Posts:
    129
    I'd add a
    UnityEngine.Debug.Log("Test")
    in the OnUpdate method to see how often that log gets printed.

    If it prints more than once, the system runs more than once and you can investigate that.
    If it prints only once, then there's something else going on, perhaps with the m_Prefab entity.
     
  22. ronJohnJr

    ronJohnJr

    Joined:
    Mar 5, 2015
    Posts:
    106
    So it runs 3 times actually. I fixed it, but I don't know if I fixed it the right way. I added another command buffer to instantiate at the beginning and then remove at the end.

    Code (CSharp):
    1.  
    2. [UpdateInGroup(typeof(LateSimulationSystemGroup))]
    3. public partial class PlayerSpawnSystem : SystemBase
    4. {
    5.     private EntityQuery m_UpdateTagQuery;
    6.  
    7.     private EndSimulationEntityCommandBufferSystem m_EndSimEcb;
    8.     private BeginSimulationEntityCommandBufferSystem m_BeginSimEcb;
    9.  
    10.     private Entity m_Prefab;
    11.  
    12.     protected override void OnCreate()
    13.     {
    14.         m_UpdateTagQuery = GetEntityQuery(new EntityQueryDesc
    15.         {
    16.             All = new ComponentType[] { typeof(RunUpdateOnceTag) },
    17.             Options = EntityQueryOptions.IncludePrefab
    18.         });
    19.  
    20.         m_EndSimEcb = World.GetOrCreateSystem<EndSimulationEntityCommandBufferSystem>();
    21.         m_BeginSimEcb = World.GetOrCreateSystem<BeginSimulationEntityCommandBufferSystem>();
    22.  
    23.         RequireForUpdate(m_UpdateTagQuery);
    24.     }
    25.  
    26.     protected override void OnUpdate()
    27.     {
    28.         Debug.Log("running");
    29.  
    30.         var endCommandBuffer = m_EndSimEcb.CreateCommandBuffer();
    31.         var beginCommandBuffer = m_BeginSimEcb.CreateCommandBuffer();
    32.        
    33.         var updateOnce = m_UpdateTagQuery.GetSingletonEntity();
    34.  
    35.         if (m_Prefab == Entity.Null)
    36.         {
    37.             m_Prefab = GetSingleton<PlayerAuthoringComponent>().Prefab;
    38.         }
    39.  
    40.         //Enabled = false;
    41.  
    42.         beginCommandBuffer.Instantiate(m_Prefab);
    43.         endCommandBuffer.RemoveComponent<RunUpdateOnceTag>(updateOnce);
    44.     }
    45. }