Skip to content

EventBus

damios edited this page Jul 28, 2018 · 3 revisions

Damit Ereignisse nicht umständlich über Listener-Interfaces und Anonyme Klassen ausgeliefert werden müssen, verwendet ProjektGG den EventBus von Guava.


Ein Event wird zunächst gepostet, um dann im Anschluss vom Event-Bus an die registrierten Subscriber weitergegeben zu werden. Quelle: greenrobot.org

Die Screen-Klassen registrieren sich dazu automatisch beim aktiv werden als momentaner Subscriber (und "unregistrieren" sich hinterher auch wieder):

@Override
public void show() {
	game.getEventBus().register(this);
}

@Override
public void hide() {
	game.getEventBus().unregister(this);
}

Ein typischer Anwendungsfall des EventBus wäre folgender: Wenn ein Client in der Lobby seine Daten (bspw. sein Wappen) ändert, sendet er eine entsprechende Nachricht (= Message) an den Server. Das ist nötig, da das RMI erst beim eigentlichen Spielstart, also nach dem Beenden der Lobby, eingerichtet wird.

Der Server verteilt die empfangenen PlayerChangedMessages an alle Clienten weiter. Die Clienten registrieren, um die Nachrichten beim Empfang verarbeiten zu können, einen TypeListener, der die Messages nach Typ (= Klasse) weiter verteilt:

client = new Client();
client.start();

TypeListener listener = new TypeListener();
// PLAYER CHANGED
listener.addTypeHandler(PlayerChangedMessage.class, (con, msg) -> {
	// Diese Methode wird auf dem Clienten aufgerufen, wenn eine 
	// PlayerChangedMessage empfangen wird 
	eventBus.post(new PlayerChangedEvent(msg.getId(), msg.getPlayer()));
});
client.addListener(listener);

Wie im obigen Codeschnipsel gesehen werden kann, postet der Client nach dem Empfang der PlayerChangedMessage ein entsprechendes Event (PlayerChangedEvent) an den EventBus, der das Event dann an den momentanen Screen (was in diesem Fall der LobbyScreen ist) weiter gibt:

@Subscribe
public void onPlayerChanged(PlayerChangedEvent event) {
	// Wird aufgerufen, wenn ein PlayerChangedEvent geposted wird; updated
	// das UI sowie die lokale Kopie des jeweiligen Spielers
	players.put(event.getNetworkId(), event.getPlayer());
	updateLobbyUI();
}

Hier ergibt sich allerdings ein Problem: Der TypeListener (s.o.) läuft auf dem Thread des Clienten (der auf ankommende Nachrichten wartet) und das Posten des Events über den EventBus daher ebenfalls. Beim Verarbeiten einiger Events wird jedoch auf den OpenGL-Kontext zugegriffen (bspw. indem etwas am UI verändert wird), was nur auf dem Render-Thread erfolgen darf. Grund dafür ist, dass der OpenGL-Kontext nur in einem Thread vorliegt und dementsprechend auch nur aus diesem auf den Kontext zugegriffen werden darf. Um diese Schwierigkeiten zu umgehen, verwendet ProjektGG einen EventQueueBus, der Events solange queued, bis ein Call aus dem Render-Thread es ihm erlaubt, die Events an die entsprechenden Subscriber zu verteilen.

/**
 * This event bus queues events first and only posts them to the subscribers
 * when {@link #distributeEvents()} is called. This can be useful if events have
 * to get handled in the rendering thread.
 */
public class EventQueueBus extends EventBus {

	/**
	 * Queue of posted events that the current screen should handle. Is taken
	 * care of when {@link #distributeEvents()} is called.
	 */
	private Queue<Object> eventQueue = new ConcurrentLinkedQueue<>();

	/**
	 * After this method is called the {@linkplain #eventQueue queued events}
	 * get posted to their respective subscribers.
	 */
	public void distributeEvents() {
		Object event = eventQueue.poll();
		while (event != null) {
			super.post(event);
			event = eventQueue.poll();
		}
	}

	/**
	 * Posts an event to all registered subscribers. This method will return successfully after the
	 * event has been posted to all subscribers, and regardless of any exceptions thrown by
	 * subscribers.
	 * <p>
	 * If no subscribers have been subscribed for {@code event}'s class, and {@code event} is not
	 * already a {@link DeadEvent}, it will be wrapped in a DeadEvent and reposted.
	 *
	 * The events get queued until {@link #distributeEvents()} is called.
	 *
	 * @param event event to post.
	 */
	public void post(Object event) {
		this.eventQueue.add(event);
	}

}