Dumb basic question

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

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.

1 Like

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?

What owns the “spawned” bool?

Sorry not 100% sure what you mean, I’m still trying to learn.

It’s being used in the PlayerSpawnSystem with World.GetOrCreateSystem();

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.

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

There’s a trick to run systems once, on the OnCreate method of your system, you use RequireSingletonForUpdate (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 and Destroy this entity, so the System no longer runs on each update, until it’s required to run again

1 Like

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 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 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.

2 Likes

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(); 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

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

World.GetExistingSystem().Enabled = false;

At the end of OnUpdate

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.

2 Likes

cool thanks

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.

1 Like

Not sure what I’m doing wrong, looks like everything is in the right place?

[UpdateInGroup(typeof(LateSimulationSystemGroup))]
public partial class PlayerSpawnSystem : SystemBase
{
    private EntityQuery m_UpdateTagQuery;

    private EndSimulationEntityCommandBufferSystem m_EndSimEcb;

    private Entity m_Prefab;

    protected override void OnCreate()
    {
        m_UpdateTagQuery = GetEntityQuery(ComponentType.ReadWrite<RunUpdateOnceTag>());

        m_EndSimEcb = World.GetOrCreateSystem<EndSimulationEntityCommandBufferSystem>();

        RequireForUpdate(m_UpdateTagQuery);
    }

    protected override void OnUpdate()
    {
        var commandBuffer = m_EndSimEcb.CreateCommandBuffer().AsParallelWriter();
      
        var updateOnce = GetSingleton<RunUpdateOnceTag>();
      
        if (m_Prefab == Entity.Null)
        {
            m_Prefab = GetSingleton<PlayerAuthoringComponent>().Prefab;
            return;
        } 
        EntityManager.Instantiate(m_Prefab);

        //Enabled = false;

        Entities
        .WithAll<RunUpdateOnceTag>()
        .ForEach((Entity entity, int entityInQueryIndex) =>
        {
            commandBuffer.RemoveComponent<RunUpdateOnceTag>(entityInQueryIndex, entity);
        }).ScheduleParallel();

        m_EndSimEcb.AddJobHandleForProducer(Dependency);
    }
}

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.

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?

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.
[UpdateInGroup(typeof(LateSimulationSystemGroup))]
public partial class PlayerSpawnSystem : SystemBase
{
    private EntityQuery m_UpdateTagQuery;

    private EndSimulationEntityCommandBufferSystem m_EndSimEcb;

    private Entity m_Prefab;

    protected override void OnCreate()
    {
        m_UpdateTagQuery = GetEntityQuery(typeof(RunUpdateOnceTag));

        m_EndSimEcb = World.GetOrCreateSystem<EndSimulationEntityCommandBufferSystem>();

        RequireForUpdate(m_UpdateTagQuery);
    }

    protected override void OnUpdate()
    {
        var commandBuffer = m_EndSimEcb.CreateCommandBuffer();
   
        var updateOnce = m_UpdateTagQuery.GetSingletonEntity();

        if (m_Prefab == Entity.Null)
        {
            m_Prefab = GetSingleton<PlayerAuthoringComponent>().Prefab;
        }

        commandBuffer.Instantiate(m_Prefab);
        commandBuffer.RemoveComponent<RunUpdateOnceTag>(updateOnce);
    }
}

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:

m_UpdateTagQuery = GetEntityQuery(new EntityQueryDesc
{
    All = new ComponentType[] { typeof(RunUpdateOnceTag) },
    Options = EntityQueryOptions.IncludePrefab
});

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 :wink:

1 Like

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