SystemAPI.Query, Structural Changes & IEnableable. When to use an ECB?

Hi there, I’m trying to understand how SystemAPI.Query works in certain scenarios. Maybe you know the answer and can lend me a hand :folded_hands:

Context:

  • I’m mostly using Systems in its main-thread form (no Jobs)
  • I run queries using SystemAPI.Query with idiomatic foreach

Scenario 1: SystemAPI.Query & structural-changes

When inside the idiomatic foreach we trigger a structural change that will affect the Entities that fall in the Query.

public struct MyComponent : IComponentData {
    public float MyFloat;
}

public struct MyTag : IComponentData, IEnableableComponent {}

// Inside an IService's OnUpdate method
foreach (var (myComponent, entity) in 
         SystemAPI.Query<RefRW<MyComponent>>()
         .WithPresent<MyTag>()
         .WithEntityAccess() {

    myComponent.ValueRW.MyFloat += 1;
    state.EntityManager.RemoveComponent<MyTag>(entity);
}

Questions:

  • What is the impact of the structural change in this context?
  • Does it affect the Query?
  • Should I use an Entity Command Buffer instead in this scenario?

Scenario 2: SystemAPI.Query & Enableable Components

Following a similar structure to scenario 1. But instead of causing a structural change, we modify the state on an IEnableableComponent that is part of the query.

public struct MyComponent : IComponentData {
    public float Value;
}

public struct MyTag : IComponentData, IEnableableComponent {}

// Inside an IService's OnUpdate method
foreach (var (myTag) in 
         SystemAPI.Query<EnabledRefRW<MyTag>>
         .WithAny<MyTag>() {

    myTag.ValueRW = false;
}

Questions:

While changing the value of an IEnableable is not a structural change, we are filtering components with a MyTag and enabled: true

  • It’s OK to change the value of the IEnableable inside the foreach?
  • How does this affect the Query?
  • Should I use an Entity Command Buffer instead in this scenario?
  • Is the .WithAny<MyTag> redundant in this example? I.e. Does EnabledRefRW only selects components that have enabled: true?

Thank you,
And as always, live long and prosper :vulcan_salute:

First, you use no job so structural changes are fine.

Why is that? Because the first structural change will cause a sync point where any pending job must complete before that first structural change can happen. But there is no job to complete, so no point to agonize over it.

A sync point is a point in program execution that waits on the main thread for the completion of all jobs that have been scheduled so far. Sync points limit your ability to use all worker threads available in the job system for a period of time. As such, you should aim to avoid sync points.

Structural changes concepts | Entities | 1.3.9.

(The only way to use worker threads is using jobs.)

Second, ECB is meant to be used in job where you’re forbidden to make structural changes. ECB is to record the commands that cause structural changes. Those commands will be played back at some point in the future by some systems on the main thread.

But you’re on the main thread already, ECB has no use here unless you find a great reason to use it. On the main thread, we should just directly use the EntityManager.

Third, the code inside the foreach loop is already processing the entity in question. The next loop will process the next entity. So even if you make structural changes for the current entity, it won’t affect the query or the next entity.

2 Likes

Thanks Laicasaane.

This makes sense, but I was not sure it was the case. Thank you for clarifying that :pray: