Using correct threads to perform actions

As already mentioned in Chapter 4, HUD and Text Display, AndEngine uses different threads for different actions. This makes sense, because the thread that takes care of drawing entities should not be affected by the thread that registers touches.

The two basic threads are the UI (sometimes called main) and update threads. There are certain actions that must be performed in the correct thread. Actions such as showing a toast, dialog, or any other Android view manipulation must be done in the UI thread. Other actions such as manipulating entities must be done in the update thread.

The most common problem with AndEngine is a crash after detaching an entity when a modifier is finished or detaching an entity after performing a touch. There exist a lot of misconceptions among the people in the AndEngine community about this. You will often hear that the problem is caused by detaching the entity in the wrong thread.

However, that is not true because AndEngine takes care of the threads and runs even the touch events, listeners, and update handlers in the update thread. To keep alive the misconception, using the runOnUpdateThread() method actually helps.

Let's see an example of the problem. The basic use case here is to remove the player character as soon as it stops moving after a scene touch. The following code snippet is the relevant part of the populate() method in the GameScene class:

setOnSceneTouchListener(new IOnSceneTouchListener() {

  @Override
  public boolean onSceneTouchEvent(Scene pScene, TouchEvent pSceneTouchEvent) {
    if (pSceneTouchEvent.isActionDown()) {
      Debug.i("Touching scene in Thread: " + Thread.currentThread().getName());
      IEntityModifierListener myEntityModifierListener = new IEntityModifierListener() {

        @Override
        public void onModifierStarted(IModifier<IEntity> pModifier, IEntity pItem) {
        }

        @Override
        public void onModifierFinished(IModifier<IEntity> pModifier, IEntity pItem) {
          Debug.i("Detaching player in Thread: " + Thread.currentThread().getName());
          player.detachSelf();
        }
      };

      player.clearEntityModifiers();
      player.registerEntityModifier(new MoveModifier(1, player.getX(), player.getY(),pSceneTouchEvent.getX(), pSceneTouchEvent.getY(), myEntityModifierListener));
      return true;
    }
    return false;
  }
});

We have added log messages that will print the name of the current thread into LogCat. There are two possible options. The UI thread is identified as the main thread and the update thread is identified as UpdateThread.

Next, we create the myEntityModifierListener class that will listen to the modifier events, and when the modifier finishes, it will try to detach the player sprite from the scene. But, when we run the game now and tap the screen, the player sprite will move there and then the game will crash with an ArrayIndexOutOfBounds exception. Now, the common answer to the question why would be that we are not detaching the player in the update thread. But, the log says otherwise, as shown in the following screenshot:

Using correct threads to perform actions

What is actually happening is that AndEngine is going through all N entities and performing all the required actions. At one point, it is handling the player entity, and it registers that the modifier has finished and calls the method of the modifier listener. There, we try to detach it. This still works but the engine will remove the entity from the list of handled entities, because it's detached and therefore no longer needs handling.

Then, the control is returned to the current iteration of the list of entities. The list has changed in the meantime; it now contains N-1 entities. But the engine still iterates as if there are N entities and this causes the exception when trying to fetch the last element, which is no longer there.

The following code shows a typical example of how to fix this problem. We will add a runOnUpdateThread() method call that will cause the code to execute at a different time.

setOnSceneTouchListener(new IOnSceneTouchListener() {

  @Override
  public boolean onSceneTouchEvent(Scene pScene, TouchEvent pSceneTouchEvent) {
    if (pSceneTouchEvent.isActionDown()) {

      IEntityModifierListener myEntityModifierListener = new IEntityModifierListener() {

        @Override
        public void onModifierStarted(IModifier<IEntity> pModifier, IEntity pItem) {
        }

        @Override
        public void onModifierFinished(IModifier<IEntity> pModifier, IEntity pItem) {

          res.activity.runOnUpdateThread(new Runnable() {
            @Override
            public void run() {
              player.detachSelf();
            }
          });

        }
      };

      player.clearEntityModifiers();
      player.registerEntityModifier(new MoveModifier(1, player.getX(), player.getY(),pSceneTouchEvent.getX(), pSceneTouchEvent.getY(), myEntityModifierListener));
      return true;
    }
    return false;
  }
});

We have added the runOnUpdateThread() method call and it fixes the problem. It might lead us to the assumption that we were using the wrong thread, but in fact this call simply delays the action to the beginning of the next game loop iteration, and therefore, will detach the player before the processing of all the entities starts. But it will happen in the update thread nevertheless.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.22.41.212