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..bf63a0e87f8c9529e473269c0626051c81bb04ea --- /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 net.minecraft.world.level.chunk.Chunk; +import java.util.Arrays; +import java.util.Iterator; +import java.util.NoSuchElementException; + +// 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 Chunk[] EMPTY_LIST = new Chunk[0]; + + protected Chunk[] chunks = EMPTY_LIST; + protected int count; + + public int size() { + return this.count; + } + + public boolean contains(final Chunk chunk) { + return this.chunkToIndex.containsKey(chunk.coordinateKey); + } + + public boolean remove(final Chunk 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 Chunk 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 Chunk 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 + } + + Chunk[] 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 Chunk 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 Chunk getUnchecked(final int index) { + return this.chunks[index]; + } + + public Chunk[] 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() { + + Chunk lastRet; + int current; + + @Override + public boolean hasNext() { + return this.current < ChunkList.this.count; + } + + @Override + public Chunk next() { + if (this.current >= ChunkList.this.count) { + throw new NoSuchElementException(); + } + return this.lastRet = ChunkList.this.chunks[this.current++]; + } + + @Override + public void remove() { + final Chunk 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..d9fdc8196e53518ceac3aeb7bf3b98a0bd348f8f --- /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 net.minecraft.world.level.block.state.IBlockData; +import net.minecraft.world.level.chunk.ChunkSection; +import net.minecraft.world.level.chunk.DataPaletteGlobal; +import java.util.Arrays; + +/** + * @author Spottedleaf + */ +public final class IBlockDataList { + + static final DataPaletteGlobal GLOBAL_PALETTE = (DataPaletteGlobal) ChunkSection.GLOBAL_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 IBlockData 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 IBlockData 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 IBlockData data) { + return this.add(getLocationKey(x, y, z), data); + } + + public long add(final int location, final IBlockData 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 IBlockData 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..24407a3e653ba32ef6b921c346571ec734a72245 --- /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.ChunkCoordIntPair; +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 ChunkCoordIntPair 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..905b76d1d65744fe35f56bb78ef75f49178a6a24 --- /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.ChunkCoordIntPair; + +/** @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 ChunkCoordIntPair 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..ed6133b07bc6c4662bd2099ea7dc8aabec37c853 --- /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.EntityPlayer; + +/** + * @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 EntityPlayer 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..eca1fea17184076635563717bb32c81187e2f6f7 --- /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.EntityPlayer; + +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 EntityPlayer 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/SystemUtils.java b/src/main/java/net/minecraft/SystemUtils.java index bc7757b929ecce998094ddcdf51a4703e165a6d6..c8bb06a31242089ad950713bd5f94abbfe12adc8 100644 --- a/src/main/java/net/minecraft/SystemUtils.java +++ b/src/main/java/net/minecraft/SystemUtils.java @@ -78,7 +78,7 @@ public class SystemUtils { } public static long getMonotonicNanos() { - return SystemUtils.a.getAsLong(); + return System.nanoTime(); // Paper } public static long getTimeMillis() { diff --git a/src/main/java/net/minecraft/core/BaseBlockPosition.java b/src/main/java/net/minecraft/core/BaseBlockPosition.java index fe3a3ce150de0e689c452b67d480b9d69471b330..25fdd55a7548cfaa45a541ad77f22f33c33e7471 100644 --- a/src/main/java/net/minecraft/core/BaseBlockPosition.java +++ b/src/main/java/net/minecraft/core/BaseBlockPosition.java @@ -18,9 +18,9 @@ public class BaseBlockPosition implements Comparable { return IntStream.of(new int[]{baseblockposition.getX(), baseblockposition.getY(), baseblockposition.getZ()}); }); public static final BaseBlockPosition ZERO = new BaseBlockPosition(0, 0, 0); - private int a; - private int b; - private int e; + private int a;public final void setX(final int x) { this.a = x; } // Paper - OBFHELPER + private int b;public final void setY(final int y) { this.b = y; } // Paper - OBFHELPER + private int e;public final void setZ(final int z) { this.e = z; } // Paper - OBFHELPER public BaseBlockPosition(int i, int j, int k) { this.a = i; @@ -64,15 +64,15 @@ public class BaseBlockPosition implements Comparable { return this.e; } - protected void o(int i) { + public void o(int i) { // Paper - protected -> public this.a = i; } - protected void p(int i) { + public void p(int i) { // Paper - protected -> public this.b = i; } - protected void q(int i) { + public void q(int i) { // Paper - protected -> public this.e = i; } @@ -108,6 +108,7 @@ public class BaseBlockPosition implements Comparable { return this.distanceSquared(iposition.getX(), iposition.getY(), iposition.getZ(), true) < d0 * d0; } + public final double distanceSquared(BaseBlockPosition baseblockposition) { return j(baseblockposition); } // Paper - OBFHELPER public double j(BaseBlockPosition baseblockposition) { return this.distanceSquared((double) baseblockposition.getX(), (double) baseblockposition.getY(), (double) baseblockposition.getZ(), true); } diff --git a/src/main/java/net/minecraft/core/BlockPosition.java b/src/main/java/net/minecraft/core/BlockPosition.java index 76675ad1633dbaebb180842b9914fac18741c62e..9fb6db18c5c1f39b5a564c0f5f70498825defa97 100644 --- a/src/main/java/net/minecraft/core/BlockPosition.java +++ b/src/main/java/net/minecraft/core/BlockPosition.java @@ -105,6 +105,7 @@ public class BlockPosition extends BaseBlockPosition { return d0 == 0.0D && d1 == 0.0D && d2 == 0.0D ? this : new BlockPosition((double) this.getX() + d0, (double) this.getY() + d1, (double) this.getZ() + d2); } + public final BlockPosition add(int i, int j, int k) {return b(i, j, k);} // Paper - OBFHELPER public BlockPosition b(int i, int j, int k) { return i == 0 && j == 0 && k == 0 ? this : new BlockPosition(this.getX() + i, this.getY() + j, this.getZ() + k); } @@ -436,6 +437,7 @@ public class BlockPosition extends BaseBlockPosition { return super.a(enumblockrotation).immutableCopy(); } + public final BlockPosition.MutableBlockPosition setValues(int i, int j, int k) { return d(i, j, k);} // Paper - OBFHELPER public BlockPosition.MutableBlockPosition d(int i, int j, int k) { this.o(i); this.p(j); @@ -443,6 +445,7 @@ public class BlockPosition extends BaseBlockPosition { return this; } + public final BlockPosition.MutableBlockPosition setValues(double d0, double d1, double d2) { return c(d0, d1, d2);} // Paper - OBFHELPER public BlockPosition.MutableBlockPosition c(double d0, double d1, double d2) { return this.d(MathHelper.floor(d0), MathHelper.floor(d1), MathHelper.floor(d2)); } @@ -496,6 +499,7 @@ public class BlockPosition extends BaseBlockPosition { } } + /* // Paper start - comment out useless overrides @Override @Override public void o(int i) { super.o(i); @@ -506,10 +510,10 @@ public class BlockPosition extends BaseBlockPosition { super.p(i); } - @Override public void q(int i) { super.q(i); } + */ // Paper end @Override public BlockPosition immutableCopy() { diff --git a/src/main/java/net/minecraft/core/RegistryBlockID.java b/src/main/java/net/minecraft/core/RegistryBlockID.java index e543b6927280a14e1d1220534758289934e31282..d5bec8b0e155ea5ae5746b6da571754a98e4125e 100644 --- a/src/main/java/net/minecraft/core/RegistryBlockID.java +++ b/src/main/java/net/minecraft/core/RegistryBlockID.java @@ -64,6 +64,7 @@ public class RegistryBlockID implements Registry { return Iterators.filter(this.c.iterator(), Predicates.notNull()); } + public int size() { return this.a(); } // Paper - OBFHELPER public int a() { return this.b.size(); } diff --git a/src/main/java/net/minecraft/nbt/NBTTagCompound.java b/src/main/java/net/minecraft/nbt/NBTTagCompound.java index b2fb24e9ae19ab6e7039a98fc0c265f801be8a99..bf4826e90976fed2ae95e84cadc7f29433af1ddf 100644 --- a/src/main/java/net/minecraft/nbt/NBTTagCompound.java +++ b/src/main/java/net/minecraft/nbt/NBTTagCompound.java @@ -76,7 +76,7 @@ public class NBTTagCompound implements NBTBase { return "TAG_Compound"; } }; - private final Map map; + public final Map map; // Paper protected NBTTagCompound(Map map) { this.map = map; @@ -139,10 +139,16 @@ public class NBTTagCompound implements NBTBase { this.map.put(s, NBTTagLong.a(i)); } + public void setUUID(String prefix, UUID uuid) { a(prefix, uuid); } // Paper - OBFHELPER public void a(String s, UUID uuid) { this.map.put(s, GameProfileSerializer.a(uuid)); } + + /** + * You must use {@link #hasUUID(String)} before or else it will throw an NPE. + */ + public UUID getUUID(String prefix) { return a(prefix); } // Paper - OBFHELPER public UUID a(String s) { return GameProfileSerializer.a(this.get(s)); } diff --git a/src/main/java/net/minecraft/network/NetworkManager.java b/src/main/java/net/minecraft/network/NetworkManager.java index 82d8a163df294e68b4db685b95553637f905db48..f093b465b868e6003bb2b5ee634a624b5b054493 100644 --- a/src/main/java/net/minecraft/network/NetworkManager.java +++ b/src/main/java/net/minecraft/network/NetworkManager.java @@ -168,6 +168,7 @@ public class NetworkManager extends SimpleChannelInboundHandler> { } + private void dispatchPacket(Packet packet, @Nullable GenericFutureListener> genericFutureListener) { this.b(packet, genericFutureListener); } // Paper - OBFHELPER private void b(Packet packet, @Nullable GenericFutureListener> genericfuturelistener) { EnumProtocol enumprotocol = EnumProtocol.a(packet); EnumProtocol enumprotocol1 = (EnumProtocol) this.channel.attr(NetworkManager.c).get(); @@ -208,6 +209,7 @@ public class NetworkManager extends SimpleChannelInboundHandler> { } + private void sendPacketQueue() { this.p(); } // Paper - OBFHELPER private void p() { if (this.channel != null && this.channel.isOpen()) { Queue queue = this.packetQueue; @@ -344,9 +346,9 @@ public class NetworkManager extends SimpleChannelInboundHandler> { static class QueuedPacket { - private final Packet a; + private final Packet a; private final Packet getPacket() { return this.a; } // Paper - OBFHELPER @Nullable - private final GenericFutureListener> b; + private final GenericFutureListener> b; private final GenericFutureListener> getGenericFutureListener() { return this.b; } // Paper - OBFHELPER public QueuedPacket(Packet packet, @Nullable GenericFutureListener> genericfuturelistener) { this.a = packet; diff --git a/src/main/java/net/minecraft/network/PacketDataSerializer.java b/src/main/java/net/minecraft/network/PacketDataSerializer.java index f94e96406d6dc9c75091ae7563dd5b325f4f0c22..5413bf93f7f0f4491fca1f07c47a925fdace7751 100644 --- a/src/main/java/net/minecraft/network/PacketDataSerializer.java +++ b/src/main/java/net/minecraft/network/PacketDataSerializer.java @@ -49,6 +49,7 @@ public class PacketDataSerializer extends ByteBuf { this.a = bytebuf; } + public static int countBytes(int i) { return PacketDataSerializer.a(i); } // Paper - OBFHELPER public static int a(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 06098698e4adc31aa96f9592975e441f965b5558..dc8cc8d6c00176c8562086282f726dc1b24b2c65 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.b(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.a()) { throw new SkipEncodeException(throwable); } else { diff --git a/src/main/java/net/minecraft/network/protocol/game/PacketPlayInBEdit.java b/src/main/java/net/minecraft/network/protocol/game/PacketPlayInBEdit.java index d748e07f8870023e74796910a457d58ee0361ca6..2b8358995e4933d3fc3498407a7df7475d7b7e26 100644 --- a/src/main/java/net/minecraft/network/protocol/game/PacketPlayInBEdit.java +++ b/src/main/java/net/minecraft/network/protocol/game/PacketPlayInBEdit.java @@ -7,7 +7,7 @@ import net.minecraft.world.item.ItemStack; public class PacketPlayInBEdit implements Packet { - private ItemStack a; + private ItemStack a; public ItemStack getBook() { return a; } // Paper - OBFHELPER private boolean b; private int c; diff --git a/src/main/java/net/minecraft/network/protocol/game/PacketPlayOutMapChunk.java b/src/main/java/net/minecraft/network/protocol/game/PacketPlayOutMapChunk.java index 820ba7c59e7bc7b6f3311f1a4ec3d724e265a2af..b6b55d5baa5e8a6b69a3e4865c06bc8a4d61a4f3 100644 --- a/src/main/java/net/minecraft/network/protocol/game/PacketPlayOutMapChunk.java +++ b/src/main/java/net/minecraft/network/protocol/game/PacketPlayOutMapChunk.java @@ -28,7 +28,7 @@ public class PacketPlayOutMapChunk implements Packet { private NBTTagCompound d; @Nullable private int[] e; - private byte[] f; + private byte[] f; private byte[] getData() { return this.f; } // Paper - OBFHELPER private List g; private boolean h; @@ -140,6 +140,7 @@ public class PacketPlayOutMapChunk implements Packet { return bytebuf; } + public int writeChunk(PacketDataSerializer packetDataSerializer, Chunk chunk, int chunkSectionSelector) { return this.a(packetDataSerializer, chunk, chunkSectionSelector); } // Paper - OBFHELPER public int a(PacketDataSerializer packetdataserializer, Chunk chunk, int i) { int j = 0; ChunkSection[] achunksection = chunk.getSections(); 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..18b56b59fd6efd618e6ff6f9cf3a02f57588d244 --- /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.BlockPosition; +import net.minecraft.core.EnumDirection; +import net.minecraft.server.level.WorldServer; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.level.ChunkCoordIntPair; +import net.minecraft.world.level.RayTrace; +import net.minecraft.world.level.World; +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(BlockPosition blockposition, int radius) { + List list = com.google.common.collect.Lists.newArrayList(); + + list.add(new ChunkCoordIntPair(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 ChunkCoordIntPair((blockposition.getX() + (x << 4)) >> 4, (blockposition.getZ() + (z << 4)) >> 4)); + list.add(new ChunkCoordIntPair((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 BlockPosition blockPos) { + return ((long)(blockPos.getZ() >> 4) << 32) | ((blockPos.getX() >> 4) & 0xFFFFFFFFL); + } + + public static long getCoordinateKey(final Entity entity) { + return ((long)(MCUtil.fastFloor(entity.locZ()) >> 4) << 32) | ((MCUtil.fastFloor(entity.locX()) >> 4) & 0xFFFFFFFFL); + } + + public static long getCoordinateKey(final ChunkCoordIntPair 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 BlockPosition 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.locX()), getBlockCoordinate(entity.locY()), getBlockCoordinate(entity.locZ())); + } + + // 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().isMainThread(); + } + + 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(BlockPosition e1, BlockPosition 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.locX(),e1.locY(),e1.locZ(), e2.locX(),e2.locY(),e2.locZ()); + } + + /** + * Gets the distance sqaured between 2 block positions + * @param pos1 + * @param pos2 + * @return + */ + public static double distanceSq(BlockPosition pos1, BlockPosition 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(World 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(World world, BlockPosition 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.getWorld().getWorld(), entity.locX(), entity.locY(), entity.locZ()); + } + + public static org.bukkit.block.Block toBukkitBlock(World world, BlockPosition pos) { + return world.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ()); + } + + public static BlockPosition toBlockPosition(Location loc) { + return new BlockPosition(loc.getBlockX(), loc.getBlockY(), loc.getBlockZ()); + } + + public static boolean isEdgeOfChunk(BlockPosition 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 WorldServer getNMSWorld(@Nonnull org.bukkit.World world) { + return ((CraftWorld) world).getHandle(); + } + + public static WorldServer getNMSWorld(@Nonnull org.bukkit.entity.Entity entity) { + return getNMSWorld(entity.getWorld()); + } + + public static RayTrace.FluidCollisionOption getNMSFluidCollisionOption(TargetBlockInfo.FluidMode fluidMode) { + if (fluidMode == TargetBlockInfo.FluidMode.NEVER) { + return RayTrace.FluidCollisionOption.NONE; + } + if (fluidMode == TargetBlockInfo.FluidMode.SOURCE_ONLY) { + return RayTrace.FluidCollisionOption.SOURCE_ONLY; + } + if (fluidMode == TargetBlockInfo.FluidMode.ALWAYS) { + return RayTrace.FluidCollisionOption.ANY; + } + return null; + } + + public static BlockFace toBukkitBlockFace(EnumDirection 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 81cff54c93bc04d19e3cc0f5307799c62d139ab2..cd3e7b8a23e40c717829bd262bfa675e4e3532f9 100644 --- a/src/main/java/net/minecraft/server/MinecraftServer.java +++ b/src/main/java/net/minecraft/server/MinecraftServer.java @@ -884,6 +884,9 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant public private final LightEngineThreaded lightEngine; private final ChunkProviderServer.a serverThreadQueue; public final PlayerChunkMap playerChunkMap; @@ -62,6 +63,158 @@ public class ChunkProviderServer extends IChunkProvider { private final IChunkAccess[] cacheChunk = new IChunkAccess[4]; @Nullable private SpawnerCreature.d p; + // 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 Chunk[] lastLoadedChunks = new Chunk[4 * 4]; + + private static int getChunkCacheKey(int x, int z) { + return x & 3 | ((z & 3) << 2); + } + + public void addLoadedChunk(Chunk 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(Chunk 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); + + Chunk cachedChunk = this.lastLoadedChunks[cacheKey]; + if (cachedChunk != null && cachedChunk.coordinateKey == chunk.coordinateKey) { + this.lastLoadedChunks[cacheKey] = null; + } + } + + public final Chunk getChunkAtIfLoadedMainThread(int x, int z) { + int cacheKey = getChunkCacheKey(x, z); + + Chunk cachedChunk = this.lastLoadedChunks[cacheKey]; + if (cachedChunk != null && cachedChunk.locX == x & cachedChunk.locZ == z) { + return this.lastLoadedChunks[cacheKey]; + } + + long chunkKey = ChunkCoordIntPair.pair(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 Chunk getChunkAtIfLoadedMainThreadNoCache(int x, int z) { + return this.loadedChunkMap.get(ChunkCoordIntPair.pair(x, z)); + } + + public final Chunk getChunkAtMainThread(int x, int z) { + Chunk ret = this.getChunkAtIfLoadedMainThread(x, z); + if (ret != null) { + return ret; + } + return (Chunk)this.getChunkAt(x, z, ChunkStatus.FULL, true); + } + + private long chunkFutureAwaitCounter; + + public void getEntityTickingChunkAsync(int x, int z, java.util.function.Consumer onLoad) { + if (Thread.currentThread() != this.serverThread) { + this.serverThreadQueue.execute(() -> { + ChunkProviderServer.this.getEntityTickingChunkAsync(x, z, onLoad); + }); + return; + } + this.getChunkFutureAsynchronously(x, z, 31, PlayerChunk::getEntityTickingFuture, onLoad); + } + + public void getTickingChunkAsync(int x, int z, java.util.function.Consumer onLoad) { + if (Thread.currentThread() != this.serverThread) { + this.serverThreadQueue.execute(() -> { + ChunkProviderServer.this.getTickingChunkAsync(x, z, onLoad); + }); + return; + } + this.getChunkFutureAsynchronously(x, z, 32, PlayerChunk::getTickingFuture, onLoad); + } + + public void getFullChunkAsync(int x, int z, java.util.function.Consumer onLoad) { + if (Thread.currentThread() != this.serverThread) { + this.serverThreadQueue.execute(() -> { + ChunkProviderServer.this.getFullChunkAsync(x, z, onLoad); + }); + return; + } + this.getChunkFutureAsynchronously(x, z, 33, PlayerChunk::getFullChunkFuture, onLoad); + } + + private void getChunkFutureAsynchronously(int x, int z, int ticketLevel, Function>> futureGet, java.util.function.Consumer onLoad) { + if (Thread.currentThread() != this.serverThread) { + throw new IllegalStateException(); + } + ChunkCoordIntPair chunkPos = new ChunkCoordIntPair(x, z); + Long identifier = this.chunkFutureAwaitCounter++; + this.chunkMapDistance.addTicketAtLevel(TicketType.FUTURE_AWAIT, chunkPos, ticketLevel, identifier); + this.tickDistanceManager(); + + PlayerChunk chunk = this.playerChunkMap.getUpdatingChunk(chunkPos.pair()); + + if (chunk == null) { + throw new IllegalStateException("Expected playerchunk " + chunkPos + " in world '" + this.world.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 '" + ChunkProviderServer.this.world.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 '" + ChunkProviderServer.this.world.getWorld().getName() + "': " + either.right().get().toString()); + } + + try { + if (onLoad != null) { + playerChunkMap.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 '" + ChunkProviderServer.this.world.getWorld().getName() + "'", thr); + return; + } + } finally { + // due to odd behaviour with CB unload implementation we need to have these AFTER the load callback. + ChunkProviderServer.this.chunkMapDistance.addTicketAtLevel(TicketType.UNKNOWN, chunkPos, ticketLevel, chunkPos); + ChunkProviderServer.this.chunkMapDistance.removeTicketAtLevel(TicketType.FUTURE_AWAIT, chunkPos, ticketLevel, identifier); + } + }, this.serverThreadQueue); + } + // Paper end public ChunkProviderServer(WorldServer worldserver, Convertable.ConversionSession convertable_conversionsession, DataFixer datafixer, DefinedStructureManager definedstructuremanager, Executor executor, ChunkGenerator chunkgenerator, int i, boolean flag, WorldLoadListener worldloadlistener, Supplier supplier) { this.world = worldserver; @@ -123,6 +276,49 @@ public class ChunkProviderServer extends IChunkProvider { this.cacheChunk[0] = ichunkaccess; } + // Paper start - "real" get chunk if loaded + // Note: Partially copied from the getChunkAt method below + @Nullable + public Chunk getChunkAtIfCachedImmediately(int x, int z) { + long k = ChunkCoordIntPair.pair(x, z); + + // Note: Bypass cache since we need to check ticket level, and to make this MT-Safe + + PlayerChunk playerChunk = this.getChunk(k); + if (playerChunk == null) { + return null; + } + + return playerChunk.getFullChunkIfCached(); + } + + @Nullable + public Chunk getChunkAtIfLoadedImmediately(int x, int z) { + long k = ChunkCoordIntPair.pair(x, z); + + if (Thread.currentThread() == this.serverThread) { + return this.getChunkAtIfLoadedMainThread(x, z); + } + + Chunk 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 IChunkAccess getChunkAt(int i, int j, ChunkStatus chunkstatus, boolean flag) { @@ -405,10 +601,9 @@ public class ChunkProviderServer extends IChunkProvider { this.p = spawnercreature_d; this.world.getMethodProfiler().exit(); - List list = Lists.newArrayList(this.playerChunkMap.f()); - - Collections.shuffle(list); - list.forEach((playerchunk) -> { + //List list = Lists.newArrayList(this.playerChunkMap.f()); // Paper + //Collections.shuffle(list); // Paper + this.playerChunkMap.f().forEach((playerchunk) -> { // Paper - no... just no... Optional optional = ((Either) playerchunk.a().getNow(PlayerChunk.UNLOADED_CHUNK)).left(); if (optional.isPresent()) { diff --git a/src/main/java/net/minecraft/server/level/EntityPlayer.java b/src/main/java/net/minecraft/server/level/EntityPlayer.java index f1459d4d2b2592aea1fc39257255979f0a8c61e5..e818bf022b74cae34a512d8c98b47ec3e5c74b9a 100644 --- a/src/main/java/net/minecraft/server/level/EntityPlayer.java +++ b/src/main/java/net/minecraft/server/level/EntityPlayer.java @@ -223,6 +223,8 @@ public class EntityPlayer extends EntityHuman implements ICrafting { public Integer clientViewDistance; // CraftBukkit end + public final com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet cachedSingleHashSet; // Paper + public EntityPlayer(MinecraftServer minecraftserver, WorldServer worldserver, GameProfile gameprofile, PlayerInteractManager playerinteractmanager) { super(worldserver, worldserver.getSpawn(), worldserver.v(), gameprofile); this.spawnDimension = World.OVERWORLD; @@ -235,6 +237,8 @@ public class EntityPlayer extends EntityHuman implements ICrafting { this.c(worldserver); this.co = minecraftserver.a(this); + this.cachedSingleHashSet = new com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<>(this); // Paper + // CraftBukkit start this.displayName = this.getName(); this.canPickUpLoot = true; diff --git a/src/main/java/net/minecraft/server/level/PlayerChunk.java b/src/main/java/net/minecraft/server/level/PlayerChunk.java index 254953c1d8ad80173bcc9ed703bacaf32ca89c9a..7dea5e783ce2a1f8ddd2b3ab7a19e03a56c36ba1 100644 --- a/src/main/java/net/minecraft/server/level/PlayerChunk.java +++ b/src/main/java/net/minecraft/server/level/PlayerChunk.java @@ -46,9 +46,9 @@ public class PlayerChunk { private static final List CHUNK_STATUSES = ChunkStatus.a(); private static final PlayerChunk.State[] CHUNK_STATES = PlayerChunk.State.values(); private final AtomicReferenceArray>> statusFutures; - private volatile CompletableFuture> fullChunkFuture; - private volatile CompletableFuture> tickingFuture; - private volatile CompletableFuture> entityTickingFuture; + private volatile CompletableFuture> fullChunkFuture; private int fullChunkCreateCount; private volatile boolean isFullChunkReady; // Paper - cache chunk ticking stage + private volatile CompletableFuture> tickingFuture; private volatile boolean isTickingReady; // Paper - cache chunk ticking stage + private volatile CompletableFuture> entityTickingFuture; private volatile boolean isEntityTickingReady; // Paper - cache chunk ticking stage private CompletableFuture chunkSave; public int oldTicketLevel; private int ticketLevel; @@ -64,6 +64,8 @@ public class PlayerChunk { private boolean hasBeenLoaded; private boolean x; + private final PlayerChunkMap chunkMap; // Paper + public PlayerChunk(ChunkCoordIntPair chunkcoordintpair, int i, LightEngine lightengine, PlayerChunk.c playerchunk_c, PlayerChunk.d playerchunk_d) { this.statusFutures = new AtomicReferenceArray(PlayerChunk.CHUNK_STATUSES.size()); this.fullChunkFuture = PlayerChunk.UNLOADED_CHUNK_FUTURE; @@ -79,10 +81,49 @@ public class PlayerChunk { this.ticketLevel = this.oldTicketLevel; this.n = this.oldTicketLevel; this.a(i); + this.chunkMap = (PlayerChunkMap)playerchunk_d; // Paper + } + + // Paper start + @Nullable + public final Chunk getEntityTickingChunk() { + CompletableFuture> completablefuture = this.entityTickingFuture; + Either either = completablefuture.getNow(null); + + return either == null ? null : either.left().orElse(null); + } + + @Nullable + public final Chunk getTickingChunk() { + CompletableFuture> completablefuture = this.tickingFuture; + Either either = completablefuture.getNow(null); + + return either == null ? null : either.left().orElse(null); + } + + @Nullable + public final Chunk 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 Chunk getFullChunk() { + public final Chunk getFullChunk() { // Paper - final for inline if (!getChunkState(this.oldTicketLevel).isAtLeast(PlayerChunk.State.BORDER)) return null; // note: using oldTicketLevel for isLoaded checks return this.getFullChunkUnchecked(); } @@ -93,6 +134,14 @@ public class PlayerChunk { return (either == null) ? null : (Chunk) either.left().orElse(null); } // CraftBukkit end + // Paper start - "real" get full chunk immediately + public final Chunk getFullChunkIfCached() { + // Note: Copied from above without ticket level check + CompletableFuture> statusFuture = this.getStatusFutureUnchecked(ChunkStatus.FULL); + Either either = (Either) statusFuture.getNow(null); + return either == null ? null : (Chunk) either.left().orElse(null); + } + // Paper end public CompletableFuture> getStatusFutureUnchecked(ChunkStatus chunkstatus) { CompletableFuture> completablefuture = (CompletableFuture) this.statusFutures.get(chunkstatus.c()); @@ -104,20 +153,23 @@ public class PlayerChunk { return getChunkStatus(this.ticketLevel).b(chunkstatus) ? this.getStatusFutureUnchecked(chunkstatus) : PlayerChunk.UNLOADED_CHUNK_ACCESS_FUTURE; } - public CompletableFuture> a() { + public final CompletableFuture> getTickingFuture() { return this.a(); } // Paper - OBFHELPER + public final CompletableFuture> a() { // Paper - final for inline return this.tickingFuture; } - public CompletableFuture> b() { + public final CompletableFuture> getEntityTickingFuture() { return this.b(); } // Paper - OBFHELPER + public final CompletableFuture> b() { // Paper - final for inline return this.entityTickingFuture; } - public CompletableFuture> c() { + public final CompletableFuture> getFullChunkFuture() { return this.c(); } // Paper - OBFHELPER + public final CompletableFuture> c() { // Paper - final for inline return this.fullChunkFuture; } @Nullable - public Chunk getChunk() { + public final Chunk getChunk() { // Paper - final for inline CompletableFuture> completablefuture = this.a(); Either either = (Either) completablefuture.getNow(null); // CraftBukkit - decompile error @@ -142,7 +194,7 @@ public class PlayerChunk { return null; } - public CompletableFuture getChunkSave() { + public final CompletableFuture getChunkSave() { // Paper - final for inline return this.chunkSave; } @@ -283,11 +335,11 @@ public class PlayerChunk { }); } - public ChunkCoordIntPair i() { + public final ChunkCoordIntPair i() { // Paper - final for inline return this.location; } - public int getTicketLevel() { + public final int getTicketLevel() { // Paper - final for inline return this.ticketLevel; } @@ -358,13 +410,27 @@ public class PlayerChunk { this.hasBeenLoaded |= flag3; if (!flag2 && flag3) { - this.fullChunkFuture = playerchunkmap.b(this); + // Paper start - cache ticking ready status + int expectCreateCount = ++this.fullChunkCreateCount; + this.fullChunkFuture = playerchunkmap.b(this); this.fullChunkFuture.thenAccept((either) -> { + if (either.left().isPresent() && PlayerChunk.this.fullChunkCreateCount == expectCreateCount) { + // note: Here is a very good place to add callbacks to logic waiting on this. + Chunk fullChunk = either.left().get(); + PlayerChunk.this.isFullChunkReady = true; + fullChunk.playerChunk = PlayerChunk.this; + + + } + }); + // Paper end this.a(this.fullChunkFuture); } if (flag2 && !flag3) { completablefuture = this.fullChunkFuture; this.fullChunkFuture = PlayerChunk.UNLOADED_CHUNK_FUTURE; + ++this.fullChunkCreateCount; // Paper - cache ticking ready status + this.isFullChunkReady = false; // Paper - cache ticking ready status this.a(((CompletableFuture>) completablefuture).thenApply((either1) -> { // CraftBukkit - decompile error playerchunkmap.getClass(); return either1.ifLeft(playerchunkmap::a); @@ -375,12 +441,24 @@ public class PlayerChunk { boolean flag5 = playerchunk_state1.isAtLeast(PlayerChunk.State.TICKING); if (!flag4 && flag5) { - this.tickingFuture = playerchunkmap.a(this); + // Paper start - cache ticking ready status + this.tickingFuture = playerchunkmap.a(this); this.tickingFuture.thenAccept((either) -> { + if (either.left().isPresent()) { + // note: Here is a very good place to add callbacks to logic waiting on this. + Chunk tickingChunk = either.left().get(); + PlayerChunk.this.isTickingReady = true; + + + + + } + }); + // Paper end this.a(this.tickingFuture); } if (flag4 && !flag5) { - this.tickingFuture.complete(PlayerChunk.UNLOADED_CHUNK); + this.tickingFuture.complete(PlayerChunk.UNLOADED_CHUNK); this.isTickingReady = false; // Paper - cache chunk ticking stage this.tickingFuture = PlayerChunk.UNLOADED_CHUNK_FUTURE; } @@ -392,12 +470,24 @@ public class PlayerChunk { throw (IllegalStateException) SystemUtils.c((Throwable) (new IllegalStateException())); } - this.entityTickingFuture = playerchunkmap.b(this.location); + // Paper start - cache ticking ready status + this.entityTickingFuture = playerchunkmap.b(this.location); this.entityTickingFuture.thenAccept((either) -> { + if (either.left().isPresent()) { + // note: Here is a very good place to add callbacks to logic waiting on this. + Chunk entityTickingChunk = either.left().get(); + PlayerChunk.this.isEntityTickingReady = true; + + + + + } + }); + // Paper end this.a(this.entityTickingFuture); } if (flag6 && !flag7) { - this.entityTickingFuture.complete(PlayerChunk.UNLOADED_CHUNK); + this.entityTickingFuture.complete(PlayerChunk.UNLOADED_CHUNK); this.isEntityTickingReady = false; // Paper - cache chunk ticking stage this.entityTickingFuture = PlayerChunk.UNLOADED_CHUNK_FUTURE; } diff --git a/src/main/java/net/minecraft/server/level/PlayerChunkMap.java b/src/main/java/net/minecraft/server/level/PlayerChunkMap.java index 565aa1690d5427f5059ab117c4c15b0754e8830b..19856555793f742abb1178ede72dea5623f0e383 100644 --- a/src/main/java/net/minecraft/server/level/PlayerChunkMap.java +++ b/src/main/java/net/minecraft/server/level/PlayerChunkMap.java @@ -54,6 +54,7 @@ import net.minecraft.network.protocol.game.PacketPlayOutLightUpdate; import net.minecraft.network.protocol.game.PacketPlayOutMapChunk; import net.minecraft.network.protocol.game.PacketPlayOutMount; import net.minecraft.network.protocol.game.PacketPlayOutViewCentre; +import net.minecraft.server.MCUtil; import net.minecraft.server.level.progress.WorldLoadListener; import net.minecraft.util.CSVWriter; import net.minecraft.util.EntitySlice; @@ -146,6 +147,26 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { }; // CraftBukkit end + // Paper start - distance maps + private final com.destroystokyo.paper.util.misc.PooledLinkedHashSets pooledLinkedPlayerHashSets = new com.destroystokyo.paper.util.misc.PooledLinkedHashSets<>(); + + void addPlayerToDistanceMaps(EntityPlayer player) { + int chunkX = MCUtil.getChunkCoordinate(player.locX()); + int chunkZ = MCUtil.getChunkCoordinate(player.locZ()); + // Note: players need to be explicitly added to distance maps before they can be updated + } + + void removePlayerFromDistanceMaps(EntityPlayer player) { + + } + + void updateMaps(EntityPlayer player) { + int chunkX = MCUtil.getChunkCoordinate(player.locX()); + int chunkZ = MCUtil.getChunkCoordinate(player.locZ()); + // Note: players need to be explicitly added to distance maps before they can be updated + } + // Paper end + public PlayerChunkMap(WorldServer worldserver, Convertable.ConversionSession convertable_conversionsession, DataFixer datafixer, DefinedStructureManager definedstructuremanager, Executor executor, IAsyncTaskHandler iasynctaskhandler, ILightAccess ilightaccess, ChunkGenerator chunkgenerator, WorldLoadListener worldloadlistener, Supplier supplier, int i, boolean flag) { super(new File(convertable_conversionsession.a(worldserver.getDimensionKey()), "region"), datafixer, flag); this.visibleChunks = this.updatingChunks.clone(); @@ -235,6 +256,14 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { }; } + // 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, PlayerChunk.Failure>> a(ChunkCoordIntPair chunkcoordintpair, int i, IntFunction intfunction) { List>> list = Lists.newArrayList(); int j = chunkcoordintpair.x; @@ -953,6 +982,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { if (!flag1) { this.chunkDistanceManager.a(SectionPosition.a((Entity) entityplayer), entityplayer); } + this.addPlayerToDistanceMaps(entityplayer); // Paper - distance maps } else { SectionPosition sectionposition = entityplayer.O(); @@ -960,6 +990,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { if (!flag2) { this.chunkDistanceManager.b(sectionposition, entityplayer); } + this.removePlayerFromDistanceMaps(entityplayer); // Paper - distance maps } for (int k = i - this.viewDistance; k <= i + this.viewDistance; ++k) { @@ -1070,6 +1101,8 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { } } + this.updateMaps(entityplayer); // Paper - distance maps + } @Override diff --git a/src/main/java/net/minecraft/server/level/RegionLimitedWorldAccess.java b/src/main/java/net/minecraft/server/level/RegionLimitedWorldAccess.java index 91d9e6b554964e1f4dd67deea220a738f2715aee..04006caeeb42b523d986efc313828557854718d7 100644 --- a/src/main/java/net/minecraft/server/level/RegionLimitedWorldAccess.java +++ b/src/main/java/net/minecraft/server/level/RegionLimitedWorldAccess.java @@ -141,6 +141,26 @@ public class RegionLimitedWorldAccess implements GeneratorAccessSeed { return i >= this.n.x && i <= this.o.x && j >= this.n.z && j <= this.o.z; } + // Paper start - if loaded util + @Nullable + @Override + public IChunkAccess getChunkIfLoadedImmediately(int x, int z) { + return this.getChunkAt(x, z, ChunkStatus.FULL, false); + } + + @Override + public IBlockData getTypeIfLoaded(BlockPosition blockposition) { + IChunkAccess chunk = this.getChunkIfLoadedImmediately(blockposition.getX() >> 4, blockposition.getZ() >> 4); + return chunk == null ? null : chunk.getType(blockposition); + } + + @Override + public Fluid getFluidIfLoaded(BlockPosition blockposition) { + IChunkAccess chunk = this.getChunkIfLoadedImmediately(blockposition.getX() >> 4, blockposition.getZ() >> 4); + return chunk == null ? null : chunk.getFluid(blockposition); + } + // Paper end + @Override public IBlockData getType(BlockPosition blockposition) { return this.getChunkAt(blockposition.getX() >> 4, blockposition.getZ() >> 4).getType(blockposition); diff --git a/src/main/java/net/minecraft/server/level/TicketType.java b/src/main/java/net/minecraft/server/level/TicketType.java index 9cae7fed34df3ff81d75105b2fcbc4510f2a0e71..285a03b57431bd6a4d26bb84e916d2c6e1eb0213 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 = a("unknown", Comparator.comparingLong(ChunkCoordIntPair::pair), 1); public static final TicketType PLUGIN = a("plugin", (a, b) -> 0); // CraftBukkit public static final TicketType PLUGIN_TICKET = a("plugin_ticket", (plugin1, plugin2) -> plugin1.getClass().getName().compareTo(plugin2.getClass().getName())); // CraftBukkit + public static final TicketType FUTURE_AWAIT = a("future_await", Long::compareTo); // Paper public static TicketType a(String s, Comparator comparator) { return new TicketType<>(s, comparator, 0L); diff --git a/src/main/java/net/minecraft/server/level/WorldServer.java b/src/main/java/net/minecraft/server/level/WorldServer.java index 7f9a1d64d123ce54608497a0e0a37d161d8c9d3c..780e541b8d594a8a6dc3a8626a82218f2502a5c7 100644 --- a/src/main/java/net/minecraft/server/level/WorldServer.java +++ b/src/main/java/net/minecraft/server/level/WorldServer.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; @@ -169,7 +170,7 @@ public class WorldServer extends World implements GeneratorAccessSeed { private final Map entitiesByUUID = Maps.newHashMap(); private final Queue entitiesToAdd = Queues.newArrayDeque(); private final List players = Lists.newArrayList(); - private final ChunkProviderServer chunkProvider; + public final ChunkProviderServer chunkProvider; // Paper - public boolean tickingEntities; private final MinecraftServer server; public final WorldDataServer worldDataServer; // CraftBukkit - type @@ -1688,7 +1689,7 @@ public class WorldServer extends World implements GeneratorAccessSeed { ObjectIterator objectiterator = spawnercreature_d.b().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", ((EnumCreatureType) it_unimi_dsi_fastutil_objects_object2intmap_entry.getKey()).b(), it_unimi_dsi_fastutil_objects_object2intmap_entry.getIntValue())); } diff --git a/src/main/java/net/minecraft/server/network/PlayerConnection.java b/src/main/java/net/minecraft/server/network/PlayerConnection.java index 1a8f4ed15f7df5c734efc83c91444a42041af978..bc9321e77c6b2f9ac17821413fa1357da2f26b05 100644 --- a/src/main/java/net/minecraft/server/network/PlayerConnection.java +++ b/src/main/java/net/minecraft/server/network/PlayerConnection.java @@ -220,9 +220,9 @@ public class PlayerConnection implements PacketListenerPlayIn { private final MinecraftServer minecraftServer; public EntityPlayer player; private int e; - private long lastKeepAlive; - private boolean awaitingKeepAlive; - private long h; + private long lastKeepAlive; private void setLastPing(long lastPing) { this.lastKeepAlive = lastPing;}; private long getLastPing() { return this.lastKeepAlive;}; // Paper - OBFHELPER + private boolean awaitingKeepAlive; private void setPendingPing(boolean isPending) { this.awaitingKeepAlive = isPending;}; private boolean isPendingPing() { return this.awaitingKeepAlive;}; // Paper - OBFHELPER + private long h; private void setKeepAliveID(long keepAliveID) { this.h = keepAliveID;}; private long getKeepAliveID() {return this.h; }; // Paper - OBFHELPER // CraftBukkit start - multithreaded fields private volatile int chatThrottle; private static final AtomicIntegerFieldUpdater chatSpamField = AtomicIntegerFieldUpdater.newUpdater(PlayerConnection.class, "chatThrottle"); diff --git a/src/main/java/net/minecraft/util/DataBits.java b/src/main/java/net/minecraft/util/DataBits.java index 54974a941a334dc0c8e62ffb8ca094772888b8fa..0c0576c8730069fb5364d8383dec8ab7e698658d 100644 --- a/src/main/java/net/minecraft/util/DataBits.java +++ b/src/main/java/net/minecraft/util/DataBits.java @@ -84,6 +84,7 @@ public class DataBits { return (int) (k >> l & this.d); } + public final long[] getDataBits() { return this.a(); } // Paper - OBFHELPER public long[] a() { return this.b; } diff --git a/src/main/java/net/minecraft/util/thread/IAsyncTaskHandler.java b/src/main/java/net/minecraft/util/thread/IAsyncTaskHandler.java index 2b2c03ab62816f3d21ef953c4a45f55e3997cca6..e5641f2b41d89a57285fc072a48b951aa03a14a7 100644 --- a/src/main/java/net/minecraft/util/thread/IAsyncTaskHandler.java +++ b/src/main/java/net/minecraft/util/thread/IAsyncTaskHandler.java @@ -68,6 +68,15 @@ public abstract class IAsyncTaskHandler implements Mailbox entitytypes, World world) { super(entitytypes, world); } diff --git a/src/main/java/net/minecraft/world/entity/EntityInsentient.java b/src/main/java/net/minecraft/world/entity/EntityInsentient.java index b91bb7f562d5c43ec1d0d88ba417e43b07dbf0f3..dbfcdc3cc7c1dccf785f5e13634e84c5af088985 100644 --- a/src/main/java/net/minecraft/world/entity/EntityInsentient.java +++ b/src/main/java/net/minecraft/world/entity/EntityInsentient.java @@ -225,6 +225,7 @@ public abstract class EntityInsentient extends EntityLiving { return this.goalTarget; } + public org.bukkit.craftbukkit.entity.CraftMob getBukkitMob() { return (org.bukkit.craftbukkit.entity.CraftMob) super.getBukkitEntity(); } // Paper public void setGoalTarget(@Nullable EntityLiving entityliving) { // CraftBukkit start - fire event setGoalTarget(entityliving, EntityTargetEvent.TargetReason.UNKNOWN, true); diff --git a/src/main/java/net/minecraft/world/entity/EntityLiving.java b/src/main/java/net/minecraft/world/entity/EntityLiving.java index 97591280da3bfb30703a95fbe4e34b56b70544f4..c76ab1e6a54399eddae1ef2a595778385cd50026 100644 --- a/src/main/java/net/minecraft/world/entity/EntityLiving.java +++ b/src/main/java/net/minecraft/world/entity/EntityLiving.java @@ -231,6 +231,7 @@ public abstract class EntityLiving 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/EntityTypes.java b/src/main/java/net/minecraft/world/entity/EntityTypes.java index a32bc63ff1960bdb874d546ee42633063834da24..ac57ab9992e141c91cf48f033148ad78433b364c 100644 --- a/src/main/java/net/minecraft/world/entity/EntityTypes.java +++ b/src/main/java/net/minecraft/world/entity/EntityTypes.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 EntityTypes { return this.br.height; } - @Nullable - public T a(World world) { + public T create(World world) { return this.a(world); } // Paper - OBFHELPER + @Nullable public T a(World world) { // Paper - OBFHELPER return this.bf.create(this, world); } diff --git a/src/main/java/net/minecraft/world/entity/monster/EntityMonster.java b/src/main/java/net/minecraft/world/entity/monster/EntityMonster.java index acebee991eca1e19fc1094718dc40822b66756e1..c484e27650364b6537fe6b2e8e14de98382b86a3 100644 --- a/src/main/java/net/minecraft/world/entity/monster/EntityMonster.java +++ b/src/main/java/net/minecraft/world/entity/monster/EntityMonster.java @@ -27,6 +27,7 @@ import net.minecraft.world.level.WorldAccess; public abstract class EntityMonster extends EntityCreature implements IMonster { + public org.bukkit.craftbukkit.entity.CraftMonster getBukkitMonster() { return (org.bukkit.craftbukkit.entity.CraftMonster) super.getBukkitEntity(); } // Paper protected EntityMonster(EntityTypes entitytypes, World world) { super(entitytypes, world); this.f = 5; diff --git a/src/main/java/net/minecraft/world/entity/player/PlayerInventory.java b/src/main/java/net/minecraft/world/entity/player/PlayerInventory.java index 97db41acf626eec3e587964d0e73c370e5695bf0..2df3ae0b72ccb5f816d55fed15396ba5a1affb7f 100644 --- a/src/main/java/net/minecraft/world/entity/player/PlayerInventory.java +++ b/src/main/java/net/minecraft/world/entity/player/PlayerInventory.java @@ -38,7 +38,7 @@ public class PlayerInventory implements IInventory, INamableTileEntity { public final NonNullList items; public final NonNullList armor; public final NonNullList extraSlots; - private final List> f; + private final List> f; public final List> getComponents() { return f; } // Paper - OBFHELPER public int itemInHandIndex; public final EntityHuman 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 dee2714cd9fc930a1a13e97d752ab2df39cd31ed..4010152dccc93019f2e7f284d80b92bae0d91c34 100644 --- a/src/main/java/net/minecraft/world/item/ItemStack.java +++ b/src/main/java/net/minecraft/world/item/ItemStack.java @@ -103,7 +103,7 @@ public final class ItemStack { })).apply(instance, ItemStack::new); }); private static final Logger LOGGER = LogManager.getLogger(); - public static final ItemStack b = new ItemStack((Item) null); + public static final ItemStack b = new ItemStack((Item) null);public static final ItemStack NULL_ITEM = b; // Paper - OBFHELPER public static final DecimalFormat c = (DecimalFormat) SystemUtils.a((new DecimalFormat("#.##")), (decimalformat) -> { // CraftBukkit - decompile error decimalformat.setDecimalFormatSymbols(DecimalFormatSymbols.getInstance(Locale.ROOT)); }); @@ -660,6 +660,24 @@ public final class ItemStack { return this.tag != null ? this.tag.getList("Enchantments", 10) : new NBTTagList(); } + // 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.cloneItemStack()); + } + 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 NBTTagCompound nbttagcompound) { this.tag = nbttagcompound; if (this.getItem().usesDurability()) { @@ -758,6 +776,7 @@ public final class ItemStack { return this.tag != null && this.tag.hasKeyOfType("Enchantments", 9) ? !this.tag.getList("Enchantments", 10).isEmpty() : false; } + public void getOrCreateTagAndSet(String s, NBTBase nbtbase) { a(s, nbtbase);} // Paper - OBFHELPER public void a(String s, NBTBase nbtbase) { this.getOrCreateTag().set(s, nbtbase); } @@ -843,6 +862,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/PotionUtil.java b/src/main/java/net/minecraft/world/item/alchemy/PotionUtil.java index daad63e731008eddccd3f51418a2a9b2d587f77b..795bc60a73e1e628590803fd515ffb78302d4f97 100644 --- a/src/main/java/net/minecraft/world/item/alchemy/PotionUtil.java +++ b/src/main/java/net/minecraft/world/item/alchemy/PotionUtil.java @@ -121,6 +121,7 @@ public class PotionUtil { return nbttagcompound == null ? Potions.EMPTY : PotionRegistry.a(nbttagcompound.getString("Potion")); } + public static ItemStack addPotionToItemStack(ItemStack itemstack, PotionRegistry potionregistry) { return a(itemstack, potionregistry); } // Paper - OBFHELPER public static ItemStack a(ItemStack itemstack, PotionRegistry potionregistry) { MinecraftKey minecraftkey = IRegistry.POTION.getKey(potionregistry); diff --git a/src/main/java/net/minecraft/world/level/BlockAccessAir.java b/src/main/java/net/minecraft/world/level/BlockAccessAir.java index 5f8022745f709b6d542182d2ac94147aefdd3f0f..543b13c1e43135c044f834c2a6231e174536b623 100644 --- a/src/main/java/net/minecraft/world/level/BlockAccessAir.java +++ b/src/main/java/net/minecraft/world/level/BlockAccessAir.java @@ -20,6 +20,18 @@ public enum BlockAccessAir implements IBlockAccess { return null; } + // Paper start - If loaded util + @Override + public Fluid getFluidIfLoaded(BlockPosition blockposition) { + return this.getFluid(blockposition); + } + + @Override + public IBlockData getTypeIfLoaded(BlockPosition blockposition) { + return this.getType(blockposition); + } + // Paper end + @Override public IBlockData getType(BlockPosition blockposition) { return Blocks.AIR.getBlockData(); diff --git a/src/main/java/net/minecraft/world/level/ChunkCache.java b/src/main/java/net/minecraft/world/level/ChunkCache.java index 8541e87a34612e8bc86cf5c291164e091641d1af..7a760ef0264c9041c38bdfb8fd31333052c26139 100644 --- a/src/main/java/net/minecraft/world/level/ChunkCache.java +++ b/src/main/java/net/minecraft/world/level/ChunkCache.java @@ -4,6 +4,7 @@ import java.util.function.Predicate; import java.util.stream.Stream; import javax.annotation.Nullable; import net.minecraft.core.BlockPosition; +import net.minecraft.server.level.WorldServer; import net.minecraft.world.entity.Entity; import net.minecraft.world.level.block.Blocks; import net.minecraft.world.level.block.entity.TileEntity; @@ -23,7 +24,7 @@ public class ChunkCache implements IBlockAccess, ICollisionAccess { protected final int b; protected final IChunkAccess[][] c; protected boolean d; - protected final World e; + protected final World e; protected final World getWorld() { return e; } // Paper - OBFHELPER public ChunkCache(World world, BlockPosition blockposition, BlockPosition blockposition1) { this.e = world; @@ -42,7 +43,7 @@ public class ChunkCache implements IBlockAccess, ICollisionAccess { for (k = this.a; k <= i; ++k) { for (l = this.b; l <= j; ++l) { - this.c[k - this.a][l - this.b] = ichunkprovider.a(k, l); + this.c[k - this.a][l - this.b] = ((WorldServer)world).getChunkProvider().getChunkAtIfLoadedMainThreadNoCache(k, l); // Paper } } @@ -67,7 +68,7 @@ public class ChunkCache implements IBlockAccess, ICollisionAccess { int k = i - this.a; int l = j - this.b; - if (k >= 0 && k < this.c.length && l >= 0 && l < this.c[k].length) { + if (k >= 0 && k < this.c.length && l >= 0 && l < this.c[k].length) { // Paper - if this changes, update getChunkIfLoaded below IChunkAccess ichunkaccess = this.c[k][l]; return (IChunkAccess) (ichunkaccess != null ? ichunkaccess : new ChunkEmpty(this.e, new ChunkCoordIntPair(i, j))); @@ -86,6 +87,29 @@ public class ChunkCache implements IBlockAccess, ICollisionAccess { return this.a(i, j); } + // Paper start - if loaded util + private IChunkAccess getChunkIfLoaded(int x, int z) { + int k = x - this.a; + int l = z - this.b; + + if (k >= 0 && k < this.c.length && l >= 0 && l < this.c[k].length) { + return this.c[k][l]; + } + return null; + } + @Override + public Fluid getFluidIfLoaded(BlockPosition blockposition) { + IChunkAccess chunk = getChunkIfLoaded(blockposition.getX() >> 4, blockposition.getZ() >> 4); + return chunk == null ? null : chunk.getFluid(blockposition); + } + + @Override + public IBlockData getTypeIfLoaded(BlockPosition blockposition) { + IChunkAccess chunk = getChunkIfLoaded(blockposition.getX() >> 4, blockposition.getZ() >> 4); + return chunk == null ? null : chunk.getType(blockposition); + } + // Paper end + @Nullable @Override public TileEntity getTileEntity(BlockPosition blockposition) { diff --git a/src/main/java/net/minecraft/world/level/ChunkCoordIntPair.java b/src/main/java/net/minecraft/world/level/ChunkCoordIntPair.java index 14e55bf842e928d1e8e2137f9efdef0f7c336362..9a88791be443a5b18934e7d752aee6dcdb8aa38f 100644 --- a/src/main/java/net/minecraft/world/level/ChunkCoordIntPair.java +++ b/src/main/java/net/minecraft/world/level/ChunkCoordIntPair.java @@ -12,27 +12,32 @@ public class ChunkCoordIntPair { public static final long a = pair(1875016, 1875016); public final int x; public final int z; + public final long longKey; // Paper public ChunkCoordIntPair(int i, int j) { this.x = i; this.z = j; + this.longKey = pair(this.x, this.z); // Paper } public ChunkCoordIntPair(BlockPosition blockposition) { this.x = blockposition.getX() >> 4; this.z = blockposition.getZ() >> 4; + this.longKey = pair(this.x, this.z); // Paper } public ChunkCoordIntPair(long i) { this.x = (int) i; this.z = (int) (i >> 32); + this.longKey = pair(this.x, this.z); // Paper } public long pair() { - return pair(this.x, this.z); + return longKey; // Paper } - public static long pair(int i, int j) { + public static long pair(final BlockPosition pos) { return pair(pos.getX() >> 4, pos.getZ() >> 4); } // Paper - OBFHELPER + public static long pair(int i, int j) { return (long) i & 4294967295L | ((long) j & 4294967295L) << 32; } diff --git a/src/main/java/net/minecraft/world/level/IBlockAccess.java b/src/main/java/net/minecraft/world/level/IBlockAccess.java index 25e50b57f42dde156443480d73c6c9985df6f0c6..e799765ecfada1eec78beb71651e52ad355a30aa 100644 --- a/src/main/java/net/minecraft/world/level/IBlockAccess.java +++ b/src/main/java/net/minecraft/world/level/IBlockAccess.java @@ -8,9 +8,11 @@ import javax.annotation.Nullable; import net.minecraft.core.BlockPosition; import net.minecraft.core.EnumDirection; import net.minecraft.util.MathHelper; +import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.entity.TileEntity; import net.minecraft.world.level.block.state.IBlockData; import net.minecraft.world.level.material.Fluid; +import net.minecraft.world.level.material.Material; import net.minecraft.world.phys.AxisAlignedBB; import net.minecraft.world.phys.MovingObjectPositionBlock; import net.minecraft.world.phys.Vec3D; @@ -22,6 +24,19 @@ public interface IBlockAccess { TileEntity getTileEntity(BlockPosition blockposition); IBlockData getType(BlockPosition blockposition); + // Paper start - if loaded util + IBlockData getTypeIfLoaded(BlockPosition blockposition); + default Material getMaterialIfLoaded(BlockPosition blockposition) { + IBlockData type = this.getTypeIfLoaded(blockposition); + return type == null ? null : type.getMaterial(); + } + + default Block getBlockIfLoaded(BlockPosition blockposition) { + IBlockData type = this.getTypeIfLoaded(blockposition); + return type == null ? null : type.getBlock(); + } + Fluid getFluidIfLoaded(BlockPosition blockposition); + // Paper end Fluid getFluid(BlockPosition blockposition); diff --git a/src/main/java/net/minecraft/world/level/IWorldReader.java b/src/main/java/net/minecraft/world/level/IWorldReader.java index d3d33e77ce09d485552076c5ab6faf08a16d90db..5f12b290d59e0b5e843d644bf7f608a946ef02c0 100644 --- a/src/main/java/net/minecraft/world/level/IWorldReader.java +++ b/src/main/java/net/minecraft/world/level/IWorldReader.java @@ -18,6 +18,7 @@ import net.minecraft.world.phys.AxisAlignedBB; public interface IWorldReader extends IBlockLightAccess, ICollisionAccess, BiomeManager.Provider { + @Nullable IChunkAccess getChunkIfLoadedImmediately(int x, int z); // Paper - ifLoaded api (we need this since current impl blocks if the chunk is loading) @Nullable IChunkAccess getChunkAt(int i, int j, ChunkStatus chunkstatus, boolean flag); diff --git a/src/main/java/net/minecraft/world/level/World.java b/src/main/java/net/minecraft/world/level/World.java index f794113e7cc5809d1da0c85648fb7311fb633f40..0cbebc2095489c240fecd3fd32f5373be2a3d684 100644 --- a/src/main/java/net/minecraft/world/level/World.java +++ b/src/main/java/net/minecraft/world/level/World.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 World implements GeneratorAccess, AutoCloseable { return i < 0 || i >= 256; } - public Chunk getChunkAtWorldCoords(BlockPosition blockposition) { + public final Chunk getChunkAtWorldCoords(BlockPosition blockposition) { // Paper - help inline return this.getChunkAt(blockposition.getX() >> 4, blockposition.getZ() >> 4); } @Override - public Chunk getChunkAt(int i, int j) { - return (Chunk) this.getChunkAt(i, j, ChunkStatus.FULL); + public final Chunk getChunkAt(int i, int j) { // Paper - final to help inline + return (Chunk) this.getChunkAt(i, j, ChunkStatus.FULL, true); // Paper - avoid a method jump + } + + // Paper start - if loaded + @Nullable + @Override + public final IChunkAccess getChunkIfLoadedImmediately(int x, int z) { + return ((WorldServer)this).chunkProvider.getChunkAtIfLoadedImmediately(x, z); } @Override - public IChunkAccess getChunkAt(int i, int j, ChunkStatus chunkstatus, boolean flag) { + public final IBlockData getTypeIfLoaded(BlockPosition blockposition) { + // CraftBukkit start - tree generation + if (captureTreeGeneration) { + CraftBlockState previous = capturedBlockStates.get(blockposition); + if (previous != null) { + return previous.getHandle(); + } + } + // CraftBukkit end + if (!isValidLocation(blockposition)) { + return Blocks.AIR.getBlockData(); + } + IChunkAccess chunk = this.getChunkIfLoadedImmediately(blockposition.getX() >> 4, blockposition.getZ() >> 4); + + return chunk == null ? null : chunk.getType(blockposition); + } + + @Override + public Fluid getFluidIfLoaded(BlockPosition blockposition) { + IChunkAccess chunk = this.getChunkIfLoadedImmediately(blockposition.getX() >> 4, blockposition.getZ() >> 4); + + return chunk == null ? null : chunk.getFluid(blockposition); + } + // Paper end + + @Override + public final IChunkAccess getChunkAt(int i, int j, ChunkStatus chunkstatus, boolean flag) { // Paper - final for inline IChunkAccess ichunkaccess = this.getChunkProvider().getChunkAt(i, j, chunkstatus, flag); if (ichunkaccess == null && flag) { @@ -276,7 +310,7 @@ public abstract class World implements GeneratorAccess, AutoCloseable { } @Override - public boolean setTypeAndData(BlockPosition blockposition, IBlockData iblockdata, int i) { + public final boolean setTypeAndData(BlockPosition blockposition, IBlockData iblockdata, int i) { // Paper - final for inline return this.a(blockposition, iblockdata, i, 512); } @@ -422,8 +456,9 @@ public abstract class World implements GeneratorAccess, AutoCloseable { public void a(BlockPosition blockposition, IBlockData iblockdata, IBlockData iblockdata1) {} - @Override - public boolean a(BlockPosition blockposition, boolean flag) { + public boolean setAir(BlockPosition blockposition) { return this.a(blockposition, false); } // Paper - OBFHELPER + public boolean setAir(BlockPosition blockposition, boolean moved) { return this.a(blockposition, moved); } // Paper - OBFHELPER + @Override public boolean a(BlockPosition blockposition, boolean flag) { // Paper - OBFHELPER Fluid fluid = this.getFluid(blockposition); return this.setTypeAndData(blockposition, fluid.getBlockData(), 3 | (flag ? 64 : 0)); @@ -569,7 +604,7 @@ public abstract class World implements GeneratorAccess, AutoCloseable { if (isOutsideWorld(blockposition)) { return Blocks.VOID_AIR.getBlockData(); } else { - Chunk chunk = this.getChunkAt(blockposition.getX() >> 4, blockposition.getZ() >> 4); + Chunk chunk = (Chunk) this.getChunkProvider().getChunkAt(blockposition.getX() >> 4, blockposition.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.getType(blockposition); } diff --git a/src/main/java/net/minecraft/world/level/block/state/BlockBase.java b/src/main/java/net/minecraft/world/level/block/state/BlockBase.java index ef652d8f3eb7371c9ddfc3afe67fd1bd669a77c0..2902117fd2803741b053a04fda7f4414fb8593cb 100644 --- a/src/main/java/net/minecraft/world/level/block/state/BlockBase.java +++ b/src/main/java/net/minecraft/world/level/block/state/BlockBase.java @@ -688,6 +688,7 @@ public abstract class BlockBase { return this.a != null ? this.a.d : Block.a(this.getCollisionShape(iblockaccess, blockposition)); } + public final IBlockData getBlockData() { return p(); } // Paper - OBFHELPER protected abstract IBlockData p(); public boolean isRequiresSpecialTool() { 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 79f645ff1b6274bbdf5dc3f96a762b3b63397c82..2bb03f1cb9671a7754a68059219f783d4508eeb9 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.b(entity.locX(), entity.locZ()); } + public final VoxelShape asVoxelShape(){ return c();} // Paper - OBFHELPER public VoxelShape c() { return this.j.m(); } diff --git a/src/main/java/net/minecraft/world/level/chunk/Chunk.java b/src/main/java/net/minecraft/world/level/chunk/Chunk.java index 4561cf94fe1dce51abad0ac8635749ed87c8b307..b15200c2a3923bd8be2ee5e73fdadfeea3e3a8dc 100644 --- a/src/main/java/net/minecraft/world/level/chunk/Chunk.java +++ b/src/main/java/net/minecraft/world/level/chunk/Chunk.java @@ -60,7 +60,7 @@ public class Chunk implements IChunkAccess { private static final Logger LOGGER = LogManager.getLogger(); @Nullable - public static final ChunkSection a = null; + public static final ChunkSection a = null; public static final ChunkSection EMPTY_CHUNK_SECTION = a; // Paper - OBFHELPER private final ChunkSection[] sections; private BiomeStorage d; private final Map e; @@ -83,7 +83,7 @@ public class Chunk implements IChunkAccess { private Supplier u; @Nullable private Consumer v; - private final ChunkCoordIntPair loc; + private final ChunkCoordIntPair loc; public final long coordinateKey; public final int locX; public final int locZ; // Paper - cache coordinate key private volatile boolean x; public Chunk(World world, ChunkCoordIntPair chunkcoordintpair, BiomeStorage biomestorage) { @@ -100,7 +100,8 @@ public class Chunk implements IChunkAccess { this.n = new ShortList[16]; this.entitySlices = (List[]) (new List[16]); // Spigot this.world = (WorldServer) world; // CraftBukkit - type - this.loc = chunkcoordintpair; + this.locX = chunkcoordintpair.x; this.locZ = chunkcoordintpair.z; // Paper - reduce need for field look ups + this.loc = chunkcoordintpair; this.coordinateKey = ChunkCoordIntPair.pair(locX, locZ); // Paper - cache long key this.i = chunkconverter; HeightMap.Type[] aheightmap_type = HeightMap.Type.values(); int j = aheightmap_type.length; @@ -146,6 +147,110 @@ public class Chunk implements IChunkAccess { 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 PlayerChunk playerChunk; + + static final int NEIGHBOUR_CACHE_RADIUS = 3; + public static int getNeighbourCacheRadius() { + return NEIGHBOUR_CACHE_RADIUS; + } + + boolean loadedTicketLevel; + private long neighbourChunksLoadedBitset; + private final Chunk[] loadedNeighbourChunks = new Chunk[(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 Chunk 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 Chunk chunk) { + if (chunk == null) { + throw new IllegalArgumentException("Chunk must be non-null, neighbour: (" + relativeX + "," + relativeZ + "), chunk: " + this.loc); + } + 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 Chunk.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 Chunk(World world, ProtoChunk protochunk) { this(world, protochunk.getPos(), protochunk.getBiomeIndex(), protochunk.p(), protochunk.n(), protochunk.o(), protochunk.getInhabitedTime(), protochunk.getSections(), (Consumer) null); Iterator iterator = protochunk.y().iterator(); @@ -251,6 +356,18 @@ public class Chunk implements IChunkAccess { } } + // Paper start - If loaded util + @Override + public Fluid getFluidIfLoaded(BlockPosition blockposition) { + return this.getFluid(blockposition); + } + + @Override + public IBlockData getTypeIfLoaded(BlockPosition blockposition) { + return this.getType(blockposition); + } + // Paper end + @Override public Fluid getFluid(BlockPosition blockposition) { return this.a(blockposition.getX(), blockposition.getY(), blockposition.getZ()); @@ -391,6 +508,7 @@ public class Chunk implements IChunkAccess { entity.chunkX = this.loc.x; entity.chunkY = k; entity.chunkZ = this.loc.z; + this.entities.add(entity); // Paper - per chunk entity list this.entitySlices[k].add(entity); } @@ -414,6 +532,7 @@ public class Chunk implements IChunkAccess { } this.entitySlices[i].remove(entity); + this.entities.remove(entity); // Paper } @Override @@ -435,6 +554,7 @@ public class Chunk implements IChunkAccess { return this.a(blockposition, Chunk.EnumTileEntityState.CHECK); } + @Nullable public final TileEntity getTileEntityImmediately(BlockPosition pos) { return this.a(pos, EnumTileEntityState.IMMEDIATE); } // Paper - OBFHELPER @Nullable public TileEntity a(BlockPosition blockposition, Chunk.EnumTileEntityState chunk_enumtileentitystate) { // CraftBukkit start @@ -546,7 +666,25 @@ public class Chunk implements IChunkAccess { // CraftBukkit start public void loadCallback() { + // Paper start - neighbour cache + int chunkX = this.loc.x; + int chunkZ = this.loc.z; + ChunkProviderServer chunkProvider = ((WorldServer)this.world).getChunkProvider(); + for (int dx = -NEIGHBOUR_CACHE_RADIUS; dx <= NEIGHBOUR_CACHE_RADIUS; ++dx) { + for (int dz = -NEIGHBOUR_CACHE_RADIUS; dz <= NEIGHBOUR_CACHE_RADIUS; ++dz) { + Chunk 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.getServer(); + ((WorldServer)this.world).getChunkProvider().addLoadedChunk(this); // Paper if (server != null) { /* * If it's a new world, the first few chunks are generated inside @@ -585,6 +723,22 @@ public class Chunk implements IChunkAccess { server.getPluginManager().callEvent(unloadEvent); // note: saving can be prevented, but not forced if no saving is actually required this.mustNotSave = !unloadEvent.isSaveChunk(); + ((WorldServer)this.world).getChunkProvider().removeLoadedChunk(this); // Paper + // Paper start - neighbour cache + int chunkX = this.loc.x; + int chunkZ = this.loc.z; + ChunkProviderServer chunkProvider = ((WorldServer)this.world).getChunkProvider(); + for (int dx = -NEIGHBOUR_CACHE_RADIUS; dx <= NEIGHBOUR_CACHE_RADIUS; ++dx) { + for (int dz = -NEIGHBOUR_CACHE_RADIUS; dz <= NEIGHBOUR_CACHE_RADIUS; ++dz) { + Chunk 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/ChunkSection.java b/src/main/java/net/minecraft/world/level/chunk/ChunkSection.java index cf2dd6da5ce88aafdcc4db63af18eda9396a066a..a4e2eb1a753e8fcb48982d78fe80e505bce5c476 100644 --- a/src/main/java/net/minecraft/world/level/chunk/ChunkSection.java +++ b/src/main/java/net/minecraft/world/level/chunk/ChunkSection.java @@ -139,6 +139,7 @@ public class ChunkSection { return this.blockIds; } + public void writeChunkSection(PacketDataSerializer packetDataSerializer) { this.b(packetDataSerializer); } // Paper - OBFHELPER public void b(PacketDataSerializer packetdataserializer) { packetdataserializer.writeShort(this.nonEmptyBlockCount); this.blockIds.b(packetdataserializer); diff --git a/src/main/java/net/minecraft/world/level/chunk/DataPalette.java b/src/main/java/net/minecraft/world/level/chunk/DataPalette.java index f1dd62541187d007a69087f0279508b6b18d5166..44fe0ee179eebfa6c4c0403a7f06735d7da5c773 100644 --- a/src/main/java/net/minecraft/world/level/chunk/DataPalette.java +++ b/src/main/java/net/minecraft/world/level/chunk/DataPalette.java @@ -7,10 +7,12 @@ import net.minecraft.network.PacketDataSerializer; public interface DataPalette { + default int getOrCreateIdFor(T object) { return this.a(object); } // Paper - OBFHELPER int a(T t0); boolean a(Predicate predicate); + @Nullable default T getObject(int dataBits) { return this.a(dataBits); } // Paper - OBFHELPER @Nullable T a(int i); diff --git a/src/main/java/net/minecraft/world/level/chunk/DataPaletteBlock.java b/src/main/java/net/minecraft/world/level/chunk/DataPaletteBlock.java index fe441146757a4ac0562d5b493fb6430e33b9ee28..e397b871b846c3a90bc75d0e1cf0683b6a3d0ca9 100644 --- a/src/main/java/net/minecraft/world/level/chunk/DataPaletteBlock.java +++ b/src/main/java/net/minecraft/world/level/chunk/DataPaletteBlock.java @@ -19,7 +19,7 @@ import net.minecraft.util.MathHelper; public class DataPaletteBlock implements DataPaletteExpandable { - private final DataPalette b; + private final DataPalette b; private final DataPalette getDataPaletteGlobal() { return this.b; } // Paper - OBFHELPER private final DataPaletteExpandable c = (i, object) -> { return 0; }; @@ -27,9 +27,9 @@ public class DataPaletteBlock implements DataPaletteExpandable { private final Function e; private final Function f; private final T g; - protected DataBits a; - private DataPalette h; - private int i; + protected DataBits a; public final DataBits getDataBits() { return this.a; } // Paper - OBFHELPER + private DataPalette h; private DataPalette getDataPalette() { return this.h; } // Paper - OBFHELPER + private int i; private int getBitsPerObject() { return this.i; } // Paper - OBFHELPER private final ReentrantLock j = new ReentrantLock(); public void a() { @@ -64,6 +64,7 @@ public class DataPaletteBlock implements DataPaletteExpandable { return j << 8 | k << 4 | i; } + private void initialize(int bitsPerObject) { this.b(bitsPerObject); } // Paper - OBFHELPER private void b(int i) { if (i != this.i) { this.i = i; @@ -141,6 +142,7 @@ public class DataPaletteBlock implements DataPaletteExpandable { return t0 == null ? this.g : t0; } + public void writeDataPaletteBlock(PacketDataSerializer packetDataSerializer) { this.b(packetDataSerializer); } // Paper - OBFHELPER public void b(PacketDataSerializer packetdataserializer) { this.a(); packetdataserializer.writeByte(this.i); 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 4192d30ad2117a12a4058b48581d6cf93b50088c..7572ca53a5cca8ca5085d18c24048b85dda4daa9 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 IChunkAccess { } + // Paper start - If loaded util + @Override + public Fluid getFluidIfLoaded(BlockPosition blockposition) { + return this.getFluid(blockposition); + } + + @Override + public IBlockData getTypeIfLoaded(BlockPosition blockposition) { + return this.getType(blockposition); + } + // Paper end + @Override public IBlockData getType(BlockPosition blockposition) { int i = blockposition.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 859561a5dccba6548967b685b20e8fcfc296db2a..9ebf2806122a308f7655cdbee1f642cd80c9932c 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 b = new AtomicBoolean(); private final ThreadedMailbox c; - private final RegionFileCache d; + private final RegionFileCache d;public RegionFileCache getRegionFileCache() { return d; } // Paper - OBFHELPER private final Map e = 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 5e3bbc6d89794e23df7b60b13ae48c0f5136f20e..d1b761055c508a4b80436b50a832e00d0449d8cb 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.e.resolve(s); } + @Nullable public synchronized DataInputStream getReadStream(ChunkCoordIntPair chunkCoordIntPair) throws IOException { return a(chunkCoordIntPair);} // Paper - OBFHELPER @Nullable public synchronized DataInputStream a(ChunkCoordIntPair chunkcoordintpair) throws IOException { int i = this.getOffset(chunkcoordintpair); diff --git a/src/main/java/net/minecraft/world/phys/AxisAlignedBB.java b/src/main/java/net/minecraft/world/phys/AxisAlignedBB.java index 633a484cebc99f4a2f071b7f84b0b63d0ec3f985..3941dd33da4b5c09d0087143f1d8a2d76fc18792 100644 --- a/src/main/java/net/minecraft/world/phys/AxisAlignedBB.java +++ b/src/main/java/net/minecraft/world/phys/AxisAlignedBB.java @@ -194,10 +194,12 @@ public class AxisAlignedBB { return this.d(vec3d.x, vec3d.y, vec3d.z); } + public final boolean intersects(AxisAlignedBB axisalignedbb) { return this.c(axisalignedbb); } // Paper - OBFHELPER public boolean c(AxisAlignedBB axisalignedbb) { return this.a(axisalignedbb.minX, axisalignedbb.minY, axisalignedbb.minZ, axisalignedbb.maxX, axisalignedbb.maxY, axisalignedbb.maxZ); } + public final boolean intersects(double d0, double d1, double d2, double d3, double d4, double d5) { return a(d0, d1, d2, d3, d4, d5); } // Paper - OBFHELPER public boolean a(double d0, double d1, double d2, double d3, double d4, double d5) { return this.minX < d3 && this.maxX > d0 && this.minY < d4 && this.maxY > d1 && this.minZ < d5 && this.maxZ > d2; } @@ -210,6 +212,7 @@ public class AxisAlignedBB { return d0 >= this.minX && d0 < this.maxX && d1 >= this.minY && d1 < this.maxY && d2 >= this.minZ && d2 < this.maxZ; } + public final double getAverageSideLength(){return a();} // Paper - OBFHELPER public double a() { double d0 = this.b(); double d1 = this.c(); diff --git a/src/main/java/net/minecraft/world/phys/shapes/VoxelShapes.java b/src/main/java/net/minecraft/world/phys/shapes/VoxelShapes.java index 44d37272a337fee9606ebaa1b6f647c0fd392320..fdd9e37a8c90fc3311e515355af0a0593efbdacc 100644 --- a/src/main/java/net/minecraft/world/phys/shapes/VoxelShapes.java +++ b/src/main/java/net/minecraft/world/phys/shapes/VoxelShapes.java @@ -31,10 +31,12 @@ public final class VoxelShapes { public static final VoxelShape a = create(Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY); private static final VoxelShape c = new VoxelShapeArray(new VoxelShapeBitSet(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 a();} // Paper - OBFHELPER public static VoxelShape a() { return VoxelShapes.c; } + public static final VoxelShape fullCube() {return b();} // Paper - OBFHELPER public static VoxelShape b() { return VoxelShapes.b; } diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java index 52fa8a38545be43a31363d1d57e42471bbb0c499..275b943a59ef28c831a068987e111e84ebba3bb7 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 f4ea5809f48fda39e32738529b4ae8f74acadb90..b2b14eada44231a619622a2baef27abdb798aa47 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 GeneratorAccess { public Fluid getFluid(BlockPosition blockposition) { throw new UnsupportedOperationException("Not supported yet."); } + // Paper start - if loaded util + @javax.annotation.Nullable + @Override + public IChunkAccess getChunkIfLoadedImmediately(int x, int z) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public IBlockData getTypeIfLoaded(BlockPosition blockposition) { + throw new UnsupportedOperationException("Not supported yet."); + } + @Override + public Fluid getFluidIfLoaded(BlockPosition 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 724c6a47846f4266c858b783f68f162e0508d2fa..c04d912adf0da8f7a5b75dd2f58739a11ca31601 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 );