Extending AndEngine’s GenericPool to Safely Add and Remove Bodies from Box2D PhysicsWorld

Anyone delving into Android game development with Box2D physics will at some time struggled with seemingly random crashes when adding or removing bodies from the physics world. The instinctive thing to do is to destroy the object when some type of event has occurred, such as a touch event or collision in the physics simulation, however simply removing entities from the simulation can cause the simulation to crumble.

In this article I’ll go through my preferred method of safely adding and removing objects from the physics simulation with the minimum performance hit.

In this example we’re attempting to remove bullets from the scene once they’ve collided with something. To achieve this we’ll need to register a ContactListener with the PhyisicsWorld.

physicsWorld.setContactListener(new ContactListener(){........})

The ContactListener interface specifies four methods…

private ContactListener getContactListener(){      
    return new ContactListener() {
        @Override
        public void beginContact(Contact contact) {
        }
        @Override
        public void endContact(Contact contact) {
        }
        @Override
        public void preSolve(Contact contact, Manifold oldManifold) {
        }
        @Override
        public void postSolve(Contact contact, ContactImpulse impulse) {
        }
    };
}
  • beginContact(Contact contact); – Called when two fixtures begin to touch
  • endContact(Contact contact); – Called when two fixtures cease to touch
  • preSolve(Contact contact, Manifold oldManifold); – Called after a contact is updated and before it goes to the solver.
  • postSolve(Contact contact, ContactImpulse impulse); – Called after the solver is finished, useful for inspecting impulses

For the purposes of this tutorial we’re going to use the preSolve() method as there we could disable the contact or modify the contact manifold if required.

@Override
public void preSolve(Contact contact, Manifold oldManifold) {
    Object[] userDataArray = {
        contact.getFixtureA().getBody().getUserData(),
        contact.getFixtureB().getBody().getUserData()
    };     
}

The contact parameter gives us access to the fixtures involved in the collision via it’s getFixtureA() & getFixtureB() methods. The fixtures in turn give us access to their bodies, to which I’ve attached references back to the object to which the body belongs via the body’s userData property. For example the userData property can be cast back to the original object e.g. a bullet or an enemy.

Next we inspect the userData objects types to see if we need to handle the the collision. We’re handling collisions between enemies and bullets here, so first we check if both of our userData objects were explicitly set in this line:
if (userDataArray[0] != null && userDataArray[1] != null) {

We’ll then check if we have a collision between an enemy and a bullet by inspecting the object’s types. If we have a bullet and an enemy we’ll set the enemy’s shotBy property to a reference to the bullet’s weapon. When the enemy is updated, it’s health will be reduced the applicable amount and the shotBy reference cleared.

As we don’t know which is our Enemy and which is our Bullet, we need to mirror the check for the bullet and the enemy for the other array position.

@Override
public void preSolve(Contact contact, Manifold oldManifold) {
    Object[] userDataArray = {
        contact.getFixtureA().getBody().getUserData(),
        contact.getFixtureB().getBody().getUserData()
    };

    if (userDataArray[0] != null && userDataArray[1] != null) {
        if (userDataArray[0] instanceof Bullet && userDataArray[1] instanceof Enemy) {
            ((Enemy) userDataArray[1]).setShotBy(((Bullet) userDataArray[0]).getWeapon());
        } else if (userDataArray[1] instanceof Bullet && userDataArray[0] instanceof Enemy) {
            ((Enemy) userDataArray[0]).setShotBy(((Bullet) userDataArray[1]).getWeapon());
        }
    }
}

Regardless of what a bullet has hit, we want it to be removed from the Scene and PhysicsWorld, so in case both fixtures involved in the collision were bullets we’ll iterate over the userDataArray and remove them all:

The Wrong Way

// Remove all bullets that have collided with anything, incl. other bullets
for (Object userData : userDataArray) {
    if(userData instanceof Bullet){            
        Bullet bullet = (Bullet)userData;
        physicsWorld.unregisterPhysicsConnector(physicsWorld.getPhysicsConnectorManager().findPhysicsConnectorByShape(bullet.getSprite()));            
        physicsWorld.destroyBody(bullet.body);
        bullet.getSprite().detachSelf();
        Vector2Pool.recycle(bullet.getVector());
    }
}

Intuitively it seems right to remove the objects when the collision happens, but the solution above will cause the physics simulation to crash horribly. As objects in the physics simulation interact with several other objects all the time removing the bullet at this point, before all of it’s possible interactions have been calculated, could cause the simulation to fail. If, for example another object is also colliding with this bullet in this step, but that collision will only be managed after this one, should the bullet be removed now we’ll have some absent references later.

The Right Way

The solution of course is to flag objects for deletion and remove them before/after the physics simulation has run in any given step in your main game loop. Solutions I’ve seen range from simple array’s filled with objects flagged for deletion to EntityManager classes containing Lists of objects to add to or remove from the game scene. At the appropriate time, an UpdateHandler loops through these lists and adds/removes the objects.

However, given the performance benefits of recycling items using object pools, I’d recommend extending AndEngine’s GenericPool<T> instead, to manage your game entities removal from the scene and physics simulation and recycle them for reuse.

public abstract class EntityPool extends GenericPool<PoolEntity> implements IUpdateHandler {
    protected BaseGameActivity activity;
    protected PhysicsWorld physicsWorld;
    protected BitmapTextureAtlas textureAtlas;
    protected ITextureRegion textureRegion;

    private Set<PoolEntity> entitiesToAddToWorld = Collections.synchronizedSet(new HashSet<PoolEntity>());
    private Set<PoolEntity> entitiesToRemoveFromWorld = Collections.synchronizedSet(new HashSet<PoolEntity>());
}

We start extending GenericPool<T> by declaring an abstract class, EntityPool with the “PoolEntity” type (entities that belong to pools). The EntityPool also implements IUpdateHandler so the pool can be registered as an update handler to create/recycle items on the update thread (the correct place to add/remove objects from the physics simulation).

The EntityPool has references to the BaseGameActivity and PhysicsWorld to which it’s entities will belong. It also has any resources it needs to create new entities incl. textures and sounds which can be added as required in sub-classes like BulletPools, ZombiePools, etc… It’s useful to store entities’ resources here as the pool can be constructed with resources up front without knowing how many instances the pool will contain or when it will become necessary to create more.

onCreateTextureAtlas() and onCreateTextureRegion() are abstract methods that I use as a standard, which must be overridden and to provide the appropriate textures.

I’ve introduced two HashSets entitiesToAddToWorld and entitiesToRemoveFromWorld to store lists of unique entities that should be added to or removed from the world on its next update.

public EntityPool(BaseGameActivity activity) throws IllegalArgumentException {
    if (activity == null) throw new IllegalArgumentException("BaseGameActivity is null");
    this.activity = activity;
    this.setTextureAtlas(onCreateTextureAtlas());
    assert getTextureAtlas() instanceof BitmapTextureAtlas;
   
    this.onTextureAtlasCreated();

    this.setTextureRegion(onCreateTextureRegion());
    assert getTextureRegion() instanceof ITextureRegion;
}

The pool delegates responsibility for an entity’s construction and destruction to the entity itself, but PoolEntity objects must refer back to their pool to get their resources.

The following are the methods for adding / removing entities to / from the simulation.

public PoolEntity addToWorld() {
    PoolEntity poolEntity = this.obtainPoolItem();
    this.entitiesToAddToWorld.add(poolEntity);
    return poolEntity;
}

public void addToWorld(PoolEntity poolEntity) {
    this.entitiesToAddToWorld.add(poolEntity);
}

public void removeFromWorld(PoolEntity poolEntity) {
    this.entitiesToRemoveFromWorld.add(poolEntity);
}

Passing objects to these methods simply stores them in the applicable HashSet.

When the onUpdate() method is called, the pool loops through the HashSets adding / removing & recycling the entities.

@Override
public void onUpdate(float pSecondsElapsed) {
    recycleWorldItems();
    addItemsToWorld();
}

The EntityPool does not manage the actual action of adding or removing the Entity but instead delegates it to that specific PoolEntity subclass by calling it’s implementation of PoolEntity.onAddToWorld() or PoolEntity.onRecycle()

private void addItemsToWorld() {
    synchronized (entitiesToAddToWorld) {
        for (PoolEntity poolEntity : entitiesToAddToWorld) {
            poolEntity.onAddToWorld(physicsWorld);
        }
        try {
            entitiesToAddToWorld.removeAll(entitiesToAddToWorld);
        } catch (UnsupportedOperationException e) {
            Log.e(getClass().getSimpleName(), e.getMessage(), e);
        } catch (NullPointerException e) {
            Log.e(getClass().getSimpleName(), e.getMessage(), e);
        }
    }
}

private void recycleWorldItems() {
    synchronized (entitiesToRemoveFromWorld) {             
        for (PoolEntity poolEntity : entitiesToRemoveFromWorld) {
            recyclePoolItem(poolEntity);
        }
        try {
            entitiesToRemoveFromWorld.removeAll(entitiesToRemoveFromWorld);
        } catch (UnsupportedOperationException e) {
            Log.e(getClass().getSimpleName(), e.getMessage(), e);
        } catch (NullPointerException e) {
            Log.e(getClass().getSimpleName(), e.getMessage(), e);
        }
    }
}

@Override
protected void onHandleRecycleItem(final PoolEntity poolEntity) {
    poolEntity.onRecycle();
}

Once all entities in a HashSet have been processes the Set is emptied and we start over.

Consider the following BulletPool class as an example of an EntityPool implementation. All the logic required for managing the pool’s PoolEntities is already handled by the EntityPool abstract class.

The only requirement is to override the GenericPool method, onAllocatePoolItem() which is responsible for instantiating new PoolEntity sub classes when there aren’t enough (or any) available in the pool.

public class BulletPool extends EntityPool {

    protected Weapon weapon;
   
    public BulletPool(GameScene gameScene, BulletResourceManager resourceManager, Weapon weapon) throws IllegalArgumentException {
        super(gameScene, resourceManager);
        this.resourceManager = resourceManager;
        if (weapon == null) {
            throw new IllegalArgumentException("Weapon must not be NULL");
        }
        this.weapon = weapon;
    }
   
    /**
     * @return the weapon
     */

    public Weapon getWeapon() {
        return weapon;
    }

    public Sound getFireWeaponSound() {
        return ((BulletResourceManager)getResourceManager()).getFireWeaponSound();     
    }

    /**
     * Called when a Bullet is required but there isn't one in the pool
     */

    @Override
    protected Bullet onAllocatePoolItem()
            throws IllegalStateException {
        if (gameScene.getPhysicsWorld() == null)
            throw new IllegalStateException(
                    "PhysicsWorld has not been initialized");

        Bullet pBullet = new Bullet(this);
        pBullet.onCreate();

        Log.d(this.getClass().getSimpleName(), "New Bullet Constructed ");

        return pBullet;
    }
}

Each PoolEntity sub class is responsible for implementing PoolEntity.onAddToWorld() and PoolEntity.onRemoveFromWorld() in their own way. These methods are called by the EntityPool to which they belong on the update thread.

As an example, a Bullet could add and remove itself from the PhysicsWorld as follows:

public class Bullet extends PoolEntity {

    protected Weapon weapon;
    protected Vector2 vector;

    Bullet(BulletPool bulletPool) {
        super(bulletPool);
        this.weapon = bulletPool.getWeapon();

    }

    /**
     * Creates Sprite
     */

    @Override
    public synchronized void onCreate() {

        if (getActivity() == null)
            throw new NullPointerException("Activity cannot be null");

        VertexBufferObjectManager vertexBufferObjectManager = getActivity().getVertexBufferObjectManager();

        ITextureRegion iTextureRegion = getEntityPool().getTextureRegion();
        if (iTextureRegion == null)
            throw new NullPointerException("BulletPool TextureRegion in NULL");

        this.sprite = new Sprite(getX(), getY(), iTextureRegion, vertexBufferObjectManager);
        this.sprite.setUserData(getResourceManager().getTextureAtlas());       
    }

    @Override
    public synchronized void onDestroy() {
        try {
            getPhysicsWorld().unregisterPhysicsConnector(getPhysicsConnector());
            getPhysicsWorld().destroyBody(getBody());
            getSprite().detachSelf();
            Vector2Pool.recycle(Bullet.this.vector);
        } catch (Exception e) {
            Log.e("Bullet", "Destroy Exception", e);
        } catch (Error e) {
            Log.e("Bullet", "Destroy Error", e);
        }

    }

    public synchronized Weapon getWeapon() {
        return weapon;
    }

    public synchronized Vector2 getVector() {
        return vector;
    }

    protected void playFireWeaponSound() {
        try {
            Sound sound = ((BulletPool) getEntityPool()).getFireWeaponSound();
            sound.play();
        } catch (SoundReleasedException e) {
            Log.e(getClass().getSimpleName(), e.getMessage(), e);
        }
    }

    @Override
    public synchronized void onAddToWorld() {

        playFireWeaponSound();

        /*
         * At this point getX & getY refer to the direction of travel required
         */

        this.vector = getLinearVelocityToPoint(0, 0, getX(), getY(), weapon.getBulletVelocity());

        setX(weapon.getBarrelX());
        setY(weapon.getBarrelY());

        // Vector2 position = Vector2Pool.obtain(getX(), getY());

        getSprite().setX(getX());
        getSprite().setY(getY());

        getSprite().setVisible(true);
        getSprite().setIgnoreUpdate(false);

        this.body = PhysicsFactory.createCircleBody(getPhysicsWorld(), getX(), getY(), 3, BodyType.DynamicBody, getFixtureDef());
        this.body.setBullet(true);
        this.body.setUserData(Bullet.this);

        Bullet.this.setPhysicsConnector(new PhysicsConnector(this.sprite, this.body, true, false));
        getPhysicsWorld().registerPhysicsConnector(getPhysicsConnector());

        if (!this.sprite.hasParent()) {
            getGameScene().attachChild(this.sprite);
        } else {
            Log.e(this.getClass().getSimpleName(), "Sprite already has a parent");
        }
        this.addedToWorld = true;

        Bullet.this.body.setLinearVelocity(this.vector);
    }

    @Override
    public void onRemoveFromWorld() {
        Log.d(getClass().getSimpleName(), "onRemoveFromWorld()");
        try {
            getBody().setActive(false);

            getBody().setAwake(false);

            getPhysicsWorld().unregisterPhysicsConnector(getPhysicsConnector());

            getPhysicsWorld().destroyBody(getBody());

            sprite.detachChildren();

            getGameScene().detachChild(sprite);

            /* Recycle */
            Vector2Pool.recycle(vector);

            addedToWorld = false;

        } catch (Exception e) {
            Log.e("Bullet", "Recycle Exception", e);
        } catch (Error e) {
            Log.e("Bullet", "Recycle Error", e);
        }
    }

    @Override
    public synchronized void onRecycle() {
        this.onRemoveFromWorld();
    }
}