2588 lines
109 KiB
Diff
2588 lines
109 KiB
Diff
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<Long, LongArrayList> 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<Long> 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<BlockPos> {
|
|
+
|
|
+ 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<BlockPos> iterator() {
|
|
+ return new Iterator<BlockPos>() {
|
|
+
|
|
+ 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<T> implements List<T> {
|
|
+ private final ReferenceArrayList<T> list;
|
|
+ private final Reference2IntOpenHashMap<T> counter;
|
|
+
|
|
+ public HashedReferenceList(List<T> 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<T> iterator() {
|
|
+ return this.listIterator();
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public Object[] toArray() {
|
|
+ return this.list.toArray();
|
|
+ }
|
|
+
|
|
+ @SuppressWarnings("SuspiciousToArrayCall")
|
|
+ @Override
|
|
+ public <T1> 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<? extends T> c) {
|
|
+ for (T obj : c) {
|
|
+ this.trackReferenceAdded(obj);
|
|
+ }
|
|
+
|
|
+ return this.list.addAll(c);
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public boolean addAll(int index, Collection<? extends T> 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<T> listIterator() {
|
|
+ return this.listIterator(0);
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public ListIterator<T> listIterator(int index) {
|
|
+ return new ListIterator<>() {
|
|
+ private final ListIterator<T> 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<T> 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 <T> HashedReferenceList<T> wrapper(List<T> 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<T> implements Palette<T> {
|
|
+ private static final int ABSENT_VALUE = -1;
|
|
+
|
|
+ private final IdMap<T> idList;
|
|
+ private final PaletteResize<T> resizeHandler;
|
|
+ private final int indexBits;
|
|
+
|
|
+ private final Reference2IntMap<T> table;
|
|
+ private T[] entries;
|
|
+ private int size = 0;
|
|
+
|
|
+ public LithiumHashPalette(IdMap<T> idList, PaletteResize<T> resizeHandler, int indexBits, T[] entries, Reference2IntMap<T> table, int size) {
|
|
+ this.idList = idList;
|
|
+ this.resizeHandler = resizeHandler;
|
|
+ this.indexBits = indexBits;
|
|
+ this.entries = entries;
|
|
+ this.table = table;
|
|
+ this.size = size;
|
|
+ }
|
|
+
|
|
+ public LithiumHashPalette(IdMap<T> idList, int bits, PaletteResize<T> resizeHandler, List<T> list) {
|
|
+ this(idList, bits, resizeHandler);
|
|
+
|
|
+ for (T t : list) {
|
|
+ this.addEntry(t);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ @SuppressWarnings("unchecked")
|
|
+ public LithiumHashPalette(IdMap<T> idList, int bits, PaletteResize<T> 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<T> 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<T> 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<T> getElements() {
|
|
+ ImmutableList.Builder<T> builder = new ImmutableList.Builder<>();
|
|
+ for (T entry : this.entries) {
|
|
+ if (entry != null) {
|
|
+ builder.add(entry);
|
|
+ }
|
|
+ }
|
|
+ return builder.build();
|
|
+ }
|
|
+
|
|
+ public static <A> Palette<A> create(int bits, IdMap<A> idList, PaletteResize<A> listener, List<A> 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<T> extends AbstractQueue<ScheduledTick<T>> {
|
|
+ private static final int INITIAL_CAPACITY = 16;
|
|
+ private static final Comparator<ScheduledTick<?>> COMPARATOR = Comparator.comparingLong(ScheduledTick::subTickOrder);
|
|
+
|
|
+ private ScheduledTick<T>[] arr;
|
|
+
|
|
+ private int lastIndexExclusive;
|
|
+ private int firstIndex;
|
|
+
|
|
+ private long currentMaxSubTickOrder = Long.MIN_VALUE;
|
|
+ private boolean isSorted;
|
|
+ private ScheduledTick<T> unsortedPeekResult;
|
|
+
|
|
+ @SuppressWarnings("unchecked")
|
|
+ public OrderedTickQueue(int capacity) {
|
|
+ this.arr = (ScheduledTick<T>[]) 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<ScheduledTick<T>> 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<T> next() {
|
|
+ return OrderedTickQueue.this.arr[this.nextIndex++];
|
|
+ }
|
|
+ };
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public ScheduledTick<T> poll() {
|
|
+ if (this.isEmpty()) {
|
|
+ return null;
|
|
+ }
|
|
+ if (!this.isSorted) {
|
|
+ this.sort();
|
|
+ }
|
|
+ ScheduledTick<T> nextTick;
|
|
+ int polledIndex = this.firstIndex++;
|
|
+ ScheduledTick<T>[] ticks = this.arr;
|
|
+ nextTick = ticks[polledIndex];
|
|
+ ticks[polledIndex] = null;
|
|
+ return nextTick;
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public ScheduledTick<T> peek() {
|
|
+ if (!this.isSorted) {
|
|
+ return this.unsortedPeekResult;
|
|
+ } else if (this.lastIndexExclusive > this.firstIndex) {
|
|
+ return this.getTickAtIndex(this.firstIndex);
|
|
+ }
|
|
+ return null;
|
|
+ }
|
|
+
|
|
+ public boolean offer(ScheduledTick<T> 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<T> 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<T> 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<T> orderedTick = this.arr[src];
|
|
+ if (orderedTick != null) {
|
|
+ this.arr[dst] = orderedTick;
|
|
+ dst++;
|
|
+ }
|
|
+ src++;
|
|
+ }
|
|
+ this.resize(dst);
|
|
+ }
|
|
+
|
|
+ public ScheduledTick<T> getTickAtIndex(int index) {
|
|
+ if (!this.isSorted) {
|
|
+ throw new IllegalStateException("Unexpected access on unsorted queue!");
|
|
+ }
|
|
+ return this.arr[index];
|
|
+ }
|
|
+
|
|
+ public void setTickAtIndex(int index, ScheduledTick<T> tick) {
|
|
+ if (!this.isSorted) {
|
|
+ throw new IllegalStateException("Unexpected access on unsorted queue!");
|
|
+ }
|
|
+ this.arr[index] = tick;
|
|
+ }
|
|
+
|
|
+ @SuppressWarnings("unchecked")
|
|
+ private static <T> ScheduledTick<T>[] copyArray(ScheduledTick<T>[] src, int size) {
|
|
+ final ScheduledTick<T>[] 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<BlockPos> 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 c888e654ab9449bfdc7dfe16078eb0786ae6c15e..00fe84ae4fee41b365223f836c82e1688fe322ee 100644
|
|
--- a/src/main/java/net/minecraft/server/level/ServerLevel.java
|
|
+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
|
|
@@ -229,6 +229,13 @@ public class ServerLevel extends Level implements WorldGenLevel {
|
|
return thr;
|
|
}
|
|
|
|
+ // 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<String> 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<ChunkAccess> 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
|
|
+
|
|
+ <T> void compact(Palette<T> srcPalette, Palette<T> 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<T> extends AbstractCollection<T> {
|
|
- private final Map<Class<?>, List<T>> byClass = Maps.newHashMap();
|
|
+ private final Map<Class<?>, List<T>> byClass = new Reference2ReferenceOpenHashMap<>(); // JettPack
|
|
private final Class<T> baseClass;
|
|
private final List<T> allInstances = Lists.newArrayList();
|
|
|
|
@@ -55,15 +56,32 @@ public class ClassInstanceMultiMap<T> extends AbstractCollection<T> {
|
|
}
|
|
|
|
public <S> Collection<S> find(Class<S> type) {
|
|
- if (!this.baseClass.isAssignableFrom(type)) {
|
|
- throw new IllegalArgumentException("Don't know how to search for " + type);
|
|
- } else {
|
|
- List<? extends T> list = this.byClass.computeIfAbsent(type, (typeClass) -> {
|
|
- return this.allInstances.stream().filter(typeClass::isInstance).collect(Collectors.toList());
|
|
- });
|
|
- return Collections.unmodifiableCollection(list);
|
|
+ // JettPack start
|
|
+ Collection<T> collection = this.byClass.get(type);
|
|
+
|
|
+ if (collection == null) {
|
|
+ collection = this.createAllOfType(type);
|
|
}
|
|
+
|
|
+ return (Collection<S>) Collections.unmodifiableCollection(collection);
|
|
+ // JettPack end
|
|
+ }
|
|
+
|
|
+ // JettPack start
|
|
+ private <S> Collection<T> createAllOfType(Class<S> type) {
|
|
+ List<T> 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<T> iterator() {
|
|
diff --git a/src/main/java/net/minecraft/util/Mth.java b/src/main/java/net/minecraft/util/Mth.java
|
|
index 46e3e884890a23f5f90efc795a21566dd40804df..c852331f0c9dddbfe29e88e2dca1dceb2d7cee44 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 <T> void compact(Palette<T> srcPalette, Palette<T> 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 <T> void compact(Palette<T> srcPalette, Palette<T> 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 193b6621d6506a04bc1f9f23571aeb2e635d562c..6f2518ec1be9af4fa00d719c50cbe64e4e57fcbc 100644
|
|
--- a/src/main/java/net/minecraft/world/entity/Entity.java
|
|
+++ b/src/main/java/net/minecraft/world/entity/Entity.java
|
|
@@ -2590,39 +2590,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 1ec9a48f2f44f5bd647e0661423ae90970aee262..219c7dcbe6bee5054aff0640a80d620eded812c0 100644
|
|
--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java
|
|
+++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java
|
|
@@ -576,11 +576,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;
|
|
@@ -590,7 +590,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
|
|
|
|
}
|
|
|
|
@@ -2549,6 +2549,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) {
|
|
@@ -3528,6 +3530,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 2c915c765ceef3ec28f5a58fa9a587282c1a906a..a3f42f8e9f31efce278481e115b1e2b683af078f 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<Attribute, AttributeInstance> attributes = Maps.newHashMap();
|
|
- private final Set<AttributeInstance> dirtyAttributes = Sets.newHashSet();
|
|
+ private final Map<Attribute, AttributeInstance> attributes = new Reference2ReferenceOpenHashMap<>(0); // Mirai
|
|
+ private final Set<AttributeInstance> dirtyAttributes = new ReferenceOpenHashSet<>(0); // Mirai
|
|
private final AttributeSupplier supplier;
|
|
private final net.minecraft.world.entity.LivingEntity entity; // Purpur
|
|
private final java.util.function.Function<Attribute, AttributeInstance> 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<Goal.Flag, WrappedGoal> lockedFlags = new EnumMap<>(Goal.Flag.class);
|
|
- public final Set<WrappedGoal> availableGoals = Sets.newLinkedHashSet();
|
|
+ public final Set<WrappedGoal> availableGoals = new ObjectLinkedOpenHashSet<>(); // Lithium - replace AI goal set with optimized collection
|
|
private final Supplier<ProfilerFiller> profiler;
|
|
private final EnumSet<Goal.Flag> 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<net.minecraft.world.entity.ai.goal.Goal.Flag> 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<BlockPos> 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 9fa0758c0a8c74dec6cfe8a16585ace1cf45df96..f912b0ec9f58861efcc4fbd65604bd0a31c6d7d5 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.Key<?>, 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 0bb14272d024af90e7aef40f2f694e184af607d3..aa27ab72d30f917ea41045db16b6f59f1442ea77 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<TickingBlockEntity> blockEntityTickers = Lists.newArrayList(); public final int getTotalTileEntityTickers() { return this.blockEntityTickers.size(); } // Paper
|
|
+ protected final List<TickingBlockEntity> 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<TickingBlockEntity> pendingBlockEntityTickers = Lists.newArrayList();
|
|
+ private final List<TickingBlockEntity> 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 4b61602a1d2d6817272e02cfd282ac5dabbd976d..57bd494395de37a34452e9c7d2899b249011651c 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<T> {
|
|
+public interface PaletteResize<T> { // 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<T> implements PaletteResize<T>, PalettedContainerRO<T> {
|
|
+ // JettPack start - lithium: chunk.serialization
|
|
+ private static final ThreadLocal<short[]> CACHED_ARRAY_4096 = ThreadLocal.withInitial(() -> new short[4096]);
|
|
+ private static final ThreadLocal<short[]> CACHED_ARRAY_64 = ThreadLocal.withInitial(() -> new short[64]);
|
|
+ private Optional<LongStream> 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<T> dummyPaletteResize = (newSize, added) -> {
|
|
return 0;
|
|
@@ -299,30 +314,54 @@ public class PalettedContainer<T> implements PaletteResize<T>, PalettedContainer
|
|
public synchronized PalettedContainerRO.PackedData<T> pack(IdMap<T> idList, PalettedContainer.Strategy paletteProvider) { // Paper - synchronize
|
|
this.acquire();
|
|
|
|
- PalettedContainerRO.PackedData var12;
|
|
+ // JettPack start - lithium: chunk.serialization
|
|
+ Optional<LongStream> data = Optional.empty();
|
|
+ List<T> elements = null;
|
|
try {
|
|
- HashMapPalette<T> 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<LongStream> 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<T> hashPalette = null;
|
|
+
|
|
+ final Palette<T> 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<T> lithiumHashPalette) {
|
|
+ hashPalette = lithiumHashPalette;
|
|
}
|
|
|
|
- var12 = new PalettedContainerRO.PackedData<>(hashMapPalette.getEntries(), optional);
|
|
+ if (elements == null) {
|
|
+ LithiumHashPalette<T> 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 <T> void swapPalette(int[] is, IntUnaryOperator applier) {
|
|
@@ -362,17 +401,37 @@ public class PalettedContainer<T> implements PaletteResize<T>, PalettedContainer
|
|
|
|
@Override
|
|
public void count(PalettedContainer.CountConsumer<T> 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<T>(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<T extends EntityAccess> {
|
|
}
|
|
|
|
public void forEachAccessibleNonEmptySection(AABB box, Consumer<EntitySection<T>> 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<T extends EntityAccess> {
|
|
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<T> 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<T> 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<EntitySection<T>> 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<EntitySection<T>> action) {
|
|
+ EntitySection<T> 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<NormalNoise.NoiseParameters> noises;
|
|
public final Holder<NoiseGeneratorSettings> settings;
|
|
private final Aquifer.FluidPicker globalFluidPicker;
|
|
+ private int cachedSeaLevel; // Mirai - lithium: gen
|
|
|
|
public NoiseBasedChunkGenerator(Registry<StructureSet> structureSetRegistry, Registry<NormalNoise.NoiseParameters> noiseRegistry, BiomeSource populationSource, Holder<NoiseGeneratorSettings> 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 ac807277a6b26d140ea9873d17c7aa4fb5fe37b2..c48f16ce223bff3c27abf3ea8979b8f0921c1505 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<T> implements SerializableTickContainer<T>, TickContainerAccess<T> {
|
|
- private final Queue<ScheduledTick<T>> tickQueue = new PriorityQueue<>(ScheduledTick.DRAIN_ORDER);
|
|
+ private Queue<ScheduledTick<T>> tickQueue = new PriorityQueue<>(ScheduledTick.DRAIN_ORDER); // Mirai - remove final
|
|
@Nullable
|
|
private List<SavedTick<T>> pendingTicks;
|
|
- private final Set<ScheduledTick<?>> ticksPerPosition = new ObjectOpenCustomHashSet<>(ScheduledTick.UNIQUE_TICK_HASH);
|
|
+ private Set<ScheduledTick<?>> ticksPerPosition = new ObjectOpenCustomHashSet<>(ScheduledTick.UNIQUE_TICK_HASH); // Mirai - remove final
|
|
@Nullable
|
|
private BiConsumer<LevelChunkTicks<T>, ScheduledTick<T>> onTickAdded;
|
|
+ // Mirai start - lithium: world.tick_scheduler
|
|
+ private static volatile Reference2IntOpenHashMap<Object> TYPE_2_INDEX;
|
|
+
|
|
+ static {
|
|
+ TYPE_2_INDEX = new Reference2IntOpenHashMap<>();
|
|
+ TYPE_2_INDEX.defaultReturnValue(-1);
|
|
+ }
|
|
+
|
|
+ private final Long2ReferenceAVLTreeMap<OrderedTickQueue<T>> tickQueuesByTimeAndPriority = new Long2ReferenceAVLTreeMap<>();
|
|
+ private OrderedTickQueue<T> nextTickQueue;
|
|
+ private final IntOpenHashSet allpendingTicks = new IntOpenHashSet();
|
|
+ // Mirai end
|
|
|
|
// Paper start - add dirty flag
|
|
private boolean dirty;
|
|
@@ -48,36 +71,135 @@ public class LevelChunkTicks<T> implements SerializableTickContainer<T>, 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; // KeYi - dont null
|
|
+ // this.tickQueue = null; // KeYi - dont 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<Object> 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<LevelChunkTicks<T>, ScheduledTick<T>> tickConsumer) {
|
|
this.onTickAdded = tickConsumer;
|
|
}
|
|
|
|
+ // Mirai start - lithium: world.tick_scheduler
|
|
+ /**
|
|
+ * @author 2No2Name
|
|
+ * @reason use faster collections
|
|
+ */
|
|
@Nullable
|
|
public ScheduledTick<T> peek() {
|
|
- return this.tickQueue.peek();
|
|
+ if (this.nextTickQueue == null) {
|
|
+ return null;
|
|
+ }
|
|
+ return this.nextTickQueue.peek();
|
|
}
|
|
|
|
+ /**
|
|
+ * @author 2No2Name
|
|
+ * @reason use faster collections
|
|
+ */
|
|
@Nullable
|
|
public ScheduledTick<T> poll() {
|
|
- ScheduledTick<T> scheduledTick = this.tickQueue.poll();
|
|
- if (scheduledTick != null) {
|
|
+ ScheduledTick<T> orderedTick = this.nextTickQueue.poll();
|
|
+ if (orderedTick != null) {
|
|
+ if (this.nextTickQueue.isEmpty()) {
|
|
+ this.updateNextTickQueue(true);
|
|
+ }
|
|
this.dirty = true; // Paper - add dirty flag
|
|
- this.ticksPerPosition.remove(scheduledTick);
|
|
+ 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<T> orderedTick) {
|
|
- if (this.ticksPerPosition.add(orderedTick)) {
|
|
+ int intTick = tickToInt(orderedTick.pos(), orderedTick.type());
|
|
+ if (this.allpendingTicks.add(intTick)) {
|
|
this.dirty = true; // Paper - add dirty flag
|
|
- this.scheduleUnchecked(orderedTick);
|
|
+ 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<T> 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<T> orderedTick) {
|
|
+ OrderedTickQueue<T> 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<T>) (Object) this, orderedTick);
|
|
+ }
|
|
}
|
|
+ // Mirai end
|
|
|
|
private void scheduleUnchecked(ScheduledTick<T> orderedTick) {
|
|
this.tickQueue.add(orderedTick);
|
|
@@ -87,50 +209,84 @@ public class LevelChunkTicks<T> implements SerializableTickContainer<T>, 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<ScheduledTick<T>> predicate) {
|
|
- Iterator<ScheduledTick<T>> iterator = this.tickQueue.iterator();
|
|
-
|
|
- while(iterator.hasNext()) {
|
|
- ScheduledTick<T> scheduledTick = iterator.next();
|
|
- if (predicate.test(scheduledTick)) {
|
|
- iterator.remove(); this.dirty = true; // Paper - add dirty flag
|
|
- this.ticksPerPosition.remove(scheduledTick);
|
|
+ for (ObjectIterator<OrderedTickQueue<T>> tickQueueIterator = this.tickQueuesByTimeAndPriority.values().iterator(); tickQueueIterator.hasNext(); ) {
|
|
+ OrderedTickQueue<T> nextTickQueue = tickQueueIterator.next();
|
|
+ nextTickQueue.sort();
|
|
+ boolean removed = false;
|
|
+ for (int i = 0; i < nextTickQueue.size(); i++) {
|
|
+ ScheduledTick<T> nextTick = nextTickQueue.getTickAtIndex(i);
|
|
+ if (predicate.test(nextTick)) {
|
|
+ nextTickQueue.setTickAtIndex(i, null);
|
|
+ this.allpendingTicks.remove(tickToInt(nextTick.pos(), nextTick.type()));
|
|
+ removed = true; this.dirty = true; // Paper - add dirty flag
|
|
+ }
|
|
+ }
|
|
+ if (removed) {
|
|
+ nextTickQueue.removeNullsAndConsumed();
|
|
+ }
|
|
+ if (nextTickQueue.isEmpty()) {
|
|
+ tickQueueIterator.remove();
|
|
}
|
|
}
|
|
-
|
|
}
|
|
|
|
+ /**
|
|
+ * @author 2No2Name
|
|
+ * @reason use faster collections
|
|
+ */
|
|
public Stream<ScheduledTick<T>> 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<T, String> function) {
|
|
this.lastSaved = l; // Paper - add dirty system to level ticks
|
|
- ListTag listTag = new ListTag();
|
|
+ ListTag nbtList = new ListTag();
|
|
if (this.pendingTicks != null) {
|
|
- for(SavedTick<T> savedTick : this.pendingTicks) {
|
|
- listTag.add(savedTick.save(function));
|
|
+ for (SavedTick<T> tick : this.pendingTicks) {
|
|
+ nbtList.add(tick.save(function));
|
|
}
|
|
}
|
|
-
|
|
- for(ScheduledTick<T> scheduledTick : this.tickQueue) {
|
|
- listTag.add(SavedTick.saveTick(scheduledTick, function, l));
|
|
+ for (OrderedTickQueue<T> nextTickQueue : this.tickQueuesByTimeAndPriority.values()) {
|
|
+ for (ScheduledTick<T> 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) {
|
|
// Paper start - add dirty system to level chunk ticks
|
|
@@ -139,14 +295,13 @@ public class LevelChunkTicks<T> implements SerializableTickContainer<T>, TickCon
|
|
}
|
|
// Paper end - add dirty system to level chunk ticks
|
|
int i = -this.pendingTicks.size();
|
|
-
|
|
- for(SavedTick<T> savedTick : this.pendingTicks) {
|
|
- this.scheduleUnchecked(savedTick.unpack(time, (long)(i++)));
|
|
+ for (SavedTick<T> tick : this.pendingTicks) {
|
|
+ this.queueTick(tick.unpack(time, i++));
|
|
}
|
|
}
|
|
-
|
|
this.pendingTicks = null;
|
|
}
|
|
+ // Mirai end
|
|
|
|
public static <T> LevelChunkTicks<T> load(ListTag tickQueue, Function<String, Optional<T>> nameToTypeFunction, ChunkPos pos) {
|
|
ImmutableList.Builder<SavedTick<T>> builder = ImmutableList.builder();
|