From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: Aikar Date: Mon, 28 Mar 2016 20:55:47 -0400 Subject: [PATCH] MC Utils diff --git a/src/main/java/com/destroystokyo/paper/util/concurrent/WeakSeqLock.java b/src/main/java/com/destroystokyo/paper/util/concurrent/WeakSeqLock.java new file mode 100644 index 0000000000000000000000000000000000000000..4029dc68cf35d63aa70c4a76c35bf65a7fc6358f --- /dev/null +++ b/src/main/java/com/destroystokyo/paper/util/concurrent/WeakSeqLock.java @@ -0,0 +1,68 @@ +package com.destroystokyo.paper.util.concurrent; + +import java.util.concurrent.atomic.AtomicLong; + +/** + * copied from https://github.com/Spottedleaf/ConcurrentUtil/blob/master/src/main/java/ca/spottedleaf/concurrentutil/lock/WeakSeqLock.java + * @author Spottedleaf + */ +public final class WeakSeqLock { + // TODO when the switch to J11 is made, nuke this class from orbit + + protected final AtomicLong lock = new AtomicLong(); + + public WeakSeqLock() { + //VarHandle.storeStoreFence(); // warn: usages must be checked to ensure this behaviour isn't needed + } + + public void acquireWrite() { + // must be release-type write + this.lock.lazySet(this.lock.get() + 1); + } + + public boolean canRead(final long read) { + return (read & 1) == 0; + } + + public boolean tryAcquireWrite() { + this.acquireWrite(); + return true; + } + + public void releaseWrite() { + // must be acquire-type write + final long lock = this.lock.get(); // volatile here acts as store-store + this.lock.lazySet(lock + 1); + } + + public void abortWrite() { + // must be acquire-type write + final long lock = this.lock.get(); // volatile here acts as store-store + this.lock.lazySet(lock ^ 1); + } + + public long acquireRead() { + int failures = 0; + long curr; + + for (curr = this.lock.get(); !this.canRead(curr); curr = this.lock.get()) { + // without j11, our only backoff is the yield() call... + + if (++failures > 5_000) { /* TODO determine a threshold */ + Thread.yield(); + } + /* Better waiting is beyond the scope of this lock; if it is needed the lock is being misused */ + } + + //VarHandle.loadLoadFence(); // volatile acts as the load-load barrier + return curr; + } + + public boolean tryReleaseRead(final long read) { + return this.lock.get() == read; // volatile acts as the load-load barrier + } + + public long getSequentialCounter() { + return this.lock.get(); + } +} diff --git a/src/main/java/com/destroystokyo/paper/util/map/QueuedChangesMapLong2Int.java b/src/main/java/com/destroystokyo/paper/util/map/QueuedChangesMapLong2Int.java new file mode 100644 index 0000000000000000000000000000000000000000..59868f37d14bbc0ece0836095cdad148778995e6 --- /dev/null +++ b/src/main/java/com/destroystokyo/paper/util/map/QueuedChangesMapLong2Int.java @@ -0,0 +1,162 @@ +package com.destroystokyo.paper.util.map; + +import com.destroystokyo.paper.util.concurrent.WeakSeqLock; +import it.unimi.dsi.fastutil.longs.Long2IntMap; +import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap; +import it.unimi.dsi.fastutil.longs.LongIterator; +import it.unimi.dsi.fastutil.longs.LongOpenHashSet; +import it.unimi.dsi.fastutil.objects.ObjectIterator; + +/** + * @author Spottedleaf + */ +public class QueuedChangesMapLong2Int { + + protected final Long2IntOpenHashMap updatingMap; + protected final Long2IntOpenHashMap visibleMap; + protected final Long2IntOpenHashMap queuedPuts; + protected final LongOpenHashSet queuedRemove; + + protected int queuedDefaultReturnValue; + + // we use a seqlock as writes are not common. + protected final WeakSeqLock updatingMapSeqLock = new WeakSeqLock(); + + public QueuedChangesMapLong2Int() { + this(16, 0.75f); + } + + public QueuedChangesMapLong2Int(final int capacity, final float loadFactor) { + this.updatingMap = new Long2IntOpenHashMap(capacity, loadFactor); + this.visibleMap = new Long2IntOpenHashMap(capacity, loadFactor); + this.queuedPuts = new Long2IntOpenHashMap(); + this.queuedRemove = new LongOpenHashSet(); + } + + public void queueDefaultReturnValue(final int dfl) { + this.queuedDefaultReturnValue = dfl; + this.updatingMap.defaultReturnValue(dfl); + } + + public int queueUpdate(final long k, final int v) { + this.queuedRemove.remove(k); + this.queuedPuts.put(k, v); + + return this.updatingMap.put(k, v); + } + + public int queueRemove(final long k) { + this.queuedPuts.remove(k); + this.queuedRemove.add(k); + + return this.updatingMap.remove(k); + } + + public int getUpdating(final long k) { + return this.updatingMap.get(k); + } + + public int getVisible(final long k) { + return this.visibleMap.get(k); + } + + public int getVisibleAsync(final long k) { + long readlock; + int ret = 0; + + do { + readlock = this.updatingMapSeqLock.acquireRead(); + try { + ret = this.visibleMap.get(k); + } catch (final Throwable thr) { + if (thr instanceof ThreadDeath) { + throw (ThreadDeath)thr; + } + // ignore... + continue; + } + + } while (!this.updatingMapSeqLock.tryReleaseRead(readlock)); + + return ret; + } + + public boolean performUpdates() { + this.updatingMapSeqLock.acquireWrite(); + this.visibleMap.defaultReturnValue(this.queuedDefaultReturnValue); + this.updatingMapSeqLock.releaseWrite(); + + if (this.queuedPuts.isEmpty() && this.queuedRemove.isEmpty()) { + return false; + } + + // update puts + final ObjectIterator iterator0 = this.queuedPuts.long2IntEntrySet().fastIterator(); + while (iterator0.hasNext()) { + final Long2IntMap.Entry entry = iterator0.next(); + final long key = entry.getLongKey(); + final int val = entry.getIntValue(); + + this.updatingMapSeqLock.acquireWrite(); + try { + this.visibleMap.put(key, val); + } finally { + this.updatingMapSeqLock.releaseWrite(); + } + } + + this.queuedPuts.clear(); + + final LongIterator iterator1 = this.queuedRemove.iterator(); + while (iterator1.hasNext()) { + final long key = iterator1.nextLong(); + + this.updatingMapSeqLock.acquireWrite(); + try { + this.visibleMap.remove(key); + } finally { + this.updatingMapSeqLock.releaseWrite(); + } + } + + this.queuedRemove.clear(); + + return true; + } + + public boolean performUpdatesLockMap() { + this.updatingMapSeqLock.acquireWrite(); + try { + this.visibleMap.defaultReturnValue(this.queuedDefaultReturnValue); + + if (this.queuedPuts.isEmpty() && this.queuedRemove.isEmpty()) { + return false; + } + + // update puts + final ObjectIterator iterator0 = this.queuedPuts.long2IntEntrySet().fastIterator(); + while (iterator0.hasNext()) { + final Long2IntMap.Entry entry = iterator0.next(); + final long key = entry.getLongKey(); + final int val = entry.getIntValue(); + + this.visibleMap.put(key, val); + } + + this.queuedPuts.clear(); + + final LongIterator iterator1 = this.queuedRemove.iterator(); + while (iterator1.hasNext()) { + final long key = iterator1.nextLong(); + + this.visibleMap.remove(key); + } + + this.queuedRemove.clear(); + + return true; + } finally { + this.updatingMapSeqLock.releaseWrite(); + } + } +} diff --git a/src/main/java/com/destroystokyo/paper/util/map/QueuedChangesMapLong2Object.java b/src/main/java/com/destroystokyo/paper/util/map/QueuedChangesMapLong2Object.java new file mode 100644 index 0000000000000000000000000000000000000000..7bab31a312463cc963d9621cdc543a281459bd32 --- /dev/null +++ b/src/main/java/com/destroystokyo/paper/util/map/QueuedChangesMapLong2Object.java @@ -0,0 +1,202 @@ +package com.destroystokyo.paper.util.map; + +import com.destroystokyo.paper.util.concurrent.WeakSeqLock; +import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap; +import it.unimi.dsi.fastutil.longs.Long2ObjectMap; +import it.unimi.dsi.fastutil.objects.ObjectBidirectionalIterator; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +/** + * @author Spottedleaf + */ +public class QueuedChangesMapLong2Object { + + protected static final Object REMOVED = new Object(); + + protected final Long2ObjectLinkedOpenHashMap updatingMap; + protected final Long2ObjectLinkedOpenHashMap visibleMap; + protected final Long2ObjectLinkedOpenHashMap queuedChanges; + + // we use a seqlock as writes are not common. + protected final WeakSeqLock updatingMapSeqLock = new WeakSeqLock(); + + public QueuedChangesMapLong2Object() { + this(16, 0.75f); // dfl for fastutil + } + + public QueuedChangesMapLong2Object(final int capacity, final float loadFactor) { + this.updatingMap = new Long2ObjectLinkedOpenHashMap<>(capacity, loadFactor); + this.visibleMap = new Long2ObjectLinkedOpenHashMap<>(capacity, loadFactor); + this.queuedChanges = new Long2ObjectLinkedOpenHashMap<>(); + } + + public V queueUpdate(final long k, final V value) { + this.queuedChanges.put(k, value); + return this.updatingMap.put(k, value); + } + + public V queueRemove(final long k) { + this.queuedChanges.put(k, REMOVED); + return this.updatingMap.remove(k); + } + + public V getUpdating(final long k) { + return this.updatingMap.get(k); + } + + public boolean updatingContainsKey(final long k) { + return this.updatingMap.containsKey(k); + } + + public V getVisible(final long k) { + return this.visibleMap.get(k); + } + + public boolean visibleContainsKey(final long k) { + return this.visibleMap.containsKey(k); + } + + public V getVisibleAsync(final long k) { + long readlock; + V ret = null; + + do { + readlock = this.updatingMapSeqLock.acquireRead(); + + try { + ret = this.visibleMap.get(k); + } catch (final Throwable thr) { + if (thr instanceof ThreadDeath) { + throw (ThreadDeath)thr; + } + // ignore... + continue; + } + + } while (!this.updatingMapSeqLock.tryReleaseRead(readlock)); + + return ret; + } + + public boolean visibleContainsKeyAsync(final long k) { + long readlock; + boolean ret = false; + + do { + readlock = this.updatingMapSeqLock.acquireRead(); + + try { + ret = this.visibleMap.containsKey(k); + } catch (final Throwable thr) { + if (thr instanceof ThreadDeath) { + throw (ThreadDeath)thr; + } + // ignore... + continue; + } + + } while (!this.updatingMapSeqLock.tryReleaseRead(readlock)); + + return ret; + } + + public Long2ObjectLinkedOpenHashMap getVisibleMap() { + return this.visibleMap; + } + + public Long2ObjectLinkedOpenHashMap getUpdatingMap() { + return this.updatingMap; + } + + public int getVisibleSize() { + return this.visibleMap.size(); + } + + public int getVisibleSizeAsync() { + long readlock; + int ret; + + do { + readlock = this.updatingMapSeqLock.acquireRead(); + ret = this.visibleMap.size(); + } while (!this.updatingMapSeqLock.tryReleaseRead(readlock)); + + return ret; + } + + // unlike mojang's impl this cannot be used async since it's not a view of an immutable map + public Collection getUpdatingValues() { + return this.updatingMap.values(); + } + + public List getUpdatingValuesCopy() { + return new ArrayList<>(this.updatingMap.values()); + } + + // unlike mojang's impl this cannot be used async since it's not a view of an immutable map + public Collection getVisibleValues() { + return this.visibleMap.values(); + } + + public List getVisibleValuesCopy() { + return new ArrayList<>(this.visibleMap.values()); + } + + public boolean performUpdates() { + if (this.queuedChanges.isEmpty()) { + return false; + } + + final ObjectBidirectionalIterator> iterator = this.queuedChanges.long2ObjectEntrySet().fastIterator(); + while (iterator.hasNext()) { + final Long2ObjectMap.Entry entry = iterator.next(); + final long key = entry.getLongKey(); + final Object val = entry.getValue(); + + this.updatingMapSeqLock.acquireWrite(); + try { + if (val == REMOVED) { + this.visibleMap.remove(key); + } else { + this.visibleMap.put(key, (V)val); + } + } finally { + this.updatingMapSeqLock.releaseWrite(); + } + } + + this.queuedChanges.clear(); + return true; + } + + public boolean performUpdatesLockMap() { + if (this.queuedChanges.isEmpty()) { + return false; + } + + final ObjectBidirectionalIterator> iterator = this.queuedChanges.long2ObjectEntrySet().fastIterator(); + + try { + this.updatingMapSeqLock.acquireWrite(); + + while (iterator.hasNext()) { + final Long2ObjectMap.Entry entry = iterator.next(); + final long key = entry.getLongKey(); + final Object val = entry.getValue(); + + if (val == REMOVED) { + this.visibleMap.remove(key); + } else { + this.visibleMap.put(key, (V)val); + } + } + } finally { + this.updatingMapSeqLock.releaseWrite(); + } + + this.queuedChanges.clear(); + return true; + } +} diff --git a/src/main/java/com/destroystokyo/paper/util/maplist/ChunkList.java b/src/main/java/com/destroystokyo/paper/util/maplist/ChunkList.java new file mode 100644 index 0000000000000000000000000000000000000000..554f4d4e63c1431721989e6f502a32ccc53a8807 --- /dev/null +++ b/src/main/java/com/destroystokyo/paper/util/maplist/ChunkList.java @@ -0,0 +1,128 @@ +package com.destroystokyo.paper.util.maplist; + +import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap; +import java.util.Arrays; +import java.util.Iterator; +import java.util.NoSuchElementException; +import net.minecraft.world.level.chunk.LevelChunk; + +// list with O(1) remove & contains +/** + * @author Spottedleaf + */ +public final class ChunkList implements Iterable { + + protected final Long2IntOpenHashMap chunkToIndex = new Long2IntOpenHashMap(2, 0.8f); + { + this.chunkToIndex.defaultReturnValue(Integer.MIN_VALUE); + } + + protected static final LevelChunk[] EMPTY_LIST = new LevelChunk[0]; + + protected LevelChunk[] chunks = EMPTY_LIST; + protected int count; + + public int size() { + return this.count; + } + + public boolean contains(final LevelChunk chunk) { + return this.chunkToIndex.containsKey(chunk.coordinateKey); + } + + public boolean remove(final LevelChunk chunk) { + final int index = this.chunkToIndex.remove(chunk.coordinateKey); + if (index == Integer.MIN_VALUE) { + return false; + } + + // move the entity at the end to this index + final int endIndex = --this.count; + final LevelChunk end = this.chunks[endIndex]; + if (index != endIndex) { + // not empty after this call + this.chunkToIndex.put(end.coordinateKey, index); // update index + } + this.chunks[index] = end; + this.chunks[endIndex] = null; + + return true; + } + + public boolean add(final LevelChunk chunk) { + final int count = this.count; + final int currIndex = this.chunkToIndex.putIfAbsent(chunk.coordinateKey, count); + + if (currIndex != Integer.MIN_VALUE) { + return false; // already in this list + } + + LevelChunk[] list = this.chunks; + + if (list.length == count) { + // resize required + list = this.chunks = Arrays.copyOf(list, (int)Math.max(4L, count * 2L)); // overflow results in negative + } + + list[count] = chunk; + this.count = count + 1; + + return true; + } + + public LevelChunk getChecked(final int index) { + if (index < 0 || index >= this.count) { + throw new IndexOutOfBoundsException("Index: " + index + " is out of bounds, size: " + this.count); + } + return this.chunks[index]; + } + + public LevelChunk getUnchecked(final int index) { + return this.chunks[index]; + } + + public LevelChunk[] getRawData() { + return this.chunks; + } + + public void clear() { + this.chunkToIndex.clear(); + Arrays.fill(this.chunks, 0, this.count, null); + this.count = 0; + } + + @Override + public Iterator iterator() { + return new Iterator() { + + LevelChunk lastRet; + int current; + + @Override + public boolean hasNext() { + return this.current < ChunkList.this.count; + } + + @Override + public LevelChunk next() { + if (this.current >= ChunkList.this.count) { + throw new NoSuchElementException(); + } + return this.lastRet = ChunkList.this.chunks[this.current++]; + } + + @Override + public void remove() { + final LevelChunk lastRet = this.lastRet; + + if (lastRet == null) { + throw new IllegalStateException(); + } + this.lastRet = null; + + ChunkList.this.remove(lastRet); + --this.current; + } + }; + } +} diff --git a/src/main/java/com/destroystokyo/paper/util/maplist/EntityList.java b/src/main/java/com/destroystokyo/paper/util/maplist/EntityList.java new file mode 100644 index 0000000000000000000000000000000000000000..0133ea6feb1ab88f021f66855669f58367e7420b --- /dev/null +++ b/src/main/java/com/destroystokyo/paper/util/maplist/EntityList.java @@ -0,0 +1,128 @@ +package com.destroystokyo.paper.util.maplist; + +import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap; +import net.minecraft.world.entity.Entity; +import java.util.Arrays; +import java.util.Iterator; +import java.util.NoSuchElementException; + +// list with O(1) remove & contains +/** + * @author Spottedleaf + */ +public final class EntityList implements Iterable { + + protected final Int2IntOpenHashMap entityToIndex = new Int2IntOpenHashMap(2, 0.8f); + { + this.entityToIndex.defaultReturnValue(Integer.MIN_VALUE); + } + + protected static final Entity[] EMPTY_LIST = new Entity[0]; + + protected Entity[] entities = EMPTY_LIST; + protected int count; + + public int size() { + return this.count; + } + + public boolean contains(final Entity entity) { + return this.entityToIndex.containsKey(entity.getId()); + } + + public boolean remove(final Entity entity) { + final int index = this.entityToIndex.remove(entity.getId()); + if (index == Integer.MIN_VALUE) { + return false; + } + + // move the entity at the end to this index + final int endIndex = --this.count; + final Entity end = this.entities[endIndex]; + if (index != endIndex) { + // not empty after this call + this.entityToIndex.put(end.getId(), index); // update index + } + this.entities[index] = end; + this.entities[endIndex] = null; + + return true; + } + + public boolean add(final Entity entity) { + final int count = this.count; + final int currIndex = this.entityToIndex.putIfAbsent(entity.getId(), count); + + if (currIndex != Integer.MIN_VALUE) { + return false; // already in this list + } + + Entity[] list = this.entities; + + if (list.length == count) { + // resize required + list = this.entities = Arrays.copyOf(list, (int)Math.max(4L, count * 2L)); // overflow results in negative + } + + list[count] = entity; + this.count = count + 1; + + return true; + } + + public Entity getChecked(final int index) { + if (index < 0 || index >= this.count) { + throw new IndexOutOfBoundsException("Index: " + index + " is out of bounds, size: " + this.count); + } + return this.entities[index]; + } + + public Entity getUnchecked(final int index) { + return this.entities[index]; + } + + public Entity[] getRawData() { + return this.entities; + } + + public void clear() { + this.entityToIndex.clear(); + Arrays.fill(this.entities, 0, this.count, null); + this.count = 0; + } + + @Override + public Iterator iterator() { + return new Iterator() { + + Entity lastRet; + int current; + + @Override + public boolean hasNext() { + return this.current < EntityList.this.count; + } + + @Override + public Entity next() { + if (this.current >= EntityList.this.count) { + throw new NoSuchElementException(); + } + return this.lastRet = EntityList.this.entities[this.current++]; + } + + @Override + public void remove() { + final Entity lastRet = this.lastRet; + + if (lastRet == null) { + throw new IllegalStateException(); + } + this.lastRet = null; + + EntityList.this.remove(lastRet); + --this.current; + } + }; + } +} diff --git a/src/main/java/com/destroystokyo/paper/util/maplist/IBlockDataList.java b/src/main/java/com/destroystokyo/paper/util/maplist/IBlockDataList.java new file mode 100644 index 0000000000000000000000000000000000000000..abe7f2f13ab713bf1cb0343059377ab7e1b48b6e --- /dev/null +++ b/src/main/java/com/destroystokyo/paper/util/maplist/IBlockDataList.java @@ -0,0 +1,128 @@ +package com.destroystokyo.paper.util.maplist; + +import it.unimi.dsi.fastutil.longs.LongIterator; +import it.unimi.dsi.fastutil.shorts.Short2LongOpenHashMap; +import java.util.Arrays; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.chunk.GlobalPalette; +import net.minecraft.world.level.chunk.LevelChunkSection; + +/** + * @author Spottedleaf + */ +public final class IBlockDataList { + + static final GlobalPalette GLOBAL_PALETTE = (GlobalPalette) LevelChunkSection.GLOBAL_BLOCKSTATE_PALETTE; + + // map of location -> (index | (location << 16) | (palette id << 32)) + private final Short2LongOpenHashMap map = new Short2LongOpenHashMap(2, 0.8f); + { + this.map.defaultReturnValue(Long.MAX_VALUE); + } + + private static final long[] EMPTY_LIST = new long[0]; + + private long[] byIndex = EMPTY_LIST; + private int size; + + public static int getLocationKey(final int x, final int y, final int z) { + return (x & 15) | (((z & 15) << 4)) | ((y & 255) << (4 + 4)); + } + + public static BlockState getBlockDataFromRaw(final long raw) { + return GLOBAL_PALETTE.getObject((int)(raw >>> 32)); + } + + public static int getIndexFromRaw(final long raw) { + return (int)(raw & 0xFFFF); + } + + public static int getLocationFromRaw(final long raw) { + return (int)((raw >>> 16) & 0xFFFF); + } + + public static long getRawFromValues(final int index, final int location, final BlockState data) { + return (long)index | ((long)location << 16) | (((long)GLOBAL_PALETTE.getOrCreateIdFor(data)) << 32); + } + + public static long setIndexRawValues(final long value, final int index) { + return value & ~(0xFFFF) | (index); + } + + public long add(final int x, final int y, final int z, final BlockState data) { + return this.add(getLocationKey(x, y, z), data); + } + + public long add(final int location, final BlockState data) { + final long curr = this.map.get((short)location); + + if (curr == Long.MAX_VALUE) { + final int index = this.size++; + final long raw = getRawFromValues(index, location, data); + this.map.put((short)location, raw); + + if (index >= this.byIndex.length) { + this.byIndex = Arrays.copyOf(this.byIndex, (int)Math.max(4L, this.byIndex.length * 2L)); + } + + this.byIndex[index] = raw; + return raw; + } else { + final int index = getIndexFromRaw(curr); + final long raw = this.byIndex[index] = getRawFromValues(index, location, data); + + this.map.put((short)location, raw); + + return raw; + } + } + + public long remove(final int x, final int y, final int z) { + return this.remove(getLocationKey(x, y, z)); + } + + public long remove(final int location) { + final long ret = this.map.remove((short)location); + final int index = getIndexFromRaw(ret); + if (ret == Long.MAX_VALUE) { + return ret; + } + + // move the entry at the end to this index + final int endIndex = --this.size; + final long end = this.byIndex[endIndex]; + if (index != endIndex) { + // not empty after this call + this.map.put((short)getLocationFromRaw(end), setIndexRawValues(end, index)); + } + this.byIndex[index] = end; + this.byIndex[endIndex] = 0L; + + return ret; + } + + public int size() { + return this.size; + } + + public long getRaw(final int index) { + return this.byIndex[index]; + } + + public int getLocation(final int index) { + return getLocationFromRaw(this.getRaw(index)); + } + + public BlockState getData(final int index) { + return getBlockDataFromRaw(this.getRaw(index)); + } + + public void clear() { + this.size = 0; + this.map.clear(); + } + + public LongIterator getRawIterator() { + return this.map.values().iterator(); + } +} diff --git a/src/main/java/com/destroystokyo/paper/util/math/IntegerUtil.java b/src/main/java/com/destroystokyo/paper/util/math/IntegerUtil.java new file mode 100644 index 0000000000000000000000000000000000000000..c3b936f54b3fff418c265639ef223292ccc89356 --- /dev/null +++ b/src/main/java/com/destroystokyo/paper/util/math/IntegerUtil.java @@ -0,0 +1,230 @@ +package com.destroystokyo.paper.util.math; + +/** + * @author Spottedleaf + */ +public final class IntegerUtil { + + public static final int HIGH_BIT_U32 = Integer.MIN_VALUE; + public static final long HIGH_BIT_U64 = Long.MIN_VALUE; + + public static int ceilLog2(final int value) { + return Integer.SIZE - Integer.numberOfLeadingZeros(value - 1); // see doc of numberOfLeadingZeros + } + + public static long ceilLog2(final long value) { + return Long.SIZE - Long.numberOfLeadingZeros(value - 1); // see doc of numberOfLeadingZeros + } + + public static int floorLog2(final int value) { + // xor is optimized subtract for 2^n -1 + // note that (2^n -1) - k = (2^n -1) ^ k for k <= (2^n - 1) + return (Integer.SIZE - 1) ^ Integer.numberOfLeadingZeros(value); // see doc of numberOfLeadingZeros + } + + public static int floorLog2(final long value) { + // xor is optimized subtract for 2^n -1 + // note that (2^n -1) - k = (2^n -1) ^ k for k <= (2^n - 1) + return (Long.SIZE - 1) ^ Long.numberOfLeadingZeros(value); // see doc of numberOfLeadingZeros + } + + public static int roundCeilLog2(final int value) { + // optimized variant of 1 << (32 - leading(val - 1)) + // given + // 1 << n = HIGH_BIT_32 >>> (31 - n) for n [0, 32) + // 1 << (32 - leading(val - 1)) = HIGH_BIT_32 >>> (31 - (32 - leading(val - 1))) + // HIGH_BIT_32 >>> (31 - (32 - leading(val - 1))) + // HIGH_BIT_32 >>> (31 - 32 + leading(val - 1)) + // HIGH_BIT_32 >>> (-1 + leading(val - 1)) + return HIGH_BIT_U32 >>> (Integer.numberOfLeadingZeros(value - 1) - 1); + } + + public static long roundCeilLog2(final long value) { + // see logic documented above + return HIGH_BIT_U64 >>> (Long.numberOfLeadingZeros(value - 1) - 1); + } + + public static int roundFloorLog2(final int value) { + // optimized variant of 1 << (31 - leading(val)) + // given + // 1 << n = HIGH_BIT_32 >>> (31 - n) for n [0, 32) + // 1 << (31 - leading(val)) = HIGH_BIT_32 >> (31 - (31 - leading(val))) + // HIGH_BIT_32 >> (31 - (31 - leading(val))) + // HIGH_BIT_32 >> (31 - 31 + leading(val)) + return HIGH_BIT_U32 >>> Integer.numberOfLeadingZeros(value); + } + + public static long roundFloorLog2(final long value) { + // see logic documented above + return HIGH_BIT_U64 >>> Long.numberOfLeadingZeros(value); + } + + public static boolean isPowerOfTwo(final int n) { + // 2^n has one bit + // note: this rets true for 0 still + return IntegerUtil.getTrailingBit(n) == n; + } + + public static boolean isPowerOfTwo(final long n) { + // 2^n has one bit + // note: this rets true for 0 still + return IntegerUtil.getTrailingBit(n) == n; + } + + + public static int getTrailingBit(final int n) { + return -n & n; + } + + public static long getTrailingBit(final long n) { + return -n & n; + } + + public static int trailingZeros(final int n) { + return Integer.numberOfTrailingZeros(n); + } + + public static long trailingZeros(final long n) { + return Long.numberOfTrailingZeros(n); + } + + // from hacker's delight (signed division magic value) + public static int getDivisorMultiple(final long numbers) { + return (int)(numbers >>> 32); + } + + // from hacker's delight (signed division magic value) + public static int getDivisorShift(final long numbers) { + return (int)numbers; + } + + // copied from hacker's delight (signed division magic value) + // http://www.hackersdelight.org/hdcodetxt/magic.c.txt + public static long getDivisorNumbers(final int d) { + final int ad = IntegerUtil.branchlessAbs(d); + + if (ad < 2) { + throw new IllegalArgumentException("|number| must be in [2, 2^31 -1], not: " + d); + } + + final int two31 = 0x80000000; + final long mask = 0xFFFFFFFFL; // mask for enforcing unsigned behaviour + + int p = 31; + + // all these variables are UNSIGNED! + int t = two31 + (d >>> 31); + int anc = t - 1 - t%ad; + int q1 = (int)((two31 & mask)/(anc & mask)); + int r1 = two31 - q1*anc; + int q2 = (int)((two31 & mask)/(ad & mask)); + int r2 = two31 - q2*ad; + int delta; + + do { + p = p + 1; + q1 = 2*q1; // Update q1 = 2**p/|nc|. + r1 = 2*r1; // Update r1 = rem(2**p, |nc|). + if ((r1 & mask) >= (anc & mask)) {// (Must be an unsigned comparison here) + q1 = q1 + 1; + r1 = r1 - anc; + } + q2 = 2*q2; // Update q2 = 2**p/|d|. + r2 = 2*r2; // Update r2 = rem(2**p, |d|). + if ((r2 & mask) >= (ad & mask)) {// (Must be an unsigned comparison here) + q2 = q2 + 1; + r2 = r2 - ad; + } + delta = ad - r2; + } while ((q1 & mask) < (delta & mask) || (q1 == delta && r1 == 0)); + + int magicNum = q2 + 1; + if (d < 0) { + magicNum = -magicNum; + } + int shift = p - 32; + return ((long)magicNum << 32) | shift; + } + + public static int branchlessAbs(final int val) { + // -n = -1 ^ n + 1 + final int mask = val >> (Integer.SIZE - 1); // -1 if < 0, 0 if >= 0 + return (mask ^ val) - mask; // if val < 0, then (0 ^ val) - 0 else (-1 ^ val) + 1 + } + + public static long branchlessAbs(final long val) { + // -n = -1 ^ n + 1 + final long mask = val >> (Long.SIZE - 1); // -1 if < 0, 0 if >= 0 + return (mask ^ val) - mask; // if val < 0, then (0 ^ val) - 0 else (-1 ^ val) + 1 + } + + //https://github.com/skeeto/hash-prospector for hash functions + + //score = ~590.47984224483832 + public static int hash0(int x) { + x *= 0x36935555; + x ^= x >>> 16; + return x; + } + + //score = ~310.01596637036749 + public static int hash1(int x) { + x ^= x >>> 15; + x *= 0x356aaaad; + x ^= x >>> 17; + return x; + } + + public static int hash2(int x) { + x ^= x >>> 16; + x *= 0x7feb352d; + x ^= x >>> 15; + x *= 0x846ca68b; + x ^= x >>> 16; + return x; + } + + public static int hash3(int x) { + x ^= x >>> 17; + x *= 0xed5ad4bb; + x ^= x >>> 11; + x *= 0xac4c1b51; + x ^= x >>> 15; + x *= 0x31848bab; + x ^= x >>> 14; + return x; + } + + //score = ~365.79959673201887 + public static long hash1(long x) { + x ^= x >>> 27; + x *= 0xb24924b71d2d354bL; + x ^= x >>> 28; + return x; + } + + //h2 hash + public static long hash2(long x) { + x ^= x >>> 32; + x *= 0xd6e8feb86659fd93L; + x ^= x >>> 32; + x *= 0xd6e8feb86659fd93L; + x ^= x >>> 32; + return x; + } + + public static long hash3(long x) { + x ^= x >>> 45; + x *= 0xc161abe5704b6c79L; + x ^= x >>> 41; + x *= 0xe3e5389aedbc90f7L; + x ^= x >>> 56; + x *= 0x1f9aba75a52db073L; + x ^= x >>> 53; + return x; + } + + private IntegerUtil() { + throw new RuntimeException(); + } +} diff --git a/src/main/java/com/destroystokyo/paper/util/misc/AreaMap.java b/src/main/java/com/destroystokyo/paper/util/misc/AreaMap.java new file mode 100644 index 0000000000000000000000000000000000000000..c601f04b9c6dff76606763ea6f4a9a89b7e83203 --- /dev/null +++ b/src/main/java/com/destroystokyo/paper/util/misc/AreaMap.java @@ -0,0 +1,453 @@ +package com.destroystokyo.paper.util.misc; + +import com.destroystokyo.paper.util.math.IntegerUtil; +import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap; +import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; +import it.unimi.dsi.fastutil.objects.Object2LongOpenHashMap; +import net.minecraft.server.MCUtil; +import net.minecraft.server.MinecraftServer; +import net.minecraft.world.level.ChunkPos; +import javax.annotation.Nullable; +import java.util.Iterator; + +/** @author Spottedleaf */ +public abstract class AreaMap { + + /* Tested via https://gist.github.com/Spottedleaf/520419c6f41ef348fe9926ce674b7217 */ + + protected final Object2LongOpenHashMap objectToLastCoordinate = new Object2LongOpenHashMap<>(); + protected final Object2IntOpenHashMap objectToViewDistance = new Object2IntOpenHashMap<>(); + + { + this.objectToViewDistance.defaultReturnValue(-1); + this.objectToLastCoordinate.defaultReturnValue(Long.MIN_VALUE); + } + + // we use linked for better iteration. + // map of: coordinate to set of objects in coordinate + protected final Long2ObjectOpenHashMap> areaMap = new Long2ObjectOpenHashMap<>(1024, 0.7f); + protected final PooledLinkedHashSets pooledHashSets; + + protected final ChangeCallback addCallback; + protected final ChangeCallback removeCallback; + protected final ChangeSourceCallback changeSourceCallback; + + public AreaMap() { + this(new PooledLinkedHashSets<>()); + } + + // let users define a "global" or "shared" pooled sets if they wish + public AreaMap(final PooledLinkedHashSets pooledHashSets) { + this(pooledHashSets, null, null); + } + + public AreaMap(final PooledLinkedHashSets pooledHashSets, final ChangeCallback addCallback, final ChangeCallback removeCallback) { + this(pooledHashSets, addCallback, removeCallback, null); + } + public AreaMap(final PooledLinkedHashSets pooledHashSets, final ChangeCallback addCallback, final ChangeCallback removeCallback, final ChangeSourceCallback changeSourceCallback) { + this.pooledHashSets = pooledHashSets; + this.addCallback = addCallback; + this.removeCallback = removeCallback; + this.changeSourceCallback = changeSourceCallback; + } + + @Nullable + public final PooledLinkedHashSets.PooledObjectLinkedOpenHashSet getObjectsInRange(final long key) { + return this.areaMap.get(key); + } + + @Nullable + public final PooledLinkedHashSets.PooledObjectLinkedOpenHashSet getObjectsInRange(final ChunkPos chunkPos) { + return this.areaMap.get(MCUtil.getCoordinateKey(chunkPos)); + } + + @Nullable + public final PooledLinkedHashSets.PooledObjectLinkedOpenHashSet getObjectsInRange(final int chunkX, final int chunkZ) { + return this.areaMap.get(MCUtil.getCoordinateKey(chunkX, chunkZ)); + } + + // Long.MIN_VALUE indicates the object is not mapped + public final long getLastCoordinate(final E object) { + return this.objectToLastCoordinate.getOrDefault(object, Long.MIN_VALUE); + } + + // -1 indicates the object is not mapped + public final int getLastViewDistance(final E object) { + return this.objectToViewDistance.getOrDefault(object, -1); + } + + // returns the total number of mapped chunks + public final int size() { + return this.areaMap.size(); + } + + public final void addOrUpdate(final E object, final int chunkX, final int chunkZ, final int viewDistance) { + final int oldViewDistance = this.objectToViewDistance.put(object, viewDistance); + final long newPos = MCUtil.getCoordinateKey(chunkX, chunkZ); + final long oldPos = this.objectToLastCoordinate.put(object, newPos); + + if (oldViewDistance == -1) { + this.addObject(object, chunkX, chunkZ, Integer.MIN_VALUE, Integer.MIN_VALUE, viewDistance); + this.addObjectCallback(object, chunkX, chunkZ, viewDistance); + } else { + this.updateObject(object, oldPos, newPos, oldViewDistance, viewDistance); + this.updateObjectCallback(object, oldPos, newPos, oldViewDistance, viewDistance); + } + //this.validate(object, viewDistance); + } + + public final boolean update(final E object, final int chunkX, final int chunkZ, final int viewDistance) { + final int oldViewDistance = this.objectToViewDistance.replace(object, viewDistance); + if (oldViewDistance == -1) { + return false; + } else { + final long newPos = MCUtil.getCoordinateKey(chunkX, chunkZ); + final long oldPos = this.objectToLastCoordinate.put(object, newPos); + this.updateObject(object, oldPos, newPos, oldViewDistance, viewDistance); + this.updateObjectCallback(object, oldPos, newPos, oldViewDistance, viewDistance); + } + //this.validate(object, viewDistance); + return true; + } + + // called after the distance map updates + protected void updateObjectCallback(final E Object, final long oldPosition, final long newPosition, final int oldViewDistance, final int newViewDistance) { + if (newPosition != oldPosition && this.changeSourceCallback != null) { + this.changeSourceCallback.accept(Object, oldPosition, newPosition); + } + } + + public final boolean add(final E object, final int chunkX, final int chunkZ, final int viewDistance) { + final int oldViewDistance = this.objectToViewDistance.putIfAbsent(object, viewDistance); + if (oldViewDistance != -1) { + return false; + } + + final long newPos = MCUtil.getCoordinateKey(chunkX, chunkZ); + this.objectToLastCoordinate.put(object, newPos); + this.addObject(object, chunkX, chunkZ, Integer.MIN_VALUE, Integer.MIN_VALUE, viewDistance); + this.addObjectCallback(object, chunkX, chunkZ, viewDistance); + + //this.validate(object, viewDistance); + + return true; + } + + // called after the distance map updates + protected void addObjectCallback(final E object, final int chunkX, final int chunkZ, final int viewDistance) {} + + public final boolean remove(final E object) { + final long position = this.objectToLastCoordinate.removeLong(object); + final int viewDistance = this.objectToViewDistance.removeInt(object); + + if (viewDistance == -1) { + return false; + } + + final int currentX = MCUtil.getCoordinateX(position); + final int currentZ = MCUtil.getCoordinateZ(position); + + this.removeObject(object, currentX, currentZ, currentX, currentZ, viewDistance); + this.removeObjectCallback(object, currentX, currentZ, viewDistance); + //this.validate(object, -1); + return true; + } + + // called after the distance map updates + protected void removeObjectCallback(final E object, final int chunkX, final int chunkZ, final int viewDistance) {} + + protected abstract PooledLinkedHashSets.PooledObjectLinkedOpenHashSet getEmptySetFor(final E object); + + // expensive op, only for debug + protected void validate(final E object, final int viewDistance) { + int entiesGot = 0; + int expectedEntries = (2 * viewDistance + 1); + expectedEntries *= expectedEntries; + if (viewDistance < 0) { + expectedEntries = 0; + } + + final long currPosition = this.objectToLastCoordinate.getLong(object); + + final int centerX = MCUtil.getCoordinateX(currPosition); + final int centerZ = MCUtil.getCoordinateZ(currPosition); + + for (Iterator>> iterator = this.areaMap.long2ObjectEntrySet().fastIterator(); + iterator.hasNext();) { + + final Long2ObjectLinkedOpenHashMap.Entry> entry = iterator.next(); + final long key = entry.getLongKey(); + final PooledLinkedHashSets.PooledObjectLinkedOpenHashSet map = entry.getValue(); + + if (map.referenceCount == 0) { + throw new IllegalStateException("Invalid map"); + } + + if (map.contains(object)) { + ++entiesGot; + + final int chunkX = MCUtil.getCoordinateX(key); + final int chunkZ = MCUtil.getCoordinateZ(key); + + final int dist = Math.max(IntegerUtil.branchlessAbs(chunkX - centerX), IntegerUtil.branchlessAbs(chunkZ - centerZ)); + + if (dist > viewDistance) { + throw new IllegalStateException("Expected view distance " + viewDistance + ", got " + dist); + } + } + } + + if (entiesGot != expectedEntries) { + throw new IllegalStateException("Expected " + expectedEntries + ", got " + entiesGot); + } + } + + private void addObjectTo(final E object, final int chunkX, final int chunkZ, final int currChunkX, + final int currChunkZ, final int prevChunkX, final int prevChunkZ) { + final long key = MCUtil.getCoordinateKey(chunkX, chunkZ); + + PooledLinkedHashSets.PooledObjectLinkedOpenHashSet empty = this.getEmptySetFor(object); + PooledLinkedHashSets.PooledObjectLinkedOpenHashSet current = this.areaMap.putIfAbsent(key, empty); + + if (current != null) { + PooledLinkedHashSets.PooledObjectLinkedOpenHashSet next = this.pooledHashSets.findMapWith(current, object); + if (next == current) { + throw new IllegalStateException("Expected different map: got " + next.toString()); + } + this.areaMap.put(key, next); + + current = next; + // fall through to callback + } else { + current = empty; + } + + if (this.addCallback != null) { + try { + this.addCallback.accept(object, chunkX, chunkZ, currChunkX, currChunkZ, prevChunkX, prevChunkZ, current); + } catch (final Throwable ex) { + if (ex instanceof ThreadDeath) { + throw (ThreadDeath)ex; + } + MinecraftServer.LOGGER.error("Add callback for map threw exception ", ex); + } + } + } + + private void removeObjectFrom(final E object, final int chunkX, final int chunkZ, final int currChunkX, + final int currChunkZ, final int prevChunkX, final int prevChunkZ) { + final long key = MCUtil.getCoordinateKey(chunkX, chunkZ); + + PooledLinkedHashSets.PooledObjectLinkedOpenHashSet current = this.areaMap.get(key); + + if (current == null) { + throw new IllegalStateException("Current map may not be null for " + object + ", (" + chunkX + "," + chunkZ + ")"); + } + + PooledLinkedHashSets.PooledObjectLinkedOpenHashSet next = this.pooledHashSets.findMapWithout(current, object); + + if (next == current) { + throw new IllegalStateException("Current map [" + next.toString() + "] should have contained " + object + ", (" + chunkX + "," + chunkZ + ")"); + } + + if (next != null) { + this.areaMap.put(key, next); + } else { + this.areaMap.remove(key); + } + + if (this.removeCallback != null) { + try { + this.removeCallback.accept(object, chunkX, chunkZ, currChunkX, currChunkZ, prevChunkX, prevChunkZ, next); + } catch (final Throwable ex) { + if (ex instanceof ThreadDeath) { + throw (ThreadDeath)ex; + } + MinecraftServer.LOGGER.error("Remove callback for map threw exception ", ex); + } + } + } + + private void addObject(final E object, final int chunkX, final int chunkZ, final int prevChunkX, final int prevChunkZ, final int viewDistance) { + final int maxX = chunkX + viewDistance; + final int maxZ = chunkZ + viewDistance; + final int minX = chunkX - viewDistance; + final int minZ = chunkZ - viewDistance; + for (int x = minX; x <= maxX; ++x) { + for (int z = minZ; z <= maxZ; ++z) { + this.addObjectTo(object, x, z, chunkX, chunkZ, prevChunkX, prevChunkZ); + } + } + } + + private void removeObject(final E object, final int chunkX, final int chunkZ, final int currentChunkX, final int currentChunkZ, final int viewDistance) { + final int maxX = chunkX + viewDistance; + final int maxZ = chunkZ + viewDistance; + final int minX = chunkX - viewDistance; + final int minZ = chunkZ - viewDistance; + for (int x = minX; x <= maxX; ++x) { + for (int z = minZ; z <= maxZ; ++z) { + this.removeObjectFrom(object, x, z, currentChunkX, currentChunkZ, chunkX, chunkZ); + } + } + } + + /* math sign function except 0 returns 1 */ + protected static int sign(int val) { + return 1 | (val >> (Integer.SIZE - 1)); + } + + private void updateObject(final E object, final long oldPosition, final long newPosition, final int oldViewDistance, final int newViewDistance) { + final int toX = MCUtil.getCoordinateX(newPosition); + final int toZ = MCUtil.getCoordinateZ(newPosition); + final int fromX = MCUtil.getCoordinateX(oldPosition); + final int fromZ = MCUtil.getCoordinateZ(oldPosition); + + final int dx = toX - fromX; + final int dz = toZ - fromZ; + + final int totalX = IntegerUtil.branchlessAbs(fromX - toX); + final int totalZ = IntegerUtil.branchlessAbs(fromZ - toZ); + + if (Math.max(totalX, totalZ) > (2 * Math.max(newViewDistance, oldViewDistance))) { + // teleported? + this.removeObject(object, fromX, fromZ, fromX, fromZ, oldViewDistance); + this.addObject(object, toX, toZ, fromX, fromZ, newViewDistance); + return; + } + + if (oldViewDistance != newViewDistance) { + // remove loop + + final int oldMinX = fromX - oldViewDistance; + final int oldMinZ = fromZ - oldViewDistance; + final int oldMaxX = fromX + oldViewDistance; + final int oldMaxZ = fromZ + oldViewDistance; + for (int currX = oldMinX; currX <= oldMaxX; ++currX) { + for (int currZ = oldMinZ; currZ <= oldMaxZ; ++currZ) { + + // only remove if we're outside the new view distance... + if (Math.max(IntegerUtil.branchlessAbs(currX - toX), IntegerUtil.branchlessAbs(currZ - toZ)) > newViewDistance) { + this.removeObjectFrom(object, currX, currZ, toX, toZ, fromX, fromZ); + } + } + } + + // add loop + + final int newMinX = toX - newViewDistance; + final int newMinZ = toZ - newViewDistance; + final int newMaxX = toX + newViewDistance; + final int newMaxZ = toZ + newViewDistance; + for (int currX = newMinX; currX <= newMaxX; ++currX) { + for (int currZ = newMinZ; currZ <= newMaxZ; ++currZ) { + + // only add if we're outside the old view distance... + if (Math.max(IntegerUtil.branchlessAbs(currX - fromX), IntegerUtil.branchlessAbs(currZ - fromZ)) > oldViewDistance) { + this.addObjectTo(object, currX, currZ, toX, toZ, fromX, fromZ); + } + } + } + + return; + } + + // x axis is width + // z axis is height + // right refers to the x axis of where we moved + // top refers to the z axis of where we moved + + // same view distance + + // used for relative positioning + final int up = sign(dz); // 1 if dz >= 0, -1 otherwise + final int right = sign(dx); // 1 if dx >= 0, -1 otherwise + + // The area excluded by overlapping the two view distance squares creates four rectangles: + // Two on the left, and two on the right. The ones on the left we consider the "removed" section + // and on the right the "added" section. + // https://i.imgur.com/MrnOBgI.png is a reference image. Note that the outside border is not actually + // exclusive to the regions they surround. + + // 4 points of the rectangle + int maxX; // exclusive + int minX; // inclusive + int maxZ; // exclusive + int minZ; // inclusive + + if (dx != 0) { + // handle right addition + + maxX = toX + (oldViewDistance * right) + right; // exclusive + minX = fromX + (oldViewDistance * right) + right; // inclusive + maxZ = fromZ + (oldViewDistance * up) + up; // exclusive + minZ = toZ - (oldViewDistance * up); // inclusive + + for (int currX = minX; currX != maxX; currX += right) { + for (int currZ = minZ; currZ != maxZ; currZ += up) { + this.addObjectTo(object, currX, currZ, toX, toZ, fromX, fromZ); + } + } + } + + if (dz != 0) { + // handle up addition + + maxX = toX + (oldViewDistance * right) + right; // exclusive + minX = toX - (oldViewDistance * right); // inclusive + maxZ = toZ + (oldViewDistance * up) + up; // exclusive + minZ = fromZ + (oldViewDistance * up) + up; // inclusive + + for (int currX = minX; currX != maxX; currX += right) { + for (int currZ = minZ; currZ != maxZ; currZ += up) { + this.addObjectTo(object, currX, currZ, toX, toZ, fromX, fromZ); + } + } + } + + if (dx != 0) { + // handle left removal + + maxX = toX - (oldViewDistance * right); // exclusive + minX = fromX - (oldViewDistance * right); // inclusive + maxZ = fromZ + (oldViewDistance * up) + up; // exclusive + minZ = toZ - (oldViewDistance * up); // inclusive + + for (int currX = minX; currX != maxX; currX += right) { + for (int currZ = minZ; currZ != maxZ; currZ += up) { + this.removeObjectFrom(object, currX, currZ, toX, toZ, fromX, fromZ); + } + } + } + + if (dz != 0) { + // handle down removal + + maxX = fromX + (oldViewDistance * right) + right; // exclusive + minX = fromX - (oldViewDistance * right); // inclusive + maxZ = toZ - (oldViewDistance * up); // exclusive + minZ = fromZ - (oldViewDistance * up); // inclusive + + for (int currX = minX; currX != maxX; currX += right) { + for (int currZ = minZ; currZ != maxZ; currZ += up) { + this.removeObjectFrom(object, currX, currZ, toX, toZ, fromX, fromZ); + } + } + } + } + + @FunctionalInterface + public static interface ChangeCallback { + + // if there is no previous position, then prevPos = Integer.MIN_VALUE + void accept(final E object, final int rangeX, final int rangeZ, final int currPosX, final int currPosZ, final int prevPosX, final int prevPosZ, + final PooledLinkedHashSets.PooledObjectLinkedOpenHashSet newState); + + } + + @FunctionalInterface + public static interface ChangeSourceCallback { + void accept(final E object, final long prevPos, final long newPos); + } +} diff --git a/src/main/java/com/destroystokyo/paper/util/misc/DistanceTrackingAreaMap.java b/src/main/java/com/destroystokyo/paper/util/misc/DistanceTrackingAreaMap.java new file mode 100644 index 0000000000000000000000000000000000000000..9b8cb361767fbcf5f592db32a12186f0bd6373bd --- /dev/null +++ b/src/main/java/com/destroystokyo/paper/util/misc/DistanceTrackingAreaMap.java @@ -0,0 +1,175 @@ +package com.destroystokyo.paper.util.misc; + +import com.destroystokyo.paper.util.math.IntegerUtil; +import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap; +import net.minecraft.server.MCUtil; +import net.minecraft.world.level.ChunkPos; + +/** @author Spottedleaf */ +public abstract class DistanceTrackingAreaMap extends AreaMap { + + // use this map only if you need distance tracking, the tracking here is obviously going to hit harder. + + protected final Long2IntOpenHashMap chunkToNearestDistance = new Long2IntOpenHashMap(1024, 0.7f); + { + this.chunkToNearestDistance.defaultReturnValue(-1); + } + + protected final DistanceChangeCallback distanceChangeCallback; + + public DistanceTrackingAreaMap() { + this(new PooledLinkedHashSets<>()); + } + + // let users define a "global" or "shared" pooled sets if they wish + public DistanceTrackingAreaMap(final PooledLinkedHashSets pooledHashSets) { + this(pooledHashSets, null, null, null); + } + + public DistanceTrackingAreaMap(final PooledLinkedHashSets pooledHashSets, final ChangeCallback addCallback, final ChangeCallback removeCallback, + final DistanceChangeCallback distanceChangeCallback) { + super(pooledHashSets, addCallback, removeCallback); + this.distanceChangeCallback = distanceChangeCallback; + } + + // ret -1 if there is nothing mapped + public final int getNearestObjectDistance(final long key) { + return this.chunkToNearestDistance.get(key); + } + + // ret -1 if there is nothing mapped + public final int getNearestObjectDistance(final ChunkPos chunkPos) { + return this.chunkToNearestDistance.get(MCUtil.getCoordinateKey(chunkPos)); + } + + // ret -1 if there is nothing mapped + public final int getNearestObjectDistance(final int chunkX, final int chunkZ) { + return this.chunkToNearestDistance.get(MCUtil.getCoordinateKey(chunkX, chunkZ)); + } + + protected final void recalculateDistance(final int chunkX, final int chunkZ) { + final long key = MCUtil.getCoordinateKey(chunkX, chunkZ); + final PooledLinkedHashSets.PooledObjectLinkedOpenHashSet state = this.areaMap.get(key); + if (state == null) { + final int oldDistance = this.chunkToNearestDistance.remove(key); + // nothing here. + if (oldDistance == -1) { + // nothing was here previously + return; + } + if (this.distanceChangeCallback != null) { + this.distanceChangeCallback.accept(chunkX, chunkZ, oldDistance, -1, null); + } + return; + } + + int newDistance = Integer.MAX_VALUE; + + final Object[] rawData = state.getBackingSet(); + for (int i = 0, len = rawData.length; i < len; ++i) { + final Object raw = rawData[i]; + + if (raw == null) { + continue; + } + + final E object = (E)raw; + final long location = this.objectToLastCoordinate.getLong(object); + + final int distance = Math.max(IntegerUtil.branchlessAbs(chunkX - MCUtil.getCoordinateX(location)), IntegerUtil.branchlessAbs(chunkZ - MCUtil.getCoordinateZ(location))); + + if (distance < newDistance) { + newDistance = distance; + } + } + + final int oldDistance = this.chunkToNearestDistance.put(key, newDistance); + + if (oldDistance != newDistance) { + if (this.distanceChangeCallback != null) { + this.distanceChangeCallback.accept(chunkX, chunkZ, oldDistance, newDistance, state); + } + } + } + + @Override + protected void addObjectCallback(final E object, final int chunkX, final int chunkZ, final int viewDistance) { + final int maxX = chunkX + viewDistance; + final int maxZ = chunkZ + viewDistance; + final int minX = chunkX - viewDistance; + final int minZ = chunkZ - viewDistance; + for (int x = minX; x <= maxX; ++x) { + for (int z = minZ; z <= maxZ; ++z) { + this.recalculateDistance(x, z); + } + } + } + + @Override + protected void removeObjectCallback(final E object, final int chunkX, final int chunkZ, final int viewDistance) { + final int maxX = chunkX + viewDistance; + final int maxZ = chunkZ + viewDistance; + final int minX = chunkX - viewDistance; + final int minZ = chunkZ - viewDistance; + for (int x = minX; x <= maxX; ++x) { + for (int z = minZ; z <= maxZ; ++z) { + this.recalculateDistance(x, z); + } + } + } + + @Override + protected void updateObjectCallback(final E object, final long oldPosition, final long newPosition, final int oldViewDistance, final int newViewDistance) { + if (oldPosition == newPosition && newViewDistance == oldViewDistance) { + return; + } + + final int toX = MCUtil.getCoordinateX(newPosition); + final int toZ = MCUtil.getCoordinateZ(newPosition); + final int fromX = MCUtil.getCoordinateX(oldPosition); + final int fromZ = MCUtil.getCoordinateZ(oldPosition); + + final int totalX = IntegerUtil.branchlessAbs(fromX - toX); + final int totalZ = IntegerUtil.branchlessAbs(fromZ - toZ); + + if (Math.max(totalX, totalZ) > (2 * Math.max(newViewDistance, oldViewDistance))) { + // teleported? + this.removeObjectCallback(object, fromX, fromZ, oldViewDistance); + this.addObjectCallback(object, toX, toZ, newViewDistance); + return; + } + + final int minX = Math.min(fromX - oldViewDistance, toX - newViewDistance); + final int maxX = Math.max(fromX + oldViewDistance, toX + newViewDistance); + final int minZ = Math.min(fromZ - oldViewDistance, toZ - newViewDistance); + final int maxZ = Math.max(fromZ + oldViewDistance, toZ + newViewDistance); + + for (int x = minX; x <= maxX; ++x) { + for (int z = minZ; z <= maxZ; ++z) { + final int distXOld = IntegerUtil.branchlessAbs(x - fromX); + final int distZOld = IntegerUtil.branchlessAbs(z - fromZ); + + if (Math.max(distXOld, distZOld) <= oldViewDistance) { + this.recalculateDistance(x, z); + continue; + } + + final int distXNew = IntegerUtil.branchlessAbs(x - toX); + final int distZNew = IntegerUtil.branchlessAbs(z - toZ); + + if (Math.max(distXNew, distZNew) <= newViewDistance) { + this.recalculateDistance(x, z); + continue; + } + } + } + } + + @FunctionalInterface + public static interface DistanceChangeCallback { + + void accept(final int posX, final int posZ, final int oldNearestDistance, final int newNearestDistance, + final PooledLinkedHashSets.PooledObjectLinkedOpenHashSet state); + + } +} diff --git a/src/main/java/com/destroystokyo/paper/util/misc/PlayerAreaMap.java b/src/main/java/com/destroystokyo/paper/util/misc/PlayerAreaMap.java new file mode 100644 index 0000000000000000000000000000000000000000..46954db7ecd35ac4018fdf476df7c8020d7ce6c8 --- /dev/null +++ b/src/main/java/com/destroystokyo/paper/util/misc/PlayerAreaMap.java @@ -0,0 +1,32 @@ +package com.destroystokyo.paper.util.misc; + +import net.minecraft.server.level.ServerPlayer; + +/** + * @author Spottedleaf + */ +public final class PlayerAreaMap extends AreaMap { + + public PlayerAreaMap() { + super(); + } + + public PlayerAreaMap(final PooledLinkedHashSets pooledHashSets) { + super(pooledHashSets); + } + + public PlayerAreaMap(final PooledLinkedHashSets pooledHashSets, final ChangeCallback addCallback, + final ChangeCallback removeCallback) { + this(pooledHashSets, addCallback, removeCallback, null); + } + + public PlayerAreaMap(final PooledLinkedHashSets pooledHashSets, final ChangeCallback addCallback, + final ChangeCallback removeCallback, final ChangeSourceCallback changeSourceCallback) { + super(pooledHashSets, addCallback, removeCallback, changeSourceCallback); + } + + @Override + protected PooledLinkedHashSets.PooledObjectLinkedOpenHashSet getEmptySetFor(final ServerPlayer player) { + return player.cachedSingleHashSet; + } +} diff --git a/src/main/java/com/destroystokyo/paper/util/misc/PlayerDistanceTrackingAreaMap.java b/src/main/java/com/destroystokyo/paper/util/misc/PlayerDistanceTrackingAreaMap.java new file mode 100644 index 0000000000000000000000000000000000000000..d05dcea15f7047b58736c7c0e07920a04d6c5abe --- /dev/null +++ b/src/main/java/com/destroystokyo/paper/util/misc/PlayerDistanceTrackingAreaMap.java @@ -0,0 +1,24 @@ +package com.destroystokyo.paper.util.misc; + +import net.minecraft.server.level.ServerPlayer; + +public class PlayerDistanceTrackingAreaMap extends DistanceTrackingAreaMap { + + public PlayerDistanceTrackingAreaMap() { + super(); + } + + public PlayerDistanceTrackingAreaMap(final PooledLinkedHashSets pooledHashSets) { + super(pooledHashSets); + } + + public PlayerDistanceTrackingAreaMap(final PooledLinkedHashSets pooledHashSets, final ChangeCallback addCallback, + final ChangeCallback removeCallback, final DistanceChangeCallback distanceChangeCallback) { + super(pooledHashSets, addCallback, removeCallback, distanceChangeCallback); + } + + @Override + protected PooledLinkedHashSets.PooledObjectLinkedOpenHashSet getEmptySetFor(final ServerPlayer player) { + return player.cachedSingleHashSet; + } +} diff --git a/src/main/java/com/destroystokyo/paper/util/misc/PooledLinkedHashSets.java b/src/main/java/com/destroystokyo/paper/util/misc/PooledLinkedHashSets.java new file mode 100644 index 0000000000000000000000000000000000000000..e51104e65a07b6ea7bbbcbb6afb066ef6401cc5b --- /dev/null +++ b/src/main/java/com/destroystokyo/paper/util/misc/PooledLinkedHashSets.java @@ -0,0 +1,287 @@ +package com.destroystokyo.paper.util.misc; + +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; +import java.lang.ref.WeakReference; + +/** @author Spottedleaf */ +public class PooledLinkedHashSets { + + /* Tested via https://gist.github.com/Spottedleaf/a93bb7a8993d6ce142d3efc5932bf573 */ + + // we really want to avoid that equals() check as much as possible... + protected final Object2ObjectOpenHashMap, PooledObjectLinkedOpenHashSet> mapPool = new Object2ObjectOpenHashMap<>(128, 0.25f); + + protected void decrementReferenceCount(final PooledObjectLinkedOpenHashSet current) { + if (current.referenceCount == 0) { + throw new IllegalStateException("Cannot decrement reference count for " + current); + } + if (current.referenceCount == -1 || --current.referenceCount > 0) { + return; + } + + this.mapPool.remove(current); + return; + } + + public PooledObjectLinkedOpenHashSet findMapWith(final PooledObjectLinkedOpenHashSet current, final E object) { + final PooledObjectLinkedOpenHashSet cached = current.getAddCache(object); + + if (cached != null) { + decrementReferenceCount(current); + + if (cached.referenceCount == 0) { + // bring the map back from the dead + PooledObjectLinkedOpenHashSet contending = this.mapPool.putIfAbsent(cached, cached); + if (contending != null) { + // a map already exists with the elements we want + if (contending.referenceCount != -1) { + ++contending.referenceCount; + } + current.updateAddCache(object, contending); + return contending; + } + + cached.referenceCount = 1; + } else if (cached.referenceCount != -1) { + ++cached.referenceCount; + } + + return cached; + } + + if (!current.add(object)) { + return current; + } + + // we use get/put since we use a different key on put + PooledObjectLinkedOpenHashSet ret = this.mapPool.get(current); + + if (ret == null) { + ret = new PooledObjectLinkedOpenHashSet<>(current); + current.remove(object); + this.mapPool.put(ret, ret); + ret.referenceCount = 1; + } else { + if (ret.referenceCount != -1) { + ++ret.referenceCount; + } + current.remove(object); + } + + current.updateAddCache(object, ret); + + decrementReferenceCount(current); + return ret; + } + + // rets null if current.size() == 1 + public PooledObjectLinkedOpenHashSet findMapWithout(final PooledObjectLinkedOpenHashSet current, final E object) { + if (current.set.size() == 1) { + decrementReferenceCount(current); + return null; + } + + final PooledObjectLinkedOpenHashSet cached = current.getRemoveCache(object); + + if (cached != null) { + decrementReferenceCount(current); + + if (cached.referenceCount == 0) { + // bring the map back from the dead + PooledObjectLinkedOpenHashSet contending = this.mapPool.putIfAbsent(cached, cached); + if (contending != null) { + // a map already exists with the elements we want + if (contending.referenceCount != -1) { + ++contending.referenceCount; + } + current.updateRemoveCache(object, contending); + return contending; + } + + cached.referenceCount = 1; + } else if (cached.referenceCount != -1) { + ++cached.referenceCount; + } + + return cached; + } + + if (!current.remove(object)) { + return current; + } + + // we use get/put since we use a different key on put + PooledObjectLinkedOpenHashSet ret = this.mapPool.get(current); + + if (ret == null) { + ret = new PooledObjectLinkedOpenHashSet<>(current); + current.add(object); + this.mapPool.put(ret, ret); + ret.referenceCount = 1; + } else { + if (ret.referenceCount != -1) { + ++ret.referenceCount; + } + current.add(object); + } + + current.updateRemoveCache(object, ret); + + decrementReferenceCount(current); + return ret; + } + + static final class RawSetObjectLinkedOpenHashSet extends ObjectOpenHashSet { + + public RawSetObjectLinkedOpenHashSet() { + super(); + } + + public RawSetObjectLinkedOpenHashSet(final int capacity) { + super(capacity); + } + + public RawSetObjectLinkedOpenHashSet(final int capacity, final float loadFactor) { + super(capacity, loadFactor); + } + + @Override + public RawSetObjectLinkedOpenHashSet clone() { + return (RawSetObjectLinkedOpenHashSet)super.clone(); + } + + public E[] getRawSet() { + return this.key; + } + } + + public static final class PooledObjectLinkedOpenHashSet { + + private static final WeakReference NULL_REFERENCE = new WeakReference<>(null); + + final RawSetObjectLinkedOpenHashSet set; + int referenceCount; // -1 if special + int hash; // optimize hashcode + + // add cache + WeakReference lastAddObject = NULL_REFERENCE; + WeakReference> lastAddMap = NULL_REFERENCE; + + // remove cache + WeakReference lastRemoveObject = NULL_REFERENCE; + WeakReference> lastRemoveMap = NULL_REFERENCE; + + public PooledObjectLinkedOpenHashSet(final PooledLinkedHashSets pooledSets) { + this.set = new RawSetObjectLinkedOpenHashSet<>(2, 0.8f); + } + + public PooledObjectLinkedOpenHashSet(final E single) { + this((PooledLinkedHashSets)null); + this.referenceCount = -1; + this.add(single); + } + + public PooledObjectLinkedOpenHashSet(final PooledObjectLinkedOpenHashSet other) { + this.set = other.set.clone(); + this.hash = other.hash; + } + + // from https://github.com/Spottedleaf/ConcurrentUtil/blob/master/src/main/java/ca/spottedleaf/concurrentutil/util/IntegerUtil.java + // generated by https://github.com/skeeto/hash-prospector + private static int hash0(int x) { + x *= 0x36935555; + x ^= x >>> 16; + return x; + } + + PooledObjectLinkedOpenHashSet getAddCache(final E element) { + final E currentAdd = this.lastAddObject.get(); + + if (currentAdd == null || !(currentAdd == element || currentAdd.equals(element))) { + return null; + } + + return this.lastAddMap.get(); + } + + PooledObjectLinkedOpenHashSet getRemoveCache(final E element) { + final E currentRemove = this.lastRemoveObject.get(); + + if (currentRemove == null || !(currentRemove == element || currentRemove.equals(element))) { + return null; + } + + return this.lastRemoveMap.get(); + } + + void updateAddCache(final E element, final PooledObjectLinkedOpenHashSet map) { + this.lastAddObject = new WeakReference<>(element); + this.lastAddMap = new WeakReference<>(map); + } + + void updateRemoveCache(final E element, final PooledObjectLinkedOpenHashSet map) { + this.lastRemoveObject = new WeakReference<>(element); + this.lastRemoveMap = new WeakReference<>(map); + } + + boolean add(final E element) { + boolean added = this.set.add(element); + + if (added) { + this.hash += hash0(element.hashCode()); + } + + return added; + } + + boolean remove(Object element) { + boolean removed = this.set.remove(element); + + if (removed) { + this.hash -= hash0(element.hashCode()); + } + + return removed; + } + + public boolean contains(final Object element) { + return this.set.contains(element); + } + + public E[] getBackingSet() { + return this.set.getRawSet(); + } + + public int size() { + return this.set.size(); + } + + @Override + public int hashCode() { + return this.hash; + } + + @Override + public boolean equals(final Object other) { + if (!(other instanceof PooledObjectLinkedOpenHashSet)) { + return false; + } + if (this.referenceCount == 0) { + return other == this; + } else { + if (other == this) { + // Unfortunately we are never equal to our own instance while in use! + return false; + } + return this.hash == ((PooledObjectLinkedOpenHashSet)other).hash && this.set.equals(((PooledObjectLinkedOpenHashSet)other).set); + } + } + + @Override + public String toString() { + return "PooledHashSet: size: " + this.set.size() + ", reference count: " + this.referenceCount + ", hash: " + + this.hashCode() + ", identity: " + System.identityHashCode(this) + " map: " + this.set.toString(); + } + } +} diff --git a/src/main/java/com/destroystokyo/paper/util/pooled/PooledObjects.java b/src/main/java/com/destroystokyo/paper/util/pooled/PooledObjects.java new file mode 100644 index 0000000000000000000000000000000000000000..d0c77068e9a53d1b8bbad0f3f6b420d6bc85f8c8 --- /dev/null +++ b/src/main/java/com/destroystokyo/paper/util/pooled/PooledObjects.java @@ -0,0 +1,85 @@ +package com.destroystokyo.paper.util.pooled; + +import net.minecraft.server.MCUtil; +import org.apache.commons.lang3.mutable.MutableInt; + +import java.util.ArrayDeque; +import java.util.function.Consumer; +import java.util.function.Supplier; + +public final class PooledObjects { + + /** + * Wrapper for an object that will be have a cleaner registered for it, and may be automatically returned to pool. + */ + public class AutoReleased { + private final E object; + private final Runnable cleaner; + + public AutoReleased(E object, Runnable cleaner) { + this.object = object; + this.cleaner = cleaner; + } + + public final E getObject() { + return object; + } + + public final Runnable getCleaner() { + return cleaner; + } + } + + public static final PooledObjects POOLED_MUTABLE_INTEGERS = new PooledObjects<>(MutableInt::new, 1024); + + private final Supplier creator; + private final Consumer releaser; + private final int maxPoolSize; + private final ArrayDeque queue; + + public PooledObjects(final Supplier creator, int maxPoolSize) { + this(creator, maxPoolSize, null); + } + public PooledObjects(final Supplier creator, int maxPoolSize, Consumer releaser) { + if (creator == null) { + throw new NullPointerException("Creator must not be null"); + } + if (maxPoolSize <= 0) { + throw new IllegalArgumentException("Max pool size must be greater-than 0"); + } + + this.queue = new ArrayDeque<>(maxPoolSize); + this.maxPoolSize = maxPoolSize; + this.creator = creator; + this.releaser = releaser; + } + + public AutoReleased acquireCleaner(Object holder) { + return acquireCleaner(holder, this::release); + } + + public AutoReleased acquireCleaner(Object holder, Consumer releaser) { + E resource = acquire(); + Runnable cleaner = MCUtil.registerCleaner(holder, resource, releaser); + return new AutoReleased(resource, cleaner); + } + + public final E acquire() { + E value; + synchronized (queue) { + value = this.queue.pollLast(); + } + return value != null ? value : this.creator.get(); + } + + public final void release(final E value) { + if (this.releaser != null) { + this.releaser.accept(value); + } + synchronized (this.queue) { + if (queue.size() < this.maxPoolSize) { + this.queue.addLast(value); + } + } + } +} diff --git a/src/main/java/com/destroystokyo/paper/util/set/OptimizedSmallEnumSet.java b/src/main/java/com/destroystokyo/paper/util/set/OptimizedSmallEnumSet.java new file mode 100644 index 0000000000000000000000000000000000000000..9df0006c1a283f77c4d01d9fce9062fc1c9bbb1f --- /dev/null +++ b/src/main/java/com/destroystokyo/paper/util/set/OptimizedSmallEnumSet.java @@ -0,0 +1,67 @@ +package com.destroystokyo.paper.util.set; + +import java.util.Collection; + +/** + * @author Spottedleaf + */ +public final class OptimizedSmallEnumSet> { + + private final Class enumClass; + private long backingSet; + + public OptimizedSmallEnumSet(final Class clazz) { + if (clazz == null) { + throw new IllegalArgumentException("Null class"); + } + if (!clazz.isEnum()) { + throw new IllegalArgumentException("Class must be enum, not " + clazz.getCanonicalName()); + } + this.enumClass = clazz; + } + + public boolean addUnchecked(final E element) { + final int ordinal = element.ordinal(); + final long key = 1L << ordinal; + + final long prev = this.backingSet; + this.backingSet = prev | key; + + return (prev & key) == 0; + } + + public boolean removeUnchecked(final E element) { + final int ordinal = element.ordinal(); + final long key = 1L << ordinal; + + final long prev = this.backingSet; + this.backingSet = prev & ~key; + + return (prev & key) != 0; + } + + public void clear() { + this.backingSet = 0L; + } + + public int size() { + return Long.bitCount(this.backingSet); + } + + public void addAllUnchecked(final Collection enums) { + for (final E element : enums) { + if (element == null) { + throw new NullPointerException("Null element"); + } + this.backingSet |= (1L << element.ordinal()); + } + } + + public long getBackingSet() { + return this.backingSet; + } + + public boolean hasCommonElements(final OptimizedSmallEnumSet other) { + return (other.backingSet & this.backingSet) != 0; + } +} diff --git a/src/main/java/net/minecraft/Util.java b/src/main/java/net/minecraft/Util.java index 8c378d3f3138953b3b22b289fecdb6b40a09ab63..67fa685f4b8de3eae1431c0de399c246678b542a 100644 --- a/src/main/java/net/minecraft/Util.java +++ b/src/main/java/net/minecraft/Util.java @@ -78,7 +78,7 @@ public class Util { } public static long getNanos() { - return Util.timeSource.getAsLong(); + return System.nanoTime(); // Paper } public static long getEpochMillis() { diff --git a/src/main/java/net/minecraft/core/BlockPos.java b/src/main/java/net/minecraft/core/BlockPos.java index 88147a1f25cf2fd549412b653b8f0eb5c60bb55d..6a58059a05e16d96894b67a544c2f595d9546c78 100644 --- a/src/main/java/net/minecraft/core/BlockPos.java +++ b/src/main/java/net/minecraft/core/BlockPos.java @@ -105,6 +105,7 @@ public class BlockPos extends Vec3i { return x == 0.0D && y == 0.0D && z == 0.0D ? this : new BlockPos((double) this.getX() + x, (double) this.getY() + y, (double) this.getZ() + z); } + public final BlockPos add(int i, int j, int k) {return offset(i, j, k);} // Paper - OBFHELPER public BlockPos offset(int x, int y, int z) { return x == 0 && y == 0 && z == 0 ? this : new BlockPos(this.getX() + x, this.getY() + y, this.getZ() + z); } @@ -436,6 +437,7 @@ public class BlockPos extends Vec3i { return super.rotate(rotation).immutable(); } + public final BlockPos.MutableBlockPos setValues(int i, int j, int k) { return set(i, j, k);} // Paper - OBFHELPER public BlockPos.MutableBlockPos set(int x, int y, int z) { this.setX(x); this.setY(y); @@ -443,6 +445,7 @@ public class BlockPos extends Vec3i { return this; } + public final BlockPos.MutableBlockPos setValues(double d0, double d1, double d2) { return set(d0, d1, d2);} // Paper - OBFHELPER public BlockPos.MutableBlockPos set(double x, double y, double z) { return this.set(Mth.floor(x), Mth.floor(y), Mth.floor(z)); } @@ -496,20 +499,21 @@ public class BlockPos extends Vec3i { } } + /* // Paper start - comment out useless overrides @Override @Override - public void setX(int x) { - super.setX(x); + public void o(int i) { + super.o(i); } @Override - public void setY(int y) { - super.setY(y); + public void p(int i) { + super.p(i); } - @Override - public void setZ(int z) { - super.setZ(z); + public void q(int i) { + super.q(i); } + */ // Paper end @Override public BlockPos immutable() { diff --git a/src/main/java/net/minecraft/core/IdMapper.java b/src/main/java/net/minecraft/core/IdMapper.java index 424c6cacc2e7c7b1c9d0b92fe198237033a3fcbd..e7358721e9d78bc9cbbfc3e71ce927ea4b82ce7c 100644 --- a/src/main/java/net/minecraft/core/IdMapper.java +++ b/src/main/java/net/minecraft/core/IdMapper.java @@ -64,6 +64,7 @@ public class IdMapper implements IdMap { return Iterators.filter(this.idToT.iterator(), Predicates.notNull()); } + public int size() { return this.size(); } // Paper - OBFHELPER public int size() { return this.tToId.size(); } diff --git a/src/main/java/net/minecraft/core/Vec3i.java b/src/main/java/net/minecraft/core/Vec3i.java index 9bd2120bdcfe204184eb9a9e1daa5e3338665e51..3e79b274b8e0406a3cbdd94c7cec091b583109ca 100644 --- a/src/main/java/net/minecraft/core/Vec3i.java +++ b/src/main/java/net/minecraft/core/Vec3i.java @@ -18,9 +18,9 @@ public class Vec3i implements Comparable { return IntStream.of(new int[]{baseblockposition.getX(), baseblockposition.getY(), baseblockposition.getZ()}); }); public static final Vec3i ZERO = new Vec3i(0, 0, 0); - private int x; - private int y; - private int z; + private int x;public final void setX(final int x) { this.x = x; } // Paper - OBFHELPER + private int y;public final void setY(final int y) { this.y = y; } // Paper - OBFHELPER + private int z;public final void setZ(final int z) { this.z = z; } // Paper - OBFHELPER public Vec3i(int x, int y, int z) { this.x = x; @@ -64,15 +64,15 @@ public class Vec3i implements Comparable { return this.z; } - protected void setX(int x) { + public void setX(int x) { // Paper - protected -> public this.x = x; } - protected void setY(int y) { + public void setY(int y) { // Paper - protected -> public this.y = y; } - protected void setZ(int z) { + public void setZ(int z) { // Paper - protected -> public this.z = z; } @@ -108,6 +108,7 @@ public class Vec3i implements Comparable { return this.distSqr(pos.x(), pos.y(), pos.z(), true) < distance * distance; } + public final double distanceSquared(Vec3i baseblockposition) { return distSqr(baseblockposition); } // Paper - OBFHELPER public double distSqr(Vec3i vec) { return this.distSqr((double) vec.getX(), (double) vec.getY(), (double) vec.getZ(), true); } diff --git a/src/main/java/net/minecraft/nbt/CompoundTag.java b/src/main/java/net/minecraft/nbt/CompoundTag.java index 077383bd9af79851351eba50e7d7ea31cc106cad..4c8f249e45e5deb7628997d4dbd9dab613ac5241 100644 --- a/src/main/java/net/minecraft/nbt/CompoundTag.java +++ b/src/main/java/net/minecraft/nbt/CompoundTag.java @@ -76,7 +76,7 @@ public class CompoundTag implements Tag { return "TAG_Compound"; } }; - private final Map tags; + public final Map tags; // Paper protected CompoundTag(Map tags) { this.tags = tags; @@ -139,10 +139,16 @@ public class CompoundTag implements Tag { this.tags.put(key, LongTag.valueOf(value)); } + public void setUUID(String prefix, UUID uuid) { putUUID(prefix, uuid); } // Paper - OBFHELPER public void putUUID(String key, UUID value) { this.tags.put(key, NbtUtils.createUUID(value)); } + + /** + * You must use {@link #hasUUID(String)} before or else it will throw an NPE. + */ + public UUID getUUID(String prefix) { return getUUID(prefix); } // Paper - OBFHELPER public UUID getUUID(String key) { return NbtUtils.loadUUID(this.get(key)); } diff --git a/src/main/java/net/minecraft/network/Connection.java b/src/main/java/net/minecraft/network/Connection.java index 8ce3a74821a0e540423a2e8e67be640b8b876035..92c5c5bbcfe364475578b6a0eddfaa85858ace8a 100644 --- a/src/main/java/net/minecraft/network/Connection.java +++ b/src/main/java/net/minecraft/network/Connection.java @@ -168,6 +168,7 @@ public class Connection extends SimpleChannelInboundHandler> { } + private void dispatchPacket(Packet packet, @Nullable GenericFutureListener> genericFutureListener) { this.sendPacket(packet, genericFutureListener); } // Paper - OBFHELPER private void sendPacket(Packet packet, @Nullable GenericFutureListener> callback) { ConnectionProtocol enumprotocol = ConnectionProtocol.getProtocolForPacket(packet); ConnectionProtocol enumprotocol1 = (ConnectionProtocol) this.channel.attr(Connection.ATTRIBUTE_PROTOCOL).get(); @@ -208,6 +209,7 @@ public class Connection extends SimpleChannelInboundHandler> { } + private void sendPacketQueue() { this.flushQueue(); } // Paper - OBFHELPER private void flushQueue() { if (this.channel != null && this.channel.isOpen()) { Queue queue = this.queue; @@ -344,9 +346,9 @@ public class Connection extends SimpleChannelInboundHandler> { static class PacketHolder { - private final Packet packet; + private final Packet packet; private final Packet getPacket() { return this.packet; } // Paper - OBFHELPER @Nullable - private final GenericFutureListener> listener; + private final GenericFutureListener> listener; private final GenericFutureListener> getGenericFutureListener() { return this.listener; } // Paper - OBFHELPER public PacketHolder(Packet packet, @Nullable GenericFutureListener> callback) { this.packet = packet; diff --git a/src/main/java/net/minecraft/network/FriendlyByteBuf.java b/src/main/java/net/minecraft/network/FriendlyByteBuf.java index e0dc41a8f408b7fa0b8554833ea4d09e7e604913..50f14acb062c2f90266279dbd1945a3297396f0b 100644 --- a/src/main/java/net/minecraft/network/FriendlyByteBuf.java +++ b/src/main/java/net/minecraft/network/FriendlyByteBuf.java @@ -48,6 +48,7 @@ public class FriendlyByteBuf extends ByteBuf { this.source = bytebuf; } + public static int countBytes(int i) { return FriendlyByteBuf.getVarIntSize(i); } // Paper - OBFHELPER public static int getVarIntSize(int i) { for (int j = 1; j < 5; ++j) { if ((i & -1 << j * 7) == 0) { diff --git a/src/main/java/net/minecraft/network/PacketEncoder.java b/src/main/java/net/minecraft/network/PacketEncoder.java index 7af36e5a889d04f6e80c80f7335bf149a4b5d224..14fa1371e52b9af5a7550a9aa144fa406b754046 100644 --- a/src/main/java/net/minecraft/network/PacketEncoder.java +++ b/src/main/java/net/minecraft/network/PacketEncoder.java @@ -44,6 +44,7 @@ public class PacketEncoder extends MessageToByteEncoder> { packet.write(packetdataserializer); } catch (Throwable throwable) { PacketEncoder.LOGGER.error(throwable); + throwable.printStackTrace(); // Paper - WHAT WAS IT? WHO DID THIS TO YOU? WHAT DID YOU SEE? if (packet.isSkippable()) { throw new SkipPacketException(throwable); } else { diff --git a/src/main/java/net/minecraft/network/protocol/game/ClientboundLevelChunkPacket.java b/src/main/java/net/minecraft/network/protocol/game/ClientboundLevelChunkPacket.java index bda189ab2b3b934e6bf9fd11da5d95bd9b37ba70..e5d4363edb8c494d2db69d2e0223a2db1519f64b 100644 --- a/src/main/java/net/minecraft/network/protocol/game/ClientboundLevelChunkPacket.java +++ b/src/main/java/net/minecraft/network/protocol/game/ClientboundLevelChunkPacket.java @@ -28,7 +28,7 @@ public class ClientboundLevelChunkPacket implements Packet blockEntitiesTags; private boolean fullChunk; @@ -140,6 +140,7 @@ public class ClientboundLevelChunkPacket implements Packet { - private ItemStack book; + private ItemStack book; public ItemStack getBook() { return book; } // Paper - OBFHELPER private boolean signing; private int slot; diff --git a/src/main/java/net/minecraft/server/MCUtil.java b/src/main/java/net/minecraft/server/MCUtil.java new file mode 100644 index 0000000000000000000000000000000000000000..d29fe67b7d39e368a873368a6be16042429e9209 --- /dev/null +++ b/src/main/java/net/minecraft/server/MCUtil.java @@ -0,0 +1,510 @@ +package net.minecraft.server; + +import com.destroystokyo.paper.block.TargetBlockInfo; +import com.google.common.util.concurrent.ThreadFactoryBuilder; +import it.unimi.dsi.fastutil.objects.ObjectRBTreeSet; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.ClipContext; +import net.minecraft.world.level.Level; +import org.apache.commons.lang.exception.ExceptionUtils; +import org.bukkit.Location; +import org.bukkit.block.BlockFace; +import org.bukkit.craftbukkit.CraftWorld; +import org.bukkit.craftbukkit.util.Waitable; +import org.spigotmc.AsyncCatcher; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.util.List; +import java.util.Queue; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.function.Supplier; + +public final class MCUtil { + public static final ThreadPoolExecutor asyncExecutor = new ThreadPoolExecutor( + 0, 2, 60L, TimeUnit.SECONDS, + new LinkedBlockingQueue(), + new ThreadFactoryBuilder().setNameFormat("Paper Async Task Handler Thread - %1$d").build() + ); + public static final ThreadPoolExecutor cleanerExecutor = new ThreadPoolExecutor( + 1, 1, 0L, TimeUnit.SECONDS, + new LinkedBlockingQueue(), + new ThreadFactoryBuilder().setNameFormat("Paper Object Cleaner").build() + ); + + public static final long INVALID_CHUNK_KEY = getCoordinateKey(Integer.MAX_VALUE, Integer.MAX_VALUE); + + + public static Runnable once(Runnable run) { + AtomicBoolean ran = new AtomicBoolean(false); + return () -> { + if (ran.compareAndSet(false, true)) { + run.run(); + } + }; + } + + public static Runnable once(List list, Consumer cb) { + return once(() -> { + list.forEach(cb); + }); + } + + private static Runnable makeCleanerCallback(Runnable run) { + return once(() -> cleanerExecutor.execute(run)); + } + + /** + * DANGER WILL ROBINSON: Be sure you do not use a lambda that lives in the object being monitored, or leaky leaky! + * @param obj + * @param run + * @return + */ + public static Runnable registerCleaner(Object obj, Runnable run) { + // Wrap callback in its own method above or the lambda will leak object + Runnable cleaner = makeCleanerCallback(run); + co.aikar.cleaner.Cleaner.register(obj, cleaner); + return cleaner; + } + + /** + * DANGER WILL ROBINSON: Be sure you do not use a lambda that lives in the object being monitored, or leaky leaky! + * @param obj + * @param list + * @param cleaner + * @param + * @return + */ + public static Runnable registerListCleaner(Object obj, List list, Consumer cleaner) { + return registerCleaner(obj, () -> { + list.forEach(cleaner); + list.clear(); + }); + } + + /** + * DANGER WILL ROBINSON: Be sure you do not use a lambda that lives in the object being monitored, or leaky leaky! + * @param obj + * @param resource + * @param cleaner + * @param + * @return + */ + public static Runnable registerCleaner(Object obj, T resource, java.util.function.Consumer cleaner) { + return registerCleaner(obj, () -> cleaner.accept(resource)); + } + + public static List getSpiralOutChunks(BlockPos blockposition, int radius) { + List list = com.google.common.collect.Lists.newArrayList(); + + list.add(new ChunkPos(blockposition.getX() >> 4, blockposition.getZ() >> 4)); + for (int r = 1; r <= radius; r++) { + int x = -r; + int z = r; + + // Iterates the edge of half of the box; then negates for other half. + while (x <= r && z > -r) { + list.add(new ChunkPos((blockposition.getX() + (x << 4)) >> 4, (blockposition.getZ() + (z << 4)) >> 4)); + list.add(new ChunkPos((blockposition.getX() - (x << 4)) >> 4, (blockposition.getZ() - (z << 4)) >> 4)); + + if (x < r) { + x++; + } else { + z--; + } + } + } + return list; + } + + public static int fastFloor(double x) { + int truncated = (int)x; + return x < (double)truncated ? truncated - 1 : truncated; + } + + public static int fastFloor(float x) { + int truncated = (int)x; + return x < (double)truncated ? truncated - 1 : truncated; + } + + public static float normalizeYaw(float f) { + float f1 = f % 360.0F; + + if (f1 >= 180.0F) { + f1 -= 360.0F; + } + + if (f1 < -180.0F) { + f1 += 360.0F; + } + + return f1; + } + + /** + * Quickly generate a stack trace for current location + * + * @return Stacktrace + */ + public static String stack() { + return ExceptionUtils.getFullStackTrace(new Throwable()); + } + + /** + * Quickly generate a stack trace for current location with message + * + * @param str + * @return Stacktrace + */ + public static String stack(String str) { + return ExceptionUtils.getFullStackTrace(new Throwable(str)); + } + + public static long getCoordinateKey(final BlockPos blockPos) { + return ((long)(blockPos.getZ() >> 4) << 32) | ((blockPos.getX() >> 4) & 0xFFFFFFFFL); + } + + public static long getCoordinateKey(final Entity entity) { + return ((long)(MCUtil.fastFloor(entity.getZ()) >> 4) << 32) | ((MCUtil.fastFloor(entity.getX()) >> 4) & 0xFFFFFFFFL); + } + + public static long getCoordinateKey(final ChunkPos pair) { + return ((long)pair.z << 32) | (pair.x & 0xFFFFFFFFL); + } + + public static long getCoordinateKey(final int x, final int z) { + return ((long)z << 32) | (x & 0xFFFFFFFFL); + } + + public static int getCoordinateX(final long key) { + return (int)key; + } + + public static int getCoordinateZ(final long key) { + return (int)(key >>> 32); + } + + public static int getChunkCoordinate(final double coordinate) { + return MCUtil.fastFloor(coordinate) >> 4; + } + + public static int getBlockCoordinate(final double coordinate) { + return MCUtil.fastFloor(coordinate); + } + + public static long getBlockKey(final int x, final int y, final int z) { + return ((long)x & 0x7FFFFFF) | (((long)z & 0x7FFFFFF) << 27) | ((long)y << 54); + } + + public static long getBlockKey(final BlockPos pos) { + return ((long)pos.getX() & 0x7FFFFFF) | (((long)pos.getZ() & 0x7FFFFFF) << 27) | ((long)pos.getY() << 54); + } + + public static long getBlockKey(final Entity entity) { + return getBlockKey(getBlockCoordinate(entity.getX()), getBlockCoordinate(entity.getY()), getBlockCoordinate(entity.getZ())); + } + + // assumes the sets have the same comparator, and if this comparator is null then assume T is Comparable + public static void mergeSortedSets(final java.util.function.Consumer consumer, final java.util.Comparator comparator, final java.util.SortedSet...sets) { + final ObjectRBTreeSet all = new ObjectRBTreeSet<>(comparator); + // note: this is done in log(n!) ~ nlogn time. It could be improved if it were to mimic what mergesort does. + for (java.util.SortedSet set : sets) { + if (set != null) { + all.addAll(set); + } + } + all.forEach(consumer); + } + + private MCUtil() {} + + public static final java.util.concurrent.Executor MAIN_EXECUTOR = (run) -> { + if (!isMainThread()) { + MinecraftServer.getServer().execute(run); + } else { + run.run(); + } + }; + + public static CompletableFuture ensureMain(CompletableFuture future) { + return future.thenApplyAsync(r -> r, MAIN_EXECUTOR); + } + + public static void thenOnMain(CompletableFuture future, Consumer consumer) { + future.thenAcceptAsync(consumer, MAIN_EXECUTOR); + } + public static void thenOnMain(CompletableFuture future, BiConsumer consumer) { + future.whenCompleteAsync(consumer, MAIN_EXECUTOR); + } + + public static boolean isMainThread() { + return MinecraftServer.getServer().isSameThread(); + } + + public static org.bukkit.scheduler.BukkitTask scheduleTask(int ticks, Runnable runnable) { + return scheduleTask(ticks, runnable, null); + } + + public static org.bukkit.scheduler.BukkitTask scheduleTask(int ticks, Runnable runnable, String taskName) { + return MinecraftServer.getServer().server.getScheduler().scheduleInternalTask(runnable, ticks, taskName); + } + + public static void processQueue() { + Runnable runnable; + Queue processQueue = getProcessQueue(); + while ((runnable = processQueue.poll()) != null) { + try { + runnable.run(); + } catch (Exception e) { + MinecraftServer.LOGGER.error("Error executing task", e); + } + } + } + public static T processQueueWhileWaiting(CompletableFuture future) { + try { + if (isMainThread()) { + while (!future.isDone()) { + try { + return future.get(1, TimeUnit.MILLISECONDS); + } catch (TimeoutException ignored) { + processQueue(); + } + } + } + return future.get(); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + public static void ensureMain(Runnable run) { + ensureMain(null, run); + } + /** + * Ensures the target code is running on the main thread + * @param reason + * @param run + * @return + */ + public static void ensureMain(String reason, Runnable run) { + if (AsyncCatcher.enabled && Thread.currentThread() != MinecraftServer.getServer().serverThread) { + if (reason != null) { + new IllegalStateException("Asynchronous " + reason + "!").printStackTrace(); + } + getProcessQueue().add(run); + return; + } + run.run(); + } + + private static Queue getProcessQueue() { + return MinecraftServer.getServer().processQueue; + } + + public static T ensureMain(Supplier run) { + return ensureMain(null, run); + } + /** + * Ensures the target code is running on the main thread + * @param reason + * @param run + * @param + * @return + */ + public static T ensureMain(String reason, Supplier run) { + if (AsyncCatcher.enabled && Thread.currentThread() != MinecraftServer.getServer().serverThread) { + if (reason != null) { + new IllegalStateException("Asynchronous " + reason + "! Blocking thread until it returns ").printStackTrace(); + } + Waitable wait = new Waitable() { + @Override + protected T evaluate() { + return run.get(); + } + }; + getProcessQueue().add(wait); + try { + return wait.get(); + } catch (InterruptedException | ExecutionException e) { + e.printStackTrace(); + } + return null; + } + return run.get(); + } + + /** + * Calculates distance between 2 entities + * @param e1 + * @param e2 + * @return + */ + public static double distance(Entity e1, Entity e2) { + return Math.sqrt(distanceSq(e1, e2)); + } + + + /** + * Calculates distance between 2 block positions + * @param e1 + * @param e2 + * @return + */ + public static double distance(BlockPos e1, BlockPos e2) { + return Math.sqrt(distanceSq(e1, e2)); + } + + /** + * Gets the distance between 2 positions + * @param x1 + * @param y1 + * @param z1 + * @param x2 + * @param y2 + * @param z2 + * @return + */ + public static double distance(double x1, double y1, double z1, double x2, double y2, double z2) { + return Math.sqrt(distanceSq(x1, y1, z1, x2, y2, z2)); + } + + /** + * Get's the distance squared between 2 entities + * @param e1 + * @param e2 + * @return + */ + public static double distanceSq(Entity e1, Entity e2) { + return distanceSq(e1.getX(),e1.getY(),e1.getZ(), e2.getX(),e2.getY(),e2.getZ()); + } + + /** + * Gets the distance sqaured between 2 block positions + * @param pos1 + * @param pos2 + * @return + */ + public static double distanceSq(BlockPos pos1, BlockPos pos2) { + return distanceSq(pos1.getX(), pos1.getY(), pos1.getZ(), pos2.getX(), pos2.getY(), pos2.getZ()); + } + + /** + * Gets the distance squared between 2 positions + * @param x1 + * @param y1 + * @param z1 + * @param x2 + * @param y2 + * @param z2 + * @return + */ + public static double distanceSq(double x1, double y1, double z1, double x2, double y2, double z2) { + return (x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2) + (z1 - z2) * (z1 - z2); + } + + /** + * Converts a NMS World/BlockPosition to Bukkit Location + * @param world + * @param x + * @param y + * @param z + * @return + */ + public static Location toLocation(Level world, double x, double y, double z) { + return new Location(world.getWorld(), x, y, z); + } + + /** + * Converts a NMS World/BlockPosition to Bukkit Location + * @param world + * @param pos + * @return + */ + public static Location toLocation(Level world, BlockPos pos) { + return new Location(world.getWorld(), pos.getX(), pos.getY(), pos.getZ()); + } + + /** + * Converts an NMS entity's current location to a Bukkit Location + * @param entity + * @return + */ + public static Location toLocation(Entity entity) { + return new Location(entity.getCommandSenderWorld().getWorld(), entity.getX(), entity.getY(), entity.getZ()); + } + + public static org.bukkit.block.Block toBukkitBlock(Level world, BlockPos pos) { + return world.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ()); + } + + public static BlockPos toBlockPosition(Location loc) { + return new BlockPos(loc.getBlockX(), loc.getBlockY(), loc.getBlockZ()); + } + + public static boolean isEdgeOfChunk(BlockPos pos) { + final int modX = pos.getX() & 15; + final int modZ = pos.getZ() & 15; + return (modX == 0 || modX == 15 || modZ == 0 || modZ == 15); + } + + /** + * Posts a task to be executed asynchronously + * @param run + */ + public static void scheduleAsyncTask(Runnable run) { + asyncExecutor.execute(run); + } + + @Nonnull + public static ServerLevel getNMSWorld(@Nonnull org.bukkit.World world) { + return ((CraftWorld) world).getHandle(); + } + + public static ServerLevel getNMSWorld(@Nonnull org.bukkit.entity.Entity entity) { + return getNMSWorld(entity.getWorld()); + } + + public static ClipContext.Fluid getNMSFluidCollisionOption(TargetBlockInfo.FluidMode fluidMode) { + if (fluidMode == TargetBlockInfo.FluidMode.NEVER) { + return ClipContext.Fluid.NONE; + } + if (fluidMode == TargetBlockInfo.FluidMode.SOURCE_ONLY) { + return ClipContext.Fluid.SOURCE_ONLY; + } + if (fluidMode == TargetBlockInfo.FluidMode.ALWAYS) { + return ClipContext.Fluid.ANY; + } + return null; + } + + public static BlockFace toBukkitBlockFace(Direction enumDirection) { + switch (enumDirection) { + case DOWN: + return BlockFace.DOWN; + case UP: + return BlockFace.UP; + case NORTH: + return BlockFace.NORTH; + case SOUTH: + return BlockFace.SOUTH; + case WEST: + return BlockFace.WEST; + case EAST: + return BlockFace.EAST; + default: + return null; + } + } +} diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java index 37a51dee4cd37844e80fdd5c9853947201151dfc..2406879e76a110e96a4753e66366432a4bc52d9b 100644 --- a/src/main/java/net/minecraft/server/MinecraftServer.java +++ b/src/main/java/net/minecraft/server/MinecraftServer.java @@ -882,6 +882,9 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop CHUNK_STATUSES = ChunkStatus.getStatusList(); private static final ChunkHolder.FullChunkStatus[] FULL_CHUNK_STATUSES = ChunkHolder.FullChunkStatus.values(); private final AtomicReferenceArray>> futures; - private volatile CompletableFuture> fullChunkFuture; - private volatile CompletableFuture> tickingChunkFuture; - private volatile CompletableFuture> entityTickingChunkFuture; + private volatile CompletableFuture> fullChunkFuture; private int fullChunkCreateCount; private volatile boolean isFullChunkReady; // Paper - cache chunk ticking stage + private volatile CompletableFuture> tickingChunkFuture; private volatile boolean isTickingReady; // Paper - cache chunk ticking stage + private volatile CompletableFuture> entityTickingChunkFuture; private volatile boolean isEntityTickingReady; // Paper - cache chunk ticking stage private CompletableFuture chunkToSave; public int oldTicketLevel; private int ticketLevel; @@ -63,6 +63,8 @@ public class ChunkHolder { private boolean wasAccessibleSinceLastSave; private boolean resendLight; + private final ChunkMap chunkMap; // Paper + public ChunkHolder(ChunkPos pos, int level, LevelLightEngine lightingProvider, ChunkHolder.LevelChangeListener levelUpdateListener, ChunkHolder.PlayerProvider playersWatchingChunkProvider) { this.futures = new AtomicReferenceArray(ChunkHolder.CHUNK_STATUSES.size()); this.fullChunkFuture = ChunkHolder.UNLOADED_LEVEL_CHUNK_FUTURE; @@ -78,10 +80,49 @@ public class ChunkHolder { this.ticketLevel = this.oldTicketLevel; this.queueLevel = this.oldTicketLevel; this.setTicketLevel(level); + this.chunkMap = (ChunkMap)playersWatchingChunkProvider; // Paper + } + + // Paper start + @Nullable + public final LevelChunk getEntityTickingChunk() { + CompletableFuture> completablefuture = this.entityTickingChunkFuture; + Either either = completablefuture.getNow(null); + + return either == null ? null : either.left().orElse(null); + } + + @Nullable + public final LevelChunk getTickingChunk() { + CompletableFuture> completablefuture = this.tickingChunkFuture; + Either either = completablefuture.getNow(null); + + return either == null ? null : either.left().orElse(null); + } + + @Nullable + public final LevelChunk getFullReadyChunk() { + CompletableFuture> completablefuture = this.fullChunkFuture; + Either either = completablefuture.getNow(null); + + return either == null ? null : either.left().orElse(null); + } + + public final boolean isEntityTickingReady() { + return this.isEntityTickingReady; + } + + public final boolean isTickingReady() { + return this.isTickingReady; + } + + public final boolean isFullChunkReady() { + return this.isFullChunkReady; } + // Paper end // CraftBukkit start - public LevelChunk getFullChunk() { + public final LevelChunk getFullChunk() { // Paper - final for inline if (!getFullChunkStatus(this.oldTicketLevel).isOrAfter(ChunkHolder.FullChunkStatus.BORDER)) return null; // note: using oldTicketLevel for isLoaded checks return this.getFullChunkUnchecked(); } @@ -92,6 +133,14 @@ public class ChunkHolder { return (either == null) ? null : (LevelChunk) either.left().orElse(null); } // CraftBukkit end + // Paper start - "real" get full chunk immediately + public final LevelChunk getFullChunkIfCached() { + // Note: Copied from above without ticket level check + CompletableFuture> statusFuture = this.getFutureIfPresentUnchecked(ChunkStatus.FULL); + Either either = (Either) statusFuture.getNow(null); + return either == null ? null : (LevelChunk) either.left().orElse(null); + } + // Paper end public CompletableFuture> getFutureIfPresentUnchecked(ChunkStatus leastStatus) { CompletableFuture> completablefuture = (CompletableFuture) this.futures.get(leastStatus.getIndex()); @@ -103,20 +152,23 @@ public class ChunkHolder { return getStatus(this.ticketLevel).isOrAfter(leastStatus) ? this.getFutureIfPresentUnchecked(leastStatus) : ChunkHolder.UNLOADED_CHUNK_FUTURE; } - public CompletableFuture> getTickingChunkFuture() { + public final CompletableFuture> getTickingFuture() { return this.getTickingChunkFuture(); } // Paper - OBFHELPER + public final CompletableFuture> getTickingChunkFuture() { // Paper - final for inline return this.tickingChunkFuture; } - public CompletableFuture> getEntityTickingChunkFuture() { + public final CompletableFuture> getEntityTickingFuture() { return this.getEntityTickingChunkFuture(); } // Paper - OBFHELPER + public final CompletableFuture> getEntityTickingChunkFuture() { // Paper - final for inline return this.entityTickingChunkFuture; } - public CompletableFuture> getFullChunkFuture() { + public final CompletableFuture> getFullChunkFuture() { return this.getFullChunkFuture(); } // Paper - OBFHELPER + public final CompletableFuture> getFullChunkFuture() { // Paper - final for inline return this.fullChunkFuture; } @Nullable - public LevelChunk getTickingChunk() { + public final LevelChunk getTickingChunk() { // Paper - final for inline CompletableFuture> completablefuture = this.getTickingChunkFuture(); Either either = (Either) completablefuture.getNow(null); // CraftBukkit - decompile error @@ -141,7 +193,7 @@ public class ChunkHolder { return null; } - public CompletableFuture getChunkToSave() { + public final CompletableFuture getChunkToSave() { // Paper - final for inline return this.chunkToSave; } @@ -282,11 +334,11 @@ public class ChunkHolder { }); } - public ChunkPos getPos() { + public final ChunkPos getPos() { // Paper - final for inline return this.pos; } - public int getTicketLevel() { + public final int getTicketLevel() { // Paper - final for inline return this.ticketLevel; } @@ -357,13 +409,27 @@ public class ChunkHolder { this.wasAccessibleSinceLastSave |= flag3; if (!flag2 && flag3) { - this.fullChunkFuture = chunkStorage.unpackTicks(this); + // Paper start - cache ticking ready status + int expectCreateCount = ++this.fullChunkCreateCount; + this.fullChunkFuture = chunkStorage.unpackTicks(this); this.fullChunkFuture.thenAccept((either) -> { + if (either.left().isPresent() && ChunkHolder.this.fullChunkCreateCount == expectCreateCount) { + // note: Here is a very good place to add callbacks to logic waiting on this. + LevelChunk fullChunk = either.left().get(); + ChunkHolder.this.isFullChunkReady = true; + fullChunk.playerChunk = ChunkHolder.this; + + + } + }); + // Paper end this.updateChunkToSave(this.fullChunkFuture); } if (flag2 && !flag3) { completablefuture = this.fullChunkFuture; this.fullChunkFuture = ChunkHolder.UNLOADED_LEVEL_CHUNK_FUTURE; + ++this.fullChunkCreateCount; // Paper - cache ticking ready status + this.isFullChunkReady = false; // Paper - cache ticking ready status this.updateChunkToSave(((CompletableFuture>) completablefuture).thenApply((either1) -> { // CraftBukkit - decompile error chunkStorage.getClass(); return either1.ifLeft(chunkStorage::packTicks); @@ -374,12 +440,24 @@ public class ChunkHolder { boolean flag5 = playerchunk_state1.isOrAfter(ChunkHolder.FullChunkStatus.TICKING); if (!flag4 && flag5) { - this.tickingChunkFuture = chunkStorage.postProcess(this); + // Paper start - cache ticking ready status + this.tickingChunkFuture = chunkStorage.postProcess(this); this.tickingChunkFuture.thenAccept((either) -> { + if (either.left().isPresent()) { + // note: Here is a very good place to add callbacks to logic waiting on this. + LevelChunk tickingChunk = either.left().get(); + ChunkHolder.this.isTickingReady = true; + + + + + } + }); + // Paper end this.updateChunkToSave(this.tickingChunkFuture); } if (flag4 && !flag5) { - this.tickingChunkFuture.complete(ChunkHolder.UNLOADED_LEVEL_CHUNK); + this.tickingChunkFuture.complete(ChunkHolder.UNLOADED_LEVEL_CHUNK); this.isTickingReady = false; // Paper - cache chunk ticking stage this.tickingChunkFuture = ChunkHolder.UNLOADED_LEVEL_CHUNK_FUTURE; } @@ -391,12 +469,24 @@ public class ChunkHolder { throw (IllegalStateException) Util.pauseInIde((Throwable) (new IllegalStateException())); } - this.entityTickingChunkFuture = chunkStorage.getEntityTickingRangeFuture(this.pos); + // Paper start - cache ticking ready status + this.entityTickingChunkFuture = chunkStorage.getEntityTickingRangeFuture(this.pos); this.entityTickingChunkFuture.thenAccept((either) -> { + if (either.left().isPresent()) { + // note: Here is a very good place to add callbacks to logic waiting on this. + LevelChunk entityTickingChunk = either.left().get(); + ChunkHolder.this.isEntityTickingReady = true; + + + + + } + }); + // Paper end this.updateChunkToSave(this.entityTickingChunkFuture); } if (flag6 && !flag7) { - this.entityTickingChunkFuture.complete(ChunkHolder.UNLOADED_LEVEL_CHUNK); + this.entityTickingChunkFuture.complete(ChunkHolder.UNLOADED_LEVEL_CHUNK); this.isEntityTickingReady = false; // Paper - cache chunk ticking stage this.entityTickingChunkFuture = ChunkHolder.UNLOADED_LEVEL_CHUNK_FUTURE; } diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java index aaa56d121cdb66066e869b7cbdc2c863974d51b0..d9d76d2ee43adc2b46d49f7fc3d9aef6af95e465 100644 --- a/src/main/java/net/minecraft/server/level/ChunkMap.java +++ b/src/main/java/net/minecraft/server/level/ChunkMap.java @@ -54,6 +54,7 @@ import net.minecraft.network.protocol.game.ClientboundSetChunkCacheCenterPacket; import net.minecraft.network.protocol.game.ClientboundSetEntityLinkPacket; import net.minecraft.network.protocol.game.ClientboundSetPassengersPacket; import net.minecraft.network.protocol.game.DebugPackets; +import net.minecraft.server.MCUtil; import net.minecraft.server.level.progress.ChunkProgressListener; import net.minecraft.util.CsvOutput; import net.minecraft.util.Mth; @@ -144,6 +145,26 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider }; // CraftBukkit end + // Paper start - distance maps + private final com.destroystokyo.paper.util.misc.PooledLinkedHashSets pooledLinkedPlayerHashSets = new com.destroystokyo.paper.util.misc.PooledLinkedHashSets<>(); + + void addPlayerToDistanceMaps(ServerPlayer player) { + int chunkX = MCUtil.getChunkCoordinate(player.getX()); + int chunkZ = MCUtil.getChunkCoordinate(player.getZ()); + // Note: players need to be explicitly added to distance maps before they can be updated + } + + void removePlayerFromDistanceMaps(ServerPlayer player) { + + } + + void updateMaps(ServerPlayer player) { + int chunkX = MCUtil.getChunkCoordinate(player.getX()); + int chunkZ = MCUtil.getChunkCoordinate(player.getZ()); + // Note: players need to be explicitly added to distance maps before they can be updated + } + // Paper end + public ChunkMap(ServerLevel worldserver, LevelStorageSource.LevelStorageAccess convertable_conversionsession, DataFixer dataFixer, StructureManager definedstructuremanager, Executor workerExecutor, BlockableEventLoop mainThreadExecutor, LightChunkGetter chunkProvider, ChunkGenerator chunkGenerator, ChunkProgressListener worldGenerationProgressListener, Supplier supplier, int i, boolean flag) { super(new File(convertable_conversionsession.getDimensionPath(worldserver.dimension()), "region"), dataFixer, flag); this.visibleChunkMap = this.updatingChunkMap.clone(); @@ -233,6 +254,14 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider }; } + // Paper start + public final int getEffectiveViewDistance() { + // TODO this needs to be checked on update + // Mojang currently sets it to +1 of the configured view distance. So subtract one to get the one we really want. + return this.viewDistance - 1; + } + // Paper end + private CompletableFuture, ChunkHolder.ChunkLoadingFailure>> getChunkRangeFuture(ChunkPos centerChunk, int margin, IntFunction distanceToStatus) { List>> list = Lists.newArrayList(); int j = centerChunk.x; @@ -951,6 +980,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider if (!flag1) { this.distanceManager.addPlayer(SectionPos.of((Entity) player), player); } + this.addPlayerToDistanceMaps(player); // Paper - distance maps } else { SectionPos sectionposition = player.getLastSectionPos(); @@ -958,6 +988,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider if (!flag2) { this.distanceManager.removePlayer(sectionposition, player); } + this.removePlayerFromDistanceMaps(player); // Paper - distance maps } for (int k = i - this.viewDistance; k <= i + this.viewDistance; ++k) { @@ -1068,6 +1099,8 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider } } + this.updateMaps(player); // Paper - distance maps + } @Override diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java index 7cc5070f70a4f740add9d971385ceaa4d44275a2..ef336645f3bd7a86129ad1dff7e1a15dc93d1e3e 100644 --- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java +++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java @@ -42,6 +42,7 @@ import net.minecraft.world.level.levelgen.structure.templatesystem.StructureMana import net.minecraft.world.level.storage.DimensionDataStorage; import net.minecraft.world.level.storage.LevelData; import net.minecraft.world.level.storage.LevelStorageSource; +import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; // Paper public class ServerChunkCache extends ChunkSource { @@ -49,7 +50,7 @@ public class ServerChunkCache extends ChunkSource { private final DistanceManager distanceManager; public final ChunkGenerator generator; private final ServerLevel level; - private final Thread mainThread; + public final Thread mainThread; // Paper - private -> public private final ThreadedLevelLightEngine lightEngine; private final ServerChunkCache.MainThreadExecutor mainThreadProcessor; public final ChunkMap chunkMap; @@ -62,6 +63,158 @@ public class ServerChunkCache extends ChunkSource { private final ChunkAccess[] lastChunk = new ChunkAccess[4]; @Nullable private NaturalSpawner.SpawnState lastSpawnState; + // Paper start + final com.destroystokyo.paper.util.concurrent.WeakSeqLock loadedChunkMapSeqLock = new com.destroystokyo.paper.util.concurrent.WeakSeqLock(); + final Long2ObjectOpenHashMap loadedChunkMap = new Long2ObjectOpenHashMap<>(8192, 0.5f); + + private final LevelChunk[] lastLoadedChunks = new LevelChunk[4 * 4]; + + private static int getChunkCacheKey(int x, int z) { + return x & 3 | ((z & 3) << 2); + } + + public void addLoadedChunk(LevelChunk chunk) { + this.loadedChunkMapSeqLock.acquireWrite(); + try { + this.loadedChunkMap.put(chunk.coordinateKey, chunk); + } finally { + this.loadedChunkMapSeqLock.releaseWrite(); + } + + // rewrite cache if we have to + // we do this since we also cache null chunks + int cacheKey = getChunkCacheKey(chunk.locX, chunk.locZ); + + this.lastLoadedChunks[cacheKey] = chunk; + } + + public void removeLoadedChunk(LevelChunk chunk) { + this.loadedChunkMapSeqLock.acquireWrite(); + try { + this.loadedChunkMap.remove(chunk.coordinateKey); + } finally { + this.loadedChunkMapSeqLock.releaseWrite(); + } + + // rewrite cache if we have to + // we do this since we also cache null chunks + int cacheKey = getChunkCacheKey(chunk.locX, chunk.locZ); + + LevelChunk cachedChunk = this.lastLoadedChunks[cacheKey]; + if (cachedChunk != null && cachedChunk.coordinateKey == chunk.coordinateKey) { + this.lastLoadedChunks[cacheKey] = null; + } + } + + public final LevelChunk getChunkAtIfLoadedMainThread(int x, int z) { + int cacheKey = getChunkCacheKey(x, z); + + LevelChunk cachedChunk = this.lastLoadedChunks[cacheKey]; + if (cachedChunk != null && cachedChunk.locX == x & cachedChunk.locZ == z) { + return this.lastLoadedChunks[cacheKey]; + } + + long chunkKey = ChunkPos.asLong(x, z); + + cachedChunk = this.loadedChunkMap.get(chunkKey); + // Skipping a null check to avoid extra instructions to improve inline capability + this.lastLoadedChunks[cacheKey] = cachedChunk; + return cachedChunk; + } + + public final LevelChunk getChunkAtIfLoadedMainThreadNoCache(int x, int z) { + return this.loadedChunkMap.get(ChunkPos.asLong(x, z)); + } + + public final LevelChunk getChunkAtMainThread(int x, int z) { + LevelChunk ret = this.getChunkAtIfLoadedMainThread(x, z); + if (ret != null) { + return ret; + } + return (LevelChunk)this.getChunk(x, z, ChunkStatus.FULL, true); + } + + private long chunkFutureAwaitCounter; + + public void getEntityTickingChunkAsync(int x, int z, java.util.function.Consumer onLoad) { + if (Thread.currentThread() != this.mainThread) { + this.mainThreadProcessor.execute(() -> { + ServerChunkCache.this.getEntityTickingChunkAsync(x, z, onLoad); + }); + return; + } + this.getChunkFutureAsynchronously(x, z, 31, ChunkHolder::getEntityTickingFuture, onLoad); + } + + public void getTickingChunkAsync(int x, int z, java.util.function.Consumer onLoad) { + if (Thread.currentThread() != this.mainThread) { + this.mainThreadProcessor.execute(() -> { + ServerChunkCache.this.getTickingChunkAsync(x, z, onLoad); + }); + return; + } + this.getChunkFutureAsynchronously(x, z, 32, ChunkHolder::getTickingFuture, onLoad); + } + + public void getFullChunkAsync(int x, int z, java.util.function.Consumer onLoad) { + if (Thread.currentThread() != this.mainThread) { + this.mainThreadProcessor.execute(() -> { + ServerChunkCache.this.getFullChunkAsync(x, z, onLoad); + }); + return; + } + this.getChunkFutureAsynchronously(x, z, 33, ChunkHolder::getFullChunkFuture, onLoad); + } + + private void getChunkFutureAsynchronously(int x, int z, int ticketLevel, Function>> futureGet, java.util.function.Consumer onLoad) { + if (Thread.currentThread() != this.mainThread) { + throw new IllegalStateException(); + } + ChunkPos chunkPos = new ChunkPos(x, z); + Long identifier = this.chunkFutureAwaitCounter++; + this.distanceManager.addTicketAtLevel(TicketType.FUTURE_AWAIT, chunkPos, ticketLevel, identifier); + this.runDistanceManagerUpdates(); + + ChunkHolder chunk = this.chunkMap.getUpdatingChunkIfPresent(chunkPos.toLong()); + + if (chunk == null) { + throw new IllegalStateException("Expected playerchunk " + chunkPos + " in world '" + this.level.getWorld().getName() + "'"); + } + + CompletableFuture> future = futureGet.apply(chunk); + + future.whenCompleteAsync((either, throwable) -> { + try { + if (throwable != null) { + if (throwable instanceof ThreadDeath) { + throw (ThreadDeath)throwable; + } + net.minecraft.server.MinecraftServer.LOGGER.fatal("Failed to complete future await for chunk " + chunkPos.toString() + " in world '" + ServerChunkCache.this.level.getWorld().getName() + "'", throwable); + } else if (either.right().isPresent()) { + net.minecraft.server.MinecraftServer.LOGGER.fatal("Failed to complete future await for chunk " + chunkPos.toString() + " in world '" + ServerChunkCache.this.level.getWorld().getName() + "': " + either.right().get().toString()); + } + + try { + if (onLoad != null) { + chunkMap.callbackExecutor.execute(() -> { + onLoad.accept(either == null ? null : either.left().orElse(null)); // indicate failure to the callback. + }); + } + } catch (Throwable thr) { + if (thr instanceof ThreadDeath) { + throw (ThreadDeath)thr; + } + net.minecraft.server.MinecraftServer.LOGGER.fatal("Load callback for future await failed " + chunkPos.toString() + " in world '" + ServerChunkCache.this.level.getWorld().getName() + "'", thr); + return; + } + } finally { + // due to odd behaviour with CB unload implementation we need to have these AFTER the load callback. + ServerChunkCache.this.distanceManager.addTicketAtLevel(TicketType.UNKNOWN, chunkPos, ticketLevel, chunkPos); + ServerChunkCache.this.distanceManager.removeTicketAtLevel(TicketType.FUTURE_AWAIT, chunkPos, ticketLevel, identifier); + } + }, this.mainThreadProcessor); + } + // Paper end public ServerChunkCache(ServerLevel worldserver, LevelStorageSource.LevelStorageAccess convertable_conversionsession, DataFixer dataFixer, StructureManager structureManager, Executor workerExecutor, ChunkGenerator chunkGenerator, int viewDistance, boolean flag, ChunkProgressListener worldloadlistener, Supplier supplier) { this.level = worldserver; @@ -123,6 +276,49 @@ public class ServerChunkCache extends ChunkSource { this.lastChunk[0] = chunk; } + // Paper start - "real" get chunk if loaded + // Note: Partially copied from the getChunkAt method below + @Nullable + public LevelChunk getChunkAtIfCachedImmediately(int x, int z) { + long k = ChunkPos.asLong(x, z); + + // Note: Bypass cache since we need to check ticket level, and to make this MT-Safe + + ChunkHolder playerChunk = this.getVisibleChunkIfPresent(k); + if (playerChunk == null) { + return null; + } + + return playerChunk.getFullChunkIfCached(); + } + + @Nullable + public LevelChunk getChunkAtIfLoadedImmediately(int x, int z) { + long k = ChunkPos.asLong(x, z); + + if (Thread.currentThread() == this.mainThread) { + return this.getChunkAtIfLoadedMainThread(x, z); + } + + LevelChunk ret = null; + long readlock; + do { + readlock = this.loadedChunkMapSeqLock.acquireRead(); + try { + ret = this.loadedChunkMap.get(k); + } catch (Throwable thr) { + if (thr instanceof ThreadDeath) { + throw (ThreadDeath)thr; + } + // re-try, this means a CME occurred... + continue; + } + } while (!this.loadedChunkMapSeqLock.tryReleaseRead(readlock)); + + return ret; + } + // Paper end + @Nullable @Override public ChunkAccess getChunk(int x, int z, ChunkStatus leastStatus, boolean create) { @@ -406,10 +602,9 @@ public class ServerChunkCache extends ChunkSource { this.lastSpawnState = spawnercreature_d; this.level.getProfiler().pop(); - List list = Lists.newArrayList(this.chunkMap.getChunks()); - - Collections.shuffle(list); - list.forEach((playerchunk) -> { + //List list = Lists.newArrayList(this.playerChunkMap.f()); // Paper + //Collections.shuffle(list); // Paper + this.chunkMap.getChunks().forEach((playerchunk) -> { // Paper - no... just no... Optional optional = ((Either) playerchunk.getTickingChunkFuture().getNow(ChunkHolder.UNLOADED_LEVEL_CHUNK)).left(); if (optional.isPresent()) { diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java index 23506a8903ce64fbfe849bb94e589bdbb6e61a74..34ed8f0d348e7bc2339660ebc6490057ba9ef214 100644 --- a/src/main/java/net/minecraft/server/level/ServerLevel.java +++ b/src/main/java/net/minecraft/server/level/ServerLevel.java @@ -12,6 +12,7 @@ import it.unimi.dsi.fastutil.ints.Int2ObjectMap.Entry; import it.unimi.dsi.fastutil.longs.LongSet; import it.unimi.dsi.fastutil.longs.LongSets; import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; +import it.unimi.dsi.fastutil.objects.Object2IntMap; import it.unimi.dsi.fastutil.objects.ObjectIterator; import it.unimi.dsi.fastutil.objects.ObjectLinkedOpenHashSet; import java.io.BufferedWriter; @@ -163,7 +164,7 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl private final Map entitiesByUuid = Maps.newHashMap(); private final Queue toAddAfterTick = Queues.newArrayDeque(); private final List players = Lists.newArrayList(); - private final ServerChunkCache chunkSource; + public final ServerChunkCache chunkSource; // Paper - public boolean tickingEntities; private final MinecraftServer server; public final PrimaryLevelData worldDataServer; // CraftBukkit - type @@ -1682,7 +1683,7 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl ObjectIterator objectiterator = spawnercreature_d.getMobCategoryCounts().object2IntEntrySet().iterator(); while (objectiterator.hasNext()) { - it.unimi.dsi.fastutil.objects.Object2IntMap.Entry it_unimi_dsi_fastutil_objects_object2intmap_entry = (it.unimi.dsi.fastutil.objects.Object2IntMap.Entry) objectiterator.next(); + Object2IntMap.Entry it_unimi_dsi_fastutil_objects_object2intmap_entry = (Object2IntMap.Entry) objectiterator.next(); // Paper - decompile fix bufferedwriter.write(String.format("spawn_count.%s: %d\n", ((MobCategory) it_unimi_dsi_fastutil_objects_object2intmap_entry.getKey()).getName(), it_unimi_dsi_fastutil_objects_object2intmap_entry.getIntValue())); } diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java index d62c74318ac3bb139570fe2b5f12cdb6e900d70d..8d7fa186b0b47688f2822038b46c33b3f32d28ae 100644 --- a/src/main/java/net/minecraft/server/level/ServerPlayer.java +++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java @@ -221,6 +221,8 @@ public class ServerPlayer extends Player implements ContainerListener { public Integer clientViewDistance; // CraftBukkit end + public final com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet cachedSingleHashSet; // Paper + public ServerPlayer(MinecraftServer server, ServerLevel world, GameProfile profile, ServerPlayerGameMode interactionManager) { super(world, world.getSharedSpawnPos(), world.getSharedSpawnAngle(), profile); this.respawnDimension = Level.OVERWORLD; @@ -233,6 +235,8 @@ public class ServerPlayer extends Player implements ContainerListener { this.fudgeSpawnLocation(world); this.textFilter = server.createTextFilterForPlayer(this); + this.cachedSingleHashSet = new com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<>(this); // Paper + // CraftBukkit start this.displayName = this.getScoreboardName(); this.canPickUpLoot = true; diff --git a/src/main/java/net/minecraft/server/level/TicketType.java b/src/main/java/net/minecraft/server/level/TicketType.java index 34dd10908101a6709e389bfa1d1c2a6599e8b102..cf3ced15c9a87e7a4dbccba17c57a7b32b77566c 100644 --- a/src/main/java/net/minecraft/server/level/TicketType.java +++ b/src/main/java/net/minecraft/server/level/TicketType.java @@ -25,6 +25,7 @@ public class TicketType { public static final TicketType UNKNOWN = create("unknown", Comparator.comparingLong(ChunkPos::toLong), 1); public static final TicketType PLUGIN = create("plugin", (a, b) -> 0); // CraftBukkit public static final TicketType PLUGIN_TICKET = create("plugin_ticket", (plugin1, plugin2) -> plugin1.getClass().getName().compareTo(plugin2.getClass().getName())); // CraftBukkit + public static final TicketType FUTURE_AWAIT = create("future_await", Long::compareTo); // Paper public static TicketType create(String name, Comparator comparator) { return new TicketType<>(name, comparator, 0L); diff --git a/src/main/java/net/minecraft/server/level/WorldGenRegion.java b/src/main/java/net/minecraft/server/level/WorldGenRegion.java index 0a9f76b7aef5c12879a408954384b4e70ac5b484..fdc56d602ef0bf3c50842f3081a3e6523b9765b0 100644 --- a/src/main/java/net/minecraft/server/level/WorldGenRegion.java +++ b/src/main/java/net/minecraft/server/level/WorldGenRegion.java @@ -141,6 +141,26 @@ public class WorldGenRegion implements WorldGenLevel { return chunkX >= this.firstPos.x && chunkX <= this.lastPos.x && chunkZ >= this.firstPos.z && chunkZ <= this.lastPos.z; } + // Paper start - if loaded util + @Nullable + @Override + public ChunkAccess getChunkIfLoadedImmediately(int x, int z) { + return this.getChunk(x, z, ChunkStatus.FULL, false); + } + + @Override + public BlockState getTypeIfLoaded(BlockPos blockposition) { + ChunkAccess chunk = this.getChunkIfLoadedImmediately(blockposition.getX() >> 4, blockposition.getZ() >> 4); + return chunk == null ? null : chunk.getBlockState(blockposition); + } + + @Override + public FluidState getFluidIfLoaded(BlockPos blockposition) { + ChunkAccess chunk = this.getChunkIfLoadedImmediately(blockposition.getX() >> 4, blockposition.getZ() >> 4); + return chunk == null ? null : chunk.getFluidState(blockposition); + } + // Paper end + @Override public BlockState getBlockState(BlockPos pos) { return this.getChunk(pos.getX() >> 4, pos.getZ() >> 4).getBlockState(pos); diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java index 8766e79c1ed7a8557ef5fd91e8f3c65c3467efcd..35f3940cebb00ee29da54b1ee148ee931fa11636 100644 --- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java @@ -218,9 +218,9 @@ public class ServerGamePacketListenerImpl implements ServerGamePacketListener { private final MinecraftServer server; public ServerPlayer player; private int tickCount; - private long keepAliveTime; - private boolean keepAlivePending; - private long keepAliveChallenge; + private long keepAliveTime; private void setLastPing(long lastPing) { this.keepAliveTime = lastPing;}; private long getLastPing() { return this.keepAliveTime;}; // Paper - OBFHELPER + private boolean keepAlivePending; private void setPendingPing(boolean isPending) { this.keepAlivePending = isPending;}; private boolean isPendingPing() { return this.keepAlivePending;}; // Paper - OBFHELPER + private long keepAliveChallenge; private void setKeepAliveID(long keepAliveID) { this.keepAliveChallenge = keepAliveID;}; private long getKeepAliveID() {return this.keepAliveChallenge; }; // Paper - OBFHELPER // CraftBukkit start - multithreaded fields private volatile int chatSpamTickCount; private static final AtomicIntegerFieldUpdater chatSpamField = AtomicIntegerFieldUpdater.newUpdater(ServerGamePacketListenerImpl.class, "chatThrottle"); diff --git a/src/main/java/net/minecraft/util/BitStorage.java b/src/main/java/net/minecraft/util/BitStorage.java index 497e23e4ebf0d8b4e1a90ea3909608cdc066265f..97bde5f8402452e59b0da94edfe1b970cdb86748 100644 --- a/src/main/java/net/minecraft/util/BitStorage.java +++ b/src/main/java/net/minecraft/util/BitStorage.java @@ -84,6 +84,7 @@ public class BitStorage { return (int) (k >> l & this.mask); } + public final long[] getDataBits() { return this.getRaw(); } // Paper - OBFHELPER public long[] getRaw() { return this.data; } diff --git a/src/main/java/net/minecraft/util/thread/BlockableEventLoop.java b/src/main/java/net/minecraft/util/thread/BlockableEventLoop.java index 03831adce7905916423d8c3834c42c90f3a1ca8f..e48fcfe2e4ff151258ae1d84cc0995d2cd54e9a6 100644 --- a/src/main/java/net/minecraft/util/thread/BlockableEventLoop.java +++ b/src/main/java/net/minecraft/util/thread/BlockableEventLoop.java @@ -68,6 +68,15 @@ public abstract class BlockableEventLoop implements Processo } + // Paper start + public void scheduleOnMain(Runnable r0) { + // postToMainThread does not work the same as older versions of mc + // This method is actually used to create a TickTask, which can then be posted onto main + this.addTask(this.wrapRunnable(r0)); + } + // Paper end + + public final void addTask(R r0) { tell(r0); }; // Paper - OBFHELPER public void tell(R r0) { this.pendingRunnables.add(r0); LockSupport.unpark(this.getRunningThread()); diff --git a/src/main/java/net/minecraft/world/entity/EntityType.java b/src/main/java/net/minecraft/world/entity/EntityType.java index ff482d0349c18d0d1ba902ea0d10611b1ca4e588..102298d57cf3143092d04ab1d5d0d69b28d696ea 100644 --- a/src/main/java/net/minecraft/world/entity/EntityType.java +++ b/src/main/java/net/minecraft/world/entity/EntityType.java @@ -3,6 +3,7 @@ package net.minecraft.world.entity; import com.google.common.collect.ImmutableSet; import java.util.Optional; import java.util.Set; // Paper +import java.util.Map; // Paper import java.util.UUID; import java.util.function.Function; import java.util.stream.Stream; @@ -441,8 +442,8 @@ public class EntityType { return this.dimensions.height; } - @Nullable - public T create(Level world) { + public T create(Level world) { return this.create(world); } // Paper - OBFHELPER + @Nullable public T create(Level world) { // Paper - OBFHELPER return this.factory.create(this, world); } diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java index 3245c0d7310285dd423c5c1d44c26fb8c3d7bf7f..9e5dde73bd65bf4e51352c628fba024c36e29ef1 100644 --- a/src/main/java/net/minecraft/world/entity/LivingEntity.java +++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java @@ -228,6 +228,7 @@ public abstract class LivingEntity extends Entity { public boolean collides = true; public Set collidableExemptions = new HashSet<>(); public boolean canPickUpLoot; + public org.bukkit.craftbukkit.entity.CraftLivingEntity getBukkitLivingEntity() { return (org.bukkit.craftbukkit.entity.CraftLivingEntity) super.getBukkitEntity(); } // Paper @Override public float getBukkitYaw() { diff --git a/src/main/java/net/minecraft/world/entity/Mob.java b/src/main/java/net/minecraft/world/entity/Mob.java index f268b7e6f89068267ea2f07f7505e0dc69968b73..99cb4dc1a1009d4a29e651c94d21babcc61388ed 100644 --- a/src/main/java/net/minecraft/world/entity/Mob.java +++ b/src/main/java/net/minecraft/world/entity/Mob.java @@ -223,6 +223,7 @@ public abstract class Mob extends LivingEntity { return this.target; } + public org.bukkit.craftbukkit.entity.CraftMob getBukkitMob() { return (org.bukkit.craftbukkit.entity.CraftMob) super.getBukkitEntity(); } // Paper public void setTarget(@Nullable LivingEntity target) { // CraftBukkit start - fire event setGoalTarget(target, EntityTargetEvent.TargetReason.UNKNOWN, true); diff --git a/src/main/java/net/minecraft/world/entity/PathfinderMob.java b/src/main/java/net/minecraft/world/entity/PathfinderMob.java index d090ffcf08e32a08d4b815b79ed58fc00bc26fd0..920ae9af8985705a0ada7da5b7085a1ed8ca7f27 100644 --- a/src/main/java/net/minecraft/world/entity/PathfinderMob.java +++ b/src/main/java/net/minecraft/world/entity/PathfinderMob.java @@ -12,6 +12,8 @@ import org.bukkit.event.entity.EntityUnleashEvent; public abstract class PathfinderMob extends Mob { + public org.bukkit.craftbukkit.entity.CraftCreature getBukkitCreature() { return (org.bukkit.craftbukkit.entity.CraftCreature) super.getBukkitEntity(); } // Paper + protected PathfinderMob(EntityType type, Level world) { super(type, world); } diff --git a/src/main/java/net/minecraft/world/entity/monster/Monster.java b/src/main/java/net/minecraft/world/entity/monster/Monster.java index fa886a89195312acfe53605169216ce95642ba87..407b7168b7e8d4408824039c06d02792d3c7e534 100644 --- a/src/main/java/net/minecraft/world/entity/monster/Monster.java +++ b/src/main/java/net/minecraft/world/entity/monster/Monster.java @@ -27,6 +27,7 @@ import net.minecraft.world.level.ServerLevelAccessor; public abstract class Monster extends PathfinderMob implements Enemy { + public org.bukkit.craftbukkit.entity.CraftMonster getBukkitMonster() { return (org.bukkit.craftbukkit.entity.CraftMonster) super.getBukkitEntity(); } // Paper protected Monster(EntityType type, Level world) { super(type, world); this.xpReward = 5; diff --git a/src/main/java/net/minecraft/world/entity/player/Inventory.java b/src/main/java/net/minecraft/world/entity/player/Inventory.java index c0e0a8deabf3dffcc974f9e5bea2332d0d6e57a5..7d06838c4c8eef7ebff64ae094cd12404b9edd53 100644 --- a/src/main/java/net/minecraft/world/entity/player/Inventory.java +++ b/src/main/java/net/minecraft/world/entity/player/Inventory.java @@ -37,7 +37,7 @@ public class Inventory implements Container, Nameable { public final NonNullList items; public final NonNullList armor; public final NonNullList offhand; - private final List> compartments; + private final List> compartments; public final List> getComponents() { return compartments; } // Paper - OBFHELPER public int selected; public final Player player; private ItemStack carried; diff --git a/src/main/java/net/minecraft/world/item/ItemStack.java b/src/main/java/net/minecraft/world/item/ItemStack.java index da0fabafad0aa6c124abf52f8da68c73b7264fe9..2a6a6e291efbd7cc8fed6532f18321bd141e1306 100644 --- a/src/main/java/net/minecraft/world/item/ItemStack.java +++ b/src/main/java/net/minecraft/world/item/ItemStack.java @@ -101,7 +101,7 @@ public final class ItemStack { })).apply(instance, ItemStack::new); }); private static final Logger LOGGER = LogManager.getLogger(); - public static final ItemStack EMPTY = new ItemStack((Item) null); + public static final ItemStack EMPTY = new ItemStack((Item) null);public static final ItemStack NULL_ITEM = EMPTY; // Paper - OBFHELPER public static final DecimalFormat ATTRIBUTE_MODIFIER_FORMAT = (DecimalFormat) Util.make((new DecimalFormat("#.##")), (decimalformat) -> { // CraftBukkit - decompile error decimalformat.setDecimalFormatSymbols(DecimalFormatSymbols.getInstance(Locale.ROOT)); }); @@ -658,6 +658,24 @@ public final class ItemStack { return this.tag != null ? this.tag.getList("Enchantments", 10) : new ListTag(); } + // Paper start - (this is just a good no conflict location) + public org.bukkit.inventory.ItemStack asBukkitMirror() { + return CraftItemStack.asCraftMirror(this); + } + public org.bukkit.inventory.ItemStack asBukkitCopy() { + return CraftItemStack.asCraftMirror(this.copy()); + } + public static ItemStack fromBukkitCopy(org.bukkit.inventory.ItemStack itemstack) { + return CraftItemStack.asNMSCopy(itemstack); + } + private org.bukkit.craftbukkit.inventory.CraftItemStack bukkitStack; + public org.bukkit.inventory.ItemStack getBukkitStack() { + if (bukkitStack == null || bukkitStack.getHandle() != this) { + bukkitStack = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(this); + } + return bukkitStack; + } + // Paper end public void setTag(@Nullable CompoundTag tag) { this.tag = tag; if (this.getItem().canBeDepleted()) { @@ -756,6 +774,7 @@ public final class ItemStack { return this.tag != null && this.tag.contains("Enchantments", 9) ? !this.tag.getList("Enchantments", 10).isEmpty() : false; } + public void getOrCreateTagAndSet(String s, Tag nbtbase) { addTagElement(s, nbtbase);} // Paper - OBFHELPER public void addTagElement(String key, Tag tag) { this.getOrCreateTag().put(key, tag); } @@ -841,6 +860,7 @@ public final class ItemStack { // CraftBukkit start @Deprecated public void setItem(Item item) { + this.bukkitStack = null; // Paper this.item = item; } // CraftBukkit end diff --git a/src/main/java/net/minecraft/world/item/alchemy/PotionUtils.java b/src/main/java/net/minecraft/world/item/alchemy/PotionUtils.java index e38c38c7d7679f267283a5ac6245e2261829aa05..2285a0cf6a35d06de77f94ad8a14ae91c1a7929f 100644 --- a/src/main/java/net/minecraft/world/item/alchemy/PotionUtils.java +++ b/src/main/java/net/minecraft/world/item/alchemy/PotionUtils.java @@ -121,6 +121,7 @@ public class PotionUtils { return compound == null ? Potions.EMPTY : Potion.byName(compound.getString("Potion")); } + public static ItemStack addPotionToItemStack(ItemStack itemstack, Potion potionregistry) { return setPotion(itemstack, potionregistry); } // Paper - OBFHELPER public static ItemStack setPotion(ItemStack stack, Potion potion) { ResourceLocation minecraftkey = Registry.POTION.getKey(potion); diff --git a/src/main/java/net/minecraft/world/level/BlockGetter.java b/src/main/java/net/minecraft/world/level/BlockGetter.java index 3bbee667743d9a249c1513ea426e7a51d9185c4c..2feb187f62be5cf5d354a1e806087417cc189ab1 100644 --- a/src/main/java/net/minecraft/world/level/BlockGetter.java +++ b/src/main/java/net/minecraft/world/level/BlockGetter.java @@ -8,9 +8,11 @@ import javax.annotation.Nullable; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; import net.minecraft.util.Mth; +import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.material.FluidState; +import net.minecraft.world.level.material.Material; import net.minecraft.world.phys.AABB; import net.minecraft.world.phys.BlockHitResult; import net.minecraft.world.phys.Vec3; @@ -22,6 +24,19 @@ public interface BlockGetter { BlockEntity getBlockEntity(BlockPos pos); BlockState getBlockState(BlockPos pos); + // Paper start - if loaded util + BlockState getTypeIfLoaded(BlockPos blockposition); + default Material getMaterialIfLoaded(BlockPos blockposition) { + BlockState type = this.getTypeIfLoaded(blockposition); + return type == null ? null : type.getMaterial(); + } + + default Block getBlockIfLoaded(BlockPos blockposition) { + BlockState type = this.getTypeIfLoaded(blockposition); + return type == null ? null : type.getBlock(); + } + FluidState getFluidIfLoaded(BlockPos blockposition); + // Paper end FluidState getFluidState(BlockPos pos); diff --git a/src/main/java/net/minecraft/world/level/ChunkPos.java b/src/main/java/net/minecraft/world/level/ChunkPos.java index 0caf067f9d888f9769db1503284d444e97c60c9c..7ccf830146c252cff8e22553d293e02d4b53dad8 100644 --- a/src/main/java/net/minecraft/world/level/ChunkPos.java +++ b/src/main/java/net/minecraft/world/level/ChunkPos.java @@ -12,27 +12,32 @@ public class ChunkPos { public static final long INVALID_CHUNK_POS = asLong(1875016, 1875016); public final int x; public final int z; + public final long longKey; // Paper public ChunkPos(int x, int z) { this.x = x; this.z = z; + this.longKey = asLong(this.x, this.z); // Paper } public ChunkPos(BlockPos pos) { this.x = pos.getX() >> 4; this.z = pos.getZ() >> 4; + this.longKey = asLong(this.x, this.z); // Paper } public ChunkPos(long pos) { this.x = (int) pos; this.z = (int) (pos >> 32); + this.longKey = asLong(this.x, this.z); // Paper } public long toLong() { - return asLong(this.x, this.z); + return longKey; // Paper } - public static long asLong(int chunkX, int chunkZ) { + public static long pair(final BlockPos pos) { return asLong(pos.getX() >> 4, pos.getZ() >> 4); } // Paper - OBFHELPER + public static long asLong(int chunkX, int chunkZ) { return (long) chunkX & 4294967295L | ((long) chunkZ & 4294967295L) << 32; } diff --git a/src/main/java/net/minecraft/world/level/EmptyBlockGetter.java b/src/main/java/net/minecraft/world/level/EmptyBlockGetter.java index 82c14e1ac66792137ab9e647c868a698c1ec9929..2b4f9849d668dede4d1f7d10f3a6ec9ef7a6d828 100644 --- a/src/main/java/net/minecraft/world/level/EmptyBlockGetter.java +++ b/src/main/java/net/minecraft/world/level/EmptyBlockGetter.java @@ -20,6 +20,18 @@ public enum EmptyBlockGetter implements BlockGetter { return null; } + // Paper start - If loaded util + @Override + public FluidState getFluidIfLoaded(BlockPos blockposition) { + return this.getFluidState(blockposition); + } + + @Override + public BlockState getTypeIfLoaded(BlockPos blockposition) { + return this.getBlockState(blockposition); + } + // Paper end + @Override public BlockState getBlockState(BlockPos pos) { return Blocks.AIR.defaultBlockState(); diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java index f08de81dcc4acd5a3e44407b431ce827a19b2e9c..5cc4c2668df72f83fb1526f4586b71d2ae0103dc 100644 --- a/src/main/java/net/minecraft/world/level/Level.java +++ b/src/main/java/net/minecraft/world/level/Level.java @@ -74,6 +74,7 @@ import org.bukkit.craftbukkit.SpigotTimings; // Spigot import org.bukkit.craftbukkit.CraftServer; import org.bukkit.craftbukkit.CraftWorld; import org.bukkit.craftbukkit.block.CapturedBlockState; +import org.bukkit.craftbukkit.block.CraftBlockState; import org.bukkit.craftbukkit.block.data.CraftBlockData; import org.bukkit.event.block.BlockPhysicsEvent; // CraftBukkit end @@ -255,17 +256,50 @@ public abstract class Level implements LevelAccessor, AutoCloseable { return y < 0 || y >= 256; } - public LevelChunk getChunkAt(BlockPos pos) { + public final LevelChunk getChunkAt(BlockPos pos) { // Paper - help inline return this.getChunk(pos.getX() >> 4, pos.getZ() >> 4); } @Override - public LevelChunk getChunk(int chunkX, int chunkZ) { - return (LevelChunk) this.getChunk(chunkX, chunkZ, ChunkStatus.FULL); + public final LevelChunk getChunk(int chunkX, int chunkZ) { // Paper - final to help inline + return (LevelChunk) this.getChunk(chunkX, chunkZ, ChunkStatus.FULL, true); // Paper - avoid a method jump + } + + // Paper start - if loaded + @Nullable + @Override + public final ChunkAccess getChunkIfLoadedImmediately(int x, int z) { + return ((ServerLevel)this).chunkSource.getChunkAtIfLoadedImmediately(x, z); } @Override - public ChunkAccess getChunk(int chunkX, int chunkZ, ChunkStatus leastStatus, boolean create) { + public final BlockState getTypeIfLoaded(BlockPos blockposition) { + // CraftBukkit start - tree generation + if (captureTreeGeneration) { + CraftBlockState previous = capturedBlockStates.get(blockposition); + if (previous != null) { + return previous.getHandle(); + } + } + // CraftBukkit end + if (!isInWorldBounds(blockposition)) { + return Blocks.AIR.defaultBlockState(); + } + ChunkAccess chunk = this.getChunkIfLoadedImmediately(blockposition.getX() >> 4, blockposition.getZ() >> 4); + + return chunk == null ? null : chunk.getBlockState(blockposition); + } + + @Override + public FluidState getFluidIfLoaded(BlockPos blockposition) { + ChunkAccess chunk = this.getChunkIfLoadedImmediately(blockposition.getX() >> 4, blockposition.getZ() >> 4); + + return chunk == null ? null : chunk.getFluidState(blockposition); + } + // Paper end + + @Override + public final ChunkAccess getChunk(int chunkX, int chunkZ, ChunkStatus leastStatus, boolean create) { // Paper - final for inline ChunkAccess ichunkaccess = this.getChunkSource().getChunk(chunkX, chunkZ, leastStatus, create); if (ichunkaccess == null && create) { @@ -276,7 +310,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { } @Override - public boolean setBlock(BlockPos pos, BlockState state, int flags) { + public final boolean setBlock(BlockPos pos, BlockState state, int flags) { // Paper - final for inline return this.setBlock(pos, state, flags, 512); } @@ -422,8 +456,9 @@ public abstract class Level implements LevelAccessor, AutoCloseable { public void onBlockStateChange(BlockPos pos, BlockState oldBlock, BlockState newBlock) {} - @Override - public boolean removeBlock(BlockPos pos, boolean move) { + public boolean setAir(BlockPos blockposition) { return this.removeBlock(blockposition, false); } // Paper - OBFHELPER + public boolean setAir(BlockPos blockposition, boolean moved) { return this.removeBlock(blockposition, moved); } // Paper - OBFHELPER + @Override public boolean removeBlock(BlockPos pos, boolean move) { // Paper - OBFHELPER FluidState fluid = this.getFluidState(pos); return this.setBlock(pos, fluid.createLegacyBlock(), 3 | (move ? 64 : 0)); @@ -569,7 +604,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { if (isOutsideBuildHeight(pos)) { return Blocks.VOID_AIR.defaultBlockState(); } else { - LevelChunk chunk = this.getChunk(pos.getX() >> 4, pos.getZ() >> 4); + LevelChunk chunk = (LevelChunk) this.getChunkSource().getChunk(pos.getX() >> 4, pos.getZ() >> 4, ChunkStatus.FULL, true); // Paper - manually inline to reduce hops and avoid unnecessary null check to reduce total byte code size, this should never return null and if it does we will see it the next line but the real stack trace will matter in the chunk engine return chunk.getBlockState(pos); } diff --git a/src/main/java/net/minecraft/world/level/LevelReader.java b/src/main/java/net/minecraft/world/level/LevelReader.java index b6877b78bf6ecf2069e59028f8910826c8b4eafe..6e4a53291ee87fac6d81d4ab9746906b6ae1b453 100644 --- a/src/main/java/net/minecraft/world/level/LevelReader.java +++ b/src/main/java/net/minecraft/world/level/LevelReader.java @@ -18,6 +18,7 @@ import net.minecraft.world.phys.AABB; public interface LevelReader extends BlockAndTintGetter, CollisionGetter, BiomeManager.NoiseBiomeSource { + @Nullable ChunkAccess getChunkIfLoadedImmediately(int x, int z); // Paper - ifLoaded api (we need this since current impl blocks if the chunk is loading) @Nullable ChunkAccess getChunk(int chunkX, int chunkZ, ChunkStatus leastStatus, boolean create); diff --git a/src/main/java/net/minecraft/world/level/PathNavigationRegion.java b/src/main/java/net/minecraft/world/level/PathNavigationRegion.java index f1d759d87291f469bde3b433031a6e7c6a19fbf2..6db3f4efa6ea4a09aad7684a3b7cc7479fad2f5c 100644 --- a/src/main/java/net/minecraft/world/level/PathNavigationRegion.java +++ b/src/main/java/net/minecraft/world/level/PathNavigationRegion.java @@ -4,6 +4,7 @@ import java.util.function.Predicate; import java.util.stream.Stream; import javax.annotation.Nullable; import net.minecraft.core.BlockPos; +import net.minecraft.server.level.ServerLevel; import net.minecraft.world.entity.Entity; import net.minecraft.world.level.block.Blocks; import net.minecraft.world.level.block.entity.BlockEntity; @@ -23,7 +24,7 @@ public class PathNavigationRegion implements BlockGetter, CollisionGetter { protected final int centerZ; protected final ChunkAccess[][] chunks; protected boolean allEmpty; - protected final Level level; + protected final Level level; protected final Level getWorld() { return level; } // Paper - OBFHELPER public PathNavigationRegion(Level world, BlockPos minPos, BlockPos maxPos) { this.level = world; @@ -42,7 +43,7 @@ public class PathNavigationRegion implements BlockGetter, CollisionGetter { for (k = this.centerX; k <= i; ++k) { for (l = this.centerZ; l <= j; ++l) { - this.chunks[k - this.centerX][l - this.centerZ] = ichunkprovider.getChunkNow(k, l); + this.chunks[k - this.centerX][l - this.centerZ] = ((ServerLevel)world).getChunkSource().getChunkAtIfLoadedMainThreadNoCache(k, l); // Paper } } @@ -67,7 +68,7 @@ public class PathNavigationRegion implements BlockGetter, CollisionGetter { int k = i - this.centerX; int l = j - this.centerZ; - if (k >= 0 && k < this.chunks.length && l >= 0 && l < this.chunks[k].length) { + if (k >= 0 && k < this.chunks.length && l >= 0 && l < this.chunks[k].length) { // Paper - if this changes, update getChunkIfLoaded below ChunkAccess ichunkaccess = this.chunks[k][l]; return (ChunkAccess) (ichunkaccess != null ? ichunkaccess : new EmptyLevelChunk(this.level, new ChunkPos(i, j))); @@ -86,6 +87,29 @@ public class PathNavigationRegion implements BlockGetter, CollisionGetter { return this.getChunk(chunkX, chunkZ); } + // Paper start - if loaded util + private ChunkAccess getChunkIfLoaded(int x, int z) { + int k = x - this.centerX; + int l = z - this.centerZ; + + if (k >= 0 && k < this.chunks.length && l >= 0 && l < this.chunks[k].length) { + return this.chunks[k][l]; + } + return null; + } + @Override + public FluidState getFluidIfLoaded(BlockPos blockposition) { + ChunkAccess chunk = getChunkIfLoaded(blockposition.getX() >> 4, blockposition.getZ() >> 4); + return chunk == null ? null : chunk.getFluidState(blockposition); + } + + @Override + public BlockState getTypeIfLoaded(BlockPos blockposition) { + ChunkAccess chunk = getChunkIfLoaded(blockposition.getX() >> 4, blockposition.getZ() >> 4); + return chunk == null ? null : chunk.getBlockState(blockposition); + } + // Paper end + @Nullable @Override public BlockEntity getBlockEntity(BlockPos pos) { diff --git a/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java b/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java index c9713d1ab6498c09790503f673b31b5ef30ce4f3..e6928557a79f51302975f2832ec911c2692eaaeb 100644 --- a/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java +++ b/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java @@ -687,6 +687,7 @@ public abstract class BlockBehaviour { return this.cache != null ? this.cache.isCollisionShapeFullBlock : Block.isShapeFullBlock(this.getCollisionShape(world, pos)); } + public final BlockState getBlockData() { return asState(); } // Paper - OBFHELPER protected abstract BlockState asState(); public boolean requiresCorrectToolForDrops() { diff --git a/src/main/java/net/minecraft/world/level/border/WorldBorder.java b/src/main/java/net/minecraft/world/level/border/WorldBorder.java index fe1c10e5eeb434cd24e94b3247abbf5f73fce9cc..31f17956b3b031d1a47bda4d282554c8a7853097 100644 --- a/src/main/java/net/minecraft/world/level/border/WorldBorder.java +++ b/src/main/java/net/minecraft/world/level/border/WorldBorder.java @@ -47,6 +47,7 @@ public class WorldBorder { return this.getDistanceToBorder(entity.getX(), entity.getZ()); } + public final VoxelShape asVoxelShape(){ return getCollisionShape();} // Paper - OBFHELPER public VoxelShape getCollisionShape() { return this.extent.getCollisionShape(); } diff --git a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java index 5a91d74f0acf393dbf098719b0924a4c00cf7128..e2c5a17aa72d1a5412d76881187d4d9ad1763297 100644 --- a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java +++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java @@ -59,7 +59,7 @@ public class LevelChunk implements ChunkAccess { private static final Logger LOGGER = LogManager.getLogger(); @Nullable - public static final LevelChunkSection EMPTY_SECTION = null; + public static final LevelChunkSection EMPTY_SECTION = null; public static final LevelChunkSection EMPTY_CHUNK_SECTION = EMPTY_SECTION; // Paper - OBFHELPER private final LevelChunkSection[] sections; private ChunkBiomeContainer biomes; private final Map pendingBlockEntities; @@ -82,7 +82,7 @@ public class LevelChunk implements ChunkAccess { private Supplier fullStatus; @Nullable private Consumer postLoad; - private final ChunkPos chunkPos; + private final ChunkPos chunkPos; public final long coordinateKey; public final int locX; public final int locZ; // Paper - cache coordinate key private volatile boolean isLightCorrect; public LevelChunk(Level world, ChunkPos pos, ChunkBiomeContainer biomes) { @@ -99,7 +99,8 @@ public class LevelChunk implements ChunkAccess { this.postProcessing = new ShortList[16]; this.entitySlices = (List[]) (new List[16]); // Spigot this.world = (ServerLevel) world; // CraftBukkit - type - this.chunkPos = pos; + this.locX = pos.x; this.locZ = pos.z; // Paper - reduce need for field look ups + this.chunkPos = pos; this.coordinateKey = ChunkPos.asLong(locX, locZ); // Paper - cache long key this.upgradeData = upgradeData; Heightmap.Types[] aheightmap_type = Heightmap.Types.values(); int j = aheightmap_type.length; @@ -145,6 +146,110 @@ public class LevelChunk implements ChunkAccess { public final org.bukkit.craftbukkit.persistence.CraftPersistentDataContainer persistentDataContainer = new org.bukkit.craftbukkit.persistence.CraftPersistentDataContainer(DATA_TYPE_REGISTRY); // CraftBukkit end + // Paper start + public final com.destroystokyo.paper.util.maplist.EntityList entities = new com.destroystokyo.paper.util.maplist.EntityList(); + public ChunkHolder playerChunk; + + static final int NEIGHBOUR_CACHE_RADIUS = 3; + public static int getNeighbourCacheRadius() { + return NEIGHBOUR_CACHE_RADIUS; + } + + boolean loadedTicketLevel; + private long neighbourChunksLoadedBitset; + private final LevelChunk[] loadedNeighbourChunks = new LevelChunk[(NEIGHBOUR_CACHE_RADIUS * 2 + 1) * (NEIGHBOUR_CACHE_RADIUS * 2 + 1)]; + + private static int getNeighbourIndex(final int relativeX, final int relativeZ) { + // index = (relativeX + NEIGHBOUR_CACHE_RADIUS) + (relativeZ + NEIGHBOUR_CACHE_RADIUS) * (NEIGHBOUR_CACHE_RADIUS * 2 + 1) + // optimised variant of the above by moving some of the ops to compile time + return relativeX + (relativeZ * (NEIGHBOUR_CACHE_RADIUS * 2 + 1)) + (NEIGHBOUR_CACHE_RADIUS + NEIGHBOUR_CACHE_RADIUS * ((NEIGHBOUR_CACHE_RADIUS * 2 + 1))); + } + + public final LevelChunk getRelativeNeighbourIfLoaded(final int relativeX, final int relativeZ) { + return this.loadedNeighbourChunks[getNeighbourIndex(relativeX, relativeZ)]; + } + + public final boolean isNeighbourLoaded(final int relativeX, final int relativeZ) { + return (this.neighbourChunksLoadedBitset & (1L << getNeighbourIndex(relativeX, relativeZ))) != 0; + } + + public final void setNeighbourLoaded(final int relativeX, final int relativeZ, final LevelChunk chunk) { + if (chunk == null) { + throw new IllegalArgumentException("Chunk must be non-null, neighbour: (" + relativeX + "," + relativeZ + "), chunk: " + this.chunkPos); + } + final long before = this.neighbourChunksLoadedBitset; + final int index = getNeighbourIndex(relativeX, relativeZ); + this.loadedNeighbourChunks[index] = chunk; + this.neighbourChunksLoadedBitset |= (1L << index); + this.onNeighbourChange(before, this.neighbourChunksLoadedBitset); + } + + public final void setNeighbourUnloaded(final int relativeX, final int relativeZ) { + final long before = this.neighbourChunksLoadedBitset; + final int index = getNeighbourIndex(relativeX, relativeZ); + this.loadedNeighbourChunks[index] = null; + this.neighbourChunksLoadedBitset &= ~(1L << index); + this.onNeighbourChange(before, this.neighbourChunksLoadedBitset); + } + + public final void resetNeighbours() { + final long before = this.neighbourChunksLoadedBitset; + this.neighbourChunksLoadedBitset = 0L; + java.util.Arrays.fill(this.loadedNeighbourChunks, null); + this.onNeighbourChange(before, 0L); + } + + protected void onNeighbourChange(final long bitsetBefore, final long bitsetAfter) { + + } + + public final boolean isAnyNeighborsLoaded() { + return neighbourChunksLoadedBitset != 0; + } + public final boolean areNeighboursLoaded(final int radius) { + return LevelChunk.areNeighboursLoaded(this.neighbourChunksLoadedBitset, radius); + } + + public static boolean areNeighboursLoaded(final long bitset, final int radius) { + // index = relativeX + (relativeZ * (NEIGHBOUR_CACHE_RADIUS * 2 + 1)) + (NEIGHBOUR_CACHE_RADIUS + NEIGHBOUR_CACHE_RADIUS * ((NEIGHBOUR_CACHE_RADIUS * 2 + 1))) + switch (radius) { + case 0: { + return (bitset & (1L << getNeighbourIndex(0, 0))) != 0; + } + case 1: { + long mask = 0L; + for (int dx = -1; dx <= 1; ++dx) { + for (int dz = -1; dz <= 1; ++dz) { + mask |= (1L << getNeighbourIndex(dx, dz)); + } + } + return (bitset & mask) == mask; + } + case 2: { + long mask = 0L; + for (int dx = -2; dx <= 2; ++dx) { + for (int dz = -2; dz <= 2; ++dz) { + mask |= (1L << getNeighbourIndex(dx, dz)); + } + } + return (bitset & mask) == mask; + } + case 3: { + long mask = 0L; + for (int dx = -3; dx <= 3; ++dx) { + for (int dz = -3; dz <= 3; ++dz) { + mask |= (1L << getNeighbourIndex(dx, dz)); + } + } + return (bitset & mask) == mask; + } + + default: + throw new IllegalArgumentException("Radius not recognized: " + radius); + } + } + // Paper end + public LevelChunk(Level world, ProtoChunk protoChunk) { this(world, protoChunk.getPos(), protoChunk.getBiomes(), protoChunk.getUpgradeData(), protoChunk.getBlockTicks(), protoChunk.getLiquidTicks(), protoChunk.getInhabitedTime(), protoChunk.getSections(), (Consumer) null); Iterator iterator = protoChunk.getEntities().iterator(); @@ -250,6 +355,18 @@ public class LevelChunk implements ChunkAccess { } } + // Paper start - If loaded util + @Override + public FluidState getFluidIfLoaded(BlockPos blockposition) { + return this.getFluidState(blockposition); + } + + @Override + public BlockState getTypeIfLoaded(BlockPos blockposition) { + return this.getBlockState(blockposition); + } + // Paper end + @Override public FluidState getFluidState(BlockPos pos) { return this.getFluidState(pos.getX(), pos.getY(), pos.getZ()); @@ -390,6 +507,7 @@ public class LevelChunk implements ChunkAccess { entity.xChunk = this.chunkPos.x; entity.yChunk = k; entity.zChunk = this.chunkPos.z; + this.entities.add(entity); // Paper - per chunk entity list this.entitySlices[k].add(entity); } @@ -413,6 +531,7 @@ public class LevelChunk implements ChunkAccess { } this.entitySlices[section].remove(entity); + this.entities.remove(entity); // Paper } @Override @@ -434,6 +553,7 @@ public class LevelChunk implements ChunkAccess { return this.getBlockEntity(pos, LevelChunk.EntityCreationType.CHECK); } + @Nullable public final BlockEntity getTileEntityImmediately(BlockPos pos) { return this.getBlockEntity(pos, EntityCreationType.IMMEDIATE); } // Paper - OBFHELPER @Nullable public BlockEntity getBlockEntity(BlockPos pos, LevelChunk.EntityCreationType creationType) { // CraftBukkit start @@ -545,7 +665,25 @@ public class LevelChunk implements ChunkAccess { // CraftBukkit start public void loadCallback() { + // Paper start - neighbour cache + int chunkX = this.chunkPos.x; + int chunkZ = this.chunkPos.z; + ChunkProviderServer chunkProvider = ((ServerLevel)this.world).getChunkSource(); + for (int dx = -NEIGHBOUR_CACHE_RADIUS; dx <= NEIGHBOUR_CACHE_RADIUS; ++dx) { + for (int dz = -NEIGHBOUR_CACHE_RADIUS; dz <= NEIGHBOUR_CACHE_RADIUS; ++dz) { + LevelChunk neighbour = chunkProvider.getChunkAtIfLoadedMainThreadNoCache(chunkX + dx, chunkZ + dz); + if (neighbour != null) { + neighbour.setNeighbourLoaded(-dx, -dz, this); + // should be in cached already + this.setNeighbourLoaded(dx, dz, neighbour); + } + } + } + this.setNeighbourLoaded(0, 0, this); + this.loadedTicketLevel = true; + // Paper end - neighbour cache org.bukkit.Server server = this.world.getCraftServer(); + ((ServerLevel)this.world).getChunkSource().addLoadedChunk(this); // Paper if (server != null) { /* * If it's a new world, the first few chunks are generated inside @@ -584,6 +722,22 @@ public class LevelChunk implements ChunkAccess { server.getPluginManager().callEvent(unloadEvent); // note: saving can be prevented, but not forced if no saving is actually required this.mustNotSave = !unloadEvent.isSaveChunk(); + ((ServerLevel)this.world).getChunkSource().removeLoadedChunk(this); // Paper + // Paper start - neighbour cache + int chunkX = this.chunkPos.x; + int chunkZ = this.chunkPos.z; + ChunkProviderServer chunkProvider = ((ServerLevel)this.world).getChunkSource(); + for (int dx = -NEIGHBOUR_CACHE_RADIUS; dx <= NEIGHBOUR_CACHE_RADIUS; ++dx) { + for (int dz = -NEIGHBOUR_CACHE_RADIUS; dz <= NEIGHBOUR_CACHE_RADIUS; ++dz) { + LevelChunk neighbour = chunkProvider.getChunkAtIfLoadedMainThreadNoCache(chunkX + dx, chunkZ + dz); + if (neighbour != null) { + neighbour.setNeighbourUnloaded(-dx, -dz); + } + } + } + this.loadedTicketLevel = false; + this.resetNeighbours(); + // Paper end } // CraftBukkit end diff --git a/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java index 8a4fca9f4882e65b831dd3f82f242e1113859fe0..b54d82e0f41a03c91e0de8df8249a91da3c04d0e 100644 --- a/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java +++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java @@ -139,6 +139,7 @@ public class LevelChunkSection { return this.states; } + public void writeChunkSection(FriendlyByteBuf packetDataSerializer) { this.write(packetDataSerializer); } // Paper - OBFHELPER public void write(FriendlyByteBuf packetdataserializer) { packetdataserializer.writeShort(this.nonEmptyBlockCount); this.states.write(packetdataserializer); diff --git a/src/main/java/net/minecraft/world/level/chunk/Palette.java b/src/main/java/net/minecraft/world/level/chunk/Palette.java index dcc030f4801b4c6e1fe5b8c2718ddfd7ba6bb248..78c56e1e2af50e923fb0b07c6ddd860c4aa77195 100644 --- a/src/main/java/net/minecraft/world/level/chunk/Palette.java +++ b/src/main/java/net/minecraft/world/level/chunk/Palette.java @@ -7,10 +7,12 @@ import net.minecraft.network.FriendlyByteBuf; public interface Palette { + default int getOrCreateIdFor(T object) { return this.idFor(object); } // Paper - OBFHELPER int idFor(T object); boolean maybeHas(Predicate predicate); + @Nullable default T getObject(int dataBits) { return this.valueFor(dataBits); } // Paper - OBFHELPER @Nullable T valueFor(int index); 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 f14f89f8916c832feaa3887bd28a5cf6b2f6ff1d..d4db27421736f665739436c1ac4d3c6d5cae95cd 100644 --- a/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java +++ b/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java @@ -19,7 +19,7 @@ import net.minecraft.util.Mth; public class PalettedContainer implements PaletteResize { - private final Palette globalPalette; + private final Palette globalPalette; private final Palette getDataPaletteGlobal() { return this.globalPalette; } // Paper - OBFHELPER private final PaletteResize dummyPaletteResize = (i, object) -> { return 0; }; @@ -27,9 +27,9 @@ public class PalettedContainer implements PaletteResize { private final Function reader; private final Function writer; private final T defaultValue; - protected BitStorage storage; - private Palette palette; - private int bits; + protected BitStorage storage; public final BitStorage getDataBits() { return this.storage; } // Paper - OBFHELPER + private Palette palette; private Palette getDataPalette() { return this.palette; } // Paper - OBFHELPER + private int bits; private int getBitsPerObject() { return this.bits; } // Paper - OBFHELPER private final ReentrantLock lock = new ReentrantLock(); public void acquire() { @@ -64,6 +64,7 @@ public class PalettedContainer implements PaletteResize { return y << 8 | z << 4 | x; } + private void initialize(int bitsPerObject) { this.setBits(bitsPerObject); } // Paper - OBFHELPER private void setBits(int size) { if (size != this.bits) { this.bits = size; @@ -141,6 +142,7 @@ public class PalettedContainer implements PaletteResize { return t0 == null ? this.defaultValue : t0; } + public void writeDataPaletteBlock(FriendlyByteBuf packetDataSerializer) { this.write(packetDataSerializer); } // Paper - OBFHELPER public void write(FriendlyByteBuf buf) { this.acquire(); buf.writeByte(this.bits); diff --git a/src/main/java/net/minecraft/world/level/chunk/ProtoChunk.java b/src/main/java/net/minecraft/world/level/chunk/ProtoChunk.java index cecf8a68f215d85e84fba157930f6987ffd21e50..7cd3f89004b0a64772fc3dfbdd132ba5a850b63e 100644 --- a/src/main/java/net/minecraft/world/level/chunk/ProtoChunk.java +++ b/src/main/java/net/minecraft/world/level/chunk/ProtoChunk.java @@ -99,6 +99,18 @@ public class ProtoChunk implements ChunkAccess { } + // Paper start - If loaded util + @Override + public FluidState getFluidIfLoaded(BlockPos blockposition) { + return this.getFluidState(blockposition); + } + + @Override + public BlockState getTypeIfLoaded(BlockPos blockposition) { + return this.getBlockState(blockposition); + } + // Paper end + @Override public BlockState getBlockState(BlockPos pos) { int i = pos.getY(); diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/IOWorker.java b/src/main/java/net/minecraft/world/level/chunk/storage/IOWorker.java index 7de765786b3504dcffab98bb0d9dac64b30b3325..5bd34b136f2892f541ba686debca19e0a4eef0be 100644 --- a/src/main/java/net/minecraft/world/level/chunk/storage/IOWorker.java +++ b/src/main/java/net/minecraft/world/level/chunk/storage/IOWorker.java @@ -27,7 +27,7 @@ public class IOWorker implements AutoCloseable { private static final Logger LOGGER = LogManager.getLogger(); private final AtomicBoolean shutdownRequested = new AtomicBoolean(); private final ProcessorMailbox mailbox; - private final RegionFileStorage storage; + private final RegionFileStorage storage;public RegionFileStorage getRegionFileCache() { return storage; } // Paper - OBFHELPER private final Map pendingWrites = Maps.newLinkedHashMap(); protected IOWorker(File file, boolean flag, String s) { diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFile.java b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFile.java index aa3d6db08e4d744cc94de71d0f8dceb99948e2ab..60f410a4f838048bbfd2cde52caa7c4c9434b0ba 100644 --- a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFile.java +++ b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFile.java @@ -112,6 +112,7 @@ public class RegionFile implements AutoCloseable { return this.externalFileDir.resolve(s); } + @Nullable public synchronized DataInputStream getReadStream(ChunkPos chunkCoordIntPair) throws IOException { return getChunkDataInputStream(chunkCoordIntPair);} // Paper - OBFHELPER @Nullable public synchronized DataInputStream getChunkDataInputStream(ChunkPos pos) throws IOException { int i = this.getOffset(pos); diff --git a/src/main/java/net/minecraft/world/phys/AABB.java b/src/main/java/net/minecraft/world/phys/AABB.java index 022fafff8b476f8bc1830bf5494760b0fef65297..983d0495ec35128ca3ef68566ada065bc4b21efc 100644 --- a/src/main/java/net/minecraft/world/phys/AABB.java +++ b/src/main/java/net/minecraft/world/phys/AABB.java @@ -194,10 +194,12 @@ public class AABB { return this.move(vec3d.x, vec3d.y, vec3d.z); } + public final boolean intersects(AABB axisalignedbb) { return this.intersects(axisalignedbb); } // Paper - OBFHELPER public boolean intersects(AABB box) { return this.intersects(box.minX, box.minY, box.minZ, box.maxX, box.maxY, box.maxZ); } + public final boolean intersects(double d0, double d1, double d2, double d3, double d4, double d5) { return intersects(d0, d1, d2, d3, d4, d5); } // Paper - OBFHELPER public boolean intersects(double minX, double minY, double minZ, double maxX, double maxY, double maxZ) { return this.minX < maxX && this.maxX > minX && this.minY < maxY && this.maxY > minY && this.minZ < maxZ && this.maxZ > minZ; } @@ -210,6 +212,7 @@ public class AABB { return x >= this.minX && x < this.maxX && y >= this.minY && y < this.maxY && z >= this.minZ && z < this.maxZ; } + public final double getAverageSideLength(){return getSize();} // Paper - OBFHELPER public double getSize() { double d0 = this.getXsize(); double d1 = this.getYsize(); diff --git a/src/main/java/net/minecraft/world/phys/shapes/Shapes.java b/src/main/java/net/minecraft/world/phys/shapes/Shapes.java index eb07309be171ccadcae21f4096c44d2b700d22b3..2371b52b450e2b43fa9b9549a91f853c702a9dc0 100644 --- a/src/main/java/net/minecraft/world/phys/shapes/Shapes.java +++ b/src/main/java/net/minecraft/world/phys/shapes/Shapes.java @@ -31,10 +31,12 @@ public final class Shapes { public static final VoxelShape INFINITY = box(Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY); private static final VoxelShape EMPTY = new ArrayVoxelShape(new BitSetDiscreteVoxelShape(0, 0, 0), new DoubleArrayList(new double[]{0.0D}), new DoubleArrayList(new double[]{0.0D}), new DoubleArrayList(new double[]{0.0D})); + public static final VoxelShape empty() {return empty();} // Paper - OBFHELPER public static VoxelShape empty() { return Shapes.EMPTY; } + public static final VoxelShape fullCube() {return block();} // Paper - OBFHELPER public static VoxelShape block() { return Shapes.BLOCK; } diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java index c34f63eaf3deca4623ca4dfbee863771014847ba..01df5263d77771a296ca091a0feec620e6e37229 100644 --- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java +++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java @@ -85,6 +85,7 @@ public final class CraftItemStack extends ItemStack { } net.minecraft.world.item.ItemStack handle; + public net.minecraft.world.item.ItemStack getHandle() { return handle; } // Paper /** * Mirror diff --git a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java index 9ad17c560c8d99a396543ab9f97c34de648f6544..4bf48f77f3f7cd62a91590543f5af441c8268029 100644 --- a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java +++ b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java @@ -43,6 +43,7 @@ import org.bukkit.scheduler.BukkitWorker; */ public class CraftScheduler implements BukkitScheduler { + static Plugin MINECRAFT = new MinecraftInternalPlugin(); /** * Counter for IDs. Order doesn't matter, only uniqueness. */ @@ -177,6 +178,11 @@ public class CraftScheduler implements BukkitScheduler { runTaskTimer(plugin, (Object) task, delay, period); } + public BukkitTask scheduleInternalTask(Runnable run, int delay, String taskName) { + final CraftTask task = new CraftTask(run, nextId(), taskName); + return handle(task, delay); + } + public BukkitTask runTaskTimer(Plugin plugin, Object runnable, long delay, long period) { validate(plugin, runnable); if (delay < 0L) { @@ -400,13 +406,20 @@ public class CraftScheduler implements BukkitScheduler { task.run(); task.timings.stopTiming(); // Spigot } catch (final Throwable throwable) { - task.getOwner().getLogger().log( + // Paper start + String msg = String.format( + "Task #%s for %s generated an exception", + task.getTaskId(), + task.getOwner().getDescription().getFullName()); + if (task.getOwner() == MINECRAFT) { + net.minecraft.server.MinecraftServer.LOGGER.error(msg, throwable); + } else { + task.getOwner().getLogger().log( Level.WARNING, - String.format( - "Task #%s for %s generated an exception", - task.getTaskId(), - task.getOwner().getDescription().getFullName()), + msg, throwable); + } + // Paper end } finally { currentTask = null; } diff --git a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftTask.java b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftTask.java index 3c7066192ea4c05c101404bb56cbc839771f4200..09aa6809c5400ce8548ac902908b750ce7c964ec 100644 --- a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftTask.java +++ b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftTask.java @@ -39,6 +39,21 @@ public class CraftTask implements BukkitTask, Runnable { // Spigot CraftTask(final Object task) { this(null, task, CraftTask.NO_REPEATING, CraftTask.NO_REPEATING); } + // Paper start + public String taskName = null; + boolean internal = false; + CraftTask(final Object task, int id, String taskName) { + this.rTask = (Runnable) task; + this.cTask = null; + this.plugin = CraftScheduler.MINECRAFT; + this.taskName = taskName; + this.internal = true; + this.id = id; + this.period = CraftTask.NO_REPEATING; + this.taskName = taskName; + this.timings = null; // Will be changed in later patch + } + // Paper end CraftTask(final Plugin plugin, final Object task, final int id, final long period) { this.plugin = plugin; diff --git a/src/main/java/org/bukkit/craftbukkit/scheduler/MinecraftInternalPlugin.java b/src/main/java/org/bukkit/craftbukkit/scheduler/MinecraftInternalPlugin.java new file mode 100644 index 0000000000000000000000000000000000000000..49dc0c441b9dd7e7745cf15ced67f383ebee1f99 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/scheduler/MinecraftInternalPlugin.java @@ -0,0 +1,132 @@ +package org.bukkit.craftbukkit.scheduler; + + +import org.bukkit.Server; +import org.bukkit.command.Command; +import org.bukkit.command.CommandSender; +import org.bukkit.configuration.file.FileConfiguration; +import org.bukkit.generator.ChunkGenerator; +import org.bukkit.plugin.PluginBase; +import org.bukkit.plugin.PluginDescriptionFile; +import org.bukkit.plugin.PluginLoader; +import org.bukkit.plugin.PluginLogger; + +import java.io.File; +import java.io.InputStream; +import java.util.List; + +public class MinecraftInternalPlugin extends PluginBase { + private boolean enabled = true; + + private final String pluginName; + private PluginDescriptionFile pdf; + + public MinecraftInternalPlugin() { + this.pluginName = "Minecraft"; + pdf = new PluginDescriptionFile(pluginName, "1.0", "nms"); + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + @Override + public File getDataFolder() { + throw new UnsupportedOperationException("Not supported."); + } + + @Override + public PluginDescriptionFile getDescription() { + return pdf; + } + + @Override + public FileConfiguration getConfig() { + throw new UnsupportedOperationException("Not supported."); + } + + @Override + public InputStream getResource(String filename) { + throw new UnsupportedOperationException("Not supported."); + } + + @Override + public void saveConfig() { + throw new UnsupportedOperationException("Not supported."); + } + + @Override + public void saveDefaultConfig() { + throw new UnsupportedOperationException("Not supported."); + } + + @Override + public void saveResource(String resourcePath, boolean replace) { + throw new UnsupportedOperationException("Not supported."); + } + + @Override + public void reloadConfig() { + throw new UnsupportedOperationException("Not supported."); + } + + @Override + public PluginLogger getLogger() { + throw new UnsupportedOperationException("Not supported."); + } + + @Override + public PluginLoader getPluginLoader() { + throw new UnsupportedOperationException("Not supported."); + } + + @Override + public Server getServer() { + throw new UnsupportedOperationException("Not supported."); + } + + @Override + public boolean isEnabled() { + return enabled; + } + + @Override + public void onDisable() { + throw new UnsupportedOperationException("Not supported."); + } + + @Override + public void onLoad() { + throw new UnsupportedOperationException("Not supported."); + } + + @Override + public void onEnable() { + throw new UnsupportedOperationException("Not supported."); + } + + @Override + public boolean isNaggable() { + throw new UnsupportedOperationException("Not supported."); + } + + @Override + public void setNaggable(boolean canNag) { + throw new UnsupportedOperationException("Not supported."); + } + + @Override + public ChunkGenerator getDefaultWorldGenerator(String worldName, String id) { + throw new UnsupportedOperationException("Not supported."); + } + + @Override + public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { + throw new UnsupportedOperationException("Not supported."); + } + + @Override + public List onTabComplete(CommandSender sender, Command command, String alias, String[] args) { + throw new UnsupportedOperationException("Not supported."); + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/util/DummyGeneratorAccess.java b/src/main/java/org/bukkit/craftbukkit/util/DummyGeneratorAccess.java index 33cd17c415ae19bc9028934257b396907995cb9a..40a2ad3e180cc50a755f44a8ff6d8261734bf733 100644 --- a/src/main/java/org/bukkit/craftbukkit/util/DummyGeneratorAccess.java +++ b/src/main/java/org/bukkit/craftbukkit/util/DummyGeneratorAccess.java @@ -168,7 +168,23 @@ public class DummyGeneratorAccess implements LevelAccessor { public FluidState getFluidState(BlockPos pos) { throw new UnsupportedOperationException("Not supported yet."); } + // Paper start - if loaded util + @javax.annotation.Nullable + @Override + public ChunkAccess getChunkIfLoadedImmediately(int x, int z) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public BlockState getTypeIfLoaded(BlockPos blockposition) { + throw new UnsupportedOperationException("Not supported yet."); + } + @Override + public FluidState getFluidIfLoaded(BlockPos blockposition) { + throw new UnsupportedOperationException("Not supported yet."); + } + // Paper end @Override public WorldBorder getWorldBorder() { throw new UnsupportedOperationException("Not supported yet."); diff --git a/src/main/java/org/bukkit/craftbukkit/util/UnsafeList.java b/src/main/java/org/bukkit/craftbukkit/util/UnsafeList.java index 1aec70a1f1a9d8fd2cd06bde4033e19e769ab331..f72c13bedaa6fa45e26f5dcad564835bdd4af61f 100644 --- a/src/main/java/org/bukkit/craftbukkit/util/UnsafeList.java +++ b/src/main/java/org/bukkit/craftbukkit/util/UnsafeList.java @@ -17,7 +17,7 @@ import java.util.RandomAccess; public class UnsafeList extends AbstractList implements List, RandomAccess, Cloneable, Serializable { private static final long serialVersionUID = 8683452581112892191L; - private transient Object[] data; + private transient Object[] data; public final Object[] getRawDataArray() { return this.data; } // Paper - expose for raw get private int size; private int initialCapacity; diff --git a/src/main/java/org/spigotmc/SpigotConfig.java b/src/main/java/org/spigotmc/SpigotConfig.java index 8d6f4d76d6f04a322a98faecaca6b1b69c5f49d6..dc11dab14624ca25e78bf0b919ecf461e0be430d 100644 --- a/src/main/java/org/spigotmc/SpigotConfig.java +++ b/src/main/java/org/spigotmc/SpigotConfig.java @@ -118,7 +118,11 @@ public class SpigotConfig } } } - + // Paper start + SpigotConfig.save(); + } + public static void save() { + // Paper end try { config.save( CONFIG_FILE );