Anuken/Arc

Events?

SSTentacleSS opened this issue · 0 comments

I just think if we had such events in some cases, imho

image
image

Why immediate?

Sometimes it is necessary that the server first perform at least operations on the code, and then only deal with the event (a bug is not a bug with Groups.player.size)

Events.on(EventType.PlayerLeave, event -> Groups.player.size) // Always +1, same but reverse with PlayerJoin

What does this solution offer

Events.imon(EventType.PlayerLeave, event -> Groups.player.size) // Always equal to the current number of players, as expected

Although this is apparently not relevant now, since this issue has already been resolved differently by changing the code

Once handlers and the ability for the developer to decide which event delete, manage their execution order and be able to remove them from the global context

Since current implementation only limits developers, even too much, that it becomes a choice whether to use reflection to get a private field or not

Events.java implementation

import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.WeakHashMap;

import arc.func.Cons;
import arc.struct.Seq;
import arc.util.Log;
import arc.util.Timer;

@SuppressWarnings("unchecked")
public final class Events {
  private static Map<Class<Object>, Seq<Cons<Object>>> listeners = new HashMap<>();
  private static WeakHashMap<Cons<Object>, Cons<Object>> bindings = new WeakHashMap<>();

  public static <T> void prepend(Class<T> eventClass, Cons<T> listener) {
    if (!listeners.containsKey(eventClass))
      listeners.put((Class<Object>) eventClass, new Seq<>());
    var eventListeners = listeners.get(eventClass);

    eventListeners.add((Cons<Object>) listener);
    listeners.put(
      (Class<Object>) eventClass,
      eventListeners.reverse().add((Cons<Object>) listener).reverse()
    );
  }

  public static <T> void append(Class<T> eventClass, Cons<T> listener) {
    if (!listeners.containsKey(eventClass))
      listeners.put((Class<Object>) eventClass, new Seq<>());
    listeners
      .get(eventClass)
      .add((Cons<Object>) listener);
  }

  public static <T> void on(Class<T> eventClass, Cons<T> listener) {
    append(eventClass, listener);
  }

  public static <T> void once(Class<T> eventClass, Cons<T> listener) {
    append(eventClass, setupOnceListener(eventClass, listener));
  }

  public static <T> void immediateAppend(Class<T> eventClass, Cons<T> listener) {
    append(eventClass, setupImmediateListener(listener));
  }

  public static <T> void immediatePrepend(Class<T> eventClass, Cons<T> listener) {
    prepend(eventClass, setupImmediateListener(listener));
  }

  public static <T> void imon(Class<T> eventClass, Cons<T> listener) {
    immediateAppend(eventClass, listener);
  }

  public static <T> void imonce(Class<T> eventClass, Cons<T> listener) {
    immediateAppend(eventClass, setupOnceListener(eventClass, listener));
  }

  public static <T> void remove() {
    listeners.clear();
  }

  public static <T> void remove(Class<T> eventClass) {
    listeners.remove(eventClass);
  }

  public static <T> void remove(Cons<T> listener) {
    if (bindings.containsKey(listener))
      remove(bindings.get(listener));
    else for (Class<? extends Object> eventClass : listeners.keySet()) {
      if (listeners.get(eventClass).remove((Cons<Object>) listener))
        return;
    }
  }

  public static Map<Class<Object>, Seq<Cons<Object>>> getListeners() {
    return listeners;
  }

  public static <T> Seq<Cons<Object>> getListeners(Class<T> eventClass) {
    return listeners.get(eventClass);
  }

  public static <T> void emit(T eventInstance) {
    Optional
      .ofNullable(listeners.get(eventInstance.getClass()))
      .ifPresent(listeners -> listeners.forEach(listener -> {
        listener.get(eventInstance);
      }));
  }

  public static <T> void emitAsync(T eventInstance) {
    Timer.schedule(() -> emit(eventInstance), 0);
  }

  private static <T> Cons<T> setupImmediateListener(Cons<T> task) {
    Cons<T> immediateTask = eventInstance -> Timer.schedule(() -> task.get(eventInstance), 0);
    
    bindings.put((Cons<Object>) task, (Cons<Object>) immediateTask);
    return immediateTask;
  }

  private static <T> Cons<T> setupOnceListener(Class<T> event, Cons<T> task) {
    Cons<T> onceTask = eventInstance -> {
      task.get(eventInstance);

      Log.info(bindings.get(task));
      remove(task);
    };

    bindings.put((Cons<Object>) task, (Cons<Object>) onceTask);
    return onceTask;
  }

}