Adding component is async sometimes
zepplondon opened this issue · 19 comments
So I noticed that when you add a component to an entity that's already in the engine, it adds asynchronously. Which means that it's not retrievable immediately after adding. As I assume it becomes available only after next frame.
My case is: I need to modify a component. But before modifying it, I have to check if it's present in the entity or not. If not, then I'm going to add a new component of needed type and then modify it. But I can't do it, because after i call entity.add(), it's not present in the entity's components array. The problem becomes really bad when you have to call the modifying/checking function multiple times. Every time the "is component present check" returns null on a given entity. So I'd like to know if there's a workaround for this problem. Thanks in advance.
+1 is there a way to either force-add or check if it's scheduled?
It seems to be add immediately if entity is "fresh" (not added yet to engine)
The wiki states that, if you add a component to an entity during engine update()
, it will only actually be added after the engine finishes updating. They will become available during the next frame. This is so we avoid total event mayhem on update.
What strategy do you suggest we adopt, it's not such a simple problem.
Maybe you can change your approach, why do you need to modify a component during the same frame it got added to the entity? Maybe you should be doing that in a system that processes entities with such component, you will only get the update for that entity once it has the component.
why do you need to modify a component during the same frame
In my experience, it's a pretty common use case - mostly when bootstrapping entities. In the current project (ECS, but not ashley) - if the user needs to modify/query an entity further, any pending operations for the entity in question are flushed. It could just as well be added to the current operation batch, but this was the easier solution.
Lifecycle dependent state propagation/inconsistencies are non-obvious to everyone except those intimately familiar with the inner workings.
I'm implementing actions for Ashley. I want to keep the possibility and syntax of adding parallel events to entities just like its done with gdx actor events where you can write things like
actor.addAction(action1);
actor.addaction(action2);
and they will work together as parallel actions.
In my implementation there's one ActionComponent and one AcionSystem. You can add an action to an entity with this kind of syntax
Actions.moveTo(entity, x, y, duration);
The static method adds the logic to the ActionComponent, but before doing it, it checks if the entity already has the ActionComponent and ads one if it hasn't. So turns out if I add 2 actions to an entity in a row, the second one won't know that the previous one already scheduled the ActionComponent creation and instead of adding only logic to the scheduled component, it will schedule a new component creation and overwrite previous one.
One solution we can implement when adding a component to an entity is (pseudocode):
addComponentToEntity(entity, component);
if (engine.isUpdating) {
triggerEvents();
}
else {
scheduleEventsToBeTriggeredAfterUpdate();
}
This would break another use case: remove PhysicsComponent from entity during update, the PhysicsSystem updates later but that entity doesn't actually have the component anymore... BOOM. We don't want people to have to check for null ALL the time.
Again, any ideas to fix this for all use cases? How does artemis do it @junkdog?
I see. I guess the best solution in this case would be if the engine or the entity had the getSceduledToAddComponents() , so I could get the component either from the entity's components list or the "scheduled" list and modify it regardless of it being "actually added".
Oh crap this is harder then I first thought. We can do lot's of outside code to schedule adding in our semi-singleton kind of places, but that has ton of other problems either. The best thing that could be done in Ashley is if getComponent can somehow return component that is yet scheduled. What kind of problem does that have? This component is "somewhere" right? so it should be possible to retrieve it?
What about the opposite? What do we do when trying to retrieve a component that was scheduled for removal? That seems rather inconsistent.
Well if it is scheduled for removal that is no problem, we get it, we modify it, and then it get's removed. (or we do not get it) whichever it is, it is not an issue.
I looked at the codes, it seems rather hard to get the scheduled component though right?
We are currently doing a solution with having maps for this particular components. for the action thing, But I am also very curious how artemis handles this. So let's dig deeper anyway.
Oh right but that's still a problem, sorry. If I remove component, then I will try to add action to it, and then I will lose it.. true.. Dabate is still going here in our room. :D And we are curious on what @junkdog will say.
How does artemis do it @junkdog?
High level, a bit old, but more or less correct: http://i.imgur.com/ARNefV6.png
It's not entirely consistent, but for the most part:
- entity add/remove components are instantaneous, however:
- updating subscription lists (mapping entities to families) happens in-between system processing
- we're essentially postponing internal house-keeping until we need it
- Transmuters (fast entity mutations) flushes the entity from the batch of EntityEdit:s - if it's been modified during the same system frame
And, about the lifecycle dependent state propagation/inconsistencies: when deleting a full entity, it's still possible to retrieve its components when listeners are informed. For the most part, this doesn't cause too much trouble, but it can throw users off guard when components are already deleted due a component delete vs it's there in the case of entity delete (it makes sense on some level however).
(I might expand a bit after work)
updating subscription lists (mapping entities to families) happens in-between system processing
We do process all pending operations in between system updates (see here). So this approach might work. Looking forward some more details.
@junkdog how does it handle when:
- During entity system update
- Add component to entity
- Remove component to entity
- Finish updating entity system
Do we call componentAdded()
and componentRemoved()
on the listeners or would one cancel the other?
Alright, I'm implemented what we discussed and adding a few unit tests... Will submit a PR to get it reviewed by some of you guys and make sure it's nothing crazy. We don't want to keep breaking things.
Do we call componentAdded() and componentRemoved() on the listeners or would one cancel the other?
Entity state changes are governed by EntitySubscriptions, and these are only triggered in-between systems; adding+immediately removing a component, the entity would end up with the same compositionId -> no EntitySubscriptions updated,
Taking a look right now.
I chose to make insertion operations immediate and removal operations deferred in retinazer, to make it possible to retrieve components in entity listeners.
Entity lists are updated in between system updates, after which components are removed (this comes with the side effect of making it cumbersome to replace components). See the flush()
method in Engine
.
What's implemented in #187 should be fine, as long as entity lists do not change while a system is updating.
Thanks guys for taking a look, I think I will merge now.