From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: 2No2Name <2No2Name@web.de> Date: Tue, 14 Dec 2021 12:04:01 -0500 Subject: [PATCH] Lithium optimizations Original code by CaffeineMC, licensed under GNU Lesser General Public License v3.0 You can find the original code on https://github.com/CaffeineMC/lithium-fabric (Yarn mappings) diff --git a/src/main/java/me/jellysquid/mods/lithium/common/cached_blockpos_iteration/IterateOutwardsCache.java b/src/main/java/me/jellysquid/mods/lithium/common/cached_blockpos_iteration/IterateOutwardsCache.java new file mode 100644 index 0000000000000000000000000000000000000000..a5d3aa309d3fdaab9e0fea2dfb91a080a3ac1193 --- /dev/null +++ b/src/main/java/me/jellysquid/mods/lithium/common/cached_blockpos_iteration/IterateOutwardsCache.java @@ -0,0 +1,71 @@ +package me.jellysquid.mods.lithium.common.cached_blockpos_iteration; + +import it.unimi.dsi.fastutil.longs.LongArrayList; +import it.unimi.dsi.fastutil.longs.LongList; +import java.util.Iterator; +import java.util.Random; +import java.util.concurrent.ConcurrentHashMap; +import net.minecraft.core.BlockPos; + +/** + * @author 2No2Name, original implemenation by SuperCoder7979 and Gegy1000 + */ +public class IterateOutwardsCache { + //POS_ZERO must not be replaced with BlockPos.ORIGIN, otherwise iterateOutwards at BlockPos.ORIGIN will not use the cache + public static final BlockPos POS_ZERO = new BlockPos(0,0,0); + + + private final ConcurrentHashMap table; + private final int capacity; + private final Random random; + + public IterateOutwardsCache(int capacity) { + this.capacity = capacity; + this.table = new ConcurrentHashMap<>(31); + this.random = new Random(); + } + + private void fillPositionsWithIterateOutwards(LongList entry, int xRange, int yRange, int zRange) { + // Add all positions to the cached list + for (BlockPos pos : BlockPos.withinManhattan(POS_ZERO, xRange, yRange, zRange)) { + entry.add(pos.asLong()); + } + } + + public LongList getOrCompute(int xRange, int yRange, int zRange) { + long key = BlockPos.asLong(xRange, yRange, zRange); + + LongArrayList entry = this.table.get(key); + if (entry != null) { + return entry; + } + + // Cache miss: compute and store + entry = new LongArrayList(128); + + this.fillPositionsWithIterateOutwards(entry, xRange, yRange, zRange); + + //decrease the array size, as of now it won't be modified anymore anyways + entry.trim(); + + //this might overwrite an entry as the same entry could have been computed and added during this thread's computation + //we do not use computeIfAbsent, as it can delay other threads for too long + Object previousEntry = this.table.put(key, entry); + + + if (previousEntry == null && this.table.size() > this.capacity) { + //prevent a memory leak by randomly removing about 1/8th of the elements when the exceed the desired capacity is exceeded + final Iterator iterator = this.table.keySet().iterator(); + //prevent an unlikely infinite loop caused by another thread filling the table concurrently using counting + for (int i = -this.capacity; iterator.hasNext() && i < 5; i++) { + Long key2 = iterator.next(); + //random is not threadsafe, but it doesn't matter here, because we don't need quality random numbers + if (this.random.nextInt(8) == 0 && key2 != key) { + iterator.remove(); + } + } + } + + return entry; + } +} \ No newline at end of file diff --git a/src/main/java/me/jellysquid/mods/lithium/common/cached_blockpos_iteration/LongList2BlockPosMutableIterable.java b/src/main/java/me/jellysquid/mods/lithium/common/cached_blockpos_iteration/LongList2BlockPosMutableIterable.java new file mode 100644 index 0000000000000000000000000000000000000000..493661ff3ac7247b68b7b02784b09b0eaf88fc52 --- /dev/null +++ b/src/main/java/me/jellysquid/mods/lithium/common/cached_blockpos_iteration/LongList2BlockPosMutableIterable.java @@ -0,0 +1,46 @@ +package me.jellysquid.mods.lithium.common.cached_blockpos_iteration; + +import it.unimi.dsi.fastutil.longs.LongIterator; +import it.unimi.dsi.fastutil.longs.LongList; +import java.util.Iterator; +import net.minecraft.core.BlockPos; + +/** + * @author 2No2Name + */ +public class LongList2BlockPosMutableIterable implements Iterable { + + private final LongList positions; + private final int xOffset, yOffset, zOffset; + + public LongList2BlockPosMutableIterable(BlockPos offset, LongList posList) { + this.xOffset = offset.getX(); + this.yOffset = offset.getY(); + this.zOffset = offset.getZ(); + this.positions = posList; + } + + @Override + public Iterator iterator() { + return new Iterator() { + + private final LongIterator it = LongList2BlockPosMutableIterable.this.positions.iterator(); + private final BlockPos.MutableBlockPos pos = new BlockPos.MutableBlockPos(); + + @Override + public boolean hasNext() { + return it.hasNext(); + } + + @Override + public net.minecraft.core.BlockPos next() { + long nextPos = this.it.nextLong(); + return this.pos.set( + LongList2BlockPosMutableIterable.this.xOffset + BlockPos.getX(nextPos), + LongList2BlockPosMutableIterable.this.yOffset + BlockPos.getY(nextPos), + LongList2BlockPosMutableIterable.this.zOffset + BlockPos.getZ(nextPos)); + } + }; + } + +} \ No newline at end of file diff --git a/src/main/java/me/jellysquid/mods/lithium/common/util/Pos.java b/src/main/java/me/jellysquid/mods/lithium/common/util/Pos.java new file mode 100644 index 0000000000000000000000000000000000000000..c99eff34c1be07508c88fe9525c3ae1a087fdef7 --- /dev/null +++ b/src/main/java/me/jellysquid/mods/lithium/common/util/Pos.java @@ -0,0 +1,92 @@ +package me.jellysquid.mods.lithium.common.util; + +import net.minecraft.core.SectionPos; +import net.minecraft.world.level.LevelHeightAccessor; + +public class Pos { + + public static class BlockCoord { + public static int getYSize(LevelHeightAccessor view) { + return view.getHeight(); + } + public static int getMinY(LevelHeightAccessor view) { + return view.getMinBuildHeight(); + } + public static int getMaxYInclusive(LevelHeightAccessor view) { + return view.getMaxBuildHeight() - 1; + } + public static int getMaxYExclusive(LevelHeightAccessor view) { + return view.getMaxBuildHeight(); + } + + public static int getMaxInSectionCoord(int sectionCoord) { + return 15 + getMinInSectionCoord(sectionCoord); + } + + public static int getMaxYInSectionIndex(LevelHeightAccessor view, int sectionIndex){ + return getMaxInSectionCoord(SectionYCoord.fromSectionIndex(view, sectionIndex)); + } + + public static int getMinInSectionCoord(int sectionCoord) { + return SectionPos.sectionToBlockCoord(sectionCoord); + } + + public static int getMinYInSectionIndex(LevelHeightAccessor view, int sectionIndex) { + return getMinInSectionCoord(SectionYCoord.fromSectionIndex(view, sectionIndex)); + } + } + + public static class ChunkCoord { + public static int fromBlockCoord(int blockCoord) { + return SectionPos.blockToSectionCoord(blockCoord); + } + + public static int fromBlockSize(int i) { + return i >> 4; //same method as fromBlockCoord, just be clear about coord/size semantic difference + } + } + + public static class SectionYCoord { + public static int getNumYSections(LevelHeightAccessor view) { + return view.getSectionsCount(); + } + public static int getMinYSection(LevelHeightAccessor view) { + return view.getMinSection(); + } + public static int getMaxYSectionInclusive(LevelHeightAccessor view) { + return view.getMaxSection() - 1; + } + public static int getMaxYSectionExclusive(LevelHeightAccessor view) { + return view.getMaxSection(); + } + + public static int fromSectionIndex(LevelHeightAccessor view, int sectionCoord) { + return sectionCoord + SectionYCoord.getMinYSection(view); + } + public static int fromBlockCoord(int blockCoord) { + return SectionPos.blockToSectionCoord(blockCoord); + } + } + + public static class SectionYIndex { + public static int getNumYSections(LevelHeightAccessor view) { + return view.getSectionsCount(); + } + public static int getMinYSectionIndex(LevelHeightAccessor view) { + return 0; + } + public static int getMaxYSectionIndexInclusive(LevelHeightAccessor view) { + return view.getSectionsCount() - 1; + } + public static int getMaxYSectionIndexExclusive(LevelHeightAccessor view) { + return view.getSectionsCount(); + } + + public static int fromSectionCoord(LevelHeightAccessor view, int sectionCoord) { + return sectionCoord - SectionYCoord.getMinYSection(view); + } + public static int fromBlockCoord(LevelHeightAccessor view, int blockCoord) { + return fromSectionCoord(view, SectionPos.blockToSectionCoord(blockCoord)); + } + } +} \ No newline at end of file diff --git a/src/main/java/me/jellysquid/mods/lithium/common/util/collections/HashedReferenceList.java b/src/main/java/me/jellysquid/mods/lithium/common/util/collections/HashedReferenceList.java new file mode 100644 index 0000000000000000000000000000000000000000..4128567173d3985257a1bdd4412c7db0ff81535d --- /dev/null +++ b/src/main/java/me/jellysquid/mods/lithium/common/util/collections/HashedReferenceList.java @@ -0,0 +1,281 @@ +package me.jellysquid.mods.lithium.common.util.collections; + +import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap; +import it.unimi.dsi.fastutil.objects.ReferenceArrayList; +import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet; +import org.jetbrains.annotations.NotNull; + +import java.util.*; + +/** + * Wraps a {@link List} with a hash table which provides O(1) lookups for {@link Collection#contains(Object)}. The type + * contained by this list must use reference-equality semantics. + */ +@SuppressWarnings("SuspiciousMethodCalls") +public class HashedReferenceList implements List { + private final ReferenceArrayList list; + private final Reference2IntOpenHashMap counter; + + public HashedReferenceList(List list) { + this.list = new ReferenceArrayList<>(); + this.list.addAll(list); + + this.counter = new Reference2IntOpenHashMap<>(); + this.counter.defaultReturnValue(0); + + for (T obj : this.list) { + this.counter.addTo(obj, 1); + } + } + + @Override + public int size() { + return this.list.size(); + } + + @Override + public boolean isEmpty() { + return this.list.isEmpty(); + } + + @Override + public boolean contains(Object o) { + return this.counter.containsKey(o); + } + + @Override + public Iterator iterator() { + return this.listIterator(); + } + + @Override + public Object[] toArray() { + return this.list.toArray(); + } + + @SuppressWarnings("SuspiciousToArrayCall") + @Override + public T1[] toArray(T1 @NotNull [] a) { + return this.list.toArray(a); + } + + @Override + public boolean add(T t) { + this.trackReferenceAdded(t); + + return this.list.add(t); + } + + @Override + public boolean remove(Object o) { + this.trackReferenceRemoved(o); + + return this.list.remove(o); + } + + @Override + public boolean containsAll(Collection c) { + for (Object obj : c) { + if (!this.counter.containsKey(obj)) { + return false; + } + } + + return true; + } + + @Override + public boolean addAll(Collection c) { + for (T obj : c) { + this.trackReferenceAdded(obj); + } + + return this.list.addAll(c); + } + + @Override + public boolean addAll(int index, Collection c) { + for (T obj : c) { + this.trackReferenceAdded(obj); + } + + return this.list.addAll(index, c); + } + + @Override + public boolean removeAll(@NotNull Collection c) { + if (this.size() >= 2 && c.size() > 4 && c instanceof List) { + //HashReferenceList uses reference equality, so using ReferenceOpenHashSet is fine + c = new ReferenceOpenHashSet<>(c); + } + this.counter.keySet().removeAll(c); + return this.list.removeAll(c); + } + + @Override + public boolean retainAll(@NotNull Collection c) { + this.counter.keySet().retainAll(c); + return this.list.retainAll(c); + } + + @Override + public void clear() { + this.counter.clear(); + this.list.clear(); + } + + @Override + public T get(int index) { + return this.list.get(index); + } + + @Override + public T set(int index, T element) { + T prev = this.list.set(index, element); + + if (prev != element) { + if (prev != null) { + this.trackReferenceRemoved(prev); + } + + this.trackReferenceAdded(element); + } + + return prev; + } + + @Override + public void add(int index, T element) { + this.trackReferenceAdded(element); + + this.list.add(index, element); + } + + @Override + public T remove(int index) { + T prev = this.list.remove(index); + + if (prev != null) { + this.trackReferenceRemoved(prev); + } + + return prev; + } + + @Override + public int indexOf(Object o) { + return this.list.indexOf(o); + } + + @Override + public int lastIndexOf(Object o) { + return this.list.lastIndexOf(o); + } + + @Override + public ListIterator listIterator() { + return this.listIterator(0); + } + + @Override + public ListIterator listIterator(int index) { + return new ListIterator<>() { + private final ListIterator inner = HashedReferenceList.this.list.listIterator(index); + + @Override + public boolean hasNext() { + return this.inner.hasNext(); + } + + @Override + public T next() { + return this.inner.next(); + } + + @Override + public boolean hasPrevious() { + return this.inner.hasPrevious(); + } + + @Override + public T previous() { + return this.inner.previous(); + } + + @Override + public int nextIndex() { + return this.inner.nextIndex(); + } + + @Override + public int previousIndex() { + return this.inner.previousIndex(); + } + + @Override + public void remove() { + int last = this.previousIndex(); + + if (last == -1) { + throw new NoSuchElementException(); + } + + T prev = HashedReferenceList.this.get(last); + + if (prev != null) { + HashedReferenceList.this.trackReferenceRemoved(prev); + } + + this.inner.remove(); + } + + @Override + public void set(T t) { + int last = this.previousIndex(); + + if (last == -1) { + throw new NoSuchElementException(); + } + + T prev = HashedReferenceList.this.get(last); + + if (prev != t) { + if (prev != null) { + HashedReferenceList.this.trackReferenceRemoved(prev); + } + + HashedReferenceList.this.trackReferenceAdded(t); + } + + this.inner.remove(); + } + + @Override + public void add(T t) { + HashedReferenceList.this.trackReferenceAdded(t); + + this.inner.add(t); + } + }; + } + + @Override + public List subList(int fromIndex, int toIndex) { + return this.list.subList(fromIndex, toIndex); + } + + private void trackReferenceAdded(T t) { + this.counter.addTo(t, 1); + } + + @SuppressWarnings("unchecked") + private void trackReferenceRemoved(Object o) { + if (this.counter.addTo((T) o, -1) <= 1) { + this.counter.removeInt(o); + } + } + + public static HashedReferenceList wrapper(List list) { + return new HashedReferenceList<>(list); + } +} \ No newline at end of file diff --git a/src/main/java/me/jellysquid/mods/lithium/common/util/math/CompactSineLUT.java b/src/main/java/me/jellysquid/mods/lithium/common/util/math/CompactSineLUT.java new file mode 100644 index 0000000000000000000000000000000000000000..b8c9cb28876c2c1781cd72870076d528b9647916 --- /dev/null +++ b/src/main/java/me/jellysquid/mods/lithium/common/util/math/CompactSineLUT.java @@ -0,0 +1,90 @@ +package me.jellysquid.mods.lithium.common.util.math; + +import net.minecraft.util.Mth; + +/** + * A replacement for the sine angle lookup table used in {@link Mth}, both reducing the size of LUT and improving + * the access patterns for common paired sin/cos operations. + * + * sin(-x) = -sin(x) + * ... to eliminate negative angles from the LUT. + * + * sin(x) = sin(pi/2 - x) + * ... to eliminate supplementary angles from the LUT. + * + * Using these identities allows us to reduce the LUT from 64K entries (256 KB) to just 16K entries (64 KB), enabling + * it to better fit into the CPU's caches at the expense of some cycles on the fast path. The implementation has been + * tightly optimized to avoid branching where possible and to use very quick integer operations. + * + * Generally speaking, reducing the size of a lookup table is always a good optimization, but since we need to spend + * extra CPU cycles trying to maintain parity with vanilla, there is the potential risk that this implementation ends + * up being slower than vanilla when the lookup table is able to be kept in cache memory. + * + * Unlike other "fast math" implementations, the values returned by this class are *bit-for-bit identical* with those + * from {@link Mth}. Validation is performed during runtime to ensure that the table is correct. + * + * @author coderbot16 Author of the original (and very clever) implementation in Rust: + * https://gitlab.com/coderbot16/i73/-/tree/master/i73-trig/src + * @author jellysquid3 Additional optimizations, port to Java + */ +public class CompactSineLUT { + private static final int[] SINE_TABLE_INT = new int[16384 + 1]; + private static final float SINE_TABLE_MIDPOINT; + + static { + final float[] SINE_TABLE = Mth.getSinTable(); + // Copy the sine table, covering to raw int bits + for (int i = 0; i < SINE_TABLE_INT.length; i++) { + SINE_TABLE_INT[i] = Float.floatToRawIntBits(SINE_TABLE[i]); + } + + SINE_TABLE_MIDPOINT = SINE_TABLE[SINE_TABLE.length / 2]; + + // Test that the lookup table is correct during runtime + for (int i = 0; i < SINE_TABLE.length; i++) { + float expected = SINE_TABLE[i]; + float value = lookup(i); + + if (expected != value) { + throw new IllegalArgumentException(String.format("LUT error at index %d (expected: %s, found: %s)", i, expected, value)); + } + } + } + + // [VanillaCopy] MathHelper#sin(float) + public static float sin(float f) { + return lookup((int) (f * 10430.378f) & 0xFFFF); + } + + // [VanillaCopy] MathHelper#cos(float) + public static float cos(float f) { + return lookup((int) (f * 10430.378f + 16384.0f) & 0xFFFF); + } + + private static float lookup(int index) { + // A special case... Is there some way to eliminate this? + if (index == 32768) { + return SINE_TABLE_MIDPOINT; + } + + // Trigonometric identity: sin(-x) = -sin(x) + // Given a domain of 0 <= x <= 2*pi, just negate the value if x > pi. + // This allows the sin table size to be halved. + int neg = (index & 0x8000) << 16; + + // All bits set if (pi/2 <= x), none set otherwise + // Extracts the 15th bit from 'half' + int mask = (index << 17) >> 31; + + // Trigonometric identity: sin(x) = sin(pi/2 - x) + int pos = (0x8001 & mask) + (index ^ mask); + + // Wrap the position in the table. Moving this down to immediately before the array access + // seems to help the Hotspot compiler optimize the bit math better. + pos &= 0x7fff; + + // Fetch the corresponding value from the LUT and invert the sign bit as needed + // This directly manipulate the sign bit on the float bits to simplify logic + return Float.intBitsToFloat(SINE_TABLE_INT[pos] ^ neg); + } +} \ No newline at end of file diff --git a/src/main/java/me/jellysquid/mods/lithium/common/world/chunk/LithiumHashPalette.java b/src/main/java/me/jellysquid/mods/lithium/common/world/chunk/LithiumHashPalette.java new file mode 100644 index 0000000000000000000000000000000000000000..16debe176798f316c122e8e7aef2b50ecb9883a6 --- /dev/null +++ b/src/main/java/me/jellysquid/mods/lithium/common/world/chunk/LithiumHashPalette.java @@ -0,0 +1,189 @@ +package me.jellysquid.mods.lithium.common.world.chunk; + +import com.google.common.collect.ImmutableList; +import it.unimi.dsi.fastutil.HashCommon; +import it.unimi.dsi.fastutil.objects.Reference2IntMap; +import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap; +import java.util.Arrays; +import java.util.List; +import java.util.function.Predicate; +import net.minecraft.core.IdMap; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.world.level.chunk.Palette; +import net.minecraft.world.level.chunk.PaletteResize; + +import static it.unimi.dsi.fastutil.Hash.FAST_LOAD_FACTOR; + +/** + * Generally provides better performance over the vanilla {@link net.minecraft.world.level.chunk.HashMapPalette} when calling + * {@link LithiumHashPalette#idFor(Object)} through using a faster backing map and reducing pointer chasing. + */ +public class LithiumHashPalette implements Palette { + private static final int ABSENT_VALUE = -1; + + private final IdMap idList; + private final PaletteResize resizeHandler; + private final int indexBits; + + private final Reference2IntMap table; + private T[] entries; + private int size = 0; + + public LithiumHashPalette(IdMap idList, PaletteResize resizeHandler, int indexBits, T[] entries, Reference2IntMap table, int size) { + this.idList = idList; + this.resizeHandler = resizeHandler; + this.indexBits = indexBits; + this.entries = entries; + this.table = table; + this.size = size; + } + + public LithiumHashPalette(IdMap idList, int bits, PaletteResize resizeHandler, List list) { + this(idList, bits, resizeHandler); + + for (T t : list) { + this.addEntry(t); + } + } + + @SuppressWarnings("unchecked") + public LithiumHashPalette(IdMap idList, int bits, PaletteResize resizeHandler) { + this.idList = idList; + this.indexBits = bits; + this.resizeHandler = resizeHandler; + + int capacity = 1 << bits; + + this.entries = (T[]) new Object[capacity]; + this.table = new Reference2IntOpenHashMap<>(capacity, FAST_LOAD_FACTOR); + this.table.defaultReturnValue(ABSENT_VALUE); + } + + @Override + public int idFor(T obj) { + int id = this.table.getInt(obj); + + if (id == ABSENT_VALUE) { + id = this.computeEntry(obj); + } + + return id; + } + + @Override + public boolean maybeHas(Predicate predicate) { + for (int i = 0; i < this.size; ++i) { + if (predicate.test(this.entries[i])) { + return true; + } + } + + return false; + } + + private int computeEntry(T obj) { + int id = this.addEntry(obj); + + if (id >= 1 << this.indexBits) { + if (this.resizeHandler == null) { + throw new IllegalStateException("Cannot grow"); + } else { + id = this.resizeHandler.onResize(this.indexBits + 1, obj); + } + } + + return id; + } + + private int addEntry(T obj) { + int nextId = this.size; + + if (nextId >= this.entries.length) { + this.resize(this.size); + } + + this.table.put(obj, nextId); + this.entries[nextId] = obj; + + this.size++; + + return nextId; + } + + private void resize(int neededCapacity) { + this.entries = Arrays.copyOf(this.entries, HashCommon.nextPowerOfTwo(neededCapacity + 1)); + } + + @Override + public T valueFor(int id) { + T[] entries = this.entries; + + if (id >= 0 && id < entries.length) { + return entries[id]; + } + + return null; + } + + @Override + public void read(FriendlyByteBuf buf) { + this.clear(); + + int entryCount = buf.readVarInt(); + + for (int i = 0; i < entryCount; ++i) { + this.addEntry(this.idList.byId(buf.readVarInt())); + } + } + + @Override + public void write(FriendlyByteBuf buf) { + int size = this.size; + buf.writeVarInt(size); + + for (int i = 0; i < size; ++i) { + buf.writeVarInt(this.idList.getId(this.valueFor(i))); + } + } + + @Override + public int getSerializedSize() { + int size = FriendlyByteBuf.getVarIntSize(this.size); + + for (int i = 0; i < this.size; ++i) { + size += FriendlyByteBuf.getVarIntSize(this.idList.getId(this.valueFor(i))); + } + + return size; + } + + @Override + public int getSize() { + return this.size; + } + + @Override + public Palette copy() { + return new LithiumHashPalette<>(this.idList, this.resizeHandler, this.indexBits, this.entries.clone(), new Reference2IntOpenHashMap<>(this.table), this.size); + } + + private void clear() { + Arrays.fill(this.entries, null); + this.table.clear(); + this.size = 0; + } + + public List getElements() { + ImmutableList.Builder builder = new ImmutableList.Builder<>(); + for (T entry : this.entries) { + if (entry != null) { + builder.add(entry); + } + } + return builder.build(); + } + + public static Palette create(int bits, IdMap idList, PaletteResize listener, List list) { + return new LithiumHashPalette<>(idList, bits, listener, list); + } +} \ No newline at end of file diff --git a/src/main/java/me/jellysquid/mods/lithium/common/world/scheduler/OrderedTickQueue.java b/src/main/java/me/jellysquid/mods/lithium/common/world/scheduler/OrderedTickQueue.java new file mode 100644 index 0000000000000000000000000000000000000000..52e90ea09b44543af661c214767073cf9d5f3d8f --- /dev/null +++ b/src/main/java/me/jellysquid/mods/lithium/common/world/scheduler/OrderedTickQueue.java @@ -0,0 +1,192 @@ +package me.jellysquid.mods.lithium.common.world.scheduler; + +import it.unimi.dsi.fastutil.HashCommon; +import java.util.*; +import net.minecraft.world.ticks.ScheduledTick; + +/** + + */ +public class OrderedTickQueue extends AbstractQueue> { + private static final int INITIAL_CAPACITY = 16; + private static final Comparator> COMPARATOR = Comparator.comparingLong(ScheduledTick::subTickOrder); + + private ScheduledTick[] arr; + + private int lastIndexExclusive; + private int firstIndex; + + private long currentMaxSubTickOrder = Long.MIN_VALUE; + private boolean isSorted; + private ScheduledTick unsortedPeekResult; + + @SuppressWarnings("unchecked") + public OrderedTickQueue(int capacity) { + this.arr = (ScheduledTick[]) new ScheduledTick[capacity]; + this.lastIndexExclusive = 0; + this.isSorted = true; + this.unsortedPeekResult = null; + this.firstIndex = 0; + } + + public OrderedTickQueue() { + this(INITIAL_CAPACITY); + } + + @Override + public void clear() { + Arrays.fill(this.arr, null); + this.lastIndexExclusive = 0; + this.firstIndex = 0; + this.currentMaxSubTickOrder = Long.MIN_VALUE; + this.isSorted = true; + this.unsortedPeekResult = null; + } + + @Override + public Iterator> iterator() { + if (this.isEmpty()) { + return Collections.emptyIterator(); + } + this.sort(); + return new Iterator<>() { + int nextIndex = OrderedTickQueue.this.firstIndex; + + @Override + public boolean hasNext() { + return this.nextIndex < OrderedTickQueue.this.lastIndexExclusive; + } + + @Override + public ScheduledTick next() { + return OrderedTickQueue.this.arr[this.nextIndex++]; + } + }; + } + + @Override + public ScheduledTick poll() { + if (this.isEmpty()) { + return null; + } + if (!this.isSorted) { + this.sort(); + } + ScheduledTick nextTick; + int polledIndex = this.firstIndex++; + ScheduledTick[] ticks = this.arr; + nextTick = ticks[polledIndex]; + ticks[polledIndex] = null; + return nextTick; + } + + @Override + public ScheduledTick peek() { + if (!this.isSorted) { + return this.unsortedPeekResult; + } else if (this.lastIndexExclusive > this.firstIndex) { + return this.getTickAtIndex(this.firstIndex); + } + return null; + } + + public boolean offer(ScheduledTick tick) { + if (this.lastIndexExclusive >= this.arr.length) { + //todo remove consumed elements first + this.arr = copyArray(this.arr, HashCommon.nextPowerOfTwo(this.arr.length + 1)); + } + if (tick.subTickOrder() <= this.currentMaxSubTickOrder) { + //Set to unsorted instead of slowing down the insertion + //This is rare but may happen in bulk + //Sorting later needs O(n*log(n)) time, but it only needs to happen when unordered insertion needs to happen + //Therefore it is better than n times log(n) time of the PriorityQueue that happens on ordered insertion too + this.isSorted = false; + ScheduledTick firstTick = this.size() > 0 ? this.arr[this.firstIndex] : null; + this.unsortedPeekResult = firstTick == null || tick.subTickOrder() < firstTick.subTickOrder() ? tick : firstTick; + } else { + this.currentMaxSubTickOrder = tick.subTickOrder(); + } + this.arr[this.lastIndexExclusive++] = tick; + return true; + } + + public int size() { + return this.lastIndexExclusive - this.firstIndex; + } + + private void resize(int size) { + // Only compact the array if it is completely empty or is less than 50% filled + if (size == 0 || size < this.arr.length / 2) { + this.arr = copyArray(this.arr, size); + } else { + // Fill the unused array elements with nulls to release our references to the elements in it + for (int i = size; i < this.arr.length; i++) { + this.arr[i] = null; + } + } + + this.firstIndex = 0; + this.lastIndexExclusive = size; + + if (size == 0 || !this.isSorted) { + this.currentMaxSubTickOrder = Long.MIN_VALUE; + } else { + ScheduledTick tick = this.arr[size - 1]; + this.currentMaxSubTickOrder = tick == null ? Long.MIN_VALUE : tick.subTickOrder(); + } + } + + public void sort() { + if (this.isSorted) { + return; + } + this.removeNullsAndConsumed(); + Arrays.sort(this.arr, this.firstIndex, this.lastIndexExclusive, COMPARATOR); + this.isSorted = true; + this.unsortedPeekResult = null; + } + + public void removeNullsAndConsumed() { + int src = this.firstIndex; + int dst = 0; + while (src < this.lastIndexExclusive) { + ScheduledTick orderedTick = this.arr[src]; + if (orderedTick != null) { + this.arr[dst] = orderedTick; + dst++; + } + src++; + } + this.resize(dst); + } + + public ScheduledTick getTickAtIndex(int index) { + if (!this.isSorted) { + throw new IllegalStateException("Unexpected access on unsorted queue!"); + } + return this.arr[index]; + } + + public void setTickAtIndex(int index, ScheduledTick tick) { + if (!this.isSorted) { + throw new IllegalStateException("Unexpected access on unsorted queue!"); + } + this.arr[index] = tick; + } + + @SuppressWarnings("unchecked") + private static ScheduledTick[] copyArray(ScheduledTick[] src, int size) { + final ScheduledTick[] copy = new ScheduledTick[size]; + + if (size != 0) { + System.arraycopy(src, 0, copy, 0, Math.min(src.length, size)); + } + + return copy; + } + + @Override + public boolean isEmpty() { + return this.lastIndexExclusive <= this.firstIndex; + } +} diff --git a/src/main/java/net/minecraft/core/BlockPos.java b/src/main/java/net/minecraft/core/BlockPos.java index 56536c39bccfe097f8227c74a0d1679943826610..e16c83aad25d8cbb4fb732ce37acb4c64eba5485 100644 --- a/src/main/java/net/minecraft/core/BlockPos.java +++ b/src/main/java/net/minecraft/core/BlockPos.java @@ -18,6 +18,12 @@ import net.minecraft.world.phys.AABB; import net.minecraft.world.phys.Vec3; import org.apache.commons.lang3.Validate; import org.slf4j.Logger; +// JettPack start +import it.unimi.dsi.fastutil.longs.LongList; +import me.jellysquid.mods.lithium.common.cached_blockpos_iteration.IterateOutwardsCache; +import me.jellysquid.mods.lithium.common.cached_blockpos_iteration.LongList2BlockPosMutableIterable; +import static me.jellysquid.mods.lithium.common.cached_blockpos_iteration.IterateOutwardsCache.POS_ZERO; +// JettPack end @Immutable public class BlockPos extends Vec3i { @@ -290,7 +296,18 @@ public class BlockPos extends Vec3i { }; } + // JettPack start - lithium: cached iterate outwards + private static final IterateOutwardsCache ITERATE_OUTWARDS_CACHE = new IterateOutwardsCache(50); + private static final LongList HOGLIN_PIGLIN_CACHE = ITERATE_OUTWARDS_CACHE.getOrCompute(8, 4, 8); + // JettPack end + public static Iterable withinManhattan(BlockPos center, int rangeX, int rangeY, int rangeZ) { + // JettPack start - lithium: cached iterate outwards + if (center != POS_ZERO) { + final LongList positions = rangeX == 8 && rangeY == 4 && rangeZ == 8 ? HOGLIN_PIGLIN_CACHE : ITERATE_OUTWARDS_CACHE.getOrCompute(rangeX, rangeY, rangeZ); + return new LongList2BlockPosMutableIterable(center, positions); + } + // JettPack end int i = rangeX + rangeY + rangeZ; // Paper start - rename variables to fix conflict with anonymous class (remap fix) int centerX = center.getX(); diff --git a/src/main/java/net/minecraft/core/Direction.java b/src/main/java/net/minecraft/core/Direction.java index 6c27b22dd1d497687c0f4d3835e34149bcf952c1..445f21c3764d148de937f558e3f087ae2006733d 100644 --- a/src/main/java/net/minecraft/core/Direction.java +++ b/src/main/java/net/minecraft/core/Direction.java @@ -41,7 +41,7 @@ public enum Direction implements StringRepresentable { private final Direction.Axis axis; private final Direction.AxisDirection axisDirection; private final Vec3i normal; - private static final Direction[] VALUES = values(); + public static final Direction[] VALUES = values(); // JettPack private static final Direction[] BY_3D_DATA = Arrays.stream(VALUES).sorted(Comparator.comparingInt((direction) -> { return direction.data3d; })).toArray((i) -> { @@ -196,7 +196,7 @@ public enum Direction implements StringRepresentable { } public Direction getOpposite() { - return from3DDataValue(this.oppositeIndex); + return VALUES[this.oppositeIndex]; // JettPack - lithium: fast util } public Direction getClockWise(Direction.Axis axis) { @@ -458,7 +458,7 @@ public enum Direction implements StringRepresentable { } public static Direction getRandom(RandomSource random) { - return Util.getRandom(VALUES, random); + return VALUES[random.nextInt(VALUES.length)]; // JettPack - lithium: fast util } public static Direction getNearest(double x, double y, double z) { diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java index 92e7ba78e18efb8263475ecc076bc49e88b85e84..2a9219e0139670674319a7cf17ad664582b42715 100644 --- a/src/main/java/net/minecraft/server/level/ServerLevel.java +++ b/src/main/java/net/minecraft/server/level/ServerLevel.java @@ -227,6 +227,13 @@ public class ServerLevel extends Level implements WorldGenLevel { return new Throwable(entity + " Added to world at " + new java.util.Date()); } + // Mirai start + @Override + public ProfilerFiller getProfiler() { + return this.getServer().getProfiler(); + } + // Mirai end + @Override public LevelChunk getChunkIfLoaded(int x, int z) { // Paper - this was added in world too but keeping here for NMS ABI return this.chunkSource.getChunkAtIfLoadedImmediately(x, z); // Paper } diff --git a/src/main/java/net/minecraft/server/level/WorldGenRegion.java b/src/main/java/net/minecraft/server/level/WorldGenRegion.java index c677a55db62b4d197eeb1bf5d929e24019fed5ec..b6e994e043f0aa403b9009754f82e1c787bcec97 100644 --- a/src/main/java/net/minecraft/server/level/WorldGenRegion.java +++ b/src/main/java/net/minecraft/server/level/WorldGenRegion.java @@ -54,6 +54,7 @@ import net.minecraft.world.phys.Vec3; import net.minecraft.world.ticks.LevelTickAccess; import net.minecraft.world.ticks.WorldGenTickAccess; import org.slf4j.Logger; +import me.jellysquid.mods.lithium.common.util.Pos; // Mirai - lithium: gen public class WorldGenRegion implements WorldGenLevel { @@ -82,6 +83,8 @@ public class WorldGenRegion implements WorldGenLevel { private Supplier currentlyGenerating; private final AtomicLong subTickCount = new AtomicLong(); private static final ResourceLocation WORLDGEN_REGION_RANDOM = new ResourceLocation("worldgen_region_random"); + private ChunkAccess[] chunksArr; // Mirai - lithium: gen + private int minChunkX, minChunkZ; // Mirai - lithium: gen public WorldGenRegion(ServerLevel world, List chunks, ChunkStatus status, int placementRadius) { this.generatingStatus = status; @@ -104,6 +107,12 @@ public class WorldGenRegion implements WorldGenLevel { this.lastPos = ((ChunkAccess) chunks.get(chunks.size() - 1)).getPos(); this.structureManager = world.structureManager().forWorldGenRegion(this); } + // Mirai start - lithium: gen + this.minChunkX = this.firstPos.x; + this.minChunkZ = this.firstPos.z; + + this.chunksArr = chunks.toArray(new ChunkAccess[0]); + // Mirai end } public boolean isOldChunkAround(ChunkPos chunkPos, int checkRadius) { @@ -119,11 +128,33 @@ public class WorldGenRegion implements WorldGenLevel { this.currentlyGenerating = structureName; } + // Mirai start - lithium: gen + /** + * @reason Use the chunk array for faster access + * @author SuperCoder7979, 2No2Name + */ @Override public ChunkAccess getChunk(int chunkX, int chunkZ) { - return this.getChunk(chunkX, chunkZ, ChunkStatus.EMPTY); + int x = chunkX - this.minChunkX; + int z = chunkZ - this.minChunkZ; + int w = this.size; + + if (x >= 0 && z >= 0 && x < w && z < w) { + return this.chunksArr[x + z * w]; + } else { + throw new NullPointerException("No chunk exists at " + new ChunkPos(chunkX, chunkZ)); + } } + /** + * Use our chunk fetch function + */ + public ChunkAccess getChunk(BlockPos pos) { + // Skip checking chunk.getStatus().isAtLeast(ChunkStatus.EMPTY) here, because it is always true + return this.getChunk(Pos.ChunkCoord.fromBlockCoord(pos.getX()), Pos.ChunkCoord.fromBlockCoord(pos.getZ())); + } + // Mirai end + @Nullable @Override public ChunkAccess getChunk(int chunkX, int chunkZ, ChunkStatus leastStatus, boolean create) { @@ -179,10 +210,24 @@ public class WorldGenRegion implements WorldGenLevel { } // Paper end + // Mirai start - lithium: gen + /** + * @reason Avoid pointer de-referencing, make method easier to inline + * @author JellySquid + */ @Override public BlockState getBlockState(BlockPos pos) { - return this.getChunk(SectionPos.blockToSectionCoord(pos.getX()), SectionPos.blockToSectionCoord(pos.getZ())).getBlockState(pos); + int x = (Pos.ChunkCoord.fromBlockCoord(pos.getX())) - this.minChunkX; + int z = (Pos.ChunkCoord.fromBlockCoord(pos.getZ())) - this.minChunkZ; + int w = this.size; + + if (x >= 0 && z >= 0 && x < w && z < w) { + return this.chunksArr[x + z * w].getBlockState(pos); + } else { + throw new NullPointerException("No chunk exists at " + new ChunkPos(pos)); + } } + // Mirai end @Override public FluidState getFluidState(BlockPos pos) { diff --git a/src/main/java/net/minecraft/util/BitStorage.java b/src/main/java/net/minecraft/util/BitStorage.java index 106610ccc74b70b557b01c61262d56c4f1147acf..fc986f02290fbe20246022072944980f35dd200c 100644 --- a/src/main/java/net/minecraft/util/BitStorage.java +++ b/src/main/java/net/minecraft/util/BitStorage.java @@ -1,6 +1,7 @@ package net.minecraft.util; import java.util.function.IntConsumer; +import net.minecraft.world.level.chunk.Palette; // JettPack public interface BitStorage { int getAndSet(int index, int value); @@ -31,4 +32,6 @@ public interface BitStorage { } // Paper end + + void compact(Palette srcPalette, Palette dstPalette, short[] out); // JettPack - lithium: chunk.serialization } diff --git a/src/main/java/net/minecraft/util/ClassInstanceMultiMap.java b/src/main/java/net/minecraft/util/ClassInstanceMultiMap.java index 50a9f33aa31e9273c7c52d4bb2b02f0f884f7ba5..6f181fc878a96b09f126ea8d3b19ce3ee4588e19 100644 --- a/src/main/java/net/minecraft/util/ClassInstanceMultiMap.java +++ b/src/main/java/net/minecraft/util/ClassInstanceMultiMap.java @@ -4,6 +4,7 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterators; import com.google.common.collect.Lists; import com.google.common.collect.Maps; +import it.unimi.dsi.fastutil.objects.Reference2ReferenceOpenHashMap; // JettPack import java.util.AbstractCollection; import java.util.Collection; import java.util.Collections; @@ -13,7 +14,7 @@ import java.util.Map; import java.util.stream.Collectors; public class ClassInstanceMultiMap extends AbstractCollection { - private final Map, List> byClass = Maps.newHashMap(); + private final Map, List> byClass = new Reference2ReferenceOpenHashMap<>(); // JettPack private final Class baseClass; private final List allInstances = Lists.newArrayList(); @@ -55,15 +56,32 @@ public class ClassInstanceMultiMap extends AbstractCollection { } public Collection find(Class type) { - if (!this.baseClass.isAssignableFrom(type)) { - throw new IllegalArgumentException("Don't know how to search for " + type); - } else { - List list = this.byClass.computeIfAbsent(type, (typeClass) -> { - return this.allInstances.stream().filter(typeClass::isInstance).collect(Collectors.toList()); - }); - return Collections.unmodifiableCollection(list); + // JettPack start + Collection collection = this.byClass.get(type); + + if (collection == null) { + collection = this.createAllOfType(type); } + + return (Collection) Collections.unmodifiableCollection(collection); + // JettPack end + } + + // JettPack start + private Collection createAllOfType(Class type) { + List list = new java.util.ArrayList<>(); + + for (T allElement : this.allInstances) { + if (type.isInstance(allElement)) { + list.add(allElement); + } + } + + this.byClass.put(type, list); + + return list; } + // JettPack end @Override public Iterator iterator() { diff --git a/src/main/java/net/minecraft/util/Mth.java b/src/main/java/net/minecraft/util/Mth.java index 9b22034aa655ceb0da151d9d8ca3147f6487889a..ec587cf6592a1dc0d90d6f54af1bdfab97aec7c6 100644 --- a/src/main/java/net/minecraft/util/Mth.java +++ b/src/main/java/net/minecraft/util/Mth.java @@ -32,6 +32,7 @@ public class Mth { }); private static final RandomSource RANDOM = RandomSource.createThreadSafe(); + public static float[] getSinTable() { return SIN; } // Mirai - lithium: CompactSineLUT private static final int[] MULTIPLY_DE_BRUIJN_BIT_POSITION = new int[]{0, 1, 28, 2, 29, 14, 24, 3, 30, 22, 20, 15, 25, 17, 4, 8, 31, 27, 13, 23, 21, 19, 16, 7, 26, 12, 18, 6, 11, 5, 10, 9}; private static final double ONE_SIXTH = 0.16666666666666666D; private static final int FRAC_EXP = 8; @@ -41,11 +42,11 @@ public class Mth { private static final double[] COS_TAB = new double[257]; public static float sin(float value) { - return SIN[(int)(value * 10430.378F) & '\uffff']; + return me.jellysquid.mods.lithium.common.util.math.CompactSineLUT.sin(value); // Mirai - lithium: CompactSineLUT } public static float cos(float value) { - return SIN[(int)(value * 10430.378F + 16384.0F) & '\uffff']; + return me.jellysquid.mods.lithium.common.util.math.CompactSineLUT.cos(value); // Mirai - lithium: CompactSineLUT } public static float sqrt(float value) { diff --git a/src/main/java/net/minecraft/util/SimpleBitStorage.java b/src/main/java/net/minecraft/util/SimpleBitStorage.java index 36e33923bf48e56c743ed043bcbc66bc32f0422f..0272dee738e86e066108f5cc3729136335d8197e 100644 --- a/src/main/java/net/minecraft/util/SimpleBitStorage.java +++ b/src/main/java/net/minecraft/util/SimpleBitStorage.java @@ -2,6 +2,7 @@ package net.minecraft.util; import java.util.function.IntConsumer; import javax.annotation.Nullable; +import net.minecraft.world.level.chunk.Palette; // JettPack import org.apache.commons.lang3.Validate; public class SimpleBitStorage implements BitStorage { @@ -201,4 +202,44 @@ public class SimpleBitStorage implements BitStorage { super(message); } } + + // JettPack start - lithium: chunk.serialization + @Override + public void compact(Palette srcPalette, Palette dstPalette, short[] out) { + if (this.size >= Short.MAX_VALUE) { + throw new IllegalStateException("Array too large"); + } + + if (this.size != out.length) { + throw new IllegalStateException("Array size mismatch"); + } + + short[] mappings = new short[(int) (this.mask + 1)]; + + int idx = 0; + + for (long word : this.data) { + long bits = word; + + for (int elementIdx = 0; elementIdx < this.valuesPerLong; ++elementIdx) { + int value = (int) (bits & this.mask); + int remappedId = mappings[value]; + + if (remappedId == 0) { + remappedId = dstPalette.idFor(srcPalette.valueFor(value)) + 1; + mappings[value] = (short) remappedId; + } + + out[idx] = (short) (remappedId - 1); + bits >>= this.bits; + + ++idx; + + if (idx >= this.size) { + return; + } + } + } + } + // JettPack end } diff --git a/src/main/java/net/minecraft/util/ZeroBitStorage.java b/src/main/java/net/minecraft/util/ZeroBitStorage.java index 5d8e9bdf5538b19681f21949368d862fab8a89ad..2224245a985dfe78565dfdb42840d3ed43530525 100644 --- a/src/main/java/net/minecraft/util/ZeroBitStorage.java +++ b/src/main/java/net/minecraft/util/ZeroBitStorage.java @@ -2,6 +2,7 @@ package net.minecraft.util; import java.util.Arrays; import java.util.function.IntConsumer; +import net.minecraft.world.level.chunk.Palette; // JettPack import org.apache.commons.lang3.Validate; public class ZeroBitStorage implements BitStorage { @@ -72,4 +73,6 @@ public class ZeroBitStorage implements BitStorage { public BitStorage copy() { return this; } + + @Override public void compact(Palette srcPalette, Palette dstPalette, short[] out) {} // JettPack } diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java index bef8f4d080fb6067f87b9df986ac33f7003c2a84..a727875f6db8d9950d1d9ada67dd39480f3b0da9 100644 --- a/src/main/java/net/minecraft/world/entity/Entity.java +++ b/src/main/java/net/minecraft/world/entity/Entity.java @@ -2578,39 +2578,64 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { return !this.isRemoved(); } + // Mirai start - lithium: suffocation + /** + * @author 2No2Name + * @reason Avoid stream code, use optimized chunk section iteration order + */ public boolean isInWall() { + // [VanillaCopy] The whole method functionality including bug below. Cannot use ChunkAwareBlockCollisionSweeper due to ignoring of oversized blocks if (this.noPhysics) { return false; - } else { - float f = this.dimensions.width * 0.8F; - AABB axisalignedbb = AABB.ofSize(this.getEyePosition(), (double) f, 1.0E-6D, (double) f); - - BlockPos.MutableBlockPos blockposition = new BlockPos.MutableBlockPos(); - int minX = Mth.floor(axisalignedbb.minX); - int minY = Mth.floor(axisalignedbb.minY); - int minZ = Mth.floor(axisalignedbb.minZ); - int maxX = Mth.floor(axisalignedbb.maxX); - int maxY = Mth.floor(axisalignedbb.maxY); - int maxZ = Mth.floor(axisalignedbb.maxZ); - for (int fz = minZ; fz <= maxZ; ++fz) { - for (int fx = minX; fx <= maxX; ++fx) { - for (int fy = minY; fy <= maxY; ++fy) { - net.minecraft.world.level.chunk.LevelChunk chunk = (net.minecraft.world.level.chunk.LevelChunk)this.level.getChunkIfLoadedImmediately(fx >> 4, fz >> 4); - if (chunk == null) { - continue; + } + Vec3 position = this.getEyePosition(); + double suffocationRadius = Math.abs((double) (this.dimensions.width * 0.8f) / 2.0); + + double suffocationMinX = position.x - suffocationRadius; + double suffocationMinY = position.y - 5.0E-7; + double suffocationMinZ = position.z - suffocationRadius; + double suffocationMaxX = position.x + suffocationRadius; + double suffocationMaxY = position.y + 5.0E-7; + double suffocationMaxZ = position.z + suffocationRadius; + int minX = Mth.floor(suffocationMinX); + int minY = Mth.floor(suffocationMinY); + int minZ = Mth.floor(suffocationMinZ); + int maxX = Mth.floor(suffocationMaxX); + int maxY = Mth.floor(suffocationMaxY); + int maxZ = Mth.floor(suffocationMaxZ); + + Level level = this.level; + //skip getting blocks when the entity is outside the world height + //also avoids infinite loop with entities below y = Integer.MIN_VALUE (some modded servers do that) + if (level.getMinBuildHeight() > maxY || level.getMaxBuildHeight() < minY) { + return false; + } + + BlockPos.MutableBlockPos blockposition = new BlockPos.MutableBlockPos(); + VoxelShape suffocationShape = null; + + for (int y = minY; y <= maxY; y++) { + for (int z = minZ; z <= maxZ; z++) { + for (int x = minX; x <= maxX; x++) { + blockposition.set(x, y, z); + BlockState iblockdata = level.getBlockState(blockposition); + if (!iblockdata.isAir() && iblockdata.isSuffocating(this.level, blockposition)) { + if (suffocationShape == null) { + suffocationShape = Shapes.create(new AABB(suffocationMinX, suffocationMinY, suffocationMinZ, suffocationMaxX, suffocationMaxY, suffocationMaxZ)); } - BlockState iblockdata = chunk.getBlockStateFinal(fx, fy, fz); - blockposition.set(fx, fy, fz); - if (!iblockdata.isAir() && iblockdata.isSuffocating(this.level, blockposition) && Shapes.joinIsNotEmpty(iblockdata.getCollisionShape(this.level, blockposition).move((double) blockposition.getX(), (double) blockposition.getY(), (double) blockposition.getZ()), Shapes.create(axisalignedbb), BooleanOp.AND)) { + if (Shapes.joinIsNotEmpty(iblockdata.getCollisionShape(this.level, blockposition). + move(blockposition.getX(), blockposition.getY(), blockposition.getZ()), + suffocationShape, BooleanOp.AND)) { return true; } } } } - return false; } + return false; } + // Mirai end public InteractionResult interact(Player player, InteractionHand hand) { return InteractionResult.PASS; diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java index 9fe6d0700f8f4d4cc018bcb4f33fffaa5f51a1d9..9d824c56914292810091b358ff7d718add617f21 100644 --- a/src/main/java/net/minecraft/world/entity/LivingEntity.java +++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java @@ -577,11 +577,11 @@ public abstract class LivingEntity extends Entity { } protected void tryAddFrost() { - if (!this.getBlockStateOnLegacy().isAir()) { + //if (!this.getBlockStateOnLegacy().isAir()) { // Mirai int i = this.getTicksFrozen(); if (i > 0) { - AttributeInstance attributemodifiable = this.getAttribute(Attributes.MOVEMENT_SPEED); + AttributeInstance attributemodifiable = this.getBlockStateOnLegacy().isAir() ? null : this.getAttribute(Attributes.MOVEMENT_SPEED); // Mirai if (attributemodifiable == null) { return; @@ -591,7 +591,7 @@ public abstract class LivingEntity extends Entity { attributemodifiable.addTransientModifier(new AttributeModifier(LivingEntity.SPEED_MODIFIER_POWDER_SNOW_UUID, "Powder snow slow", (double) f, AttributeModifier.Operation.ADDITION)); } - } + //} // Mirai } @@ -2552,6 +2552,8 @@ public abstract class LivingEntity extends Entity { } protected void updateSwingTime() { + if (!this.swinging && this.swingTime == 0) return; // Mirai + int i = this.getCurrentSwingDuration(); if (this.swinging) { @@ -3531,6 +3533,8 @@ public abstract class LivingEntity extends Entity { } private void updateFallFlying() { + if (!this.isFallFlying()) return; // Mirai + boolean flag = this.getSharedFlag(7); if (flag && !this.onGround && !this.isPassenger() && !this.hasEffect(MobEffects.LEVITATION)) { diff --git a/src/main/java/net/minecraft/world/entity/ai/attributes/AttributeMap.java b/src/main/java/net/minecraft/world/entity/ai/attributes/AttributeMap.java index 00fc98797aea23e1f586b8e7f85fc27e2019352f..1094792600f30632b2aa79dd0025dac3e4937aa6 100644 --- a/src/main/java/net/minecraft/world/entity/ai/attributes/AttributeMap.java +++ b/src/main/java/net/minecraft/world/entity/ai/attributes/AttributeMap.java @@ -16,11 +16,13 @@ import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.ListTag; import net.minecraft.resources.ResourceLocation; import org.slf4j.Logger; +import it.unimi.dsi.fastutil.objects.Reference2ReferenceOpenHashMap; // Mirai +import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet; // Mirai public class AttributeMap { private static final Logger LOGGER = LogUtils.getLogger(); - private final Map attributes = Maps.newHashMap(); - private final Set dirtyAttributes = Sets.newHashSet(); + private final Map attributes = new Reference2ReferenceOpenHashMap<>(0); // Mirai + private final Set dirtyAttributes = new ReferenceOpenHashSet<>(0); // Mirai private final AttributeSupplier supplier; private final net.minecraft.world.entity.LivingEntity entity; // Purpur private final java.util.function.Function createInstance; // Pufferfish diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/GoalSelector.java b/src/main/java/net/minecraft/world/entity/ai/goal/GoalSelector.java index 872ec431ae6beb0ef603d833f38aedb9d87e5466..759a53072fe3672c9fd7ba505bf0bdb138b446d1 100644 --- a/src/main/java/net/minecraft/world/entity/ai/goal/GoalSelector.java +++ b/src/main/java/net/minecraft/world/entity/ai/goal/GoalSelector.java @@ -12,6 +12,7 @@ import java.util.function.Supplier; import java.util.stream.Stream; import net.minecraft.util.profiling.ProfilerFiller; import org.slf4j.Logger; +import it.unimi.dsi.fastutil.objects.ObjectLinkedOpenHashSet; // Lithium public class GoalSelector { private static final Logger LOGGER = LogUtils.getLogger(); @@ -27,7 +28,7 @@ public class GoalSelector { } }; private final Map lockedFlags = new EnumMap<>(Goal.Flag.class); - public final Set availableGoals = Sets.newLinkedHashSet(); + public final Set availableGoals = new ObjectLinkedOpenHashSet<>(); // Lithium - replace AI goal set with optimized collection private final Supplier profiler; private final EnumSet disabledFlags = EnumSet.noneOf(Goal.Flag.class); // Paper unused, but dummy to prevent plugins from crashing as hard. Theyll need to support paper in a special case if this is super important, but really doesn't seem like it would be. private final com.destroystokyo.paper.util.set.OptimizedSmallEnumSet goalTypes = new com.destroystokyo.paper.util.set.OptimizedSmallEnumSet<>(Goal.Flag.class); // Paper - remove streams from pathfindergoalselector diff --git a/src/main/java/net/minecraft/world/entity/raid/Raid.java b/src/main/java/net/minecraft/world/entity/raid/Raid.java index 6a0a1731fd6804eb69d3641213712d31bce085b2..06110d58342d07cf454f1829140855ac762c8b80 100644 --- a/src/main/java/net/minecraft/world/entity/raid/Raid.java +++ b/src/main/java/net/minecraft/world/entity/raid/Raid.java @@ -79,6 +79,7 @@ public class Raid { public static final int TICKS_PER_DAY = 24000; public static final int DEFAULT_MAX_BAD_OMEN_LEVEL = 5; private static final int LOW_MOB_THRESHOLD = 2; + private static final ItemStack CACHED_LEADER_BANNER; // KeYi - cache the banner private static final Component RAID_NAME_COMPONENT = Component.translatable("event.minecraft.raid"); private static final Component VICTORY = Component.translatable("event.minecraft.raid.victory"); private static final Component DEFEAT = Component.translatable("event.minecraft.raid.defeat"); @@ -108,6 +109,20 @@ public class Raid { private int celebrationTicks; private Optional waveSpawnPos; + // KeYi start - cache the banner + static { + ItemStack itemstack = new ItemStack(Items.WHITE_BANNER); + CompoundTag nbttagcompound = new CompoundTag(); + ListTag nbttaglist = (new BannerPattern.Builder()).addPattern(BannerPatterns.RHOMBUS_MIDDLE, DyeColor.CYAN).addPattern(BannerPatterns.STRIPE_BOTTOM, DyeColor.LIGHT_GRAY).addPattern(BannerPatterns.STRIPE_CENTER, DyeColor.GRAY).addPattern(BannerPatterns.BORDER, DyeColor.LIGHT_GRAY).addPattern(BannerPatterns.STRIPE_MIDDLE, DyeColor.BLACK).addPattern(BannerPatterns.HALF_HORIZONTAL, DyeColor.LIGHT_GRAY).addPattern(BannerPatterns.CIRCLE_MIDDLE, DyeColor.LIGHT_GRAY).addPattern(BannerPatterns.BORDER, DyeColor.BLACK).toListTag(); + + nbttagcompound.put("Patterns", nbttaglist); + BlockItem.setBlockEntityData(itemstack, BlockEntityType.BANNER, nbttagcompound); + itemstack.hideTooltipPart(ItemStack.TooltipPart.ADDITIONAL); + itemstack.setHoverName(Component.translatable("block.minecraft.ominous_banner").withStyle(ChatFormatting.GOLD)); + CACHED_LEADER_BANNER = itemstack; + } + // KeYi end + public Raid(int id, ServerLevel world, BlockPos pos) { this.raidEvent = new ServerBossEvent(Raid.RAID_NAME_COMPONENT, BossEvent.BossBarColor.RED, BossEvent.BossBarOverlay.NOTCHED_10); this.random = RandomSource.create(); @@ -270,6 +285,13 @@ public class Raid { } public void tick() { + // KeYi start - Lithium - ai.raid: reduce updates + if (this.isBarDirty) { + this.raidEvent.setProgress(Mth.clamp(this.getHealthOfLivingRaiders() / this.totalHealth, 0.0F, 1.0F)); + this.isBarDirty = false; + } + // KeYi end + if (!this.isStopped()) { if (this.status == Raid.RaidStatus.ONGOING) { boolean flag = this.active; @@ -625,8 +647,10 @@ public class Raid { } + private boolean isBarDirty = false; // KeYi - Lithium - ai.raid: reduce updates + public void updateBossbar() { - this.raidEvent.setProgress(Mth.clamp(this.getHealthOfLivingRaiders() / this.totalHealth, 0.0F, 1.0F)); + this.isBarDirty = true; // KeYi - Lithium - ai.raid: reduce updates } public float getHealthOfLivingRaiders() { @@ -678,15 +702,7 @@ public class Raid { } public static ItemStack getLeaderBannerInstance() { - ItemStack itemstack = new ItemStack(Items.WHITE_BANNER); - CompoundTag nbttagcompound = new CompoundTag(); - ListTag nbttaglist = (new BannerPattern.Builder()).addPattern(BannerPatterns.RHOMBUS_MIDDLE, DyeColor.CYAN).addPattern(BannerPatterns.STRIPE_BOTTOM, DyeColor.LIGHT_GRAY).addPattern(BannerPatterns.STRIPE_CENTER, DyeColor.GRAY).addPattern(BannerPatterns.BORDER, DyeColor.LIGHT_GRAY).addPattern(BannerPatterns.STRIPE_MIDDLE, DyeColor.BLACK).addPattern(BannerPatterns.HALF_HORIZONTAL, DyeColor.LIGHT_GRAY).addPattern(BannerPatterns.CIRCLE_MIDDLE, DyeColor.LIGHT_GRAY).addPattern(BannerPatterns.BORDER, DyeColor.BLACK).toListTag(); - - nbttagcompound.put("Patterns", nbttaglist); - BlockItem.setBlockEntityData(itemstack, BlockEntityType.BANNER, nbttagcompound); - itemstack.hideTooltipPart(ItemStack.TooltipPart.ADDITIONAL); - itemstack.setHoverName(Component.translatable("block.minecraft.ominous_banner").withStyle(ChatFormatting.GOLD)); - return itemstack; + return CACHED_LEADER_BANNER; // KeYi - cache the banner } @Nullable diff --git a/src/main/java/net/minecraft/world/level/GameRules.java b/src/main/java/net/minecraft/world/level/GameRules.java index 17e869074b8cf29a8c3280499a27e95179896750..97a5aec7da267b8b9f6d191c871316ccb89c448c 100644 --- a/src/main/java/net/minecraft/world/level/GameRules.java +++ b/src/main/java/net/minecraft/world/level/GameRules.java @@ -27,6 +27,7 @@ import net.minecraft.network.protocol.game.ClientboundGameEventPacket; import net.minecraft.server.MinecraftServer; import net.minecraft.server.level.ServerPlayer; import org.slf4j.Logger; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; // JettPack public class GameRules { @@ -111,14 +112,16 @@ public class GameRules { public GameRules() { // Pufferfish start - use this to ensure gameruleArray is initialized - this((Map) GameRules.GAME_RULE_TYPES.entrySet().stream().collect(ImmutableMap.toImmutableMap(Entry::getKey, (entry) -> { + // JettPack start - lithium: store gamerules in fastutil hashmap + this(new Object2ObjectOpenHashMap<>((Map) GameRules.GAME_RULE_TYPES.entrySet().stream().collect(ImmutableMap.toImmutableMap(Entry::getKey, (entry) -> { return ((GameRules.Type) entry.getValue()).createRule(); - }))); + })))); + // JettPack end // Pufferfish end } private GameRules(Map, GameRules.Value> rules) { - this.rules = rules; + this.rules = new Object2ObjectOpenHashMap<>(rules); // JettPack - lithium: store gamerules in fastutil hashmap // Pufferfish start int arraySize = rules.keySet().stream().mapToInt(key -> key.gameRuleIndex).max().orElse(-1) + 1; diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java index cf8693d02de53f1e02d55936f889c5724889e3f5..9f0126aedb94f74d01dc47112496160cc4e1f02c 100644 --- a/src/main/java/net/minecraft/world/level/Level.java +++ b/src/main/java/net/minecraft/world/level/Level.java @@ -117,9 +117,9 @@ public abstract class Level implements LevelAccessor, AutoCloseable { public static final int TICKS_PER_DAY = 24000; public static final int MAX_ENTITY_SPAWN_Y = 20000000; public static final int MIN_ENTITY_SPAWN_Y = -20000000; - protected final List blockEntityTickers = Lists.newArrayList(); public final int getTotalTileEntityTickers() { return this.blockEntityTickers.size(); } // Paper + protected final List blockEntityTickers = me.jellysquid.mods.lithium.common.util.collections.HashedReferenceList.wrapper(Lists.newArrayList()); public final int getTotalTileEntityTickers() { return this.blockEntityTickers.size(); } // Paper // Jettpack - lithium: HashedReferenceList protected final NeighborUpdater neighborUpdater; - private final List pendingBlockEntityTickers = Lists.newArrayList(); + private final List pendingBlockEntityTickers = me.jellysquid.mods.lithium.common.util.collections.HashedReferenceList.wrapper(Lists.newArrayList()); // Jettpack - lithium: HashedReferenceList private boolean tickingBlockEntities; public final Thread thread; private final boolean isDebug; diff --git a/src/main/java/net/minecraft/world/level/block/Block.java b/src/main/java/net/minecraft/world/level/block/Block.java index e94ca6283a0471a49b31942de763472ccb989dcb..9212786dd26a48999e7d7159a77f19b6fc66a6fe 100644 --- a/src/main/java/net/minecraft/world/level/block/Block.java +++ b/src/main/java/net/minecraft/world/level/block/Block.java @@ -673,11 +673,18 @@ public class Block extends BlockBehaviour implements ItemLike { private final BlockState first; private final BlockState second; private final Direction direction; + private int hash; // JettPack public BlockStatePairKey(BlockState self, BlockState other, Direction facing) { this.first = self; this.second = other; this.direction = facing; + // JettPack start - lithium: cached_hashcode + int hash = this.first.hashCode(); + hash = 31 * hash + this.second.hashCode(); + hash = 31 * hash + this.direction.hashCode(); + this.hash = hash; + // JettPack end } public boolean equals(Object object) { @@ -693,11 +700,7 @@ public class Block extends BlockBehaviour implements ItemLike { } public int hashCode() { - int i = this.first.hashCode(); - - i = 31 * i + this.second.hashCode(); - i = 31 * i + this.direction.hashCode(); - return i; + return this.hash; // JettPack } } } diff --git a/src/main/java/net/minecraft/world/level/block/piston/PistonMovingBlockEntity.java b/src/main/java/net/minecraft/world/level/block/piston/PistonMovingBlockEntity.java index 7c59d44a3bafdc65f453d77ff3e25cffb742ad6c..636721a111cad13e7329f1157981ca03a8f339b3 100644 --- a/src/main/java/net/minecraft/world/level/block/piston/PistonMovingBlockEntity.java +++ b/src/main/java/net/minecraft/world/level/block/piston/PistonMovingBlockEntity.java @@ -52,6 +52,74 @@ public class PistonMovingBlockEntity extends BlockEntity { this.extending = extending; this.isSourcePiston = source; } + // JettPack start - lithium: block.moving_block_shapes + private static final VoxelShape[] PISTON_BASE_WITH_MOVING_HEAD_SHAPES = precomputePistonBaseWithMovingHeadShapes(); + + /** + * We cache the offset and simplified VoxelShapes that are otherwise constructed on every call of getCollisionShape. + * For each offset direction and distance (6 directions, 2 distances each, and no direction with 0 distance) we + * store the offset and simplified VoxelShapes in the original VoxelShape when they are accessed the first time. + * We use safe publication, because both the Render and Server thread are using the cache. + * + * @param blockShape the original shape, must not be modified after passing it as an argument to this method + * @param offset the offset distance + * @param direction the offset direction + * @return blockShape offset and simplified + */ + private static VoxelShape getOffsetAndSimplified(VoxelShape blockShape, float offset, Direction direction) { + VoxelShape offsetSimplifiedShape = blockShape.getOffsetSimplifiedShape(offset, direction); + if (offsetSimplifiedShape == null) { + //create the offset shape and store it for later use + offsetSimplifiedShape = blockShape.move(direction.getStepX() * offset, direction.getStepY() * offset, direction.getStepZ() * offset).optimize(); + blockShape.setShape(offset, direction, offsetSimplifiedShape); + } + return offsetSimplifiedShape; + } + + /** + * Precompute all 18 possible configurations for the merged piston base and head shape. + * + * @return The array of the merged VoxelShapes, indexed by {@link PistonBlockEntityMixin#getIndexForMergedShape(float, Direction)} + */ + private static VoxelShape[] precomputePistonBaseWithMovingHeadShapes() { + float[] offsets = {0f, 0.5f, 1f}; + Direction[] directions = Direction.values(); + + VoxelShape[] mergedShapes = new VoxelShape[offsets.length * directions.length]; + + for (Direction facing : directions) { + VoxelShape baseShape = Blocks.PISTON.defaultBlockState().setValue(PistonBaseBlock.EXTENDED, true) + .setValue(PistonBaseBlock.FACING, facing).getCollisionShape(null, null); + for (float offset : offsets) { + //this cache is only required for the merged piston head + base shape. + //this shape is only used when !this.extending + //here: isShort = this.extending != 1.0F - this.progress < 0.25F can be simplified to: + //isShort = f < 0.25F , because f = getAmountExtended(this.progress) can be simplified to f == 1.0F - this.progress + //therefore isShort is dependent on the offset: + boolean isShort = offset < 0.25f; + + VoxelShape headShape = (Blocks.PISTON_HEAD.defaultBlockState().setValue(PistonHeadBlock.FACING, facing)) + .setValue(PistonHeadBlock.SHORT, isShort).getCollisionShape(null, null); + + VoxelShape offsetHead = headShape.move(facing.getStepX() * offset, + facing.getStepY() * offset, + facing.getStepZ() * offset); + mergedShapes[getIndexForMergedShape(offset, facing)] = Shapes.or(baseShape, offsetHead); + } + + } + + return mergedShapes; + } + + private static int getIndexForMergedShape(float offset, Direction direction) { + if (offset != 0f && offset != 0.5f && offset != 1f) { + return -1; + } + //shape of offset 0 is still dependent on the direction, due to piston head and base being directional blocks + return (int) (2 * offset) + (3 * direction.get3DDataValue()); + } + // JettPack end @Override public CompoundTag getUpdateTag() { @@ -351,10 +419,27 @@ public class PistonMovingBlockEntity extends BlockEntity { } float f = this.getExtendedProgress(this.progress); + // JettPack start - lithium: block.moving_block_shapes + if (this.extending || !this.isSourcePiston || !(this.movedState.getBlock() instanceof PistonBaseBlock)) { + //here voxelShape2.isEmpty() is guaranteed, vanilla code would call union() which calls simplify() + VoxelShape blockShape = blockState.getCollisionShape(world, pos); + + //we cache the simplified shapes, as the simplify() method costs a lot of CPU time and allocates several objects + VoxelShape offsetAndSimplified = getOffsetAndSimplified(blockShape, Math.abs(f), f < 0f ? this.direction.getOpposite() : this.direction); + return offsetAndSimplified; + } else { + //retracting piston heads have to act like their base as well, as the base block is replaced with the moving block + //f >= 0f is guaranteed (assuming no other mod interferes) + int index = getIndexForMergedShape(f, this.direction); + return PISTON_BASE_WITH_MOVING_HEAD_SHAPES[index]; + } + /* double d = (double)((float)this.direction.getStepX() * f); double e = (double)((float)this.direction.getStepY() * f); double g = (double)((float)this.direction.getStepZ() * f); return Shapes.or(voxelShape, blockState.getCollisionShape(world, pos).move(d, e, g)); + */ + // JettPack end } } diff --git a/src/main/java/net/minecraft/world/level/chunk/PaletteResize.java b/src/main/java/net/minecraft/world/level/chunk/PaletteResize.java index acae3eb30e0689048937f479dc3070f0688abdad..9c2b79655f2c63a208c7087d5d897db0fb23f697 100644 --- a/src/main/java/net/minecraft/world/level/chunk/PaletteResize.java +++ b/src/main/java/net/minecraft/world/level/chunk/PaletteResize.java @@ -1,5 +1,5 @@ package net.minecraft.world.level.chunk; -interface PaletteResize { +public interface PaletteResize { // JettPack - make public int onResize(int newBits, T object); } diff --git a/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java b/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java index 7908360dd47937b2cb702e381802b7b278a5198e..5f578da4a7251b17d6a12821a3cd090e66b52a8a 100644 --- a/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java +++ b/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java @@ -22,8 +22,23 @@ import net.minecraft.util.Mth; import net.minecraft.util.SimpleBitStorage; import net.minecraft.util.ThreadingDetector; import net.minecraft.util.ZeroBitStorage; +import me.jellysquid.mods.lithium.common.world.chunk.LithiumHashPalette; // JettPack public class PalettedContainer implements PaletteResize, PalettedContainerRO { + // JettPack start - lithium: chunk.serialization + private static final ThreadLocal CACHED_ARRAY_4096 = ThreadLocal.withInitial(() -> new short[4096]); + private static final ThreadLocal CACHED_ARRAY_64 = ThreadLocal.withInitial(() -> new short[64]); + private Optional asOptional(long[] data) { + return Optional.of(Arrays.stream(data)); + } + private short[] getOrCreate(int size) { + return switch (size) { + case 64 -> CACHED_ARRAY_64.get(); + case 4096 -> CACHED_ARRAY_4096.get(); + default -> new short[size]; + }; + } + // JettPack end private static final int MIN_PALETTE_BITS = 0; private final PaletteResize dummyPaletteResize = (newSize, added) -> { return 0; @@ -299,30 +314,54 @@ public class PalettedContainer implements PaletteResize, PalettedContainer public synchronized PalettedContainerRO.PackedData pack(IdMap idList, PalettedContainer.Strategy paletteProvider) { // Paper - synchronize this.acquire(); - PalettedContainerRO.PackedData var12; + // JettPack start - lithium: chunk.serialization + Optional data = Optional.empty(); + List elements = null; try { - HashMapPalette hashMapPalette = new HashMapPalette<>(idList, this.data.storage.getBits(), this.dummyPaletteResize); - int i = paletteProvider.size(); - int[] is = new int[i]; - this.data.storage.unpack(is); - swapPalette(is, (id) -> { - return hashMapPalette.idFor(this.data.palette.valueFor(id)); - }); - int j = paletteProvider.calculateBitsForSerialization(idList, hashMapPalette.getSize()); - Optional optional; - if (j != 0) { - SimpleBitStorage simpleBitStorage = new SimpleBitStorage(j, i, is); - optional = Optional.of(Arrays.stream(simpleBitStorage.getRaw())); - } else { - optional = Optional.empty(); + // The palette that will be serialized + LithiumHashPalette hashPalette = null; + + final Palette palette = this.data.palette(); + final BitStorage storage = this.data.storage(); + if (storage instanceof ZeroBitStorage || palette.getSize() == 1) { + // If the palette only contains one entry, don't attempt to repack it. + elements = List.of(palette.valueFor(0)); + } else if (palette instanceof LithiumHashPalette lithiumHashPalette) { + hashPalette = lithiumHashPalette; } - var12 = new PalettedContainerRO.PackedData<>(hashMapPalette.getEntries(), optional); + if (elements == null) { + LithiumHashPalette compactedPalette = new LithiumHashPalette<>(idList, storage.getBits(), this.dummyPaletteResize); + short[] array = this.getOrCreate(paletteProvider.size()); + + storage.compact(this.data.palette(), compactedPalette, array); + + // If the palette didn't change during compaction, do a simple copy of the data array + if (hashPalette != null && hashPalette.getSize() == compactedPalette.getSize() && storage.getBits() == paletteProvider.calculateBitsForSerialization(idList, hashPalette.getSize())) { // paletteSize can de-sync from palette - see https://github.com/CaffeineMC/lithium-fabric/issues/279 + data = this.asOptional(storage.getRaw().clone()); + elements = hashPalette.getElements(); + } else { + int bits = paletteProvider.calculateBitsForSerialization(idList, compactedPalette.getSize()); + if (bits != 0) { + // Re-pack the integer array as the palette has changed size + SimpleBitStorage copy = new SimpleBitStorage(bits, array.length); + for (int i = 0; i < array.length; ++i) { + copy.set(i, array[i]); + } + + // We don't need to clone the data array as we are the sole owner of it + data = this.asOptional(copy.getRaw()); + } + + elements = compactedPalette.getElements(); + } + } } finally { this.release(); } - return var12; + return new PalettedContainerRO.PackedData<>(elements, data); + // JettPack end } private static void swapPalette(int[] is, IntUnaryOperator applier) { @@ -362,17 +401,37 @@ public class PalettedContainer implements PaletteResize, PalettedContainer @Override public void count(PalettedContainer.CountConsumer counter) { - if (this.data.palette.getSize() == 1) { - counter.accept(this.data.palette.valueFor(0), this.data.storage.getSize()); - } else { - Int2IntOpenHashMap int2IntOpenHashMap = new Int2IntOpenHashMap(); - this.data.storage.getAll((key) -> { - int2IntOpenHashMap.addTo(key, 1); - }); - int2IntOpenHashMap.int2IntEntrySet().forEach((entry) -> { - counter.accept(this.data.palette.valueFor(entry.getIntKey()), entry.getIntValue()); - }); + // JettPack start - lithium: chunk.serialization + int len = this.data.palette().getSize(); + + // Do not allocate huge arrays if we're using a large palette + if (len > 4096) { + // VanillaCopy + if (this.data.palette.getSize() == 1) { + counter.accept(this.data.palette.valueFor(0), this.data.storage.getSize()); + } else { + Int2IntOpenHashMap int2IntOpenHashMap = new Int2IntOpenHashMap(); + this.data.storage.getAll((key) -> { + int2IntOpenHashMap.addTo(key, 1); + }); + int2IntOpenHashMap.int2IntEntrySet().forEach((entry) -> { + counter.accept(this.data.palette.valueFor(entry.getIntKey()), entry.getIntValue()); + }); + } + } + + short[] counts = new short[len]; + + this.data.storage().getAll(i -> counts[i]++); + + for (int i = 0; i < counts.length; i++) { + T obj = this.data.palette().valueFor(i); + + if (obj != null) { + counter.accept(obj, counts[i]); + } } + // JettPack end } static record Configuration(Palette.Factory factory, int bits) { diff --git a/src/main/java/net/minecraft/world/level/entity/EntitySectionStorage.java b/src/main/java/net/minecraft/world/level/entity/EntitySectionStorage.java index 4c1e7c219e1ca153be4423347bd239ebaec4a31d..b3fedb74c71627d58afd47ddf0f3d368e41ab64f 100644 --- a/src/main/java/net/minecraft/world/level/entity/EntitySectionStorage.java +++ b/src/main/java/net/minecraft/world/level/entity/EntitySectionStorage.java @@ -33,7 +33,7 @@ public class EntitySectionStorage { } public void forEachAccessibleNonEmptySection(AABB box, Consumer> action) { - int i = 2; + // Mirai start - lithium: fast retrieval int j = SectionPos.posToSectionCoord(box.minX - 2.0D); int k = SectionPos.posToSectionCoord(box.minY - 4.0D); int l = SectionPos.posToSectionCoord(box.minZ - 2.0D); @@ -41,25 +41,67 @@ public class EntitySectionStorage { int n = SectionPos.posToSectionCoord(box.maxY + 0.0D); int o = SectionPos.posToSectionCoord(box.maxZ + 2.0D); - for(int p = j; p <= m; ++p) { - long q = SectionPos.asLong(p, 0, 0); - long r = SectionPos.asLong(p, -1, -1); - LongIterator longIterator = this.sectionIds.subSet(q, r + 1L).iterator(); - - while(longIterator.hasNext()) { - long s = longIterator.nextLong(); - int t = SectionPos.y(s); - int u = SectionPos.z(s); - if (t >= k && t <= n && u >= l && u <= o) { - EntitySection entitySection = this.sections.get(s); - if (entitySection != null && !entitySection.isEmpty() && entitySection.getStatus().isAccessible()) { - action.accept(entitySection); + if (m >= j + 4 || o >= l + 4) { + // Vanilla is likely more optimized when shooting entities with TNT cannons over huge distances. + // Choosing a cutoff of 4 chunk size, as it becomes more likely that these entity sections do not exist when + // they are far away from the shot entity (player despawn range, position maybe not on the ground, etc) + for (int p = j; p <= m; p++) { + long q = SectionPos.asLong(p, 0, 0); + long r = SectionPos.asLong(p, -1, -1); + LongIterator longIterator = this.sectionIds.subSet(q, r + 1L).iterator(); + + while (longIterator.hasNext()) { + long s = longIterator.nextLong(); + int t = SectionPos.y(s); + int u = SectionPos.z(s); + if (t >= k && t <= n && u >= l && u <= o) { + EntitySection entitySection = this.sections.get(s); + if (entitySection != null && !entitySection.isEmpty() && entitySection.getStatus().isAccessible()) { + action.accept(entitySection); + } } } } + } else { + // Vanilla order of the AVL long set is sorting by ascending long value. The x, y, z positions are packed into + // a long with the x position's lowest 22 bits placed at the MSB. + // Therefore the long is negative iff the 22th bit of the x position is set, which happens iff the x position + // is negative. A positive x position will never have its 22th bit set, as these big coordinates are far outside + // the world. y and z positions are treated as unsigned when sorting by ascending long value, as their sign bits + // are placed somewhere inside the packed long + for (int x = j; x <= m; x++) { + for (int z = Math.max(l, 0); z <= o; z++) { + this.forEachInColumn(x, k, n, z, action); + } + + int bound = Math.min(-1, o); + for (int z = l; z <= bound; z++) { + this.forEachInColumn(x, k, n, z, action); + } + } } + // Mirai end + } + // Mirai start - lithium: fast retrieval + private void forEachInColumn(int x, int k, int n, int z, Consumer> action) { + //y from negative to positive, but y is treated as unsigned + for (int y = Math.max(k, 0); y <= n; y++) { + this.consumeSection(SectionPos.asLong(x, y, z), action); + } + int bound = Math.min(-1, n); + for (int y = k; y <= bound; y++) { + this.consumeSection(SectionPos.asLong(x, y, z), action); + } + } + + private void consumeSection(long pos, Consumer> action) { + EntitySection entitySection = this.getSection(pos); + if (entitySection != null && 0 != entitySection.size() && entitySection.getStatus().isAccessible()) { + action.accept(entitySection); + } } + // Mirai end public LongStream getExistingSectionPositionsInChunk(long chunkPos) { int i = ChunkPos.getX(chunkPos); diff --git a/src/main/java/net/minecraft/world/level/levelgen/NoiseBasedChunkGenerator.java b/src/main/java/net/minecraft/world/level/levelgen/NoiseBasedChunkGenerator.java index cf87490a446285132daaf9d90154ac6d477a62fe..3d6c50822701a3828cbde704f419d1c900a67954 100644 --- a/src/main/java/net/minecraft/world/level/levelgen/NoiseBasedChunkGenerator.java +++ b/src/main/java/net/minecraft/world/level/levelgen/NoiseBasedChunkGenerator.java @@ -67,6 +67,7 @@ public final class NoiseBasedChunkGenerator extends ChunkGenerator { public final Registry noises; public final Holder settings; private final Aquifer.FluidPicker globalFluidPicker; + private int cachedSeaLevel; // Mirai - lithium: gen public NoiseBasedChunkGenerator(Registry structureSetRegistry, Registry noiseRegistry, BiomeSource populationSource, Holder settings) { super(structureSetRegistry, Optional.empty(), populationSource); @@ -83,6 +84,7 @@ public final class NoiseBasedChunkGenerator extends ChunkGenerator { this.globalFluidPicker = (j, k, l) -> { return k < Math.min(-54, i) ? aquifer_b : aquifer_b1; }; + this.cachedSeaLevel = ((NoiseGeneratorSettings) this.settings.value()).seaLevel(); // Mirai - lithium: gen } @Override @@ -398,10 +400,19 @@ public final class NoiseBasedChunkGenerator extends ChunkGenerator { return ((NoiseGeneratorSettings) this.settings.value()).noiseSettings().height(); } + // Mirai start - lithium: gen + /** + * Use cached sea level instead of retrieving from the registry every time. + * This method is called for every block in the chunk so this will save a lot of registry lookups. + * + * @author SuperCoder79 + * @reason avoid registry lookup + */ @Override public int getSeaLevel() { - return ((NoiseGeneratorSettings) this.settings.value()).seaLevel(); + return this.cachedSeaLevel; } + // Mirai end @Override public int getMinY() { diff --git a/src/main/java/net/minecraft/world/phys/AABB.java b/src/main/java/net/minecraft/world/phys/AABB.java index cfb2e46b34b2982d6724f18214557fc80cf4adfa..a9a357dbc34dc9191bdc5f0be27142a4645d626c 100644 --- a/src/main/java/net/minecraft/world/phys/AABB.java +++ b/src/main/java/net/minecraft/world/phys/AABB.java @@ -16,6 +16,15 @@ public class AABB { public final double maxY; public final double maxZ; + // JettPack start - lithium: fast_util + static { + assert Direction.Axis.X.ordinal() == 0; + assert Direction.Axis.Y.ordinal() == 1; + assert Direction.Axis.Z.ordinal() == 2; + assert Direction.Axis.values().length == 3; + } + // JettPack end + public AABB(double x1, double y1, double z1, double x2, double y2, double z2) { this.minX = Math.min(x1, x2); this.minY = Math.min(y1, y2); @@ -81,11 +90,33 @@ public class AABB { } public double min(Direction.Axis axis) { - return axis.choose(this.minX, this.minY, this.minZ); + // JettPack start - lithium: fast_util + switch (axis.ordinal()) { + case 0: //X + return this.minX; + case 1: //Y + return this.minY; + case 2: //Z + return this.minZ; + } + + throw new IllegalArgumentException(); + // JettPack end } public double max(Direction.Axis axis) { - return axis.choose(this.maxX, this.maxY, this.maxZ); + // JettPack start - lithium: fast_util + switch (axis.ordinal()) { + case 0: //X + return this.maxX; + case 1: //Y + return this.maxY; + case 2: //Z + return this.maxZ; + } + + throw new IllegalArgumentException(); + // JettPack end } @Override diff --git a/src/main/java/net/minecraft/world/phys/shapes/CubePointRange.java b/src/main/java/net/minecraft/world/phys/shapes/CubePointRange.java index a544db042c8d2ecec8d323770552c4f10ca758a6..c04da8da5b40430b61972bce32cec4e8c0370bac 100644 --- a/src/main/java/net/minecraft/world/phys/shapes/CubePointRange.java +++ b/src/main/java/net/minecraft/world/phys/shapes/CubePointRange.java @@ -4,6 +4,7 @@ import it.unimi.dsi.fastutil.doubles.AbstractDoubleList; public class CubePointRange extends AbstractDoubleList { private final int parts; + private double scale; // JettPack - lithium: shapes.precompute_shape_arrays CubePointRange(int sectionCount) { if (sectionCount <= 0) { @@ -11,10 +12,11 @@ public class CubePointRange extends AbstractDoubleList { } else { this.parts = sectionCount; } + this.scale = 1.0D / sectionCount; // JettPack - lithium: shapes.precompute_shape_arrays } public double getDouble(int i) { - return (double)i / (double)this.parts; + return i * this.scale; // JettPack - lithium: shapes.precompute_shape_arrays } public int size() { diff --git a/src/main/java/net/minecraft/world/phys/shapes/CubeVoxelShape.java b/src/main/java/net/minecraft/world/phys/shapes/CubeVoxelShape.java index 68e89dbd79171627046e89699057964e44c40e7d..959588962acb0196ec9f1cc2502e62117f6ccdc4 100644 --- a/src/main/java/net/minecraft/world/phys/shapes/CubeVoxelShape.java +++ b/src/main/java/net/minecraft/world/phys/shapes/CubeVoxelShape.java @@ -3,15 +3,25 @@ package net.minecraft.world.phys.shapes; import it.unimi.dsi.fastutil.doubles.DoubleList; import net.minecraft.core.Direction; import net.minecraft.util.Mth; +import net.minecraft.world.phys.shapes.CubePointRange; // JettPack public final class CubeVoxelShape extends VoxelShape { + private DoubleList[] list; // JettPack - lithium: shapes.precompute_shape_arrays + protected CubeVoxelShape(DiscreteVoxelShape voxels) { super(voxels); + // JettPack start - lithium: shapes.precompute_shape_arrays + this.list = new DoubleList[Direction.VALUES.length]; + + for (Direction.Axis axis : Direction.Axis.VALUES) { + this.list[axis.ordinal()] = new CubePointRange(voxels.getSize(axis)); + } + // JettPack end } @Override protected DoubleList getCoords(Direction.Axis axis) { - return new CubePointRange(this.shape.getSize(axis)); + return this.list[axis.ordinal()]; // JettPack - lithium: shapes.precompute_shape_arrays } @Override diff --git a/src/main/java/net/minecraft/world/phys/shapes/VoxelShape.java b/src/main/java/net/minecraft/world/phys/shapes/VoxelShape.java index 2182afd1b95acf14c55bddfeec17dae0a63e1f00..461ac9a464c4a66e302798032c6019bb60f6862b 100644 --- a/src/main/java/net/minecraft/world/phys/shapes/VoxelShape.java +++ b/src/main/java/net/minecraft/world/phys/shapes/VoxelShape.java @@ -26,6 +26,44 @@ public abstract class VoxelShape { } // Paper end + // JettPack start - lithium: block.moving_block_shapes + private volatile VoxelShape[] offsetAndSimplified; + + public void setShape(float offset, Direction direction, VoxelShape offsetShape) { + if (offsetShape == null) { + throw new IllegalArgumentException("offsetShape must not be null!"); + } + int index = getIndexForOffsetSimplifiedShapes(offset, direction); + VoxelShape[] offsetAndSimplifiedShapes = this.offsetAndSimplified; + if (offsetAndSimplifiedShapes == null) { + offsetAndSimplifiedShapes = new VoxelShape[1 + 2 * 6]; + } else { + offsetAndSimplifiedShapes = offsetAndSimplifiedShapes.clone(); + } + offsetAndSimplifiedShapes[index] = offsetShape; + this.offsetAndSimplified = offsetAndSimplifiedShapes; + } + + public VoxelShape getOffsetSimplifiedShape(float offset, Direction direction) { + VoxelShape[] offsetAndSimplified = this.offsetAndSimplified; + if (offsetAndSimplified == null) { + return null; + } + int index = getIndexForOffsetSimplifiedShapes(offset, direction); + return offsetAndSimplified[index]; + } + + private static int getIndexForOffsetSimplifiedShapes(float offset, Direction direction) { + if (offset != 0f && offset != 0.5f && offset != 1f) { + throw new IllegalArgumentException("offset must be one of {0f, 0.5f, 1f}"); + } + if (offset == 0f) { + return 0; //can treat offsetting by 0 in all directions the same + } + return (int) (2 * offset) + 2 * direction.get3DDataValue(); + } + // JettPack end + protected VoxelShape(DiscreteVoxelShape voxels) { // Paper - protected this.shape = voxels; } diff --git a/src/main/java/net/minecraft/world/ticks/LevelChunkTicks.java b/src/main/java/net/minecraft/world/ticks/LevelChunkTicks.java index 9f6c2e5b5d9e8d714a47c770e255d06c0ef7c190..826ced345c97bd2eb04749f42744a086fafc4ce8 100644 --- a/src/main/java/net/minecraft/world/ticks/LevelChunkTicks.java +++ b/src/main/java/net/minecraft/world/ticks/LevelChunkTicks.java @@ -16,14 +16,37 @@ import javax.annotation.Nullable; import net.minecraft.core.BlockPos; import net.minecraft.nbt.ListTag; import net.minecraft.world.level.ChunkPos; +// Mirai start - lithium: world.tick_scheduler +import it.unimi.dsi.fastutil.ints.IntOpenHashSet; +import it.unimi.dsi.fastutil.longs.Long2ReferenceAVLTreeMap; +import it.unimi.dsi.fastutil.objects.ObjectIterator; +import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap; +import me.jellysquid.mods.lithium.common.world.scheduler.OrderedTickQueue; +import net.minecraft.world.ticks.SavedTick; +import net.minecraft.world.ticks.ScheduledTick; +import net.minecraft.world.ticks.TickPriority; +import java.util.Collection; +// Mirai end public class LevelChunkTicks implements SerializableTickContainer, TickContainerAccess { - private final Queue> tickQueue = new PriorityQueue<>(ScheduledTick.DRAIN_ORDER); + private Queue> tickQueue = new PriorityQueue<>(ScheduledTick.DRAIN_ORDER); // Mirai - remove final @Nullable private List> pendingTicks; - private final Set> ticksPerPosition = new ObjectOpenCustomHashSet<>(ScheduledTick.UNIQUE_TICK_HASH); + private Set> ticksPerPosition = new ObjectOpenCustomHashSet<>(ScheduledTick.UNIQUE_TICK_HASH); // Mirai - remove final @Nullable private BiConsumer, ScheduledTick> onTickAdded; + // Mirai start - lithium: world.tick_scheduler + private static volatile Reference2IntOpenHashMap TYPE_2_INDEX; + + static { + TYPE_2_INDEX = new Reference2IntOpenHashMap<>(); + TYPE_2_INDEX.defaultReturnValue(-1); + } + + private final Long2ReferenceAVLTreeMap> tickQueuesByTimeAndPriority = new Long2ReferenceAVLTreeMap<>(); + private OrderedTickQueue nextTickQueue; + private final IntOpenHashSet allpendingTicks = new IntOpenHashSet(); + // Mirai end public LevelChunkTicks() { } @@ -35,34 +58,133 @@ public class LevelChunkTicks implements SerializableTickContainer, TickCon this.ticksPerPosition.add(ScheduledTick.probe(savedTick.type(), savedTick.pos())); } + // Mirai start - lithium: world.tick_scheduler + //Remove replaced collections + if (this.pendingTicks != null) { + for (SavedTick orderedTick : this.pendingTicks) { + this.allpendingTicks.add(tickToInt(orderedTick.pos(), orderedTick.type())); + } + } + this.ticksPerPosition = null; + this.tickQueue = null; + // Mirai end + } + + // Mirai start - lithium: world.tick_scheduler + private static int tickToInt(BlockPos pos, Object type) { + //Y coordinate is 12 bits (BlockPos.toLong) + //X and Z coordinate is 4 bits each (This scheduler is for a single chunk) + //20 bits are in use for pos + //12 bits remaining for the type, so up to 4096 different tickable blocks/fluids (not block states) -> can upgrade to long if needed + int typeIndex = TYPE_2_INDEX.getInt(type); + if (typeIndex == -1) { + typeIndex = fixMissingType2Index(type); + } + + int ret = ((pos.getX() & 0xF) << 16) | ((pos.getY() & (0xfff)) << 4) | (pos.getZ() & 0xF); + ret |= typeIndex << 20; + return ret; } + //This method must be synchronized, otherwise type->int assignments can be overwritten and therefore change + //Uses clone and volatile store to ensure only fully initialized maps are used, all threads share the same mapping + private static synchronized int fixMissingType2Index(Object type) { + //check again, other thread might have replaced the collection + int typeIndex = TYPE_2_INDEX.getInt(type); + if (typeIndex == -1) { + Reference2IntOpenHashMap clonedType2Index = TYPE_2_INDEX.clone(); + clonedType2Index.put(type, typeIndex = clonedType2Index.size()); + TYPE_2_INDEX = clonedType2Index; + if (typeIndex >= 4096) { + throw new IllegalStateException("Lithium Tick Scheduler assumes at most 4096 different block types that receive scheduled pendingTicks exist! Open an issue on GitHub if you see this error!"); + } + } + return typeIndex; + } + // Mirai end + public void setOnTickAdded(@Nullable BiConsumer, ScheduledTick> tickConsumer) { this.onTickAdded = tickConsumer; } + // Mirai start - lithium: world.tick_scheduler + /** + * @author 2No2Name + * @reason use faster collections + */ @Nullable public ScheduledTick peek() { - return this.tickQueue.peek(); + if (this.nextTickQueue == null) { + return null; + } + return this.nextTickQueue.peek(); } + /** + * @author 2No2Name + * @reason use faster collections + */ @Nullable public ScheduledTick poll() { - ScheduledTick scheduledTick = this.tickQueue.poll(); - if (scheduledTick != null) { - this.ticksPerPosition.remove(scheduledTick); + ScheduledTick orderedTick = this.nextTickQueue.poll(); + if (orderedTick != null) { + if (this.nextTickQueue.isEmpty()) { + this.updateNextTickQueue(true); + } + this.allpendingTicks.remove(tickToInt(orderedTick.pos(), orderedTick.type())); + return orderedTick; } - - return scheduledTick; + return null; } + /** + * @author 2No2Name + * @reason use faster collections + */ @Override public void schedule(ScheduledTick orderedTick) { - if (this.ticksPerPosition.add(orderedTick)) { - this.scheduleUnchecked(orderedTick); + int intTick = tickToInt(orderedTick.pos(), orderedTick.type()); + if (this.allpendingTicks.add(intTick)) { + this.queueTick(orderedTick); + } + } + + // Computes a timestamped key including the tick's priority + // Keys can be sorted in descending order to find what should be executed first + // 60 time bits, 4 priority bits + private static long getBucketKey(long time, TickPriority priority) { + //using priority.ordinal() as is not negative instead of priority.index + return (time << 4L) | (priority.ordinal() & 15); + } + + private void updateNextTickQueue(boolean elementRemoved) { + if (elementRemoved && this.nextTickQueue != null && this.nextTickQueue.isEmpty()) { + OrderedTickQueue removed = this.tickQueuesByTimeAndPriority.remove(this.tickQueuesByTimeAndPriority.firstLongKey()); + if (removed != this.nextTickQueue) { + throw new IllegalStateException("Next tick queue doesn't have the lowest key!"); + } + } + if (this.tickQueuesByTimeAndPriority.isEmpty()) { + this.nextTickQueue = null; + return; } + long firstKey = this.tickQueuesByTimeAndPriority.firstLongKey(); + this.nextTickQueue = this.tickQueuesByTimeAndPriority.get(firstKey); + } + + private void queueTick(ScheduledTick orderedTick) { + OrderedTickQueue tickQueue = this.tickQueuesByTimeAndPriority.computeIfAbsent(getBucketKey(orderedTick.triggerTick(), orderedTick.priority()), key -> new OrderedTickQueue<>()); + if (tickQueue.isEmpty()) { + this.updateNextTickQueue(false); + } + tickQueue.offer(orderedTick); + if (this.onTickAdded != null) { + //noinspection unchecked + this.onTickAdded.accept((LevelChunkTicks) (Object) this, orderedTick); + } } + // Mirai end private void scheduleUnchecked(ScheduledTick orderedTick) { this.tickQueue.add(orderedTick); @@ -72,60 +194,93 @@ public class LevelChunkTicks implements SerializableTickContainer, TickCon } + // Mirai start - lithium: world.tick_scheduler + /** + * @author 2No2Name + * @reason use faster collections + */ @Override public boolean hasScheduledTick(BlockPos pos, T type) { - return this.ticksPerPosition.contains(ScheduledTick.probe(type, pos)); + return this.allpendingTicks.contains(tickToInt(pos, type)); } + /** + * @author 2No2Name + * @reason use faster collections + */ public void removeIf(Predicate> predicate) { - Iterator> iterator = this.tickQueue.iterator(); - - while(iterator.hasNext()) { - ScheduledTick scheduledTick = iterator.next(); - if (predicate.test(scheduledTick)) { - iterator.remove(); - this.ticksPerPosition.remove(scheduledTick); + for (ObjectIterator> tickQueueIterator = this.tickQueuesByTimeAndPriority.values().iterator(); tickQueueIterator.hasNext(); ) { + OrderedTickQueue nextTickQueue = tickQueueIterator.next(); + nextTickQueue.sort(); + boolean removed = false; + for (int i = 0; i < nextTickQueue.size(); i++) { + ScheduledTick nextTick = nextTickQueue.getTickAtIndex(i); + if (predicate.test(nextTick)) { + nextTickQueue.setTickAtIndex(i, null); + this.allpendingTicks.remove(tickToInt(nextTick.pos(), nextTick.type())); + removed = true; + } + } + if (removed) { + nextTickQueue.removeNullsAndConsumed(); + } + if (nextTickQueue.isEmpty()) { + tickQueueIterator.remove(); } } - } + /** + * @author 2No2Name + * @reason use faster collections + */ public Stream> getAll() { - return this.tickQueue.stream(); + return this.tickQueuesByTimeAndPriority.values().stream().flatMap(Collection::stream); } + /** + * @author 2No2Name + * @reason not use unused field + */ @Override public int count() { - return this.tickQueue.size() + (this.pendingTicks != null ? this.pendingTicks.size() : 0); + return this.allpendingTicks.size(); } + /** + * @author 2No2Name + * @reason not use unused field + */ @Override public ListTag save(long l, Function function) { - ListTag listTag = new ListTag(); + ListTag nbtList = new ListTag(); if (this.pendingTicks != null) { - for(SavedTick savedTick : this.pendingTicks) { - listTag.add(savedTick.save(function)); + for (SavedTick tick : this.pendingTicks) { + nbtList.add(tick.save(function)); } } - - for(ScheduledTick scheduledTick : this.tickQueue) { - listTag.add(SavedTick.saveTick(scheduledTick, function, l)); + for (OrderedTickQueue nextTickQueue : this.tickQueuesByTimeAndPriority.values()) { + for (ScheduledTick orderedTick : nextTickQueue) { + nbtList.add(SavedTick.saveTick(orderedTick, function, l)); + } } - - return listTag; + return nbtList; } + /** + * @author 2No2Name + * @reason use our datastructures + */ public void unpack(long time) { if (this.pendingTicks != null) { int i = -this.pendingTicks.size(); - - for(SavedTick savedTick : this.pendingTicks) { - this.scheduleUnchecked(savedTick.unpack(time, (long)(i++))); + for (SavedTick tick : this.pendingTicks) { + this.queueTick(tick.unpack(time, i++)); } } - this.pendingTicks = null; } + // Mirai end public static LevelChunkTicks load(ListTag tickQueue, Function> nameToTypeFunction, ChunkPos pos) { ImmutableList.Builder> builder = ImmutableList.builder();