package com.hypixel.hytale.event;
import com.hypixel.fastutil.shorts.Short2ObjectConcurrentHashMap;
import com.hypixel.hytale.logger.HytaleLogger;
import com.hypixel.hytale.metrics.metric.Metric;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
public abstract class EventBusRegistry<KeyType, EventType extends IBaseEvent<KeyType>, ConsumerMapType extends EventBusRegistry.EventConsumerMap<EventType, ?, ?>> {
@Nonnull
protected static final Object NULL = new Object();
@Nonnull
protected final HytaleLogger logger;
@Nonnull
protected final Class<EventType> eventClass;
@Nonnull
protected final Map<KeyType, ConsumerMapType> map = new ConcurrentHashMap();
@Nonnull
protected final ConsumerMapType global;
@Nonnull
protected final ConsumerMapType unhandled;
protected boolean timeEvents;
protected boolean shutdown;
public EventBusRegistry(@Nonnull HytaleLogger logger, @Nonnull Class<EventType> eventClass, @Nonnull ConsumerMapType global, @Nonnull ConsumerMapType unhandled) {
this.logger = logger;
this.eventClass = eventClass;
this.global = global;
this.unhandled = unhandled;
}
@Nonnull
public Class<EventType> getEventClass() {
return this.eventClass;
}
public boolean isTimeEvents() {
return this.timeEvents;
}
public void setTimeEvents(boolean timeEvents) {
this.timeEvents = timeEvents;
}
public void shutdown() {
this.shutdown = true;
this.map.clear();
}
public boolean isAlive() {
return !this.shutdown;
}
public abstract EventRegistration<KeyType, EventType> register(short var1, @Nullable KeyType var2, @Nonnull Consumer<EventType> var3);
public abstract EventRegistration<KeyType, EventType> registerGlobal(short var1, @Nonnull Consumer<EventType> var2);
public abstract EventRegistration<KeyType, EventType> registerUnhandled(short var1, @Nonnull Consumer<EventType> var2);
public abstract IEventDispatcher<EventType, ?> dispatchFor(KeyType var1);
public abstract static class EventConsumer {
@Nonnull
protected static final AtomicInteger consumerIndex = new AtomicInteger();
protected final int index;
protected final short priority;
@Nonnull
protected final String consumerString;
@Nonnull
protected final Metric timer = new Metric();
public EventConsumer(short priority, @Nonnull String consumerString) {
this.priority = priority;
this.consumerString = consumerString;
this.index = consumerIndex.getAndIncrement();
}
public int getIndex() {
return this.index;
}
public short getPriority() {
return this.priority;
}
@Nonnull
public String getConsumerString() {
return this.consumerString;
}
@Nonnull
public Metric getTimer() {
return this.timer;
}
@Nonnull
public String toString() {
int var10000 = this.index;
return "EventConsumer{index=" + var10000 + ", priority=" + this.priority + ", consumerString='" + this.consumerString + "', timer=" + String.valueOf(this.timer) + "}";
}
}
public abstract static class EventConsumerMap<EventType extends IBaseEvent, ConsumerType extends EventConsumer, ReturnType> implements IEventDispatcher<EventType, ReturnType> {
private static final short[] EMPTY_SHORT_ARRAY = new short[0];
private final AtomicReference<short[]> prioritiesRef;
@Nonnull
private final Short2ObjectConcurrentHashMap<List<ConsumerType>> map;
public EventConsumerMap() {
this.prioritiesRef = new AtomicReference(EMPTY_SHORT_ARRAY);
this.map = new Short2ObjectConcurrentHashMap<List<ConsumerType>>(true, (short)-32768);
}
public boolean isEmpty() {
return this.map.isEmpty();
}
public void add(@Nonnull ConsumerType eventConsumer) {
short priority = eventConsumer.getPriority();
boolean[] wasPriorityAdded = new boolean[]{false};
((List)this.map.computeIfAbsent(priority, (s) -> {
wasPriorityAdded[0] = true;
return new CopyOnWriteArrayList();
})).add(eventConsumer);
if (wasPriorityAdded[0]) {
this.addPriority(priority);
}
}
public boolean remove(@Nonnull ConsumerType consumer) {
short priority = consumer.getPriority();
boolean[] wasRemoved = new boolean[]{false, false};
this.map.computeIfPresent(priority, (key, obj) -> {
wasRemoved[0] = obj.remove(consumer);
if (!obj.isEmpty()) {
return obj;
} else {
wasRemoved[1] = true;
return null;
}
});
if (wasRemoved[1]) {
this.removePriority(priority);
}
return wasRemoved[0];
}
public short[] getPriorities() {
return (short[])this.prioritiesRef.get();
}
@Nullable
public List<ConsumerType> get(short priority) {
return this.map.get(priority);
}
private void addPriority(short priority) {
while(this.map.containsKey(priority)) {
short[] currentPriorities = (short[])this.prioritiesRef.get();
int index = Arrays.binarySearch(currentPriorities, priority);
if (index >= 0) {
return;
}
int insertionPoint = -(index + 1);
int newLength = currentPriorities.length + 1;
short[] newPriorities = new short[newLength];
System.arraycopy(currentPriorities, 0, newPriorities, 0, insertionPoint);
newPriorities[insertionPoint] = priority;
System.arraycopy(currentPriorities, insertionPoint, newPriorities, insertionPoint + 1, currentPriorities.length - insertionPoint);
if (this.prioritiesRef.compareAndSet(currentPriorities, newPriorities)) {
return;
}
}
}
private void removePriority(short priority) {
while(!this.map.containsKey(priority)) {
short[] currentPriorities = (short[])this.prioritiesRef.get();
int index = Arrays.binarySearch(currentPriorities, priority);
if (index < 0) {
return;
}
int newLength = currentPriorities.length - 1;
short[] newPriorities = new short[newLength];
System.arraycopy(currentPriorities, 0, newPriorities, 0, index);
System.arraycopy(currentPriorities, index + 1, newPriorities, index, newLength - index);
if (this.prioritiesRef.compareAndSet(currentPriorities, newPriorities)) {
return;
}
}
}
}
}