897dd2c840
This commit doesn't do much on its own, but adds a new Java Cleaner API that lets us hook into Garbage Collector events to reclaim pooled objects and return them to the pool. Adds framework for Network Packets to know when a packet has finished dispatching to get an idea when a packet is done sending to players. Rewrites PooledObjects impl to properly respect max pool size and remove almost all risk of contention. Bumps the Paper Async Task Queue to use 2 threads, and properly shuts it down on shutdown.
4489 Zeilen
182 KiB
Diff
4489 Zeilen
182 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..c71ed11834557f71504de5038d3bb593824f6f95
|
|
--- /dev/null
|
|
+++ b/src/main/java/com/destroystokyo/paper/util/misc/AreaMap.java
|
|
@@ -0,0 +1,439 @@
|
|
+package com.destroystokyo.paper.util.misc;
|
|
+
|
|
+import com.destroystokyo.paper.util.math.IntegerUtil;
|
|
+import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap;
|
|
+import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
|
|
+import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
|
|
+import it.unimi.dsi.fastutil.objects.Object2LongOpenHashMap;
|
|
+import net.minecraft.server.ChunkCoordIntPair;
|
|
+import net.minecraft.server.MCUtil;
|
|
+import net.minecraft.server.MinecraftServer;
|
|
+import javax.annotation.Nullable;
|
|
+import java.util.Iterator;
|
|
+
|
|
+/** @author Spottedleaf */
|
|
+public abstract class AreaMap<E> {
|
|
+
|
|
+ /* Tested via https://gist.github.com/Spottedleaf/520419c6f41ef348fe9926ce674b7217 */
|
|
+
|
|
+ protected final Object2LongOpenHashMap<E> objectToLastCoordinate = new Object2LongOpenHashMap<>();
|
|
+ protected final Object2IntOpenHashMap<E> objectToViewDistance = new Object2IntOpenHashMap<>();
|
|
+
|
|
+ {
|
|
+ this.objectToViewDistance.defaultReturnValue(-1);
|
|
+ this.objectToLastCoordinate.defaultReturnValue(Long.MIN_VALUE);
|
|
+ }
|
|
+
|
|
+ // we use linked for better iteration.
|
|
+ // map of: coordinate to set of objects in coordinate
|
|
+ protected final Long2ObjectOpenHashMap<PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<E>> areaMap = new Long2ObjectOpenHashMap<>(1024, 0.7f);
|
|
+ protected final PooledLinkedHashSets<E> pooledHashSets;
|
|
+
|
|
+ protected final ChangeCallback<E> addCallback;
|
|
+ protected final ChangeCallback<E> removeCallback;
|
|
+
|
|
+ public AreaMap() {
|
|
+ this(new PooledLinkedHashSets<>());
|
|
+ }
|
|
+
|
|
+ // let users define a "global" or "shared" pooled sets if they wish
|
|
+ public AreaMap(final PooledLinkedHashSets<E> pooledHashSets) {
|
|
+ this(pooledHashSets, null, null);
|
|
+ }
|
|
+
|
|
+ public AreaMap(final PooledLinkedHashSets<E> pooledHashSets, final ChangeCallback<E> addCallback, final ChangeCallback<E> removeCallback) {
|
|
+ this.pooledHashSets = pooledHashSets;
|
|
+ this.addCallback = addCallback;
|
|
+ this.removeCallback = removeCallback;
|
|
+ }
|
|
+
|
|
+ @Nullable
|
|
+ public final PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<E> getObjectsInRange(final long key) {
|
|
+ return this.areaMap.get(key);
|
|
+ }
|
|
+
|
|
+ @Nullable
|
|
+ public final PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<E> getObjectsInRange(final ChunkCoordIntPair chunkPos) {
|
|
+ return this.areaMap.get(MCUtil.getCoordinateKey(chunkPos));
|
|
+ }
|
|
+
|
|
+ @Nullable
|
|
+ public final PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<E> getObjectsInRange(final int chunkX, final int chunkZ) {
|
|
+ return this.areaMap.get(MCUtil.getCoordinateKey(chunkX, chunkZ));
|
|
+ }
|
|
+
|
|
+ // Long.MIN_VALUE indicates the object is not mapped
|
|
+ public final long getLastCoordinate(final E object) {
|
|
+ return this.objectToLastCoordinate.getOrDefault(object, Long.MIN_VALUE);
|
|
+ }
|
|
+
|
|
+ // -1 indicates the object is not mapped
|
|
+ public final int getLastViewDistance(final E object) {
|
|
+ return this.objectToViewDistance.getOrDefault(object, -1);
|
|
+ }
|
|
+
|
|
+ // returns the total number of mapped chunks
|
|
+ public final int size() {
|
|
+ return this.areaMap.size();
|
|
+ }
|
|
+
|
|
+ public final void 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) {}
|
|
+
|
|
+ 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);
|
|
+
|
|
+ }
|
|
+}
|
|
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..8a552a87abf5cc6fc0e10bf93de3cf8168d57cb5
|
|
--- /dev/null
|
|
+++ b/src/main/java/com/destroystokyo/paper/util/misc/PlayerAreaMap.java
|
|
@@ -0,0 +1,27 @@
|
|
+package com.destroystokyo.paper.util.misc;
|
|
+
|
|
+import net.minecraft.server.EntityPlayer;
|
|
+
|
|
+/**
|
|
+ * @author Spottedleaf
|
|
+ */
|
|
+public final class PlayerAreaMap extends AreaMap<EntityPlayer> {
|
|
+
|
|
+ public PlayerAreaMap() {
|
|
+ super();
|
|
+ }
|
|
+
|
|
+ public PlayerAreaMap(final PooledLinkedHashSets<EntityPlayer> pooledHashSets) {
|
|
+ super(pooledHashSets);
|
|
+ }
|
|
+
|
|
+ public PlayerAreaMap(final PooledLinkedHashSets<EntityPlayer> pooledHashSets, final ChangeCallback<EntityPlayer> addCallback,
|
|
+ final ChangeCallback<EntityPlayer> removeCallback) {
|
|
+ super(pooledHashSets, addCallback, removeCallback);
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ protected PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<EntityPlayer> getEmptySetFor(final EntityPlayer player) {
|
|
+ return player.cachedSingleHashSet;
|
|
+ }
|
|
+}
|
|
diff --git a/src/main/java/com/destroystokyo/paper/util/misc/PlayerDistanceTrackingAreaMap.java b/src/main/java/com/destroystokyo/paper/util/misc/PlayerDistanceTrackingAreaMap.java
|
|
new file mode 100644
|
|
index 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..7164f46516bdf49ed52062f2d72f33418506bae0
|
|
--- /dev/null
|
|
+++ b/src/main/java/net/minecraft/server/MCUtil.java
|
|
@@ -0,0 +1,473 @@
|
|
+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 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;
|
|
|