junkdog / artemis-odb

A continuation of the popular Artemis ECS framework
BSD 2-Clause "Simplified" License
776 stars 111 forks source link

Removing a component does not notify systems about the change #504

Closed manabreak closed 7 years ago

manabreak commented 7 years ago

I have a strong feeling this is just an error on my behalf, but I have been hunting this down for quite some time and can't figure out what's going wrong here.

I'm working on a small editor that uses Artemis as its core. When an "Add" button is clicked on the UI, a new entity is created along with a default set of components:

private ComponentMapper<SpriteReference> spriteReferences;

void addClicked() {
    World world = editorGameContainer.getWorld();
    int id = world.create();
    SpriteReference ref = spriteReferences.create(id);
    ref.region = "white";

    Transform t = transforms.create(id);
    t.position.set(camera.position.x - 0.5f, camera.position.y - 0.5f, 0f);
}

This works fine and dandy, and the entity appears. Now, clicking on the sprite of the entity selects the entity and populates the UI with all the components of the entity. This works just fine. Now, each component entry on the UI has a "remove" button, which should remove the component.

Class<? extends Component> c = component.getClass();
ImageButton btnRemove = ...;
btnRemove.addListener(new ChangeListener() {
    @Override
    public void changed(ChangeEvent event, Actor actor) {
        ComponentRef cref = c.getAnnotation(ComponentRef.class);
        if (cref != null) world.edit(id).remove(cref.value());
        world.edit(id).remove(c);
    }
});

This seems to "work", but it's not consistent. If I remove the Transform component, the entity disappears and the interested systems are notified via removed(int id) method. However, with the SpriteReference component, it's a tad more complicated.

The SpriteReference component is basically just a serialization-friendly component that tells what kind of sprite the actual SpriteComponent should use. The two components are like this, first the SpriteReference:

@ComponentRef(SpriteComponent.class)
@PooledWeaver
public class SpriteReference extends Component {
    public String region = "";
}

And the SpriteComponent:

@Transient
@NonEditable
@PooledWeaver
public class SpriteComponent extends Component {
    public Sprite sprite;
}

The relationship is managed by AssetReferenceManager:

private ComponentMapper<SpriteReference> references;
private ComponentMapper<SpriteComponent> sprites;

public AssetReferenceManager() {
    super(Aspect.all(SpriteReference.class).exclude(SpriteComponent.class));
}

@Override
protected void inserted(int id) {
    SpriteReference ref = references.get(id);
    SpriteComponent sprite = sprites.create(id);
    sprite.sprite = new Sprite(Res.findRegion(ref.region));
}

This all works when the entity is created, but for some reason, the other systems (e.g. SpriteSystem with Aspect.all(SpriteComponent.class, Transform.class) subscription) are not notified when the SpriteReference and SpriteComponent components are removed - the SpriteSystem keeps drawing the sprite even though the components are removed.

On the other hand, if I remove the Transform component, the SpriteSystem gets notified and won't draw the sprite anymore. Also, if I regenerate the UI list of components, the SpriteReference and SpriteComponent components are removed altogether. It's just weird that the SpriteSystem still draws the sprites.

I'm not sure if it's of any relevance, but here's the SpriteSystem that still keeps drawing (with the relevant code left and otherwise pruned):

private ComponentMapper<SpriteComponent> sprites;
private ComponentMapper<Transform> transforms;

public SpriteSystem() {
    super(Aspect.all(SpriteComponent.class, Transform.class));
}

@Override
protected void begin() {
    batch.setProjectionMatrix(camera.combined);
    batch.begin();
}

@Override
protected void process(int id) {
    SpriteComponent c = sprites.get(id);
    Sprite sprite = c.sprite;
    Transform t = transforms.get(id);
    if (sprite != null) {
        sprite.draw(batch);
    }
}

@Override
protected void end() {
    batch.end();
}

I'm really at my wit's end here. Any ideas?

ghroot commented 6 years ago

Why was this closed? I feel it is very similar to my comment on https://github.com/junkdog/artemis-odb/issues/498