Question with ecb.RemoveComponent<T>(entityQuery)

It seems that ecb.RemoveComponent(entityQuery) will also remove component of entities that just added T or will be added T by another ecb which playbacked in same ECBsystem.

e.g.
Get “ECB-A” and “ECB-B” from EntityCommandBufferSystem “ECB-Sys” in order A, B. ECB-A remove component T from Query-Q, and ECB-B add T to Entity-E which match Query-Q. So when ECB-Sys is update, no matter ECB-B is playbacked before or after ECB-A , component T should be added by ECB-B will anyway be removed from Entity-E.

Is this designed?

Yes, as

ecb.AddComponent<T>(EntityQuery);
ecb.RemoveComponent<T>(EntityQuery);

Process EQ on entities at Playback time, as a result, when RemoveCommand is executed it removes component and your EntityQuery used for AddComponent doesn’t see any matching entities for processing, and AddCommand just skips, as EQ with type T empty (the previous command just removed component and entities now doesn’t match that query).

These ECB commands with EQ really awkward and annoying, and sometimes you can fall in that hole not understanding what happens. As we only have a small remark in code\doc for them that they executed at Playback time.

You can use AddComponentForEntityQuery\RemoveComponentForEntityQuery which works on entities set gathered at queue time. (but they’re way slower than pure EQ processing)

1 Like

Yes, but what I found is beyond (more annoying) than your description.
ecbA.RemoveComponent(EntityQuery) execute first
than ecbB.AddComponent(Entity) execute later.
Two ecb playbacked in same buffer system.
Component T will not be added to Entity.
That is saying, the query is not only be performed at playback time, but will also include command that will be playbacked later at the same structrual change point.
That is really astonishing me.:hushed:

You can always add a separate EntityCommandBufferSystem and specify its update order against ECBSystem. (via UpdateBefore / UpdateAfter).

This allows to ensure correct order of operations.

Mmm maybe you misunderstood what I wrote, but this is exactly what I described - first Remove, second Add (read post itself where described order of commands and it’s result, top code block with commands is just example of commands with EQ overload without any specific order). After executing Remove, Add will be skipped, as EQ didn’t match, as Remove have removed component from entities which is used by EQ for Add, as result - no entities in archetype to which Add component. This is exact behaviour described in my previous post, didn’t see what is:

it’s not. EQ commands executes exactly at command playback, which means if you record Remove first in ecb, it will be executed first, if you record Add first it will be executed first.

1 Like

Well, look, I add component to “entity” not using EQ at all. That is queried in creation time, and the entity is valid. Also I removing T with quering T but, adding T should be without quering T. So removing T will not change result of adding query.

Ah, apologize I missed that you used Entity overload in the latest sample :slight_smile:

Well this works as expected, and I don’t see any problems with that, if you record RemoveComponent(EQ) before AddComponent(Entity) it will work as expected and will remove a component from all entities which match EQ and then add a component to specified entities in same ECB playback.

//Components
public struct Comp : IComponentData { }
public struct CompToRemoveAndAdd : IComponentData { }

//Instantiate new entities and get results from previous frame
[AlwaysUpdateSystem]
[UpdateBefore(typeof(SysA))]
public class BootstrapSys : SystemBase
{
    private EntityQuery _query;
    protected override void OnCreate()
    {
        _query = GetEntityQuery(new EntityQueryDesc()
        {
            All = new[]
            {
                ComponentType.ReadOnly<Comp>(),
                ComponentType.ReadOnly<CompToRemoveAndAdd>()
            }
        });
    }
    protected override void OnUpdate()
    {
        if (!_query.IsEmpty)
        {
            Debug.Log($"Match count AFTER playback: {_query.CalculateEntityCount()}");
        }
     
        if (Input.GetKeyDown(KeyCode.Space))
        {
            Debug.Log("Create entities!");
            EntityManager.CreateEntity(EntityManager.CreateArchetype(typeof(Comp), typeof(CompToRemoveAndAdd)), 10);
            Debug.Log($"Match count BEFORE playback: {_query.CalculateEntityCount()}");
        }
    }
}

//Remove component CompToRemoveAndAdd through ECB from sys by EntityQuery
[UpdateBefore(typeof(SysB))]
public class SysA : SystemBase
{
    private EntityQuery _query;
    protected override void OnCreate()
    {
        _query = GetEntityQuery(new EntityQueryDesc()
{
            All = new[]
            {
                ComponentType.ReadOnly<Comp>(),
                ComponentType.ReadOnly<CompToRemoveAndAdd>()
            }
        });
    }

    protected override void OnUpdate()
    {
        Debug.Log("Queue Remove component through EntityQuery!");
        var ecb = World.GetExistingSystem<ECBSys>().CreateCommandBuffer();
        ecb.RemoveComponentForEntityQuery<CompToRemoveAndAdd>(_query);
        World.GetExistingSystem<ECBSys>().AddJobHandleForProducer(Dependency);
    }
}

//Add CompToRemoveAndAdd through ECB from sys by Entity
[UpdateBefore(typeof(ECBSys))]
public class SysB : SystemBase
{
    private EntityQuery _query;
    protected override void OnCreate()
    {
        _query = GetEntityQuery(new EntityQueryDesc()
        {
            All = new[]
    {
                ComponentType.ReadOnly<Comp>(),
                ComponentType.ReadOnly<CompToRemoveAndAdd>()
            }
        });
    }

    protected override void OnUpdate()
    {
        Debug.Log("Queue Add component for entities!");
        var ecb      = World.GetExistingSystem<ECBSys>().CreateCommandBuffer();
        var entities = _query.ToEntityArray(Allocator.Temp);
        for (var i = 0; i < math.min(entities.Length, 5); i++)
        {
            ecb.AddComponent<CompToRemoveAndAdd>(entities[i]);
        }
        entities.Dispose();
        World.GetExistingSystem<ECBSys>().AddJobHandleForProducer(Dependency);
    }
}

//ECB system playback
public class ECBSys : EntityCommandBufferSystem { }

And as you can see Match count AFTER playback shows the correct count of 5 entities to which we add components after we remove it from all entities through EQ.
7724895--969738--upload_2021-12-10_15-40-42.png

2 Likes

It turns out that the EntityCommandBuffer commands which evaluate their EntityQuery targets at playback time are problematic for many reasons; this is one of them. We’ve removed them internally for the next DOTS release; going forward, the EntityCommandBuffer.*ForEntityQuery() variants will be the only way to target queries with ECB commands. These variants evaluate the provided query at command-recording time, and serialize the list of matching entities. This can be slower than the alternative for queries matching a large number of entities, but provides a much more predictable (and less error-prone) experience.

3 Likes

Well, yes you re right. I tried your code, and modfy it to my situation. And I have mistaken the system update order of my code. (server side order is different from client side order). Ecb create first should not prevent later ecb’s effect. Thank you!

Debuging all day long, my head turn to bug now.:eyes:

When will new public version will be availalbe? We’re really looking forward to it.

Q1 2022 is their current target. https://discussions.unity.com/t/864804

1 Like

Why not keep those commands, given that they are faster?
If you then don’t want people in general using those commands, you could maybe rename them to something that feels less accessible for use.

It feels like a loss to throw out a faster option just because some people may not be aware of what it actually does…

2 Likes