4700 lines
190 KiB
Diff
4700 lines
190 KiB
Diff
From 0000000000000000000000000000000000000000 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 0000000000000000000000000000000000000000..4029dc68cf35d63aa70c4a76c35bf65a7fc6358f
|
|
--- /dev/null
|
|
+++ b/src/main/java/com/destroystokyo/paper/util/concurrent/WeakSeqLock.java
|
|
@@ -0,0 +1,68 @@
|
|
+package com.destroystokyo.paper.util.concurrent;
|
|
+
|
|
+import java.util.concurrent.atomic.AtomicLong;
|
|
+
|
|
+/**
|
|
+ * copied from https://github.com/Spottedleaf/ConcurrentUtil/blob/master/src/main/java/ca/spottedleaf/concurrentutil/lock/WeakSeqLock.java
|
|
+ * @author Spottedleaf
|
|
+ */
|
|
+public final class WeakSeqLock {
|
|
+ // TODO when the switch to J11 is made, nuke this class from orbit
|
|
+
|
|
+ protected final AtomicLong lock = new AtomicLong();
|
|
+
|
|
+ public WeakSeqLock() {
|
|
+ //VarHandle.storeStoreFence(); // warn: usages must be checked to ensure this behaviour isn't needed
|
|
+ }
|
|
+
|
|
+ public void acquireWrite() {
|
|
+ // must be release-type write
|
|
+ this.lock.lazySet(this.lock.get() + 1);
|
|
+ }
|
|
+
|
|
+ public boolean canRead(final long read) {
|
|
+ return (read & 1) == 0;
|
|
+ }
|
|
+
|
|
+ public boolean tryAcquireWrite() {
|
|
+ this.acquireWrite();
|
|
+ return true;
|
|
+ }
|
|
+
|
|
+ public void releaseWrite() {
|
|
+ // must be acquire-type write
|
|
+ final long lock = this.lock.get(); // volatile here acts as store-store
|
|
+ this.lock.lazySet(lock + 1);
|
|
+ }
|
|
+
|
|
+ public void abortWrite() {
|
|
+ // must be acquire-type write
|
|
+ final long lock = this.lock.get(); // volatile here acts as store-store
|
|
+ this.lock.lazySet(lock ^ 1);
|
|
+ }
|
|
+
|
|
+ public long acquireRead() {
|
|
+ int failures = 0;
|
|
+ long curr;
|
|
+
|
|
+ for (curr = this.lock.get(); !this.canRead(curr); curr = this.lock.get()) {
|
|
+ // without j11, our only backoff is the yield() call...
|
|
+
|
|
+ if (++failures > 5_000) { /* TODO determine a threshold */
|
|
+ Thread.yield();
|
|
+ }
|
|
+ /* Better waiting is beyond the scope of this lock; if it is needed the lock is being misused */
|
|
+ }
|
|
+
|
|
+ //VarHandle.loadLoadFence(); // volatile acts as the load-load barrier
|
|
+ return curr;
|
|
+ }
|
|
+
|
|
+ public boolean tryReleaseRead(final long read) {
|
|
+ return this.lock.get() == read; // volatile acts as the load-load barrier
|
|
+ }
|
|
+
|
|
+ public long getSequentialCounter() {
|
|
+ return this.lock.get();
|
|
+ }
|
|
+}
|
|
diff --git a/src/main/java/com/destroystokyo/paper/util/map/QueuedChangesMapLong2Int.java b/src/main/java/com/destroystokyo/paper/util/map/QueuedChangesMapLong2Int.java
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..59868f37d14bbc0ece0836095cdad148778995e6
|
|
--- /dev/null
|
|
+++ b/src/main/java/com/destroystokyo/paper/util/map/QueuedChangesMapLong2Int.java
|
|
@@ -0,0 +1,162 @@
|
|
+package com.destroystokyo.paper.util.map;
|
|
+
|
|
+import com.destroystokyo.paper.util.concurrent.WeakSeqLock;
|
|
+import it.unimi.dsi.fastutil.longs.Long2IntMap;
|
|
+import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap;
|
|
+import it.unimi.dsi.fastutil.longs.LongIterator;
|
|
+import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
|
|
+import it.unimi.dsi.fastutil.objects.ObjectIterator;
|
|
+
|
|
+/**
|
|
+ * @author Spottedleaf
|
|
+ */
|
|
+public class QueuedChangesMapLong2Int {
|
|
+
|
|
+ protected final Long2IntOpenHashMap updatingMap;
|
|
+ protected final Long2IntOpenHashMap visibleMap;
|
|
+ protected final Long2IntOpenHashMap queuedPuts;
|
|
+ protected final LongOpenHashSet queuedRemove;
|
|
+
|
|
+ protected int queuedDefaultReturnValue;
|
|
+
|
|
+ // we use a seqlock as writes are not common.
|
|
+ protected final WeakSeqLock updatingMapSeqLock = new WeakSeqLock();
|
|
+
|
|
+ public QueuedChangesMapLong2Int() {
|
|
+ this(16, 0.75f);
|
|
+ }
|
|
+
|
|
+ public QueuedChangesMapLong2Int(final int capacity, final float loadFactor) {
|
|
+ this.updatingMap = new Long2IntOpenHashMap(capacity, loadFactor);
|
|
+ this.visibleMap = new Long2IntOpenHashMap(capacity, loadFactor);
|
|
+ this.queuedPuts = new Long2IntOpenHashMap();
|
|
+ this.queuedRemove = new LongOpenHashSet();
|
|
+ }
|
|
+
|
|
+ public void queueDefaultReturnValue(final int dfl) {
|
|
+ this.queuedDefaultReturnValue = dfl;
|
|
+ this.updatingMap.defaultReturnValue(dfl);
|
|
+ }
|
|
+
|
|
+ public int queueUpdate(final long k, final int v) {
|
|
+ this.queuedRemove.remove(k);
|
|
+ this.queuedPuts.put(k, v);
|
|
+
|
|
+ return this.updatingMap.put(k, v);
|
|
+ }
|
|
+
|
|
+ public int queueRemove(final long k) {
|
|
+ this.queuedPuts.remove(k);
|
|
+ this.queuedRemove.add(k);
|
|
+
|
|
+ return this.updatingMap.remove(k);
|
|
+ }
|
|
+
|
|
+ public int getUpdating(final long k) {
|
|
+ return this.updatingMap.get(k);
|
|
+ }
|
|
+
|
|
+ public int getVisible(final long k) {
|
|
+ return this.visibleMap.get(k);
|
|
+ }
|
|
+
|
|
+ public int getVisibleAsync(final long k) {
|
|
+ long readlock;
|
|
+ int ret = 0;
|
|
+
|
|
+ do {
|
|
+ readlock = this.updatingMapSeqLock.acquireRead();
|
|
+ try {
|
|
+ ret = this.visibleMap.get(k);
|
|
+ } catch (final Throwable thr) {
|
|
+ if (thr instanceof ThreadDeath) {
|
|
+ throw (ThreadDeath)thr;
|
|
+ }
|
|
+ // ignore...
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ } while (!this.updatingMapSeqLock.tryReleaseRead(readlock));
|
|
+
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ public boolean performUpdates() {
|
|
+ this.updatingMapSeqLock.acquireWrite();
|
|
+ this.visibleMap.defaultReturnValue(this.queuedDefaultReturnValue);
|
|
+ this.updatingMapSeqLock.releaseWrite();
|
|
+
|
|
+ if (this.queuedPuts.isEmpty() && this.queuedRemove.isEmpty()) {
|
|
+ return false;
|
|
+ }
|
|
+
|
|
+ // update puts
|
|
+ final ObjectIterator<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 0000000000000000000000000000000000000000..7bab31a312463cc963d9621cdc543a281459bd32
|
|
--- /dev/null
|
|
+++ b/src/main/java/com/destroystokyo/paper/util/map/QueuedChangesMapLong2Object.java
|
|
@@ -0,0 +1,202 @@
|
|
+package com.destroystokyo.paper.util.map;
|
|
+
|
|
+import com.destroystokyo.paper.util.concurrent.WeakSeqLock;
|
|
+import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap;
|
|
+import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
|
|
+import it.unimi.dsi.fastutil.objects.ObjectBidirectionalIterator;
|
|
+import java.util.ArrayList;
|
|
+import java.util.Collection;
|
|
+import java.util.List;
|
|
+
|
|
+/**
|
|
+ * @author Spottedleaf
|
|
+ */
|
|
+public class QueuedChangesMapLong2Object<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 boolean updatingContainsKey(final long k) {
|
|
+ return this.updatingMap.containsKey(k);
|
|
+ }
|
|
+
|
|
+ public V getVisible(final long k) {
|
|
+ return this.visibleMap.get(k);
|
|
+ }
|
|
+
|
|
+ public boolean visibleContainsKey(final long k) {
|
|
+ return this.visibleMap.containsKey(k);
|
|
+ }
|
|
+
|
|
+ public V getVisibleAsync(final long k) {
|
|
+ long readlock;
|
|
+ V ret = null;
|
|
+
|
|
+ do {
|
|
+ readlock = this.updatingMapSeqLock.acquireRead();
|
|
+
|
|
+ try {
|
|
+ ret = this.visibleMap.get(k);
|
|
+ } catch (final Throwable thr) {
|
|
+ if (thr instanceof ThreadDeath) {
|
|
+ throw (ThreadDeath)thr;
|
|
+ }
|
|
+ // ignore...
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ } while (!this.updatingMapSeqLock.tryReleaseRead(readlock));
|
|
+
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ public boolean visibleContainsKeyAsync(final long k) {
|
|
+ long readlock;
|
|
+ boolean ret = false;
|
|
+
|
|
+ do {
|
|
+ readlock = this.updatingMapSeqLock.acquireRead();
|
|
+
|
|
+ try {
|
|
+ ret = this.visibleMap.containsKey(k);
|
|
+ } catch (final Throwable thr) {
|
|
+ if (thr instanceof ThreadDeath) {
|
|
+ throw (ThreadDeath)thr;
|
|
+ }
|
|
+ // ignore...
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ } while (!this.updatingMapSeqLock.tryReleaseRead(readlock));
|
|
+
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ public Long2ObjectLinkedOpenHashMap<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 0000000000000000000000000000000000000000..4eac0577862450e0e3299f5579f9ff6759b0256d
|
|
--- /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 0000000000000000000000000000000000000000..cdda74564ced196ae577a64782236c2bfe36e433
|
|
--- /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 0000000000000000000000000000000000000000..84ef8d9ecab4745a90504718f803110b9e2dbf65
|
|
--- /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 0000000000000000000000000000000000000000..c3b936f54b3fff418c265639ef223292ccc89356
|
|
--- /dev/null
|
|
+++ b/src/main/java/com/destroystokyo/paper/util/math/IntegerUtil.java
|
|
@@ -0,0 +1,230 @@
|
|
+package com.destroystokyo.paper.util.math;
|
|
+
|
|
+/**
|
|
+ * @author Spottedleaf <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 0000000000000000000000000000000000000000..1330df2c1d3c4f52dad0adeb169409eb412814ab
|
|
--- /dev/null
|
|
+++ b/src/main/java/com/destroystokyo/paper/util/misc/AreaMap.java
|
|
@@ -0,0 +1,453 @@
|
|
+package com.destroystokyo.paper.util.misc;
|
|
+
|
|
+import com.destroystokyo.paper.util.math.IntegerUtil;
|
|
+import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap;
|
|
+import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
|
|
+import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
|
|
+import it.unimi.dsi.fastutil.objects.Object2LongOpenHashMap;
|
|
+import net.minecraft.server.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;
|
|
+ protected final ChangeSourceCallback<E> changeSourceCallback;
|
|
+
|
|
+ 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, addCallback, removeCallback, null);
|
|
+ }
|
|
+ public AreaMap(final PooledLinkedHashSets<E> pooledHashSets, final ChangeCallback<E> addCallback, final ChangeCallback<E> removeCallback, final ChangeSourceCallback<E> changeSourceCallback) {
|
|
+ this.pooledHashSets = pooledHashSets;
|
|
+ this.addCallback = addCallback;
|
|
+ this.removeCallback = removeCallback;
|
|
+ this.changeSourceCallback = changeSourceCallback;
|
|
+ }
|
|
+
|
|
+ @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 addOrUpdate(final E object, final int chunkX, final int chunkZ, final int viewDistance) {
|
|
+ final int oldViewDistance = this.objectToViewDistance.put(object, viewDistance);
|
|
+ final long newPos = MCUtil.getCoordinateKey(chunkX, chunkZ);
|
|
+ final long oldPos = this.objectToLastCoordinate.put(object, newPos);
|
|
+
|
|
+ if (oldViewDistance == -1) {
|
|
+ this.addObject(object, chunkX, chunkZ, Integer.MIN_VALUE, Integer.MIN_VALUE, viewDistance);
|
|
+ this.addObjectCallback(object, chunkX, chunkZ, viewDistance);
|
|
+ } else {
|
|
+ this.updateObject(object, oldPos, newPos, oldViewDistance, viewDistance);
|
|
+ this.updateObjectCallback(object, oldPos, newPos, oldViewDistance, viewDistance);
|
|
+ }
|
|
+ //this.validate(object, viewDistance);
|
|
+ }
|
|
+
|
|
+ public final boolean update(final E object, final int chunkX, final int chunkZ, final int viewDistance) {
|
|
+ final int oldViewDistance = this.objectToViewDistance.replace(object, viewDistance);
|
|
+ if (oldViewDistance == -1) {
|
|
+ return false;
|
|
+ } else {
|
|
+ final long newPos = MCUtil.getCoordinateKey(chunkX, chunkZ);
|
|
+ final long oldPos = this.objectToLastCoordinate.put(object, newPos);
|
|
+ this.updateObject(object, oldPos, newPos, oldViewDistance, viewDistance);
|
|
+ this.updateObjectCallback(object, oldPos, newPos, oldViewDistance, viewDistance);
|
|
+ }
|
|
+ //this.validate(object, viewDistance);
|
|
+ return true;
|
|
+ }
|
|
+
|
|
+ // called after the distance map updates
|
|
+ protected void updateObjectCallback(final E Object, final long oldPosition, final long newPosition, final int oldViewDistance, final int newViewDistance) {
|
|
+ if (newPosition != oldPosition && this.changeSourceCallback != null) {
|
|
+ this.changeSourceCallback.accept(Object, oldPosition, newPosition);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public final boolean add(final E object, final int chunkX, final int chunkZ, final int viewDistance) {
|
|
+ final int oldViewDistance = this.objectToViewDistance.putIfAbsent(object, viewDistance);
|
|
+ if (oldViewDistance != -1) {
|
|
+ return false;
|
|
+ }
|
|
+
|
|
+ final long newPos = MCUtil.getCoordinateKey(chunkX, chunkZ);
|
|
+ this.objectToLastCoordinate.put(object, newPos);
|
|
+ this.addObject(object, chunkX, chunkZ, Integer.MIN_VALUE, Integer.MIN_VALUE, viewDistance);
|
|
+ this.addObjectCallback(object, chunkX, chunkZ, viewDistance);
|
|
+
|
|
+ //this.validate(object, viewDistance);
|
|
+
|
|
+ return true;
|
|
+ }
|
|
+
|
|
+ // called after the distance map updates
|
|
+ protected void addObjectCallback(final E object, final int chunkX, final int chunkZ, final int viewDistance) {}
|
|
+
|
|
+ public final boolean remove(final E object) {
|
|
+ final long position = this.objectToLastCoordinate.removeLong(object);
|
|
+ final int viewDistance = this.objectToViewDistance.removeInt(object);
|
|
+
|
|
+ if (viewDistance == -1) {
|
|
+ return false;
|
|
+ }
|
|
+
|
|
+ final int currentX = MCUtil.getCoordinateX(position);
|
|
+ final int currentZ = MCUtil.getCoordinateZ(position);
|
|
+
|
|
+ this.removeObject(object, currentX, currentZ, currentX, currentZ, viewDistance);
|
|
+ this.removeObjectCallback(object, currentX, currentZ, viewDistance);
|
|
+ //this.validate(object, -1);
|
|
+ return true;
|
|
+ }
|
|
+
|
|
+ // called after the distance map updates
|
|
+ protected void removeObjectCallback(final E object, final int chunkX, final int chunkZ, final int viewDistance) {}
|
|
+
|
|
+ protected abstract PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<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);
|
|
+
|
|
+ }
|
|
+
|
|
+ @FunctionalInterface
|
|
+ public static interface ChangeSourceCallback<E> {
|
|
+ void accept(final E object, final long prevPos, final long newPos);
|
|
+ }
|
|
+}
|
|
diff --git a/src/main/java/com/destroystokyo/paper/util/misc/DistanceTrackingAreaMap.java b/src/main/java/com/destroystokyo/paper/util/misc/DistanceTrackingAreaMap.java
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..3f86c1ad43782bdc56be6c0eca053311e51228ca
|
|
--- /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 0000000000000000000000000000000000000000..b1396f405d041fc3ca1f7ce1e0f884a3cfb8b96e
|
|
--- /dev/null
|
|
+++ b/src/main/java/com/destroystokyo/paper/util/misc/PlayerAreaMap.java
|
|
@@ -0,0 +1,32 @@
|
|
+package com.destroystokyo.paper.util.misc;
|
|
+
|
|
+import net.minecraft.server.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) {
|
|
+ this(pooledHashSets, addCallback, removeCallback, null);
|
|
+ }
|
|
+
|
|
+ public PlayerAreaMap(final PooledLinkedHashSets<EntityPlayer> pooledHashSets, final ChangeCallback<EntityPlayer> addCallback,
|
|
+ final ChangeCallback<EntityPlayer> removeCallback, final ChangeSourceCallback<EntityPlayer> changeSourceCallback) {
|
|
+ super(pooledHashSets, addCallback, removeCallback, changeSourceCallback);
|
|
+ }
|
|
+
|
|
+ @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 0000000000000000000000000000000000000000..0292afc5224326b767bd56d0718c215c184f2e0f
|
|
--- /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 0000000000000000000000000000000000000000..e51104e65a07b6ea7bbbcbb6afb066ef6401cc5b
|
|
--- /dev/null
|
|
+++ b/src/main/java/com/destroystokyo/paper/util/misc/PooledLinkedHashSets.java
|
|
@@ -0,0 +1,287 @@
|
|
+package com.destroystokyo.paper.util.misc;
|
|
+
|
|
+import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
|
|
+import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet;
|
|
+import java.lang.ref.WeakReference;
|
|
+
|
|
+/** @author Spottedleaf */
|
|
+public class PooledLinkedHashSets<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/pooled/PooledObjects.java b/src/main/java/com/destroystokyo/paper/util/pooled/PooledObjects.java
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..d0c77068e9a53d1b8bbad0f3f6b420d6bc85f8c8
|
|
--- /dev/null
|
|
+++ b/src/main/java/com/destroystokyo/paper/util/pooled/PooledObjects.java
|
|
@@ -0,0 +1,85 @@
|
|
+package com.destroystokyo.paper.util.pooled;
|
|
+
|
|
+import net.minecraft.server.MCUtil;
|
|
+import org.apache.commons.lang3.mutable.MutableInt;
|
|
+
|
|
+import java.util.ArrayDeque;
|
|
+import java.util.function.Consumer;
|
|
+import java.util.function.Supplier;
|
|
+
|
|
+public final class PooledObjects<E> {
|
|
+
|
|
+ /**
|
|
+ * Wrapper for an object that will be have a cleaner registered for it, and may be automatically returned to pool.
|
|
+ */
|
|
+ public class AutoReleased {
|
|
+ private final E object;
|
|
+ private final Runnable cleaner;
|
|
+
|
|
+ public AutoReleased(E object, Runnable cleaner) {
|
|
+ this.object = object;
|
|
+ this.cleaner = cleaner;
|
|
+ }
|
|
+
|
|
+ public final E getObject() {
|
|
+ return object;
|
|
+ }
|
|
+
|
|
+ public final Runnable getCleaner() {
|
|
+ return cleaner;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public static final PooledObjects<MutableInt> POOLED_MUTABLE_INTEGERS = new PooledObjects<>(MutableInt::new, 1024);
|
|
+
|
|
+ private final Supplier<E> creator;
|
|
+ private final Consumer<E> releaser;
|
|
+ private final int maxPoolSize;
|
|
+ private final ArrayDeque<E> queue;
|
|
+
|
|
+ public PooledObjects(final Supplier<E> creator, int maxPoolSize) {
|
|
+ this(creator, maxPoolSize, null);
|
|
+ }
|
|
+ public PooledObjects(final Supplier<E> creator, int maxPoolSize, Consumer<E> releaser) {
|
|
+ if (creator == null) {
|
|
+ throw new NullPointerException("Creator must not be null");
|
|
+ }
|
|
+ if (maxPoolSize <= 0) {
|
|
+ throw new IllegalArgumentException("Max pool size must be greater-than 0");
|
|
+ }
|
|
+
|
|
+ this.queue = new ArrayDeque<>(maxPoolSize);
|
|
+ this.maxPoolSize = maxPoolSize;
|
|
+ this.creator = creator;
|
|
+ this.releaser = releaser;
|
|
+ }
|
|
+
|
|
+ public AutoReleased acquireCleaner(Object holder) {
|
|
+ return acquireCleaner(holder, this::release);
|
|
+ }
|
|
+
|
|
+ public AutoReleased acquireCleaner(Object holder, Consumer<E> releaser) {
|
|
+ E resource = acquire();
|
|
+ Runnable cleaner = MCUtil.registerCleaner(holder, resource, releaser);
|
|
+ return new AutoReleased(resource, cleaner);
|
|
+ }
|
|
+
|
|
+ public final E acquire() {
|
|
+ E value;
|
|
+ synchronized (queue) {
|
|
+ value = this.queue.pollLast();
|
|
+ }
|
|
+ return value != null ? value : this.creator.get();
|
|
+ }
|
|
+
|
|
+ public final void release(final E value) {
|
|
+ if (this.releaser != null) {
|
|
+ this.releaser.accept(value);
|
|
+ }
|
|
+ synchronized (this.queue) {
|
|
+ if (queue.size() < this.maxPoolSize) {
|
|
+ this.queue.addLast(value);
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+}
|
|
diff --git a/src/main/java/com/destroystokyo/paper/util/set/OptimizedSmallEnumSet.java b/src/main/java/com/destroystokyo/paper/util/set/OptimizedSmallEnumSet.java
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..9df0006c1a283f77c4d01d9fce9062fc1c9bbb1f
|
|
--- /dev/null
|
|
+++ b/src/main/java/com/destroystokyo/paper/util/set/OptimizedSmallEnumSet.java
|
|
@@ -0,0 +1,67 @@
|
|
+package com.destroystokyo.paper.util.set;
|
|
+
|
|
+import java.util.Collection;
|
|
+
|
|
+/**
|
|
+ * @author Spottedleaf <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 277d7a124e9a21803fe4d5a66fc0b311df2cfba7..02c09f39848399a86d46bd17569b4f01a7b5ab1f 100644
|
|
--- a/src/main/java/net/minecraft/server/AxisAlignedBB.java
|
|
+++ b/src/main/java/net/minecraft/server/AxisAlignedBB.java
|
|
@@ -190,10 +190,12 @@ public class AxisAlignedBB {
|
|
return this.d(vec3d.x, vec3d.y, vec3d.z);
|
|
}
|
|
|
|
+ public final boolean intersects(AxisAlignedBB axisalignedbb) { return this.c(axisalignedbb); } // Paper - OBFHELPER
|
|
public boolean c(AxisAlignedBB axisalignedbb) {
|
|
return this.a(axisalignedbb.minX, axisalignedbb.minY, axisalignedbb.minZ, axisalignedbb.maxX, axisalignedbb.maxY, axisalignedbb.maxZ);
|
|
}
|
|
|
|
+ public final boolean intersects(double d0, double d1, double d2, double d3, double d4, double d5) { return a(d0, d1, d2, d3, d4, d5); } // Paper - OBFHELPER
|
|
public boolean a(double d0, double d1, double d2, double d3, double d4, double d5) {
|
|
return this.minX < d3 && this.maxX > d0 && this.minY < d4 && this.maxY > d1 && this.minZ < d5 && this.maxZ > d2;
|
|
}
|
|
@@ -206,6 +208,7 @@ public class AxisAlignedBB {
|
|
return d0 >= this.minX && d0 < this.maxX && d1 >= this.minY && d1 < this.maxY && d2 >= this.minZ && d2 < this.maxZ;
|
|
}
|
|
|
|
+ public final double getAverageSideLength(){return a();} // Paper - OBFHELPER
|
|
public double a() {
|
|
double d0 = this.b();
|
|
double d1 = this.c();
|
|
diff --git a/src/main/java/net/minecraft/server/BaseBlockPosition.java b/src/main/java/net/minecraft/server/BaseBlockPosition.java
|
|
index ff4b23927bb0e0ac8221d71fe2543cbee54f913a..ee28d0335418a0053f8448ab5e12ebba5a9a3b2d 100644
|
|
--- a/src/main/java/net/minecraft/server/BaseBlockPosition.java
|
|
+++ b/src/main/java/net/minecraft/server/BaseBlockPosition.java
|
|
@@ -98,6 +98,7 @@ public class BaseBlockPosition implements Comparable<BaseBlockPosition> {
|
|
return this.distanceSquared(iposition.getX(), iposition.getY(), iposition.getZ(), true) < d0 * d0;
|
|
}
|
|
|
|
+ public final double distanceSquared(BaseBlockPosition baseblockposition) { return j(baseblockposition); } // Paper - OBFHELPER
|
|
public double j(BaseBlockPosition baseblockposition) {
|
|
return this.distanceSquared((double) baseblockposition.getX(), (double) baseblockposition.getY(), (double) baseblockposition.getZ(), true);
|
|
}
|
|
diff --git a/src/main/java/net/minecraft/server/BlockAccessAir.java b/src/main/java/net/minecraft/server/BlockAccessAir.java
|
|
index eff6ebcd30b538cbaedaa031a46a59ea956253ba..30cbfc8eac20910aa55951e3dce63862f5a43c37 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/BlockBase.java b/src/main/java/net/minecraft/server/BlockBase.java
|
|
index 47324feca49786b49563d3d0e854e74ad27c190b..26d446077bb563ca3c5bb0339695b3364a3e41bf 100644
|
|
--- a/src/main/java/net/minecraft/server/BlockBase.java
|
|
+++ b/src/main/java/net/minecraft/server/BlockBase.java
|
|
@@ -632,6 +632,7 @@ public abstract class BlockBase {
|
|
return this.a != null ? this.a.e : Block.a(this.getCollisionShape(iblockaccess, blockposition));
|
|
}
|
|
|
|
+ public IBlockData getBlockData() { return p(); } // Paper - OBFHELPER
|
|
protected abstract IBlockData p();
|
|
|
|
public boolean isAlwaysDestroyable() {
|
|
diff --git a/src/main/java/net/minecraft/server/BlockPosition.java b/src/main/java/net/minecraft/server/BlockPosition.java
|
|
index 8eb94bcb605f882c9ce096fc758df5e3ae3ab28d..b61651fcd6605cd8638ce14f15e41b878add3f1c 100644
|
|
--- a/src/main/java/net/minecraft/server/BlockPosition.java
|
|
+++ b/src/main/java/net/minecraft/server/BlockPosition.java
|
|
@@ -98,6 +98,7 @@ public class BlockPosition extends BaseBlockPosition {
|
|
return d0 == 0.0D && d1 == 0.0D && d2 == 0.0D ? this : new BlockPosition((double) this.getX() + d0, (double) this.getY() + d1, (double) this.getZ() + d2);
|
|
}
|
|
|
|
+ public 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);
|
|
}
|
|
@@ -188,6 +189,8 @@ public class BlockPosition extends BaseBlockPosition {
|
|
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;
|
|
}
|
|
@@ -368,6 +371,7 @@ public class BlockPosition extends BaseBlockPosition {
|
|
return super.a(enumblockrotation).immutableCopy();
|
|
}
|
|
|
|
+ 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.o(i);
|
|
this.p(j);
|
|
@@ -375,6 +379,7 @@ public class BlockPosition extends BaseBlockPosition {
|
|
return this;
|
|
}
|
|
|
|
+ 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));
|
|
}
|
|
@@ -424,16 +429,19 @@ public class BlockPosition extends BaseBlockPosition {
|
|
}
|
|
}
|
|
|
|
+ public final void setX(final int x) { super.o(x); } // Paper - OBFHELPER
|
|
@Override
|
|
public void o(int i) {
|
|
super.o(i);
|
|
}
|
|
|
|
+ public final void setY(final int y) { super.p(y); } // Paper - OBFHELPER
|
|
@Override
|
|
public void p(int i) {
|
|
super.p(i);
|
|
}
|
|
|
|
+ public final void setZ(final int z) { super.q(z); } // Paper - OBFHELPER
|
|
@Override
|
|
public void q(int i) {
|
|
super.q(i);
|
|
@@ -444,4 +452,13 @@ public class BlockPosition extends BaseBlockPosition {
|
|
return new BlockPosition(this);
|
|
}
|
|
}
|
|
+
|
|
+ // Paper start
|
|
+ public static class PooledBlockPosition extends BlockPosition.MutableBlockPosition implements AutoCloseable {
|
|
+ @Deprecated
|
|
+ public void close() {}
|
|
+ @Deprecated
|
|
+ public static BlockPosition.PooledBlockPosition acquire() { return new PooledBlockPosition(); }
|
|
+ }
|
|
+ // Paper end
|
|
}
|
|
diff --git a/src/main/java/net/minecraft/server/Chunk.java b/src/main/java/net/minecraft/server/Chunk.java
|
|
index 3cdcdc60df4f28197cf19c59ea42a92ae4af3819..7c7826cf3adb19814984ab627e4c4726d8933244 100644
|
|
--- a/src/main/java/net/minecraft/server/Chunk.java
|
|
+++ b/src/main/java/net/minecraft/server/Chunk.java
|
|
@@ -26,7 +26,7 @@ public class Chunk implements IChunkAccess {
|
|
|
|
private static final Logger LOGGER = LogManager.getLogger();
|
|
@Nullable
|
|
- public static final ChunkSection a = null;
|
|
+ public static final ChunkSection a = null; public static final ChunkSection EMPTY_CHUNK_SECTION = a; // Paper - OBFHELPER
|
|
private final ChunkSection[] sections;
|
|
private BiomeStorage d;
|
|
private final Map<BlockPosition, NBTTagCompound> e;
|
|
@@ -49,7 +49,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) {
|
|
@@ -66,7 +66,7 @@ public class Chunk implements IChunkAccess {
|
|
this.n = new ShortList[16];
|
|
this.entitySlices = (List[]) (new List[16]); // Spigot
|
|
this.world = (WorldServer) world; // CraftBukkit - type
|
|
- this.loc = chunkcoordintpair;
|
|
+ this.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;
|
|
@@ -109,6 +109,110 @@ 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 long before = this.neighbourChunksLoadedBitset;
|
|
+ final int index = getNeighbourIndex(relativeX, relativeZ);
|
|
+ this.loadedNeighbourChunks[index] = chunk;
|
|
+ this.neighbourChunksLoadedBitset |= (1L << index);
|
|
+ this.onNeighbourChange(before, this.neighbourChunksLoadedBitset);
|
|
+ }
|
|
+
|
|
+ public final void setNeighbourUnloaded(final int relativeX, final int relativeZ) {
|
|
+ final long before = this.neighbourChunksLoadedBitset;
|
|
+ final int index = getNeighbourIndex(relativeX, relativeZ);
|
|
+ this.loadedNeighbourChunks[index] = null;
|
|
+ this.neighbourChunksLoadedBitset &= ~(1L << index);
|
|
+ this.onNeighbourChange(before, this.neighbourChunksLoadedBitset);
|
|
+ }
|
|
+
|
|
+ public final void resetNeighbours() {
|
|
+ final long before = this.neighbourChunksLoadedBitset;
|
|
+ this.neighbourChunksLoadedBitset = 0L;
|
|
+ java.util.Arrays.fill(this.loadedNeighbourChunks, null);
|
|
+ this.onNeighbourChange(before, 0L);
|
|
+ }
|
|
+
|
|
+ protected void onNeighbourChange(final long bitsetBefore, final long bitsetAfter) {
|
|
+
|
|
+ }
|
|
+
|
|
+ public final boolean isAnyNeighborsLoaded() {
|
|
+ return neighbourChunksLoadedBitset != 0;
|
|
+ }
|
|
+ public final boolean areNeighboursLoaded(final int radius) {
|
|
+ return Chunk.areNeighboursLoaded(this.neighbourChunksLoadedBitset, radius);
|
|
+ }
|
|
+
|
|
+ public static boolean areNeighboursLoaded(final long bitset, final int radius) {
|
|
+ // index = relativeX + (relativeZ * (NEIGHBOUR_CACHE_RADIUS * 2 + 1)) + (NEIGHBOUR_CACHE_RADIUS + NEIGHBOUR_CACHE_RADIUS * ((NEIGHBOUR_CACHE_RADIUS * 2 + 1)))
|
|
+ switch (radius) {
|
|
+ case 0: {
|
|
+ return (bitset & (1L << getNeighbourIndex(0, 0))) != 0;
|
|
+ }
|
|
+ case 1: {
|
|
+ long mask = 0L;
|
|
+ for (int dx = -1; dx <= 1; ++dx) {
|
|
+ for (int dz = -1; dz <= 1; ++dz) {
|
|
+ mask |= (1L << getNeighbourIndex(dx, dz));
|
|
+ }
|
|
+ }
|
|
+ return (bitset & mask) == mask;
|
|
+ }
|
|
+ case 2: {
|
|
+ long mask = 0L;
|
|
+ for (int dx = -2; dx <= 2; ++dx) {
|
|
+ for (int dz = -2; dz <= 2; ++dz) {
|
|
+ mask |= (1L << getNeighbourIndex(dx, dz));
|
|
+ }
|
|
+ }
|
|
+ return (bitset & mask) == mask;
|
|
+ }
|
|
+ case 3: {
|
|
+ long mask = 0L;
|
|
+ for (int dx = -3; dx <= 3; ++dx) {
|
|
+ for (int dz = -3; dz <= 3; ++dz) {
|
|
+ mask |= (1L << getNeighbourIndex(dx, dz));
|
|
+ }
|
|
+ }
|
|
+ return (bitset & mask) == mask;
|
|
+ }
|
|
+
|
|
+ default:
|
|
+ throw new IllegalArgumentException("Radius not recognized: " + radius);
|
|
+ }
|
|
+ }
|
|
+ // Paper end
|
|
+
|
|
public Chunk(World world, ProtoChunk protochunk) {
|
|
this(world, protochunk.getPos(), protochunk.getBiomeIndex(), protochunk.p(), protochunk.n(), protochunk.o(), protochunk.getInhabitedTime(), protochunk.getSections(), (Consumer) null);
|
|
Iterator iterator = protochunk.y().iterator();
|
|
@@ -214,6 +318,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());
|
|
@@ -353,6 +469,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);
|
|
}
|
|
|
|
@@ -375,6 +492,7 @@ public class Chunk implements IChunkAccess {
|
|
}
|
|
|
|
this.entitySlices[i].remove(entity);
|
|
+ this.entities.remove(entity); // Paper
|
|
}
|
|
|
|
@Override
|
|
@@ -396,6 +514,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
|
|
@@ -507,7 +626,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
|
|
@@ -546,6 +683,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 b703382204a3ccd57e642cff18c7c28fef157cc0..8eecdcde510661ec3a13a25a04ba394f6b6dc012 100644
|
|
--- a/src/main/java/net/minecraft/server/ChunkCache.java
|
|
+++ b/src/main/java/net/minecraft/server/ChunkCache.java
|
|
@@ -10,7 +10,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;
|
|
@@ -29,7 +29,7 @@ public class ChunkCache implements IBlockAccess, ICollisionAccess {
|
|
|
|
for (k = this.a; k <= i; ++k) {
|
|
for (l = this.b; l <= j; ++l) {
|
|
- this.c[k - this.a][l - this.b] = ichunkprovider.a(k, l);
|
|
+ this.c[k - this.a][l - this.b] = ((WorldServer)world).getChunkProvider().getChunkAtIfLoadedMainThreadNoCache(k, l); // Paper
|
|
}
|
|
}
|
|
|
|
@@ -54,7 +54,7 @@ public class ChunkCache implements IBlockAccess, ICollisionAccess {
|
|
int k = i - this.a;
|
|
int l = j - this.b;
|
|
|
|
- if (k >= 0 && k < this.c.length && l >= 0 && l < this.c[k].length) {
|
|
+ if (k >= 0 && k < this.c.length && l >= 0 && l < this.c[k].length) { // Paper - if this changes, update getChunkIfLoaded below
|
|
IChunkAccess ichunkaccess = this.c[k][l];
|
|
|
|
return (IChunkAccess) (ichunkaccess != null ? ichunkaccess : new ChunkEmpty(this.e, new ChunkCoordIntPair(i, j)));
|
|
@@ -73,6 +73,29 @@ public class ChunkCache implements IBlockAccess, ICollisionAccess {
|
|
return this.a(i, j);
|
|
}
|
|
|
|
+ // Paper start - if loaded util
|
|
+ private IChunkAccess getChunkIfLoaded(int x, int z) {
|
|
+ int k = x - this.a;
|
|
+ int l = z - this.b;
|
|
+
|
|
+ if (k >= 0 && k < this.c.length && l >= 0 && l < this.c[k].length) {
|
|
+ return this.c[k][l];
|
|
+ }
|
|
+ return null;
|
|
+ }
|
|
+ @Override
|
|
+ public Fluid getFluidIfLoaded(BlockPosition blockposition) {
|
|
+ IChunkAccess chunk = getChunkIfLoaded(blockposition.getX() >> 4, blockposition.getZ() >> 4);
|
|
+ return chunk == null ? null : chunk.getFluid(blockposition);
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public IBlockData getTypeIfLoaded(BlockPosition blockposition) {
|
|
+ IChunkAccess chunk = getChunkIfLoaded(blockposition.getX() >> 4, blockposition.getZ() >> 4);
|
|
+ return chunk == null ? null : chunk.getType(blockposition);
|
|
+ }
|
|
+ // Paper end
|
|
+
|
|
@Nullable
|
|
@Override
|
|
public TileEntity getTileEntity(BlockPosition blockposition) {
|
|
diff --git a/src/main/java/net/minecraft/server/ChunkCoordIntPair.java b/src/main/java/net/minecraft/server/ChunkCoordIntPair.java
|
|
index 2837823547bdc9655376af3af89c43d84719d513..35b8a85d1280ba3be757b14b14388954ac1617d4 100644
|
|
--- a/src/main/java/net/minecraft/server/ChunkCoordIntPair.java
|
|
+++ b/src/main/java/net/minecraft/server/ChunkCoordIntPair.java
|
|
@@ -11,27 +11,33 @@ public class ChunkCoordIntPair {
|
|
public static final long a = pair(1875016, 1875016);
|
|
public final int x;
|
|
public final int z;
|
|
+ public final long longKey; // Paper
|
|
|
|
public ChunkCoordIntPair(int i, int j) {
|
|
this.x = i;
|
|
this.z = j;
|
|
+ this.longKey = pair(this.x, this.z); // Paper
|
|
}
|
|
|
|
public ChunkCoordIntPair(BlockPosition blockposition) {
|
|
this.x = blockposition.getX() >> 4;
|
|
this.z = blockposition.getZ() >> 4;
|
|
+ this.longKey = pair(this.x, this.z); // Paper
|
|
}
|
|
|
|
public ChunkCoordIntPair(long i) {
|
|
this.x = (int) i;
|
|
this.z = (int) (i >> 32);
|
|
+ this.longKey = pair(this.x, this.z); // Paper
|
|
}
|
|
|
|
public long pair() {
|
|
- return pair(this.x, this.z);
|
|
+ return longKey; // Paper
|
|
}
|
|
|
|
- public static long pair(int i, int j) {
|
|
+ public static long 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 5585b5646b5f3650aa3b795be06f920699a85403..359441cd993a95f933f23aebcec8180f314a5f09 100644
|
|
--- a/src/main/java/net/minecraft/server/ChunkProviderServer.java
|
|
+++ b/src/main/java/net/minecraft/server/ChunkProviderServer.java
|
|
@@ -24,7 +24,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;
|
|
@@ -37,6 +37,167 @@ public class ChunkProviderServer extends IChunkProvider {
|
|
private final IChunkAccess[] cacheChunk = new IChunkAccess[4];
|
|
@Nullable
|
|
private SpawnerCreature.d p;
|
|
+ // Paper start
|
|
+ final com.destroystokyo.paper.util.concurrent.WeakSeqLock loadedChunkMapSeqLock = new com.destroystokyo.paper.util.concurrent.WeakSeqLock();
|
|
+ final 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.chunkMapDistance.addTicketAtLevel(TicketType.FUTURE_AWAIT, chunkPos, ticketLevel, identifier);
|
|
+ this.tickDistanceManager();
|
|
+
|
|
+ PlayerChunk chunk = this.playerChunkMap.getUpdatingChunk(chunkPos.pair());
|
|
+
|
|
+ if (chunk == null) {
|
|
+ throw new IllegalStateException("Expected playerchunk " + chunkPos + " in world '" + this.world.getWorld().getName() + "'");
|
|
+ }
|
|
+
|
|
+ CompletableFuture<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) {
|
|
+ playerChunkMap.callbackExecutor.execute(() -> {
|
|
+ onLoad.accept(either == null ? null : either.left().orElse(null)); // indicate failure to the callback.
|
|
+ });
|
|
+ }
|
|
+ } catch (Throwable thr) {
|
|
+ if (thr instanceof ThreadDeath) {
|
|
+ throw (ThreadDeath)thr;
|
|
+ }
|
|
+ MinecraftServer.LOGGER.fatal("Load callback for future await failed " + chunkPos.toString() + " in world '" + ChunkProviderServer.this.world.getWorld().getName() + "'", thr);
|
|
+ return;
|
|
+ }
|
|
+ } finally {
|
|
+ // due to odd behaviour with CB unload implementation we need to have these AFTER the load callback.
|
|
+ ChunkProviderServer.this.chunkMapDistance.addTicketAtLevel(TicketType.UNKNOWN, chunkPos, ticketLevel, chunkPos);
|
|
+ ChunkProviderServer.this.chunkMapDistance.removeTicketAtLevel(TicketType.FUTURE_AWAIT, chunkPos, ticketLevel, identifier);
|
|
+ }
|
|
+ }, this.serverThreadQueue);
|
|
+ }
|
|
+ // Paper end
|
|
|
|
public ChunkProviderServer(WorldServer worldserver, Convertable.ConversionSession convertable_conversionsession, DataFixer datafixer, DefinedStructureManager definedstructuremanager, Executor executor, ChunkGenerator chunkgenerator, int i, boolean flag, WorldLoadListener worldloadlistener, Supplier<WorldPersistentData> supplier) {
|
|
this.world = worldserver;
|
|
@@ -90,6 +251,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) {
|
|
@@ -372,10 +576,9 @@ public class ChunkProviderServer extends IChunkProvider {
|
|
|
|
this.p = spawnercreature_d;
|
|
this.world.getMethodProfiler().exit();
|
|
- List<PlayerChunk> list = Lists.newArrayList(this.playerChunkMap.f());
|
|
-
|
|
- Collections.shuffle(list);
|
|
- list.forEach((playerchunk) -> {
|
|
+ //List<PlayerChunk> list = Lists.newArrayList(this.playerChunkMap.f()); // Paper
|
|
+ //Collections.shuffle(list); // Paper
|
|
+ this.playerChunkMap.f().forEach((playerchunk) -> { // Paper - no... just no...
|
|
Optional<Chunk> optional = ((Either) playerchunk.a().getNow(PlayerChunk.UNLOADED_CHUNK)).left();
|
|
|
|
if (optional.isPresent()) {
|
|
diff --git a/src/main/java/net/minecraft/server/ChunkSection.java b/src/main/java/net/minecraft/server/ChunkSection.java
|
|
index b0f7ea97d4795655b6c30b296fd929806dac4ef1..882c2733beaff1df68b892d44fc77cacf4364ff4 100644
|
|
--- a/src/main/java/net/minecraft/server/ChunkSection.java
|
|
+++ b/src/main/java/net/minecraft/server/ChunkSection.java
|
|
@@ -133,6 +133,7 @@ public class ChunkSection {
|
|
return this.blockIds;
|
|
}
|
|
|
|
+ public void writeChunkSection(PacketDataSerializer packetDataSerializer) { this.b(packetDataSerializer); } // Paper - OBFHELPER
|
|
public void b(PacketDataSerializer packetdataserializer) {
|
|
packetdataserializer.writeShort(this.nonEmptyBlockCount);
|
|
this.blockIds.b(packetdataserializer);
|
|
diff --git a/src/main/java/net/minecraft/server/DataBits.java b/src/main/java/net/minecraft/server/DataBits.java
|
|
index cd572c76522b2ee900a03fd5cf6753f3297c1ccd..2c3580c1c7bcd6afc83a45550c0f672a592e0c38 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 {
|
|
return (int) (k >> l & this.d);
|
|
}
|
|
|
|
+ public long[] getDataBits() { return this.a(); } // Paper - OBFHELPER
|
|
public long[] a() {
|
|
return this.b;
|
|
}
|
|
diff --git a/src/main/java/net/minecraft/server/DataPalette.java b/src/main/java/net/minecraft/server/DataPalette.java
|
|
index b7f4330bbe3b51e6792043cbd0c46c73aad457cb..75b721933ccbe8edc1cd7ea5cc4562214e26b66d 100644
|
|
--- a/src/main/java/net/minecraft/server/DataPalette.java
|
|
+++ b/src/main/java/net/minecraft/server/DataPalette.java
|
|
@@ -5,10 +5,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 a(Predicate<T> predicate);
|
|
|
|
+ @Nullable default T getObject(int dataBits) { return this.a(dataBits); } // Paper - OBFHELPER
|
|
@Nullable
|
|
T a(int i);
|
|
|
|
diff --git a/src/main/java/net/minecraft/server/DataPaletteBlock.java b/src/main/java/net/minecraft/server/DataPaletteBlock.java
|
|
index 8856981da86219bdb036aa6246152f382ff8a818..4c6979903d287f7f37d9029f6ce2551742f26164 100644
|
|
--- a/src/main/java/net/minecraft/server/DataPaletteBlock.java
|
|
+++ b/src/main/java/net/minecraft/server/DataPaletteBlock.java
|
|
@@ -10,7 +10,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;
|
|
};
|
|
@@ -18,9 +18,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() {
|
|
@@ -55,6 +55,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;
|
|
@@ -132,6 +133,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/Entity.java b/src/main/java/net/minecraft/server/Entity.java
|
|
index 781eaaa4544dfa6e2c523d4d3d99929e7ab46ac7..0622f0d8d765c1249fd97f27c3c83af519f01c00 100644
|
|
--- a/src/main/java/net/minecraft/server/Entity.java
|
|
+++ b/src/main/java/net/minecraft/server/Entity.java
|
|
@@ -983,8 +983,9 @@ public abstract class Entity implements INamableTileEntity, ICommandListener {
|
|
|
|
}
|
|
|
|
- @Nullable
|
|
- public AxisAlignedBB ay() {
|
|
+
|
|
+ @Nullable public final AxisAlignedBB getCollisionBox(){return ay();} //Paper - OBFHELPER
|
|
+ @Nullable public AxisAlignedBB ay() {
|
|
return null;
|
|
}
|
|
|
|
@@ -1774,6 +1775,7 @@ public abstract class Entity implements INamableTileEntity, ICommandListener {
|
|
return EnumInteractionResult.PASS;
|
|
}
|
|
|
|
+ public final AxisAlignedBB getHardCollisionBox(Entity entity){ return j(entity);}//Paper - OBFHELPER
|
|
@Nullable
|
|
public AxisAlignedBB j(Entity entity) {
|
|
return null;
|
|
diff --git a/src/main/java/net/minecraft/server/EntityCreature.java b/src/main/java/net/minecraft/server/EntityCreature.java
|
|
index e83d587fd4feee1e36c18c49b98e669c09f5de20..c94197a50269622e8995685119bac984c45e6833 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 b309d9a13b35f5c04c3a0f048e858df99d8ac617..e5455d99e3f5607a5754e5760d42853a62dddb82 100644
|
|
--- a/src/main/java/net/minecraft/server/EntityInsentient.java
|
|
+++ b/src/main/java/net/minecraft/server/EntityInsentient.java
|
|
@@ -160,6 +160,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 8ff295ed373cc316e56e4a01a268f98b7b773bd5..dccb315440f7429fe881bd0d12af8f1ae8e35c3d 100644
|
|
--- a/src/main/java/net/minecraft/server/EntityLiving.java
|
|
+++ b/src/main/java/net/minecraft/server/EntityLiving.java
|
|
@@ -137,6 +137,7 @@ public abstract class EntityLiving extends Entity {
|
|
public boolean collides = true;
|
|
public Set<UUID> collidableExemptions = new HashSet<>();
|
|
public boolean canPickUpLoot;
|
|
+ public org.bukkit.craftbukkit.entity.CraftLivingEntity getBukkitLivingEntity() { return (org.bukkit.craftbukkit.entity.CraftLivingEntity) super.getBukkitEntity(); } // Paper
|
|
|
|
@Override
|
|
public float getBukkitYaw() {
|
|
diff --git a/src/main/java/net/minecraft/server/EntityMonster.java b/src/main/java/net/minecraft/server/EntityMonster.java
|
|
index fb0f281261455c9f127abd8d3b336135ad84fc27..ebdd990829edb8e423f482fa4352fe2d468efcba 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 a356e3258a69e059fd752212d3c5f13078ed62ab..183346d37e36be70348ecf0f2f26a0e0ef35eab8 100644
|
|
--- a/src/main/java/net/minecraft/server/EntityPlayer.java
|
|
+++ b/src/main/java/net/minecraft/server/EntityPlayer.java
|
|
@@ -91,6 +91,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(worldserver, worldserver.getSpawn(), gameprofile);
|
|
this.spawnDimension = World.OVERWORLD;
|
|
@@ -102,6 +104,8 @@ public class EntityPlayer extends EntityHuman implements ICrafting {
|
|
this.G = 1.0F;
|
|
this.b(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 484e78746aa62bb0b12968165bf8e056b27152f3..9a772e40ad8f9858e6278b99d9d1ff5dc54513cb 100644
|
|
--- a/src/main/java/net/minecraft/server/EntityTypes.java
|
|
+++ b/src/main/java/net/minecraft/server/EntityTypes.java
|
|
@@ -3,6 +3,7 @@ package net.minecraft.server;
|
|
import com.google.common.collect.ImmutableSet;
|
|
import java.util.Optional;
|
|
import java.util.Set; // Paper
|
|
+import java.util.Map; // Paper
|
|
import java.util.UUID;
|
|
import java.util.function.Function;
|
|
import java.util.stream.Stream;
|
|
@@ -305,8 +306,8 @@ public class EntityTypes<T extends Entity> {
|
|
return this.bq.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.be.create(this, world);
|
|
}
|
|
|
|
diff --git a/src/main/java/net/minecraft/server/IAsyncTaskHandler.java b/src/main/java/net/minecraft/server/IAsyncTaskHandler.java
|
|
index b77a0f0c2ee30df44b113aa6c8d4fa9206d3e2ba..1ba26ee10f338edbec0f580bb55d083a3d6d2284 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 33238e2ac98e2add810ac21e40714cc2f43ac389..077fcb67f6248156aaebe7545e370ade4311b9e4 100644
|
|
--- a/src/main/java/net/minecraft/server/IBlockAccess.java
|
|
+++ b/src/main/java/net/minecraft/server/IBlockAccess.java
|
|
@@ -10,10 +10,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).f();
|
|
}
|
|
diff --git a/src/main/java/net/minecraft/server/IOWorker.java b/src/main/java/net/minecraft/server/IOWorker.java
|
|
index 38ccfd78639a85abcefb915c5c231be5881cebc1..8668b8f3941f37a7bc30a55c33baf74bd8ac49e3 100644
|
|
--- a/src/main/java/net/minecraft/server/IOWorker.java
|
|
+++ b/src/main/java/net/minecraft/server/IOWorker.java
|
|
@@ -21,7 +21,7 @@ public class IOWorker implements AutoCloseable {
|
|
private static final Logger LOGGER = LogManager.getLogger();
|
|
private final AtomicBoolean b = new AtomicBoolean();
|
|
private final ThreadedMailbox<PairedQueue.b> c;
|
|
- private final RegionFileCache d;
|
|
+ private final RegionFileCache d;public RegionFileCache getRegionFileCache() { return d; } // Paper - OBFHELPER
|
|
private final Map<ChunkCoordIntPair, IOWorker.a> e = Maps.newLinkedHashMap();
|
|
|
|
protected IOWorker(File file, boolean flag, String s) {
|
|
diff --git a/src/main/java/net/minecraft/server/IWorldReader.java b/src/main/java/net/minecraft/server/IWorldReader.java
|
|
index 284283c5f062107df88e77edaf092cf23302a109..b6d6905260cdd32873010f24ef5a3505e66159be 100644
|
|
--- a/src/main/java/net/minecraft/server/IWorldReader.java
|
|
+++ b/src/main/java/net/minecraft/server/IWorldReader.java
|
|
@@ -5,6 +5,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 dc5625f54a54db35e5dfa630f1414887c736b704..76091ab3f149decc0d3c848b79edd24e20cf181d 100644
|
|
--- a/src/main/java/net/minecraft/server/ItemStack.java
|
|
+++ b/src/main/java/net/minecraft/server/ItemStack.java
|
|
@@ -49,7 +49,7 @@ public final class ItemStack {
|
|
})).apply(instance, ItemStack::new);
|
|
});
|
|
private static final Logger LOGGER = LogManager.getLogger();
|
|
- public static final ItemStack b = new ItemStack((Item) null);
|
|
+ public static final ItemStack b = new ItemStack((Item) null);public static final ItemStack NULL_ITEM = b; // Paper - OBFHELPER
|
|
public static final DecimalFormat c = (DecimalFormat) SystemUtils.a((new DecimalFormat("#.##")), (decimalformat) -> { // CraftBukkit - decompile error
|
|
decimalformat.setDecimalFormatSymbols(DecimalFormatSymbols.getInstance(Locale.ROOT));
|
|
});
|
|
@@ -606,6 +606,24 @@ public final class ItemStack {
|
|
return this.tag != null ? this.tag.getList("Enchantments", 10) : new NBTTagList();
|
|
}
|
|
|
|
+ // Paper start - (this is just a good no conflict location)
|
|
+ public org.bukkit.inventory.ItemStack asBukkitMirror() {
|
|
+ return CraftItemStack.asCraftMirror(this);
|
|
+ }
|
|
+ public org.bukkit.inventory.ItemStack asBukkitCopy() {
|
|
+ return CraftItemStack.asCraftMirror(this.cloneItemStack());
|
|
+ }
|
|
+ public static ItemStack fromBukkitCopy(org.bukkit.inventory.ItemStack itemstack) {
|
|
+ return CraftItemStack.asNMSCopy(itemstack);
|
|
+ }
|
|
+ private org.bukkit.craftbukkit.inventory.CraftItemStack bukkitStack;
|
|
+ public org.bukkit.inventory.ItemStack getBukkitStack() {
|
|
+ if (bukkitStack == null || bukkitStack.getHandle() != this) {
|
|
+ bukkitStack = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(this);
|
|
+ }
|
|
+ return bukkitStack;
|
|
+ }
|
|
+ // Paper end
|
|
public void setTag(@Nullable NBTTagCompound nbttagcompound) {
|
|
this.tag = nbttagcompound;
|
|
if (this.getItem().usesDurability()) {
|
|
@@ -698,6 +716,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);
|
|
}
|
|
@@ -783,6 +802,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 0000000000000000000000000000000000000000..da7a325d070e194cd1664ed20dcb3a762c9a517a
|
|
--- /dev/null
|
|
+++ b/src/main/java/net/minecraft/server/MCUtil.java
|
|
@@ -0,0 +1,502 @@
|
|
+package net.minecraft.server;
|
|
+
|
|
+import com.destroystokyo.paper.block.TargetBlockInfo;
|
|
+import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
|
+import org.apache.commons.lang.exception.ExceptionUtils;
|
|
+import org.bukkit.Location;
|
|
+import org.bukkit.block.BlockFace;
|
|
+import org.bukkit.craftbukkit.CraftWorld;
|
|
+import org.bukkit.craftbukkit.util.Waitable;
|
|
+import org.spigotmc.AsyncCatcher;
|
|
+
|
|
+import javax.annotation.Nonnull;
|
|
+import javax.annotation.Nullable;
|
|
+import java.util.List;
|
|
+import java.util.Queue;
|
|
+import java.util.concurrent.CompletableFuture;
|
|
+import java.util.concurrent.ExecutionException;
|
|
+import java.util.concurrent.LinkedBlockingQueue;
|
|
+import java.util.concurrent.ThreadPoolExecutor;
|
|
+import java.util.concurrent.TimeUnit;
|
|
+import java.util.concurrent.TimeoutException;
|
|
+import java.util.concurrent.atomic.AtomicBoolean;
|
|
+import java.util.function.BiConsumer;
|
|
+import java.util.function.Consumer;
|
|
+import java.util.function.Supplier;
|
|
+
|
|
+public final class MCUtil {
|
|
+ public static final ThreadPoolExecutor asyncExecutor = new ThreadPoolExecutor(
|
|
+ 0, 2, 60L, TimeUnit.SECONDS,
|
|
+ new LinkedBlockingQueue<Runnable>(),
|
|
+ new ThreadFactoryBuilder().setNameFormat("Paper Async Task Handler Thread - %1$d").build()
|
|
+ );
|
|
+ public static final ThreadPoolExecutor cleanerExecutor = new ThreadPoolExecutor(
|
|
+ 1, 1, 0L, TimeUnit.SECONDS,
|
|
+ new LinkedBlockingQueue<Runnable>(),
|
|
+ new ThreadFactoryBuilder().setNameFormat("Paper Object Cleaner").build()
|
|
+ );
|
|
+
|
|
+ public static final long INVALID_CHUNK_KEY = getCoordinateKey(Integer.MAX_VALUE, Integer.MAX_VALUE);
|
|
+
|
|
+
|
|
+ public static Runnable once(Runnable run) {
|
|
+ AtomicBoolean ran = new AtomicBoolean(false);
|
|
+ return () -> {
|
|
+ if (ran.compareAndSet(false, true)) {
|
|
+ run.run();
|
|
+ }
|
|
+ };
|
|
+ }
|
|
+
|
|
+ public static <T> Runnable once(List<T> list, Consumer<T> cb) {
|
|
+ return once(() -> {
|
|
+ list.forEach(cb);
|
|
+ });
|
|
+ }
|
|
+
|
|
+ private static Runnable makeCleanerCallback(Runnable run) {
|
|
+ return once(() -> cleanerExecutor.execute(run));
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * DANGER WILL ROBINSON: Be sure you do not use a lambda that lives in the object being monitored, or leaky leaky!
|
|
+ * @param obj
|
|
+ * @param run
|
|
+ * @return
|
|
+ */
|
|
+ public static Runnable registerCleaner(Object obj, Runnable run) {
|
|
+ // Wrap callback in its own method above or the lambda will leak object
|
|
+ Runnable cleaner = makeCleanerCallback(run);
|
|
+ co.aikar.cleaner.Cleaner.register(obj, cleaner);
|
|
+ return cleaner;
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * DANGER WILL ROBINSON: Be sure you do not use a lambda that lives in the object being monitored, or leaky leaky!
|
|
+ * @param obj
|
|
+ * @param list
|
|
+ * @param cleaner
|
|
+ * @param <T>
|
|
+ * @return
|
|
+ */
|
|
+ public static <T> Runnable registerListCleaner(Object obj, List<T> list, Consumer<T> cleaner) {
|
|
+ return registerCleaner(obj, () -> {
|
|
+ list.forEach(cleaner);
|
|
+ list.clear();
|
|
+ });
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * DANGER WILL ROBINSON: Be sure you do not use a lambda that lives in the object being monitored, or leaky leaky!
|
|
+ * @param obj
|
|
+ * @param resource
|
|
+ * @param cleaner
|
|
+ * @param <T>
|
|
+ * @return
|
|
+ */
|
|
+ public static <T> Runnable registerCleaner(Object obj, T resource, java.util.function.Consumer<T> cleaner) {
|
|
+ return registerCleaner(obj, () -> cleaner.accept(resource));
|
|
+ }
|
|
+
|
|
+ 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 int fastFloor(double x) {
|
|
+ int truncated = (int)x;
|
|
+ return x < (double)truncated ? truncated - 1 : truncated;
|
|
+ }
|
|
+
|
|
+ public static int fastFloor(float x) {
|
|
+ int truncated = (int)x;
|
|
+ return x < (double)truncated ? truncated - 1 : truncated;
|
|
+ }
|
|
+
|
|
+ public static float normalizeYaw(float f) {
|
|
+ float f1 = f % 360.0F;
|
|
+
|
|
+ if (f1 >= 180.0F) {
|
|
+ f1 -= 360.0F;
|
|
+ }
|
|
+
|
|
+ if (f1 < -180.0F) {
|
|
+ f1 += 360.0F;
|
|
+ }
|
|
+
|
|
+ return f1;
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * Quickly generate a stack trace for current location
|
|
+ *
|
|
+ * @return Stacktrace
|
|
+ */
|
|
+ public static String stack() {
|
|
+ return ExceptionUtils.getFullStackTrace(new Throwable());
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * Quickly generate a stack trace for current location with message
|
|
+ *
|
|
+ * @param str
|
|
+ * @return Stacktrace
|
|
+ */
|
|
+ public static String stack(String str) {
|
|
+ return ExceptionUtils.getFullStackTrace(new Throwable(str));
|
|
+ }
|
|
+
|
|
+ public static long getCoordinateKey(final BlockPosition blockPos) {
|
|
+ return ((long)(blockPos.getZ() >> 4) << 32) | ((blockPos.getX() >> 4) & 0xFFFFFFFFL);
|
|
+ }
|
|
+
|
|
+ public static long getCoordinateKey(final Entity entity) {
|
|
+ return ((long)(MCUtil.fastFloor(entity.locZ()) >> 4) << 32) | ((MCUtil.fastFloor(entity.locX()) >> 4) & 0xFFFFFFFFL);
|
|
+ }
|
|
+
|
|
+ public static long getCoordinateKey(final ChunkCoordIntPair pair) {
|
|
+ return ((long)pair.z << 32) | (pair.x & 0xFFFFFFFFL);
|
|
+ }
|
|
+
|
|
+ public static long getCoordinateKey(final int x, final int z) {
|
|
+ return ((long)z << 32) | (x & 0xFFFFFFFFL);
|
|
+ }
|
|
+
|
|
+ public static int getCoordinateX(final long key) {
|
|
+ return (int)key;
|
|
+ }
|
|
+
|
|
+ public static int getCoordinateZ(final long key) {
|
|
+ return (int)(key >>> 32);
|
|
+ }
|
|
+
|
|
+ public static int getChunkCoordinate(final double coordinate) {
|
|
+ return MCUtil.fastFloor(coordinate) >> 4;
|
|
+ }
|
|
+
|
|
+ public static int getBlockCoordinate(final double coordinate) {
|
|
+ return MCUtil.fastFloor(coordinate);
|
|
+ }
|
|
+
|
|
+ public static long getBlockKey(final int x, final int y, final int z) {
|
|
+ return ((long)x & 0x7FFFFFF) | (((long)z & 0x7FFFFFF) << 27) | ((long)y << 54);
|
|
+ }
|
|
+
|
|
+ public static long getBlockKey(final BlockPosition pos) {
|
|
+ return ((long)pos.getX() & 0x7FFFFFF) | (((long)pos.getZ() & 0x7FFFFFF) << 27) | ((long)pos.getY() << 54);
|
|
+ }
|
|
+
|
|
+ public static long getBlockKey(final Entity entity) {
|
|
+ return getBlockKey(getBlockCoordinate(entity.locX()), getBlockCoordinate(entity.locY()), getBlockCoordinate(entity.locZ()));
|
|
+ }
|
|
+
|
|
+ // assumes the sets have the same comparator, and if this comparator is null then assume T is Comparable
|
|
+ public static <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 final java.util.concurrent.Executor MAIN_EXECUTOR = (run) -> {
|
|
+ if (!isMainThread()) {
|
|
+ MinecraftServer.getServer().execute(run);
|
|
+ } else {
|
|
+ run.run();
|
|
+ }
|
|
+ };
|
|
+
|
|
+ public static <T> CompletableFuture<T> ensureMain(CompletableFuture<T> future) {
|
|
+ return future.thenApplyAsync(r -> r, MAIN_EXECUTOR);
|
|
+ }
|
|
+
|
|
+ public static <T> void thenOnMain(CompletableFuture<T> future, Consumer<T> consumer) {
|
|
+ future.thenAcceptAsync(consumer, MAIN_EXECUTOR);
|
|
+ }
|
|
+ public static <T> void thenOnMain(CompletableFuture<T> future, BiConsumer<T, Throwable> consumer) {
|
|
+ future.whenCompleteAsync(consumer, MAIN_EXECUTOR);
|
|
+ }
|
|
+
|
|
+ public static boolean isMainThread() {
|
|
+ return MinecraftServer.getServer().isMainThread();
|
|
+ }
|
|
+
|
|
+ public static org.bukkit.scheduler.BukkitTask scheduleTask(int ticks, Runnable runnable) {
|
|
+ return scheduleTask(ticks, runnable, null);
|
|
+ }
|
|
+
|
|
+ public static org.bukkit.scheduler.BukkitTask scheduleTask(int ticks, Runnable runnable, String taskName) {
|
|
+ return MinecraftServer.getServer().server.getScheduler().scheduleInternalTask(runnable, ticks, taskName);
|
|
+ }
|
|
+
|
|
+ public static void processQueue() {
|
|
+ Runnable runnable;
|
|
+ Queue<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);
|
|
+ }
|
|
+
|
|
+ @Nonnull
|
|
+ public static WorldServer getNMSWorld(@Nonnull org.bukkit.World world) {
|
|
+ return ((CraftWorld) world).getHandle();
|
|
+ }
|
|
+
|
|
+ public static WorldServer getNMSWorld(@Nonnull org.bukkit.entity.Entity entity) {
|
|
+ return getNMSWorld(entity.getWorld());
|
|
+ }
|
|
+
|
|
+ public static RayTrace.FluidCollisionOption getNMSFluidCollisionOption(TargetBlockInfo.FluidMode fluidMode) {
|
|
+ if (fluidMode == TargetBlockInfo.FluidMode.NEVER) {
|
|
+ return RayTrace.FluidCollisionOption.NONE;
|
|
+ }
|
|
+ if (fluidMode == TargetBlockInfo.FluidMode.SOURCE_ONLY) {
|
|
+ return RayTrace.FluidCollisionOption.SOURCE_ONLY;
|
|
+ }
|
|
+ if (fluidMode == TargetBlockInfo.FluidMode.ALWAYS) {
|
|
+ return RayTrace.FluidCollisionOption.ANY;
|
|
+ }
|
|
+ return null;
|
|
+ }
|
|
+
|
|
+ public static BlockFace toBukkitBlockFace(EnumDirection enumDirection) {
|
|
+ switch (enumDirection) {
|
|
+ case DOWN:
|
|
+ return BlockFace.DOWN;
|
|
+ case UP:
|
|
+ return BlockFace.UP;
|
|
+ case NORTH:
|
|
+ return BlockFace.NORTH;
|
|
+ case SOUTH:
|
|
+ return BlockFace.SOUTH;
|
|
+ case WEST:
|
|
+ return BlockFace.WEST;
|
|
+ case EAST:
|
|
+ return BlockFace.EAST;
|
|
+ default:
|
|
+ return null;
|
|
+ }
|
|
+ }
|
|
+}
|
|
diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java
|
|
index bae5ddbd97bd3f3422c699c42f025337b7cef574..64994a9635efa36ec09623c3869f22faed09b21c 100644
|
|
--- a/src/main/java/net/minecraft/server/MinecraftServer.java
|
|
+++ b/src/main/java/net/minecraft/server/MinecraftServer.java
|
|
@@ -766,6 +766,9 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant<TickTas
|
|
MinecraftServer.LOGGER.error("Failed to unlock level {}", this.convertable.getLevelName(), ioexception1);
|
|
}
|
|
// Spigot start
|
|
+ MCUtil.asyncExecutor.shutdown(); // Paper
|
|
+ try { MCUtil.asyncExecutor.awaitTermination(30, java.util.concurrent.TimeUnit.SECONDS); // Paper
|
|
+ } catch (java.lang.InterruptedException ignored) {} // Paper
|
|
if (org.spigotmc.SpigotConfig.saveUserCacheOnStopOnly) {
|
|
LOGGER.info("Saving usercache.json");
|
|
this.getUserCache().c();
|
|
diff --git a/src/main/java/net/minecraft/server/NBTTagCompound.java b/src/main/java/net/minecraft/server/NBTTagCompound.java
|
|
index ef2dee1987c45a2c43188584169dd260ee80826c..c16ff6723d3fd191b990002d40dc021d7870555d 100644
|
|
--- a/src/main/java/net/minecraft/server/NBTTagCompound.java
|
|
+++ b/src/main/java/net/minecraft/server/NBTTagCompound.java
|
|
@@ -70,7 +70,7 @@ public class NBTTagCompound implements NBTBase {
|
|
return "TAG_Compound";
|
|
}
|
|
};
|
|
- private final Map<String, NBTBase> map;
|
|
+ public final Map<String, NBTBase> map; // Paper
|
|
|
|
protected NBTTagCompound(Map<String, NBTBase> map) {
|
|
this.map = map;
|
|
@@ -133,10 +133,14 @@ public class NBTTagCompound implements NBTBase {
|
|
this.map.put(s, NBTTagLong.a(i));
|
|
}
|
|
|
|
+ public void setUUID(String prefix, UUID uuid) { a(prefix, uuid); } // Paper - OBFHELPER
|
|
public void a(String s, UUID uuid) {
|
|
this.map.put(s, GameProfileSerializer.a(uuid));
|
|
}
|
|
|
|
+
|
|
+ @Nullable public UUID getUUID(String prefix) { return a(prefix); } // Paper - OBFHELPER
|
|
+ @Nullable
|
|
public UUID a(String s) {
|
|
return GameProfileSerializer.a(this.get(s));
|
|
}
|
|
diff --git a/src/main/java/net/minecraft/server/NetworkManager.java b/src/main/java/net/minecraft/server/NetworkManager.java
|
|
index 69718caa347bc75ed243041797a6b53d90fc553c..c2fdccfb9192aa7ec55fd67c169cba71a695075c 100644
|
|
--- a/src/main/java/net/minecraft/server/NetworkManager.java
|
|
+++ b/src/main/java/net/minecraft/server/NetworkManager.java
|
|
@@ -158,6 +158,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();
|
|
@@ -198,6 +199,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;
|
|
@@ -326,9 +328,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 f574a28b68cf4d3745ee5f1a3756e19dbc23ca92..6e049c2e2a142ce022b9dc278a3bb302f723e42c 100644
|
|
--- a/src/main/java/net/minecraft/server/PacketDataSerializer.java
|
|
+++ b/src/main/java/net/minecraft/server/PacketDataSerializer.java
|
|
@@ -36,6 +36,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 90223deae3376fd6828eddf3831dab96650afef2..63c4dbd327beb7b6ab42eb44650d68accd3b0de6 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 cb0b7e7157b629c259b0ccb946c29cbcb3c6fa98..e336437207f9d6adbab69ef2785c129ff2ec1b36 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;
|
|
private boolean i;
|
|
@@ -133,6 +133,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 dc5e59bcecbe080445f33daa01b81ba747ed6337..ca41e420a1ab22f097dd0b98e156fd51434733d8 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;
|
|
@@ -37,6 +37,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;
|
|
@@ -52,8 +54,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
|
|
@@ -62,6 +103,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());
|
|
@@ -73,14 +122,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;
|
|
}
|
|
@@ -322,13 +374,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);
|
|
@@ -339,12 +405,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;
|
|
}
|
|
|
|
@@ -356,12 +434,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 5f692d719fd270120207ebcf6d0a2a24e8d59f7b..3e2e355177e32856dac07dc8b98658ad1b717045 100644
|
|
--- a/src/main/java/net/minecraft/server/PlayerChunkMap.java
|
|
+++ b/src/main/java/net/minecraft/server/PlayerChunkMap.java
|
|
@@ -101,6 +101,26 @@ 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) {
|
|
+ int chunkX = MCUtil.getChunkCoordinate(player.locX());
|
|
+ int chunkZ = MCUtil.getChunkCoordinate(player.locZ());
|
|
+ // Note: players need to be explicitly added to distance maps before they can be updated
|
|
+ }
|
|
+
|
|
+ void removePlayerFromDistanceMaps(EntityPlayer player) {
|
|
+
|
|
+ }
|
|
+
|
|
+ void updateMaps(EntityPlayer player) {
|
|
+ int chunkX = MCUtil.getChunkCoordinate(player.locX());
|
|
+ int chunkZ = MCUtil.getChunkCoordinate(player.locZ());
|
|
+ // Note: players need to be explicitly added to distance maps before they can be updated
|
|
+ }
|
|
+ // Paper end
|
|
+
|
|
public PlayerChunkMap(WorldServer worldserver, Convertable.ConversionSession convertable_conversionsession, DataFixer datafixer, DefinedStructureManager definedstructuremanager, Executor executor, IAsyncTaskHandler<Runnable> iasynctaskhandler, ILightAccess ilightaccess, ChunkGenerator chunkgenerator, WorldLoadListener worldloadlistener, Supplier<WorldPersistentData> supplier, int i, boolean flag) {
|
|
super(new File(convertable_conversionsession.a(worldserver.getDimensionKey()), "region"), datafixer, flag);
|
|
this.visibleChunks = this.updatingChunks.clone();
|
|
@@ -190,6 +210,14 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
|
|
};
|
|
}
|
|
|
|
+ // Paper start
|
|
+ public final int getEffectiveViewDistance() {
|
|
+ // TODO this needs to be checked on update
|
|
+ // Mojang currently sets it to +1 of the configured view distance. So subtract one to get the one we really want.
|
|
+ return this.viewDistance - 1;
|
|
+ }
|
|
+ // Paper end
|
|
+
|
|
private CompletableFuture<Either<List<IChunkAccess>, PlayerChunk.Failure>> a(ChunkCoordIntPair chunkcoordintpair, int i, IntFunction<ChunkStatus> intfunction) {
|
|
List<CompletableFuture<Either<IChunkAccess, PlayerChunk.Failure>>> list = Lists.newArrayList();
|
|
int j = chunkcoordintpair.x;
|
|
@@ -900,6 +928,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
|
|
if (!flag1) {
|
|
this.chunkDistanceManager.a(SectionPosition.a((Entity) entityplayer), entityplayer);
|
|
}
|
|
+ this.addPlayerToDistanceMaps(entityplayer); // Paper - distance maps
|
|
} else {
|
|
SectionPosition sectionposition = entityplayer.N();
|
|
|
|
@@ -907,6 +936,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
|
|
if (!flag2) {
|
|
this.chunkDistanceManager.b(sectionposition, entityplayer);
|
|
}
|
|
+ this.removePlayerFromDistanceMaps(entityplayer); // Paper - distance maps
|
|
}
|
|
|
|
for (int k = i - this.viewDistance; k <= i + this.viewDistance; ++k) {
|
|
@@ -1017,6 +1047,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 883b7bd8ca2094e6d43be3a8575fff6d90a5990b..a8f11d1842e9a8ef82230e2ae4998923f3b02ca9 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 c027fb94881be14396cba879087861df35023500..3b65711b91c51ac7b4b5b2b0144ffd279fe60eeb 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 b3824898daa80da791cdc8cfd06900e9a0b3b5b5..bf4172be525d5bdd7c152117afce8bf00106a139 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 f92fbf136158336b65217a504ecd422bfcc4964f..070449198273e6c42e72c891882b82361d1c8dbd 100644
|
|
--- a/src/main/java/net/minecraft/server/ProtoChunk.java
|
|
+++ b/src/main/java/net/minecraft/server/ProtoChunk.java
|
|
@@ -81,6 +81,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 43c5b8258d9cd15d86d4160e9c614c7ca6152962..1ce85ab949213efb9eae6daddca6ac8fb15dd472 100644
|
|
--- a/src/main/java/net/minecraft/server/RegionFile.java
|
|
+++ b/src/main/java/net/minecraft/server/RegionFile.java
|
|
@@ -93,6 +93,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 a2ec45a6b8bd63299508113e3522436cea2507e5..478d252953c65792df9f0068a4c1afd1985151ab 100644
|
|
--- a/src/main/java/net/minecraft/server/RegionLimitedWorldAccess.java
|
|
+++ b/src/main/java/net/minecraft/server/RegionLimitedWorldAccess.java
|
|
@@ -101,6 +101,26 @@ public class RegionLimitedWorldAccess implements GeneratorAccessSeed {
|
|
return i >= this.n.x && i <= this.o.x && j >= this.n.z && j <= this.o.z;
|
|
}
|
|
|
|
+ // Paper start - if loaded util
|
|
+ @Nullable
|
|
+ @Override
|
|
+ public IChunkAccess getChunkIfLoadedImmediately(int x, int z) {
|
|
+ return this.getChunkAt(x, z, ChunkStatus.FULL, false);
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public IBlockData getTypeIfLoaded(BlockPosition blockposition) {
|
|
+ IChunkAccess chunk = this.getChunkIfLoadedImmediately(blockposition.getX() >> 4, blockposition.getZ() >> 4);
|
|
+ return chunk == null ? null : chunk.getType(blockposition);
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public Fluid getFluidIfLoaded(BlockPosition blockposition) {
|
|
+ IChunkAccess chunk = this.getChunkIfLoadedImmediately(blockposition.getX() >> 4, blockposition.getZ() >> 4);
|
|
+ return chunk == null ? null : chunk.getFluid(blockposition);
|
|
+ }
|
|
+ // Paper end
|
|
+
|
|
@Override
|
|
public IBlockData getType(BlockPosition blockposition) {
|
|
return this.getChunkAt(blockposition.getX() >> 4, blockposition.getZ() >> 4).getType(blockposition);
|
|
diff --git a/src/main/java/net/minecraft/server/RegistryBlockID.java b/src/main/java/net/minecraft/server/RegistryBlockID.java
|
|
index 4efcb8b595750891b421e524812542f0f67e9f3f..60948afa4ead71010dc27c7cef3e5acdb0ba005a 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 80ee8d196436e271833503a0e123401d1b3314a9..275c1d2d1eb2649de9a9b5aece6e88c21362efba 100644
|
|
--- a/src/main/java/net/minecraft/server/SystemUtils.java
|
|
+++ b/src/main/java/net/minecraft/server/SystemUtils.java
|
|
@@ -68,7 +68,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 f82db93f88223ffddc55deec8f21efc5b774d900..75ab9f185b3231113dfa387c956a707b403bb2db 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/VoxelShapes.java b/src/main/java/net/minecraft/server/VoxelShapes.java
|
|
index 3c08ae0cf92ab1f5395662e957ab4d9c03e13deb..86f6f082fe2991ea9065b09c9680b76ca1cf7154 100644
|
|
--- a/src/main/java/net/minecraft/server/VoxelShapes.java
|
|
+++ b/src/main/java/net/minecraft/server/VoxelShapes.java
|
|
@@ -21,10 +21,12 @@ public final class VoxelShapes {
|
|
public static final VoxelShape a = create(Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY);
|
|
private static final VoxelShape c = new VoxelShapeArray(new VoxelShapeBitSet(0, 0, 0), new DoubleArrayList(new double[]{0.0D}), new DoubleArrayList(new double[]{0.0D}), new DoubleArrayList(new double[]{0.0D}));
|
|
|
|
+ public static final VoxelShape empty() {return a();} // Paper - OBFHELPER
|
|
public static VoxelShape a() {
|
|
return VoxelShapes.c;
|
|
}
|
|
|
|
+ public static final VoxelShape fullCube() {return b();} // Paper - OBFHELPER
|
|
public static VoxelShape b() {
|
|
return VoxelShapes.b;
|
|
}
|
|
diff --git a/src/main/java/net/minecraft/server/World.java b/src/main/java/net/minecraft/server/World.java
|
|
index 63d3d43f74bed94cd03aa3b7254e66302be861d5..bd5a2eee9cb435a5ee75a39cea231151fba00387 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
|
|
@@ -95,7 +96,7 @@ public abstract class World implements GeneratorAccess, AutoCloseable {
|
|
|
|
protected World(WorldDataMutable worlddatamutable, ResourceKey<World> resourcekey, ResourceKey<DimensionManager> resourcekey1, DimensionManager dimensionmanager, Supplier<GameProfilerFiller> supplier, boolean flag, boolean flag1, long i, org.bukkit.generator.ChunkGenerator gen, org.bukkit.World.Environment env) {
|
|
this.spigotConfig = new org.spigotmc.SpigotWorldConfig(((WorldDataServer) worlddatamutable).getName()); // Spigot
|
|
- this.paperConfig = new com.destroystokyo.paper.PaperWorldConfig(worlddata.getName(), this.spigotConfig); // Paper
|
|
+ this.paperConfig = new com.destroystokyo.paper.PaperWorldConfig((((WorldDataServer)worlddatamutable).getName()), this.spigotConfig); // Paper
|
|
this.generator = gen;
|
|
this.world = new CraftWorld((WorldServer) this, gen, env);
|
|
this.ticksPerAnimalSpawns = this.getServer().getTicksPerAnimalSpawns(); // CraftBukkit
|
|
@@ -247,6 +248,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 ((WorldServer)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.getChunkProvider().getChunkAt(i, j, chunkstatus, flag);
|
|
@@ -405,8 +439,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/WorldBorder.java b/src/main/java/net/minecraft/server/WorldBorder.java
|
|
index ddc4570139fd9b82f4b740697e84775f5ff0a75a..d039e715624d33fc3ec9e87d5ad992415e7dc6b9 100644
|
|
--- a/src/main/java/net/minecraft/server/WorldBorder.java
|
|
+++ b/src/main/java/net/minecraft/server/WorldBorder.java
|
|
@@ -37,6 +37,7 @@ public class WorldBorder {
|
|
return this.b(entity.locX(), entity.locZ());
|
|
}
|
|
|
|
+ public final VoxelShape asVoxelShape(){ return c();} // Paper - OBFHELPER
|
|
public VoxelShape c() {
|
|
return this.j.m();
|
|
}
|
|
diff --git a/src/main/java/net/minecraft/server/WorldServer.java b/src/main/java/net/minecraft/server/WorldServer.java
|
|
index ac7c9efc53123e4fa964b33f67cf8196846056cc..f45b5ddc66024e993ac94436ee25a03e085742b4 100644
|
|
--- a/src/main/java/net/minecraft/server/WorldServer.java
|
|
+++ b/src/main/java/net/minecraft/server/WorldServer.java
|
|
@@ -56,7 +56,7 @@ public class WorldServer extends World implements GeneratorAccessSeed {
|
|
private final Map<UUID, Entity> entitiesByUUID = Maps.newHashMap();
|
|
private final Queue<Entity> entitiesToAdd = Queues.newArrayDeque();
|
|
private final List<EntityPlayer> players = Lists.newArrayList();
|
|
- private final ChunkProviderServer chunkProvider;
|
|
+ public final ChunkProviderServer chunkProvider; // Paper - public
|
|
boolean tickingEntities;
|
|
private final MinecraftServer server;
|
|
public final WorldDataServer worldDataServer; // CraftBukkit - type
|
|
diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java
|
|
index 993fae70fe6844581b2a52bd4df8088539d49b8a..2097ea9b2683bf4063fbcb601720e8d297fb3b13 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/scheduler/CraftScheduler.java b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java
|
|
index 9ad17c560c8d99a396543ab9f97c34de648f6544..533c0bc55fc7ac4cc1f493f898a85a6617371031 100644
|
|
--- a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java
|
|
+++ b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java
|
|
@@ -43,6 +43,7 @@ import org.bukkit.scheduler.BukkitWorker;
|
|
*/
|
|
public class CraftScheduler implements BukkitScheduler {
|
|
|
|
+ static Plugin MINECRAFT = new MinecraftInternalPlugin();
|
|
/**
|
|
* Counter for IDs. Order doesn't matter, only uniqueness.
|
|
*/
|
|
@@ -177,6 +178,11 @@ public class CraftScheduler implements BukkitScheduler {
|
|
runTaskTimer(plugin, (Object) task, delay, period);
|
|
}
|
|
|
|
+ public BukkitTask scheduleInternalTask(Runnable run, int delay, String taskName) {
|
|
+ final CraftTask task = new CraftTask(run, nextId(), taskName);
|
|
+ return handle(task, delay);
|
|
+ }
|
|
+
|
|
public BukkitTask runTaskTimer(Plugin plugin, Object runnable, long delay, long period) {
|
|
validate(plugin, runnable);
|
|
if (delay < 0L) {
|
|
diff --git a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftTask.java b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftTask.java
|
|
index 3f55381c152b9841b524f623c9b32360e97cb8ed..d85e21b75054067b926ecfee89d62c6dd0744189 100644
|
|
--- a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftTask.java
|
|
+++ b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftTask.java
|
|
@@ -39,6 +39,21 @@ public class CraftTask implements BukkitTask, Runnable { // Spigot
|
|
CraftTask(final Object task) {
|
|
this(null, task, CraftTask.NO_REPEATING, CraftTask.NO_REPEATING);
|
|
}
|
|
+ // Paper start
|
|
+ public String taskName = null;
|
|
+ boolean internal = false;
|
|
+ CraftTask(final Object task, int id, String taskName) {
|
|
+ this.rTask = (Runnable) task;
|
|
+ this.cTask = null;
|
|
+ this.plugin = CraftScheduler.MINECRAFT;
|
|
+ this.taskName = taskName;
|
|
+ this.internal = true;
|
|
+ this.id = id;
|
|
+ this.period = CraftTask.NO_REPEATING;
|
|
+ this.taskName = taskName;
|
|
+ this.timings = null; // Will be changed in later patch
|
|
+ }
|
|
+ // Paper end
|
|
|
|
CraftTask(final Plugin plugin, final Object task, final int id, final long period) {
|
|
this.plugin = plugin;
|
|
diff --git a/src/main/java/org/bukkit/craftbukkit/scheduler/MinecraftInternalPlugin.java b/src/main/java/org/bukkit/craftbukkit/scheduler/MinecraftInternalPlugin.java
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..49dc0c441b9dd7e7745cf15ced67f383ebee1f99
|
|
--- /dev/null
|
|
+++ b/src/main/java/org/bukkit/craftbukkit/scheduler/MinecraftInternalPlugin.java
|
|
@@ -0,0 +1,132 @@
|
|
+package org.bukkit.craftbukkit.scheduler;
|
|
+
|
|
+
|
|
+import org.bukkit.Server;
|
|
+import org.bukkit.command.Command;
|
|
+import org.bukkit.command.CommandSender;
|
|
+import org.bukkit.configuration.file.FileConfiguration;
|
|
+import org.bukkit.generator.ChunkGenerator;
|
|
+import org.bukkit.plugin.PluginBase;
|
|
+import org.bukkit.plugin.PluginDescriptionFile;
|
|
+import org.bukkit.plugin.PluginLoader;
|
|
+import org.bukkit.plugin.PluginLogger;
|
|
+
|
|
+import java.io.File;
|
|
+import java.io.InputStream;
|
|
+import java.util.List;
|
|
+
|
|
+public class MinecraftInternalPlugin extends PluginBase {
|
|
+ private boolean enabled = true;
|
|
+
|
|
+ private final String pluginName;
|
|
+ private PluginDescriptionFile pdf;
|
|
+
|
|
+ public MinecraftInternalPlugin() {
|
|
+ this.pluginName = "Minecraft";
|
|
+ pdf = new PluginDescriptionFile(pluginName, "1.0", "nms");
|
|
+ }
|
|
+
|
|
+ public void setEnabled(boolean enabled) {
|
|
+ this.enabled = enabled;
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public File getDataFolder() {
|
|
+ throw new UnsupportedOperationException("Not supported.");
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public PluginDescriptionFile getDescription() {
|
|
+ return pdf;
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public FileConfiguration getConfig() {
|
|
+ throw new UnsupportedOperationException("Not supported.");
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public InputStream getResource(String filename) {
|
|
+ throw new UnsupportedOperationException("Not supported.");
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public void saveConfig() {
|
|
+ throw new UnsupportedOperationException("Not supported.");
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public void saveDefaultConfig() {
|
|
+ throw new UnsupportedOperationException("Not supported.");
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public void saveResource(String resourcePath, boolean replace) {
|
|
+ throw new UnsupportedOperationException("Not supported.");
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public void reloadConfig() {
|
|
+ throw new UnsupportedOperationException("Not supported.");
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public PluginLogger getLogger() {
|
|
+ throw new UnsupportedOperationException("Not supported.");
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public PluginLoader getPluginLoader() {
|
|
+ throw new UnsupportedOperationException("Not supported.");
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public Server getServer() {
|
|
+ throw new UnsupportedOperationException("Not supported.");
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public boolean isEnabled() {
|
|
+ return enabled;
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public void onDisable() {
|
|
+ throw new UnsupportedOperationException("Not supported.");
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public void onLoad() {
|
|
+ throw new UnsupportedOperationException("Not supported.");
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public void onEnable() {
|
|
+ throw new UnsupportedOperationException("Not supported.");
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public boolean isNaggable() {
|
|
+ throw new UnsupportedOperationException("Not supported.");
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public void setNaggable(boolean canNag) {
|
|
+ throw new UnsupportedOperationException("Not supported.");
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public ChunkGenerator getDefaultWorldGenerator(String worldName, String id) {
|
|
+ throw new UnsupportedOperationException("Not supported.");
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
|
|
+ throw new UnsupportedOperationException("Not supported.");
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public List<String> onTabComplete(CommandSender sender, Command command, String alias, String[] args) {
|
|
+ throw new UnsupportedOperationException("Not supported.");
|
|
+ }
|
|
+}
|
|
diff --git a/src/main/java/org/bukkit/craftbukkit/util/DummyGeneratorAccess.java b/src/main/java/org/bukkit/craftbukkit/util/DummyGeneratorAccess.java
|
|
index 254bc29d7188f08bac865bd56765f12dba753adc..8a4eb405f7e4a0e0a79a6b264610da1f970df79b 100644
|
|
--- a/src/main/java/org/bukkit/craftbukkit/util/DummyGeneratorAccess.java
|
|
+++ b/src/main/java/org/bukkit/craftbukkit/util/DummyGeneratorAccess.java
|
|
@@ -186,4 +186,22 @@ public class DummyGeneratorAccess implements GeneratorAccess {
|
|
public boolean a(BlockPosition blockposition, boolean flag, Entity entity, int i) {
|
|
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 1aec70a1f1a9d8fd2cd06bde4033e19e769ab331..f72c13bedaa6fa45e26f5dcad564835bdd4af61f 100644
|
|
--- a/src/main/java/org/bukkit/craftbukkit/util/UnsafeList.java
|
|
+++ b/src/main/java/org/bukkit/craftbukkit/util/UnsafeList.java
|
|
@@ -17,7 +17,7 @@ import java.util.RandomAccess;
|
|
public class UnsafeList<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;
|
|
|