Way to make Engine#removeAllEntities() "atomic" operation
See original GitHub issueHi guys, here is today problem…
Overall situation:
In some moment of my game life-cycle I need to reset world. By “reset world” I mean remove all entities from engine and then recreate game objects from scratch. Simple code may looks so:
void resetWorld() {
engine.removeAllEntities();
initiWorld();
}
void initWorld() {
// Some initial entities like "hero" create here
}
void update(float delta) {
engine.update(delta);
}
And consider all entity systems must survive through every reset and handle entity add/remove events correct. So now assume we have some sophisticate system that handles two types of entities at same time (like layer entities, and drawable entities that bound to layers). And when the system had notified about layer entity has been removed from the engine, system removes all drawable entities of that layer from the engine too.
Here I have to say that I use PooledEngine
and PooledEntities
that I obtain only from engine. So here finally the problem (case when pt.1 occurs outside of engine update):
- Someone calls
resetWorld()
; - All entities became deleted and engine notifies entity listeners about each deletion (
Engine#notifying = true
); - Our render system had been notified about layer entity removed, and now it calls
Engine#removeEntity(Entity)
for each drawable entity (which actually already deleted). - Inside
Engine#removeEntity(Entity)
creates newEntityOperation
of remove type (because engine right now hasnotifying
flag); - Then runtime reaches
initWorld()
and here some new entities create from engine pool and get filled. - Then game loop executes my
update()
method and here engine finally processes all delete operations from pt.3. - Here we’ve got ugly bugs, some random entities get lost and deleted unexpectedly 😦
All that may go even bad if you call resetWorld()
during engine passing update stage. But the problem is I have no ability to check it, Engine#updating
is private and has no accessor. My first question is should we add getter for it (same question about Engine#notifying
)?
Now my temporary solution is to call extra engine.update(0)
before initWorld()
so all pending EntityOperration
s will be gone. Which is not so good solution, because I cannot even imaging what may happen when I call Engine.update()
inside Engine.update()
(engine does not check that case…). And the other similar solution is to call initWorld()
using Gdx.app.postRunnable()
which does pretty the same, but it’s not fits my game architecture, because there are some other things inside update()
that should not be called in such state.
Perhaps the best thing that may help is extra check inside Engine#removeEntity(Entity)
that will just skip entities which is not active in the system right now (Engine#entities
doesn’t contain it)?
Conclusion
I’m trying to find a way to remove all entities during one engine update cycle, avoiding any pending EntityOperation
s after that.
Thanks for read that long story and I’m kindly waiting for your thoughts 😃
Issue Analytics
- State:
- Created 8 years ago
- Comments:27 (27 by maintainers)
Top GitHub Comments
@carlislefox you misunderstand the initial problem. “Atomic” means not just instant removal of entities from the engine, but the approach in which engine should remove all entities right after update and have no pending operations remain for the next updates after it. Anyway the issue was about non update state of engine, where all entities should be removed instantly by design, but in illustrated case they weren’t. Just consider that this is no longer the case, because
Engine
’s code evolved since and handlesremoveAllEntities()
properly now.Just like to weigh in here if I can as I am really opposed to this ticket’s request… in my project I have a removal System that removes any entity with a Remove component, but also has a boolean in it that fires a removeAll(). This system is always the last system to run. I have found working extensively with Ashley that deferring entity removals to the end of a system cycle with a component as a flag is a really smart way to work as aside from the functional consistency you can cancel entity removals mid frame or even cancel the entire removeAll operation before it occurs.
Working in this way, if I have a load of stuff I want to action immediately after the removeAll has occured I have another System who’s job is simply to process runnables that I have submitted at the start of each frame. this means if I want to create a tonne of entities etc (as I am loading a new level for example) straight after I have nuked everything, I will set the removeAll flag on my removal system then post a runnable to my ExecuteNextFrameSystem. This also gives you a thread safe way to submit stuff directly to your Ashley loop which was a big requirement for me.
Other than building these features directly into the core of Ashley and normalising this behaviour I’m not sure how you will get around it directly. Personally I hate the idea of an atomic removeAll() as that nukes the per frame state consistency that makes Ashley so freaking awesome, deferring all of that stuff to the end of the frame is really smart and is really neat to work with, so I would actively fight the idea of atomic removals mid-frame, I think it actually breaks one of Ashley’s fundamental design paradigms.
IMO the removeAll method is a bit dangerous at the moment and should function like remove, in that once called it will register an event to be processed at the end of the current loop. Either that, or it should be a flag on engine that is checked at the end of each frame, that way you could decide mid-frame actually you don’t want to nuke everything afterall and set it back to false should that edge case ever occur.
An example for why my Remove component is so good is server side I have entities that represent players, if they are in combat and disconnect their connection drops but their entity stays in game until they are out of combat… it happens (more frequently than you’d think) that they reconnect the exact same frame their entity is being removed, this means people were logging in and being paired back up to their entity just as it got removed, much bugs ensued. By using the Remove component as they are paired back up with their entity I can check for that component and remove it, thereby cancelling the remove and rescuing the entity. I don’t know if such a case exists for a full removeAll call, but hopefully this demonstrates the robust nature of working in this way.
I think your efforts would be better suited to exploring why you have an atomic mid-frame removal operation in the first place to be honest as this seems to be a square peg round hole situation.
Liam