Archiviert
13
0
Dieses Repository wurde am 2024-12-25 archiviert. Du kannst Dateien ansehen und es klonen, aber nicht pushen oder Issues/Pull-Requests öffnen.
Paper-Old/Spigot-Server-Patches/0004-MC-Utils.patch
Spottedleaf f29c7ebd84
Improve async login (#3189)
Add helper functions to ChunkProviderServer to make this easier
for other uses

Co-authored-by: Spottedleaf <Spottedleaf@users.noreply.github.com>
2020-04-19 13:58:02 -04:00

4037 Zeilen
159 KiB
Diff

From 14de9d711e280f43a4c18b4f295eef6407d99f80 Mon Sep 17 00:00:00 2001
From: Aikar <aikar@aikar.co>
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 000000000..4029dc68c
--- /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 000000000..59868f37d
--- /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<Long2IntMap.Entry> 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<Long2IntMap.Entry> 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 000000000..07685b6bd
--- /dev/null
+++ b/src/main/java/com/destroystokyo/paper/util/map/QueuedChangesMapLong2Object.java
@@ -0,0 +1,172 @@
+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<V> {
+
+ protected static final Object REMOVED = new Object();
+
+ protected final Long2ObjectLinkedOpenHashMap<V> updatingMap;
+ protected final Long2ObjectLinkedOpenHashMap<V> visibleMap;
+ protected final Long2ObjectLinkedOpenHashMap<Object> 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 V getVisible(final long k) {
+ return this.visibleMap.get(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 Long2ObjectLinkedOpenHashMap<V> getVisibleMap() {
+ return this.visibleMap;
+ }
+
+ public Long2ObjectLinkedOpenHashMap<V> 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<V> getUpdatingValues() {
+ return this.updatingMap.values();
+ }
+
+ public List<V> 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<V> getVisibleValues() {
+ return this.visibleMap.values();
+ }
+
+ public List<V> getVisibleValuesCopy() {
+ return new ArrayList<>(this.visibleMap.values());
+ }
+
+ public boolean performUpdates() {
+ if (this.queuedChanges.isEmpty()) {
+ return false;
+ }
+
+ final ObjectBidirectionalIterator<Long2ObjectMap.Entry<Object>> iterator = this.queuedChanges.long2ObjectEntrySet().fastIterator();
+ while (iterator.hasNext()) {
+ final Long2ObjectMap.Entry<Object> 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<Long2ObjectMap.Entry<Object>> iterator = this.queuedChanges.long2ObjectEntrySet().fastIterator();
+
+ try {
+ this.updatingMapSeqLock.acquireWrite();
+
+ while (iterator.hasNext()) {
+ final Long2ObjectMap.Entry<Object> 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 000000000..4eac05778
--- /dev/null
+++ b/src/main/java/com/destroystokyo/paper/util/maplist/ChunkList.java
@@ -0,0 +1,129 @@
+package com.destroystokyo.paper.util.maplist;
+
+import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap;
+import net.minecraft.server.Chunk;
+import net.minecraft.server.MCUtil;
+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<Chunk> {
+
+ 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<Chunk> iterator() {
+ return new Iterator<Chunk>() {
+
+ 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 000000000..cdda74564
--- /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.server.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<Entity> {
+
+ 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<Entity> iterator() {
+ return new Iterator<Entity>() {
+
+ 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 000000000..84ef8d9ec
--- /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.server.ChunkSection;
+import net.minecraft.server.DataPaletteGlobal;
+import net.minecraft.server.IBlockData;
+import java.util.Arrays;
+
+/**
+ * @author Spottedleaf
+ */
+public final class IBlockDataList {
+
+ static final DataPaletteGlobal<IBlockData> 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 000000000..c3b936f54
--- /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 <Spottedleaf@users.noreply.github.com>
+ */
+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 000000000..f625da9f0
--- /dev/null
+++ b/src/main/java/com/destroystokyo/paper/util/misc/AreaMap.java
@@ -0,0 +1,409 @@
+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.ChunkCoordIntPair;
+import net.minecraft.server.MCUtil;
+import net.minecraft.server.MinecraftServer;
+import javax.annotation.Nullable;
+import java.util.Iterator;
+
+/** @author Spottedleaf */
+public abstract class AreaMap<E> {
+
+ /* Tested via https://gist.github.com/Spottedleaf/520419c6f41ef348fe9926ce674b7217 */
+
+ protected final Object2LongOpenHashMap<E> objectToLastCoordinate = new Object2LongOpenHashMap<>();
+ protected final Object2IntOpenHashMap<E> 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<PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<E>> areaMap = new Long2ObjectOpenHashMap<>(1024, 0.7f);
+ protected final PooledLinkedHashSets<E> pooledHashSets;
+
+ protected final ChangeCallback<E> addCallback;
+ protected final ChangeCallback<E> removeCallback;
+
+ public AreaMap() {
+ this(new PooledLinkedHashSets<>());
+ }
+
+ // let users define a "global" or "shared" pooled sets if they wish
+ public AreaMap(final PooledLinkedHashSets<E> pooledHashSets) {
+ this(pooledHashSets, null, null);
+ }
+
+ public AreaMap(final PooledLinkedHashSets<E> pooledHashSets, final ChangeCallback<E> addCallback, final ChangeCallback<E> removeCallback) {
+ this.pooledHashSets = pooledHashSets;
+ this.addCallback = addCallback;
+ this.removeCallback = removeCallback;
+ }
+
+ @Nullable
+ public final PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<E> getObjectsInRange(final long key) {
+ return this.areaMap.get(key);
+ }
+
+ @Nullable
+ public final PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<E> getObjectsInRange(final ChunkCoordIntPair chunkPos) {
+ return this.areaMap.get(MCUtil.getCoordinateKey(chunkPos));
+ }
+
+ @Nullable
+ public final PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<E> 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 update(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);
+ if (oldViewDistance == -1) {
+ this.objectToLastCoordinate.put(object, newPos);
+ this.addObject(object, chunkX, chunkZ, Integer.MIN_VALUE, Integer.MIN_VALUE, viewDistance);
+ this.addObjectCallback(object, chunkX, chunkZ, viewDistance);
+ } else {
+ 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);
+ }
+
+ // called after the distance map updates
+ protected void addObjectCallback(final E object, final int chunkX, final int chunkZ, final int viewDistance) {}
+
+ // called after the distance map updates
+ protected void updateObjectCallback(final E Object, final long oldPosition, final long newPosition, final int oldViewDistance, final int newViewDistance) {}
+
+ 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<E> 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<Long2ObjectLinkedOpenHashMap.Entry<PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<E>>> iterator = this.areaMap.long2ObjectEntrySet().fastIterator();
+ iterator.hasNext();) {
+
+ final Long2ObjectLinkedOpenHashMap.Entry<PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<E>> entry = iterator.next();
+ final long key = entry.getLongKey();
+ final PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<E> 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<E> empty = this.getEmptySetFor(object);
+ PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<E> current = this.areaMap.putIfAbsent(key, empty);
+
+ if (current != null) {
+ PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<E> 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<E> current = this.areaMap.get(key);
+
+ if (current == null) {
+ throw new IllegalStateException("Current map may not be null for " + object + ", (" + chunkX + "," + chunkZ + ")");
+ }
+
+ PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<E> 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<E> {
+
+ // 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<E> newState);
+
+ }
+}
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 000000000..3f86c1ad4
--- /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.ChunkCoordIntPair;
+import net.minecraft.server.MCUtil;
+
+/** @author Spottedleaf */
+public abstract class DistanceTrackingAreaMap<E> extends AreaMap<E> {
+
+ // 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<E> distanceChangeCallback;
+
+ public DistanceTrackingAreaMap() {
+ this(new PooledLinkedHashSets<>());
+ }
+
+ // let users define a "global" or "shared" pooled sets if they wish
+ public DistanceTrackingAreaMap(final PooledLinkedHashSets<E> pooledHashSets) {
+ this(pooledHashSets, null, null, null);
+ }
+
+ public DistanceTrackingAreaMap(final PooledLinkedHashSets<E> pooledHashSets, final ChangeCallback<E> addCallback, final ChangeCallback<E> removeCallback,
+ final DistanceChangeCallback<E> 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<E> 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<E> {
+
+ void accept(final int posX, final int posZ, final int oldNearestDistance, final int newNearestDistance,
+ final PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<E> 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 000000000..8a552a87a
--- /dev/null
+++ b/src/main/java/com/destroystokyo/paper/util/misc/PlayerAreaMap.java
@@ -0,0 +1,27 @@
+package com.destroystokyo.paper.util.misc;
+
+import net.minecraft.server.EntityPlayer;
+
+/**
+ * @author Spottedleaf
+ */
+public final class PlayerAreaMap extends AreaMap<EntityPlayer> {
+
+ public PlayerAreaMap() {
+ super();
+ }
+
+ public PlayerAreaMap(final PooledLinkedHashSets<EntityPlayer> pooledHashSets) {
+ super(pooledHashSets);
+ }
+
+ public PlayerAreaMap(final PooledLinkedHashSets<EntityPlayer> pooledHashSets, final ChangeCallback<EntityPlayer> addCallback,
+ final ChangeCallback<EntityPlayer> removeCallback) {
+ super(pooledHashSets, addCallback, removeCallback);
+ }
+
+ @Override
+ protected PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<EntityPlayer> 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 000000000..0292afc52
--- /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.EntityPlayer;
+
+public class PlayerDistanceTrackingAreaMap extends DistanceTrackingAreaMap<EntityPlayer> {
+
+ public PlayerDistanceTrackingAreaMap() {
+ super();
+ }
+
+ public PlayerDistanceTrackingAreaMap(final PooledLinkedHashSets<EntityPlayer> pooledHashSets) {
+ super(pooledHashSets);
+ }
+
+ public PlayerDistanceTrackingAreaMap(final PooledLinkedHashSets<EntityPlayer> pooledHashSets, final ChangeCallback<EntityPlayer> addCallback,
+ final ChangeCallback<EntityPlayer> removeCallback, final DistanceChangeCallback<EntityPlayer> distanceChangeCallback) {
+ super(pooledHashSets, addCallback, removeCallback, distanceChangeCallback);
+ }
+
+ @Override
+ protected PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<EntityPlayer> 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 000000000..e51104e65
--- /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<E> {
+
+ /* Tested via https://gist.github.com/Spottedleaf/a93bb7a8993d6ce142d3efc5932bf573 */
+
+ // we really want to avoid that equals() check as much as possible...
+ protected final Object2ObjectOpenHashMap<PooledObjectLinkedOpenHashSet<E>, PooledObjectLinkedOpenHashSet<E>> mapPool = new Object2ObjectOpenHashMap<>(128, 0.25f);
+
+ protected void decrementReferenceCount(final PooledObjectLinkedOpenHashSet<E> 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<E> findMapWith(final PooledObjectLinkedOpenHashSet<E> current, final E object) {
+ final PooledObjectLinkedOpenHashSet<E> cached = current.getAddCache(object);
+
+ if (cached != null) {
+ decrementReferenceCount(current);
+
+ if (cached.referenceCount == 0) {
+ // bring the map back from the dead
+ PooledObjectLinkedOpenHashSet<E> 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<E> 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<E> findMapWithout(final PooledObjectLinkedOpenHashSet<E> current, final E object) {
+ if (current.set.size() == 1) {
+ decrementReferenceCount(current);
+ return null;
+ }
+
+ final PooledObjectLinkedOpenHashSet<E> cached = current.getRemoveCache(object);
+
+ if (cached != null) {
+ decrementReferenceCount(current);
+
+ if (cached.referenceCount == 0) {
+ // bring the map back from the dead
+ PooledObjectLinkedOpenHashSet<E> 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<E> 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<E> extends ObjectOpenHashSet<E> {
+
+ public RawSetObjectLinkedOpenHashSet() {
+ super();
+ }
+
+ public RawSetObjectLinkedOpenHashSet(final int capacity) {
+ super(capacity);
+ }
+
+ public RawSetObjectLinkedOpenHashSet(final int capacity, final float loadFactor) {
+ super(capacity, loadFactor);
+ }
+
+ @Override
+ public RawSetObjectLinkedOpenHashSet<E> clone() {
+ return (RawSetObjectLinkedOpenHashSet<E>)super.clone();
+ }
+
+ public E[] getRawSet() {
+ return this.key;
+ }
+ }
+
+ public static final class PooledObjectLinkedOpenHashSet<E> {
+
+ private static final WeakReference NULL_REFERENCE = new WeakReference<>(null);
+
+ final RawSetObjectLinkedOpenHashSet<E> set;
+ int referenceCount; // -1 if special
+ int hash; // optimize hashcode
+
+ // add cache
+ WeakReference<E> lastAddObject = NULL_REFERENCE;
+ WeakReference<PooledObjectLinkedOpenHashSet<E>> lastAddMap = NULL_REFERENCE;
+
+ // remove cache
+ WeakReference<E> lastRemoveObject = NULL_REFERENCE;
+ WeakReference<PooledObjectLinkedOpenHashSet<E>> lastRemoveMap = NULL_REFERENCE;
+
+ public PooledObjectLinkedOpenHashSet(final PooledLinkedHashSets<E> pooledSets) {
+ this.set = new RawSetObjectLinkedOpenHashSet<>(2, 0.8f);
+ }
+
+ public PooledObjectLinkedOpenHashSet(final E single) {
+ this((PooledLinkedHashSets<E>)null);
+ this.referenceCount = -1;
+ this.add(single);
+ }
+
+ public PooledObjectLinkedOpenHashSet(final PooledObjectLinkedOpenHashSet<E> 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<E> 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<E> 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<E> map) {
+ this.lastAddObject = new WeakReference<>(element);
+ this.lastAddMap = new WeakReference<>(map);
+ }
+
+ void updateRemoveCache(final E element, final PooledObjectLinkedOpenHashSet<E> 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/set/OptimizedSmallEnumSet.java b/src/main/java/com/destroystokyo/paper/util/set/OptimizedSmallEnumSet.java
new file mode 100644
index 000000000..9df0006c1
--- /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 <Spottedleaf@users.noreply.github.com>
+ */
+public final class OptimizedSmallEnumSet<E extends Enum<E>> {
+
+ private final Class<E> enumClass;
+ private long backingSet;
+
+ public OptimizedSmallEnumSet(final Class<E> 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<E> 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<E> other) {
+ return (other.backingSet & this.backingSet) != 0;
+ }
+}
diff --git a/src/main/java/net/minecraft/server/AxisAlignedBB.java b/src/main/java/net/minecraft/server/AxisAlignedBB.java
index 4f60b931a..f427953a8 100644
--- a/src/main/java/net/minecraft/server/AxisAlignedBB.java
+++ b/src/main/java/net/minecraft/server/AxisAlignedBB.java
@@ -186,6 +186,7 @@ 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);
}
diff --git a/src/main/java/net/minecraft/server/BlockAccessAir.java b/src/main/java/net/minecraft/server/BlockAccessAir.java
index eff6ebcd3..30cbfc8ea 100644
--- a/src/main/java/net/minecraft/server/BlockAccessAir.java
+++ b/src/main/java/net/minecraft/server/BlockAccessAir.java
@@ -14,6 +14,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/server/BlockDataAbstract.java b/src/main/java/net/minecraft/server/BlockDataAbstract.java
index 1cf97cefc..2040f1834 100644
--- a/src/main/java/net/minecraft/server/BlockDataAbstract.java
+++ b/src/main/java/net/minecraft/server/BlockDataAbstract.java
@@ -78,6 +78,7 @@ public abstract class BlockDataAbstract<O, S> implements IBlockDataHolder<S> {
return Collections.unmodifiableCollection(this.d.keySet());
}
+ public final <T extends Comparable<T>> boolean hasProperty(IBlockState<T> iblockstate) { return this.b(iblockstate); } // Paper - OBFHELPER
public <T extends Comparable<T>> boolean b(IBlockState<T> iblockstate) {
return this.d.containsKey(iblockstate);
}
diff --git a/src/main/java/net/minecraft/server/BlockPosition.java b/src/main/java/net/minecraft/server/BlockPosition.java
index c88a62f6b..5dbd3e60f 100644
--- a/src/main/java/net/minecraft/server/BlockPosition.java
+++ b/src/main/java/net/minecraft/server/BlockPosition.java
@@ -120,6 +120,7 @@ public class BlockPosition extends BaseBlockPosition implements MinecraftSeriali
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 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);
}
@@ -210,6 +211,8 @@ public class BlockPosition extends BaseBlockPosition implements MinecraftSeriali
return new BlockPosition(this.getY() * baseblockposition.getZ() - this.getZ() * baseblockposition.getY(), this.getZ() * baseblockposition.getX() - this.getX() * baseblockposition.getZ(), this.getX() * baseblockposition.getY() - this.getY() * baseblockposition.getX());
}
+ @Deprecated // We'll replace this...
+ public BlockPosition asImmutable() { return immutableCopy(); } // Paper - OBFHELPER
public BlockPosition immutableCopy() {
return this;
}
@@ -402,6 +405,7 @@ public class BlockPosition extends BaseBlockPosition implements MinecraftSeriali
return this.d;
}
+ public 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.b = i;
this.c = j;
@@ -413,6 +417,7 @@ public class BlockPosition extends BaseBlockPosition implements MinecraftSeriali
return this.c(entity.locX(), entity.locY(), entity.locZ());
}
+ public 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));
}
@@ -441,14 +446,17 @@ public class BlockPosition extends BaseBlockPosition implements MinecraftSeriali
return this.d(this.b + i, this.c + j, this.d + k);
}
+ public final void setX(final int x) { this.o(x); } // Paper - OBFHELPER
public void o(int i) {
this.b = i;
}
+ public final void setY(final int y) { this.p(y); } // Paper - OBFHELPER
public void p(int i) {
this.c = i;
}
+ public final void setZ(final int z) { this.q(z); } // Paper - OBFHELPER
public void q(int i) {
this.d = i;
}
diff --git a/src/main/java/net/minecraft/server/Chunk.java b/src/main/java/net/minecraft/server/Chunk.java
index 55373cae0..c50fe1c24 100644
--- a/src/main/java/net/minecraft/server/Chunk.java
+++ b/src/main/java/net/minecraft/server/Chunk.java
@@ -25,7 +25,7 @@ import org.apache.logging.log4j.Logger;
public class Chunk implements IChunkAccess {
private static final Logger LOGGER = LogManager.getLogger();
- public static final ChunkSection a = null;
+ public static final ChunkSection a = null; public static final ChunkSection EMPTY_CHUNK_SECTION = Chunk.a; // Paper - OBFHELPER
private final ChunkSection[] sections;
private BiomeStorage d;
private final Map<BlockPosition, NBTTagCompound> e;
@@ -48,7 +48,7 @@ public class Chunk implements IChunkAccess {
private Supplier<PlayerChunk.State> u;
@Nullable
private Consumer<Chunk> v;
- private final ChunkCoordIntPair loc;
+ private final ChunkCoordIntPair loc; public final long coordinateKey; // Paper - cache coordinate key
private volatile boolean x;
public Chunk(World world, ChunkCoordIntPair chunkcoordintpair, BiomeStorage biomestorage) {
@@ -65,7 +65,7 @@ public class Chunk implements IChunkAccess {
this.n = new ShortList[16];
this.entitySlices = (List[]) (new List[16]); // Spigot
this.world = world;
- this.loc = chunkcoordintpair;
+ this.loc = chunkcoordintpair; this.coordinateKey = MCUtil.getCoordinateKey(chunkcoordintpair); // Paper - cache coordinate key
this.i = chunkconverter;
HeightMap.Type[] aheightmap_type = HeightMap.Type.values();
int j = aheightmap_type.length;
@@ -108,6 +108,93 @@ public class Chunk implements IChunkAccess {
public boolean needsDecoration;
// 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 int index = getNeighbourIndex(relativeX, relativeZ);
+ this.loadedNeighbourChunks[index] = chunk;
+ this.neighbourChunksLoadedBitset |= (1L << index);
+ }
+
+ public final void setNeighbourUnloaded(final int relativeX, final int relativeZ) {
+ final int index = getNeighbourIndex(relativeX, relativeZ);
+ this.loadedNeighbourChunks[index] = null;
+ this.neighbourChunksLoadedBitset &= ~(1L << index);
+ }
+
+ public final void resetNeighbours() {
+ this.neighbourChunksLoadedBitset = 0L;
+ java.util.Arrays.fill(this.loadedNeighbourChunks, null);
+ }
+
+ public final boolean areNeighboursLoaded(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 this.loadedTicketLevel;
+ }
+ 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 (this.neighbourChunksLoadedBitset & 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 (this.neighbourChunksLoadedBitset & 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 (this.neighbourChunksLoadedBitset & 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();
@@ -213,6 +300,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());
@@ -352,6 +451,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);
}
@@ -374,6 +474,7 @@ public class Chunk implements IChunkAccess {
}
this.entitySlices[i].remove(entity);
+ this.entities.remove(entity); // Paper
}
@Override
@@ -395,6 +496,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
@@ -506,7 +608,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
@@ -545,6 +665,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/server/ChunkCache.java b/src/main/java/net/minecraft/server/ChunkCache.java
index 11c4d23ba..53c15c1c0 100644
--- a/src/main/java/net/minecraft/server/ChunkCache.java
+++ b/src/main/java/net/minecraft/server/ChunkCache.java
@@ -8,7 +8,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;
@@ -71,6 +71,20 @@ public class ChunkCache implements IBlockAccess, ICollisionAccess {
return this.a(i, j);
}
+ // Paper start - if loaded util
+ @Override
+ public Fluid getFluidIfLoaded(BlockPosition blockposition) {
+ IChunkAccess chunk = getWorld().getChunkIfLoadedImmediately(blockposition.getX() >> 4, blockposition.getZ() >> 4);
+ return chunk == null ? null : chunk.getFluid(blockposition);
+ }
+
+ @Override
+ public IBlockData getTypeIfLoaded(BlockPosition blockposition) {
+ IChunkAccess chunk = getWorld().getChunkIfLoadedImmediately(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/server/ChunkCoordIntPair.java b/src/main/java/net/minecraft/server/ChunkCoordIntPair.java
index 260644bf0..f2a19acd8 100644
--- a/src/main/java/net/minecraft/server/ChunkCoordIntPair.java
+++ b/src/main/java/net/minecraft/server/ChunkCoordIntPair.java
@@ -31,7 +31,9 @@ public class ChunkCoordIntPair {
return pair(this.x, this.z);
}
- public static long pair(int i, int j) {
+ public static long asLong(final BlockPosition pos) { return pair(pos.getX() >> 4, pos.getZ() >> 4); } // Paper - OBFHELPER
+ public static long asLong(int x, int z) { return pair(x, z); } // 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/server/ChunkProviderServer.java b/src/main/java/net/minecraft/server/ChunkProviderServer.java
index 32c496fa8..940bcf6ab 100644
--- a/src/main/java/net/minecraft/server/ChunkProviderServer.java
+++ b/src/main/java/net/minecraft/server/ChunkProviderServer.java
@@ -23,7 +23,7 @@ public class ChunkProviderServer extends IChunkProvider {
private final ChunkMapDistance chunkMapDistance;
public final ChunkGenerator<?> chunkGenerator;
private final WorldServer world;
- private final Thread serverThread;
+ public final Thread serverThread; // Paper - private -> public
private final LightEngineThreaded lightEngine;
private final ChunkProviderServer.a serverThreadQueue;
public final PlayerChunkMap playerChunkMap;
@@ -35,6 +35,167 @@ public class ChunkProviderServer extends IChunkProvider {
private final ChunkStatus[] cacheStatus = new ChunkStatus[4];
private final IChunkAccess[] cacheChunk = new IChunkAccess[4];
+ // Paper start
+ final com.destroystokyo.paper.util.concurrent.WeakSeqLock loadedChunkMapSeqLock = new com.destroystokyo.paper.util.concurrent.WeakSeqLock();
+ final it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap<Chunk> loadedChunkMap = new it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap<>(8192, 0.5f);
+
+ private final Chunk[] lastLoadedChunks = new Chunk[4 * 4];
+ private final long[] lastLoadedChunkKeys = new long[4 * 4];
+
+ {
+ java.util.Arrays.fill(this.lastLoadedChunkKeys, MCUtil.INVALID_CHUNK_KEY);
+ }
+
+ private static int getCacheKey(int x, int z) {
+ return x & 3 | ((z & 3) << 2);
+ }
+
+ 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 = getCacheKey(chunk.getPos().x, chunk.getPos().z);
+
+ long cachedKey = this.lastLoadedChunkKeys[cacheKey];
+ if (cachedKey == chunk.coordinateKey) {
+ this.lastLoadedChunks[cacheKey] = chunk;
+ }
+ }
+
+ 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 = getCacheKey(chunk.getPos().x, chunk.getPos().z);
+
+ long cachedKey = this.lastLoadedChunkKeys[cacheKey];
+ if (cachedKey == chunk.coordinateKey) {
+ this.lastLoadedChunks[cacheKey] = null;
+ }
+ }
+
+ public Chunk getChunkAtIfLoadedMainThread(int x, int z) {
+ int cacheKey = getCacheKey(x, z);
+ long chunkKey = MCUtil.getCoordinateKey(x, z);
+
+ long cachedKey = this.lastLoadedChunkKeys[cacheKey];
+ if (cachedKey == chunkKey) {
+ return this.lastLoadedChunks[cacheKey];
+ }
+
+ Chunk ret = this.loadedChunkMap.get(chunkKey);
+
+ this.lastLoadedChunkKeys[cacheKey] = chunkKey;
+ this.lastLoadedChunks[cacheKey] = ret;
+
+ return ret;
+ }
+
+ public Chunk getChunkAtIfLoadedMainThreadNoCache(int x, int z) {
+ return this.loadedChunkMap.get(MCUtil.getCoordinateKey(x, z));
+ }
+
+ public 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<Chunk> 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<Chunk> 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<Chunk> 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<PlayerChunk, CompletableFuture<Either<Chunk, PlayerChunk.Failure>>> futureGet, java.util.function.Consumer<Chunk> onLoad) {
+ if (Thread.currentThread() != this.serverThread) {
+ throw new IllegalStateException();
+ }
+ ChunkCoordIntPair chunkPos = new ChunkCoordIntPair(x, z);
+ Long identifier = Long.valueOf(this.chunkFutureAwaitCounter++);
+ this.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<Either<Chunk, PlayerChunk.Failure>> future = futureGet.apply(chunk);
+
+ future.whenCompleteAsync((either, throwable) -> {
+ try {
+ if (throwable != null) {
+ if (throwable instanceof ThreadDeath) {
+ throw (ThreadDeath)throwable;
+ }
+ 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()) {
+ 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) {
+ onLoad.accept(either == null ? null : either.left().orElse(null)); // indicate failure to the callback.
+ }
+ } catch (Throwable thr) {
+ if (thr instanceof ThreadDeath) {
+ throw (ThreadDeath)thr;
+ }
+ 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.addTicketAtLevel(TicketType.UNKNOWN, chunkPos, ticketLevel, chunkPos);
+ ChunkProviderServer.this.removeTicketAtLevel(TicketType.FUTURE_AWAIT, chunkPos, ticketLevel, identifier);
+ }
+ }, this.serverThreadQueue);
+ }
+ // Paper end
+
+
public ChunkProviderServer(WorldServer worldserver, File file, DataFixer datafixer, DefinedStructureManager definedstructuremanager, Executor executor, ChunkGenerator<?> chunkgenerator, int i, WorldLoadListener worldloadlistener, Supplier<WorldPersistentData> supplier) {
this.world = worldserver;
this.serverThreadQueue = new ChunkProviderServer.a(worldserver);
@@ -77,6 +238,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) {
diff --git a/src/main/java/net/minecraft/server/DataBits.java b/src/main/java/net/minecraft/server/DataBits.java
index 7ca3a1d0c..2edd9b871 100644
--- a/src/main/java/net/minecraft/server/DataBits.java
+++ b/src/main/java/net/minecraft/server/DataBits.java
@@ -83,6 +83,7 @@ public class DataBits {
}
}
+ public long[] getDataBits() { return this.a(); } // Paper - OBFHELPER
public long[] a() {
return this.a;
}
diff --git a/src/main/java/net/minecraft/server/DataPalette.java b/src/main/java/net/minecraft/server/DataPalette.java
index 75ba69886..45403fbe3 100644
--- a/src/main/java/net/minecraft/server/DataPalette.java
+++ b/src/main/java/net/minecraft/server/DataPalette.java
@@ -4,10 +4,12 @@ import javax.annotation.Nullable;
public interface DataPalette<T> {
+ default int getOrCreateIdFor(T object) { return this.a(object); } // Paper - OBFHELPER
int a(T t0);
boolean b(T t0);
+ @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/server/DataPaletteBlock.java b/src/main/java/net/minecraft/server/DataPaletteBlock.java
index 774a8f543..d5f5a5187 100644
--- a/src/main/java/net/minecraft/server/DataPaletteBlock.java
+++ b/src/main/java/net/minecraft/server/DataPaletteBlock.java
@@ -11,7 +11,7 @@ import java.util.stream.Collectors;
public class DataPaletteBlock<T> implements DataPaletteExpandable<T> {
- private final DataPalette<T> b;
+ private final DataPalette<T> b; private final DataPalette<T> getDataPaletteGlobal() { return this.b; } // Paper - OBFHELPER
private final DataPaletteExpandable<T> c = (i, object) -> {
return 0;
};
@@ -19,9 +19,9 @@ public class DataPaletteBlock<T> implements DataPaletteExpandable<T> {
private final Function<NBTTagCompound, T> e;
private final Function<T, NBTTagCompound> f;
private final T g;
- protected DataBits a;
- private DataPalette<T> h;
- private int i;
+ protected DataBits a; protected DataBits getDataBits() { return this.a; } // Paper - OBFHELPER
+ private DataPalette<T> h; private DataPalette<T> 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() {
@@ -56,6 +56,7 @@ public class DataPaletteBlock<T> implements DataPaletteExpandable<T> {
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;
@@ -133,6 +134,7 @@ public class DataPaletteBlock<T> implements DataPaletteExpandable<T> {
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/server/EntityCreature.java b/src/main/java/net/minecraft/server/EntityCreature.java
index fe69161e5..b40c8d2f8 100644
--- a/src/main/java/net/minecraft/server/EntityCreature.java
+++ b/src/main/java/net/minecraft/server/EntityCreature.java
@@ -6,6 +6,8 @@ import org.bukkit.event.entity.EntityUnleashEvent;
public abstract class EntityCreature extends EntityInsentient {
+ public org.bukkit.craftbukkit.entity.CraftCreature getBukkitCreature() { return (org.bukkit.craftbukkit.entity.CraftCreature) super.getBukkitEntity(); } // Paper
+
protected EntityCreature(EntityTypes<? extends EntityCreature> entitytypes, World world) {
super(entitytypes, world);
}
diff --git a/src/main/java/net/minecraft/server/EntityInsentient.java b/src/main/java/net/minecraft/server/EntityInsentient.java
index bdfb17385..0b06fa2b6 100644
--- a/src/main/java/net/minecraft/server/EntityInsentient.java
+++ b/src/main/java/net/minecraft/server/EntityInsentient.java
@@ -146,6 +146,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/server/EntityLiving.java b/src/main/java/net/minecraft/server/EntityLiving.java
index 3b1bcf349..1f350e335 100644
--- a/src/main/java/net/minecraft/server/EntityLiving.java
+++ b/src/main/java/net/minecraft/server/EntityLiving.java
@@ -129,6 +129,7 @@ public abstract class EntityLiving extends Entity {
public org.bukkit.craftbukkit.attribute.CraftAttributeMap craftAttributes;
public boolean collides = true;
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/server/EntityMonster.java b/src/main/java/net/minecraft/server/EntityMonster.java
index 00c3b666d..e5322fbae 100644
--- a/src/main/java/net/minecraft/server/EntityMonster.java
+++ b/src/main/java/net/minecraft/server/EntityMonster.java
@@ -5,6 +5,7 @@ import java.util.function.Predicate;
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<? extends EntityMonster> entitytypes, World world) {
super(entitytypes, world);
this.f = 5;
diff --git a/src/main/java/net/minecraft/server/EntityPlayer.java b/src/main/java/net/minecraft/server/EntityPlayer.java
index ce4821092..57ce9bde6 100644
--- a/src/main/java/net/minecraft/server/EntityPlayer.java
+++ b/src/main/java/net/minecraft/server/EntityPlayer.java
@@ -87,6 +87,8 @@ public class EntityPlayer extends EntityHuman implements ICrafting {
public Integer clientViewDistance;
// CraftBukkit end
+ public final com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<EntityPlayer> cachedSingleHashSet; // Paper
+
public EntityPlayer(MinecraftServer minecraftserver, WorldServer worldserver, GameProfile gameprofile, PlayerInteractManager playerinteractmanager) {
super((World) worldserver, gameprofile);
playerinteractmanager.player = this;
@@ -98,6 +100,8 @@ public class EntityPlayer extends EntityHuman implements ICrafting {
this.H = 1.0F;
this.a(worldserver);
+ 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/EntityTypes.java b/src/main/java/net/minecraft/server/EntityTypes.java
index 29e776ca1..4328273b1 100644
--- a/src/main/java/net/minecraft/server/EntityTypes.java
+++ b/src/main/java/net/minecraft/server/EntityTypes.java
@@ -4,6 +4,7 @@ import com.mojang.datafixers.DataFixUtils;
import java.util.Collections;
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;
@@ -290,8 +291,8 @@ public class EntityTypes<T extends Entity> {
return this.bj.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.ba.create(this, world);
}
diff --git a/src/main/java/net/minecraft/server/IAsyncTaskHandler.java b/src/main/java/net/minecraft/server/IAsyncTaskHandler.java
index 1890c760f..7e5ece9d5 100644
--- a/src/main/java/net/minecraft/server/IAsyncTaskHandler.java
+++ b/src/main/java/net/minecraft/server/IAsyncTaskHandler.java
@@ -68,6 +68,15 @@ public abstract class IAsyncTaskHandler<R extends Runnable> implements Mailbox<R
}
+ // Paper start
+ public void scheduleOnMain(Runnable r0) {
+ // postToMainThread does not work the same as older versions of mc
+ // This method is actually used to create a TickTask, which can then be posted onto main
+ this.addTask(this.postToMainThread(r0));
+ }
+ // Paper end
+
+ public void addTask(R r0) { a(r0); }; // Paper - OBFHELPER
public void a(R r0) {
this.d.add(r0);
LockSupport.unpark(this.getThread());
diff --git a/src/main/java/net/minecraft/server/IBlockAccess.java b/src/main/java/net/minecraft/server/IBlockAccess.java
index 3b0877080..0dff02352 100644
--- a/src/main/java/net/minecraft/server/IBlockAccess.java
+++ b/src/main/java/net/minecraft/server/IBlockAccess.java
@@ -9,10 +9,24 @@ public interface IBlockAccess {
@Nullable
TileEntity getTileEntity(BlockPosition blockposition);
+ IBlockData getTypeIfLoaded(BlockPosition blockposition); // Paper - if loaded util
IBlockData getType(BlockPosition blockposition);
+ Fluid getFluidIfLoaded(BlockPosition blockposition); // Paper - if loaded util
Fluid getFluid(BlockPosition blockposition);
+ // Paper start - if loaded util
+ 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();
+ }
+ // Paper end
+
default int h(BlockPosition blockposition) {
return this.getType(blockposition).h();
}
diff --git a/src/main/java/net/minecraft/server/IOWorker.java b/src/main/java/net/minecraft/server/IOWorker.java
index c5658c077..b90baef0f 100644
--- a/src/main/java/net/minecraft/server/IOWorker.java
+++ b/src/main/java/net/minecraft/server/IOWorker.java
@@ -22,7 +22,7 @@ public class IOWorker implements AutoCloseable {
private final Thread b;
private final AtomicBoolean c = new AtomicBoolean();
private final Queue<Runnable> d = Queues.newConcurrentLinkedQueue();
- private final RegionFileCache e;
+ private final RegionFileCache e; public RegionFileCache getRegionFileCache() { return e; } // Paper - OBFHELPER
private final Map<ChunkCoordIntPair, IOWorker.a> f = Maps.newLinkedHashMap();
private boolean g = true;
private CompletableFuture<Void> h = new CompletableFuture();
diff --git a/src/main/java/net/minecraft/server/IWorldReader.java b/src/main/java/net/minecraft/server/IWorldReader.java
index ba315131e..cbe2aa4c0 100644
--- a/src/main/java/net/minecraft/server/IWorldReader.java
+++ b/src/main/java/net/minecraft/server/IWorldReader.java
@@ -4,6 +4,7 @@ import javax.annotation.Nullable;
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/server/ItemStack.java b/src/main/java/net/minecraft/server/ItemStack.java
index 75308712d..aa7501d36 100644
--- a/src/main/java/net/minecraft/server/ItemStack.java
+++ b/src/main/java/net/minecraft/server/ItemStack.java
@@ -37,10 +37,19 @@ import org.bukkit.event.world.StructureGrowEvent;
public final class ItemStack {
private static final Logger LOGGER = LogManager.getLogger();
- public static final ItemStack a = new ItemStack((Item) null);
+ public static final ItemStack a = new ItemStack((Item) null);public static final ItemStack NULL_ITEM = a; // Paper - OBFHELPER
public static final DecimalFormat b = H();
private int count;
private int e;
+ // Paper start
+ 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
@Deprecated
private Item item;
private NBTTagCompound tag;
@@ -593,6 +602,17 @@ 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);
+ }
+ // Paper end
public void setTag(@Nullable NBTTagCompound nbttagcompound) {
this.tag = nbttagcompound;
if (this.getItem().usesDurability()) {
@@ -685,6 +705,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);
}
@@ -761,6 +782,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/server/MCUtil.java b/src/main/java/net/minecraft/server/MCUtil.java
new file mode 100644
index 000000000..9fb9a96cc
--- /dev/null
+++ b/src/main/java/net/minecraft/server/MCUtil.java
@@ -0,0 +1,414 @@
+package net.minecraft.server;
+
+import com.destroystokyo.paper.block.TargetBlockInfo;
+import com.google.common.util.concurrent.ThreadFactoryBuilder;
+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.Executor;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.function.Supplier;
+
+public final class MCUtil {
+ private static final Executor asyncExecutor = Executors.newSingleThreadExecutor(new ThreadFactoryBuilder().setNameFormat("Paper Async Task Handler Thread - %1$d").build());
+
+ public static final long INVALID_CHUNK_KEY = getCoordinateKey(Integer.MAX_VALUE, Integer.MAX_VALUE);
+
+ public static void ensureTickThread(final String reason) {
+ if (MinecraftServer.getServer().serverThread != Thread.currentThread()) {
+ throw new IllegalStateException(reason);
+ }
+ }
+
+ public static List<ChunkCoordIntPair> getSpiralOutChunks(BlockPosition blockposition, int radius) {
+ List<ChunkCoordIntPair> 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 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 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 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 <T> void mergeSortedSets(final java.util.function.Consumer<T> consumer, final java.util.Comparator<? super T> comparator, final java.util.SortedSet<T>...sets) {
+ final it.unimi.dsi.fastutil.objects.ObjectRBTreeSet<T> all = new it.unimi.dsi.fastutil.objects.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<T> set : sets) {
+ if (set != null) {
+ all.addAll(set);
+ }
+ }
+ all.forEach(consumer);
+ }
+
+ private MCUtil() {}
+
+
+ public static boolean isMainThread() {
+ return MinecraftServer.getServer().isMainThread();
+ }
+
+ private static class DelayedRunnable implements Runnable {
+
+ private final int ticks;
+ private final Runnable run;
+
+ private DelayedRunnable(int ticks, Runnable run) {
+ this.ticks = ticks;
+ this.run = run;
+ }
+
+ @Override
+ public void run() {
+ if (ticks <= 0) {
+ run.run();
+ } else {
+ scheduleTask(ticks-1, run);
+ }
+ }
+ }
+
+ public static void scheduleTask(int ticks, Runnable runnable) {
+ // We use post to main instead of process queue as we don't want to process these mid tick if
+ // Someone uses processQueueWhileWaiting
+ MinecraftServer.getServer().scheduleOnMain(new DelayedRunnable(ticks, runnable));
+ }
+
+ public static void processQueue() {
+ Runnable runnable;
+ Queue<Runnable> processQueue = getProcessQueue();
+ while ((runnable = processQueue.poll()) != null) {
+ try {
+ runnable.run();
+ } catch (Exception e) {
+ MinecraftServer.LOGGER.error("Error executing task", e);
+ }
+ }
+ }
+ public static <T> T processQueueWhileWaiting(CompletableFuture <T> 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<Runnable> getProcessQueue() {
+ return MinecraftServer.getServer().processQueue;
+ }
+
+ public static <T> T ensureMain(Supplier<T> run) {
+ return ensureMain(null, run);
+ }
+ /**
+ * Ensures the target code is running on the main thread
+ * @param reason
+ * @param run
+ * @param <T>
+ * @return
+ */
+ public static <T> T ensureMain(String reason, Supplier<T> run) {
+ if (AsyncCatcher.enabled && Thread.currentThread() != MinecraftServer.getServer().serverThread) {
+ if (reason != null) {
+ new IllegalStateException("Asynchronous " + reason + "! Blocking thread until it returns ").printStackTrace();
+ }
+ Waitable<T> wait = new Waitable<T>() {
+ @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);
+ }
+
+ @Nullable
+ public static TileEntityHopper getHopper(World world, BlockPosition pos) {
+ Chunk chunk = world.getChunkIfLoaded(pos.getX() >> 4, pos.getZ() >> 4);
+ if (chunk != null && chunk.getType(new BlockPosition(pos.getX(), pos.getY(), pos.getZ())).getBlock() == Blocks.HOPPER) {
+ TileEntity tileEntity = chunk.getTileEntityImmediately(pos);
+ if (tileEntity instanceof TileEntityHopper) {
+ return (TileEntityHopper) tileEntity;
+ }
+ }
+ return null;
+ }
+
+ @Nonnull
+ public static World getNMSWorld(@Nonnull org.bukkit.World world) {
+ return ((CraftWorld) world).getHandle();
+ }
+
+ public static World 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/NBTTagCompound.java b/src/main/java/net/minecraft/server/NBTTagCompound.java
index e85b24a32..75604dbc6 100644
--- a/src/main/java/net/minecraft/server/NBTTagCompound.java
+++ b/src/main/java/net/minecraft/server/NBTTagCompound.java
@@ -60,7 +60,7 @@ public class NBTTagCompound implements NBTBase {
return "TAG_Compound";
}
};
- private final Map<String, NBTBase> map;
+ public final Map<String, NBTBase> map; // Paper
private NBTTagCompound(Map<String, NBTBase> map) {
this.map = map;
@@ -123,11 +123,15 @@ 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.setLong(s + "Most", uuid.getMostSignificantBits());
this.setLong(s + "Least", uuid.getLeastSignificantBits());
}
+
+ @Nullable public UUID getUUID(String prefix) { return a(prefix); } // Paper - OBFHELPER
+ @Nullable
public UUID a(String s) {
return new UUID(this.getLong(s + "Most"), this.getLong(s + "Least"));
}
diff --git a/src/main/java/net/minecraft/server/NetworkManager.java b/src/main/java/net/minecraft/server/NetworkManager.java
index 6700582e3..3ccf16636 100644
--- a/src/main/java/net/minecraft/server/NetworkManager.java
+++ b/src/main/java/net/minecraft/server/NetworkManager.java
@@ -159,6 +159,7 @@ public class NetworkManager extends SimpleChannelInboundHandler<Packet<?>> {
}
+ private void dispatchPacket(Packet<?> packet, @Nullable GenericFutureListener<? extends Future<? super Void>> genericFutureListener) { this.b(packet, genericFutureListener); } // Paper - OBFHELPER
private void b(Packet<?> packet, @Nullable GenericFutureListener<? extends Future<? super Void>> genericfuturelistener) {
EnumProtocol enumprotocol = EnumProtocol.a(packet);
EnumProtocol enumprotocol1 = (EnumProtocol) this.channel.attr(NetworkManager.c).get();
@@ -199,6 +200,7 @@ public class NetworkManager extends SimpleChannelInboundHandler<Packet<?>> {
}
+ private void sendPacketQueue() { this.o(); } // Paper - OBFHELPER
private void o() {
if (this.channel != null && this.channel.isOpen()) {
Queue queue = this.packetQueue;
@@ -327,9 +329,9 @@ public class NetworkManager extends SimpleChannelInboundHandler<Packet<?>> {
static class QueuedPacket {
- private final Packet<?> a;
+ private final Packet<?> a; private final Packet<?> getPacket() { return this.a; } // Paper - OBFHELPER
@Nullable
- private final GenericFutureListener<? extends Future<? super Void>> b;
+ private final GenericFutureListener<? extends Future<? super Void>> b; private final GenericFutureListener<? extends Future<? super Void>> getGenericFutureListener() { return this.b; } // Paper - OBFHELPER
public QueuedPacket(Packet<?> packet, @Nullable GenericFutureListener<? extends Future<? super Void>> genericfuturelistener) {
this.a = packet;
diff --git a/src/main/java/net/minecraft/server/PacketDataSerializer.java b/src/main/java/net/minecraft/server/PacketDataSerializer.java
index 81b6f4581..d9574a9ac 100644
--- a/src/main/java/net/minecraft/server/PacketDataSerializer.java
+++ b/src/main/java/net/minecraft/server/PacketDataSerializer.java
@@ -33,6 +33,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/server/PacketEncoder.java b/src/main/java/net/minecraft/server/PacketEncoder.java
index 90223deae..63c4dbd32 100644
--- a/src/main/java/net/minecraft/server/PacketEncoder.java
+++ b/src/main/java/net/minecraft/server/PacketEncoder.java
@@ -42,6 +42,7 @@ public class PacketEncoder extends MessageToByteEncoder<Packet<?>> {
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/server/PacketPlayOutMapChunk.java b/src/main/java/net/minecraft/server/PacketPlayOutMapChunk.java
index 677e3e5f6..3a1d0deb0 100644
--- a/src/main/java/net/minecraft/server/PacketPlayOutMapChunk.java
+++ b/src/main/java/net/minecraft/server/PacketPlayOutMapChunk.java
@@ -17,7 +17,7 @@ public class PacketPlayOutMapChunk implements Packet<PacketListenerPlayOut> {
private NBTTagCompound d;
@Nullable
private BiomeStorage e;
- private byte[] f;
+ private byte[] f; private byte[] getData() { return this.f; } // Paper - OBFHELPER
private List<NBTTagCompound> g;
private boolean h;
@@ -129,6 +129,7 @@ public class PacketPlayOutMapChunk implements Packet<PacketListenerPlayOut> {
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/PlayerChunk.java b/src/main/java/net/minecraft/server/PlayerChunk.java
index 5c5bf010d..6e9f402fb 100644
--- a/src/main/java/net/minecraft/server/PlayerChunk.java
+++ b/src/main/java/net/minecraft/server/PlayerChunk.java
@@ -19,9 +19,9 @@ public class PlayerChunk {
private static final List<ChunkStatus> CHUNK_STATUSES = ChunkStatus.a();
private static final PlayerChunk.State[] CHUNK_STATES = PlayerChunk.State.values();
private final AtomicReferenceArray<CompletableFuture<Either<IChunkAccess, PlayerChunk.Failure>>> statusFutures;
- private volatile CompletableFuture<Either<Chunk, PlayerChunk.Failure>> fullChunkFuture;
- private volatile CompletableFuture<Either<Chunk, PlayerChunk.Failure>> tickingFuture;
- private volatile CompletableFuture<Either<Chunk, PlayerChunk.Failure>> entityTickingFuture;
+ private volatile CompletableFuture<Either<Chunk, PlayerChunk.Failure>> fullChunkFuture; private int fullChunkCreateCount; private volatile boolean isFullChunkReady; // Paper - cache chunk ticking stage
+ private volatile CompletableFuture<Either<Chunk, PlayerChunk.Failure>> tickingFuture; private volatile boolean isTickingReady; // Paper - cache chunk ticking stage
+ private volatile CompletableFuture<Either<Chunk, PlayerChunk.Failure>> entityTickingFuture; private volatile boolean isEntityTickingReady; // Paper - cache chunk ticking stage
private CompletableFuture<IChunkAccess> chunkSave;
public int oldTicketLevel;
private int ticketLevel;
@@ -38,6 +38,8 @@ public class PlayerChunk {
public final PlayerChunk.d players;
private boolean hasBeenLoaded;
+ 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;
@@ -53,8 +55,47 @@ 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<Either<Chunk, PlayerChunk.Failure>> completablefuture = this.entityTickingFuture;
+ Either<Chunk, PlayerChunk.Failure> either = completablefuture.getNow(null);
+
+ return either == null ? null : either.left().orElse(null);
+ }
+
+ @Nullable
+ public final Chunk getTickingChunk() {
+ CompletableFuture<Either<Chunk, PlayerChunk.Failure>> completablefuture = this.tickingFuture;
+ Either<Chunk, PlayerChunk.Failure> either = completablefuture.getNow(null);
+
+ return either == null ? null : either.left().orElse(null);
+ }
+
+ @Nullable
+ public final Chunk getFullReadyChunk() {
+ CompletableFuture<Either<Chunk, PlayerChunk.Failure>> completablefuture = this.fullChunkFuture;
+ Either<Chunk, PlayerChunk.Failure> 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() {
if (!getChunkState(this.oldTicketLevel).isAtLeast(PlayerChunk.State.BORDER)) return null; // note: using oldTicketLevel for isLoaded checks
@@ -63,6 +104,14 @@ public class PlayerChunk {
return either == null ? null : (Chunk) either.left().orElse(null);
}
// CraftBukkit end
+ // Paper start - "real" get full chunk immediately
+ public Chunk getFullChunkIfCached() {
+ // Note: Copied from above without ticket level check
+ CompletableFuture<Either<IChunkAccess, PlayerChunk.Failure>> statusFuture = this.getStatusFutureUnchecked(ChunkStatus.FULL);
+ Either<IChunkAccess, PlayerChunk.Failure> either = (Either<IChunkAccess, PlayerChunk.Failure>) statusFuture.getNow(null);
+ return either == null ? null : (Chunk) either.left().orElse(null);
+ }
+ // Paper end
public CompletableFuture<Either<IChunkAccess, PlayerChunk.Failure>> getStatusFutureUnchecked(ChunkStatus chunkstatus) {
CompletableFuture<Either<IChunkAccess, PlayerChunk.Failure>> completablefuture = (CompletableFuture) this.statusFutures.get(chunkstatus.c());
@@ -74,14 +123,17 @@ public class PlayerChunk {
return getChunkStatus(this.ticketLevel).b(chunkstatus) ? this.getStatusFutureUnchecked(chunkstatus) : PlayerChunk.UNLOADED_CHUNK_ACCESS_FUTURE;
}
+ public final CompletableFuture<Either<Chunk, PlayerChunk.Failure>> getTickingFuture() { return this.a(); } // Paper - OBFHELPER
public CompletableFuture<Either<Chunk, PlayerChunk.Failure>> a() {
return this.tickingFuture;
}
+ public final CompletableFuture<Either<Chunk, PlayerChunk.Failure>> getEntityTickingFuture() { return this.b(); } // Paper - OBFHELPER
public CompletableFuture<Either<Chunk, PlayerChunk.Failure>> b() {
return this.entityTickingFuture;
}
+ public final CompletableFuture<Either<Chunk, PlayerChunk.Failure>> getFullChunkFuture() { return this.c(); } // Paper - OBFHELPER
public CompletableFuture<Either<Chunk, PlayerChunk.Failure>> c() {
return this.fullChunkFuture;
}
@@ -335,13 +387,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<Either<Chunk, PlayerChunk.Failure>>) completablefuture).thenApply((either1) -> { // CraftBukkit - decompile error
playerchunkmap.getClass();
return either1.ifLeft(playerchunkmap::a);
@@ -352,12 +418,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;
}
@@ -369,12 +447,24 @@ public class PlayerChunk {
throw (IllegalStateException) SystemUtils.c(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/PlayerChunkMap.java b/src/main/java/net/minecraft/server/PlayerChunkMap.java
index 7ad30548e..93d838ec2 100644
--- a/src/main/java/net/minecraft/server/PlayerChunkMap.java
+++ b/src/main/java/net/minecraft/server/PlayerChunkMap.java
@@ -99,6 +99,35 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
};
// CraftBukkit end
+ // Paper start - distance maps
+ private final com.destroystokyo.paper.util.misc.PooledLinkedHashSets<EntityPlayer> pooledLinkedPlayerHashSets = new com.destroystokyo.paper.util.misc.PooledLinkedHashSets<>();
+
+ void addPlayerToDistanceMaps(EntityPlayer player) {
+ this.updateMaps(player);
+
+
+
+ }
+
+ void removePlayerFromDistanceMaps(EntityPlayer player) {
+
+
+
+
+ }
+
+ void updateMaps(EntityPlayer player) {
+ int chunkX = MCUtil.getChunkCoordinate(player.locX());
+ int chunkZ = MCUtil.getChunkCoordinate(player.locZ());
+
+
+
+
+ }
+
+
+ // Paper end
+
public PlayerChunkMap(WorldServer worldserver, File file, DataFixer datafixer, DefinedStructureManager definedstructuremanager, Executor executor, IAsyncTaskHandler<Runnable> iasynctaskhandler, ILightAccess ilightaccess, ChunkGenerator<?> chunkgenerator, WorldLoadListener worldloadlistener, Supplier<WorldPersistentData> supplier, int i) {
super(new File(worldserver.getWorldProvider().getDimensionManager().a(file), "region"), datafixer);
this.visibleChunks = this.updatingChunks.clone();
@@ -984,6 +1013,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/PlayerConnection.java b/src/main/java/net/minecraft/server/PlayerConnection.java
index 0c496ee0a..6a681d694 100644
--- a/src/main/java/net/minecraft/server/PlayerConnection.java
+++ b/src/main/java/net/minecraft/server/PlayerConnection.java
@@ -67,9 +67,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/server/PlayerInventory.java b/src/main/java/net/minecraft/server/PlayerInventory.java
index 08768a3c8..d103cfaac 100644
--- a/src/main/java/net/minecraft/server/PlayerInventory.java
+++ b/src/main/java/net/minecraft/server/PlayerInventory.java
@@ -17,7 +17,7 @@ public class PlayerInventory implements IInventory, INamableTileEntity {
public final NonNullList<ItemStack> items;
public final NonNullList<ItemStack> armor;
public final NonNullList<ItemStack> extraSlots;
- private final List<NonNullList<ItemStack>> f;
+ private final List<NonNullList<ItemStack>> f;List<NonNullList<ItemStack>> getComponents() { return f; } // Paper - OBFHELPER
public int itemInHandIndex;
public final EntityHuman player;
private ItemStack carried;
diff --git a/src/main/java/net/minecraft/server/PotionUtil.java b/src/main/java/net/minecraft/server/PotionUtil.java
index b3824898d..bf4172be5 100644
--- a/src/main/java/net/minecraft/server/PotionUtil.java
+++ b/src/main/java/net/minecraft/server/PotionUtil.java
@@ -110,6 +110,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/server/ProtoChunk.java b/src/main/java/net/minecraft/server/ProtoChunk.java
index 6e65306a2..39339fa27 100644
--- a/src/main/java/net/minecraft/server/ProtoChunk.java
+++ b/src/main/java/net/minecraft/server/ProtoChunk.java
@@ -80,6 +80,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/server/RegionFile.java b/src/main/java/net/minecraft/server/RegionFile.java
index 7b6e0e86b..187c4e0f5 100644
--- a/src/main/java/net/minecraft/server/RegionFile.java
+++ b/src/main/java/net/minecraft/server/RegionFile.java
@@ -88,6 +88,7 @@ public class RegionFile implements AutoCloseable {
return this.d.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/server/RegionLimitedWorldAccess.java b/src/main/java/net/minecraft/server/RegionLimitedWorldAccess.java
index 8c123f265..9d0e8c2d4 100644
--- a/src/main/java/net/minecraft/server/RegionLimitedWorldAccess.java
+++ b/src/main/java/net/minecraft/server/RegionLimitedWorldAccess.java
@@ -108,6 +108,26 @@ public class RegionLimitedWorldAccess implements GeneratorAccess {
return i >= ichunkaccess.getPos().x && i <= ichunkaccess1.getPos().x && j >= ichunkaccess.getPos().z && j <= ichunkaccess1.getPos().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/RegistryBlockID.java b/src/main/java/net/minecraft/server/RegistryBlockID.java
index 4efcb8b59..60948afa4 100644
--- a/src/main/java/net/minecraft/server/RegistryBlockID.java
+++ b/src/main/java/net/minecraft/server/RegistryBlockID.java
@@ -57,6 +57,7 @@ public class RegistryBlockID<T> implements Registry<T> {
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/server/SystemUtils.java b/src/main/java/net/minecraft/server/SystemUtils.java
index 7b92ecfff..7e224ebef 100644
--- a/src/main/java/net/minecraft/server/SystemUtils.java
+++ b/src/main/java/net/minecraft/server/SystemUtils.java
@@ -58,7 +58,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/server/TicketType.java b/src/main/java/net/minecraft/server/TicketType.java
index f82db93f8..75ab9f185 100644
--- a/src/main/java/net/minecraft/server/TicketType.java
+++ b/src/main/java/net/minecraft/server/TicketType.java
@@ -21,6 +21,7 @@ public class TicketType<T> {
public static final TicketType<ChunkCoordIntPair> UNKNOWN = a("unknown", Comparator.comparingLong(ChunkCoordIntPair::pair), 1);
public static final TicketType<Unit> PLUGIN = a("plugin", (a, b) -> 0); // CraftBukkit
public static final TicketType<org.bukkit.plugin.Plugin> PLUGIN_TICKET = a("plugin_ticket", (plugin1, plugin2) -> plugin1.getClass().getName().compareTo(plugin2.getClass().getName())); // CraftBukkit
+ public static final TicketType<Long> FUTURE_AWAIT = a("future_await", Long::compareTo); // Paper
public static <T> TicketType<T> a(String s, Comparator<T> comparator) {
return new TicketType<>(s, comparator, 0L);
diff --git a/src/main/java/net/minecraft/server/World.java b/src/main/java/net/minecraft/server/World.java
index 2e1eabba1..2a4fa455f 100644
--- a/src/main/java/net/minecraft/server/World.java
+++ b/src/main/java/net/minecraft/server/World.java
@@ -22,6 +22,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
@@ -183,6 +184,39 @@ public abstract class World implements GeneratorAccess, AutoCloseable {
return (Chunk) this.getChunkAt(i, j, ChunkStatus.FULL);
}
+ // Paper start - if loaded
+ @Nullable
+ @Override
+ public IChunkAccess getChunkIfLoadedImmediately(int x, int z) {
+ return ((ChunkProviderServer)this.chunkProvider).getChunkAtIfLoadedImmediately(x, z);
+ }
+
+ @Override
+ public 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 IChunkAccess getChunkAt(int i, int j, ChunkStatus chunkstatus, boolean flag) {
IChunkAccess ichunkaccess = this.chunkProvider.getChunkAt(i, j, chunkstatus, flag);
@@ -336,8 +370,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));
diff --git a/src/main/java/net/minecraft/server/WorldServer.java b/src/main/java/net/minecraft/server/WorldServer.java
index d5014abc9..8a5ac6f69 100644
--- a/src/main/java/net/minecraft/server/WorldServer.java
+++ b/src/main/java/net/minecraft/server/WorldServer.java
@@ -973,6 +973,7 @@ public class WorldServer extends World {
}
this.registerEntity(entityplayer);
+ this.getChunkProvider().playerChunkMap.addPlayerToDistanceMaps(entityplayer); // Paper - distance maps
}
// CraftBukkit start
@@ -1115,6 +1116,7 @@ public class WorldServer extends World {
EntityPlayer entityplayer = (EntityPlayer) entity;
this.players.remove(entityplayer);
+ this.getChunkProvider().playerChunkMap.removePlayerFromDistanceMaps(entityplayer); // Paper - distance maps
}
this.getScoreboard().a(entity);
diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java
index e181df6f4..4a9132c70 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.server.ItemStack handle;
+ public net.minecraft.server.ItemStack getHandle() { return handle; } // Paper
/**
* Mirror
diff --git a/src/main/java/org/bukkit/craftbukkit/util/DummyGeneratorAccess.java b/src/main/java/org/bukkit/craftbukkit/util/DummyGeneratorAccess.java
index d8358a0f0..d0b813008 100644
--- a/src/main/java/org/bukkit/craftbukkit/util/DummyGeneratorAccess.java
+++ b/src/main/java/org/bukkit/craftbukkit/util/DummyGeneratorAccess.java
@@ -196,4 +196,22 @@ public class DummyGeneratorAccess implements GeneratorAccess {
public boolean a(BlockPosition blockposition, boolean flag, Entity entity) {
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
}
diff --git a/src/main/java/org/bukkit/craftbukkit/util/UnsafeList.java b/src/main/java/org/bukkit/craftbukkit/util/UnsafeList.java
index 1aec70a1f..f72c13bed 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<E> extends AbstractList<E> implements List<E>, 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;
--
2.26.0