Mirror von
https://github.com/PaperMC/Paper.git
synchronisiert 2024-12-15 02:50:09 +01:00
614a664bd3
Mark chunks that are blocking main thread for world generation as urgent Implements a general priority system so that chunks that are sorted in the generator queues can prioritize certain chunks over another. Urgent chunks will jump to the front of the line, ensuring that a sync chunk load on an ungenerated chunk does not lag the server for a long period of time if the servers generator queues are filled with lots of chunks already. This massively reduces the lag spikes from sync chunk gens. Then we further prioritize loading order so nearby chunks have higher priority than distant chunks, reducing the pressure a high no tick view distance holds on you. Chunks in front of the player have higher priority, to help with fast traveling players keep up with their movement. This commit also improves single core cpu scenarios in that we will now automatically disable Async Chunks as well as Minecrafts thread pool. It is never recommended to use async chunks on a single CPU as context switching will be slower than just running it all on main. This also bumps the number of server worker threads by default too. Mojang does not utilize the workers in an effecient manner, resulting in them using barely any sustained CPU. So give it more workers so more chunks can be processed concurrently This change also improves urgent chunk loading, so players flying into unloaded chunks will hurt a little bit less (but still hurt) Ping #3395 #3363 (Not marking as closed, we need to make prevent moving work)
4522 Zeilen
183 KiB
Diff
4522 Zeilen
183 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..9841212a60346870535e81b22851261e12380650
|
|
--- /dev/null
|
|
+++ b/src/main/java/com/destroystokyo/paper/util/pooled/PooledObjects.java
|
|
@@ -0,0 +1,174 @@
|
|
+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.concurrent.ThreadLocalRandom;
|
|
+import java.util.concurrent.atomic.AtomicLong;
|
|
+import java.util.concurrent.locks.ReentrantLock;
|
|
+import java.util.function.Consumer;
|
|
+
|
|
+/**
|
|
+ * Object pooling with thread safe, low contention design. Pooled objects have no additional object overhead
|
|
+ * due to usage of ArrayDeque per insertion/removal unless a resizing is needed in the buckets.
|
|
+ * Supports up to bucket size (default 8) threads concurrently accessing if all buckets have a value.
|
|
+ * Releasing may conditionally have contention if multiple buckets have same current size, but randomization will be used.
|
|
+ *
|
|
+ * Original interface API by Spottedleaf
|
|
+ * Implementation by Aikar <aikar@aikar.co>
|
|
+ * @license MIT
|
|
+ */
|
|
+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, 16);
|
|
+
|
|
+ private final PooledObjectHandler<E> handler;
|
|
+ private final int bucketCount;
|
|
+ private final int bucketSize;
|
|
+ private final ArrayDeque<E>[] buckets;
|
|
+ private final ReentrantLock[] locks;
|
|
+ private final AtomicLong bucketIdCounter = new AtomicLong(0);
|
|
+
|
|
+ public PooledObjects(final PooledObjectHandler<E> handler, int maxPoolSize) {
|
|
+ this(handler, maxPoolSize, 8);
|
|
+ }
|
|
+ public PooledObjects(final PooledObjectHandler<E> handler, int maxPoolSize, int bucketCount) {
|
|
+ if (handler == null) {
|
|
+ throw new NullPointerException("Handler must not be null");
|
|
+ }
|
|
+ if (maxPoolSize <= 0) {
|
|
+ throw new IllegalArgumentException("Max pool size must be greater-than 0");
|
|
+ }
|
|
+ if (bucketCount < 1) {
|
|
+ throw new IllegalArgumentException("Bucket count must be greater-than 0");
|
|
+ }
|
|
+ int remainder = maxPoolSize % bucketCount;
|
|
+ if (remainder > 0) {
|
|
+ // Auto adjust up to the next bucket divisible size
|
|
+ maxPoolSize = maxPoolSize - remainder + bucketCount;
|
|
+ }
|
|
+ //noinspection unchecked
|
|
+ this.buckets = new ArrayDeque[bucketCount];
|
|
+ this.locks = new ReentrantLock[bucketCount];
|
|
+ this.bucketCount = bucketCount;
|
|
+ this.handler = handler;
|
|
+ this.bucketSize = maxPoolSize / bucketCount;
|
|
+ for (int i = 0; i < bucketCount; i++) {
|
|
+ this.buckets[i] = new ArrayDeque<>(bucketSize / 4);
|
|
+ this.locks[i] = new ReentrantLock();
|
|
+ }
|
|
+ }
|
|
+
|
|
+ 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 long size() {
|
|
+ long size = 0;
|
|
+ for (int i = 0; i < bucketCount; i++) {
|
|
+ size += this.buckets[i].size();
|
|
+ }
|
|
+
|
|
+ return size;
|
|
+ }
|
|
+ public E acquire() {
|
|
+ for (int base = (int) (this.bucketIdCounter.getAndIncrement() % bucketCount), i = 0; i < bucketCount; i++ ) {
|
|
+ int bucketId = (base + i) % bucketCount;
|
|
+ if (this.buckets[bucketId].isEmpty()) continue;
|
|
+ // lock will alloc an object if blocked, so spinwait instead since lock duration is super fast
|
|
+ lockBucket(bucketId);
|
|
+ E value = this.buckets[bucketId].poll();
|
|
+ this.locks[bucketId].unlock();
|
|
+ if (value != null) {
|
|
+ this.handler.onAcquire(value);
|
|
+ return value;
|
|
+ }
|
|
+ }
|
|
+ return this.handler.createNew();
|
|
+ }
|
|
+
|
|
+ private void lockBucket(int bucketId) {
|
|
+ // lock will alloc an object if blocked, try to avoid unless 2 failures
|
|
+ ReentrantLock lock = this.locks[bucketId];
|
|
+ if (!lock.tryLock()) {
|
|
+ Thread.yield();
|
|
+ } else {
|
|
+ return;
|
|
+ }
|
|
+ if (!lock.tryLock()) {
|
|
+ Thread.yield();
|
|
+ lock.lock();
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public void release(final E value) {
|
|
+ int attempts = 3; // cap on contention
|
|
+ do {
|
|
+ // find least filled bucket before locking
|
|
+ int smallestIdx = -1;
|
|
+ int smallest = Integer.MAX_VALUE;
|
|
+ for (int i = 0; i < bucketCount; i++ ) {
|
|
+ ArrayDeque<E> bucket = this.buckets[i];
|
|
+ int size = bucket.size();
|
|
+ if (size < this.bucketSize && (smallestIdx == -1 || size < smallest || (size == smallest && ThreadLocalRandom.current().nextBoolean()))) {
|
|
+ smallestIdx = i;
|
|
+ smallest = size;
|
|
+ }
|
|
+ }
|
|
+ if (smallestIdx == -1) return; // Can not find a bucket to fill
|
|
+
|
|
+ lockBucket(smallestIdx);
|
|
+ ArrayDeque<E> bucket = this.buckets[smallestIdx];
|
|
+ if (bucket.size() < this.bucketSize) {
|
|
+ this.handler.onRelease(value);
|
|
+ bucket.push(value);
|
|
+ this.locks[smallestIdx].unlock();
|
|
+ return;
|
|
+ } else {
|
|
+ this.locks[smallestIdx].unlock();
|
|
+ }
|
|
+ } while (attempts-- > 0);
|
|
+ }
|
|
+
|
|
+ /** This object is restricted from interacting with any pool */
|
|
+ public interface PooledObjectHandler<E> {
|
|
+
|
|
+ /**
|
|
+ * Must return a non-null object
|
|
+ */
|
|
+ E createNew();
|
|
+
|
|
+ default void onAcquire(final E value) {}
|
|
+
|
|
+ default void onRelease(final E 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 4f60b931a143ebf70a8469913ec445ff13da4d8d..3e90b57b6fd5dcb6cb1325861306e2ff84d0cccb 100644
|
|
--- a/src/main/java/net/minecraft/server/AxisAlignedBB.java
|
|
+++ b/src/main/java/net/minecraft/server/AxisAlignedBB.java
|
|
@@ -186,10 +186,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;
|
|
}
|
|
@@ -202,6 +204,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/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/BlockDataAbstract.java b/src/main/java/net/minecraft/server/BlockDataAbstract.java
|
|
index 1cf97cefc9d113583214f340e72b35d5560d1e5d..2040f183490d515b913df048ae8ab07bbecaa9a4 100644
|
|
--- a/src/main/java/net/minecraft/server/BlockDataAbstract.java
|
|
+++ b/src/main/java/net/minecraft/server/BlockDataAbstract.java
|
|
@@ -78,6 +78,7 @@ public abstract class BlockDataAbstract<O, S> implements IBlockDataHolder<S> {
|
|
return Collections.unmodifiableCollection(this.d.keySet());
|
|
}
|
|
|
|
+ public final <T extends Comparable<T>> boolean hasProperty(IBlockState<T> iblockstate) { return this.b(iblockstate); } // Paper - OBFHELPER
|
|
public <T extends Comparable<T>> boolean b(IBlockState<T> iblockstate) {
|
|
return this.d.containsKey(iblockstate);
|
|
}
|
|
diff --git a/src/main/java/net/minecraft/server/BlockPosition.java b/src/main/java/net/minecraft/server/BlockPosition.java
|
|
index c88a62f6b72a8851b95587bb49c898569d74e0c6..f8ac39e1b019b0918996f745d99f6ed09db0fd11 100644
|
|
--- a/src/main/java/net/minecraft/server/BlockPosition.java
|
|
+++ b/src/main/java/net/minecraft/server/BlockPosition.java
|
|
@@ -120,6 +120,7 @@ public class BlockPosition extends BaseBlockPosition implements MinecraftSeriali
|
|
return d0 == 0.0D && d1 == 0.0D && d2 == 0.0D ? this : new BlockPosition((double) this.getX() + d0, (double) this.getY() + d1, (double) this.getZ() + d2);
|
|
}
|
|
|
|
+ public BlockPosition add(int i, int j, int k) {return b(i, j, k);} // Paper - OBFHELPER
|
|
public BlockPosition b(int i, int j, int k) {
|
|
return i == 0 && j == 0 && k == 0 ? this : new BlockPosition(this.getX() + i, this.getY() + j, this.getZ() + k);
|
|
}
|
|
@@ -210,6 +211,8 @@ public class BlockPosition extends BaseBlockPosition implements MinecraftSeriali
|
|
return new BlockPosition(this.getY() * baseblockposition.getZ() - this.getZ() * baseblockposition.getY(), this.getZ() * baseblockposition.getX() - this.getX() * baseblockposition.getZ(), this.getX() * baseblockposition.getY() - this.getY() * baseblockposition.getX());
|
|
}
|
|
|
|
+ @Deprecated // We'll replace this...
|
|
+ public BlockPosition asImmutable() { return immutableCopy(); } // Paper - OBFHELPER
|
|
public BlockPosition immutableCopy() {
|
|
return this;
|
|
}
|
|
@@ -264,6 +267,7 @@ public class BlockPosition extends BaseBlockPosition implements MinecraftSeriali
|
|
super(i, j, k);
|
|
}
|
|
|
|
+ public static BlockPosition.PooledBlockPosition acquire() { return r(); } // Paper - OBFHELPER
|
|
public static BlockPosition.PooledBlockPosition r() {
|
|
return f(0, 0, 0);
|
|
}
|
|
@@ -402,6 +406,7 @@ public class BlockPosition extends BaseBlockPosition implements MinecraftSeriali
|
|
return this.d;
|
|
}
|
|
|
|
+ public BlockPosition.MutableBlockPosition setValues(int i, int j, int k) { return d(i, j, k);} // Paper - OBFHELPER
|
|
public BlockPosition.MutableBlockPosition d(int i, int j, int k) {
|
|
this.b = i;
|
|
this.c = j;
|
|
@@ -413,6 +418,7 @@ public class BlockPosition extends BaseBlockPosition implements MinecraftSeriali
|
|
return this.c(entity.locX(), entity.locY(), entity.locZ());
|
|
}
|
|
|
|
+ public BlockPosition.MutableBlockPosition setValues(double d0, double d1, double d2) { return c(d0, d1, d2);} // Paper - OBFHELPER
|
|
public BlockPosition.MutableBlockPosition c(double d0, double d1, double d2) {
|
|
return this.d(MathHelper.floor(d0), MathHelper.floor(d1), MathHelper.floor(d2));
|
|
}
|
|
@@ -441,14 +447,17 @@ public class BlockPosition extends BaseBlockPosition implements MinecraftSeriali
|
|
return this.d(this.b + i, this.c + j, this.d + k);
|
|
}
|
|
|
|
+ public final void setX(final int x) { this.o(x); } // Paper - OBFHELPER
|
|
public void o(int i) {
|
|
this.b = i;
|
|
}
|
|
|
|
+ public final void setY(final int y) { this.p(y); } // Paper - OBFHELPER
|
|
public void p(int i) {
|
|
this.c = i;
|
|
}
|
|
|
|
+ public final void setZ(final int z) { this.q(z); } // Paper - OBFHELPER
|
|
public void q(int i) {
|
|
this.d = i;
|
|
}
|
|
diff --git a/src/main/java/net/minecraft/server/Chunk.java b/src/main/java/net/minecraft/server/Chunk.java
|
|
index 55373cae078ddaf6c7c974abf59183698f669c24..b39ce329aa0b2e8da679a7b658f707f1da1f1a99 100644
|
|
--- a/src/main/java/net/minecraft/server/Chunk.java
|
|
+++ b/src/main/java/net/minecraft/server/Chunk.java
|
|
@@ -25,7 +25,7 @@ import org.apache.logging.log4j.Logger;
|
|
public class Chunk implements IChunkAccess {
|
|
|
|
private static final Logger LOGGER = LogManager.getLogger();
|
|
- public static final ChunkSection a = null;
|
|
+ public static final ChunkSection a = null; public static final ChunkSection EMPTY_CHUNK_SECTION = Chunk.a; // Paper - OBFHELPER
|
|
private final ChunkSection[] sections;
|
|
private BiomeStorage d;
|
|
private final Map<BlockPosition, NBTTagCompound> e;
|
|
@@ -48,7 +48,7 @@ public class Chunk implements IChunkAccess {
|
|
private Supplier<PlayerChunk.State> u;
|
|
@Nullable
|
|
private Consumer<Chunk> v;
|
|
- private final ChunkCoordIntPair loc;
|
|
+ private final ChunkCoordIntPair loc; public final long coordinateKey; // Paper - cache coordinate key
|
|
private volatile boolean x;
|
|
|
|
public Chunk(World world, ChunkCoordIntPair chunkcoordintpair, BiomeStorage biomestorage) {
|
|
@@ -65,7 +65,7 @@ public class Chunk implements IChunkAccess {
|
|
this.n = new ShortList[16];
|
|
this.entitySlices = (List[]) (new List[16]); // Spigot
|
|
this.world = world;
|
|
- this.loc = chunkcoordintpair;
|
|
+ this.loc = chunkcoordintpair; this.coordinateKey = MCUtil.getCoordinateKey(chunkcoordintpair); // Paper - cache coordinate key
|
|
this.i = chunkconverter;
|
|
HeightMap.Type[] aheightmap_type = HeightMap.Type.values();
|
|
int j = aheightmap_type.length;
|
|
@@ -108,6 +108,107 @@ 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 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();
|
|
@@ -213,6 +314,18 @@ public class Chunk implements IChunkAccess {
|
|
}
|
|
}
|
|
|
|
+ // Paper start - If loaded util
|
|
+ @Override
|
|
+ public Fluid getFluidIfLoaded(BlockPosition blockposition) {
|
|
+ return this.getFluid(blockposition);
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public IBlockData getTypeIfLoaded(BlockPosition blockposition) {
|
|
+ return this.getType(blockposition);
|
|
+ }
|
|
+ // Paper end
|
|
+
|
|
@Override
|
|
public Fluid getFluid(BlockPosition blockposition) {
|
|
return this.a(blockposition.getX(), blockposition.getY(), blockposition.getZ());
|
|
@@ -352,6 +465,7 @@ public class Chunk implements IChunkAccess {
|
|
entity.chunkX = this.loc.x;
|
|
entity.chunkY = k;
|
|
entity.chunkZ = this.loc.z;
|
|
+ this.entities.add(entity); // Paper - per chunk entity list
|
|
this.entitySlices[k].add(entity);
|
|
}
|
|
|
|
@@ -374,6 +488,7 @@ public class Chunk implements IChunkAccess {
|
|
}
|
|
|
|
this.entitySlices[i].remove(entity);
|
|
+ this.entities.remove(entity); // Paper
|
|
}
|
|
|
|
@Override
|
|
@@ -395,6 +510,7 @@ public class Chunk implements IChunkAccess {
|
|
return this.a(blockposition, Chunk.EnumTileEntityState.CHECK);
|
|
}
|
|
|
|
+ @Nullable public final TileEntity getTileEntityImmediately(BlockPosition pos) { return this.a(pos, EnumTileEntityState.IMMEDIATE); } // Paper - OBFHELPER
|
|
@Nullable
|
|
public TileEntity a(BlockPosition blockposition, Chunk.EnumTileEntityState chunk_enumtileentitystate) {
|
|
// CraftBukkit start
|
|
@@ -506,7 +622,25 @@ public class Chunk implements IChunkAccess {
|
|
|
|
// CraftBukkit start
|
|
public void loadCallback() {
|
|
+ // Paper start - neighbour cache
|
|
+ int chunkX = this.loc.x;
|
|
+ int chunkZ = this.loc.z;
|
|
+ ChunkProviderServer chunkProvider = ((WorldServer)this.world).getChunkProvider();
|
|
+ for (int dx = -NEIGHBOUR_CACHE_RADIUS; dx <= NEIGHBOUR_CACHE_RADIUS; ++dx) {
|
|
+ for (int dz = -NEIGHBOUR_CACHE_RADIUS; dz <= NEIGHBOUR_CACHE_RADIUS; ++dz) {
|
|
+ Chunk neighbour = chunkProvider.getChunkAtIfLoadedMainThreadNoCache(chunkX + dx, chunkZ + dz);
|
|
+ if (neighbour != null) {
|
|
+ neighbour.setNeighbourLoaded(-dx, -dz, this);
|
|
+ // should be in cached already
|
|
+ this.setNeighbourLoaded(dx, dz, neighbour);
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ this.setNeighbourLoaded(0, 0, this);
|
|
+ this.loadedTicketLevel = true;
|
|
+ // Paper end - neighbour cache
|
|
org.bukkit.Server server = this.world.getServer();
|
|
+ ((WorldServer)this.world).getChunkProvider().addLoadedChunk(this); // Paper
|
|
if (server != null) {
|
|
/*
|
|
* If it's a new world, the first few chunks are generated inside
|
|
@@ -545,6 +679,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 11c4d23ba988dac5f3fd85142083c77fb603e673..a03e4c5b8693395f4a145ca565b074d6aed1913f 100644
|
|
--- a/src/main/java/net/minecraft/server/ChunkCache.java
|
|
+++ b/src/main/java/net/minecraft/server/ChunkCache.java
|
|
@@ -8,7 +8,7 @@ public class ChunkCache implements IBlockAccess, ICollisionAccess {
|
|
protected final int b;
|
|
protected final IChunkAccess[][] c;
|
|
protected boolean d;
|
|
- protected final World e;
|
|
+ protected final World e; protected final World getWorld() { return e; } // Paper - OBFHELPER
|
|
|
|
public ChunkCache(World world, BlockPosition blockposition, BlockPosition blockposition1) {
|
|
this.e = world;
|
|
@@ -27,7 +27,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
|
|
}
|
|
}
|
|
|
|
@@ -52,7 +52,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)));
|
|
@@ -71,6 +71,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 260644bf0be4c5b2d96033f11382f88231048ce3..f2a19acd84561e746bfc8da0331b5d4055e95327 100644
|
|
--- a/src/main/java/net/minecraft/server/ChunkCoordIntPair.java
|
|
+++ b/src/main/java/net/minecraft/server/ChunkCoordIntPair.java
|
|
@@ -31,7 +31,9 @@ public class ChunkCoordIntPair {
|
|
return pair(this.x, this.z);
|
|
}
|
|
|
|
- public static long pair(int i, int j) {
|
|
+ public static long asLong(final BlockPosition pos) { return pair(pos.getX() >> 4, pos.getZ() >> 4); } // Paper - OBFHELPER
|
|
+ public static long asLong(int x, int z) { return pair(x, z); } // Paper - OBFHELPER
|
|
+ public static long pair(int i, int j) {
|
|
return (long) i & 4294967295L | ((long) j & 4294967295L) << 32;
|
|
}
|
|
|
|
diff --git a/src/main/java/net/minecraft/server/ChunkProviderServer.java b/src/main/java/net/minecraft/server/ChunkProviderServer.java
|
|
index 32c496fa88eb1426ab4996fa1bd9803f60648267..ba2af2abe2dd09eb6801f431a0942bd93755c97e 100644
|
|
--- a/src/main/java/net/minecraft/server/ChunkProviderServer.java
|
|
+++ b/src/main/java/net/minecraft/server/ChunkProviderServer.java
|
|
@@ -23,7 +23,7 @@ public class ChunkProviderServer extends IChunkProvider {
|
|
private final ChunkMapDistance chunkMapDistance;
|
|
public final ChunkGenerator<?> chunkGenerator;
|
|
private final WorldServer world;
|
|
- private final Thread serverThread;
|
|
+ public final Thread serverThread; // Paper - private -> public
|
|
private final LightEngineThreaded lightEngine;
|
|
private final ChunkProviderServer.a serverThreadQueue;
|
|
public final PlayerChunkMap playerChunkMap;
|
|
@@ -35,6 +35,169 @@ public class ChunkProviderServer extends IChunkProvider {
|
|
private final ChunkStatus[] cacheStatus = new ChunkStatus[4];
|
|
private final IChunkAccess[] cacheChunk = new IChunkAccess[4];
|
|
|
|
+ // Paper start
|
|
+ final com.destroystokyo.paper.util.concurrent.WeakSeqLock loadedChunkMapSeqLock = new com.destroystokyo.paper.util.concurrent.WeakSeqLock();
|
|
+ final it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap<Chunk> loadedChunkMap = new it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap<>(8192, 0.5f);
|
|
+
|
|
+ private final Chunk[] lastLoadedChunks = new Chunk[4 * 4];
|
|
+ private final long[] lastLoadedChunkKeys = new long[4 * 4];
|
|
+
|
|
+ {
|
|
+ java.util.Arrays.fill(this.lastLoadedChunkKeys, MCUtil.INVALID_CHUNK_KEY);
|
|
+ }
|
|
+
|
|
+ private static int getCacheKey(int x, int z) {
|
|
+ return x & 3 | ((z & 3) << 2);
|
|
+ }
|
|
+
|
|
+ void addLoadedChunk(Chunk chunk) {
|
|
+ this.loadedChunkMapSeqLock.acquireWrite();
|
|
+ try {
|
|
+ this.loadedChunkMap.put(chunk.coordinateKey, chunk);
|
|
+ } finally {
|
|
+ this.loadedChunkMapSeqLock.releaseWrite();
|
|
+ }
|
|
+
|
|
+ // rewrite cache if we have to
|
|
+ // we do this since we also cache null chunks
|
|
+ int cacheKey = getCacheKey(chunk.getPos().x, chunk.getPos().z);
|
|
+
|
|
+ long cachedKey = this.lastLoadedChunkKeys[cacheKey];
|
|
+ if (cachedKey == chunk.coordinateKey) {
|
|
+ this.lastLoadedChunks[cacheKey] = chunk;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ void removeLoadedChunk(Chunk chunk) {
|
|
+ this.loadedChunkMapSeqLock.acquireWrite();
|
|
+ try {
|
|
+ this.loadedChunkMap.remove(chunk.coordinateKey);
|
|
+ } finally {
|
|
+ this.loadedChunkMapSeqLock.releaseWrite();
|
|
+ }
|
|
+
|
|
+ // rewrite cache if we have to
|
|
+ // we do this since we also cache null chunks
|
|
+ int cacheKey = getCacheKey(chunk.getPos().x, chunk.getPos().z);
|
|
+
|
|
+ long cachedKey = this.lastLoadedChunkKeys[cacheKey];
|
|
+ if (cachedKey == chunk.coordinateKey) {
|
|
+ this.lastLoadedChunks[cacheKey] = null;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public Chunk getChunkAtIfLoadedMainThread(int x, int z) {
|
|
+ int cacheKey = getCacheKey(x, z);
|
|
+ long chunkKey = MCUtil.getCoordinateKey(x, z);
|
|
+
|
|
+ long cachedKey = this.lastLoadedChunkKeys[cacheKey];
|
|
+ if (cachedKey == chunkKey) {
|
|
+ return this.lastLoadedChunks[cacheKey];
|
|
+ }
|
|
+
|
|
+ Chunk ret = this.loadedChunkMap.get(chunkKey);
|
|
+
|
|
+ this.lastLoadedChunkKeys[cacheKey] = chunkKey;
|
|
+ this.lastLoadedChunks[cacheKey] = ret;
|
|
+
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ public Chunk getChunkAtIfLoadedMainThreadNoCache(int x, int z) {
|
|
+ return this.loadedChunkMap.get(MCUtil.getCoordinateKey(x, z));
|
|
+ }
|
|
+
|
|
+ public Chunk getChunkAtMainThread(int x, int z) {
|
|
+ Chunk ret = this.getChunkAtIfLoadedMainThread(x, z);
|
|
+ if (ret != null) {
|
|
+ return ret;
|
|
+ }
|
|
+ return (Chunk)this.getChunkAt(x, z, ChunkStatus.FULL, true);
|
|
+ }
|
|
+
|
|
+ private long chunkFutureAwaitCounter;
|
|
+
|
|
+ public void getEntityTickingChunkAsync(int x, int z, java.util.function.Consumer<Chunk> onLoad) {
|
|
+ if (Thread.currentThread() != this.serverThread) {
|
|
+ this.serverThreadQueue.execute(() -> {
|
|
+ ChunkProviderServer.this.getEntityTickingChunkAsync(x, z, onLoad);
|
|
+ });
|
|
+ return;
|
|
+ }
|
|
+ this.getChunkFutureAsynchronously(x, z, 31, PlayerChunk::getEntityTickingFuture, onLoad);
|
|
+ }
|
|
+
|
|
+ public void getTickingChunkAsync(int x, int z, java.util.function.Consumer<Chunk> onLoad) {
|
|
+ if (Thread.currentThread() != this.serverThread) {
|
|
+ this.serverThreadQueue.execute(() -> {
|
|
+ ChunkProviderServer.this.getTickingChunkAsync(x, z, onLoad);
|
|
+ });
|
|
+ return;
|
|
+ }
|
|
+ this.getChunkFutureAsynchronously(x, z, 32, PlayerChunk::getTickingFuture, onLoad);
|
|
+ }
|
|
+
|
|
+ public void getFullChunkAsync(int x, int z, java.util.function.Consumer<Chunk> onLoad) {
|
|
+ if (Thread.currentThread() != this.serverThread) {
|
|
+ this.serverThreadQueue.execute(() -> {
|
|
+ ChunkProviderServer.this.getFullChunkAsync(x, z, onLoad);
|
|
+ });
|
|
+ return;
|
|
+ }
|
|
+ this.getChunkFutureAsynchronously(x, z, 33, PlayerChunk::getFullChunkFuture, onLoad);
|
|
+ }
|
|
+
|
|
+ private void getChunkFutureAsynchronously(int x, int z, int ticketLevel, Function<PlayerChunk, CompletableFuture<Either<Chunk, PlayerChunk.Failure>>> futureGet, java.util.function.Consumer<Chunk> onLoad) {
|
|
+ if (Thread.currentThread() != this.serverThread) {
|
|
+ throw new IllegalStateException();
|
|
+ }
|
|
+ ChunkCoordIntPair chunkPos = new ChunkCoordIntPair(x, z);
|
|
+ Long identifier = Long.valueOf(this.chunkFutureAwaitCounter++);
|
|
+ this.addTicketAtLevel(TicketType.FUTURE_AWAIT, chunkPos, ticketLevel, identifier);
|
|
+ this.tickDistanceManager();
|
|
+
|
|
+ PlayerChunk chunk = this.playerChunkMap.getUpdatingChunk(chunkPos.pair());
|
|
+
|
|
+ if (chunk == null) {
|
|
+ throw new IllegalStateException("Expected playerchunk " + chunkPos + " in world '" + this.world.getWorld().getName() + "'");
|
|
+ }
|
|
+
|
|
+ CompletableFuture<Either<Chunk, PlayerChunk.Failure>> future = futureGet.apply(chunk);
|
|
+
|
|
+ future.whenCompleteAsync((either, throwable) -> {
|
|
+ try {
|
|
+ if (throwable != null) {
|
|
+ if (throwable instanceof ThreadDeath) {
|
|
+ throw (ThreadDeath)throwable;
|
|
+ }
|
|
+ MinecraftServer.LOGGER.fatal("Failed to complete future await for chunk " + chunkPos.toString() + " in world '" + ChunkProviderServer.this.world.getWorld().getName() + "'", throwable);
|
|
+ } else if (either.right().isPresent()) {
|
|
+ MinecraftServer.LOGGER.fatal("Failed to complete future await for chunk " + chunkPos.toString() + " in world '" + ChunkProviderServer.this.world.getWorld().getName() + "': " + either.right().get().toString());
|
|
+ }
|
|
+
|
|
+ try {
|
|
+ if (onLoad != null) {
|
|
+ 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.addTicketAtLevel(TicketType.UNKNOWN, chunkPos, ticketLevel, chunkPos);
|
|
+ ChunkProviderServer.this.removeTicketAtLevel(TicketType.FUTURE_AWAIT, chunkPos, ticketLevel, identifier);
|
|
+ }
|
|
+ }, this.serverThreadQueue);
|
|
+ }
|
|
+ // Paper end
|
|
+
|
|
+
|
|
public ChunkProviderServer(WorldServer worldserver, File file, DataFixer datafixer, DefinedStructureManager definedstructuremanager, Executor executor, ChunkGenerator<?> chunkgenerator, int i, WorldLoadListener worldloadlistener, Supplier<WorldPersistentData> supplier) {
|
|
this.world = worldserver;
|
|
this.serverThreadQueue = new ChunkProviderServer.a(worldserver);
|
|
@@ -77,6 +240,49 @@ public class ChunkProviderServer extends IChunkProvider {
|
|
this.cacheChunk[0] = ichunkaccess;
|
|
}
|
|
|
|
+ // Paper start - "real" get chunk if loaded
|
|
+ // Note: Partially copied from the getChunkAt method below
|
|
+ @Nullable
|
|
+ public Chunk getChunkAtIfCachedImmediately(int x, int z) {
|
|
+ long k = ChunkCoordIntPair.pair(x, z);
|
|
+
|
|
+ // Note: Bypass cache since we need to check ticket level, and to make this MT-Safe
|
|
+
|
|
+ PlayerChunk playerChunk = this.getChunk(k);
|
|
+ if (playerChunk == null) {
|
|
+ return null;
|
|
+ }
|
|
+
|
|
+ return playerChunk.getFullChunkIfCached();
|
|
+ }
|
|
+
|
|
+ @Nullable
|
|
+ public Chunk getChunkAtIfLoadedImmediately(int x, int z) {
|
|
+ long k = ChunkCoordIntPair.pair(x, z);
|
|
+
|
|
+ if (Thread.currentThread() == this.serverThread) {
|
|
+ return this.getChunkAtIfLoadedMainThread(x, z);
|
|
+ }
|
|
+
|
|
+ Chunk ret = null;
|
|
+ long readlock;
|
|
+ do {
|
|
+ readlock = this.loadedChunkMapSeqLock.acquireRead();
|
|
+ try {
|
|
+ ret = this.loadedChunkMap.get(k);
|
|
+ } catch (Throwable thr) {
|
|
+ if (thr instanceof ThreadDeath) {
|
|
+ throw (ThreadDeath)thr;
|
|
+ }
|
|
+ // re-try, this means a CME occurred...
|
|
+ continue;
|
|
+ }
|
|
+ } while (!this.loadedChunkMapSeqLock.tryReleaseRead(readlock));
|
|
+
|
|
+ return ret;
|
|
+ }
|
|
+ // Paper end
|
|
+
|
|
@Nullable
|
|
@Override
|
|
public IChunkAccess getChunkAt(int i, int j, ChunkStatus chunkstatus, boolean flag) {
|
|
diff --git a/src/main/java/net/minecraft/server/ChunkSection.java b/src/main/java/net/minecraft/server/ChunkSection.java
|
|
index 652067757a6b9510b19c339072f1f4183e8d64b6..638b0e39798a3f75566fcf9ea48b81024e60b471 100644
|
|
--- a/src/main/java/net/minecraft/server/ChunkSection.java
|
|
+++ b/src/main/java/net/minecraft/server/ChunkSection.java
|
|
@@ -132,6 +132,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 7ca3a1d0c592df0038953c9fd81783f9bb5c8beb..2edd9b87146a3d1b6623b0efb17b28b524f18c2f 100644
|
|
--- a/src/main/java/net/minecraft/server/DataBits.java
|
|
+++ b/src/main/java/net/minecraft/server/DataBits.java
|
|
@@ -83,6 +83,7 @@ public class DataBits {
|
|
}
|
|
}
|
|
|
|
+ public long[] getDataBits() { return this.a(); } // Paper - OBFHELPER
|
|
public long[] a() {
|
|
return this.a;
|
|
}
|
|
diff --git a/src/main/java/net/minecraft/server/DataPalette.java b/src/main/java/net/minecraft/server/DataPalette.java
|
|
index 75ba69886872f737ff102cb68ec229feb268f94a..45403fbe308cbc6192536d5df606cf8643a65e13 100644
|
|
--- a/src/main/java/net/minecraft/server/DataPalette.java
|
|
+++ b/src/main/java/net/minecraft/server/DataPalette.java
|
|
@@ -4,10 +4,12 @@ import javax.annotation.Nullable;
|
|
|
|
public interface DataPalette<T> {
|
|
|
|
+ default int getOrCreateIdFor(T object) { return this.a(object); } // Paper - OBFHELPER
|
|
int a(T t0);
|
|
|
|
boolean b(T t0);
|
|
|
|
+ @Nullable default T getObject(int dataBits) { return this.a(dataBits); } // Paper - OBFHELPER
|
|
@Nullable
|
|
T a(int i);
|
|
|
|
diff --git a/src/main/java/net/minecraft/server/DataPaletteBlock.java b/src/main/java/net/minecraft/server/DataPaletteBlock.java
|
|
index 774a8f543424853be5fc8c0367d734ddf196d7f9..d5f5a51872dfabdbb828b6c20d61893aed2efec7 100644
|
|
--- a/src/main/java/net/minecraft/server/DataPaletteBlock.java
|
|
+++ b/src/main/java/net/minecraft/server/DataPaletteBlock.java
|
|
@@ -11,7 +11,7 @@ import java.util.stream.Collectors;
|
|
|
|
public class DataPaletteBlock<T> implements DataPaletteExpandable<T> {
|
|
|
|
- private final DataPalette<T> b;
|
|
+ private final DataPalette<T> b; private final DataPalette<T> getDataPaletteGlobal() { return this.b; } // Paper - OBFHELPER
|
|
private final DataPaletteExpandable<T> c = (i, object) -> {
|
|
return 0;
|
|
};
|
|
@@ -19,9 +19,9 @@ public class DataPaletteBlock<T> implements DataPaletteExpandable<T> {
|
|
private final Function<NBTTagCompound, T> e;
|
|
private final Function<T, NBTTagCompound> f;
|
|
private final T g;
|
|
- protected DataBits a;
|
|
- private DataPalette<T> h;
|
|
- private int i;
|
|
+ protected DataBits a; protected DataBits getDataBits() { return this.a; } // Paper - OBFHELPER
|
|
+ private DataPalette<T> h; private DataPalette<T> getDataPalette() { return this.h; } // Paper - OBFHELPER
|
|
+ private int i; private int getBitsPerObject() { return this.i; } // Paper - OBFHELPER
|
|
private final ReentrantLock j = new ReentrantLock();
|
|
|
|
public void a() {
|
|
@@ -56,6 +56,7 @@ public class DataPaletteBlock<T> implements DataPaletteExpandable<T> {
|
|
return j << 8 | k << 4 | i;
|
|
}
|
|
|
|
+ private void initialize(int bitsPerObject) { this.b(bitsPerObject); } // Paper - OBFHELPER
|
|
private void b(int i) {
|
|
if (i != this.i) {
|
|
this.i = i;
|
|
@@ -133,6 +134,7 @@ public class DataPaletteBlock<T> implements DataPaletteExpandable<T> {
|
|
return t0 == null ? this.g : t0;
|
|
}
|
|
|
|
+ public void writeDataPaletteBlock(PacketDataSerializer packetDataSerializer) { this.b(packetDataSerializer); } // Paper - OBFHELPER
|
|
public void b(PacketDataSerializer packetdataserializer) {
|
|
this.a();
|
|
packetdataserializer.writeByte(this.i);
|
|
diff --git a/src/main/java/net/minecraft/server/Entity.java b/src/main/java/net/minecraft/server/Entity.java
|
|
index f4863852b04c5fa55b79acabe40ce59909b9bbbd..7e01f6a1807f9885a7f4b163ce6bb626d8786a9a 100644
|
|
--- a/src/main/java/net/minecraft/server/Entity.java
|
|
+++ b/src/main/java/net/minecraft/server/Entity.java
|
|
@@ -1033,6 +1033,7 @@ public abstract class Entity implements INamableTileEntity, ICommandListener {
|
|
|
|
}
|
|
|
|
+ public final AxisAlignedBB getCollisionBox(){return au();} //Paper - OBFHELPER
|
|
@Nullable
|
|
public AxisAlignedBB au() {
|
|
return null;
|
|
@@ -1842,6 +1843,7 @@ public abstract class Entity implements INamableTileEntity, ICommandListener {
|
|
return false;
|
|
}
|
|
|
|
+ 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 fe69161e5b9e69c75696a3434a681d0489f3863e..b40c8d2f83a80bcb8925632a1e7d6bb4cc0caebf 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 bdfb1738539d0e0c9043ecb63950a5e7fce1c532..0b06fa2b664830d40cdb93968bc2f8c98415d1bf 100644
|
|
--- a/src/main/java/net/minecraft/server/EntityInsentient.java
|
|
+++ b/src/main/java/net/minecraft/server/EntityInsentient.java
|
|
@@ -146,6 +146,7 @@ public abstract class EntityInsentient extends EntityLiving {
|
|
return this.goalTarget;
|
|
}
|
|
|
|
+ public org.bukkit.craftbukkit.entity.CraftMob getBukkitMob() { return (org.bukkit.craftbukkit.entity.CraftMob) super.getBukkitEntity(); } // Paper
|
|
public void setGoalTarget(@Nullable EntityLiving entityliving) {
|
|
// CraftBukkit start - fire event
|
|
setGoalTarget(entityliving, EntityTargetEvent.TargetReason.UNKNOWN, true);
|
|
diff --git a/src/main/java/net/minecraft/server/EntityLiving.java b/src/main/java/net/minecraft/server/EntityLiving.java
|
|
index 9d1adaaa2c0dbd3773f81a1b54aac98fd16ae26d..3ace8ee854c11abd607dc27b93fe61a0982a73de 100644
|
|
--- a/src/main/java/net/minecraft/server/EntityLiving.java
|
|
+++ b/src/main/java/net/minecraft/server/EntityLiving.java
|
|
@@ -129,6 +129,7 @@ public abstract class EntityLiving extends Entity {
|
|
public org.bukkit.craftbukkit.attribute.CraftAttributeMap craftAttributes;
|
|
public boolean collides = true;
|
|
public boolean canPickUpLoot;
|
|
+ public org.bukkit.craftbukkit.entity.CraftLivingEntity getBukkitLivingEntity() { return (org.bukkit.craftbukkit.entity.CraftLivingEntity) super.getBukkitEntity(); } // Paper
|
|
|
|
@Override
|
|
public float getBukkitYaw() {
|
|
diff --git a/src/main/java/net/minecraft/server/EntityMonster.java b/src/main/java/net/minecraft/server/EntityMonster.java
|
|
index 00c3b666d7b568f1619d885de6ca7ab2ce0daa7a..e5322fbae5140ce784dad49b690767af8c52ce1c 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 541ddc928b9a4e6a7bf5c6a4f64d78422d241d90..26e32aa1dbd6de0ba971c32f8daef0d92d65b683 100644
|
|
--- a/src/main/java/net/minecraft/server/EntityPlayer.java
|
|
+++ b/src/main/java/net/minecraft/server/EntityPlayer.java
|
|
@@ -87,6 +87,8 @@ public class EntityPlayer extends EntityHuman implements ICrafting {
|
|
public Integer clientViewDistance;
|
|
// CraftBukkit end
|
|
|
|
+ public final com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<EntityPlayer> cachedSingleHashSet; // Paper
|
|
+
|
|
public EntityPlayer(MinecraftServer minecraftserver, WorldServer worldserver, GameProfile gameprofile, PlayerInteractManager playerinteractmanager) {
|
|
super((World) worldserver, gameprofile);
|
|
playerinteractmanager.player = this;
|
|
@@ -98,6 +100,8 @@ public class EntityPlayer extends EntityHuman implements ICrafting {
|
|
this.H = 1.0F;
|
|
this.a(worldserver);
|
|
|
|
+ this.cachedSingleHashSet = new com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<>(this); // Paper
|
|
+
|
|
// CraftBukkit start
|
|
this.displayName = this.getName();
|
|
this.canPickUpLoot = true;
|
|
diff --git a/src/main/java/net/minecraft/server/EntityTypes.java b/src/main/java/net/minecraft/server/EntityTypes.java
|
|
index 29e776ca19621b93d5b295d12f0576e6980cf11a..4328273b1fca165320097dbac0650b31fae4e5ca 100644
|
|
--- a/src/main/java/net/minecraft/server/EntityTypes.java
|
|
+++ b/src/main/java/net/minecraft/server/EntityTypes.java
|
|
@@ -4,6 +4,7 @@ import com.mojang.datafixers.DataFixUtils;
|
|
import java.util.Collections;
|
|
import java.util.Optional;
|
|
import java.util.Set; // Paper
|
|
+import java.util.Map; // Paper
|
|
import java.util.UUID;
|
|
import java.util.function.Function;
|
|
import java.util.stream.Stream;
|
|
@@ -290,8 +291,8 @@ public class EntityTypes<T extends Entity> {
|
|
return this.bj.height;
|
|
}
|
|
|
|
- @Nullable
|
|
- public T a(World world) {
|
|
+ public T create(World world) { return this.a(world); } // Paper - OBFHELPER
|
|
+ @Nullable public T a(World world) { // Paper - OBFHELPER
|
|
return this.ba.create(this, world);
|
|
}
|
|
|
|
diff --git a/src/main/java/net/minecraft/server/IAsyncTaskHandler.java b/src/main/java/net/minecraft/server/IAsyncTaskHandler.java
|
|
index 1890c760f9ffd7628d6ae3db40c36f5272379227..7e5ece9d50af7151ad4cc084e3680dae41ac92be 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 3b08770801ca072c853ca422212e40c5db35b2d2..0dff023529ba9e44a7406ad6388cad35730917ba 100644
|
|
--- a/src/main/java/net/minecraft/server/IBlockAccess.java
|
|
+++ b/src/main/java/net/minecraft/server/IBlockAccess.java
|
|
@@ -9,10 +9,24 @@ public interface IBlockAccess {
|
|
@Nullable
|
|
TileEntity getTileEntity(BlockPosition blockposition);
|
|
|
|
+ IBlockData getTypeIfLoaded(BlockPosition blockposition); // Paper - if loaded util
|
|
IBlockData getType(BlockPosition blockposition);
|
|
|
|
+ Fluid getFluidIfLoaded(BlockPosition blockposition); // Paper - if loaded util
|
|
Fluid getFluid(BlockPosition blockposition);
|
|
|
|
+ // Paper start - if loaded util
|
|
+ default Material getMaterialIfLoaded(BlockPosition blockposition) {
|
|
+ IBlockData type = this.getTypeIfLoaded(blockposition);
|
|
+ return type == null ? null : type.getMaterial();
|
|
+ }
|
|
+
|
|
+ default Block getBlockIfLoaded(BlockPosition blockposition) {
|
|
+ IBlockData type = this.getTypeIfLoaded(blockposition);
|
|
+ return type == null ? null : type.getBlock();
|
|
+ }
|
|
+ // Paper end
|
|
+
|
|
default int h(BlockPosition blockposition) {
|
|
return this.getType(blockposition).h();
|
|
}
|
|
diff --git a/src/main/java/net/minecraft/server/IBlockData.java b/src/main/java/net/minecraft/server/IBlockData.java
|
|
index 3a1ad2346b4cae833de90dafb716c56e6e2f606d..181661fc8fe9d46cb36cfffac38c58f0d5003192 100644
|
|
--- a/src/main/java/net/minecraft/server/IBlockData.java
|
|
+++ b/src/main/java/net/minecraft/server/IBlockData.java
|
|
@@ -58,6 +58,7 @@ public class IBlockData extends BlockDataAbstract<Block, IBlockData> implements
|
|
return this.c != null && this.c.f != null ? this.c.f[enumdirection.ordinal()] : VoxelShapes.a(this.j(iblockaccess, blockposition), enumdirection);
|
|
}
|
|
|
|
+ public final boolean exceedsCube(){ return f();} // Paper - OBFHELPER
|
|
public boolean f() {
|
|
return this.c == null || this.c.h;
|
|
}
|
|
diff --git a/src/main/java/net/minecraft/server/IOWorker.java b/src/main/java/net/minecraft/server/IOWorker.java
|
|
index c5658c0779b0e0d51fd4921456b6fef0711d7be3..b90baef0f54bdb8f5ee51cdde149f64e39887ef5 100644
|
|
--- a/src/main/java/net/minecraft/server/IOWorker.java
|
|
+++ b/src/main/java/net/minecraft/server/IOWorker.java
|
|
@@ -22,7 +22,7 @@ public class IOWorker implements AutoCloseable {
|
|
private final Thread b;
|
|
private final AtomicBoolean c = new AtomicBoolean();
|
|
private final Queue<Runnable> d = Queues.newConcurrentLinkedQueue();
|
|
- private final RegionFileCache e;
|
|
+ private final RegionFileCache e; public RegionFileCache getRegionFileCache() { return e; } // Paper - OBFHELPER
|
|
private final Map<ChunkCoordIntPair, IOWorker.a> f = Maps.newLinkedHashMap();
|
|
private boolean g = true;
|
|
private CompletableFuture<Void> h = new CompletableFuture();
|
|
diff --git a/src/main/java/net/minecraft/server/IWorldReader.java b/src/main/java/net/minecraft/server/IWorldReader.java
|
|
index ba315131e1633ce8b9b8824b00d2be47950d6f19..cbe2aa4c0acbdcc0b453fdad9a192a3e264406c6 100644
|
|
--- a/src/main/java/net/minecraft/server/IWorldReader.java
|
|
+++ b/src/main/java/net/minecraft/server/IWorldReader.java
|
|
@@ -4,6 +4,7 @@ import javax.annotation.Nullable;
|
|
|
|
public interface IWorldReader extends IBlockLightAccess, ICollisionAccess, BiomeManager.Provider {
|
|
|
|
+ @Nullable IChunkAccess getChunkIfLoadedImmediately(int x, int z); // Paper - ifLoaded api (we need this since current impl blocks if the chunk is loading)
|
|
@Nullable
|
|
IChunkAccess getChunkAt(int i, int j, ChunkStatus chunkstatus, boolean flag);
|
|
|
|
diff --git a/src/main/java/net/minecraft/server/ItemStack.java b/src/main/java/net/minecraft/server/ItemStack.java
|
|
index 75308712d0642d5ab168de653023349df8aee5ed..aa7501d366b15e7f7f64b7d98a1dccff99f731d2 100644
|
|
--- a/src/main/java/net/minecraft/server/ItemStack.java
|
|
+++ b/src/main/java/net/minecraft/server/ItemStack.java
|
|
@@ -37,10 +37,19 @@ import org.bukkit.event.world.StructureGrowEvent;
|
|
public final class ItemStack {
|
|
|
|
private static final Logger LOGGER = LogManager.getLogger();
|
|
- public static final ItemStack a = new ItemStack((Item) null);
|
|
+ public static final ItemStack a = new ItemStack((Item) null);public static final ItemStack NULL_ITEM = a; // Paper - OBFHELPER
|
|
public static final DecimalFormat b = H();
|
|
private int count;
|
|
private int e;
|
|
+ // Paper start
|
|
+ private org.bukkit.craftbukkit.inventory.CraftItemStack bukkitStack;
|
|
+ public org.bukkit.inventory.ItemStack getBukkitStack() {
|
|
+ if (bukkitStack == null || bukkitStack.getHandle() != this) {
|
|
+ bukkitStack = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(this);
|
|
+ }
|
|
+ return bukkitStack;
|
|
+ }
|
|
+ // Paper end
|
|
@Deprecated
|
|
private Item item;
|
|
private NBTTagCompound tag;
|
|
@@ -593,6 +602,17 @@ public final class ItemStack {
|
|
return this.tag != null ? this.tag.getList("Enchantments", 10) : new NBTTagList();
|
|
}
|
|
|
|
+ // Paper start - (this is just a good no conflict location)
|
|
+ public org.bukkit.inventory.ItemStack asBukkitMirror() {
|
|
+ return CraftItemStack.asCraftMirror(this);
|
|
+ }
|
|
+ public org.bukkit.inventory.ItemStack asBukkitCopy() {
|
|
+ return CraftItemStack.asCraftMirror(this.cloneItemStack());
|
|
+ }
|
|
+ public static ItemStack fromBukkitCopy(org.bukkit.inventory.ItemStack itemstack) {
|
|
+ return CraftItemStack.asNMSCopy(itemstack);
|
|
+ }
|
|
+ // Paper end
|
|
public void setTag(@Nullable NBTTagCompound nbttagcompound) {
|
|
this.tag = nbttagcompound;
|
|
if (this.getItem().usesDurability()) {
|
|
@@ -685,6 +705,7 @@ public final class ItemStack {
|
|
return this.tag != null && this.tag.hasKeyOfType("Enchantments", 9) ? !this.tag.getList("Enchantments", 10).isEmpty() : false;
|
|
}
|
|
|
|
+ public void getOrCreateTagAndSet(String s, NBTBase nbtbase) { a(s, nbtbase);} // Paper - OBFHELPER
|
|
public void a(String s, NBTBase nbtbase) {
|
|
this.getOrCreateTag().set(s, nbtbase);
|
|
}
|
|
@@ -761,6 +782,7 @@ public final class ItemStack {
|
|
// CraftBukkit start
|
|
@Deprecated
|
|
public void setItem(Item item) {
|
|
+ this.bukkitStack = null; // Paper
|
|
this.item = item;
|
|
}
|
|
// CraftBukkit end
|
|
diff --git a/src/main/java/net/minecraft/server/MCUtil.java b/src/main/java/net/minecraft/server/MCUtil.java
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..b40cd1fad5c9e2f0f85c87a559caf2b780814017
|
|
--- /dev/null
|
|
+++ b/src/main/java/net/minecraft/server/MCUtil.java
|
|
@@ -0,0 +1,487 @@
|
|
+package net.minecraft.server;
|
|
+
|
|
+import com.destroystokyo.paper.block.TargetBlockInfo;
|
|
+import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
|
+import org.bukkit.Location;
|
|
+import org.bukkit.block.BlockFace;
|
|
+import org.bukkit.craftbukkit.CraftWorld;
|
|
+import org.bukkit.craftbukkit.util.Waitable;
|
|
+import org.spigotmc.AsyncCatcher;
|
|
+
|
|
+import javax.annotation.Nonnull;
|
|
+import javax.annotation.Nullable;
|
|
+import java.util.List;
|
|
+import java.util.Queue;
|
|
+import java.util.concurrent.CompletableFuture;
|
|
+import java.util.concurrent.ExecutionException;
|
|
+import java.util.concurrent.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.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();
|
|
+ }
|
|
+ };
|
|
+ }
|
|
+
|
|
+ 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 long getCoordinateKey(final BlockPosition blockPos) {
|
|
+ return ((long)(blockPos.getZ() >> 4) << 32) | ((blockPos.getX() >> 4) & 0xFFFFFFFFL);
|
|
+ }
|
|
+
|
|
+ public static long getCoordinateKey(final Entity entity) {
|
|
+ return ((long)(MCUtil.fastFloor(entity.locZ()) >> 4) << 32) | ((MCUtil.fastFloor(entity.locX()) >> 4) & 0xFFFFFFFFL);
|
|
+ }
|
|
+
|
|
+ public static int fastFloor(double x) {
|
|
+ int truncated = (int)x;
|
|
+ return x < (double)truncated ? truncated - 1 : truncated;
|
|
+ }
|
|
+
|
|
+ public static 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;
|
|
+ }
|
|
+
|
|
+ public static int fastFloor(float x) {
|
|
+ int truncated = (int)x;
|
|
+ return x < (double)truncated ? truncated - 1 : truncated;
|
|
+ }
|
|
+
|
|
+ public static long getCoordinateKey(final ChunkCoordIntPair pair) {
|
|
+ return ((long)pair.z << 32) | (pair.x & 0xFFFFFFFFL);
|
|
+ }
|
|
+
|
|
+ public static long getCoordinateKey(final int x, final int z) {
|
|
+ return ((long)z << 32) | (x & 0xFFFFFFFFL);
|
|
+ }
|
|
+
|
|
+ public static int getCoordinateX(final long key) {
|
|
+ return (int)key;
|
|
+ }
|
|
+
|
|
+ public static int getCoordinateZ(final long key) {
|
|
+ return (int)(key >>> 32);
|
|
+ }
|
|
+
|
|
+ public static int getChunkCoordinate(final double coordinate) {
|
|
+ return MCUtil.fastFloor(coordinate) >> 4;
|
|
+ }
|
|
+
|
|
+ public static int getBlockCoordinate(final double coordinate) {
|
|
+ return MCUtil.fastFloor(coordinate);
|
|
+ }
|
|
+
|
|
+ public static long getBlockKey(final int x, final int y, final int z) {
|
|
+ return ((long)x & 0x7FFFFFF) | (((long)z & 0x7FFFFFF) << 27) | ((long)y << 54);
|
|
+ }
|
|
+
|
|
+ public static long getBlockKey(final BlockPosition pos) {
|
|
+ return ((long)pos.getX() & 0x7FFFFFF) | (((long)pos.getZ() & 0x7FFFFFF) << 27) | ((long)pos.getY() << 54);
|
|
+ }
|
|
+
|
|
+ public static long getBlockKey(final Entity entity) {
|
|
+ return getBlockKey(getBlockCoordinate(entity.locX()), getBlockCoordinate(entity.locY()), getBlockCoordinate(entity.locZ()));
|
|
+ }
|
|
+
|
|
+ // assumes the sets have the same comparator, and if this comparator is null then assume T is Comparable
|
|
+ public static <T> void mergeSortedSets(final java.util.function.Consumer<T> consumer, final java.util.Comparator<? super T> comparator, final java.util.SortedSet<T>...sets) {
|
|
+ final it.unimi.dsi.fastutil.objects.ObjectRBTreeSet<T> all = new it.unimi.dsi.fastutil.objects.ObjectRBTreeSet<>(comparator);
|
|
+ // note: this is done in log(n!) ~ nlogn time. It could be improved if it were to mimic what mergesort does.
|
|
+ for (java.util.SortedSet<T> set : sets) {
|
|
+ if (set != null) {
|
|
+ all.addAll(set);
|
|
+ }
|
|
+ }
|
|
+ all.forEach(consumer);
|
|
+ }
|
|
+
|
|
+ private MCUtil() {}
|
|
+
|
|
+
|
|
+ public static boolean isMainThread() {
|
|
+ return MinecraftServer.getServer().isMainThread();
|
|
+ }
|
|
+
|
|
+ private static class DelayedRunnable implements Runnable {
|
|
+
|
|
+ private final int ticks;
|
|
+ private final Runnable run;
|
|
+
|
|
+ private DelayedRunnable(int ticks, Runnable run) {
|
|
+ this.ticks = ticks;
|
|
+ this.run = run;
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public void run() {
|
|
+ if (ticks <= 0) {
|
|
+ run.run();
|
|
+ } else {
|
|
+ scheduleTask(ticks-1, run);
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public static void scheduleTask(int ticks, Runnable runnable) {
|
|
+ // We use post to main instead of process queue as we don't want to process these mid tick if
|
|
+ // Someone uses processQueueWhileWaiting
|
|
+ MinecraftServer.getServer().scheduleOnMain(new DelayedRunnable(ticks, runnable));
|
|
+ }
|
|
+
|
|
+ public static void processQueue() {
|
|
+ Runnable runnable;
|
|
+ Queue<Runnable> processQueue = getProcessQueue();
|
|
+ while ((runnable = processQueue.poll()) != null) {
|
|
+ try {
|
|
+ runnable.run();
|
|
+ } catch (Exception e) {
|
|
+ MinecraftServer.LOGGER.error("Error executing task", e);
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ public static <T> T processQueueWhileWaiting(CompletableFuture <T> future) {
|
|
+ try {
|
|
+ if (isMainThread()) {
|
|
+ while (!future.isDone()) {
|
|
+ try {
|
|
+ return future.get(1, TimeUnit.MILLISECONDS);
|
|
+ } catch (TimeoutException ignored) {
|
|
+ processQueue();
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ return future.get();
|
|
+ } catch (Exception e) {
|
|
+ throw new RuntimeException(e);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public static void ensureMain(Runnable run) {
|
|
+ ensureMain(null, run);
|
|
+ }
|
|
+ /**
|
|
+ * Ensures the target code is running on the main thread
|
|
+ * @param reason
|
|
+ * @param run
|
|
+ * @return
|
|
+ */
|
|
+ public static void ensureMain(String reason, Runnable run) {
|
|
+ if (AsyncCatcher.enabled && Thread.currentThread() != MinecraftServer.getServer().serverThread) {
|
|
+ if (reason != null) {
|
|
+ new IllegalStateException("Asynchronous " + reason + "!").printStackTrace();
|
|
+ }
|
|
+ getProcessQueue().add(run);
|
|
+ return;
|
|
+ }
|
|
+ run.run();
|
|
+ }
|
|
+
|
|
+ private static Queue<Runnable> getProcessQueue() {
|
|
+ return MinecraftServer.getServer().processQueue;
|
|
+ }
|
|
+
|
|
+ public static <T> T ensureMain(Supplier<T> run) {
|
|
+ return ensureMain(null, run);
|
|
+ }
|
|
+ /**
|
|
+ * Ensures the target code is running on the main thread
|
|
+ * @param reason
|
|
+ * @param run
|
|
+ * @param <T>
|
|
+ * @return
|
|
+ */
|
|
+ public static <T> T ensureMain(String reason, Supplier<T> run) {
|
|
+ if (AsyncCatcher.enabled && Thread.currentThread() != MinecraftServer.getServer().serverThread) {
|
|
+ if (reason != null) {
|
|
+ new IllegalStateException("Asynchronous " + reason + "! Blocking thread until it returns ").printStackTrace();
|
|
+ }
|
|
+ Waitable<T> wait = new Waitable<T>() {
|
|
+ @Override
|
|
+ protected T evaluate() {
|
|
+ return run.get();
|
|
+ }
|
|
+ };
|
|
+ getProcessQueue().add(wait);
|
|
+ try {
|
|
+ return wait.get();
|
|
+ } catch (InterruptedException | ExecutionException e) {
|
|
+ e.printStackTrace();
|
|
+ }
|
|
+ return null;
|
|
+ }
|
|
+ return run.get();
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * Calculates distance between 2 entities
|
|
+ * @param e1
|
|
+ * @param e2
|
|
+ * @return
|
|
+ */
|
|
+ public static double distance(Entity e1, Entity e2) {
|
|
+ return Math.sqrt(distanceSq(e1, e2));
|
|
+ }
|
|
+
|
|
+
|
|
+ /**
|
|
+ * Calculates distance between 2 block positions
|
|
+ * @param e1
|
|
+ * @param e2
|
|
+ * @return
|
|
+ */
|
|
+ public static double distance(BlockPosition e1, BlockPosition e2) {
|
|
+ return Math.sqrt(distanceSq(e1, e2));
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * Gets the distance between 2 positions
|
|
+ * @param x1
|
|
+ * @param y1
|
|
+ * @param z1
|
|
+ * @param x2
|
|
+ * @param y2
|
|
+ * @param z2
|
|
+ * @return
|
|
+ */
|
|
+ public static double distance(double x1, double y1, double z1, double x2, double y2, double z2) {
|
|
+ return Math.sqrt(distanceSq(x1, y1, z1, x2, y2, z2));
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * Get's the distance squared between 2 entities
|
|
+ * @param e1
|
|
+ * @param e2
|
|
+ * @return
|
|
+ */
|
|
+ public static double distanceSq(Entity e1, Entity e2) {
|
|
+ return distanceSq(e1.locX(),e1.locY(),e1.locZ(), e2.locX(),e2.locY(),e2.locZ());
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * Gets the distance sqaured between 2 block positions
|
|
+ * @param pos1
|
|
+ * @param pos2
|
|
+ * @return
|
|
+ */
|
|
+ public static double distanceSq(BlockPosition pos1, BlockPosition pos2) {
|
|
+ return distanceSq(pos1.getX(), pos1.getY(), pos1.getZ(), pos2.getX(), pos2.getY(), pos2.getZ());
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * Gets the distance squared between 2 positions
|
|
+ * @param x1
|
|
+ * @param y1
|
|
+ * @param z1
|
|
+ * @param x2
|
|
+ * @param y2
|
|
+ * @param z2
|
|
+ * @return
|
|
+ */
|
|
+ public static double distanceSq(double x1, double y1, double z1, double x2, double y2, double z2) {
|
|
+ return (x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2) + (z1 - z2) * (z1 - z2);
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * Converts a NMS World/BlockPosition to Bukkit Location
|
|
+ * @param world
|
|
+ * @param x
|
|
+ * @param y
|
|
+ * @param z
|
|
+ * @return
|
|
+ */
|
|
+ public static Location toLocation(World world, double x, double y, double z) {
|
|
+ return new Location(world.getWorld(), x, y, z);
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * Converts a NMS World/BlockPosition to Bukkit Location
|
|
+ * @param world
|
|
+ * @param pos
|
|
+ * @return
|
|
+ */
|
|
+ public static Location toLocation(World world, BlockPosition pos) {
|
|
+ return new Location(world.getWorld(), pos.getX(), pos.getY(), pos.getZ());
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * Converts an NMS entity's current location to a Bukkit Location
|
|
+ * @param entity
|
|
+ * @return
|
|
+ */
|
|
+ public static Location toLocation(Entity entity) {
|
|
+ return new Location(entity.getWorld().getWorld(), entity.locX(), entity.locY(), entity.locZ());
|
|
+ }
|
|
+
|
|
+ public static org.bukkit.block.Block toBukkitBlock(World world, BlockPosition pos) {
|
|
+ return world.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ());
|
|
+ }
|
|
+
|
|
+ public static BlockPosition toBlockPosition(Location loc) {
|
|
+ return new BlockPosition(loc.getBlockX(), loc.getBlockY(), loc.getBlockZ());
|
|
+ }
|
|
+
|
|
+ public static boolean isEdgeOfChunk(BlockPosition pos) {
|
|
+ final int modX = pos.getX() & 15;
|
|
+ final int modZ = pos.getZ() & 15;
|
|
+ return (modX == 0 || modX == 15 || modZ == 0 || modZ == 15);
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * Posts a task to be executed asynchronously
|
|
+ * @param run
|
|
+ */
|
|
+ public static void scheduleAsyncTask(Runnable run) {
|
|
+ asyncExecutor.execute(run);
|
|
+ }
|
|
+
|
|
+ @Nullable
|
|
+ public static TileEntityHopper getHopper(World world, BlockPosition pos) {
|
|
+ Chunk chunk = world.getChunkIfLoaded(pos.getX() >> 4, pos.getZ() >> 4);
|
|
+ if (chunk != null && chunk.getType(new BlockPosition(pos.getX(), pos.getY(), pos.getZ())).getBlock() == Blocks.HOPPER) {
|
|
+ TileEntity tileEntity = chunk.getTileEntityImmediately(pos);
|
|
+ if (tileEntity instanceof TileEntityHopper) {
|
|
+ return (TileEntityHopper) tileEntity;
|
|
+ }
|
|
+ }
|
|
+ return null;
|
|
+ }
|
|
+
|
|
+ @Nonnull
|
|
+ public static World getNMSWorld(@Nonnull org.bukkit.World world) {
|
|
+ return ((CraftWorld) world).getHandle();
|
|
+ }
|
|
+
|
|
+ public static World getNMSWorld(@Nonnull org.bukkit.entity.Entity entity) {
|
|
+ return getNMSWorld(entity.getWorld());
|
|
+ }
|
|
+
|
|
+ public static RayTrace.FluidCollisionOption getNMSFluidCollisionOption(TargetBlockInfo.FluidMode fluidMode) {
|
|
+ if (fluidMode == TargetBlockInfo.FluidMode.NEVER) {
|
|
+ return RayTrace.FluidCollisionOption.NONE;
|
|
+ }
|
|
+ if (fluidMode == TargetBlockInfo.FluidMode.SOURCE_ONLY) {
|
|
+ return RayTrace.FluidCollisionOption.SOURCE_ONLY;
|
|
+ }
|
|
+ if (fluidMode == TargetBlockInfo.FluidMode.ALWAYS) {
|
|
+ return RayTrace.FluidCollisionOption.ANY;
|
|
+ }
|
|
+ return null;
|
|
+ }
|
|
+
|
|
+ public static BlockFace toBukkitBlockFace(EnumDirection enumDirection) {
|
|
+ switch (enumDirection) {
|
|
+ case DOWN:
|
|
+ return BlockFace.DOWN;
|
|
+ case UP:
|
|
+ return BlockFace.UP;
|
|
+ case NORTH:
|
|
+ return BlockFace.NORTH;
|
|
+ case SOUTH:
|
|
+ return BlockFace.SOUTH;
|
|
+ case WEST:
|
|
+ return BlockFace.WEST;
|
|
+ case EAST:
|
|
+ return BlockFace.EAST;
|
|
+ default:
|
|
+ return null;
|
|
+ }
|
|
+ }
|
|
+}
|
|
diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java
|
|
index b4a0bd79511a3b1185a165991c937375aeecf3d1..786d38438cc1bd5a736b2dfa80aca9b9c6253e65 100644
|
|
--- a/src/main/java/net/minecraft/server/MinecraftServer.java
|
|
+++ b/src/main/java/net/minecraft/server/MinecraftServer.java
|
|
@@ -741,6 +741,9 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant<TickTas
|
|
}
|
|
|
|
// 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 e85b24a327fb0a17ed28ed3c90cd039c2bdbed6a..75604dbc69d0a416dc9d56ae3f795ed03e120af8 100644
|
|
--- a/src/main/java/net/minecraft/server/NBTTagCompound.java
|
|
+++ b/src/main/java/net/minecraft/server/NBTTagCompound.java
|
|
@@ -60,7 +60,7 @@ public class NBTTagCompound implements NBTBase {
|
|
return "TAG_Compound";
|
|
}
|
|
};
|
|
- private final Map<String, NBTBase> map;
|
|
+ public final Map<String, NBTBase> map; // Paper
|
|
|
|
private NBTTagCompound(Map<String, NBTBase> map) {
|
|
this.map = map;
|
|
@@ -123,11 +123,15 @@ public class NBTTagCompound implements NBTBase {
|
|
this.map.put(s, NBTTagLong.a(i));
|
|
}
|
|
|
|
+ public void setUUID(String prefix, UUID uuid) { a(prefix, uuid); } // Paper - OBFHELPER
|
|
public void a(String s, UUID uuid) {
|
|
this.setLong(s + "Most", uuid.getMostSignificantBits());
|
|
this.setLong(s + "Least", uuid.getLeastSignificantBits());
|
|
}
|
|
|
|
+
|
|
+ @Nullable public UUID getUUID(String prefix) { return a(prefix); } // Paper - OBFHELPER
|
|
+ @Nullable
|
|
public UUID a(String s) {
|
|
return new UUID(this.getLong(s + "Most"), this.getLong(s + "Least"));
|
|
}
|
|
diff --git a/src/main/java/net/minecraft/server/NetworkManager.java b/src/main/java/net/minecraft/server/NetworkManager.java
|
|
index 6700582e362b3ff2e0aa4a203981f75de0f22d89..3ccf1663669c79a63a6b3a9f6dc17a6e25e91915 100644
|
|
--- a/src/main/java/net/minecraft/server/NetworkManager.java
|
|
+++ b/src/main/java/net/minecraft/server/NetworkManager.java
|
|
@@ -159,6 +159,7 @@ public class NetworkManager extends SimpleChannelInboundHandler<Packet<?>> {
|
|
|
|
}
|
|
|
|
+ private void dispatchPacket(Packet<?> packet, @Nullable GenericFutureListener<? extends Future<? super Void>> genericFutureListener) { this.b(packet, genericFutureListener); } // Paper - OBFHELPER
|
|
private void b(Packet<?> packet, @Nullable GenericFutureListener<? extends Future<? super Void>> genericfuturelistener) {
|
|
EnumProtocol enumprotocol = EnumProtocol.a(packet);
|
|
EnumProtocol enumprotocol1 = (EnumProtocol) this.channel.attr(NetworkManager.c).get();
|
|
@@ -199,6 +200,7 @@ public class NetworkManager extends SimpleChannelInboundHandler<Packet<?>> {
|
|
|
|
}
|
|
|
|
+ private void sendPacketQueue() { this.o(); } // Paper - OBFHELPER
|
|
private void o() {
|
|
if (this.channel != null && this.channel.isOpen()) {
|
|
Queue queue = this.packetQueue;
|
|
@@ -327,9 +329,9 @@ public class NetworkManager extends SimpleChannelInboundHandler<Packet<?>> {
|
|
|
|
static class QueuedPacket {
|
|
|
|
- private final Packet<?> a;
|
|
+ private final Packet<?> a; private final Packet<?> getPacket() { return this.a; } // Paper - OBFHELPER
|
|
@Nullable
|
|
- private final GenericFutureListener<? extends Future<? super Void>> b;
|
|
+ private final GenericFutureListener<? extends Future<? super Void>> b; private final GenericFutureListener<? extends Future<? super Void>> getGenericFutureListener() { return this.b; } // Paper - OBFHELPER
|
|
|
|
public QueuedPacket(Packet<?> packet, @Nullable GenericFutureListener<? extends Future<? super Void>> genericfuturelistener) {
|
|
this.a = packet;
|
|
diff --git a/src/main/java/net/minecraft/server/PacketDataSerializer.java b/src/main/java/net/minecraft/server/PacketDataSerializer.java
|
|
index 81b6f4581f8e4d956668535a44342f991d677267..d9574a9ace96d8c5666e62a5aed96a67021b91d8 100644
|
|
--- a/src/main/java/net/minecraft/server/PacketDataSerializer.java
|
|
+++ b/src/main/java/net/minecraft/server/PacketDataSerializer.java
|
|
@@ -33,6 +33,7 @@ public class PacketDataSerializer extends ByteBuf {
|
|
this.a = bytebuf;
|
|
}
|
|
|
|
+ public static int countBytes(int i) { return PacketDataSerializer.a(i); } // Paper - OBFHELPER
|
|
public static int a(int i) {
|
|
for (int j = 1; j < 5; ++j) {
|
|
if ((i & -1 << j * 7) == 0) {
|
|
diff --git a/src/main/java/net/minecraft/server/PacketEncoder.java b/src/main/java/net/minecraft/server/PacketEncoder.java
|
|
index 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 677e3e5f687e81ffb6c6aec134e2a19b90bd61cf..3a1d0deb0dec880d73185690e2a7c769a2731479 100644
|
|
--- a/src/main/java/net/minecraft/server/PacketPlayOutMapChunk.java
|
|
+++ b/src/main/java/net/minecraft/server/PacketPlayOutMapChunk.java
|
|
@@ -17,7 +17,7 @@ public class PacketPlayOutMapChunk implements Packet<PacketListenerPlayOut> {
|
|
private NBTTagCompound d;
|
|
@Nullable
|
|
private BiomeStorage e;
|
|
- private byte[] f;
|
|
+ private byte[] f; private byte[] getData() { return this.f; } // Paper - OBFHELPER
|
|
private List<NBTTagCompound> g;
|
|
private boolean h;
|
|
|
|
@@ -129,6 +129,7 @@ public class PacketPlayOutMapChunk implements Packet<PacketListenerPlayOut> {
|
|
return bytebuf;
|
|
}
|
|
|
|
+ public int writeChunk(PacketDataSerializer packetDataSerializer, Chunk chunk, int chunkSectionSelector) { return this.a(packetDataSerializer, chunk, chunkSectionSelector); } // Paper - OBFHELPER
|
|
public int a(PacketDataSerializer packetdataserializer, Chunk chunk, int i) {
|
|
int j = 0;
|
|
ChunkSection[] achunksection = chunk.getSections();
|
|
diff --git a/src/main/java/net/minecraft/server/PlayerChunk.java b/src/main/java/net/minecraft/server/PlayerChunk.java
|
|
index 5c5bf010d07b65f1328541843b0f24ce5d50e6ac..6e9f402fb0faccc222b4289deb36e2d85a66eb7c 100644
|
|
--- a/src/main/java/net/minecraft/server/PlayerChunk.java
|
|
+++ b/src/main/java/net/minecraft/server/PlayerChunk.java
|
|
@@ -19,9 +19,9 @@ public class PlayerChunk {
|
|
private static final List<ChunkStatus> CHUNK_STATUSES = ChunkStatus.a();
|
|
private static final PlayerChunk.State[] CHUNK_STATES = PlayerChunk.State.values();
|
|
private final AtomicReferenceArray<CompletableFuture<Either<IChunkAccess, PlayerChunk.Failure>>> statusFutures;
|
|
- private volatile CompletableFuture<Either<Chunk, PlayerChunk.Failure>> fullChunkFuture;
|
|
- private volatile CompletableFuture<Either<Chunk, PlayerChunk.Failure>> tickingFuture;
|
|
- private volatile CompletableFuture<Either<Chunk, PlayerChunk.Failure>> entityTickingFuture;
|
|
+ private volatile CompletableFuture<Either<Chunk, PlayerChunk.Failure>> fullChunkFuture; private int fullChunkCreateCount; private volatile boolean isFullChunkReady; // Paper - cache chunk ticking stage
|
|
+ private volatile CompletableFuture<Either<Chunk, PlayerChunk.Failure>> tickingFuture; private volatile boolean isTickingReady; // Paper - cache chunk ticking stage
|
|
+ private volatile CompletableFuture<Either<Chunk, PlayerChunk.Failure>> entityTickingFuture; private volatile boolean isEntityTickingReady; // Paper - cache chunk ticking stage
|
|
private CompletableFuture<IChunkAccess> chunkSave;
|
|
public int oldTicketLevel;
|
|
private int ticketLevel;
|
|
@@ -38,6 +38,8 @@ public class PlayerChunk {
|
|
public final PlayerChunk.d players;
|
|
private boolean hasBeenLoaded;
|
|
|
|
+ private final PlayerChunkMap chunkMap; // Paper
|
|
+
|
|
public PlayerChunk(ChunkCoordIntPair chunkcoordintpair, int i, LightEngine lightengine, PlayerChunk.c playerchunk_c, PlayerChunk.d playerchunk_d) {
|
|
this.statusFutures = new AtomicReferenceArray(PlayerChunk.CHUNK_STATUSES.size());
|
|
this.fullChunkFuture = PlayerChunk.UNLOADED_CHUNK_FUTURE;
|
|
@@ -53,8 +55,47 @@ public class PlayerChunk {
|
|
this.ticketLevel = this.oldTicketLevel;
|
|
this.n = this.oldTicketLevel;
|
|
this.a(i);
|
|
+ this.chunkMap = (PlayerChunkMap)playerchunk_d; // Paper
|
|
+ }
|
|
+
|
|
+ // Paper start
|
|
+ @Nullable
|
|
+ public final Chunk getEntityTickingChunk() {
|
|
+ CompletableFuture<Either<Chunk, PlayerChunk.Failure>> completablefuture = this.entityTickingFuture;
|
|
+ Either<Chunk, PlayerChunk.Failure> either = completablefuture.getNow(null);
|
|
+
|
|
+ return either == null ? null : either.left().orElse(null);
|
|
+ }
|
|
+
|
|
+ @Nullable
|
|
+ public final Chunk getTickingChunk() {
|
|
+ CompletableFuture<Either<Chunk, PlayerChunk.Failure>> completablefuture = this.tickingFuture;
|
|
+ Either<Chunk, PlayerChunk.Failure> either = completablefuture.getNow(null);
|
|
+
|
|
+ return either == null ? null : either.left().orElse(null);
|
|
+ }
|
|
+
|
|
+ @Nullable
|
|
+ public final Chunk getFullReadyChunk() {
|
|
+ CompletableFuture<Either<Chunk, PlayerChunk.Failure>> completablefuture = this.fullChunkFuture;
|
|
+ Either<Chunk, PlayerChunk.Failure> either = completablefuture.getNow(null);
|
|
+
|
|
+ return either == null ? null : either.left().orElse(null);
|
|
+ }
|
|
+
|
|
+ public final boolean isEntityTickingReady() {
|
|
+ return this.isEntityTickingReady;
|
|
}
|
|
|
|
+ public final boolean isTickingReady() {
|
|
+ return this.isTickingReady;
|
|
+ }
|
|
+
|
|
+ public final boolean isFullChunkReady() {
|
|
+ return this.isFullChunkReady;
|
|
+ }
|
|
+ // Paper end
|
|
+
|
|
// CraftBukkit start
|
|
public Chunk getFullChunk() {
|
|
if (!getChunkState(this.oldTicketLevel).isAtLeast(PlayerChunk.State.BORDER)) return null; // note: using oldTicketLevel for isLoaded checks
|
|
@@ -63,6 +104,14 @@ public class PlayerChunk {
|
|
return either == null ? null : (Chunk) either.left().orElse(null);
|
|
}
|
|
// CraftBukkit end
|
|
+ // Paper start - "real" get full chunk immediately
|
|
+ public Chunk getFullChunkIfCached() {
|
|
+ // Note: Copied from above without ticket level check
|
|
+ CompletableFuture<Either<IChunkAccess, PlayerChunk.Failure>> statusFuture = this.getStatusFutureUnchecked(ChunkStatus.FULL);
|
|
+ Either<IChunkAccess, PlayerChunk.Failure> either = (Either<IChunkAccess, PlayerChunk.Failure>) statusFuture.getNow(null);
|
|
+ return either == null ? null : (Chunk) either.left().orElse(null);
|
|
+ }
|
|
+ // Paper end
|
|
|
|
public CompletableFuture<Either<IChunkAccess, PlayerChunk.Failure>> getStatusFutureUnchecked(ChunkStatus chunkstatus) {
|
|
CompletableFuture<Either<IChunkAccess, PlayerChunk.Failure>> completablefuture = (CompletableFuture) this.statusFutures.get(chunkstatus.c());
|
|
@@ -74,14 +123,17 @@ public class PlayerChunk {
|
|
return getChunkStatus(this.ticketLevel).b(chunkstatus) ? this.getStatusFutureUnchecked(chunkstatus) : PlayerChunk.UNLOADED_CHUNK_ACCESS_FUTURE;
|
|
}
|
|
|
|
+ public final CompletableFuture<Either<Chunk, PlayerChunk.Failure>> getTickingFuture() { return this.a(); } // Paper - OBFHELPER
|
|
public CompletableFuture<Either<Chunk, PlayerChunk.Failure>> a() {
|
|
return this.tickingFuture;
|
|
}
|
|
|
|
+ public final CompletableFuture<Either<Chunk, PlayerChunk.Failure>> getEntityTickingFuture() { return this.b(); } // Paper - OBFHELPER
|
|
public CompletableFuture<Either<Chunk, PlayerChunk.Failure>> b() {
|
|
return this.entityTickingFuture;
|
|
}
|
|
|
|
+ public final CompletableFuture<Either<Chunk, PlayerChunk.Failure>> getFullChunkFuture() { return this.c(); } // Paper - OBFHELPER
|
|
public CompletableFuture<Either<Chunk, PlayerChunk.Failure>> c() {
|
|
return this.fullChunkFuture;
|
|
}
|
|
@@ -335,13 +387,27 @@ public class PlayerChunk {
|
|
|
|
this.hasBeenLoaded |= flag3;
|
|
if (!flag2 && flag3) {
|
|
- this.fullChunkFuture = playerchunkmap.b(this);
|
|
+ // Paper start - cache ticking ready status
|
|
+ int expectCreateCount = ++this.fullChunkCreateCount;
|
|
+ this.fullChunkFuture = playerchunkmap.b(this); this.fullChunkFuture.thenAccept((either) -> {
|
|
+ if (either.left().isPresent() && PlayerChunk.this.fullChunkCreateCount == expectCreateCount) {
|
|
+ // note: Here is a very good place to add callbacks to logic waiting on this.
|
|
+ Chunk fullChunk = either.left().get();
|
|
+ PlayerChunk.this.isFullChunkReady = true;
|
|
+ fullChunk.playerChunk = PlayerChunk.this;
|
|
+
|
|
+
|
|
+ }
|
|
+ });
|
|
+ // Paper end
|
|
this.a(this.fullChunkFuture);
|
|
}
|
|
|
|
if (flag2 && !flag3) {
|
|
completablefuture = this.fullChunkFuture;
|
|
this.fullChunkFuture = PlayerChunk.UNLOADED_CHUNK_FUTURE;
|
|
+ ++this.fullChunkCreateCount; // Paper - cache ticking ready status
|
|
+ this.isFullChunkReady = false; // Paper - cache ticking ready status
|
|
this.a(((CompletableFuture<Either<Chunk, PlayerChunk.Failure>>) completablefuture).thenApply((either1) -> { // CraftBukkit - decompile error
|
|
playerchunkmap.getClass();
|
|
return either1.ifLeft(playerchunkmap::a);
|
|
@@ -352,12 +418,24 @@ public class PlayerChunk {
|
|
boolean flag5 = playerchunk_state1.isAtLeast(PlayerChunk.State.TICKING);
|
|
|
|
if (!flag4 && flag5) {
|
|
- this.tickingFuture = playerchunkmap.a(this);
|
|
+ // Paper start - cache ticking ready status
|
|
+ this.tickingFuture = playerchunkmap.a(this); this.tickingFuture.thenAccept((either) -> {
|
|
+ if (either.left().isPresent()) {
|
|
+ // note: Here is a very good place to add callbacks to logic waiting on this.
|
|
+ Chunk tickingChunk = either.left().get();
|
|
+ PlayerChunk.this.isTickingReady = true;
|
|
+
|
|
+
|
|
+
|
|
+
|
|
+ }
|
|
+ });
|
|
+ // Paper end
|
|
this.a(this.tickingFuture);
|
|
}
|
|
|
|
if (flag4 && !flag5) {
|
|
- this.tickingFuture.complete(PlayerChunk.UNLOADED_CHUNK);
|
|
+ this.tickingFuture.complete(PlayerChunk.UNLOADED_CHUNK); this.isTickingReady = false; // Paper - cache chunk ticking stage
|
|
this.tickingFuture = PlayerChunk.UNLOADED_CHUNK_FUTURE;
|
|
}
|
|
|
|
@@ -369,12 +447,24 @@ public class PlayerChunk {
|
|
throw (IllegalStateException) SystemUtils.c(new IllegalStateException());
|
|
}
|
|
|
|
- this.entityTickingFuture = playerchunkmap.b(this.location);
|
|
+ // Paper start - cache ticking ready status
|
|
+ this.entityTickingFuture = playerchunkmap.b(this.location); this.entityTickingFuture.thenAccept((either) -> {
|
|
+ if (either.left().isPresent()) {
|
|
+ // note: Here is a very good place to add callbacks to logic waiting on this.
|
|
+ Chunk entityTickingChunk = either.left().get();
|
|
+ PlayerChunk.this.isEntityTickingReady = true;
|
|
+
|
|
+
|
|
+
|
|
+
|
|
+ }
|
|
+ });
|
|
+ // Paper end
|
|
this.a(this.entityTickingFuture);
|
|
}
|
|
|
|
if (flag6 && !flag7) {
|
|
- this.entityTickingFuture.complete(PlayerChunk.UNLOADED_CHUNK);
|
|
+ this.entityTickingFuture.complete(PlayerChunk.UNLOADED_CHUNK); this.isEntityTickingReady = false; // Paper - cache chunk ticking stage
|
|
this.entityTickingFuture = PlayerChunk.UNLOADED_CHUNK_FUTURE;
|
|
}
|
|
|
|
diff --git a/src/main/java/net/minecraft/server/PlayerChunkMap.java b/src/main/java/net/minecraft/server/PlayerChunkMap.java
|
|
index 7ad30548e2ad221494d6870b0b3a08e1d2f3ed06..b505244516321292e56609eaa54693d84e0bf617 100644
|
|
--- a/src/main/java/net/minecraft/server/PlayerChunkMap.java
|
|
+++ b/src/main/java/net/minecraft/server/PlayerChunkMap.java
|
|
@@ -99,6 +99,28 @@ 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, File file, DataFixer datafixer, DefinedStructureManager definedstructuremanager, Executor executor, IAsyncTaskHandler<Runnable> iasynctaskhandler, ILightAccess ilightaccess, ChunkGenerator<?> chunkgenerator, WorldLoadListener worldloadlistener, Supplier<WorldPersistentData> supplier, int i) {
|
|
super(new File(worldserver.getWorldProvider().getDimensionManager().a(file), "region"), datafixer);
|
|
this.visibleChunks = this.updatingChunks.clone();
|
|
@@ -187,6 +209,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;
|
|
@@ -867,6 +897,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.K();
|
|
|
|
@@ -874,6 +905,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) {
|
|
@@ -984,6 +1016,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 0c496ee0a04da4f0523f6e629f26e99c490f171a..6a681d694e76fa6f38d00ee7bae67762f3e5c34f 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 08768a3c877bfe26c2a9533af390068b172a5996..d103cfaace4f42aaad677103f4eef578490699da 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 6e65306a275fe91ce82b28e4e6155f91dceaa3f2..39339fa27551b06a9bfd8ea67b1ec8c66726f488 100644
|
|
--- a/src/main/java/net/minecraft/server/ProtoChunk.java
|
|
+++ b/src/main/java/net/minecraft/server/ProtoChunk.java
|
|
@@ -80,6 +80,18 @@ public class ProtoChunk implements IChunkAccess {
|
|
|
|
}
|
|
|
|
+ // Paper start - If loaded util
|
|
+ @Override
|
|
+ public Fluid getFluidIfLoaded(BlockPosition blockposition) {
|
|
+ return this.getFluid(blockposition);
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public IBlockData getTypeIfLoaded(BlockPosition blockposition) {
|
|
+ return this.getType(blockposition);
|
|
+ }
|
|
+ // Paper end
|
|
+
|
|
@Override
|
|
public IBlockData getType(BlockPosition blockposition) {
|
|
int i = blockposition.getY();
|
|
diff --git a/src/main/java/net/minecraft/server/RegionFile.java b/src/main/java/net/minecraft/server/RegionFile.java
|
|
index 7b6e0e86b00c7750267f0090b79b738696f115c4..187c4e0f58b7de58dfd2194afb194cbed0a58957 100644
|
|
--- a/src/main/java/net/minecraft/server/RegionFile.java
|
|
+++ b/src/main/java/net/minecraft/server/RegionFile.java
|
|
@@ -88,6 +88,7 @@ public class RegionFile implements AutoCloseable {
|
|
return this.d.resolve(s);
|
|
}
|
|
|
|
+ @Nullable public synchronized DataInputStream getReadStream(ChunkCoordIntPair chunkCoordIntPair) throws IOException { return a(chunkCoordIntPair);} // Paper - OBFHELPER
|
|
@Nullable
|
|
public synchronized DataInputStream a(ChunkCoordIntPair chunkcoordintpair) throws IOException {
|
|
int i = this.getOffset(chunkcoordintpair);
|
|
diff --git a/src/main/java/net/minecraft/server/RegionLimitedWorldAccess.java b/src/main/java/net/minecraft/server/RegionLimitedWorldAccess.java
|
|
index 8c123f265e674ca99bc06e4d64f212148f6cac1a..9d0e8c2d43ba56d53bc30c0a3e2d7d170d8be474 100644
|
|
--- a/src/main/java/net/minecraft/server/RegionLimitedWorldAccess.java
|
|
+++ b/src/main/java/net/minecraft/server/RegionLimitedWorldAccess.java
|
|
@@ -108,6 +108,26 @@ public class RegionLimitedWorldAccess implements GeneratorAccess {
|
|
return i >= ichunkaccess.getPos().x && i <= ichunkaccess1.getPos().x && j >= ichunkaccess.getPos().z && j <= ichunkaccess1.getPos().z;
|
|
}
|
|
|
|
+ // Paper start - if loaded util
|
|
+ @Nullable
|
|
+ @Override
|
|
+ public IChunkAccess getChunkIfLoadedImmediately(int x, int z) {
|
|
+ return this.getChunkAt(x, z, ChunkStatus.FULL, false);
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public IBlockData getTypeIfLoaded(BlockPosition blockposition) {
|
|
+ IChunkAccess chunk = this.getChunkIfLoadedImmediately(blockposition.getX() >> 4, blockposition.getZ() >> 4);
|
|
+ return chunk == null ? null : chunk.getType(blockposition);
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public Fluid getFluidIfLoaded(BlockPosition blockposition) {
|
|
+ IChunkAccess chunk = this.getChunkIfLoadedImmediately(blockposition.getX() >> 4, blockposition.getZ() >> 4);
|
|
+ return chunk == null ? null : chunk.getFluid(blockposition);
|
|
+ }
|
|
+ // Paper end
|
|
+
|
|
@Override
|
|
public IBlockData getType(BlockPosition blockposition) {
|
|
return this.getChunkAt(blockposition.getX() >> 4, blockposition.getZ() >> 4).getType(blockposition);
|
|
diff --git a/src/main/java/net/minecraft/server/RegistryBlockID.java b/src/main/java/net/minecraft/server/RegistryBlockID.java
|
|
index 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 7b92ecfff94e3c4a69269139ebed75fc59bbd4f1..7e224ebeff3bf34270df173a47b08d3290c00670 100644
|
|
--- a/src/main/java/net/minecraft/server/SystemUtils.java
|
|
+++ b/src/main/java/net/minecraft/server/SystemUtils.java
|
|
@@ -58,7 +58,7 @@ public class SystemUtils {
|
|
}
|
|
|
|
public static long getMonotonicNanos() {
|
|
- return SystemUtils.a.getAsLong();
|
|
+ return System.nanoTime(); // Paper
|
|
}
|
|
|
|
public static long getTimeMillis() {
|
|
diff --git a/src/main/java/net/minecraft/server/TicketType.java b/src/main/java/net/minecraft/server/TicketType.java
|
|
index 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 143be566c683ae035997f9a4058381a109f3de23..0e30d8c9933dc6595b9715ef6dc99cc8910892cb 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 2e1eabba14a3757d03fd90741651001e78c6322f..2a4fa455ff3065f9b1ad9bcf8d236bbb6f830bc9 100644
|
|
--- a/src/main/java/net/minecraft/server/World.java
|
|
+++ b/src/main/java/net/minecraft/server/World.java
|
|
@@ -22,6 +22,7 @@ import org.bukkit.craftbukkit.SpigotTimings; // Spigot
|
|
import org.bukkit.craftbukkit.CraftServer;
|
|
import org.bukkit.craftbukkit.CraftWorld;
|
|
import org.bukkit.craftbukkit.block.CapturedBlockState;
|
|
+import org.bukkit.craftbukkit.block.CraftBlockState;
|
|
import org.bukkit.craftbukkit.block.data.CraftBlockData;
|
|
import org.bukkit.event.block.BlockPhysicsEvent;
|
|
// CraftBukkit end
|
|
@@ -183,6 +184,39 @@ public abstract class World implements GeneratorAccess, AutoCloseable {
|
|
return (Chunk) this.getChunkAt(i, j, ChunkStatus.FULL);
|
|
}
|
|
|
|
+ // Paper start - if loaded
|
|
+ @Nullable
|
|
+ @Override
|
|
+ public IChunkAccess getChunkIfLoadedImmediately(int x, int z) {
|
|
+ return ((ChunkProviderServer)this.chunkProvider).getChunkAtIfLoadedImmediately(x, z);
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public IBlockData getTypeIfLoaded(BlockPosition blockposition) {
|
|
+ // CraftBukkit start - tree generation
|
|
+ if (captureTreeGeneration) {
|
|
+ CraftBlockState previous = capturedBlockStates.get(blockposition);
|
|
+ if (previous != null) {
|
|
+ return previous.getHandle();
|
|
+ }
|
|
+ }
|
|
+ // CraftBukkit end
|
|
+ if (!isValidLocation(blockposition)) {
|
|
+ return Blocks.AIR.getBlockData();
|
|
+ }
|
|
+ IChunkAccess chunk = this.getChunkIfLoadedImmediately(blockposition.getX() >> 4, blockposition.getZ() >> 4);
|
|
+
|
|
+ return chunk == null ? null : chunk.getType(blockposition);
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public Fluid getFluidIfLoaded(BlockPosition blockposition) {
|
|
+ IChunkAccess chunk = this.getChunkIfLoadedImmediately(blockposition.getX() >> 4, blockposition.getZ() >> 4);
|
|
+
|
|
+ return chunk == null ? null : chunk.getFluid(blockposition);
|
|
+ }
|
|
+ // Paper end
|
|
+
|
|
@Override
|
|
public IChunkAccess getChunkAt(int i, int j, ChunkStatus chunkstatus, boolean flag) {
|
|
IChunkAccess ichunkaccess = this.chunkProvider.getChunkAt(i, j, chunkstatus, flag);
|
|
@@ -336,8 +370,9 @@ public abstract class World implements GeneratorAccess, AutoCloseable {
|
|
|
|
public void a(BlockPosition blockposition, IBlockData iblockdata, IBlockData iblockdata1) {}
|
|
|
|
- @Override
|
|
- public boolean a(BlockPosition blockposition, boolean flag) {
|
|
+ public boolean setAir(BlockPosition blockposition) { return this.a(blockposition, false); } // Paper - OBFHELPER
|
|
+ public boolean setAir(BlockPosition blockposition, boolean moved) { return this.a(blockposition, moved); } // Paper - OBFHELPER
|
|
+ @Override public boolean a(BlockPosition blockposition, boolean flag) { // Paper - OBFHELPER
|
|
Fluid fluid = this.getFluid(blockposition);
|
|
|
|
return this.setTypeAndData(blockposition, fluid.getBlockData(), 3 | (flag ? 64 : 0));
|
|
diff --git a/src/main/java/net/minecraft/server/WorldBorder.java b/src/main/java/net/minecraft/server/WorldBorder.java
|
|
index 020e5c171a44f8886395849d718efd6f90659bab..3db276f176301ebf15d5a2ba44d0edb5c7ec6097 100644
|
|
--- a/src/main/java/net/minecraft/server/WorldBorder.java
|
|
+++ b/src/main/java/net/minecraft/server/WorldBorder.java
|
|
@@ -35,6 +35,8 @@ public class WorldBorder {
|
|
return this.b(entity.locX(), entity.locZ());
|
|
}
|
|
|
|
+ public final VoxelShape asVoxelShape(){ return a();} // Paper - OBFHELPER
|
|
+
|
|
public VoxelShape a() {
|
|
return this.i.m();
|
|
}
|
|
diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java
|
|
index e181df6f4d08b88835db7342f97e0b848bcf01ef..4a9132c7016b076ab35b5d66ce81bbd247e1a3ce 100644
|
|
--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java
|
|
+++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java
|
|
@@ -85,6 +85,7 @@ public final class CraftItemStack extends ItemStack {
|
|
}
|
|
|
|
net.minecraft.server.ItemStack handle;
|
|
+ public net.minecraft.server.ItemStack getHandle() { return handle; } // Paper
|
|
|
|
/**
|
|
* Mirror
|
|
diff --git a/src/main/java/org/bukkit/craftbukkit/util/DummyGeneratorAccess.java b/src/main/java/org/bukkit/craftbukkit/util/DummyGeneratorAccess.java
|
|
index d8358a0f031ca6e5d5dc1700172175446f74384e..d0b813008ca21fe6aa9b514ed4325596113fd459 100644
|
|
--- a/src/main/java/org/bukkit/craftbukkit/util/DummyGeneratorAccess.java
|
|
+++ b/src/main/java/org/bukkit/craftbukkit/util/DummyGeneratorAccess.java
|
|
@@ -196,4 +196,22 @@ public class DummyGeneratorAccess implements GeneratorAccess {
|
|
public boolean a(BlockPosition blockposition, boolean flag, Entity entity) {
|
|
throw new UnsupportedOperationException("Not supported yet.");
|
|
}
|
|
+
|
|
+ // Paper start - if loaded util
|
|
+ @javax.annotation.Nullable
|
|
+ @Override
|
|
+ public IChunkAccess getChunkIfLoadedImmediately(int x, int z) {
|
|
+ throw new UnsupportedOperationException("Not supported yet.");
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public IBlockData getTypeIfLoaded(BlockPosition blockposition) {
|
|
+ throw new UnsupportedOperationException("Not supported yet.");
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public Fluid getFluidIfLoaded(BlockPosition blockposition) {
|
|
+ throw new UnsupportedOperationException("Not supported yet.");
|
|
+ }
|
|
+ // Paper end
|
|
}
|
|
diff --git a/src/main/java/org/bukkit/craftbukkit/util/UnsafeList.java b/src/main/java/org/bukkit/craftbukkit/util/UnsafeList.java
|
|
index 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;
|
|
|