package com.github.czyzby.autumn.processor.event; import java.util.Iterator; import com.badlogic.gdx.Gdx; import com.badlogic.gdx.utils.ObjectMap; import com.badlogic.gdx.utils.ObjectSet; import com.badlogic.gdx.utils.reflect.Method; import com.github.czyzby.autumn.annotation.OnEvent; import com.github.czyzby.autumn.context.Context; import com.github.czyzby.autumn.context.ContextDestroyer; import com.github.czyzby.autumn.context.ContextInitializer; import com.github.czyzby.autumn.context.error.ContextInitiationException; import com.github.czyzby.autumn.processor.AbstractAnnotationProcessor; import com.github.czyzby.autumn.processor.event.impl.ReflectionEventListener; import com.github.czyzby.kiwi.util.gdx.collection.lazy.LazyObjectMap; /** Processes events. Can be injected and used to invoke registered listeners with {@link #postEvent(Object)}. If this * processor is not injected into any component, it will mostly likely get garbage-collected after context initiation, * even if there are methods or components annotated with {@link OnEvent}. * * @author MJ */ public class EventDispatcher extends AbstractAnnotationProcessor<OnEvent> { private final ObjectMap<Class<?>, ObjectSet<EventListener<?>>> listeners = LazyObjectMap.newMapOfSets(); private final ObjectMap<Class<?>, ObjectSet<EventListener<?>>> mainThreadListeners = LazyObjectMap.newMapOfSets(); @Override public Class<OnEvent> getSupportedAnnotationType() { return OnEvent.class; } @Override public boolean isSupportingMethods() { return true; } @Override public void processMethod(final Method method, final OnEvent annotation, final Object component, final Context context, final ContextInitializer initializer, final ContextDestroyer contextDestroyer) { addListener(new ReflectionEventListener(method, component, context, annotation.removeAfterInvocation(), annotation.strict()), annotation); } @Override public boolean isSupportingTypes() { return true; } @Override public void processType(final Class<?> type, final OnEvent annotation, final Object component, final Context context, final ContextInitializer initializer, final ContextDestroyer contextDestroyer) { if (component instanceof EventListener<?>) { addListener((EventListener<?>) component, annotation); } else { throw new ContextInitiationException("Unable to register listener. " + component + " is annotated with OnEvent, but does not implement EventListener interface."); } } /** * @param listener will be registered. * @param annotation contains listener's data. */ public void addListener(final EventListener<?> listener, final OnEvent annotation) { addListener(listener, annotation.value(), annotation.forceMainThread()); } /** * @param listener will be registered. Will be invoked as soon as the event is posted. * @param eventType type of handled events. */ public void addListener(final EventListener<?> listener, final Class<?> eventType) { addListener(listener, eventType, false); } /** * @param listener will be registered. * @param eventType type of handled events. * @param forceMainThread if true, listener will be invoked only on main LibGDX thread with * Gdx.app.postRunnable(Runnable). Otherwise the listener is invoked as soon as the event is posted. */ public void addListener(final EventListener<?> listener, final Class<?> eventType, final boolean forceMainThread) { if (forceMainThread) { mainThreadListeners.get(eventType).add(listener); } else { listeners.get(eventType).add(listener); } } /** * @param listener will be removed (if registered). * @param eventType type of the event that the listener is registered to handle. */ public void removeListener(final EventListener<?> listener, final Class<?> eventType) { listeners.get(eventType).remove(listener); mainThreadListeners.get(eventType).remove(listener); } /** * @param eventType all listeners registered to handle this type will be removed. */ public void removeListenersForType(final Class<?> eventType) { listeners.remove(eventType); mainThreadListeners.remove(eventType); } /** * Removes all registered listeners. Use with care. */ public void clearListeners() { listeners.clear(); mainThreadListeners.clear(); } /** @param event will be posted and invoke all listeners registered to its exact class. Nulls are ignored. */ public void postEvent(final Object event) { if (event == null) { return; } if (listeners.containsKey(event.getClass())) { invokeEventListeners(event, listeners.get(event.getClass())); } if (mainThreadListeners.containsKey(event.getClass())) { Gdx.app.postRunnable(new EventRunnable(event, mainThreadListeners.get(event.getClass()))); } } /** @param event was just posted. * @param listeners will be invoked. */ @SuppressWarnings({ "rawtypes", "unchecked" }) // Types are always correct. protected static void invokeEventListeners(final Object event, final ObjectSet<EventListener<?>> listeners) { for (final Iterator<EventListener<?>> iterator = listeners.iterator(); iterator.hasNext();) { final EventListener listener = iterator.next(); if (!listener.processEvent(event)) { iterator.remove(); } } } /** Invokes listeners. * * @author MJ */ public static class EventRunnable implements Runnable { private final Object event; private final ObjectSet<EventListener<?>> listeners; public EventRunnable(final Object event, final ObjectSet<EventListener<?>> listeners) { this.event = event; this.listeners = listeners; } @Override public void run() { invokeEventListeners(event, listeners); } } }