question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Way to make Engine#removeAllEntities() "atomic" operation

See original GitHub issue

Hi 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):

  1. Someone calls resetWorld();
  2. All entities became deleted and engine notifies entity listeners about each deletion (Engine#notifying = true);
  3. 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).
  4. Inside Engine#removeEntity(Entity) creates new EntityOperation of remove type (because engine right now has notifying flag);
  5. Then runtime reaches initWorld() and here some new entities create from engine pool and get filled.
  6. Then game loop executes my update() method and here engine finally processes all delete operations from pt.3.
  7. 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 EntityOperrations 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 EntityOperations after that.

Thanks for read that long story and I’m kindly waiting for your thoughts 😃

Issue Analytics

  • State:open
  • Created 8 years ago
  • Comments:27 (27 by maintainers)

github_iconTop GitHub Comments

1reaction
metaphorecommented, Jun 19, 2017

@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 handles removeAllEntities() properly now.

0reactions
carlislefoxcommented, Jun 19, 2017

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

Read more comments on GitHub >

github_iconTop Results From Across the Web

How to delete all entities for NDB Model in Google App ...
No, but you could easily do this with something like: from google.appengine.ext import ndb ndb.delete_multi( ...
Read more >
Lock-free multithreading with atomic operations
Leveraging atomic operations in multithreading​​ I assume that fetch_and_add() and load() are atomic operations based on the corresponding ...
Read more >
Hibernate Tips: The best way to remove entities from a many ...
You need to clean up all associations if you remove the entity that doesn't own the association. Let's take a quick look at...
Read more >
13.1.1 Atomic Data Definition Statement Support
The storage engine methods involved in a DDL operation do not perform ... DROP TABLE operations are fully atomic if all named tables...
Read more >
Voting and Shuffling to Optimize Atomic Operations
However, there is a way to remove this warp divergence (and a ... distributes it using a __shfl() operation to all other lanes...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found