290 lines
11 KiB
Java
290 lines
11 KiB
Java
package net.minecraft.world.ticks;
|
|
|
|
import it.unimi.dsi.fastutil.longs.Long2LongMap;
|
|
import it.unimi.dsi.fastutil.longs.Long2LongMaps;
|
|
import it.unimi.dsi.fastutil.longs.Long2LongOpenHashMap;
|
|
import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
|
|
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
|
|
import it.unimi.dsi.fastutil.longs.Long2LongMap.Entry;
|
|
import it.unimi.dsi.fastutil.objects.ObjectIterator;
|
|
import it.unimi.dsi.fastutil.objects.ObjectOpenCustomHashSet;
|
|
import java.util.ArrayDeque;
|
|
import java.util.ArrayList;
|
|
import java.util.Comparator;
|
|
import java.util.List;
|
|
import java.util.LongSummaryStatistics;
|
|
import java.util.PriorityQueue;
|
|
import java.util.Queue;
|
|
import java.util.Set;
|
|
import java.util.function.BiConsumer;
|
|
import java.util.function.LongPredicate;
|
|
import java.util.function.Predicate;
|
|
import net.minecraft.Util;
|
|
import net.minecraft.core.BlockPos;
|
|
import net.minecraft.core.SectionPos;
|
|
import net.minecraft.core.Vec3i;
|
|
import net.minecraft.util.profiling.Profiler;
|
|
import net.minecraft.util.profiling.ProfilerFiller;
|
|
import net.minecraft.world.level.ChunkPos;
|
|
import net.minecraft.world.level.levelgen.structure.BoundingBox;
|
|
|
|
public class LevelTicks<T> implements LevelTickAccess<T> {
|
|
private static final Comparator<LevelChunkTicks<?>> CONTAINER_DRAIN_ORDER = (levelChunkTicks, levelChunkTicks2) -> ScheduledTick.INTRA_TICK_DRAIN_ORDER
|
|
.compare(levelChunkTicks.peek(), levelChunkTicks2.peek());
|
|
private final LongPredicate tickCheck;
|
|
private final Long2ObjectMap<LevelChunkTicks<T>> allContainers = new Long2ObjectOpenHashMap<>();
|
|
private final Long2LongMap nextTickForContainer = Util.make(
|
|
new Long2LongOpenHashMap(), long2LongOpenHashMap -> long2LongOpenHashMap.defaultReturnValue(Long.MAX_VALUE)
|
|
);
|
|
private final Queue<LevelChunkTicks<T>> containersToTick = new PriorityQueue(CONTAINER_DRAIN_ORDER);
|
|
private final Queue<ScheduledTick<T>> toRunThisTick = new ArrayDeque();
|
|
private final List<ScheduledTick<T>> alreadyRunThisTick = new ArrayList();
|
|
private final Set<ScheduledTick<?>> toRunThisTickSet = new ObjectOpenCustomHashSet<>(ScheduledTick.UNIQUE_TICK_HASH);
|
|
private final BiConsumer<LevelChunkTicks<T>, ScheduledTick<T>> chunkScheduleUpdater = (levelChunkTicks, scheduledTick) -> {
|
|
if (scheduledTick.equals(levelChunkTicks.peek())) {
|
|
this.updateContainerScheduling(scheduledTick);
|
|
}
|
|
};
|
|
|
|
public LevelTicks(LongPredicate tickCheck) {
|
|
this.tickCheck = tickCheck;
|
|
}
|
|
|
|
public void addContainer(ChunkPos chunkPos, LevelChunkTicks<T> chunkTicks) {
|
|
long l = chunkPos.toLong();
|
|
this.allContainers.put(l, chunkTicks);
|
|
ScheduledTick<T> scheduledTick = chunkTicks.peek();
|
|
if (scheduledTick != null) {
|
|
this.nextTickForContainer.put(l, scheduledTick.triggerTick());
|
|
}
|
|
|
|
chunkTicks.setOnTickAdded(this.chunkScheduleUpdater);
|
|
}
|
|
|
|
public void removeContainer(ChunkPos chunkPos) {
|
|
long l = chunkPos.toLong();
|
|
LevelChunkTicks<T> levelChunkTicks = this.allContainers.remove(l);
|
|
this.nextTickForContainer.remove(l);
|
|
if (levelChunkTicks != null) {
|
|
levelChunkTicks.setOnTickAdded(null);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void schedule(ScheduledTick<T> tick) {
|
|
long l = ChunkPos.asLong(tick.pos());
|
|
LevelChunkTicks<T> levelChunkTicks = this.allContainers.get(l);
|
|
if (levelChunkTicks == null) {
|
|
Util.logAndPauseIfInIde("Trying to schedule tick in not loaded position " + tick.pos());
|
|
} else {
|
|
levelChunkTicks.schedule(tick);
|
|
}
|
|
}
|
|
|
|
public void tick(long gameTime, int maxAllowedTicks, BiConsumer<BlockPos, T> ticker) {
|
|
ProfilerFiller profilerFiller = Profiler.get();
|
|
profilerFiller.push("collect");
|
|
this.collectTicks(gameTime, maxAllowedTicks, profilerFiller);
|
|
profilerFiller.popPush("run");
|
|
profilerFiller.incrementCounter("ticksToRun", this.toRunThisTick.size());
|
|
this.runCollectedTicks(ticker);
|
|
profilerFiller.popPush("cleanup");
|
|
this.cleanupAfterTick();
|
|
profilerFiller.pop();
|
|
}
|
|
|
|
private void collectTicks(long gameTime, int maxAllowedTicks, ProfilerFiller profiler) {
|
|
this.sortContainersToTick(gameTime);
|
|
profiler.incrementCounter("containersToTick", this.containersToTick.size());
|
|
this.drainContainers(gameTime, maxAllowedTicks);
|
|
this.rescheduleLeftoverContainers();
|
|
}
|
|
|
|
private void sortContainersToTick(long gameTime) {
|
|
ObjectIterator<Entry> objectIterator = Long2LongMaps.fastIterator(this.nextTickForContainer);
|
|
|
|
while (objectIterator.hasNext()) {
|
|
Entry entry = (Entry)objectIterator.next();
|
|
long l = entry.getLongKey();
|
|
long m = entry.getLongValue();
|
|
if (m <= gameTime) {
|
|
LevelChunkTicks<T> levelChunkTicks = this.allContainers.get(l);
|
|
if (levelChunkTicks == null) {
|
|
objectIterator.remove();
|
|
} else {
|
|
ScheduledTick<T> scheduledTick = levelChunkTicks.peek();
|
|
if (scheduledTick == null) {
|
|
objectIterator.remove();
|
|
} else if (scheduledTick.triggerTick() > gameTime) {
|
|
entry.setValue(scheduledTick.triggerTick());
|
|
} else if (this.tickCheck.test(l)) {
|
|
objectIterator.remove();
|
|
this.containersToTick.add(levelChunkTicks);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private void drainContainers(long gameTime, int maxAllowedTicks) {
|
|
LevelChunkTicks<T> levelChunkTicks;
|
|
while (this.canScheduleMoreTicks(maxAllowedTicks) && (levelChunkTicks = (LevelChunkTicks<T>)this.containersToTick.poll()) != null) {
|
|
ScheduledTick<T> scheduledTick = levelChunkTicks.poll();
|
|
this.scheduleForThisTick(scheduledTick);
|
|
this.drainFromCurrentContainer(this.containersToTick, levelChunkTicks, gameTime, maxAllowedTicks);
|
|
ScheduledTick<T> scheduledTick2 = levelChunkTicks.peek();
|
|
if (scheduledTick2 != null) {
|
|
if (scheduledTick2.triggerTick() <= gameTime && this.canScheduleMoreTicks(maxAllowedTicks)) {
|
|
this.containersToTick.add(levelChunkTicks);
|
|
} else {
|
|
this.updateContainerScheduling(scheduledTick2);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private void rescheduleLeftoverContainers() {
|
|
for (LevelChunkTicks<T> levelChunkTicks : this.containersToTick) {
|
|
this.updateContainerScheduling(levelChunkTicks.peek());
|
|
}
|
|
}
|
|
|
|
private void updateContainerScheduling(ScheduledTick<T> tick) {
|
|
this.nextTickForContainer.put(ChunkPos.asLong(tick.pos()), tick.triggerTick());
|
|
}
|
|
|
|
private void drainFromCurrentContainer(Queue<LevelChunkTicks<T>> containersToTick, LevelChunkTicks<T> levelChunkTicks, long gameTime, int maxAllowedTicks) {
|
|
if (this.canScheduleMoreTicks(maxAllowedTicks)) {
|
|
LevelChunkTicks<T> levelChunkTicks2 = (LevelChunkTicks<T>)containersToTick.peek();
|
|
ScheduledTick<T> scheduledTick = levelChunkTicks2 != null ? levelChunkTicks2.peek() : null;
|
|
|
|
while (this.canScheduleMoreTicks(maxAllowedTicks)) {
|
|
ScheduledTick<T> scheduledTick2 = levelChunkTicks.peek();
|
|
if (scheduledTick2 == null
|
|
|| scheduledTick2.triggerTick() > gameTime
|
|
|| scheduledTick != null && ScheduledTick.INTRA_TICK_DRAIN_ORDER.compare(scheduledTick2, scheduledTick) > 0) {
|
|
break;
|
|
}
|
|
|
|
levelChunkTicks.poll();
|
|
this.scheduleForThisTick(scheduledTick2);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void scheduleForThisTick(ScheduledTick<T> tick) {
|
|
this.toRunThisTick.add(tick);
|
|
}
|
|
|
|
private boolean canScheduleMoreTicks(int maxAllowedTicks) {
|
|
return this.toRunThisTick.size() < maxAllowedTicks;
|
|
}
|
|
|
|
private void runCollectedTicks(BiConsumer<BlockPos, T> ticker) {
|
|
while (!this.toRunThisTick.isEmpty()) {
|
|
ScheduledTick<T> scheduledTick = (ScheduledTick<T>)this.toRunThisTick.poll();
|
|
if (!this.toRunThisTickSet.isEmpty()) {
|
|
this.toRunThisTickSet.remove(scheduledTick);
|
|
}
|
|
|
|
this.alreadyRunThisTick.add(scheduledTick);
|
|
ticker.accept(scheduledTick.pos(), scheduledTick.type());
|
|
}
|
|
}
|
|
|
|
private void cleanupAfterTick() {
|
|
this.toRunThisTick.clear();
|
|
this.containersToTick.clear();
|
|
this.alreadyRunThisTick.clear();
|
|
this.toRunThisTickSet.clear();
|
|
}
|
|
|
|
@Override
|
|
public boolean hasScheduledTick(BlockPos pos, T type) {
|
|
LevelChunkTicks<T> levelChunkTicks = this.allContainers.get(ChunkPos.asLong(pos));
|
|
return levelChunkTicks != null && levelChunkTicks.hasScheduledTick(pos, type);
|
|
}
|
|
|
|
@Override
|
|
public boolean willTickThisTick(BlockPos pos, T type) {
|
|
this.calculateTickSetIfNeeded();
|
|
return this.toRunThisTickSet.contains(ScheduledTick.probe(type, pos));
|
|
}
|
|
|
|
private void calculateTickSetIfNeeded() {
|
|
if (this.toRunThisTickSet.isEmpty() && !this.toRunThisTick.isEmpty()) {
|
|
this.toRunThisTickSet.addAll(this.toRunThisTick);
|
|
}
|
|
}
|
|
|
|
private void forContainersInArea(BoundingBox area, LevelTicks.PosAndContainerConsumer<T> action) {
|
|
int i = SectionPos.posToSectionCoord(area.minX());
|
|
int j = SectionPos.posToSectionCoord(area.minZ());
|
|
int k = SectionPos.posToSectionCoord(area.maxX());
|
|
int l = SectionPos.posToSectionCoord(area.maxZ());
|
|
|
|
for (int m = i; m <= k; m++) {
|
|
for (int n = j; n <= l; n++) {
|
|
long o = ChunkPos.asLong(m, n);
|
|
LevelChunkTicks<T> levelChunkTicks = this.allContainers.get(o);
|
|
if (levelChunkTicks != null) {
|
|
action.accept(o, levelChunkTicks);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public void clearArea(BoundingBox area) {
|
|
Predicate<ScheduledTick<T>> predicate = scheduledTick -> area.isInside(scheduledTick.pos());
|
|
this.forContainersInArea(area, (l, levelChunkTicks) -> {
|
|
ScheduledTick<T> scheduledTick = levelChunkTicks.peek();
|
|
levelChunkTicks.removeIf(predicate);
|
|
ScheduledTick<T> scheduledTick2 = levelChunkTicks.peek();
|
|
if (scheduledTick2 != scheduledTick) {
|
|
if (scheduledTick2 != null) {
|
|
this.updateContainerScheduling(scheduledTick2);
|
|
} else {
|
|
this.nextTickForContainer.remove(l);
|
|
}
|
|
}
|
|
});
|
|
this.alreadyRunThisTick.removeIf(predicate);
|
|
this.toRunThisTick.removeIf(predicate);
|
|
}
|
|
|
|
public void copyArea(BoundingBox area, Vec3i offset) {
|
|
this.copyAreaFrom(this, area, offset);
|
|
}
|
|
|
|
public void copyAreaFrom(LevelTicks<T> levelTicks, BoundingBox area, Vec3i offset) {
|
|
List<ScheduledTick<T>> list = new ArrayList();
|
|
Predicate<ScheduledTick<T>> predicate = scheduledTick -> area.isInside(scheduledTick.pos());
|
|
levelTicks.alreadyRunThisTick.stream().filter(predicate).forEach(list::add);
|
|
levelTicks.toRunThisTick.stream().filter(predicate).forEach(list::add);
|
|
levelTicks.forContainersInArea(area, (lx, levelChunkTicks) -> levelChunkTicks.getAll().filter(predicate).forEach(list::add));
|
|
LongSummaryStatistics longSummaryStatistics = list.stream().mapToLong(ScheduledTick::subTickOrder).summaryStatistics();
|
|
long l = longSummaryStatistics.getMin();
|
|
long m = longSummaryStatistics.getMax();
|
|
list.forEach(
|
|
scheduledTick -> this.schedule(
|
|
new ScheduledTick<>(
|
|
(T)scheduledTick.type(),
|
|
scheduledTick.pos().offset(offset),
|
|
scheduledTick.triggerTick(),
|
|
scheduledTick.priority(),
|
|
scheduledTick.subTickOrder() - l + m + 1L
|
|
)
|
|
)
|
|
);
|
|
}
|
|
|
|
@Override
|
|
public int count() {
|
|
return this.allContainers.values().stream().mapToInt(TickAccess::count).sum();
|
|
}
|
|
|
|
@FunctionalInterface
|
|
interface PosAndContainerConsumer<T> {
|
|
void accept(long l, LevelChunkTicks<T> levelChunkTicks);
|
|
}
|
|
}
|