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, ConsumerMapType extends EventBusRegistry.EventConsumerMap> { @Nonnull protected static final Object NULL = new Object(); @Nonnull protected final HytaleLogger logger; @Nonnull protected final Class eventClass; @Nonnull protected final Map 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 eventClass, @Nonnull ConsumerMapType global, @Nonnull ConsumerMapType unhandled ) { this.logger = logger; this.eventClass = eventClass; this.global = global; this.unhandled = unhandled; } @Nonnull public Class 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 register(short var1, @Nullable KeyType var2, @Nonnull Consumer var3); public abstract EventRegistration registerGlobal(short var1, @Nonnull Consumer var2); public abstract EventRegistration registerUnhandled(short var1, @Nonnull Consumer var2); public abstract IEventDispatcher 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 @Override public String toString() { return "EventConsumer{index=" + this.index + ", priority=" + this.priority + ", consumerString='" + this.consumerString + "', timer=" + this.timer + "}"; } } public abstract static class EventConsumerMap implements IEventDispatcher { private static final short[] EMPTY_SHORT_ARRAY = new short[0]; private final AtomicReference prioritiesRef = new AtomicReference<>(EMPTY_SHORT_ARRAY); @Nonnull private final Short2ObjectConcurrentHashMap> map = new Short2ObjectConcurrentHashMap<>(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}; 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 this.prioritiesRef.get(); } @Nullable public List get(short priority) { return this.map.get(priority); } private void addPriority(short priority) { while (this.map.containsKey(priority)) { short[] currentPriorities = 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 = 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; } } } } }