package net.minecraft.world.level.timers; import com.google.common.collect.HashBasedTable; import com.google.common.collect.Table; import com.google.common.primitives.UnsignedLong; import com.mojang.logging.LogUtils; import com.mojang.serialization.Dynamic; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.PriorityQueue; import java.util.Queue; import java.util.Set; import java.util.stream.Stream; import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.ListTag; import net.minecraft.nbt.NbtOps; import net.minecraft.nbt.Tag; import org.slf4j.Logger; public class TimerQueue { private static final Logger LOGGER = LogUtils.getLogger(); private static final String CALLBACK_DATA_TAG = "Callback"; private static final String TIMER_NAME_TAG = "Name"; private static final String TIMER_TRIGGER_TIME_TAG = "TriggerTime"; private final TimerCallbacks callbacksRegistry; private final Queue> queue = new PriorityQueue(createComparator()); private UnsignedLong sequentialId = UnsignedLong.ZERO; private final Table> events = HashBasedTable.create(); private static Comparator> createComparator() { return Comparator.comparingLong(event -> event.triggerTime).thenComparing(event -> event.sequentialId); } public TimerQueue(TimerCallbacks callbacksRegistry, Stream> scheduledEventsDynamic) { this(callbacksRegistry); this.queue.clear(); this.events.clear(); this.sequentialId = UnsignedLong.ZERO; scheduledEventsDynamic.forEach(dynamic -> { Tag tag = dynamic.convert(NbtOps.INSTANCE).getValue(); if (tag instanceof CompoundTag compoundTag) { this.loadEvent(compoundTag); } else { LOGGER.warn("Invalid format of events: {}", tag); } }); } public TimerQueue(TimerCallbacks callbacksRegistry) { this.callbacksRegistry = callbacksRegistry; } public void tick(T obj, long gameTime) { while (true) { TimerQueue.Event event = (TimerQueue.Event)this.queue.peek(); if (event == null || event.triggerTime > gameTime) { return; } this.queue.remove(); this.events.remove(event.id, gameTime); event.callback.handle(obj, this, gameTime); } } public void schedule(String id, long triggerTime, TimerCallback callback) { if (!this.events.contains(id, triggerTime)) { this.sequentialId = this.sequentialId.plus(UnsignedLong.ONE); TimerQueue.Event event = new TimerQueue.Event<>(triggerTime, this.sequentialId, id, callback); this.events.put(id, triggerTime, event); this.queue.add(event); } } public int remove(String eventId) { Collection> collection = this.events.row(eventId).values(); collection.forEach(this.queue::remove); int i = collection.size(); collection.clear(); return i; } public Set getEventsIds() { return Collections.unmodifiableSet(this.events.rowKeySet()); } private void loadEvent(CompoundTag tag) { TimerCallback timerCallback = (TimerCallback)tag.read("Callback", this.callbacksRegistry.codec()).orElse(null); if (timerCallback != null) { String string = tag.getStringOr("Name", ""); long l = tag.getLongOr("TriggerTime", 0L); this.schedule(string, l, timerCallback); } } private CompoundTag storeEvent(TimerQueue.Event event) { CompoundTag compoundTag = new CompoundTag(); compoundTag.putString("Name", event.id); compoundTag.putLong("TriggerTime", event.triggerTime); compoundTag.store("Callback", this.callbacksRegistry.codec(), event.callback); return compoundTag; } public ListTag store() { ListTag listTag = new ListTag(); this.queue.stream().sorted(createComparator()).map(this::storeEvent).forEach(listTag::add); return listTag; } public static class Event { public final long triggerTime; public final UnsignedLong sequentialId; public final String id; public final TimerCallback callback; Event(long triggerTime, UnsignedLong sequentialId, String id, TimerCallback callback) { this.triggerTime = triggerTime; this.sequentialId = sequentialId; this.id = id; this.callback = callback; } } }