mirror of
https://github.com/PaperMC/Paper.git
synced 2024-12-28 07:20:24 +01:00
5bb7bd0f66
The common changes from Moonrise should be entirely in MCUtils
5801 lines
234 KiB
Diff
5801 lines
234 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
|
|
|
|
== AT ==
|
|
public net.minecraft.server.level.ServerChunkCache mainThread
|
|
public net.minecraft.server.level.ServerLevel chunkSource
|
|
public org.bukkit.craftbukkit.inventory.CraftItemStack handle
|
|
public net.minecraft.server.level.ChunkMap getVisibleChunkIfPresent(J)Lnet/minecraft/server/level/ChunkHolder;
|
|
public net.minecraft.server.level.ServerChunkCache mainThreadProcessor
|
|
public net.minecraft.server.level.ServerChunkCache$MainThreadExecutor
|
|
public net.minecraft.world.level.chunk.LevelChunkSection states
|
|
|
|
diff --git a/src/main/java/ca/spottedleaf/moonrise/common/list/EntityList.java b/src/main/java/ca/spottedleaf/moonrise/common/list/EntityList.java
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
|
|
--- /dev/null
|
|
+++ b/src/main/java/ca/spottedleaf/moonrise/common/list/EntityList.java
|
|
@@ -0,0 +0,0 @@
|
|
+package ca.spottedleaf.moonrise.common.list;
|
|
+
|
|
+import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
|
|
+import net.minecraft.world.entity.Entity;
|
|
+import java.util.Arrays;
|
|
+import java.util.Iterator;
|
|
+import java.util.NoSuchElementException;
|
|
+
|
|
+// list with O(1) remove & contains
|
|
+
|
|
+/**
|
|
+ * @author Spottedleaf
|
|
+ */
|
|
+public final class EntityList implements Iterable<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/ca/spottedleaf/moonrise/common/list/IBlockDataList.java b/src/main/java/ca/spottedleaf/moonrise/common/list/IBlockDataList.java
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
|
|
--- /dev/null
|
|
+++ b/src/main/java/ca/spottedleaf/moonrise/common/list/IBlockDataList.java
|
|
@@ -0,0 +0,0 @@
|
|
+package ca.spottedleaf.moonrise.common.list;
|
|
+
|
|
+import it.unimi.dsi.fastutil.longs.LongIterator;
|
|
+import it.unimi.dsi.fastutil.shorts.Short2LongOpenHashMap;
|
|
+import java.util.Arrays;
|
|
+import net.minecraft.world.level.block.Block;
|
|
+import net.minecraft.world.level.block.state.BlockState;
|
|
+import net.minecraft.world.level.chunk.GlobalPalette;
|
|
+
|
|
+public final class IBlockDataList {
|
|
+
|
|
+ private static final GlobalPalette<BlockState> GLOBAL_PALETTE = new GlobalPalette<>(Block.BLOCK_STATE_REGISTRY);
|
|
+
|
|
+ // 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 BlockState getBlockDataFromRaw(final long raw) {
|
|
+ return GLOBAL_PALETTE.valueFor((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 BlockState data) {
|
|
+ return (long)index | ((long)location << 16) | (((long)GLOBAL_PALETTE.idFor(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 BlockState data) {
|
|
+ return this.add(getLocationKey(x, y, z), data);
|
|
+ }
|
|
+
|
|
+ public long add(final int location, final BlockState 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 BlockState 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();
|
|
+ }
|
|
+}
|
|
\ No newline at end of file
|
|
diff --git a/src/main/java/ca/spottedleaf/moonrise/common/list/IteratorSafeOrderedReferenceSet.java b/src/main/java/ca/spottedleaf/moonrise/common/list/IteratorSafeOrderedReferenceSet.java
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
|
|
--- /dev/null
|
|
+++ b/src/main/java/ca/spottedleaf/moonrise/common/list/IteratorSafeOrderedReferenceSet.java
|
|
@@ -0,0 +0,0 @@
|
|
+package ca.spottedleaf.moonrise.common.list;
|
|
+
|
|
+import it.unimi.dsi.fastutil.objects.Reference2IntLinkedOpenHashMap;
|
|
+import it.unimi.dsi.fastutil.objects.Reference2IntMap;
|
|
+import java.util.Arrays;
|
|
+import java.util.NoSuchElementException;
|
|
+
|
|
+public final class IteratorSafeOrderedReferenceSet<E> {
|
|
+
|
|
+ public static final int ITERATOR_FLAG_SEE_ADDITIONS = 1 << 0;
|
|
+
|
|
+ private final Reference2IntLinkedOpenHashMap<E> indexMap;
|
|
+ private int firstInvalidIndex = -1;
|
|
+
|
|
+ /* list impl */
|
|
+ private E[] listElements;
|
|
+ private int listSize;
|
|
+
|
|
+ private final double maxFragFactor;
|
|
+
|
|
+ private int iteratorCount;
|
|
+
|
|
+ public IteratorSafeOrderedReferenceSet() {
|
|
+ this(16, 0.75f, 16, 0.2);
|
|
+ }
|
|
+
|
|
+ public IteratorSafeOrderedReferenceSet(final int setCapacity, final float setLoadFactor, final int arrayCapacity,
|
|
+ final double maxFragFactor) {
|
|
+ this.indexMap = new Reference2IntLinkedOpenHashMap<>(setCapacity, setLoadFactor);
|
|
+ this.indexMap.defaultReturnValue(-1);
|
|
+ this.maxFragFactor = maxFragFactor;
|
|
+ this.listElements = (E[])new Object[arrayCapacity];
|
|
+ }
|
|
+
|
|
+ /*
|
|
+ public void check() {
|
|
+ int iterated = 0;
|
|
+ ReferenceOpenHashSet<E> check = new ReferenceOpenHashSet<>();
|
|
+ if (this.listElements != null) {
|
|
+ for (int i = 0; i < this.listSize; ++i) {
|
|
+ Object obj = this.listElements[i];
|
|
+ if (obj != null) {
|
|
+ iterated++;
|
|
+ if (!check.add((E)obj)) {
|
|
+ throw new IllegalStateException("contains duplicate");
|
|
+ }
|
|
+ if (!this.contains((E)obj)) {
|
|
+ throw new IllegalStateException("desync");
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ if (iterated != this.size()) {
|
|
+ throw new IllegalStateException("Size is mismatched! Got " + iterated + ", expected " + this.size());
|
|
+ }
|
|
+
|
|
+ check.clear();
|
|
+ iterated = 0;
|
|
+ for (final java.util.Iterator<E> iterator = this.unsafeIterator(IteratorSafeOrderedReferenceSet.ITERATOR_FLAG_SEE_ADDITIONS); iterator.hasNext();) {
|
|
+ final E element = iterator.next();
|
|
+ iterated++;
|
|
+ if (!check.add(element)) {
|
|
+ throw new IllegalStateException("contains duplicate (iterator is wrong)");
|
|
+ }
|
|
+ if (!this.contains(element)) {
|
|
+ throw new IllegalStateException("desync (iterator is wrong)");
|
|
+ }
|
|
+ }
|
|
+
|
|
+ if (iterated != this.size()) {
|
|
+ throw new IllegalStateException("Size is mismatched! (iterator is wrong) Got " + iterated + ", expected " + this.size());
|
|
+ }
|
|
+ }
|
|
+ */
|
|
+
|
|
+ private double getFragFactor() {
|
|
+ return 1.0 - ((double)this.indexMap.size() / (double)this.listSize);
|
|
+ }
|
|
+
|
|
+ public int createRawIterator() {
|
|
+ ++this.iteratorCount;
|
|
+ if (this.indexMap.isEmpty()) {
|
|
+ return -1;
|
|
+ } else {
|
|
+ return this.firstInvalidIndex == 0 ? this.indexMap.getInt(this.indexMap.firstKey()) : 0;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public int advanceRawIterator(final int index) {
|
|
+ final E[] elements = this.listElements;
|
|
+ int ret = index + 1;
|
|
+ for (int len = this.listSize; ret < len; ++ret) {
|
|
+ if (elements[ret] != null) {
|
|
+ return ret;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ return -1;
|
|
+ }
|
|
+
|
|
+ public void finishRawIterator() {
|
|
+ if (--this.iteratorCount == 0) {
|
|
+ if (this.getFragFactor() >= this.maxFragFactor) {
|
|
+ this.defrag();
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public boolean remove(final E element) {
|
|
+ final int index = this.indexMap.removeInt(element);
|
|
+ if (index >= 0) {
|
|
+ if (this.firstInvalidIndex < 0 || index < this.firstInvalidIndex) {
|
|
+ this.firstInvalidIndex = index;
|
|
+ }
|
|
+ if (this.listElements[index] != element) {
|
|
+ throw new IllegalStateException();
|
|
+ }
|
|
+ this.listElements[index] = null;
|
|
+ if (this.iteratorCount == 0 && this.getFragFactor() >= this.maxFragFactor) {
|
|
+ this.defrag();
|
|
+ }
|
|
+ //this.check();
|
|
+ return true;
|
|
+ }
|
|
+ return false;
|
|
+ }
|
|
+
|
|
+ public boolean contains(final E element) {
|
|
+ return this.indexMap.containsKey(element);
|
|
+ }
|
|
+
|
|
+ public boolean add(final E element) {
|
|
+ final int listSize = this.listSize;
|
|
+
|
|
+ final int previous = this.indexMap.putIfAbsent(element, listSize);
|
|
+ if (previous != -1) {
|
|
+ return false;
|
|
+ }
|
|
+
|
|
+ if (listSize >= this.listElements.length) {
|
|
+ this.listElements = Arrays.copyOf(this.listElements, listSize * 2);
|
|
+ }
|
|
+ this.listElements[listSize] = element;
|
|
+ this.listSize = listSize + 1;
|
|
+
|
|
+ //this.check();
|
|
+ return true;
|
|
+ }
|
|
+
|
|
+ private void defrag() {
|
|
+ if (this.firstInvalidIndex < 0) {
|
|
+ return; // nothing to do
|
|
+ }
|
|
+
|
|
+ if (this.indexMap.isEmpty()) {
|
|
+ Arrays.fill(this.listElements, 0, this.listSize, null);
|
|
+ this.listSize = 0;
|
|
+ this.firstInvalidIndex = -1;
|
|
+ //this.check();
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ final E[] backingArray = this.listElements;
|
|
+
|
|
+ int lastValidIndex;
|
|
+ java.util.Iterator<Reference2IntMap.Entry<E>> iterator;
|
|
+
|
|
+ if (this.firstInvalidIndex == 0) {
|
|
+ iterator = this.indexMap.reference2IntEntrySet().fastIterator();
|
|
+ lastValidIndex = 0;
|
|
+ } else {
|
|
+ lastValidIndex = this.firstInvalidIndex;
|
|
+ final E key = backingArray[lastValidIndex - 1];
|
|
+ iterator = this.indexMap.reference2IntEntrySet().fastIterator(new Reference2IntMap.Entry<E>() {
|
|
+ @Override
|
|
+ public int getIntValue() {
|
|
+ throw new UnsupportedOperationException();
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public int setValue(int i) {
|
|
+ throw new UnsupportedOperationException();
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public E getKey() {
|
|
+ return key;
|
|
+ }
|
|
+ });
|
|
+ }
|
|
+
|
|
+ while (iterator.hasNext()) {
|
|
+ final Reference2IntMap.Entry<E> entry = iterator.next();
|
|
+
|
|
+ final int newIndex = lastValidIndex++;
|
|
+ backingArray[newIndex] = entry.getKey();
|
|
+ entry.setValue(newIndex);
|
|
+ }
|
|
+
|
|
+ // cleanup end
|
|
+ Arrays.fill(backingArray, lastValidIndex, this.listSize, null);
|
|
+ this.listSize = lastValidIndex;
|
|
+ this.firstInvalidIndex = -1;
|
|
+ //this.check();
|
|
+ }
|
|
+
|
|
+ public E rawGet(final int index) {
|
|
+ return this.listElements[index];
|
|
+ }
|
|
+
|
|
+ public int size() {
|
|
+ // always returns the correct amount - listSize can be different
|
|
+ return this.indexMap.size();
|
|
+ }
|
|
+
|
|
+ public IteratorSafeOrderedReferenceSet.Iterator<E> iterator() {
|
|
+ return this.iterator(0);
|
|
+ }
|
|
+
|
|
+ public IteratorSafeOrderedReferenceSet.Iterator<E> iterator(final int flags) {
|
|
+ ++this.iteratorCount;
|
|
+ return new BaseIterator<>(this, true, (flags & ITERATOR_FLAG_SEE_ADDITIONS) != 0 ? Integer.MAX_VALUE : this.listSize);
|
|
+ }
|
|
+
|
|
+ public java.util.Iterator<E> unsafeIterator() {
|
|
+ return this.unsafeIterator(0);
|
|
+ }
|
|
+ public java.util.Iterator<E> unsafeIterator(final int flags) {
|
|
+ return new BaseIterator<>(this, false, (flags & ITERATOR_FLAG_SEE_ADDITIONS) != 0 ? Integer.MAX_VALUE : this.listSize);
|
|
+ }
|
|
+
|
|
+ public static interface Iterator<E> extends java.util.Iterator<E> {
|
|
+
|
|
+ public void finishedIterating();
|
|
+
|
|
+ }
|
|
+
|
|
+ private static final class BaseIterator<E> implements IteratorSafeOrderedReferenceSet.Iterator<E> {
|
|
+
|
|
+ private final IteratorSafeOrderedReferenceSet<E> set;
|
|
+ private final boolean canFinish;
|
|
+ private final int maxIndex;
|
|
+ private int nextIndex;
|
|
+ private E pendingValue;
|
|
+ private boolean finished;
|
|
+ private E lastReturned;
|
|
+
|
|
+ private BaseIterator(final IteratorSafeOrderedReferenceSet<E> set, final boolean canFinish, final int maxIndex) {
|
|
+ this.set = set;
|
|
+ this.canFinish = canFinish;
|
|
+ this.maxIndex = maxIndex;
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public boolean hasNext() {
|
|
+ if (this.finished) {
|
|
+ return false;
|
|
+ }
|
|
+ if (this.pendingValue != null) {
|
|
+ return true;
|
|
+ }
|
|
+
|
|
+ final E[] elements = this.set.listElements;
|
|
+ int index, len;
|
|
+ for (index = this.nextIndex, len = Math.min(this.maxIndex, this.set.listSize); index < len; ++index) {
|
|
+ final E element = elements[index];
|
|
+ if (element != null) {
|
|
+ this.pendingValue = element;
|
|
+ this.nextIndex = index + 1;
|
|
+ return true;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ this.nextIndex = index;
|
|
+ return false;
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public E next() {
|
|
+ if (!this.hasNext()) {
|
|
+ throw new NoSuchElementException();
|
|
+ }
|
|
+ final E ret = this.pendingValue;
|
|
+
|
|
+ this.pendingValue = null;
|
|
+ this.lastReturned = ret;
|
|
+
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public void remove() {
|
|
+ final E lastReturned = this.lastReturned;
|
|
+ if (lastReturned == null) {
|
|
+ throw new IllegalStateException();
|
|
+ }
|
|
+ this.lastReturned = null;
|
|
+ this.set.remove(lastReturned);
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public void finishedIterating() {
|
|
+ if (this.finished || !this.canFinish) {
|
|
+ throw new IllegalStateException();
|
|
+ }
|
|
+ this.lastReturned = null;
|
|
+ this.finished = true;
|
|
+ this.set.finishRawIterator();
|
|
+ }
|
|
+ }
|
|
+}
|
|
diff --git a/src/main/java/ca/spottedleaf/moonrise/common/list/ReferenceList.java b/src/main/java/ca/spottedleaf/moonrise/common/list/ReferenceList.java
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
|
|
--- /dev/null
|
|
+++ b/src/main/java/ca/spottedleaf/moonrise/common/list/ReferenceList.java
|
|
@@ -0,0 +0,0 @@
|
|
+package ca.spottedleaf.moonrise.common.list;
|
|
+
|
|
+import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap;
|
|
+import java.util.Arrays;
|
|
+import java.util.Iterator;
|
|
+import java.util.NoSuchElementException;
|
|
+
|
|
+public final class ReferenceList<E> implements Iterable<E> {
|
|
+
|
|
+ private static final Object[] EMPTY_LIST = new Object[0];
|
|
+
|
|
+ private final Reference2IntOpenHashMap<E> referenceToIndex;
|
|
+ private E[] references;
|
|
+ private int count;
|
|
+
|
|
+ public ReferenceList() {
|
|
+ this((E[])EMPTY_LIST);
|
|
+ }
|
|
+
|
|
+ public ReferenceList(final E[] referenceArray) {
|
|
+ this.references = referenceArray;
|
|
+ this.referenceToIndex = new Reference2IntOpenHashMap<>(2, 0.8f);
|
|
+ this.referenceToIndex.defaultReturnValue(Integer.MIN_VALUE);
|
|
+ }
|
|
+
|
|
+ private ReferenceList(final E[] references, final int count, final Reference2IntOpenHashMap<E> referenceToIndex) {
|
|
+ this.references = references;
|
|
+ this.count = count;
|
|
+ this.referenceToIndex = referenceToIndex;
|
|
+ }
|
|
+
|
|
+ public ReferenceList<E> copy() {
|
|
+ return new ReferenceList<>(this.references.clone(), this.count, this.referenceToIndex.clone());
|
|
+ }
|
|
+
|
|
+ public int size() {
|
|
+ return this.count;
|
|
+ }
|
|
+
|
|
+ public boolean contains(final E obj) {
|
|
+ return this.referenceToIndex.containsKey(obj);
|
|
+ }
|
|
+
|
|
+ public boolean remove(final E obj) {
|
|
+ final int index = this.referenceToIndex.removeInt(obj);
|
|
+ if (index == Integer.MIN_VALUE) {
|
|
+ return false;
|
|
+ }
|
|
+
|
|
+ // move the object at the end to this index
|
|
+ final int endIndex = --this.count;
|
|
+ final E end = (E)this.references[endIndex];
|
|
+ if (index != endIndex) {
|
|
+ // not empty after this call
|
|
+ this.referenceToIndex.put(end, index); // update index
|
|
+ }
|
|
+ this.references[index] = end;
|
|
+ this.references[endIndex] = null;
|
|
+
|
|
+ return true;
|
|
+ }
|
|
+
|
|
+ public boolean add(final E obj) {
|
|
+ final int count = this.count;
|
|
+ final int currIndex = this.referenceToIndex.putIfAbsent(obj, count);
|
|
+
|
|
+ if (currIndex != Integer.MIN_VALUE) {
|
|
+ return false; // already in this list
|
|
+ }
|
|
+
|
|
+ E[] list = this.references;
|
|
+
|
|
+ if (list.length == count) {
|
|
+ // resize required
|
|
+ list = this.references = Arrays.copyOf(list, (int)Math.max(4L, count * 2L)); // overflow results in negative
|
|
+ }
|
|
+
|
|
+ list[count] = obj;
|
|
+ this.count = count + 1;
|
|
+
|
|
+ return true;
|
|
+ }
|
|
+
|
|
+ public E getChecked(final int index) {
|
|
+ if (index < 0 || index >= this.count) {
|
|
+ throw new IndexOutOfBoundsException("Index: " + index + " is out of bounds, size: " + this.count);
|
|
+ }
|
|
+ return this.references[index];
|
|
+ }
|
|
+
|
|
+ public E getUnchecked(final int index) {
|
|
+ return this.references[index];
|
|
+ }
|
|
+
|
|
+ public Object[] getRawData() {
|
|
+ return this.references;
|
|
+ }
|
|
+
|
|
+ public E[] getRawDataUnchecked() {
|
|
+ return this.references;
|
|
+ }
|
|
+
|
|
+ public void clear() {
|
|
+ this.referenceToIndex.clear();
|
|
+ Arrays.fill(this.references, 0, this.count, null);
|
|
+ this.count = 0;
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public Iterator<E> iterator() {
|
|
+ return new Iterator<>() {
|
|
+ private E lastRet;
|
|
+ private int current;
|
|
+
|
|
+ @Override
|
|
+ public boolean hasNext() {
|
|
+ return this.current < ReferenceList.this.count;
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public E next() {
|
|
+ if (this.current >= ReferenceList.this.count) {
|
|
+ throw new NoSuchElementException();
|
|
+ }
|
|
+ return this.lastRet = ReferenceList.this.references[this.current++];
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public void remove() {
|
|
+ final E lastRet = this.lastRet;
|
|
+
|
|
+ if (lastRet == null) {
|
|
+ throw new IllegalStateException();
|
|
+ }
|
|
+ this.lastRet = null;
|
|
+
|
|
+ ReferenceList.this.remove(lastRet);
|
|
+ --this.current;
|
|
+ }
|
|
+ };
|
|
+ }
|
|
+}
|
|
diff --git a/src/main/java/ca/spottedleaf/moonrise/common/list/SortedList.java b/src/main/java/ca/spottedleaf/moonrise/common/list/SortedList.java
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
|
|
--- /dev/null
|
|
+++ b/src/main/java/ca/spottedleaf/moonrise/common/list/SortedList.java
|
|
@@ -0,0 +0,0 @@
|
|
+package ca.spottedleaf.moonrise.common.list;
|
|
+
|
|
+import java.lang.reflect.Array;
|
|
+import java.util.Arrays;
|
|
+import java.util.Comparator;
|
|
+
|
|
+public final class SortedList<E> {
|
|
+
|
|
+ private static final Object[] EMPTY_LIST = new Object[0];
|
|
+
|
|
+ private Comparator<? super E> comparator;
|
|
+ private E[] elements;
|
|
+ private int count;
|
|
+
|
|
+ public SortedList(final Comparator<? super E> comparator) {
|
|
+ this((E[])EMPTY_LIST, comparator);
|
|
+ }
|
|
+
|
|
+ public SortedList(final E[] elements, final Comparator<? super E> comparator) {
|
|
+ this.elements = elements;
|
|
+ this.comparator = comparator;
|
|
+ }
|
|
+
|
|
+ // start, end are inclusive
|
|
+ private static <E> int insertIdx(final E[] elements, final E element, final Comparator<E> comparator,
|
|
+ int start, int end) {
|
|
+ while (start <= end) {
|
|
+ final int middle = (start + end) >>> 1;
|
|
+
|
|
+ final E middleVal = elements[middle];
|
|
+
|
|
+ final int cmp = comparator.compare(element, middleVal);
|
|
+
|
|
+ if (cmp < 0) {
|
|
+ end = middle - 1;
|
|
+ } else {
|
|
+ start = middle + 1;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ return start;
|
|
+ }
|
|
+
|
|
+ public int size() {
|
|
+ return this.count;
|
|
+ }
|
|
+
|
|
+ public boolean isEmpty() {
|
|
+ return this.count == 0;
|
|
+ }
|
|
+
|
|
+ public int add(final E element) {
|
|
+ E[] elements = this.elements;
|
|
+ final int count = this.count;
|
|
+ this.count = count + 1;
|
|
+ final Comparator<? super E> comparator = this.comparator;
|
|
+
|
|
+ final int idx = insertIdx(elements, element, comparator, 0, count - 1);
|
|
+
|
|
+ if (count >= elements.length) {
|
|
+ // copy and insert at the same time
|
|
+ if (idx == count) {
|
|
+ this.elements = elements = Arrays.copyOf(elements, (int)Math.max(4L, count * 2L)); // overflow results in negative
|
|
+ elements[count] = element;
|
|
+ return idx;
|
|
+ } else {
|
|
+ final E[] newElements = (E[])Array.newInstance(elements.getClass().getComponentType(), (int)Math.max(4L, count * 2L));
|
|
+ System.arraycopy(elements, 0, newElements, 0, idx);
|
|
+ newElements[idx] = element;
|
|
+ System.arraycopy(elements, idx, newElements, idx + 1, count - idx);
|
|
+ this.elements = newElements;
|
|
+ return idx;
|
|
+ }
|
|
+ } else {
|
|
+ if (idx == count) {
|
|
+ // no copy needed
|
|
+ elements[idx] = element;
|
|
+ return idx;
|
|
+ } else {
|
|
+ // shift elements down
|
|
+ System.arraycopy(elements, idx, elements, idx + 1, count - idx);
|
|
+ elements[idx] = element;
|
|
+ return idx;
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public E get(final int idx) {
|
|
+ if (idx < 0 || idx >= this.count) {
|
|
+ throw new IndexOutOfBoundsException(idx);
|
|
+ }
|
|
+ return this.elements[idx];
|
|
+ }
|
|
+
|
|
+
|
|
+ public E remove(final E element) {
|
|
+ E[] elements = this.elements;
|
|
+ final int count = this.count;
|
|
+ final Comparator<? super E> comparator = this.comparator;
|
|
+
|
|
+ final int idx = Arrays.binarySearch(elements, 0, count, element, comparator);
|
|
+ if (idx < 0) {
|
|
+ return null;
|
|
+ }
|
|
+
|
|
+ final int last = this.count - 1;
|
|
+ this.count = last;
|
|
+
|
|
+ final E ret = elements[idx];
|
|
+
|
|
+ System.arraycopy(elements, idx + 1, elements, idx, last - idx);
|
|
+
|
|
+ elements[last] = null;
|
|
+
|
|
+ return ret;
|
|
+ }
|
|
+}
|
|
diff --git a/src/main/java/ca/spottedleaf/moonrise/common/map/Int2IntArraySortedMap.java b/src/main/java/ca/spottedleaf/moonrise/common/map/Int2IntArraySortedMap.java
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
|
|
--- /dev/null
|
|
+++ b/src/main/java/ca/spottedleaf/moonrise/common/map/Int2IntArraySortedMap.java
|
|
@@ -0,0 +0,0 @@
|
|
+package ca.spottedleaf.moonrise.common.map;
|
|
+
|
|
+import it.unimi.dsi.fastutil.ints.Int2IntFunction;
|
|
+
|
|
+import java.util.Arrays;
|
|
+
|
|
+public class Int2IntArraySortedMap {
|
|
+
|
|
+ protected int[] key;
|
|
+ protected int[] val;
|
|
+ protected int size;
|
|
+
|
|
+ public Int2IntArraySortedMap() {
|
|
+ this.key = new int[8];
|
|
+ this.val = new int[8];
|
|
+ }
|
|
+
|
|
+ public int put(final int key, final int value) {
|
|
+ final int index = Arrays.binarySearch(this.key, 0, this.size, key);
|
|
+ if (index >= 0) {
|
|
+ final int current = this.val[index];
|
|
+ this.val[index] = value;
|
|
+ return current;
|
|
+ }
|
|
+ final int insert = -(index + 1);
|
|
+ // shift entries down
|
|
+ if (this.size >= this.val.length) {
|
|
+ this.key = Arrays.copyOf(this.key, this.key.length * 2);
|
|
+ this.val = Arrays.copyOf(this.val, this.val.length * 2);
|
|
+ }
|
|
+ System.arraycopy(this.key, insert, this.key, insert + 1, this.size - insert);
|
|
+ System.arraycopy(this.val, insert, this.val, insert + 1, this.size - insert);
|
|
+ ++this.size;
|
|
+
|
|
+ this.key[insert] = key;
|
|
+ this.val[insert] = value;
|
|
+
|
|
+ return 0;
|
|
+ }
|
|
+
|
|
+ public int computeIfAbsent(final int key, final Int2IntFunction producer) {
|
|
+ final int index = Arrays.binarySearch(this.key, 0, this.size, key);
|
|
+ if (index >= 0) {
|
|
+ return this.val[index];
|
|
+ }
|
|
+ final int insert = -(index + 1);
|
|
+ // shift entries down
|
|
+ if (this.size >= this.val.length) {
|
|
+ this.key = Arrays.copyOf(this.key, this.key.length * 2);
|
|
+ this.val = Arrays.copyOf(this.val, this.val.length * 2);
|
|
+ }
|
|
+ System.arraycopy(this.key, insert, this.key, insert + 1, this.size - insert);
|
|
+ System.arraycopy(this.val, insert, this.val, insert + 1, this.size - insert);
|
|
+ ++this.size;
|
|
+
|
|
+ this.key[insert] = key;
|
|
+
|
|
+ return this.val[insert] = producer.apply(key);
|
|
+ }
|
|
+
|
|
+ public int get(final int key) {
|
|
+ final int index = Arrays.binarySearch(this.key, 0, this.size, key);
|
|
+ if (index < 0) {
|
|
+ return 0;
|
|
+ }
|
|
+ return this.val[index];
|
|
+ }
|
|
+
|
|
+ public int getFloor(final int key) {
|
|
+ final int index = Arrays.binarySearch(this.key, 0, this.size, key);
|
|
+ if (index < 0) {
|
|
+ final int insert = -(index + 1) - 1;
|
|
+ return insert < 0 ? 0 : this.val[insert];
|
|
+ }
|
|
+ return this.val[index];
|
|
+ }
|
|
+}
|
|
diff --git a/src/main/java/ca/spottedleaf/moonrise/common/map/Int2ObjectArraySortedMap.java b/src/main/java/ca/spottedleaf/moonrise/common/map/Int2ObjectArraySortedMap.java
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
|
|
--- /dev/null
|
|
+++ b/src/main/java/ca/spottedleaf/moonrise/common/map/Int2ObjectArraySortedMap.java
|
|
@@ -0,0 +0,0 @@
|
|
+package ca.spottedleaf.moonrise.common.map;
|
|
+
|
|
+import java.util.Arrays;
|
|
+import java.util.function.IntFunction;
|
|
+
|
|
+public class Int2ObjectArraySortedMap<V> {
|
|
+
|
|
+ protected int[] key;
|
|
+ protected V[] val;
|
|
+ protected int size;
|
|
+
|
|
+ public Int2ObjectArraySortedMap() {
|
|
+ this.key = new int[8];
|
|
+ this.val = (V[])new Object[8];
|
|
+ }
|
|
+
|
|
+ public V put(final int key, final V value) {
|
|
+ final int index = Arrays.binarySearch(this.key, 0, this.size, key);
|
|
+ if (index >= 0) {
|
|
+ final V current = this.val[index];
|
|
+ this.val[index] = value;
|
|
+ return current;
|
|
+ }
|
|
+ final int insert = -(index + 1);
|
|
+ // shift entries down
|
|
+ if (this.size >= this.val.length) {
|
|
+ this.key = Arrays.copyOf(this.key, this.key.length * 2);
|
|
+ this.val = Arrays.copyOf(this.val, this.val.length * 2);
|
|
+ }
|
|
+ System.arraycopy(this.key, insert, this.key, insert + 1, this.size - insert);
|
|
+ System.arraycopy(this.val, insert, this.val, insert + 1, this.size - insert);
|
|
+
|
|
+ this.key[insert] = key;
|
|
+ this.val[insert] = value;
|
|
+
|
|
+ return null;
|
|
+ }
|
|
+
|
|
+ public V computeIfAbsent(final int key, final IntFunction<V> producer) {
|
|
+ final int index = Arrays.binarySearch(this.key, 0, this.size, key);
|
|
+ if (index >= 0) {
|
|
+ return this.val[index];
|
|
+ }
|
|
+ final int insert = -(index + 1);
|
|
+ // shift entries down
|
|
+ if (this.size >= this.val.length) {
|
|
+ this.key = Arrays.copyOf(this.key, this.key.length * 2);
|
|
+ this.val = Arrays.copyOf(this.val, this.val.length * 2);
|
|
+ }
|
|
+ System.arraycopy(this.key, insert, this.key, insert + 1, this.size - insert);
|
|
+ System.arraycopy(this.val, insert, this.val, insert + 1, this.size - insert);
|
|
+
|
|
+ this.key[insert] = key;
|
|
+
|
|
+ return this.val[insert] = producer.apply(key);
|
|
+ }
|
|
+
|
|
+ public V get(final int key) {
|
|
+ final int index = Arrays.binarySearch(this.key, 0, this.size, key);
|
|
+ if (index < 0) {
|
|
+ return null;
|
|
+ }
|
|
+ return this.val[index];
|
|
+ }
|
|
+
|
|
+ public V getFloor(final int key) {
|
|
+ final int index = Arrays.binarySearch(this.key, 0, this.size, key);
|
|
+ if (index < 0) {
|
|
+ final int insert = -(index + 1);
|
|
+ return this.val[insert];
|
|
+ }
|
|
+ return this.val[index];
|
|
+ }
|
|
+}
|
|
diff --git a/src/main/java/ca/spottedleaf/moonrise/common/map/Long2IntArraySortedMap.java b/src/main/java/ca/spottedleaf/moonrise/common/map/Long2IntArraySortedMap.java
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
|
|
--- /dev/null
|
|
+++ b/src/main/java/ca/spottedleaf/moonrise/common/map/Long2IntArraySortedMap.java
|
|
@@ -0,0 +0,0 @@
|
|
+package ca.spottedleaf.moonrise.common.map;
|
|
+
|
|
+import it.unimi.dsi.fastutil.longs.Long2IntFunction;
|
|
+
|
|
+import java.util.Arrays;
|
|
+
|
|
+public class Long2IntArraySortedMap {
|
|
+
|
|
+ protected long[] key;
|
|
+ protected int[] val;
|
|
+ protected int size;
|
|
+
|
|
+ public Long2IntArraySortedMap() {
|
|
+ this.key = new long[8];
|
|
+ this.val = new int[8];
|
|
+ }
|
|
+
|
|
+ public int put(final long key, final int value) {
|
|
+ final int index = Arrays.binarySearch(this.key, 0, this.size, key);
|
|
+ if (index >= 0) {
|
|
+ final int current = this.val[index];
|
|
+ this.val[index] = value;
|
|
+ return current;
|
|
+ }
|
|
+ final int insert = -(index + 1);
|
|
+ // shift entries down
|
|
+ if (this.size >= this.val.length) {
|
|
+ this.key = Arrays.copyOf(this.key, this.key.length * 2);
|
|
+ this.val = Arrays.copyOf(this.val, this.val.length * 2);
|
|
+ }
|
|
+ System.arraycopy(this.key, insert, this.key, insert + 1, this.size - insert);
|
|
+ System.arraycopy(this.val, insert, this.val, insert + 1, this.size - insert);
|
|
+ ++this.size;
|
|
+
|
|
+ this.key[insert] = key;
|
|
+ this.val[insert] = value;
|
|
+
|
|
+ return 0;
|
|
+ }
|
|
+
|
|
+ public int computeIfAbsent(final long key, final Long2IntFunction producer) {
|
|
+ final int index = Arrays.binarySearch(this.key, 0, this.size, key);
|
|
+ if (index >= 0) {
|
|
+ return this.val[index];
|
|
+ }
|
|
+ final int insert = -(index + 1);
|
|
+ // shift entries down
|
|
+ if (this.size >= this.val.length) {
|
|
+ this.key = Arrays.copyOf(this.key, this.key.length * 2);
|
|
+ this.val = Arrays.copyOf(this.val, this.val.length * 2);
|
|
+ }
|
|
+ System.arraycopy(this.key, insert, this.key, insert + 1, this.size - insert);
|
|
+ System.arraycopy(this.val, insert, this.val, insert + 1, this.size - insert);
|
|
+ ++this.size;
|
|
+
|
|
+ this.key[insert] = key;
|
|
+
|
|
+ return this.val[insert] = producer.apply(key);
|
|
+ }
|
|
+
|
|
+ public int get(final long key) {
|
|
+ final int index = Arrays.binarySearch(this.key, 0, this.size, key);
|
|
+ if (index < 0) {
|
|
+ return 0;
|
|
+ }
|
|
+ return this.val[index];
|
|
+ }
|
|
+
|
|
+ public int getFloor(final long key) {
|
|
+ final int index = Arrays.binarySearch(this.key, 0, this.size, key);
|
|
+ if (index < 0) {
|
|
+ final int insert = -(index + 1) - 1;
|
|
+ return insert < 0 ? 0 : this.val[insert];
|
|
+ }
|
|
+ return this.val[index];
|
|
+ }
|
|
+}
|
|
diff --git a/src/main/java/ca/spottedleaf/moonrise/common/map/Long2ObjectArraySortedMap.java b/src/main/java/ca/spottedleaf/moonrise/common/map/Long2ObjectArraySortedMap.java
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
|
|
--- /dev/null
|
|
+++ b/src/main/java/ca/spottedleaf/moonrise/common/map/Long2ObjectArraySortedMap.java
|
|
@@ -0,0 +0,0 @@
|
|
+package ca.spottedleaf.moonrise.common.map;
|
|
+
|
|
+import java.util.Arrays;
|
|
+import java.util.function.LongFunction;
|
|
+
|
|
+public class Long2ObjectArraySortedMap<V> {
|
|
+
|
|
+ protected long[] key;
|
|
+ protected V[] val;
|
|
+ protected int size;
|
|
+
|
|
+ public Long2ObjectArraySortedMap() {
|
|
+ this.key = new long[8];
|
|
+ this.val = (V[])new Object[8];
|
|
+ }
|
|
+
|
|
+ public V put(final long key, final V value) {
|
|
+ final int index = Arrays.binarySearch(this.key, 0, this.size, key);
|
|
+ if (index >= 0) {
|
|
+ final V current = this.val[index];
|
|
+ this.val[index] = value;
|
|
+ return current;
|
|
+ }
|
|
+ final int insert = -(index + 1);
|
|
+ // shift entries down
|
|
+ if (this.size >= this.val.length) {
|
|
+ this.key = Arrays.copyOf(this.key, this.key.length * 2);
|
|
+ this.val = Arrays.copyOf(this.val, this.val.length * 2);
|
|
+ }
|
|
+ System.arraycopy(this.key, insert, this.key, insert + 1, this.size - insert);
|
|
+ System.arraycopy(this.val, insert, this.val, insert + 1, this.size - insert);
|
|
+ ++this.size;
|
|
+
|
|
+ this.key[insert] = key;
|
|
+ this.val[insert] = value;
|
|
+
|
|
+ return null;
|
|
+ }
|
|
+
|
|
+ public V computeIfAbsent(final long key, final LongFunction<V> producer) {
|
|
+ final int index = Arrays.binarySearch(this.key, 0, this.size, key);
|
|
+ if (index >= 0) {
|
|
+ return this.val[index];
|
|
+ }
|
|
+ final int insert = -(index + 1);
|
|
+ // shift entries down
|
|
+ if (this.size >= this.val.length) {
|
|
+ this.key = Arrays.copyOf(this.key, this.key.length * 2);
|
|
+ this.val = Arrays.copyOf(this.val, this.val.length * 2);
|
|
+ }
|
|
+ System.arraycopy(this.key, insert, this.key, insert + 1, this.size - insert);
|
|
+ System.arraycopy(this.val, insert, this.val, insert + 1, this.size - insert);
|
|
+ ++this.size;
|
|
+
|
|
+ this.key[insert] = key;
|
|
+
|
|
+ return this.val[insert] = producer.apply(key);
|
|
+ }
|
|
+
|
|
+ public V get(final long key) {
|
|
+ final int index = Arrays.binarySearch(this.key, 0, this.size, key);
|
|
+ if (index < 0) {
|
|
+ return null;
|
|
+ }
|
|
+ return this.val[index];
|
|
+ }
|
|
+
|
|
+ public V getFloor(final long key) {
|
|
+ final int index = Arrays.binarySearch(this.key, 0, this.size, key);
|
|
+ if (index < 0) {
|
|
+ final int insert = -(index + 1) - 1;
|
|
+ return insert < 0 ? null : this.val[insert];
|
|
+ }
|
|
+ return this.val[index];
|
|
+ }
|
|
+}
|
|
diff --git a/src/main/java/ca/spottedleaf/moonrise/common/map/SynchronisedLong2BooleanMap.java b/src/main/java/ca/spottedleaf/moonrise/common/map/SynchronisedLong2BooleanMap.java
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
|
|
--- /dev/null
|
|
+++ b/src/main/java/ca/spottedleaf/moonrise/common/map/SynchronisedLong2BooleanMap.java
|
|
@@ -0,0 +0,0 @@
|
|
+package ca.spottedleaf.moonrise.common.map;
|
|
+
|
|
+import it.unimi.dsi.fastutil.longs.Long2BooleanFunction;
|
|
+import it.unimi.dsi.fastutil.longs.Long2BooleanLinkedOpenHashMap;
|
|
+
|
|
+public final class SynchronisedLong2BooleanMap {
|
|
+ private final Long2BooleanLinkedOpenHashMap map = new Long2BooleanLinkedOpenHashMap();
|
|
+ private final int limit;
|
|
+
|
|
+ public SynchronisedLong2BooleanMap(final int limit) {
|
|
+ this.limit = limit;
|
|
+ }
|
|
+
|
|
+ // must hold lock on map
|
|
+ private void purgeEntries() {
|
|
+ while (this.map.size() > this.limit) {
|
|
+ this.map.removeLastBoolean();
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public boolean remove(final long key) {
|
|
+ synchronized (this.map) {
|
|
+ return this.map.remove(key);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ // note:
|
|
+ public boolean getOrCompute(final long key, final Long2BooleanFunction ifAbsent) {
|
|
+ synchronized (this.map) {
|
|
+ if (this.map.containsKey(key)) {
|
|
+ return this.map.getAndMoveToFirst(key);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ final boolean put = ifAbsent.get(key);
|
|
+
|
|
+ synchronized (this.map) {
|
|
+ if (this.map.containsKey(key)) {
|
|
+ return this.map.getAndMoveToFirst(key);
|
|
+ }
|
|
+ this.map.putAndMoveToFirst(key, put);
|
|
+
|
|
+ this.purgeEntries();
|
|
+
|
|
+ return put;
|
|
+ }
|
|
+ }
|
|
+}
|
|
\ No newline at end of file
|
|
diff --git a/src/main/java/ca/spottedleaf/moonrise/common/map/SynchronisedLong2ObjectMap.java b/src/main/java/ca/spottedleaf/moonrise/common/map/SynchronisedLong2ObjectMap.java
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
|
|
--- /dev/null
|
|
+++ b/src/main/java/ca/spottedleaf/moonrise/common/map/SynchronisedLong2ObjectMap.java
|
|
@@ -0,0 +0,0 @@
|
|
+package ca.spottedleaf.moonrise.common.map;
|
|
+
|
|
+import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap;
|
|
+import java.util.function.BiFunction;
|
|
+
|
|
+public final class SynchronisedLong2ObjectMap<V> {
|
|
+ private final Long2ObjectLinkedOpenHashMap<V> map = new Long2ObjectLinkedOpenHashMap<>();
|
|
+ private final int limit;
|
|
+
|
|
+ public SynchronisedLong2ObjectMap(final int limit) {
|
|
+ this.limit = limit;
|
|
+ }
|
|
+
|
|
+ // must hold lock on map
|
|
+ private void purgeEntries() {
|
|
+ while (this.map.size() > this.limit) {
|
|
+ this.map.removeLast();
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public V get(final long key) {
|
|
+ synchronized (this.map) {
|
|
+ return this.map.getAndMoveToFirst(key);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public V put(final long key, final V value) {
|
|
+ synchronized (this.map) {
|
|
+ final V ret = this.map.putAndMoveToFirst(key, value);
|
|
+ this.purgeEntries();
|
|
+ return ret;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public V compute(final long key, final BiFunction<? super Long, ? super V, ? extends V> remappingFunction) {
|
|
+ synchronized (this.map) {
|
|
+ // first, compute the value - if one is added, it will be at the last entry
|
|
+ this.map.compute(key, remappingFunction);
|
|
+ // move the entry to first, just in case it was added at last
|
|
+ final V ret = this.map.getAndMoveToFirst(key);
|
|
+ // now purge the last entries
|
|
+ this.purgeEntries();
|
|
+
|
|
+ return ret;
|
|
+ }
|
|
+ }
|
|
+}
|
|
\ No newline at end of file
|
|
diff --git a/src/main/java/ca/spottedleaf/moonrise/common/misc/AllocatingRateLimiter.java b/src/main/java/ca/spottedleaf/moonrise/common/misc/AllocatingRateLimiter.java
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
|
|
--- /dev/null
|
|
+++ b/src/main/java/ca/spottedleaf/moonrise/common/misc/AllocatingRateLimiter.java
|
|
@@ -0,0 +0,0 @@
|
|
+package ca.spottedleaf.moonrise.common.misc;
|
|
+
|
|
+public final class AllocatingRateLimiter {
|
|
+
|
|
+ // max difference granularity in ns
|
|
+ private final long maxGranularity;
|
|
+
|
|
+ private double allocation = 0.0;
|
|
+ private long lastAllocationUpdate;
|
|
+ // the carry is used to store the remainder of the last take, so that the take amount remains the same (minus floating point error)
|
|
+ // over any time period using take regardless of the number of take calls or the intervals between the take calls
|
|
+ // i.e. take obtains 3.5 elements, stores 0.5 to this field for the next take() call to use and returns 3
|
|
+ private double takeCarry = 0.0;
|
|
+ private long lastTakeUpdate;
|
|
+
|
|
+ public AllocatingRateLimiter(final long maxGranularity) {
|
|
+ this.maxGranularity = maxGranularity;
|
|
+ }
|
|
+
|
|
+ public void reset(final long time) {
|
|
+ this.allocation = 0.0;
|
|
+ this.lastAllocationUpdate = time;
|
|
+ this.takeCarry = 0.0;
|
|
+ this.lastTakeUpdate = time;
|
|
+ }
|
|
+
|
|
+ // rate in units/s, and time in ns
|
|
+ public void tickAllocation(final long time, final double rate, final double maxAllocation) {
|
|
+ final long diff = Math.min(this.maxGranularity, time - this.lastAllocationUpdate);
|
|
+ this.lastAllocationUpdate = time;
|
|
+
|
|
+ this.allocation = Math.min(maxAllocation - this.takeCarry, this.allocation + rate * (diff*1.0E-9D));
|
|
+ }
|
|
+
|
|
+ public long previewAllocation(final long time, final double rate, final long maxTake) {
|
|
+ if (maxTake < 1L) {
|
|
+ return 0L;
|
|
+ }
|
|
+
|
|
+ final long diff = Math.min(this.maxGranularity, time - this.lastTakeUpdate);
|
|
+
|
|
+ // note: abs(takeCarry) <= 1.0
|
|
+ final double take = Math.min(
|
|
+ Math.min((double)maxTake - this.takeCarry, this.allocation),
|
|
+ rate * (diff*1.0E-9)
|
|
+ );
|
|
+
|
|
+ return (long)Math.floor(this.takeCarry + take);
|
|
+ }
|
|
+
|
|
+ // rate in units/s, and time in ns
|
|
+ public long takeAllocation(final long time, final double rate, final long maxTake) {
|
|
+ if (maxTake < 1L) {
|
|
+ return 0L;
|
|
+ }
|
|
+
|
|
+ double ret = this.takeCarry;
|
|
+ final long diff = Math.min(this.maxGranularity, time - this.lastTakeUpdate);
|
|
+ this.lastTakeUpdate = time;
|
|
+
|
|
+ // note: abs(takeCarry) <= 1.0
|
|
+ final double take = Math.min(
|
|
+ Math.min((double)maxTake - this.takeCarry, this.allocation),
|
|
+ rate * (diff*1.0E-9)
|
|
+ );
|
|
+
|
|
+ ret += take;
|
|
+ this.allocation -= take;
|
|
+
|
|
+ final long retInteger = (long)Math.floor(ret);
|
|
+ this.takeCarry = ret - (double)retInteger;
|
|
+
|
|
+ return retInteger;
|
|
+ }
|
|
+}
|
|
diff --git a/src/main/java/ca/spottedleaf/moonrise/common/misc/Delayed26WayDistancePropagator3D.java b/src/main/java/ca/spottedleaf/moonrise/common/misc/Delayed26WayDistancePropagator3D.java
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
|
|
--- /dev/null
|
|
+++ b/src/main/java/ca/spottedleaf/moonrise/common/misc/Delayed26WayDistancePropagator3D.java
|
|
@@ -0,0 +0,0 @@
|
|
+package ca.spottedleaf.moonrise.common.misc;
|
|
+
|
|
+import ca.spottedleaf.moonrise.common.util.CoordinateUtils;
|
|
+import it.unimi.dsi.fastutil.longs.Long2ByteOpenHashMap;
|
|
+import it.unimi.dsi.fastutil.longs.LongIterator;
|
|
+import it.unimi.dsi.fastutil.longs.LongLinkedOpenHashSet;
|
|
+
|
|
+public final class Delayed26WayDistancePropagator3D {
|
|
+
|
|
+ // this map is considered "stale" unless updates are propagated.
|
|
+ protected final Delayed8WayDistancePropagator2D.LevelMap levels = new Delayed8WayDistancePropagator2D.LevelMap(8192*2, 0.6f);
|
|
+
|
|
+ // this map is never stale
|
|
+ protected final Long2ByteOpenHashMap sources = new Long2ByteOpenHashMap(4096, 0.6f);
|
|
+
|
|
+ // Generally updates to positions are made close to other updates, so we link to decrease cache misses when
|
|
+ // propagating updates
|
|
+ protected final LongLinkedOpenHashSet updatedSources = new LongLinkedOpenHashSet();
|
|
+
|
|
+ @FunctionalInterface
|
|
+ public static interface LevelChangeCallback {
|
|
+
|
|
+ /**
|
|
+ * This can be called for intermediate updates. So do not rely on newLevel being close to or
|
|
+ * the exact level that is expected after a full propagation has occured.
|
|
+ */
|
|
+ public void onLevelUpdate(final long coordinate, final byte oldLevel, final byte newLevel);
|
|
+
|
|
+ }
|
|
+
|
|
+ protected final LevelChangeCallback changeCallback;
|
|
+
|
|
+ public Delayed26WayDistancePropagator3D() {
|
|
+ this(null);
|
|
+ }
|
|
+
|
|
+ public Delayed26WayDistancePropagator3D(final LevelChangeCallback changeCallback) {
|
|
+ this.changeCallback = changeCallback;
|
|
+ }
|
|
+
|
|
+ public int getLevel(final long pos) {
|
|
+ return this.levels.get(pos);
|
|
+ }
|
|
+
|
|
+ public int getLevel(final int x, final int y, final int z) {
|
|
+ return this.levels.get(CoordinateUtils.getChunkSectionKey(x, y, z));
|
|
+ }
|
|
+
|
|
+ public void setSource(final int x, final int y, final int z, final int level) {
|
|
+ this.setSource(CoordinateUtils.getChunkSectionKey(x, y, z), level);
|
|
+ }
|
|
+
|
|
+ public void setSource(final long coordinate, final int level) {
|
|
+ if ((level & 63) != level || level == 0) {
|
|
+ throw new IllegalArgumentException("Level must be in (0, 63], not " + level);
|
|
+ }
|
|
+
|
|
+ final byte byteLevel = (byte)level;
|
|
+ final byte oldLevel = this.sources.put(coordinate, byteLevel);
|
|
+
|
|
+ if (oldLevel == byteLevel) {
|
|
+ return; // nothing to do
|
|
+ }
|
|
+
|
|
+ // queue to update later
|
|
+ this.updatedSources.add(coordinate);
|
|
+ }
|
|
+
|
|
+ public void removeSource(final int x, final int y, final int z) {
|
|
+ this.removeSource(CoordinateUtils.getChunkSectionKey(x, y, z));
|
|
+ }
|
|
+
|
|
+ public void removeSource(final long coordinate) {
|
|
+ if (this.sources.remove(coordinate) != 0) {
|
|
+ this.updatedSources.add(coordinate);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ // queues used for BFS propagating levels
|
|
+ protected final Delayed8WayDistancePropagator2D.WorkQueue[] levelIncreaseWorkQueues = new Delayed8WayDistancePropagator2D.WorkQueue[64];
|
|
+ {
|
|
+ for (int i = 0; i < this.levelIncreaseWorkQueues.length; ++i) {
|
|
+ this.levelIncreaseWorkQueues[i] = new Delayed8WayDistancePropagator2D.WorkQueue();
|
|
+ }
|
|
+ }
|
|
+ protected final Delayed8WayDistancePropagator2D.WorkQueue[] levelRemoveWorkQueues = new Delayed8WayDistancePropagator2D.WorkQueue[64];
|
|
+ {
|
|
+ for (int i = 0; i < this.levelRemoveWorkQueues.length; ++i) {
|
|
+ this.levelRemoveWorkQueues[i] = new Delayed8WayDistancePropagator2D.WorkQueue();
|
|
+ }
|
|
+ }
|
|
+ protected long levelIncreaseWorkQueueBitset;
|
|
+ protected long levelRemoveWorkQueueBitset;
|
|
+
|
|
+ protected final void addToIncreaseWorkQueue(final long coordinate, final byte level) {
|
|
+ final Delayed8WayDistancePropagator2D.WorkQueue queue = this.levelIncreaseWorkQueues[level];
|
|
+ queue.queuedCoordinates.enqueue(coordinate);
|
|
+ queue.queuedLevels.enqueue(level);
|
|
+
|
|
+ this.levelIncreaseWorkQueueBitset |= (1L << level);
|
|
+ }
|
|
+
|
|
+ protected final void addToIncreaseWorkQueue(final long coordinate, final byte index, final byte level) {
|
|
+ final Delayed8WayDistancePropagator2D.WorkQueue queue = this.levelIncreaseWorkQueues[index];
|
|
+ queue.queuedCoordinates.enqueue(coordinate);
|
|
+ queue.queuedLevels.enqueue(level);
|
|
+
|
|
+ this.levelIncreaseWorkQueueBitset |= (1L << index);
|
|
+ }
|
|
+
|
|
+ protected final void addToRemoveWorkQueue(final long coordinate, final byte level) {
|
|
+ final Delayed8WayDistancePropagator2D.WorkQueue queue = this.levelRemoveWorkQueues[level];
|
|
+ queue.queuedCoordinates.enqueue(coordinate);
|
|
+ queue.queuedLevels.enqueue(level);
|
|
+
|
|
+ this.levelRemoveWorkQueueBitset |= (1L << level);
|
|
+ }
|
|
+
|
|
+ public boolean propagateUpdates() {
|
|
+ if (this.updatedSources.isEmpty()) {
|
|
+ return false;
|
|
+ }
|
|
+
|
|
+ boolean ret = false;
|
|
+
|
|
+ for (final LongIterator iterator = this.updatedSources.iterator(); iterator.hasNext();) {
|
|
+ final long coordinate = iterator.nextLong();
|
|
+
|
|
+ final byte currentLevel = this.levels.get(coordinate);
|
|
+ final byte updatedSource = this.sources.get(coordinate);
|
|
+
|
|
+ if (currentLevel == updatedSource) {
|
|
+ continue;
|
|
+ }
|
|
+ ret = true;
|
|
+
|
|
+ if (updatedSource > currentLevel) {
|
|
+ // level increase
|
|
+ this.addToIncreaseWorkQueue(coordinate, updatedSource);
|
|
+ } else {
|
|
+ // level decrease
|
|
+ this.addToRemoveWorkQueue(coordinate, currentLevel);
|
|
+ // if the current coordinate is a source, then the decrease propagation will detect that and queue
|
|
+ // the source propagation
|
|
+ }
|
|
+ }
|
|
+
|
|
+ this.updatedSources.clear();
|
|
+
|
|
+ // propagate source level increases first for performance reasons (in crowded areas hopefully the additions
|
|
+ // make the removes remove less)
|
|
+ this.propagateIncreases();
|
|
+
|
|
+ // now we propagate the decreases (which will then re-propagate clobbered sources)
|
|
+ this.propagateDecreases();
|
|
+
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ protected void propagateIncreases() {
|
|
+ for (int queueIndex = 63 ^ Long.numberOfLeadingZeros(this.levelIncreaseWorkQueueBitset);
|
|
+ this.levelIncreaseWorkQueueBitset != 0L;
|
|
+ this.levelIncreaseWorkQueueBitset ^= (1L << queueIndex), queueIndex = 63 ^ Long.numberOfLeadingZeros(this.levelIncreaseWorkQueueBitset)) {
|
|
+
|
|
+ final Delayed8WayDistancePropagator2D.WorkQueue queue = this.levelIncreaseWorkQueues[queueIndex];
|
|
+ while (!queue.queuedLevels.isEmpty()) {
|
|
+ final long coordinate = queue.queuedCoordinates.removeFirstLong();
|
|
+ byte level = queue.queuedLevels.removeFirstByte();
|
|
+
|
|
+ final boolean neighbourCheck = level < 0;
|
|
+
|
|
+ final byte currentLevel;
|
|
+ if (neighbourCheck) {
|
|
+ level = (byte)-level;
|
|
+ currentLevel = this.levels.get(coordinate);
|
|
+ } else {
|
|
+ currentLevel = this.levels.putIfGreater(coordinate, level);
|
|
+ }
|
|
+
|
|
+ if (neighbourCheck) {
|
|
+ // used when propagating from decrease to indicate that this level needs to check its neighbours
|
|
+ // this means the level at coordinate could be equal, but would still need neighbours checked
|
|
+
|
|
+ if (currentLevel != level) {
|
|
+ // something caused the level to change, which means something propagated to it (which means
|
|
+ // us propagating here is redundant), or something removed the level (which means we
|
|
+ // cannot propagate further)
|
|
+ continue;
|
|
+ }
|
|
+ } else if (currentLevel >= level) {
|
|
+ // something higher/equal propagated
|
|
+ continue;
|
|
+ }
|
|
+ if (this.changeCallback != null) {
|
|
+ this.changeCallback.onLevelUpdate(coordinate, currentLevel, level);
|
|
+ }
|
|
+
|
|
+ if (level == 1) {
|
|
+ // can't propagate 0 to neighbours
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ // propagate to neighbours
|
|
+ final byte neighbourLevel = (byte)(level - 1);
|
|
+ final int x = CoordinateUtils.getChunkSectionX(coordinate);
|
|
+ final int y = CoordinateUtils.getChunkSectionY(coordinate);
|
|
+ final int z = CoordinateUtils.getChunkSectionZ(coordinate);
|
|
+
|
|
+ for (int dy = -1; dy <= 1; ++dy) {
|
|
+ for (int dz = -1; dz <= 1; ++dz) {
|
|
+ for (int dx = -1; dx <= 1; ++dx) {
|
|
+ if ((dy | dz | dx) == 0) {
|
|
+ // already propagated to coordinate
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ // sure we can check the neighbour level in the map right now and avoid a propagation,
|
|
+ // but then we would still have to recheck it when popping the value off of the queue!
|
|
+ // so just avoid the double lookup
|
|
+ final long neighbourCoordinate = CoordinateUtils.getChunkSectionKey(dx + x, dy + y, dz + z);
|
|
+ this.addToIncreaseWorkQueue(neighbourCoordinate, neighbourLevel);
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ protected void propagateDecreases() {
|
|
+ for (int queueIndex = 63 ^ Long.numberOfLeadingZeros(this.levelRemoveWorkQueueBitset);
|
|
+ this.levelRemoveWorkQueueBitset != 0L;
|
|
+ this.levelRemoveWorkQueueBitset ^= (1L << queueIndex), queueIndex = 63 ^ Long.numberOfLeadingZeros(this.levelRemoveWorkQueueBitset)) {
|
|
+
|
|
+ final Delayed8WayDistancePropagator2D.WorkQueue queue = this.levelRemoveWorkQueues[queueIndex];
|
|
+ while (!queue.queuedLevels.isEmpty()) {
|
|
+ final long coordinate = queue.queuedCoordinates.removeFirstLong();
|
|
+ final byte level = queue.queuedLevels.removeFirstByte();
|
|
+
|
|
+ final byte currentLevel = this.levels.removeIfGreaterOrEqual(coordinate, level);
|
|
+ if (currentLevel == 0) {
|
|
+ // something else removed
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ if (currentLevel > level) {
|
|
+ // something higher propagated here or we hit the propagation of another source
|
|
+ // in the second case we need to re-propagate because we could have just clobbered another source's
|
|
+ // propagation
|
|
+ this.addToIncreaseWorkQueue(coordinate, currentLevel, (byte)-currentLevel); // indicate to the increase code that the level's neighbours need checking
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ if (this.changeCallback != null) {
|
|
+ this.changeCallback.onLevelUpdate(coordinate, currentLevel, (byte)0);
|
|
+ }
|
|
+
|
|
+ final byte source = this.sources.get(coordinate);
|
|
+ if (source != 0) {
|
|
+ // must re-propagate source later
|
|
+ this.addToIncreaseWorkQueue(coordinate, source);
|
|
+ }
|
|
+
|
|
+ if (level == 0) {
|
|
+ // can't propagate -1 to neighbours
|
|
+ // we have to check neighbours for removing 1 just in case the neighbour is 2
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ // propagate to neighbours
|
|
+ final byte neighbourLevel = (byte)(level - 1);
|
|
+ final int x = CoordinateUtils.getChunkSectionX(coordinate);
|
|
+ final int y = CoordinateUtils.getChunkSectionY(coordinate);
|
|
+ final int z = CoordinateUtils.getChunkSectionZ(coordinate);
|
|
+
|
|
+ for (int dy = -1; dy <= 1; ++dy) {
|
|
+ for (int dz = -1; dz <= 1; ++dz) {
|
|
+ for (int dx = -1; dx <= 1; ++dx) {
|
|
+ if ((dy | dz | dx) == 0) {
|
|
+ // already propagated to coordinate
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ // sure we can check the neighbour level in the map right now and avoid a propagation,
|
|
+ // but then we would still have to recheck it when popping the value off of the queue!
|
|
+ // so just avoid the double lookup
|
|
+ final long neighbourCoordinate = CoordinateUtils.getChunkSectionKey(dx + x, dy + y, dz + z);
|
|
+ this.addToRemoveWorkQueue(neighbourCoordinate, neighbourLevel);
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ // propagate sources we clobbered in the process
|
|
+ this.propagateIncreases();
|
|
+ }
|
|
+}
|
|
diff --git a/src/main/java/ca/spottedleaf/moonrise/common/misc/Delayed8WayDistancePropagator2D.java b/src/main/java/ca/spottedleaf/moonrise/common/misc/Delayed8WayDistancePropagator2D.java
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
|
|
--- /dev/null
|
|
+++ b/src/main/java/ca/spottedleaf/moonrise/common/misc/Delayed8WayDistancePropagator2D.java
|
|
@@ -0,0 +0,0 @@
|
|
+package ca.spottedleaf.moonrise.common.misc;
|
|
+
|
|
+import ca.spottedleaf.moonrise.common.util.CoordinateUtils;
|
|
+import it.unimi.dsi.fastutil.HashCommon;
|
|
+import it.unimi.dsi.fastutil.bytes.ByteArrayFIFOQueue;
|
|
+import it.unimi.dsi.fastutil.longs.Long2ByteOpenHashMap;
|
|
+import it.unimi.dsi.fastutil.longs.LongArrayFIFOQueue;
|
|
+import it.unimi.dsi.fastutil.longs.LongIterator;
|
|
+import it.unimi.dsi.fastutil.longs.LongLinkedOpenHashSet;
|
|
+
|
|
+public final class Delayed8WayDistancePropagator2D {
|
|
+
|
|
+ // Test
|
|
+ /*
|
|
+ protected static void test(int x, int z, com.destroystokyo.paper.util.misc.DistanceTrackingAreaMap<Ticket> reference, Delayed8WayDistancePropagator2D test) {
|
|
+ int got = test.getLevel(x, z);
|
|
+
|
|
+ int expect = 0;
|
|
+ Object[] nearest = reference.getObjectsInRange(x, z) == null ? null : reference.getObjectsInRange(x, z).getBackingSet();
|
|
+ if (nearest != null) {
|
|
+ for (Object _obj : nearest) {
|
|
+ if (_obj instanceof Ticket) {
|
|
+ Ticket ticket = (Ticket)_obj;
|
|
+ long ticketCoord = reference.getLastCoordinate(ticket);
|
|
+ int viewDistance = reference.getLastViewDistance(ticket);
|
|
+ int distance = Math.max(com.destroystokyo.paper.util.math.IntegerUtil.branchlessAbs(MCUtil.getCoordinateX(ticketCoord) - x),
|
|
+ com.destroystokyo.paper.util.math.IntegerUtil.branchlessAbs(MCUtil.getCoordinateZ(ticketCoord) - z));
|
|
+ int level = viewDistance - distance;
|
|
+ if (level > expect) {
|
|
+ expect = level;
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ if (expect != got) {
|
|
+ throw new IllegalStateException("Expected " + expect + " at pos (" + x + "," + z + ") but got " + got);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ static class Ticket {
|
|
+
|
|
+ int x;
|
|
+ int z;
|
|
+
|
|
+ final com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<Ticket> empty
|
|
+ = new com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<>(this);
|
|
+
|
|
+ }
|
|
+
|
|
+ public static void main(final String[] args) {
|
|
+ com.destroystokyo.paper.util.misc.DistanceTrackingAreaMap<Ticket> reference = new com.destroystokyo.paper.util.misc.DistanceTrackingAreaMap<Ticket>() {
|
|
+ @Override
|
|
+ protected com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<Ticket> getEmptySetFor(Ticket object) {
|
|
+ return object.empty;
|
|
+ }
|
|
+ };
|
|
+ Delayed8WayDistancePropagator2D test = new Delayed8WayDistancePropagator2D();
|
|
+
|
|
+ final int maxDistance = 64;
|
|
+ // test origin
|
|
+ {
|
|
+ Ticket originTicket = new Ticket();
|
|
+ int originDistance = 31;
|
|
+ // test single source
|
|
+ reference.add(originTicket, 0, 0, originDistance);
|
|
+ test.setSource(0, 0, originDistance); test.propagateUpdates(); // set and propagate
|
|
+ for (int dx = -originDistance; dx <= originDistance; ++dx) {
|
|
+ for (int dz = -originDistance; dz <= originDistance; ++dz) {
|
|
+ test(dx, dz, reference, test);
|
|
+ }
|
|
+ }
|
|
+ // test single source decrease
|
|
+ reference.update(originTicket, 0, 0, originDistance/2);
|
|
+ test.setSource(0, 0, originDistance/2); test.propagateUpdates(); // set and propagate
|
|
+ for (int dx = -originDistance; dx <= originDistance; ++dx) {
|
|
+ for (int dz = -originDistance; dz <= originDistance; ++dz) {
|
|
+ test(dx, dz, reference, test);
|
|
+ }
|
|
+ }
|
|
+ // test source increase
|
|
+ originDistance = 2*originDistance;
|
|
+ reference.update(originTicket, 0, 0, originDistance);
|
|
+ test.setSource(0, 0, originDistance); test.propagateUpdates(); // set and propagate
|
|
+ for (int dx = -4*originDistance; dx <= 4*originDistance; ++dx) {
|
|
+ for (int dz = -4*originDistance; dz <= 4*originDistance; ++dz) {
|
|
+ test(dx, dz, reference, test);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ reference.remove(originTicket);
|
|
+ test.removeSource(0, 0); test.propagateUpdates();
|
|
+ }
|
|
+
|
|
+ // test multiple sources at origin
|
|
+ {
|
|
+ int originDistance = 31;
|
|
+ java.util.List<Ticket> list = new java.util.ArrayList<>();
|
|
+ for (int i = 0; i < 10; ++i) {
|
|
+ Ticket a = new Ticket();
|
|
+ list.add(a);
|
|
+ a.x = (i & 1) == 1 ? -i : i;
|
|
+ a.z = (i & 1) == 1 ? -i : i;
|
|
+ }
|
|
+ for (Ticket ticket : list) {
|
|
+ reference.add(ticket, ticket.x, ticket.z, originDistance);
|
|
+ test.setSource(ticket.x, ticket.z, originDistance);
|
|
+ }
|
|
+ test.propagateUpdates();
|
|
+
|
|
+ for (int dx = -8*originDistance; dx <= 8*originDistance; ++dx) {
|
|
+ for (int dz = -8*originDistance; dz <= 8*originDistance; ++dz) {
|
|
+ test(dx, dz, reference, test);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ // test ticket level decrease
|
|
+
|
|
+ for (Ticket ticket : list) {
|
|
+ reference.update(ticket, ticket.x, ticket.z, originDistance/2);
|
|
+ test.setSource(ticket.x, ticket.z, originDistance/2);
|
|
+ }
|
|
+ test.propagateUpdates();
|
|
+
|
|
+ for (int dx = -8*originDistance; dx <= 8*originDistance; ++dx) {
|
|
+ for (int dz = -8*originDistance; dz <= 8*originDistance; ++dz) {
|
|
+ test(dx, dz, reference, test);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ // test ticket level increase
|
|
+
|
|
+ for (Ticket ticket : list) {
|
|
+ reference.update(ticket, ticket.x, ticket.z, originDistance*2);
|
|
+ test.setSource(ticket.x, ticket.z, originDistance*2);
|
|
+ }
|
|
+ test.propagateUpdates();
|
|
+
|
|
+ for (int dx = -16*originDistance; dx <= 16*originDistance; ++dx) {
|
|
+ for (int dz = -16*originDistance; dz <= 16*originDistance; ++dz) {
|
|
+ test(dx, dz, reference, test);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ // test ticket remove
|
|
+ for (int i = 0, len = list.size(); i < len; ++i) {
|
|
+ if ((i & 3) != 0) {
|
|
+ continue;
|
|
+ }
|
|
+ Ticket ticket = list.get(i);
|
|
+ reference.remove(ticket);
|
|
+ test.removeSource(ticket.x, ticket.z);
|
|
+ }
|
|
+ test.propagateUpdates();
|
|
+
|
|
+ for (int dx = -16*originDistance; dx <= 16*originDistance; ++dx) {
|
|
+ for (int dz = -16*originDistance; dz <= 16*originDistance; ++dz) {
|
|
+ test(dx, dz, reference, test);
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ // now test at coordinate offsets
|
|
+ // test offset
|
|
+ {
|
|
+ Ticket originTicket = new Ticket();
|
|
+ int originDistance = 31;
|
|
+ int offX = 54432;
|
|
+ int offZ = -134567;
|
|
+ // test single source
|
|
+ reference.add(originTicket, offX, offZ, originDistance);
|
|
+ test.setSource(offX, offZ, originDistance); test.propagateUpdates(); // set and propagate
|
|
+ for (int dx = -originDistance; dx <= originDistance; ++dx) {
|
|
+ for (int dz = -originDistance; dz <= originDistance; ++dz) {
|
|
+ test(dx + offX, dz + offZ, reference, test);
|
|
+ }
|
|
+ }
|
|
+ // test single source decrease
|
|
+ reference.update(originTicket, offX, offZ, originDistance/2);
|
|
+ test.setSource(offX, offZ, originDistance/2); test.propagateUpdates(); // set and propagate
|
|
+ for (int dx = -originDistance; dx <= originDistance; ++dx) {
|
|
+ for (int dz = -originDistance; dz <= originDistance; ++dz) {
|
|
+ test(dx + offX, dz + offZ, reference, test);
|
|
+ }
|
|
+ }
|
|
+ // test source increase
|
|
+ originDistance = 2*originDistance;
|
|
+ reference.update(originTicket, offX, offZ, originDistance);
|
|
+ test.setSource(offX, offZ, originDistance); test.propagateUpdates(); // set and propagate
|
|
+ for (int dx = -4*originDistance; dx <= 4*originDistance; ++dx) {
|
|
+ for (int dz = -4*originDistance; dz <= 4*originDistance; ++dz) {
|
|
+ test(dx + offX, dz + offZ, reference, test);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ reference.remove(originTicket);
|
|
+ test.removeSource(offX, offZ); test.propagateUpdates();
|
|
+ }
|
|
+
|
|
+ // test multiple sources at origin
|
|
+ {
|
|
+ int originDistance = 31;
|
|
+ int offX = 54432;
|
|
+ int offZ = -134567;
|
|
+ java.util.List<Ticket> list = new java.util.ArrayList<>();
|
|
+ for (int i = 0; i < 10; ++i) {
|
|
+ Ticket a = new Ticket();
|
|
+ list.add(a);
|
|
+ a.x = offX + ((i & 1) == 1 ? -i : i);
|
|
+ a.z = offZ + ((i & 1) == 1 ? -i : i);
|
|
+ }
|
|
+ for (Ticket ticket : list) {
|
|
+ reference.add(ticket, ticket.x, ticket.z, originDistance);
|
|
+ test.setSource(ticket.x, ticket.z, originDistance);
|
|
+ }
|
|
+ test.propagateUpdates();
|
|
+
|
|
+ for (int dx = -8*originDistance; dx <= 8*originDistance; ++dx) {
|
|
+ for (int dz = -8*originDistance; dz <= 8*originDistance; ++dz) {
|
|
+ test(dx, dz, reference, test);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ // test ticket level decrease
|
|
+
|
|
+ for (Ticket ticket : list) {
|
|
+ reference.update(ticket, ticket.x, ticket.z, originDistance/2);
|
|
+ test.setSource(ticket.x, ticket.z, originDistance/2);
|
|
+ }
|
|
+ test.propagateUpdates();
|
|
+
|
|
+ for (int dx = -8*originDistance; dx <= 8*originDistance; ++dx) {
|
|
+ for (int dz = -8*originDistance; dz <= 8*originDistance; ++dz) {
|
|
+ test(dx, dz, reference, test);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ // test ticket level increase
|
|
+
|
|
+ for (Ticket ticket : list) {
|
|
+ reference.update(ticket, ticket.x, ticket.z, originDistance*2);
|
|
+ test.setSource(ticket.x, ticket.z, originDistance*2);
|
|
+ }
|
|
+ test.propagateUpdates();
|
|
+
|
|
+ for (int dx = -16*originDistance; dx <= 16*originDistance; ++dx) {
|
|
+ for (int dz = -16*originDistance; dz <= 16*originDistance; ++dz) {
|
|
+ test(dx, dz, reference, test);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ // test ticket remove
|
|
+ for (int i = 0, len = list.size(); i < len; ++i) {
|
|
+ if ((i & 3) != 0) {
|
|
+ continue;
|
|
+ }
|
|
+ Ticket ticket = list.get(i);
|
|
+ reference.remove(ticket);
|
|
+ test.removeSource(ticket.x, ticket.z);
|
|
+ }
|
|
+ test.propagateUpdates();
|
|
+
|
|
+ for (int dx = -16*originDistance; dx <= 16*originDistance; ++dx) {
|
|
+ for (int dz = -16*originDistance; dz <= 16*originDistance; ++dz) {
|
|
+ test(dx, dz, reference, test);
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ */
|
|
+
|
|
+ // this map is considered "stale" unless updates are propagated.
|
|
+ protected final LevelMap levels = new LevelMap(8192*2, 0.6f);
|
|
+
|
|
+ // this map is never stale
|
|
+ protected final Long2ByteOpenHashMap sources = new Long2ByteOpenHashMap(4096, 0.6f);
|
|
+
|
|
+ // Generally updates to positions are made close to other updates, so we link to decrease cache misses when
|
|
+ // propagating updates
|
|
+ protected final LongLinkedOpenHashSet updatedSources = new LongLinkedOpenHashSet();
|
|
+
|
|
+ @FunctionalInterface
|
|
+ public static interface LevelChangeCallback {
|
|
+
|
|
+ /**
|
|
+ * This can be called for intermediate updates. So do not rely on newLevel being close to or
|
|
+ * the exact level that is expected after a full propagation has occured.
|
|
+ */
|
|
+ public void onLevelUpdate(final long coordinate, final byte oldLevel, final byte newLevel);
|
|
+
|
|
+ }
|
|
+
|
|
+ protected final LevelChangeCallback changeCallback;
|
|
+
|
|
+ public Delayed8WayDistancePropagator2D() {
|
|
+ this(null);
|
|
+ }
|
|
+
|
|
+ public Delayed8WayDistancePropagator2D(final LevelChangeCallback changeCallback) {
|
|
+ this.changeCallback = changeCallback;
|
|
+ }
|
|
+
|
|
+ public int getLevel(final long pos) {
|
|
+ return this.levels.get(pos);
|
|
+ }
|
|
+
|
|
+ public int getLevel(final int x, final int z) {
|
|
+ return this.levels.get(CoordinateUtils.getChunkKey(x, z));
|
|
+ }
|
|
+
|
|
+ public void setSource(final int x, final int z, final int level) {
|
|
+ this.setSource(CoordinateUtils.getChunkKey(x, z), level);
|
|
+ }
|
|
+
|
|
+ public void setSource(final long coordinate, final int level) {
|
|
+ if ((level & 63) != level || level == 0) {
|
|
+ throw new IllegalArgumentException("Level must be in (0, 63], not " + level);
|
|
+ }
|
|
+
|
|
+ final byte byteLevel = (byte)level;
|
|
+ final byte oldLevel = this.sources.put(coordinate, byteLevel);
|
|
+
|
|
+ if (oldLevel == byteLevel) {
|
|
+ return; // nothing to do
|
|
+ }
|
|
+
|
|
+ // queue to update later
|
|
+ this.updatedSources.add(coordinate);
|
|
+ }
|
|
+
|
|
+ public void removeSource(final int x, final int z) {
|
|
+ this.removeSource(CoordinateUtils.getChunkKey(x, z));
|
|
+ }
|
|
+
|
|
+ public void removeSource(final long coordinate) {
|
|
+ if (this.sources.remove(coordinate) != 0) {
|
|
+ this.updatedSources.add(coordinate);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ // queues used for BFS propagating levels
|
|
+ protected final WorkQueue[] levelIncreaseWorkQueues = new WorkQueue[64];
|
|
+ {
|
|
+ for (int i = 0; i < this.levelIncreaseWorkQueues.length; ++i) {
|
|
+ this.levelIncreaseWorkQueues[i] = new WorkQueue();
|
|
+ }
|
|
+ }
|
|
+ protected final WorkQueue[] levelRemoveWorkQueues = new WorkQueue[64];
|
|
+ {
|
|
+ for (int i = 0; i < this.levelRemoveWorkQueues.length; ++i) {
|
|
+ this.levelRemoveWorkQueues[i] = new WorkQueue();
|
|
+ }
|
|
+ }
|
|
+ protected long levelIncreaseWorkQueueBitset;
|
|
+ protected long levelRemoveWorkQueueBitset;
|
|
+
|
|
+ protected final void addToIncreaseWorkQueue(final long coordinate, final byte level) {
|
|
+ final WorkQueue queue = this.levelIncreaseWorkQueues[level];
|
|
+ queue.queuedCoordinates.enqueue(coordinate);
|
|
+ queue.queuedLevels.enqueue(level);
|
|
+
|
|
+ this.levelIncreaseWorkQueueBitset |= (1L << level);
|
|
+ }
|
|
+
|
|
+ protected final void addToIncreaseWorkQueue(final long coordinate, final byte index, final byte level) {
|
|
+ final WorkQueue queue = this.levelIncreaseWorkQueues[index];
|
|
+ queue.queuedCoordinates.enqueue(coordinate);
|
|
+ queue.queuedLevels.enqueue(level);
|
|
+
|
|
+ this.levelIncreaseWorkQueueBitset |= (1L << index);
|
|
+ }
|
|
+
|
|
+ protected final void addToRemoveWorkQueue(final long coordinate, final byte level) {
|
|
+ final WorkQueue queue = this.levelRemoveWorkQueues[level];
|
|
+ queue.queuedCoordinates.enqueue(coordinate);
|
|
+ queue.queuedLevels.enqueue(level);
|
|
+
|
|
+ this.levelRemoveWorkQueueBitset |= (1L << level);
|
|
+ }
|
|
+
|
|
+ public boolean propagateUpdates() {
|
|
+ if (this.updatedSources.isEmpty()) {
|
|
+ return false;
|
|
+ }
|
|
+
|
|
+ boolean ret = false;
|
|
+
|
|
+ for (final LongIterator iterator = this.updatedSources.iterator(); iterator.hasNext();) {
|
|
+ final long coordinate = iterator.nextLong();
|
|
+
|
|
+ final byte currentLevel = this.levels.get(coordinate);
|
|
+ final byte updatedSource = this.sources.get(coordinate);
|
|
+
|
|
+ if (currentLevel == updatedSource) {
|
|
+ continue;
|
|
+ }
|
|
+ ret = true;
|
|
+
|
|
+ if (updatedSource > currentLevel) {
|
|
+ // level increase
|
|
+ this.addToIncreaseWorkQueue(coordinate, updatedSource);
|
|
+ } else {
|
|
+ // level decrease
|
|
+ this.addToRemoveWorkQueue(coordinate, currentLevel);
|
|
+ // if the current coordinate is a source, then the decrease propagation will detect that and queue
|
|
+ // the source propagation
|
|
+ }
|
|
+ }
|
|
+
|
|
+ this.updatedSources.clear();
|
|
+
|
|
+ // propagate source level increases first for performance reasons (in crowded areas hopefully the additions
|
|
+ // make the removes remove less)
|
|
+ this.propagateIncreases();
|
|
+
|
|
+ // now we propagate the decreases (which will then re-propagate clobbered sources)
|
|
+ this.propagateDecreases();
|
|
+
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ protected void propagateIncreases() {
|
|
+ for (int queueIndex = 63 ^ Long.numberOfLeadingZeros(this.levelIncreaseWorkQueueBitset);
|
|
+ this.levelIncreaseWorkQueueBitset != 0L;
|
|
+ this.levelIncreaseWorkQueueBitset ^= (1L << queueIndex), queueIndex = 63 ^ Long.numberOfLeadingZeros(this.levelIncreaseWorkQueueBitset)) {
|
|
+
|
|
+ final WorkQueue queue = this.levelIncreaseWorkQueues[queueIndex];
|
|
+ while (!queue.queuedLevels.isEmpty()) {
|
|
+ final long coordinate = queue.queuedCoordinates.removeFirstLong();
|
|
+ byte level = queue.queuedLevels.removeFirstByte();
|
|
+
|
|
+ final boolean neighbourCheck = level < 0;
|
|
+
|
|
+ final byte currentLevel;
|
|
+ if (neighbourCheck) {
|
|
+ level = (byte)-level;
|
|
+ currentLevel = this.levels.get(coordinate);
|
|
+ } else {
|
|
+ currentLevel = this.levels.putIfGreater(coordinate, level);
|
|
+ }
|
|
+
|
|
+ if (neighbourCheck) {
|
|
+ // used when propagating from decrease to indicate that this level needs to check its neighbours
|
|
+ // this means the level at coordinate could be equal, but would still need neighbours checked
|
|
+
|
|
+ if (currentLevel != level) {
|
|
+ // something caused the level to change, which means something propagated to it (which means
|
|
+ // us propagating here is redundant), or something removed the level (which means we
|
|
+ // cannot propagate further)
|
|
+ continue;
|
|
+ }
|
|
+ } else if (currentLevel >= level) {
|
|
+ // something higher/equal propagated
|
|
+ continue;
|
|
+ }
|
|
+ if (this.changeCallback != null) {
|
|
+ this.changeCallback.onLevelUpdate(coordinate, currentLevel, level);
|
|
+ }
|
|
+
|
|
+ if (level == 1) {
|
|
+ // can't propagate 0 to neighbours
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ // propagate to neighbours
|
|
+ final byte neighbourLevel = (byte)(level - 1);
|
|
+ final int x = (int)coordinate;
|
|
+ final int z = (int)(coordinate >>> 32);
|
|
+
|
|
+ for (int dx = -1; dx <= 1; ++dx) {
|
|
+ for (int dz = -1; dz <= 1; ++dz) {
|
|
+ if ((dx | dz) == 0) {
|
|
+ // already propagated to coordinate
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ // sure we can check the neighbour level in the map right now and avoid a propagation,
|
|
+ // but then we would still have to recheck it when popping the value off of the queue!
|
|
+ // so just avoid the double lookup
|
|
+ final long neighbourCoordinate = CoordinateUtils.getChunkKey(x + dx, z + dz);
|
|
+ this.addToIncreaseWorkQueue(neighbourCoordinate, neighbourLevel);
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ protected void propagateDecreases() {
|
|
+ for (int queueIndex = 63 ^ Long.numberOfLeadingZeros(this.levelRemoveWorkQueueBitset);
|
|
+ this.levelRemoveWorkQueueBitset != 0L;
|
|
+ this.levelRemoveWorkQueueBitset ^= (1L << queueIndex), queueIndex = 63 ^ Long.numberOfLeadingZeros(this.levelRemoveWorkQueueBitset)) {
|
|
+
|
|
+ final WorkQueue queue = this.levelRemoveWorkQueues[queueIndex];
|
|
+ while (!queue.queuedLevels.isEmpty()) {
|
|
+ final long coordinate = queue.queuedCoordinates.removeFirstLong();
|
|
+ final byte level = queue.queuedLevels.removeFirstByte();
|
|
+
|
|
+ final byte currentLevel = this.levels.removeIfGreaterOrEqual(coordinate, level);
|
|
+ if (currentLevel == 0) {
|
|
+ // something else removed
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ if (currentLevel > level) {
|
|
+ // something higher propagated here or we hit the propagation of another source
|
|
+ // in the second case we need to re-propagate because we could have just clobbered another source's
|
|
+ // propagation
|
|
+ this.addToIncreaseWorkQueue(coordinate, currentLevel, (byte)-currentLevel); // indicate to the increase code that the level's neighbours need checking
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ if (this.changeCallback != null) {
|
|
+ this.changeCallback.onLevelUpdate(coordinate, currentLevel, (byte)0);
|
|
+ }
|
|
+
|
|
+ final byte source = this.sources.get(coordinate);
|
|
+ if (source != 0) {
|
|
+ // must re-propagate source later
|
|
+ this.addToIncreaseWorkQueue(coordinate, source);
|
|
+ }
|
|
+
|
|
+ if (level == 0) {
|
|
+ // can't propagate -1 to neighbours
|
|
+ // we have to check neighbours for removing 1 just in case the neighbour is 2
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ // propagate to neighbours
|
|
+ final byte neighbourLevel = (byte)(level - 1);
|
|
+ final int x = (int)coordinate;
|
|
+ final int z = (int)(coordinate >>> 32);
|
|
+
|
|
+ for (int dx = -1; dx <= 1; ++dx) {
|
|
+ for (int dz = -1; dz <= 1; ++dz) {
|
|
+ if ((dx | dz) == 0) {
|
|
+ // already propagated to coordinate
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ // sure we can check the neighbour level in the map right now and avoid a propagation,
|
|
+ // but then we would still have to recheck it when popping the value off of the queue!
|
|
+ // so just avoid the double lookup
|
|
+ final long neighbourCoordinate = CoordinateUtils.getChunkKey(x + dx, z + dz);
|
|
+ this.addToRemoveWorkQueue(neighbourCoordinate, neighbourLevel);
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ // propagate sources we clobbered in the process
|
|
+ this.propagateIncreases();
|
|
+ }
|
|
+
|
|
+ protected static final class LevelMap extends Long2ByteOpenHashMap {
|
|
+ public LevelMap() {
|
|
+ super();
|
|
+ }
|
|
+
|
|
+ public LevelMap(final int expected, final float loadFactor) {
|
|
+ super(expected, loadFactor);
|
|
+ }
|
|
+
|
|
+ // copied from superclass
|
|
+ private int find(final long k) {
|
|
+ if (k == 0L) {
|
|
+ return this.containsNullKey ? this.n : -(this.n + 1);
|
|
+ } else {
|
|
+ final long[] key = this.key;
|
|
+ long curr;
|
|
+ int pos;
|
|
+ if ((curr = key[pos = (int)HashCommon.mix(k) & this.mask]) == 0L) {
|
|
+ return -(pos + 1);
|
|
+ } else if (k == curr) {
|
|
+ return pos;
|
|
+ } else {
|
|
+ while((curr = key[pos = pos + 1 & this.mask]) != 0L) {
|
|
+ if (k == curr) {
|
|
+ return pos;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ return -(pos + 1);
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ // copied from superclass
|
|
+ private void insert(final int pos, final long k, final byte v) {
|
|
+ if (pos == this.n) {
|
|
+ this.containsNullKey = true;
|
|
+ }
|
|
+
|
|
+ this.key[pos] = k;
|
|
+ this.value[pos] = v;
|
|
+ if (this.size++ >= this.maxFill) {
|
|
+ this.rehash(HashCommon.arraySize(this.size + 1, this.f));
|
|
+ }
|
|
+ }
|
|
+
|
|
+ // copied from superclass
|
|
+ public byte putIfGreater(final long key, final byte value) {
|
|
+ final int pos = this.find(key);
|
|
+ if (pos < 0) {
|
|
+ if (this.defRetValue < value) {
|
|
+ this.insert(-pos - 1, key, value);
|
|
+ }
|
|
+ return this.defRetValue;
|
|
+ } else {
|
|
+ final byte curr = this.value[pos];
|
|
+ if (value > curr) {
|
|
+ this.value[pos] = value;
|
|
+ return curr;
|
|
+ }
|
|
+ return curr;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ // copied from superclass
|
|
+ private void removeEntry(final int pos) {
|
|
+ --this.size;
|
|
+ this.shiftKeys(pos);
|
|
+ if (this.n > this.minN && this.size < this.maxFill / 4 && this.n > 16) {
|
|
+ this.rehash(this.n / 2);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ // copied from superclass
|
|
+ private void removeNullEntry() {
|
|
+ this.containsNullKey = false;
|
|
+ --this.size;
|
|
+ if (this.n > this.minN && this.size < this.maxFill / 4 && this.n > 16) {
|
|
+ this.rehash(this.n / 2);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ // copied from superclass
|
|
+ public byte removeIfGreaterOrEqual(final long key, final byte value) {
|
|
+ if (key == 0L) {
|
|
+ if (!this.containsNullKey) {
|
|
+ return this.defRetValue;
|
|
+ }
|
|
+ final byte current = this.value[this.n];
|
|
+ if (value >= current) {
|
|
+ this.removeNullEntry();
|
|
+ return current;
|
|
+ }
|
|
+ return current;
|
|
+ } else {
|
|
+ long[] keys = this.key;
|
|
+ byte[] values = this.value;
|
|
+ long curr;
|
|
+ int pos;
|
|
+ if ((curr = keys[pos = (int)HashCommon.mix(key) & this.mask]) == 0L) {
|
|
+ return this.defRetValue;
|
|
+ } else if (key == curr) {
|
|
+ final byte current = values[pos];
|
|
+ if (value >= current) {
|
|
+ this.removeEntry(pos);
|
|
+ return current;
|
|
+ }
|
|
+ return current;
|
|
+ } else {
|
|
+ while((curr = keys[pos = pos + 1 & this.mask]) != 0L) {
|
|
+ if (key == curr) {
|
|
+ final byte current = values[pos];
|
|
+ if (value >= current) {
|
|
+ this.removeEntry(pos);
|
|
+ return current;
|
|
+ }
|
|
+ return current;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ return this.defRetValue;
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ protected static final class WorkQueue {
|
|
+
|
|
+ public final NoResizeLongArrayFIFODeque queuedCoordinates = new NoResizeLongArrayFIFODeque();
|
|
+ public final NoResizeByteArrayFIFODeque queuedLevels = new NoResizeByteArrayFIFODeque();
|
|
+
|
|
+ }
|
|
+
|
|
+ protected static final class NoResizeLongArrayFIFODeque extends LongArrayFIFOQueue {
|
|
+
|
|
+ /**
|
|
+ * Assumes non-empty. If empty, undefined behaviour.
|
|
+ */
|
|
+ public long removeFirstLong() {
|
|
+ // copied from superclass
|
|
+ long t = this.array[this.start];
|
|
+ if (++this.start == this.length) {
|
|
+ this.start = 0;
|
|
+ }
|
|
+
|
|
+ return t;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ protected static final class NoResizeByteArrayFIFODeque extends ByteArrayFIFOQueue {
|
|
+
|
|
+ /**
|
|
+ * Assumes non-empty. If empty, undefined behaviour.
|
|
+ */
|
|
+ public byte removeFirstByte() {
|
|
+ // copied from superclass
|
|
+ byte t = this.array[this.start];
|
|
+ if (++this.start == this.length) {
|
|
+ this.start = 0;
|
|
+ }
|
|
+
|
|
+ return t;
|
|
+ }
|
|
+ }
|
|
+}
|
|
diff --git a/src/main/java/ca/spottedleaf/moonrise/common/misc/NearbyPlayers.java b/src/main/java/ca/spottedleaf/moonrise/common/misc/NearbyPlayers.java
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
|
|
--- /dev/null
|
|
+++ b/src/main/java/ca/spottedleaf/moonrise/common/misc/NearbyPlayers.java
|
|
@@ -0,0 +0,0 @@
|
|
+package ca.spottedleaf.moonrise.common.misc;
|
|
+
|
|
+import ca.spottedleaf.moonrise.common.list.ReferenceList;
|
|
+import ca.spottedleaf.moonrise.common.util.CoordinateUtils;
|
|
+import ca.spottedleaf.moonrise.common.util.MoonriseConstants;
|
|
+import ca.spottedleaf.moonrise.common.util.ChunkSystem;
|
|
+import ca.spottedleaf.moonrise.patches.chunk_tick_iteration.ChunkTickConstants;
|
|
+import it.unimi.dsi.fastutil.longs.Long2ReferenceOpenHashMap;
|
|
+import it.unimi.dsi.fastutil.objects.Reference2ReferenceOpenHashMap;
|
|
+import net.minecraft.core.BlockPos;
|
|
+import net.minecraft.server.level.ServerLevel;
|
|
+import net.minecraft.server.level.ServerPlayer;
|
|
+import net.minecraft.world.level.ChunkPos;
|
|
+
|
|
+public final class NearbyPlayers {
|
|
+
|
|
+ public static enum NearbyMapType {
|
|
+ GENERAL,
|
|
+ GENERAL_SMALL,
|
|
+ GENERAL_REALLY_SMALL,
|
|
+ TICK_VIEW_DISTANCE,
|
|
+ VIEW_DISTANCE,
|
|
+ SPAWN_RANGE, // Moonrise - chunk tick iteration
|
|
+ }
|
|
+
|
|
+ private static final NearbyMapType[] MAP_TYPES = NearbyMapType.values();
|
|
+ public static final int TOTAL_MAP_TYPES = MAP_TYPES.length;
|
|
+
|
|
+ private static final int GENERAL_AREA_VIEW_DISTANCE = MoonriseConstants.MAX_VIEW_DISTANCE + 1;
|
|
+ private static final int GENERAL_SMALL_VIEW_DISTANCE = 10;
|
|
+ private static final int GENERAL_REALLY_SMALL_VIEW_DISTANCE = 3;
|
|
+
|
|
+ public static final int GENERAL_AREA_VIEW_DISTANCE_BLOCKS = (GENERAL_AREA_VIEW_DISTANCE << 4);
|
|
+ public static final int GENERAL_SMALL_AREA_VIEW_DISTANCE_BLOCKS = (GENERAL_SMALL_VIEW_DISTANCE << 4);
|
|
+ public static final int GENERAL_REALLY_SMALL_AREA_VIEW_DISTANCE_BLOCKS = (GENERAL_REALLY_SMALL_VIEW_DISTANCE << 4);
|
|
+
|
|
+ private final ServerLevel world;
|
|
+ private final Reference2ReferenceOpenHashMap<ServerPlayer, TrackedPlayer[]> players = new Reference2ReferenceOpenHashMap<>();
|
|
+ private final Long2ReferenceOpenHashMap<TrackedChunk> byChunk = new Long2ReferenceOpenHashMap<>();
|
|
+
|
|
+ public NearbyPlayers(final ServerLevel world) {
|
|
+ this.world = world;
|
|
+ }
|
|
+
|
|
+ public void addPlayer(final ServerPlayer player) {
|
|
+ final TrackedPlayer[] newTrackers = new TrackedPlayer[TOTAL_MAP_TYPES];
|
|
+ if (this.players.putIfAbsent(player, newTrackers) != null) {
|
|
+ throw new IllegalStateException("Already have player " + player);
|
|
+ }
|
|
+
|
|
+ final ChunkPos chunk = player.chunkPosition();
|
|
+
|
|
+ for (int i = 0; i < TOTAL_MAP_TYPES; ++i) {
|
|
+ // use 0 for default, will be updated by tickPlayer
|
|
+ (newTrackers[i] = new TrackedPlayer(player, MAP_TYPES[i])).add(chunk.x, chunk.z, 0);
|
|
+ }
|
|
+
|
|
+ // update view distances
|
|
+ this.tickPlayer(player);
|
|
+ }
|
|
+
|
|
+ public void removePlayer(final ServerPlayer player) {
|
|
+ final TrackedPlayer[] players = this.players.remove(player);
|
|
+ if (players == null) {
|
|
+ return; // May be called during teleportation before the player is actually placed
|
|
+ }
|
|
+
|
|
+ for (final TrackedPlayer tracker : players) {
|
|
+ tracker.remove();
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public void tickPlayer(final ServerPlayer player) {
|
|
+ final TrackedPlayer[] players = this.players.get(player);
|
|
+ if (players == null) {
|
|
+ throw new IllegalStateException("Don't have player " + player);
|
|
+ }
|
|
+
|
|
+ final ChunkPos chunk = player.chunkPosition();
|
|
+
|
|
+ players[NearbyMapType.GENERAL.ordinal()].update(chunk.x, chunk.z, GENERAL_AREA_VIEW_DISTANCE);
|
|
+ players[NearbyMapType.GENERAL_SMALL.ordinal()].update(chunk.x, chunk.z, GENERAL_SMALL_VIEW_DISTANCE);
|
|
+ players[NearbyMapType.GENERAL_REALLY_SMALL.ordinal()].update(chunk.x, chunk.z, GENERAL_REALLY_SMALL_VIEW_DISTANCE);
|
|
+ players[NearbyMapType.TICK_VIEW_DISTANCE.ordinal()].update(chunk.x, chunk.z, ChunkSystem.getTickViewDistance(player));
|
|
+ players[NearbyMapType.VIEW_DISTANCE.ordinal()].update(chunk.x, chunk.z, ChunkSystem.getLoadViewDistance(player));
|
|
+ players[NearbyMapType.SPAWN_RANGE.ordinal()].update(chunk.x, chunk.z, ChunkTickConstants.PLAYER_SPAWN_TRACK_RANGE); // Moonrise - chunk tick iteration
|
|
+ }
|
|
+
|
|
+ public TrackedChunk getChunk(final ChunkPos pos) {
|
|
+ return this.byChunk.get(CoordinateUtils.getChunkKey(pos));
|
|
+ }
|
|
+
|
|
+ public TrackedChunk getChunk(final BlockPos pos) {
|
|
+ return this.byChunk.get(CoordinateUtils.getChunkKey(pos));
|
|
+ }
|
|
+
|
|
+ public ReferenceList<ServerPlayer> getPlayers(final BlockPos pos, final NearbyMapType type) {
|
|
+ final TrackedChunk chunk = this.byChunk.get(CoordinateUtils.getChunkKey(pos));
|
|
+
|
|
+ return chunk == null ? null : chunk.players[type.ordinal()];
|
|
+ }
|
|
+
|
|
+ public ReferenceList<ServerPlayer> getPlayers(final ChunkPos pos, final NearbyMapType type) {
|
|
+ final TrackedChunk chunk = this.byChunk.get(CoordinateUtils.getChunkKey(pos));
|
|
+
|
|
+ return chunk == null ? null : chunk.players[type.ordinal()];
|
|
+ }
|
|
+
|
|
+ public ReferenceList<ServerPlayer> getPlayersByChunk(final int chunkX, final int chunkZ, final NearbyMapType type) {
|
|
+ final TrackedChunk chunk = this.byChunk.get(CoordinateUtils.getChunkKey(chunkX, chunkZ));
|
|
+
|
|
+ return chunk == null ? null : chunk.players[type.ordinal()];
|
|
+ }
|
|
+
|
|
+ public ReferenceList<ServerPlayer> getPlayersByBlock(final int blockX, final int blockZ, final NearbyMapType type) {
|
|
+ final TrackedChunk chunk = this.byChunk.get(CoordinateUtils.getChunkKey(blockX >> 4, blockZ >> 4));
|
|
+
|
|
+ return chunk == null ? null : chunk.players[type.ordinal()];
|
|
+ }
|
|
+
|
|
+ public static final class TrackedChunk {
|
|
+
|
|
+ private static final ServerPlayer[] EMPTY_PLAYERS_ARRAY = new ServerPlayer[0];
|
|
+
|
|
+ private final ReferenceList<ServerPlayer>[] players = new ReferenceList[TOTAL_MAP_TYPES];
|
|
+ private int nonEmptyLists;
|
|
+ private long updateCount;
|
|
+
|
|
+ public boolean isEmpty() {
|
|
+ return this.nonEmptyLists == 0;
|
|
+ }
|
|
+
|
|
+ public long getUpdateCount() {
|
|
+ return this.updateCount;
|
|
+ }
|
|
+
|
|
+ public ReferenceList<ServerPlayer> getPlayers(final NearbyMapType type) {
|
|
+ return this.players[type.ordinal()];
|
|
+ }
|
|
+
|
|
+ public void addPlayer(final ServerPlayer player, final NearbyMapType type) {
|
|
+ ++this.updateCount;
|
|
+
|
|
+ final int idx = type.ordinal();
|
|
+ final ReferenceList<ServerPlayer> list = this.players[idx];
|
|
+ if (list == null) {
|
|
+ ++this.nonEmptyLists;
|
|
+ (this.players[idx] = new ReferenceList<>(EMPTY_PLAYERS_ARRAY)).add(player);
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ if (!list.add(player)) {
|
|
+ throw new IllegalStateException("Already contains player " + player);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public void removePlayer(final ServerPlayer player, final NearbyMapType type) {
|
|
+ ++this.updateCount;
|
|
+
|
|
+ final int idx = type.ordinal();
|
|
+ final ReferenceList<ServerPlayer> list = this.players[idx];
|
|
+ if (list == null) {
|
|
+ throw new IllegalStateException("Does not contain player " + player);
|
|
+ }
|
|
+
|
|
+ if (!list.remove(player)) {
|
|
+ throw new IllegalStateException("Does not contain player " + player);
|
|
+ }
|
|
+
|
|
+ if (list.size() == 0) {
|
|
+ this.players[idx] = null;
|
|
+ --this.nonEmptyLists;
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ private final class TrackedPlayer extends SingleUserAreaMap<ServerPlayer> {
|
|
+
|
|
+ private final NearbyMapType type;
|
|
+
|
|
+ public TrackedPlayer(final ServerPlayer player, final NearbyMapType type) {
|
|
+ super(player);
|
|
+ this.type = type;
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ protected void addCallback(final ServerPlayer parameter, final int chunkX, final int chunkZ) {
|
|
+ final long chunkKey = CoordinateUtils.getChunkKey(chunkX, chunkZ);
|
|
+
|
|
+ NearbyPlayers.this.byChunk.computeIfAbsent(chunkKey, (final long keyInMap) -> {
|
|
+ return new TrackedChunk();
|
|
+ }).addPlayer(parameter, this.type);
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ protected void removeCallback(final ServerPlayer parameter, final int chunkX, final int chunkZ) {
|
|
+ final long chunkKey = CoordinateUtils.getChunkKey(chunkX, chunkZ);
|
|
+
|
|
+ final TrackedChunk chunk = NearbyPlayers.this.byChunk.get(chunkKey);
|
|
+ if (chunk == null) {
|
|
+ throw new IllegalStateException("Chunk should exist at " + new ChunkPos(chunkKey));
|
|
+ }
|
|
+
|
|
+ chunk.removePlayer(parameter, this.type);
|
|
+
|
|
+ if (chunk.isEmpty()) {
|
|
+ NearbyPlayers.this.byChunk.remove(chunkKey);
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+}
|
|
diff --git a/src/main/java/ca/spottedleaf/moonrise/common/misc/PositionCountingAreaMap.java b/src/main/java/ca/spottedleaf/moonrise/common/misc/PositionCountingAreaMap.java
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
|
|
--- /dev/null
|
|
+++ b/src/main/java/ca/spottedleaf/moonrise/common/misc/PositionCountingAreaMap.java
|
|
@@ -0,0 +0,0 @@
|
|
+package ca.spottedleaf.moonrise.common.misc;
|
|
+
|
|
+import ca.spottedleaf.concurrentutil.util.IntPairUtil;
|
|
+import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap;
|
|
+import it.unimi.dsi.fastutil.objects.Reference2ReferenceOpenHashMap;
|
|
+import it.unimi.dsi.fastutil.objects.ReferenceSet;
|
|
+
|
|
+public final class PositionCountingAreaMap<T> {
|
|
+
|
|
+ private final Reference2ReferenceOpenHashMap<T, PositionCounter> counters = new Reference2ReferenceOpenHashMap<>();
|
|
+ private final Long2IntOpenHashMap positions = new Long2IntOpenHashMap();
|
|
+
|
|
+ public ReferenceSet<T> getObjects() {
|
|
+ return this.counters.keySet();
|
|
+ }
|
|
+
|
|
+ public int getTotalPositions() {
|
|
+ return this.positions.size();
|
|
+ }
|
|
+
|
|
+ public boolean hasObjectsNear(final int toX, final int toZ) {
|
|
+ return this.positions.containsKey(IntPairUtil.key(toX, toZ));
|
|
+ }
|
|
+
|
|
+ public int getObjectsNear(final int toX, final int toZ) {
|
|
+ return this.positions.get(IntPairUtil.key(toX, toZ));
|
|
+ }
|
|
+
|
|
+ public boolean add(final T parameter, final int toX, final int toZ, final int distance) {
|
|
+ final PositionCounter existing = this.counters.get(parameter);
|
|
+ if (existing != null) {
|
|
+ return false;
|
|
+ }
|
|
+
|
|
+ final PositionCounter counter = new PositionCounter(parameter);
|
|
+
|
|
+ this.counters.put(parameter, counter);
|
|
+
|
|
+ return counter.add(toX, toZ, distance);
|
|
+ }
|
|
+
|
|
+ public boolean addOrUpdate(final T parameter, final int toX, final int toZ, final int distance) {
|
|
+ final PositionCounter existing = this.counters.get(parameter);
|
|
+ if (existing != null) {
|
|
+ return existing.update(toX, toZ, distance);
|
|
+ }
|
|
+
|
|
+ final PositionCounter counter = new PositionCounter(parameter);
|
|
+
|
|
+ this.counters.put(parameter, counter);
|
|
+
|
|
+ return counter.add(toX, toZ, distance);
|
|
+ }
|
|
+
|
|
+ public boolean remove(final T parameter) {
|
|
+ final PositionCounter counter = this.counters.remove(parameter);
|
|
+ if (counter == null) {
|
|
+ return false;
|
|
+ }
|
|
+
|
|
+ counter.remove();
|
|
+
|
|
+ return true;
|
|
+ }
|
|
+
|
|
+ public boolean update(final T parameter, final int toX, final int toZ, final int distance) {
|
|
+ final PositionCounter counter = this.counters.get(parameter);
|
|
+ if (counter == null) {
|
|
+ return false;
|
|
+ }
|
|
+
|
|
+ return counter.update(toX, toZ, distance);
|
|
+ }
|
|
+
|
|
+ private final class PositionCounter extends SingleUserAreaMap<T> {
|
|
+
|
|
+ public PositionCounter(final T parameter) {
|
|
+ super(parameter);
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ protected void addCallback(final T parameter, final int toX, final int toZ) {
|
|
+ PositionCountingAreaMap.this.positions.addTo(IntPairUtil.key(toX, toZ), 1);
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ protected void removeCallback(final T parameter, final int toX, final int toZ) {
|
|
+ final long key = IntPairUtil.key(toX, toZ);
|
|
+ if (PositionCountingAreaMap.this.positions.addTo(key, -1) == 1) {
|
|
+ PositionCountingAreaMap.this.positions.remove(key);
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+}
|
|
diff --git a/src/main/java/ca/spottedleaf/moonrise/common/misc/SingleUserAreaMap.java b/src/main/java/ca/spottedleaf/moonrise/common/misc/SingleUserAreaMap.java
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
|
|
--- /dev/null
|
|
+++ b/src/main/java/ca/spottedleaf/moonrise/common/misc/SingleUserAreaMap.java
|
|
@@ -0,0 +0,0 @@
|
|
+package ca.spottedleaf.moonrise.common.misc;
|
|
+
|
|
+import ca.spottedleaf.concurrentutil.util.IntegerUtil;
|
|
+
|
|
+public abstract class SingleUserAreaMap<T> {
|
|
+
|
|
+ public static final int NOT_SET = Integer.MIN_VALUE;
|
|
+
|
|
+ private final T parameter;
|
|
+ private int lastChunkX = NOT_SET;
|
|
+ private int lastChunkZ = NOT_SET;
|
|
+ private int distance = NOT_SET;
|
|
+
|
|
+ public SingleUserAreaMap(final T parameter) {
|
|
+ this.parameter = parameter;
|
|
+ }
|
|
+
|
|
+ public final T getParameter() {
|
|
+ return this.parameter;
|
|
+ }
|
|
+
|
|
+ public final int getLastChunkX() {
|
|
+ return this.lastChunkX;
|
|
+ }
|
|
+
|
|
+ public final int getLastChunkZ() {
|
|
+ return this.lastChunkZ;
|
|
+ }
|
|
+
|
|
+ public final int getLastDistance() {
|
|
+ return this.distance;
|
|
+ }
|
|
+
|
|
+ /* math sign function except 0 returns 1 */
|
|
+ protected static int sign(int val) {
|
|
+ return 1 | (val >> (Integer.SIZE - 1));
|
|
+ }
|
|
+
|
|
+ protected abstract void addCallback(final T parameter, final int chunkX, final int chunkZ);
|
|
+
|
|
+ protected abstract void removeCallback(final T parameter, final int chunkX, final int chunkZ);
|
|
+
|
|
+ private void addToNew(final T parameter, final int chunkX, final int chunkZ, final int distance) {
|
|
+ final int maxX = chunkX + distance;
|
|
+ final int maxZ = chunkZ + distance;
|
|
+
|
|
+ for (int cx = chunkX - distance; cx <= maxX; ++cx) {
|
|
+ for (int cz = chunkZ - distance; cz <= maxZ; ++cz) {
|
|
+ this.addCallback(parameter, cx, cz);
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ private void removeFromOld(final T parameter, final int chunkX, final int chunkZ, final int distance) {
|
|
+ final int maxX = chunkX + distance;
|
|
+ final int maxZ = chunkZ + distance;
|
|
+
|
|
+ for (int cx = chunkX - distance; cx <= maxX; ++cx) {
|
|
+ for (int cz = chunkZ - distance; cz <= maxZ; ++cz) {
|
|
+ this.removeCallback(parameter, cx, cz);
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public final boolean add(final int chunkX, final int chunkZ, final int distance) {
|
|
+ if (distance < 0) {
|
|
+ throw new IllegalArgumentException(Integer.toString(distance));
|
|
+ }
|
|
+ if (this.lastChunkX != NOT_SET) {
|
|
+ return false;
|
|
+ }
|
|
+ this.lastChunkX = chunkX;
|
|
+ this.lastChunkZ = chunkZ;
|
|
+ this.distance = distance;
|
|
+
|
|
+ this.addToNew(this.parameter, chunkX, chunkZ, distance);
|
|
+
|
|
+ return true;
|
|
+ }
|
|
+
|
|
+ public final boolean update(final int toX, final int toZ, final int newViewDistance) {
|
|
+ if (newViewDistance < 0) {
|
|
+ throw new IllegalArgumentException(Integer.toString(newViewDistance));
|
|
+ }
|
|
+ final int fromX = this.lastChunkX;
|
|
+ final int fromZ = this.lastChunkZ;
|
|
+ final int oldViewDistance = this.distance;
|
|
+ if (fromX == NOT_SET) {
|
|
+ return false;
|
|
+ }
|
|
+
|
|
+ this.lastChunkX = toX;
|
|
+ this.lastChunkZ = toZ;
|
|
+ this.distance = newViewDistance;
|
|
+
|
|
+ final T parameter = this.parameter;
|
|
+
|
|
+
|
|
+ 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.removeFromOld(parameter, fromX, fromZ, oldViewDistance);
|
|
+ this.addToNew(parameter, toX, toZ, newViewDistance);
|
|
+ return true;
|
|
+ }
|
|
+
|
|
+ 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.removeCallback(parameter, currX, currZ);
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ // 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.addCallback(parameter, currX, currZ);
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ return true;
|
|
+ }
|
|
+
|
|
+ // 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.addCallback(parameter, currX, currZ);
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ 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.addCallback(parameter, currX, currZ);
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ 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.removeCallback(parameter, currX, currZ);
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ 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.removeCallback(parameter, currX, currZ);
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ return true;
|
|
+ }
|
|
+
|
|
+ public final boolean remove() {
|
|
+ final int chunkX = this.lastChunkX;
|
|
+ final int chunkZ = this.lastChunkZ;
|
|
+ final int distance = this.distance;
|
|
+ if (chunkX == NOT_SET) {
|
|
+ return false;
|
|
+ }
|
|
+
|
|
+ this.lastChunkX = this.lastChunkZ = this.distance = NOT_SET;
|
|
+
|
|
+ this.removeFromOld(this.parameter, chunkX, chunkZ, distance);
|
|
+
|
|
+ return true;
|
|
+ }
|
|
+}
|
|
diff --git a/src/main/java/ca/spottedleaf/moonrise/common/set/OptimizedSmallEnumSet.java b/src/main/java/ca/spottedleaf/moonrise/common/set/OptimizedSmallEnumSet.java
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
|
|
--- /dev/null
|
|
+++ b/src/main/java/ca/spottedleaf/moonrise/common/set/OptimizedSmallEnumSet.java
|
|
@@ -0,0 +0,0 @@
|
|
+package ca.spottedleaf.moonrise.common.set;
|
|
+
|
|
+import java.util.Collection;
|
|
+
|
|
+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;
|
|
+ }
|
|
+
|
|
+ public boolean hasElement(final E element) {
|
|
+ return (this.backingSet & (1L << element.ordinal())) != 0;
|
|
+ }
|
|
+}
|
|
diff --git a/src/main/java/ca/spottedleaf/moonrise/common/util/ChunkSystem.java b/src/main/java/ca/spottedleaf/moonrise/common/util/ChunkSystem.java
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
|
|
--- /dev/null
|
|
+++ b/src/main/java/ca/spottedleaf/moonrise/common/util/ChunkSystem.java
|
|
@@ -0,0 +0,0 @@
|
|
+package ca.spottedleaf.moonrise.common.util;
|
|
+
|
|
+import ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor;
|
|
+import com.mojang.logging.LogUtils;
|
|
+import net.minecraft.server.level.ChunkHolder;
|
|
+import net.minecraft.server.level.FullChunkStatus;
|
|
+import net.minecraft.server.level.ServerLevel;
|
|
+import net.minecraft.server.level.ServerPlayer;
|
|
+import net.minecraft.world.entity.Entity;
|
|
+import net.minecraft.world.level.chunk.ChunkAccess;
|
|
+import net.minecraft.world.level.chunk.LevelChunk;
|
|
+import net.minecraft.world.level.chunk.status.ChunkStatus;
|
|
+import org.slf4j.Logger;
|
|
+import java.util.List;
|
|
+import java.util.function.Consumer;
|
|
+
|
|
+public final class ChunkSystem {
|
|
+
|
|
+ private static final Logger LOGGER = LogUtils.getLogger();
|
|
+ private static final net.minecraft.world.level.chunk.status.ChunkStep FULL_CHUNK_STEP = net.minecraft.world.level.chunk.status.ChunkPyramid.GENERATION_PYRAMID.getStepTo(ChunkStatus.FULL);
|
|
+
|
|
+ private static int getDistance(final ChunkStatus status) {
|
|
+ return FULL_CHUNK_STEP.getAccumulatedRadiusOf(status);
|
|
+ }
|
|
+
|
|
+ public static void scheduleChunkTask(final ServerLevel level, final int chunkX, final int chunkZ, final Runnable run) {
|
|
+ scheduleChunkTask(level, chunkX, chunkZ, run, PrioritisedExecutor.Priority.NORMAL);
|
|
+ }
|
|
+
|
|
+ public static void scheduleChunkTask(final ServerLevel level, final int chunkX, final int chunkZ, final Runnable run, final PrioritisedExecutor.Priority priority) {
|
|
+ level.chunkSource.mainThreadProcessor.execute(run);
|
|
+ }
|
|
+
|
|
+ public static void scheduleChunkLoad(final ServerLevel level, final int chunkX, final int chunkZ, final boolean gen,
|
|
+ final ChunkStatus toStatus, final boolean addTicket, final PrioritisedExecutor.Priority priority,
|
|
+ final Consumer<ChunkAccess> onComplete) {
|
|
+ if (gen) {
|
|
+ scheduleChunkLoad(level, chunkX, chunkZ, toStatus, addTicket, priority, onComplete);
|
|
+ return;
|
|
+ }
|
|
+ scheduleChunkLoad(level, chunkX, chunkZ, ChunkStatus.EMPTY, addTicket, priority, (final ChunkAccess chunk) -> {
|
|
+ if (chunk == null) {
|
|
+ if (onComplete != null) {
|
|
+ onComplete.accept(null);
|
|
+ }
|
|
+ } else {
|
|
+ if (chunk.getPersistedStatus().isOrAfter(toStatus)) {
|
|
+ scheduleChunkLoad(level, chunkX, chunkZ, toStatus, addTicket, priority, onComplete);
|
|
+ } else {
|
|
+ if (onComplete != null) {
|
|
+ onComplete.accept(null);
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ });
|
|
+ }
|
|
+
|
|
+ static final net.minecraft.server.level.TicketType<Long> CHUNK_LOAD = net.minecraft.server.level.TicketType.create("chunk_load", Long::compareTo);
|
|
+
|
|
+ private static long chunkLoadCounter = 0L;
|
|
+ public static void scheduleChunkLoad(final ServerLevel level, final int chunkX, final int chunkZ, final ChunkStatus toStatus,
|
|
+ final boolean addTicket, final PrioritisedExecutor.Priority priority, final Consumer<ChunkAccess> onComplete) {
|
|
+ if (!org.bukkit.Bukkit.isPrimaryThread()) {
|
|
+ scheduleChunkTask(level, chunkX, chunkZ, () -> {
|
|
+ scheduleChunkLoad(level, chunkX, chunkZ, toStatus, addTicket, priority, onComplete);
|
|
+ }, priority);
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ final int minLevel = 33 + getDistance(toStatus);
|
|
+ final Long chunkReference = addTicket ? Long.valueOf(++chunkLoadCounter) : null;
|
|
+ final net.minecraft.world.level.ChunkPos chunkPos = new net.minecraft.world.level.ChunkPos(chunkX, chunkZ);
|
|
+
|
|
+ if (addTicket) {
|
|
+ level.chunkSource.addTicketAtLevel(CHUNK_LOAD, chunkPos, minLevel, chunkReference);
|
|
+ }
|
|
+ level.chunkSource.runDistanceManagerUpdates();
|
|
+
|
|
+ final Consumer<ChunkAccess> loadCallback = (final ChunkAccess chunk) -> {
|
|
+ try {
|
|
+ if (onComplete != null) {
|
|
+ onComplete.accept(chunk);
|
|
+ }
|
|
+ } catch (final Throwable thr) {
|
|
+ LOGGER.error("Exception handling chunk load callback", thr);
|
|
+ com.destroystokyo.paper.util.SneakyThrow.sneaky(thr);
|
|
+ } finally {
|
|
+ if (addTicket) {
|
|
+ level.chunkSource.addTicketAtLevel(net.minecraft.server.level.TicketType.UNKNOWN, chunkPos, minLevel, chunkPos);
|
|
+ level.chunkSource.removeTicketAtLevel(CHUNK_LOAD, chunkPos, minLevel, chunkReference);
|
|
+ }
|
|
+ }
|
|
+ };
|
|
+
|
|
+ final ChunkHolder holder = level.chunkSource.chunkMap.updatingChunkMap.get(CoordinateUtils.getChunkKey(chunkX, chunkZ));
|
|
+
|
|
+ if (holder == null || holder.getTicketLevel() > minLevel) {
|
|
+ loadCallback.accept(null);
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ final java.util.concurrent.CompletableFuture<net.minecraft.server.level.ChunkResult<net.minecraft.world.level.chunk.ChunkAccess>> loadFuture = holder.scheduleChunkGenerationTask(toStatus, level.chunkSource.chunkMap);
|
|
+
|
|
+ if (loadFuture.isDone()) {
|
|
+ loadCallback.accept(loadFuture.join().orElse(null));
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ loadFuture.whenCompleteAsync((final net.minecraft.server.level.ChunkResult<net.minecraft.world.level.chunk.ChunkAccess> result, final Throwable thr) -> {
|
|
+ if (thr != null) {
|
|
+ loadCallback.accept(null);
|
|
+ return;
|
|
+ }
|
|
+ loadCallback.accept(result.orElse(null));
|
|
+ }, (final Runnable r) -> {
|
|
+ scheduleChunkTask(level, chunkX, chunkZ, r, PrioritisedExecutor.Priority.HIGHEST);
|
|
+ });
|
|
+ }
|
|
+
|
|
+ public static void scheduleTickingState(final ServerLevel level, final int chunkX, final int chunkZ,
|
|
+ final FullChunkStatus toStatus, final boolean addTicket,
|
|
+ final PrioritisedExecutor.Priority priority, final Consumer<LevelChunk> onComplete) {
|
|
+ // This method goes unused until the chunk system rewrite
|
|
+ if (toStatus == FullChunkStatus.INACCESSIBLE) {
|
|
+ throw new IllegalArgumentException("Cannot wait for INACCESSIBLE status");
|
|
+ }
|
|
+
|
|
+ if (!org.bukkit.Bukkit.isPrimaryThread()) {
|
|
+ scheduleChunkTask(level, chunkX, chunkZ, () -> {
|
|
+ scheduleTickingState(level, chunkX, chunkZ, toStatus, addTicket, priority, onComplete);
|
|
+ }, priority);
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ final int minLevel = 33 - (toStatus.ordinal() - 1);
|
|
+ final int radius = toStatus.ordinal() - 1;
|
|
+ final Long chunkReference = addTicket ? Long.valueOf(++chunkLoadCounter) : null;
|
|
+ final net.minecraft.world.level.ChunkPos chunkPos = new net.minecraft.world.level.ChunkPos(chunkX, chunkZ);
|
|
+
|
|
+ if (addTicket) {
|
|
+ level.chunkSource.addTicketAtLevel(CHUNK_LOAD, chunkPos, minLevel, chunkReference);
|
|
+ }
|
|
+ level.chunkSource.runDistanceManagerUpdates();
|
|
+
|
|
+ final Consumer<LevelChunk> loadCallback = (final LevelChunk chunk) -> {
|
|
+ try {
|
|
+ if (onComplete != null) {
|
|
+ onComplete.accept(chunk);
|
|
+ }
|
|
+ } catch (final Throwable thr) {
|
|
+ LOGGER.error("Exception handling chunk load callback", thr);
|
|
+ com.destroystokyo.paper.util.SneakyThrow.sneaky(thr);
|
|
+ } finally {
|
|
+ if (addTicket) {
|
|
+ level.chunkSource.addTicketAtLevel(net.minecraft.server.level.TicketType.UNKNOWN, chunkPos, minLevel, chunkPos);
|
|
+ level.chunkSource.removeTicketAtLevel(CHUNK_LOAD, chunkPos, minLevel, chunkReference);
|
|
+ }
|
|
+ }
|
|
+ };
|
|
+
|
|
+ final ChunkHolder holder = level.chunkSource.chunkMap.updatingChunkMap.get(CoordinateUtils.getChunkKey(chunkX, chunkZ));
|
|
+
|
|
+ if (holder == null || holder.getTicketLevel() > minLevel) {
|
|
+ loadCallback.accept(null);
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ final java.util.concurrent.CompletableFuture<net.minecraft.server.level.ChunkResult<net.minecraft.world.level.chunk.LevelChunk>> tickingState;
|
|
+ switch (toStatus) {
|
|
+ case FULL: {
|
|
+ tickingState = holder.getFullChunkFuture();
|
|
+ break;
|
|
+ }
|
|
+ case BLOCK_TICKING: {
|
|
+ tickingState = holder.getTickingChunkFuture();
|
|
+ break;
|
|
+ }
|
|
+ case ENTITY_TICKING: {
|
|
+ tickingState = holder.getEntityTickingChunkFuture();
|
|
+ break;
|
|
+ }
|
|
+ default: {
|
|
+ throw new IllegalStateException("Cannot reach here");
|
|
+ }
|
|
+ }
|
|
+
|
|
+ if (tickingState.isDone()) {
|
|
+ loadCallback.accept(tickingState.join().orElse(null));
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ tickingState.whenCompleteAsync((final net.minecraft.server.level.ChunkResult<net.minecraft.world.level.chunk.LevelChunk> result, final Throwable thr) -> {
|
|
+ if (thr != null) {
|
|
+ loadCallback.accept(null);
|
|
+ return;
|
|
+ }
|
|
+ loadCallback.accept(result.orElse(null));
|
|
+ }, (final Runnable r) -> {
|
|
+ scheduleChunkTask(level, chunkX, chunkZ, r, PrioritisedExecutor.Priority.HIGHEST);
|
|
+ });
|
|
+ }
|
|
+
|
|
+ public static List<ChunkHolder> getVisibleChunkHolders(final ServerLevel level) {
|
|
+ return new java.util.ArrayList<>(level.chunkSource.chunkMap.visibleChunkMap.values());
|
|
+ }
|
|
+
|
|
+ public static List<ChunkHolder> getUpdatingChunkHolders(final ServerLevel level) {
|
|
+ return new java.util.ArrayList<>(level.chunkSource.chunkMap.updatingChunkMap.values());
|
|
+ }
|
|
+
|
|
+ public static int getVisibleChunkHolderCount(final ServerLevel level) {
|
|
+ return level.chunkSource.chunkMap.visibleChunkMap.size();
|
|
+ }
|
|
+
|
|
+ public static int getUpdatingChunkHolderCount(final ServerLevel level) {
|
|
+ return level.chunkSource.chunkMap.updatingChunkMap.size();
|
|
+ }
|
|
+
|
|
+ public static boolean hasAnyChunkHolders(final ServerLevel level) {
|
|
+ return getUpdatingChunkHolderCount(level) != 0;
|
|
+ }
|
|
+
|
|
+ public static boolean screenEntity(final ServerLevel level, final Entity entity) {
|
|
+ return true;
|
|
+ }
|
|
+
|
|
+ public static void onChunkHolderCreate(final ServerLevel level, final ChunkHolder holder) {
|
|
+
|
|
+ }
|
|
+
|
|
+ public static void onChunkHolderDelete(final ServerLevel level, final ChunkHolder holder) {
|
|
+
|
|
+ }
|
|
+
|
|
+ public static void onChunkBorder(final LevelChunk chunk, final ChunkHolder holder) {
|
|
+
|
|
+ }
|
|
+
|
|
+ public static void onChunkNotBorder(final LevelChunk chunk, final ChunkHolder holder) {
|
|
+
|
|
+ }
|
|
+
|
|
+ public static void onChunkTicking(final LevelChunk chunk, final ChunkHolder holder) {
|
|
+
|
|
+ }
|
|
+
|
|
+ public static void onChunkNotTicking(final LevelChunk chunk, final ChunkHolder holder) {
|
|
+
|
|
+ }
|
|
+
|
|
+ public static void onChunkEntityTicking(final LevelChunk chunk, final ChunkHolder holder) {
|
|
+
|
|
+ }
|
|
+
|
|
+ public static void onChunkNotEntityTicking(final LevelChunk chunk, final ChunkHolder holder) {
|
|
+
|
|
+ }
|
|
+
|
|
+ public static ChunkHolder getUnloadingChunkHolder(final ServerLevel level, final int chunkX, final int chunkZ) {
|
|
+ return level.chunkSource.chunkMap.getUnloadingChunkHolder(chunkX, chunkZ);
|
|
+ }
|
|
+
|
|
+ public static int getSendViewDistance(final ServerPlayer player) {
|
|
+ return getLoadViewDistance(player);
|
|
+ }
|
|
+
|
|
+ public static int getLoadViewDistance(final ServerPlayer player) {
|
|
+ final ServerLevel level = player.serverLevel();
|
|
+ if (level == null) {
|
|
+ return org.bukkit.Bukkit.getViewDistance();
|
|
+ }
|
|
+ return level.chunkSource.chunkMap.getPlayerViewDistance(player);
|
|
+ }
|
|
+
|
|
+ public static int getTickViewDistance(final ServerPlayer player) {
|
|
+ final ServerLevel level = player.serverLevel();
|
|
+ if (level == null) {
|
|
+ return org.bukkit.Bukkit.getSimulationDistance();
|
|
+ }
|
|
+ return level.chunkSource.chunkMap.distanceManager.simulationDistance;
|
|
+ }
|
|
+
|
|
+ private ChunkSystem() {}
|
|
+}
|
|
diff --git a/src/main/java/ca/spottedleaf/moonrise/common/util/CoordinateUtils.java b/src/main/java/ca/spottedleaf/moonrise/common/util/CoordinateUtils.java
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
|
|
--- /dev/null
|
|
+++ b/src/main/java/ca/spottedleaf/moonrise/common/util/CoordinateUtils.java
|
|
@@ -0,0 +0,0 @@
|
|
+package ca.spottedleaf.moonrise.common.util;
|
|
+
|
|
+import net.minecraft.core.BlockPos;
|
|
+import net.minecraft.core.SectionPos;
|
|
+import net.minecraft.util.Mth;
|
|
+import net.minecraft.world.entity.Entity;
|
|
+import net.minecraft.world.level.ChunkPos;
|
|
+import net.minecraft.world.phys.Vec3;
|
|
+
|
|
+public final class CoordinateUtils {
|
|
+
|
|
+ // the chunk keys are compatible with vanilla
|
|
+
|
|
+ public static long getChunkKey(final BlockPos pos) {
|
|
+ return ((long)(pos.getZ() >> 4) << 32) | ((pos.getX() >> 4) & 0xFFFFFFFFL);
|
|
+ }
|
|
+
|
|
+ public static long getChunkKey(final Entity entity) {
|
|
+ return ((Mth.lfloor(entity.getZ()) >> 4) << 32) | ((Mth.lfloor(entity.getX()) >> 4) & 0xFFFFFFFFL);
|
|
+ }
|
|
+
|
|
+ public static long getChunkKey(final ChunkPos pos) {
|
|
+ return ((long)pos.z << 32) | (pos.x & 0xFFFFFFFFL);
|
|
+ }
|
|
+
|
|
+ public static long getChunkKey(final SectionPos pos) {
|
|
+ return ((long)pos.getZ() << 32) | (pos.getX() & 0xFFFFFFFFL);
|
|
+ }
|
|
+
|
|
+ public static long getChunkKey(final int x, final int z) {
|
|
+ return ((long)z << 32) | (x & 0xFFFFFFFFL);
|
|
+ }
|
|
+
|
|
+ public static int getChunkX(final long chunkKey) {
|
|
+ return (int)chunkKey;
|
|
+ }
|
|
+
|
|
+ public static int getChunkZ(final long chunkKey) {
|
|
+ return (int)(chunkKey >>> 32);
|
|
+ }
|
|
+
|
|
+ public static int getChunkCoordinate(final double blockCoordinate) {
|
|
+ return Mth.floor(blockCoordinate) >> 4;
|
|
+ }
|
|
+
|
|
+ // the section keys are compatible with vanilla's
|
|
+
|
|
+ static final int SECTION_X_BITS = 22;
|
|
+ static final long SECTION_X_MASK = (1L << SECTION_X_BITS) - 1;
|
|
+ static final int SECTION_Y_BITS = 20;
|
|
+ static final long SECTION_Y_MASK = (1L << SECTION_Y_BITS) - 1;
|
|
+ static final int SECTION_Z_BITS = 22;
|
|
+ static final long SECTION_Z_MASK = (1L << SECTION_Z_BITS) - 1;
|
|
+ // format is y,z,x (in order of LSB to MSB)
|
|
+ static final int SECTION_Y_SHIFT = 0;
|
|
+ static final int SECTION_Z_SHIFT = SECTION_Y_SHIFT + SECTION_Y_BITS;
|
|
+ static final int SECTION_X_SHIFT = SECTION_Z_SHIFT + SECTION_X_BITS;
|
|
+ static final int SECTION_TO_BLOCK_SHIFT = 4;
|
|
+
|
|
+ public static long getChunkSectionKey(final int x, final int y, final int z) {
|
|
+ return ((x & SECTION_X_MASK) << SECTION_X_SHIFT)
|
|
+ | ((y & SECTION_Y_MASK) << SECTION_Y_SHIFT)
|
|
+ | ((z & SECTION_Z_MASK) << SECTION_Z_SHIFT);
|
|
+ }
|
|
+
|
|
+ public static long getChunkSectionKey(final SectionPos pos) {
|
|
+ return ((pos.getX() & SECTION_X_MASK) << SECTION_X_SHIFT)
|
|
+ | ((pos.getY() & SECTION_Y_MASK) << SECTION_Y_SHIFT)
|
|
+ | ((pos.getZ() & SECTION_Z_MASK) << SECTION_Z_SHIFT);
|
|
+ }
|
|
+
|
|
+ public static long getChunkSectionKey(final ChunkPos pos, final int y) {
|
|
+ return ((pos.x & SECTION_X_MASK) << SECTION_X_SHIFT)
|
|
+ | ((y & SECTION_Y_MASK) << SECTION_Y_SHIFT)
|
|
+ | ((pos.z & SECTION_Z_MASK) << SECTION_Z_SHIFT);
|
|
+ }
|
|
+
|
|
+ public static long getChunkSectionKey(final BlockPos pos) {
|
|
+ return (((long)pos.getX() << (SECTION_X_SHIFT - SECTION_TO_BLOCK_SHIFT)) & (SECTION_X_MASK << SECTION_X_SHIFT)) |
|
|
+ ((pos.getY() >> SECTION_TO_BLOCK_SHIFT) & (SECTION_Y_MASK << SECTION_Y_SHIFT)) |
|
|
+ (((long)pos.getZ() << (SECTION_Z_SHIFT - SECTION_TO_BLOCK_SHIFT)) & (SECTION_Z_MASK << SECTION_Z_SHIFT));
|
|
+ }
|
|
+
|
|
+ public static long getChunkSectionKey(final Entity entity) {
|
|
+ return ((Mth.lfloor(entity.getX()) << (SECTION_X_SHIFT - SECTION_TO_BLOCK_SHIFT)) & (SECTION_X_MASK << SECTION_X_SHIFT)) |
|
|
+ ((Mth.lfloor(entity.getY()) >> SECTION_TO_BLOCK_SHIFT) & (SECTION_Y_MASK << SECTION_Y_SHIFT)) |
|
|
+ ((Mth.lfloor(entity.getZ()) << (SECTION_Z_SHIFT - SECTION_TO_BLOCK_SHIFT)) & (SECTION_Z_MASK << SECTION_Z_SHIFT));
|
|
+ }
|
|
+
|
|
+ public static int getChunkSectionX(final long key) {
|
|
+ return (int)(key << (Long.SIZE - (SECTION_X_SHIFT + SECTION_X_BITS)) >> (Long.SIZE - SECTION_X_BITS));
|
|
+ }
|
|
+
|
|
+ public static int getChunkSectionY(final long key) {
|
|
+ return (int)(key << (Long.SIZE - (SECTION_Y_SHIFT + SECTION_Y_BITS)) >> (Long.SIZE - SECTION_Y_BITS));
|
|
+ }
|
|
+
|
|
+ public static int getChunkSectionZ(final long key) {
|
|
+ return (int)(key << (Long.SIZE - (SECTION_Z_SHIFT + SECTION_Z_BITS)) >> (Long.SIZE - SECTION_Z_BITS));
|
|
+ }
|
|
+
|
|
+ public static int getBlockX(final Vec3 pos) {
|
|
+ return Mth.floor(pos.x);
|
|
+ }
|
|
+
|
|
+ public static int getBlockY(final Vec3 pos) {
|
|
+ return Mth.floor(pos.y);
|
|
+ }
|
|
+
|
|
+ public static int getBlockZ(final Vec3 pos) {
|
|
+ return Mth.floor(pos.z);
|
|
+ }
|
|
+
|
|
+ public static int getChunkX(final Vec3 pos) {
|
|
+ return Mth.floor(pos.x) >> 4;
|
|
+ }
|
|
+
|
|
+ public static int getChunkY(final Vec3 pos) {
|
|
+ return Mth.floor(pos.y) >> 4;
|
|
+ }
|
|
+
|
|
+ public static int getChunkZ(final Vec3 pos) {
|
|
+ return Mth.floor(pos.z) >> 4;
|
|
+ }
|
|
+
|
|
+ private CoordinateUtils() {
|
|
+ throw new RuntimeException();
|
|
+ }
|
|
+}
|
|
diff --git a/src/main/java/ca/spottedleaf/moonrise/common/util/FlatBitsetUtil.java b/src/main/java/ca/spottedleaf/moonrise/common/util/FlatBitsetUtil.java
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
|
|
--- /dev/null
|
|
+++ b/src/main/java/ca/spottedleaf/moonrise/common/util/FlatBitsetUtil.java
|
|
@@ -0,0 +0,0 @@
|
|
+package ca.spottedleaf.moonrise.common.util;
|
|
+
|
|
+import java.util.Objects;
|
|
+
|
|
+public final class FlatBitsetUtil {
|
|
+
|
|
+ private static final int LOG2_LONG = 6;
|
|
+ private static final long ALL_SET = -1L;
|
|
+ private static final int BITS_PER_LONG = Long.SIZE;
|
|
+
|
|
+ // from inclusive
|
|
+ // to exclusive
|
|
+ public static int firstSet(final long[] bitset, final int from, final int to) {
|
|
+ if ((from | to | (to - from)) < 0) {
|
|
+ throw new IndexOutOfBoundsException();
|
|
+ }
|
|
+
|
|
+ int bitsetIdx = from >>> LOG2_LONG;
|
|
+ int bitIdx = from & ~(BITS_PER_LONG - 1);
|
|
+
|
|
+ long tmp = bitset[bitsetIdx] & (ALL_SET << from);
|
|
+ for (;;) {
|
|
+ if (tmp != 0L) {
|
|
+ final int ret = bitIdx | Long.numberOfTrailingZeros(tmp);
|
|
+ return ret >= to ? -1 : ret;
|
|
+ }
|
|
+
|
|
+ bitIdx += BITS_PER_LONG;
|
|
+
|
|
+ if (bitIdx >= to) {
|
|
+ return -1;
|
|
+ }
|
|
+
|
|
+ tmp = bitset[++bitsetIdx];
|
|
+ }
|
|
+ }
|
|
+
|
|
+ // from inclusive
|
|
+ // to exclusive
|
|
+ public static int firstClear(final long[] bitset, final int from, final int to) {
|
|
+ if ((from | to | (to - from)) < 0) {
|
|
+ throw new IndexOutOfBoundsException();
|
|
+ }
|
|
+ // like firstSet, but invert the bitset
|
|
+
|
|
+ int bitsetIdx = from >>> LOG2_LONG;
|
|
+ int bitIdx = from & ~(BITS_PER_LONG - 1);
|
|
+
|
|
+ long tmp = (~bitset[bitsetIdx]) & (ALL_SET << from);
|
|
+ for (;;) {
|
|
+ if (tmp != 0L) {
|
|
+ final int ret = bitIdx | Long.numberOfTrailingZeros(tmp);
|
|
+ return ret >= to ? -1 : ret;
|
|
+ }
|
|
+
|
|
+ bitIdx += BITS_PER_LONG;
|
|
+
|
|
+ if (bitIdx >= to) {
|
|
+ return -1;
|
|
+ }
|
|
+
|
|
+ tmp = ~bitset[++bitsetIdx];
|
|
+ }
|
|
+ }
|
|
+
|
|
+ // from inclusive
|
|
+ // to exclusive
|
|
+ public static void clearRange(final long[] bitset, final int from, int to) {
|
|
+ if ((from | to | (to - from)) < 0) {
|
|
+ throw new IndexOutOfBoundsException();
|
|
+ }
|
|
+
|
|
+ if (from == to) {
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ --to;
|
|
+
|
|
+ final int fromBitsetIdx = from >>> LOG2_LONG;
|
|
+ final int toBitsetIdx = to >>> LOG2_LONG;
|
|
+
|
|
+ final long keepFirst = ~(ALL_SET << from);
|
|
+ final long keepLast = ~(ALL_SET >>> ((BITS_PER_LONG - 1) ^ to));
|
|
+
|
|
+ Objects.checkFromToIndex(fromBitsetIdx, toBitsetIdx, bitset.length);
|
|
+
|
|
+ if (fromBitsetIdx == toBitsetIdx) {
|
|
+ // special case: need to keep both first and last
|
|
+ bitset[fromBitsetIdx] &= (keepFirst | keepLast);
|
|
+ } else {
|
|
+ bitset[fromBitsetIdx] &= keepFirst;
|
|
+
|
|
+ for (int i = fromBitsetIdx + 1; i < toBitsetIdx; ++i) {
|
|
+ bitset[i] = 0L;
|
|
+ }
|
|
+
|
|
+ bitset[toBitsetIdx] &= keepLast;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ // from inclusive
|
|
+ // to exclusive
|
|
+ public static boolean isRangeSet(final long[] bitset, final int from, final int to) {
|
|
+ return firstClear(bitset, from, to) == -1;
|
|
+ }
|
|
+
|
|
+
|
|
+ private FlatBitsetUtil() {}
|
|
+}
|
|
diff --git a/src/main/java/ca/spottedleaf/moonrise/common/util/JsonUtil.java b/src/main/java/ca/spottedleaf/moonrise/common/util/JsonUtil.java
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
|
|
--- /dev/null
|
|
+++ b/src/main/java/ca/spottedleaf/moonrise/common/util/JsonUtil.java
|
|
@@ -0,0 +0,0 @@
|
|
+package ca.spottedleaf.moonrise.common.util;
|
|
+
|
|
+import com.google.gson.JsonElement;
|
|
+import com.google.gson.internal.Streams;
|
|
+import com.google.gson.stream.JsonWriter;
|
|
+import java.io.File;
|
|
+import java.io.FileOutputStream;
|
|
+import java.io.IOException;
|
|
+import java.io.PrintStream;
|
|
+import java.io.StringWriter;
|
|
+import java.nio.charset.StandardCharsets;
|
|
+
|
|
+public final class JsonUtil {
|
|
+
|
|
+ public static void writeJson(final JsonElement element, final File file) throws IOException {
|
|
+ final StringWriter stringWriter = new StringWriter();
|
|
+ final JsonWriter jsonWriter = new JsonWriter(stringWriter);
|
|
+ jsonWriter.setIndent(" ");
|
|
+ jsonWriter.setLenient(false);
|
|
+ Streams.write(element, jsonWriter);
|
|
+
|
|
+ final String jsonString = stringWriter.toString();
|
|
+
|
|
+ final File parent = file.getParentFile();
|
|
+ if (parent != null) {
|
|
+ parent.mkdirs();
|
|
+ }
|
|
+ file.createNewFile();
|
|
+ try (final PrintStream out = new PrintStream(new FileOutputStream(file), false, StandardCharsets.UTF_8)) {
|
|
+ out.print(jsonString);
|
|
+ }
|
|
+ }
|
|
+
|
|
+}
|
|
diff --git a/src/main/java/ca/spottedleaf/moonrise/common/util/MixinWorkarounds.java b/src/main/java/ca/spottedleaf/moonrise/common/util/MixinWorkarounds.java
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
|
|
--- /dev/null
|
|
+++ b/src/main/java/ca/spottedleaf/moonrise/common/util/MixinWorkarounds.java
|
|
@@ -0,0 +0,0 @@
|
|
+package ca.spottedleaf.moonrise.common.util;
|
|
+
|
|
+public final class MixinWorkarounds {
|
|
+
|
|
+ // mixins tries to find the owner of the clone() method, which doesn't exist and NPEs
|
|
+ public static long[] clone(final long[] values) {
|
|
+ return values.clone();
|
|
+ }
|
|
+
|
|
+}
|
|
diff --git a/src/main/java/ca/spottedleaf/moonrise/common/util/MoonriseCommon.java b/src/main/java/ca/spottedleaf/moonrise/common/util/MoonriseCommon.java
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
|
|
--- /dev/null
|
|
+++ b/src/main/java/ca/spottedleaf/moonrise/common/util/MoonriseCommon.java
|
|
@@ -0,0 +0,0 @@
|
|
+package ca.spottedleaf.moonrise.common.util;
|
|
+
|
|
+import ca.spottedleaf.concurrentutil.executor.standard.PrioritisedThreadPool;
|
|
+import org.slf4j.Logger;
|
|
+import org.slf4j.LoggerFactory;
|
|
+import java.io.File;
|
|
+
|
|
+public final class MoonriseCommon {
|
|
+
|
|
+ private static final Logger LOGGER = LoggerFactory.getLogger(MoonriseCommon.class);
|
|
+
|
|
+ // Paper start
|
|
+ public static PrioritisedThreadPool WORKER_POOL;
|
|
+ public static int WORKER_THREADS;
|
|
+ public static void init(io.papermc.paper.configuration.GlobalConfiguration.ChunkSystem chunkSystem) {
|
|
+ // Paper end
|
|
+ int defaultWorkerThreads = Runtime.getRuntime().availableProcessors() / 2;
|
|
+ if (defaultWorkerThreads <= 4) {
|
|
+ defaultWorkerThreads = defaultWorkerThreads <= 3 ? 1 : 2;
|
|
+ } else {
|
|
+ defaultWorkerThreads = defaultWorkerThreads / 2;
|
|
+ }
|
|
+ defaultWorkerThreads = Integer.getInteger("Paper.WorkerThreadCount", Integer.valueOf(defaultWorkerThreads)); // Paper
|
|
+
|
|
+ int workerThreads = chunkSystem.workerThreads; // Paper
|
|
+
|
|
+ if (workerThreads <= 0) {
|
|
+ workerThreads = defaultWorkerThreads;
|
|
+ }
|
|
+
|
|
+ WORKER_POOL = new PrioritisedThreadPool(
|
|
+ "Paper Worker Pool", workerThreads, // Paper
|
|
+ (final Thread thread, final Integer id) -> {
|
|
+ thread.setName("Paper Common Worker #" + id.intValue()); // Paper
|
|
+ thread.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
|
|
+ @Override
|
|
+ public void uncaughtException(final Thread thread, final Throwable throwable) {
|
|
+ LOGGER.error("Uncaught exception in thread " + thread.getName(), throwable);
|
|
+ }
|
|
+ });
|
|
+ }, (long)(20.0e6)); // 20ms
|
|
+ WORKER_THREADS = workerThreads;
|
|
+ }
|
|
+
|
|
+ private MoonriseCommon() {}
|
|
+}
|
|
diff --git a/src/main/java/ca/spottedleaf/moonrise/common/util/MoonriseConstants.java b/src/main/java/ca/spottedleaf/moonrise/common/util/MoonriseConstants.java
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
|
|
--- /dev/null
|
|
+++ b/src/main/java/ca/spottedleaf/moonrise/common/util/MoonriseConstants.java
|
|
@@ -0,0 +0,0 @@
|
|
+package ca.spottedleaf.moonrise.common.util;
|
|
+
|
|
+public final class MoonriseConstants {
|
|
+
|
|
+ public static final int MAX_VIEW_DISTANCE = 32;
|
|
+
|
|
+ private MoonriseConstants() {}
|
|
+
|
|
+}
|
|
diff --git a/src/main/java/ca/spottedleaf/moonrise/common/util/TickThread.java b/src/main/java/ca/spottedleaf/moonrise/common/util/TickThread.java
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
|
|
--- /dev/null
|
|
+++ b/src/main/java/ca/spottedleaf/moonrise/common/util/TickThread.java
|
|
@@ -0,0 +0,0 @@
|
|
+package ca.spottedleaf.moonrise.common.util;
|
|
+
|
|
+import net.minecraft.core.BlockPos;
|
|
+import net.minecraft.world.entity.Entity;
|
|
+import net.minecraft.world.level.ChunkPos;
|
|
+import net.minecraft.world.level.Level;
|
|
+import net.minecraft.world.phys.AABB;
|
|
+import net.minecraft.world.phys.Vec3;
|
|
+import org.slf4j.Logger;
|
|
+import org.slf4j.LoggerFactory;
|
|
+
|
|
+import java.util.concurrent.atomic.AtomicInteger;
|
|
+
|
|
+public class TickThread extends Thread {
|
|
+
|
|
+ private static final Logger LOGGER = LoggerFactory.getLogger(TickThread.class);
|
|
+
|
|
+ /**
|
|
+ * @deprecated
|
|
+ */
|
|
+ @Deprecated
|
|
+ public static void ensureTickThread(final String reason) {
|
|
+ if (!isTickThread()) {
|
|
+ LOGGER.error("Thread " + Thread.currentThread().getName() + " failed main thread check: " + reason, new Throwable());
|
|
+ throw new IllegalStateException(reason);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public static void ensureTickThread(final Level world, final BlockPos pos, final String reason) {
|
|
+ if (!isTickThreadFor(world, pos)) {
|
|
+ LOGGER.error("Thread " + Thread.currentThread().getName() + " failed main thread check: " + reason, new Throwable());
|
|
+ throw new IllegalStateException(reason);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public static void ensureTickThread(final Level world, final ChunkPos pos, final String reason) {
|
|
+ if (!isTickThreadFor(world, pos)) {
|
|
+ LOGGER.error("Thread " + Thread.currentThread().getName() + " failed main thread check: " + reason, new Throwable());
|
|
+ throw new IllegalStateException(reason);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public static void ensureTickThread(final Level world, final int chunkX, final int chunkZ, final String reason) {
|
|
+ if (!isTickThreadFor(world, chunkX, chunkZ)) {
|
|
+ LOGGER.error("Thread " + Thread.currentThread().getName() + " failed main thread check: " + reason, new Throwable());
|
|
+ throw new IllegalStateException(reason);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public static void ensureTickThread(final Entity entity, final String reason) {
|
|
+ if (!isTickThreadFor(entity)) {
|
|
+ LOGGER.error("Thread " + Thread.currentThread().getName() + " failed main thread check: " + reason, new Throwable());
|
|
+ throw new IllegalStateException(reason);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public static void ensureTickThread(final Level world, final AABB aabb, final String reason) {
|
|
+ if (!isTickThreadFor(world, aabb)) {
|
|
+ LOGGER.error("Thread " + Thread.currentThread().getName() + " failed main thread check: " + reason, new Throwable());
|
|
+ throw new IllegalStateException(reason);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public static void ensureTickThread(final Level world, final double blockX, final double blockZ, final String reason) {
|
|
+ if (!isTickThreadFor(world, blockX, blockZ)) {
|
|
+ LOGGER.error("Thread " + Thread.currentThread().getName() + " failed main thread check: " + reason, new Throwable());
|
|
+ throw new IllegalStateException(reason);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public final int id; /* We don't override getId as the spec requires that it be unique (with respect to all other threads) */
|
|
+
|
|
+ private static final AtomicInteger ID_GENERATOR = new AtomicInteger();
|
|
+
|
|
+ public TickThread(final String name) {
|
|
+ this(null, name);
|
|
+ }
|
|
+
|
|
+ public TickThread(final Runnable run, final String name) {
|
|
+ this(run, name, ID_GENERATOR.incrementAndGet());
|
|
+ }
|
|
+
|
|
+ private TickThread(final Runnable run, final String name, final int id) {
|
|
+ super(run, name);
|
|
+ this.id = id;
|
|
+ }
|
|
+
|
|
+ public static TickThread getCurrentTickThread() {
|
|
+ return (TickThread)Thread.currentThread();
|
|
+ }
|
|
+
|
|
+ public static boolean isTickThread() {
|
|
+ return Thread.currentThread() instanceof TickThread;
|
|
+ }
|
|
+
|
|
+ public static boolean isShutdownThread() {
|
|
+ return false;
|
|
+ }
|
|
+
|
|
+ public static boolean isTickThreadFor(final Level world, final BlockPos pos) {
|
|
+ return isTickThread();
|
|
+ }
|
|
+
|
|
+ public static boolean isTickThreadFor(final Level world, final ChunkPos pos) {
|
|
+ return isTickThread();
|
|
+ }
|
|
+
|
|
+ public static boolean isTickThreadFor(final Level world, final Vec3 pos) {
|
|
+ return isTickThread();
|
|
+ }
|
|
+
|
|
+ public static boolean isTickThreadFor(final Level world, final int chunkX, final int chunkZ) {
|
|
+ return isTickThread();
|
|
+ }
|
|
+
|
|
+ public static boolean isTickThreadFor(final Level world, final AABB aabb) {
|
|
+ return isTickThread();
|
|
+ }
|
|
+
|
|
+ public static boolean isTickThreadFor(final Level world, final double blockX, final double blockZ) {
|
|
+ return isTickThread();
|
|
+ }
|
|
+
|
|
+ public static boolean isTickThreadFor(final Level world, final Vec3 position, final Vec3 deltaMovement, final int buffer) {
|
|
+ return isTickThread();
|
|
+ }
|
|
+
|
|
+ public static boolean isTickThreadFor(final Level world, final int fromChunkX, final int fromChunkZ, final int toChunkX, final int toChunkZ) {
|
|
+ return isTickThread();
|
|
+ }
|
|
+
|
|
+ public static boolean isTickThreadFor(final Level world, final int chunkX, final int chunkZ, final int radius) {
|
|
+ return isTickThread();
|
|
+ }
|
|
+
|
|
+ public static boolean isTickThreadFor(final Entity entity) {
|
|
+ return isTickThread();
|
|
+ }
|
|
+}
|
|
diff --git a/src/main/java/ca/spottedleaf/moonrise/common/util/WorldUtil.java b/src/main/java/ca/spottedleaf/moonrise/common/util/WorldUtil.java
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
|
|
--- /dev/null
|
|
+++ b/src/main/java/ca/spottedleaf/moonrise/common/util/WorldUtil.java
|
|
@@ -0,0 +0,0 @@
|
|
+package ca.spottedleaf.moonrise.common.util;
|
|
+
|
|
+import net.minecraft.world.level.Level;
|
|
+import net.minecraft.world.level.LevelHeightAccessor;
|
|
+
|
|
+public final class WorldUtil {
|
|
+
|
|
+ // min, max are inclusive
|
|
+
|
|
+ public static int getMaxSection(final LevelHeightAccessor world) {
|
|
+ return world.getMaxSection() - 1; // getMaxSection() is exclusive
|
|
+ }
|
|
+
|
|
+ public static int getMinSection(final LevelHeightAccessor world) {
|
|
+ return world.getMinSection();
|
|
+ }
|
|
+
|
|
+ public static int getMaxLightSection(final LevelHeightAccessor world) {
|
|
+ return getMaxSection(world) + 1;
|
|
+ }
|
|
+
|
|
+ public static int getMinLightSection(final LevelHeightAccessor world) {
|
|
+ return getMinSection(world) - 1;
|
|
+ }
|
|
+
|
|
+
|
|
+
|
|
+ public static int getTotalSections(final LevelHeightAccessor world) {
|
|
+ return getMaxSection(world) - getMinSection(world) + 1;
|
|
+ }
|
|
+
|
|
+ public static int getTotalLightSections(final LevelHeightAccessor world) {
|
|
+ return getMaxLightSection(world) - getMinLightSection(world) + 1;
|
|
+ }
|
|
+
|
|
+ public static int getMinBlockY(final LevelHeightAccessor world) {
|
|
+ return getMinSection(world) << 4;
|
|
+ }
|
|
+
|
|
+ public static int getMaxBlockY(final LevelHeightAccessor world) {
|
|
+ return (getMaxSection(world) << 4) | 15;
|
|
+ }
|
|
+
|
|
+ public static String getWorldName(final Level world) {
|
|
+ if (world == null) {
|
|
+ return "null world";
|
|
+ }
|
|
+ return world.getWorld().getName(); // Paper
|
|
+ }
|
|
+
|
|
+ private WorldUtil() {
|
|
+ throw new RuntimeException();
|
|
+ }
|
|
+}
|
|
diff --git a/src/main/java/com/mojang/logging/LogUtils.java b/src/main/java/com/mojang/logging/LogUtils.java
|
|
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
|
|
--- a/src/main/java/com/mojang/logging/LogUtils.java
|
|
+++ b/src/main/java/com/mojang/logging/LogUtils.java
|
|
@@ -0,0 +0,0 @@ public class LogUtils {
|
|
public static Logger getLogger() {
|
|
return LoggerFactory.getLogger(STACK_WALKER.getCallerClass());
|
|
}
|
|
+ // Paper start
|
|
+ public static Logger getClassLogger() {
|
|
+ return LoggerFactory.getLogger(STACK_WALKER.getCallerClass().getSimpleName());
|
|
+ }
|
|
+ // Paper end
|
|
}
|
|
diff --git a/src/main/java/io/papermc/paper/util/IntervalledCounter.java b/src/main/java/io/papermc/paper/util/IntervalledCounter.java
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
|
|
--- /dev/null
|
|
+++ b/src/main/java/io/papermc/paper/util/IntervalledCounter.java
|
|
@@ -0,0 +0,0 @@
|
|
+package io.papermc.paper.util;
|
|
+
|
|
+public final class IntervalledCounter {
|
|
+
|
|
+ private static final int INITIAL_SIZE = 8;
|
|
+
|
|
+ protected long[] times;
|
|
+ protected long[] counts;
|
|
+ protected final long interval;
|
|
+ protected long minTime;
|
|
+ protected long sum;
|
|
+ protected int head; // inclusive
|
|
+ protected int tail; // exclusive
|
|
+
|
|
+ public IntervalledCounter(final long interval) {
|
|
+ this.times = new long[INITIAL_SIZE];
|
|
+ this.counts = new long[INITIAL_SIZE];
|
|
+ this.interval = interval;
|
|
+ }
|
|
+
|
|
+ public void updateCurrentTime() {
|
|
+ this.updateCurrentTime(System.nanoTime());
|
|
+ }
|
|
+
|
|
+ public void updateCurrentTime(final long currentTime) {
|
|
+ long sum = this.sum;
|
|
+ int head = this.head;
|
|
+ final int tail = this.tail;
|
|
+ final long minTime = currentTime - this.interval;
|
|
+
|
|
+ final int arrayLen = this.times.length;
|
|
+
|
|
+ // guard against overflow by using subtraction
|
|
+ while (head != tail && this.times[head] - minTime < 0) {
|
|
+ sum -= this.counts[head];
|
|
+ // there are two ways we can do this:
|
|
+ // 1. free the count when adding
|
|
+ // 2. free it now
|
|
+ // option #2
|
|
+ this.counts[head] = 0;
|
|
+ if (++head >= arrayLen) {
|
|
+ head = 0;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ this.sum = sum;
|
|
+ this.head = head;
|
|
+ this.minTime = minTime;
|
|
+ }
|
|
+
|
|
+ public void addTime(final long currTime) {
|
|
+ this.addTime(currTime, 1L);
|
|
+ }
|
|
+
|
|
+ public void addTime(final long currTime, final long count) {
|
|
+ // guard against overflow by using subtraction
|
|
+ if (currTime - this.minTime < 0) {
|
|
+ return;
|
|
+ }
|
|
+ int nextTail = (this.tail + 1) % this.times.length;
|
|
+ if (nextTail == this.head) {
|
|
+ this.resize();
|
|
+ nextTail = (this.tail + 1) % this.times.length;
|
|
+ }
|
|
+
|
|
+ this.times[this.tail] = currTime;
|
|
+ this.counts[this.tail] += count;
|
|
+ this.sum += count;
|
|
+ this.tail = nextTail;
|
|
+ }
|
|
+
|
|
+ public void updateAndAdd(final long count) {
|
|
+ final long currTime = System.nanoTime();
|
|
+ this.updateCurrentTime(currTime);
|
|
+ this.addTime(currTime, count);
|
|
+ }
|
|
+
|
|
+ public void updateAndAdd(final long count, final long currTime) {
|
|
+ this.updateCurrentTime(currTime);
|
|
+ this.addTime(currTime, count);
|
|
+ }
|
|
+
|
|
+ private void resize() {
|
|
+ final long[] oldElements = this.times;
|
|
+ final long[] oldCounts = this.counts;
|
|
+ final long[] newElements = new long[this.times.length * 2];
|
|
+ final long[] newCounts = new long[this.times.length * 2];
|
|
+ this.times = newElements;
|
|
+ this.counts = newCounts;
|
|
+
|
|
+ final int head = this.head;
|
|
+ final int tail = this.tail;
|
|
+ final int size = tail >= head ? (tail - head) : (tail + (oldElements.length - head));
|
|
+ this.head = 0;
|
|
+ this.tail = size;
|
|
+
|
|
+ if (tail >= head) {
|
|
+ // sequentially ordered from [head, tail)
|
|
+ System.arraycopy(oldElements, head, newElements, 0, size);
|
|
+ System.arraycopy(oldCounts, head, newCounts, 0, size);
|
|
+ } else {
|
|
+ // ordered from [head, length)
|
|
+ // then followed by [0, tail)
|
|
+
|
|
+ System.arraycopy(oldElements, head, newElements, 0, oldElements.length - head);
|
|
+ System.arraycopy(oldElements, 0, newElements, oldElements.length - head, tail);
|
|
+
|
|
+ System.arraycopy(oldCounts, head, newCounts, 0, oldCounts.length - head);
|
|
+ System.arraycopy(oldCounts, 0, newCounts, oldCounts.length - head, tail);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ // returns in units per second
|
|
+ public double getRate() {
|
|
+ return (double)this.sum / ((double)this.interval * 1.0E-9);
|
|
+ }
|
|
+
|
|
+ public long getInterval() {
|
|
+ return this.interval;
|
|
+ }
|
|
+
|
|
+ public long getSum() {
|
|
+ return this.sum;
|
|
+ }
|
|
+
|
|
+ public int totalDataPoints() {
|
|
+ return this.tail >= this.head ? (this.tail - this.head) : (this.tail + (this.counts.length - this.head));
|
|
+ }
|
|
+}
|
|
diff --git a/src/main/java/io/papermc/paper/util/MCUtil.java b/src/main/java/io/papermc/paper/util/MCUtil.java
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
|
|
--- /dev/null
|
|
+++ b/src/main/java/io/papermc/paper/util/MCUtil.java
|
|
@@ -0,0 +0,0 @@
|
|
+package io.papermc.paper.util;
|
|
+
|
|
+import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
|
+import io.papermc.paper.math.BlockPosition;
|
|
+import io.papermc.paper.math.FinePosition;
|
|
+import io.papermc.paper.math.Position;
|
|
+import java.util.List;
|
|
+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.function.BiConsumer;
|
|
+import java.util.function.Consumer;
|
|
+import java.util.function.Supplier;
|
|
+import net.minecraft.core.BlockPos;
|
|
+import net.minecraft.core.Vec3i;
|
|
+import net.minecraft.server.MinecraftServer;
|
|
+import net.minecraft.world.level.ChunkPos;
|
|
+import net.minecraft.world.level.Level;
|
|
+import net.minecraft.world.phys.Vec3;
|
|
+import org.bukkit.Location;
|
|
+import org.bukkit.craftbukkit.util.Waitable;
|
|
+
|
|
+public final class MCUtil {
|
|
+ public static final java.util.concurrent.Executor MAIN_EXECUTOR = (run) -> {
|
|
+ if (!isMainThread()) {
|
|
+ MinecraftServer.getServer().execute(run);
|
|
+ } else {
|
|
+ run.run();
|
|
+ }
|
|
+ };
|
|
+ public static final ThreadPoolExecutor asyncExecutor = new ThreadPoolExecutor(
|
|
+ 2, 2, 60L, TimeUnit.SECONDS,
|
|
+ new LinkedBlockingQueue<>(),
|
|
+ new ThreadFactoryBuilder()
|
|
+ .setNameFormat("Paper Async Task Handler Thread - %1$d")
|
|
+ .setUncaughtExceptionHandler(new net.minecraft.DefaultUncaughtExceptionHandlerWithName(MinecraftServer.LOGGER))
|
|
+ .build()
|
|
+ );
|
|
+
|
|
+ private MCUtil() {
|
|
+ }
|
|
+
|
|
+ public static List<ChunkPos> getSpiralOutChunks(BlockPos blockposition, int radius) {
|
|
+ List<ChunkPos> list = com.google.common.collect.Lists.newArrayList();
|
|
+
|
|
+ list.add(new ChunkPos(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 ChunkPos((blockposition.getX() + (x << 4)) >> 4, (blockposition.getZ() + (z << 4)) >> 4));
|
|
+ list.add(new ChunkPos((blockposition.getX() - (x << 4)) >> 4, (blockposition.getZ() - (z << 4)) >> 4));
|
|
+
|
|
+ if (x < r) {
|
|
+ x++;
|
|
+ } else {
|
|
+ z--;
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ return list;
|
|
+ }
|
|
+
|
|
+ public static <T> CompletableFuture<T> ensureMain(CompletableFuture<T> future) {
|
|
+ return future.thenApplyAsync(r -> r, MAIN_EXECUTOR);
|
|
+ }
|
|
+
|
|
+ public static <T> void thenOnMain(CompletableFuture<T> future, Consumer<T> consumer) {
|
|
+ future.thenAcceptAsync(consumer, MAIN_EXECUTOR);
|
|
+ }
|
|
+
|
|
+ public static <T> void thenOnMain(CompletableFuture<T> future, BiConsumer<T, Throwable> consumer) {
|
|
+ future.whenCompleteAsync(consumer, MAIN_EXECUTOR);
|
|
+ }
|
|
+
|
|
+ public static boolean isMainThread() {
|
|
+ return MinecraftServer.getServer().isSameThread();
|
|
+ }
|
|
+
|
|
+ public static void ensureMain(Runnable run) {
|
|
+ ensureMain(null, run);
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * Ensures the target code is running on the main thread.
|
|
+ */
|
|
+ public static void ensureMain(String reason, Runnable run) {
|
|
+ if (!isMainThread()) {
|
|
+ if (reason != null) {
|
|
+ MinecraftServer.LOGGER.warn("Asynchronous " + reason + "!", new IllegalStateException());
|
|
+ }
|
|
+ MinecraftServer.getServer().processQueue.add(run);
|
|
+ return;
|
|
+ }
|
|
+ run.run();
|
|
+ }
|
|
+
|
|
+ public static <T> T ensureMain(Supplier<T> run) {
|
|
+ return ensureMain(null, run);
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * Ensures the target code is running on the main thread.
|
|
+ */
|
|
+ public static <T> T ensureMain(String reason, Supplier<T> run) {
|
|
+ if (!isMainThread()) {
|
|
+ if (reason != null) {
|
|
+ MinecraftServer.LOGGER.warn("Asynchronous " + reason + "! Blocking thread until it returns ", new IllegalStateException());
|
|
+ }
|
|
+ Waitable<T> wait = new Waitable<>() {
|
|
+ @Override
|
|
+ protected T evaluate() {
|
|
+ return run.get();
|
|
+ }
|
|
+ };
|
|
+ MinecraftServer.getServer().processQueue.add(wait);
|
|
+ try {
|
|
+ return wait.get();
|
|
+ } catch (InterruptedException | ExecutionException e) {
|
|
+ MinecraftServer.LOGGER.warn("Encountered exception", e);
|
|
+ }
|
|
+ return null;
|
|
+ }
|
|
+ return run.get();
|
|
+ }
|
|
+
|
|
+ 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));
|
|
+ }
|
|
+
|
|
+ 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);
|
|
+ }
|
|
+
|
|
+ public static Location toLocation(Level world, double x, double y, double z) {
|
|
+ return new Location(world.getWorld(), x, y, z);
|
|
+ }
|
|
+
|
|
+ public static Location toLocation(Level world, BlockPos pos) {
|
|
+ return new Location(world.getWorld(), pos.getX(), pos.getY(), pos.getZ());
|
|
+ }
|
|
+
|
|
+ public static BlockPos toBlockPosition(Location loc) {
|
|
+ return new BlockPos(loc.getBlockX(), loc.getBlockY(), loc.getBlockZ());
|
|
+ }
|
|
+
|
|
+ public static BlockPos toBlockPos(Position pos) {
|
|
+ return new BlockPos(pos.blockX(), pos.blockY(), pos.blockZ());
|
|
+ }
|
|
+
|
|
+ public static FinePosition toPosition(Vec3 vector) {
|
|
+ return Position.fine(vector.x, vector.y, vector.z);
|
|
+ }
|
|
+
|
|
+ public static BlockPosition toPosition(Vec3i vector) {
|
|
+ return Position.block(vector.getX(), vector.getY(), vector.getZ());
|
|
+ }
|
|
+
|
|
+ public static Vec3 toVec3(Position position) {
|
|
+ return new Vec3(position.x(), position.y(), position.z());
|
|
+ }
|
|
+
|
|
+ public static boolean isEdgeOfChunk(BlockPos pos) {
|
|
+ final int modX = pos.getX() & 15;
|
|
+ final int modZ = pos.getZ() & 15;
|
|
+ return (modX == 0 || modX == 15 || modZ == 0 || modZ == 15);
|
|
+ }
|
|
+
|
|
+ public static void scheduleAsyncTask(Runnable run) {
|
|
+ asyncExecutor.execute(run);
|
|
+ }
|
|
+}
|
|
diff --git a/src/main/java/io/papermc/paper/util/StackWalkerUtil.java b/src/main/java/io/papermc/paper/util/StackWalkerUtil.java
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
|
|
--- /dev/null
|
|
+++ b/src/main/java/io/papermc/paper/util/StackWalkerUtil.java
|
|
@@ -0,0 +0,0 @@
|
|
+package io.papermc.paper.util;
|
|
+
|
|
+import org.bukkit.plugin.java.JavaPlugin;
|
|
+import org.bukkit.plugin.java.PluginClassLoader;
|
|
+import org.jetbrains.annotations.Nullable;
|
|
+
|
|
+import java.util.Optional;
|
|
+
|
|
+public class StackWalkerUtil {
|
|
+
|
|
+ @Nullable
|
|
+ public static JavaPlugin getFirstPluginCaller() {
|
|
+ Optional<JavaPlugin> foundFrame = StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE)
|
|
+ .walk(stream -> stream
|
|
+ .filter(frame -> frame.getDeclaringClass().getClassLoader() instanceof PluginClassLoader)
|
|
+ .map((frame) -> {
|
|
+ PluginClassLoader classLoader = (PluginClassLoader) frame.getDeclaringClass().getClassLoader();
|
|
+ return classLoader.getPlugin();
|
|
+ })
|
|
+ .findFirst());
|
|
+
|
|
+ return foundFrame.orElse(null);
|
|
+ }
|
|
+}
|
|
diff --git a/src/main/java/net/minecraft/Util.java b/src/main/java/net/minecraft/Util.java
|
|
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
|
|
--- a/src/main/java/net/minecraft/Util.java
|
|
+++ b/src/main/java/net/minecraft/Util.java
|
|
@@ -0,0 +0,0 @@ public class Util {
|
|
}
|
|
|
|
public static long getNanos() {
|
|
- return timeSource.getAsLong();
|
|
+ return System.nanoTime(); // Paper
|
|
}
|
|
|
|
public static long getEpochMillis() {
|
|
diff --git a/src/main/java/net/minecraft/nbt/CompoundTag.java b/src/main/java/net/minecraft/nbt/CompoundTag.java
|
|
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
|
|
--- a/src/main/java/net/minecraft/nbt/CompoundTag.java
|
|
+++ b/src/main/java/net/minecraft/nbt/CompoundTag.java
|
|
@@ -0,0 +0,0 @@ public class CompoundTag implements Tag {
|
|
this.tags.put(key, NbtUtils.createUUID(value));
|
|
}
|
|
|
|
+
|
|
+ /**
|
|
+ * You must use {@link #hasUUID(String)} before or else it <b>will</b> throw an NPE.
|
|
+ */
|
|
public UUID getUUID(String key) {
|
|
return NbtUtils.loadUUID(this.get(key));
|
|
}
|
|
diff --git a/src/main/java/net/minecraft/network/Connection.java b/src/main/java/net/minecraft/network/Connection.java
|
|
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
|
|
--- a/src/main/java/net/minecraft/network/Connection.java
|
|
+++ b/src/main/java/net/minecraft/network/Connection.java
|
|
@@ -0,0 +0,0 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
|
|
BandwidthDebugMonitor bandwidthDebugMonitor;
|
|
public String hostname = ""; // CraftBukkit - add field
|
|
|
|
+ // Paper start - add utility methods
|
|
+ public final net.minecraft.server.level.ServerPlayer getPlayer() {
|
|
+ if (this.packetListener instanceof net.minecraft.server.network.ServerGamePacketListenerImpl impl) {
|
|
+ return impl.player;
|
|
+ } else if (this.packetListener instanceof net.minecraft.server.network.ServerCommonPacketListenerImpl impl) {
|
|
+ org.bukkit.craftbukkit.entity.CraftPlayer player = impl.getCraftPlayer();
|
|
+ return player == null ? null : player.getHandle();
|
|
+ }
|
|
+ return null;
|
|
+ }
|
|
+ // Paper end - add utility methods
|
|
+
|
|
public Connection(PacketFlow side) {
|
|
this.receiving = side;
|
|
}
|
|
diff --git a/src/main/java/net/minecraft/network/PacketEncoder.java b/src/main/java/net/minecraft/network/PacketEncoder.java
|
|
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
|
|
--- a/src/main/java/net/minecraft/network/PacketEncoder.java
|
|
+++ b/src/main/java/net/minecraft/network/PacketEncoder.java
|
|
@@ -0,0 +0,0 @@ public class PacketEncoder<T extends PacketListener> extends MessageToByteEncode
|
|
|
|
JvmProfiler.INSTANCE.onPacketSent(this.protocolInfo.id(), packetType, channelHandlerContext.channel().remoteAddress(), i);
|
|
} catch (Throwable var9) {
|
|
- LOGGER.error("Error sending packet {}", packetType, var9);
|
|
+ LOGGER.error("Error sending packet {} (skippable? {})", packetType, packet.isSkippable(), var9);
|
|
if (packet.isSkippable()) {
|
|
throw new SkipPacketException(var9);
|
|
}
|
|
diff --git a/src/main/java/net/minecraft/network/protocol/login/ClientboundCustomQueryPacket.java b/src/main/java/net/minecraft/network/protocol/login/ClientboundCustomQueryPacket.java
|
|
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
|
|
--- a/src/main/java/net/minecraft/network/protocol/login/ClientboundCustomQueryPacket.java
|
|
+++ b/src/main/java/net/minecraft/network/protocol/login/ClientboundCustomQueryPacket.java
|
|
@@ -0,0 +0,0 @@ public record ClientboundCustomQueryPacket(int transactionId, CustomQueryPayload
|
|
public void handle(ClientLoginPacketListener listener) {
|
|
listener.handleCustomQuery(this);
|
|
}
|
|
+
|
|
+ // Paper start - MC Utils - default query payloads
|
|
+ public static record PlayerInfoChannelPayload(ResourceLocation id, FriendlyByteBuf buffer) implements CustomQueryPayload {
|
|
+
|
|
+ @Override
|
|
+ public void write(final FriendlyByteBuf buf) {
|
|
+ buf.writeBytes(this.buffer.copy());
|
|
+ }
|
|
+ }
|
|
+ // Paper end - MC Utils - default query payloads
|
|
}
|
|
diff --git a/src/main/java/net/minecraft/network/protocol/login/ServerboundCustomQueryAnswerPacket.java b/src/main/java/net/minecraft/network/protocol/login/ServerboundCustomQueryAnswerPacket.java
|
|
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
|
|
--- a/src/main/java/net/minecraft/network/protocol/login/ServerboundCustomQueryAnswerPacket.java
|
|
+++ b/src/main/java/net/minecraft/network/protocol/login/ServerboundCustomQueryAnswerPacket.java
|
|
@@ -0,0 +0,0 @@ public record ServerboundCustomQueryAnswerPacket(int transactionId, @Nullable Cu
|
|
}
|
|
|
|
private static CustomQueryAnswerPayload readPayload(int queryId, FriendlyByteBuf buf) {
|
|
- return readUnknownPayload(buf);
|
|
+ // Paper start - MC Utils - default query payloads
|
|
+ FriendlyByteBuf buffer = buf.readNullable((buf2) -> {
|
|
+ int i = buf2.readableBytes();
|
|
+ if (i >= 0 && i <= MAX_PAYLOAD_SIZE) {
|
|
+ return new FriendlyByteBuf(buf2.readBytes(i));
|
|
+ } else {
|
|
+ throw new IllegalArgumentException("Payload may not be larger than " + MAX_PAYLOAD_SIZE + " bytes");
|
|
+ }
|
|
+ });
|
|
+ return buffer == null ? null : new net.minecraft.network.protocol.login.ServerboundCustomQueryAnswerPacket.QueryAnswerPayload(buffer);
|
|
+ // Paper end - MC Utils - default query payloads
|
|
}
|
|
|
|
private static CustomQueryAnswerPayload readUnknownPayload(FriendlyByteBuf buf) {
|
|
@@ -0,0 +0,0 @@ public record ServerboundCustomQueryAnswerPacket(int transactionId, @Nullable Cu
|
|
public void handle(ServerLoginPacketListener listener) {
|
|
listener.handleCustomQueryPacket(this);
|
|
}
|
|
+
|
|
+ // Paper start - MC Utils - default query payloads
|
|
+ public static final class QueryAnswerPayload implements CustomQueryAnswerPayload {
|
|
+
|
|
+ public final FriendlyByteBuf buffer;
|
|
+
|
|
+ public QueryAnswerPayload(final net.minecraft.network.FriendlyByteBuf buffer) {
|
|
+ this.buffer = buffer;
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public void write(final net.minecraft.network.FriendlyByteBuf buf) {
|
|
+ buf.writeBytes(this.buffer.copy());
|
|
+ }
|
|
+ }
|
|
+ // Paper end - MC Utils - default query payloads
|
|
+
|
|
}
|
|
diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java
|
|
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
|
|
--- a/src/main/java/net/minecraft/server/MinecraftServer.java
|
|
+++ b/src/main/java/net/minecraft/server/MinecraftServer.java
|
|
@@ -0,0 +0,0 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
|
public final double[] recentTps = new double[ 3 ];
|
|
// Spigot end
|
|
public final io.papermc.paper.configuration.PaperConfigurations paperConfigurations; // Paper - add paper configuration files
|
|
+ public static long currentTickLong = 0L; // Paper - track current tick as a long
|
|
|
|
public static <S extends MinecraftServer> S spin(Function<Thread, S> serverFactory) {
|
|
AtomicReference<S> atomicreference = new AtomicReference();
|
|
@@ -0,0 +0,0 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
|
MinecraftServer.LOGGER.error("Failed to unlock level {}", this.storageSource.getLevelId(), ioexception1);
|
|
}
|
|
// Spigot start
|
|
+ io.papermc.paper.util.MCUtil.asyncExecutor.shutdown(); // Paper
|
|
+ try { io.papermc.paper.util.MCUtil.asyncExecutor.awaitTermination(30, java.util.concurrent.TimeUnit.SECONDS); // Paper
|
|
+ } catch (java.lang.InterruptedException ignored) {} // Paper
|
|
if (org.spigotmc.SpigotConfig.saveUserCacheOnStopOnly) {
|
|
MinecraftServer.LOGGER.info("Saving usercache.json");
|
|
this.getProfileCache().save();
|
|
@@ -0,0 +0,0 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
|
}
|
|
}
|
|
// Spigot start
|
|
+ ++MinecraftServer.currentTickLong; // Paper - track current tick as a long
|
|
if ( tickCount++ % MinecraftServer.SAMPLE_INTERVAL == 0 )
|
|
{
|
|
long curTime = Util.getMillis();
|
|
@@ -0,0 +0,0 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
|
MinecraftServer.LOGGER.debug("Autosave finished");
|
|
SpigotTimings.worldSaveTimer.stopTiming(); // Spigot
|
|
}
|
|
-
|
|
this.profiler.push("tallying");
|
|
long j = Util.getNanos() - i;
|
|
int k = this.tickCount % 100;
|
|
diff --git a/src/main/java/net/minecraft/server/level/ChunkHolder.java b/src/main/java/net/minecraft/server/level/ChunkHolder.java
|
|
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
|
|
--- a/src/main/java/net/minecraft/server/level/ChunkHolder.java
|
|
+++ b/src/main/java/net/minecraft/server/level/ChunkHolder.java
|
|
@@ -0,0 +0,0 @@ public class ChunkHolder extends GenerationChunkHolder {
|
|
public static final ChunkResult<LevelChunk> UNLOADED_LEVEL_CHUNK = ChunkResult.error("Unloaded level chunk");
|
|
private static final CompletableFuture<ChunkResult<LevelChunk>> UNLOADED_LEVEL_CHUNK_FUTURE = CompletableFuture.completedFuture(ChunkHolder.UNLOADED_LEVEL_CHUNK);
|
|
private final LevelHeightAccessor levelHeightAccessor;
|
|
- private volatile CompletableFuture<ChunkResult<LevelChunk>> fullChunkFuture;
|
|
- private volatile CompletableFuture<ChunkResult<LevelChunk>> tickingChunkFuture;
|
|
- private volatile CompletableFuture<ChunkResult<LevelChunk>> entityTickingChunkFuture;
|
|
+ private volatile CompletableFuture<ChunkResult<LevelChunk>> fullChunkFuture; private int fullChunkCreateCount; private volatile boolean isFullChunkReady; // Paper - cache chunk ticking stage
|
|
+ private volatile CompletableFuture<ChunkResult<LevelChunk>> tickingChunkFuture; private volatile boolean isTickingReady; // Paper - cache chunk ticking stage
|
|
+ private volatile CompletableFuture<ChunkResult<LevelChunk>> entityTickingChunkFuture; private volatile boolean isEntityTickingReady; // Paper - cache chunk ticking stage
|
|
public int oldTicketLevel;
|
|
private int ticketLevel;
|
|
private int queueLevel;
|
|
@@ -0,0 +0,0 @@ public class ChunkHolder extends GenerationChunkHolder {
|
|
}
|
|
|
|
@Nullable
|
|
- public LevelChunk getTickingChunk() {
|
|
+ public final LevelChunk getTickingChunk() { // Paper - final for inline
|
|
return (LevelChunk) ((ChunkResult) this.getTickingChunkFuture().getNow(ChunkHolder.UNLOADED_LEVEL_CHUNK)).orElse(null); // CraftBukkit - decompile error
|
|
}
|
|
|
|
@@ -0,0 +0,0 @@ public class ChunkHolder extends GenerationChunkHolder {
|
|
|
|
this.wasAccessibleSinceLastSave |= flag1;
|
|
if (!flag && flag1) {
|
|
+ int expectCreateCount = ++this.fullChunkCreateCount; // Paper
|
|
this.fullChunkFuture = chunkLoadingManager.prepareAccessibleChunk(this);
|
|
this.scheduleFullChunkPromotion(chunkLoadingManager, this.fullChunkFuture, executor, FullChunkStatus.FULL);
|
|
+ // Paper start - cache ticking ready status
|
|
+ this.fullChunkFuture.thenAccept(chunkResult -> {
|
|
+ chunkResult.ifSuccess(chunk -> {
|
|
+ if (ChunkHolder.this.fullChunkCreateCount == expectCreateCount) {
|
|
+ ChunkHolder.this.isFullChunkReady = true;
|
|
+ ca.spottedleaf.moonrise.common.util.ChunkSystem.onChunkBorder(chunk, this);
|
|
+ }
|
|
+ });
|
|
+ });
|
|
+ // Paper end - cache ticking ready status
|
|
this.addSaveDependency(this.fullChunkFuture);
|
|
}
|
|
|
|
if (flag && !flag1) {
|
|
+ // Paper start
|
|
+ if (this.isFullChunkReady) {
|
|
+ ca.spottedleaf.moonrise.common.util.ChunkSystem.onChunkNotBorder(this.fullChunkFuture.join().orElseThrow(IllegalStateException::new), this); // Paper
|
|
+ }
|
|
+ // Paper end
|
|
this.fullChunkFuture.complete(ChunkHolder.UNLOADED_LEVEL_CHUNK);
|
|
this.fullChunkFuture = ChunkHolder.UNLOADED_LEVEL_CHUNK_FUTURE;
|
|
}
|
|
@@ -0,0 +0,0 @@ public class ChunkHolder extends GenerationChunkHolder {
|
|
if (!flag2 && flag3) {
|
|
this.tickingChunkFuture = chunkLoadingManager.prepareTickingChunk(this);
|
|
this.scheduleFullChunkPromotion(chunkLoadingManager, this.tickingChunkFuture, executor, FullChunkStatus.BLOCK_TICKING);
|
|
+ // Paper start - cache ticking ready status
|
|
+ this.tickingChunkFuture.thenAccept(chunkResult -> {
|
|
+ chunkResult.ifSuccess(chunk -> {
|
|
+ // note: Here is a very good place to add callbacks to logic waiting on this.
|
|
+ ChunkHolder.this.isTickingReady = true;
|
|
+ ca.spottedleaf.moonrise.common.util.ChunkSystem.onChunkTicking(chunk, this);
|
|
+ });
|
|
+ });
|
|
+ // Paper end
|
|
this.addSaveDependency(this.tickingChunkFuture);
|
|
}
|
|
|
|
if (flag2 && !flag3) {
|
|
- this.tickingChunkFuture.complete(ChunkHolder.UNLOADED_LEVEL_CHUNK);
|
|
+ // Paper start
|
|
+ if (this.isTickingReady) {
|
|
+ ca.spottedleaf.moonrise.common.util.ChunkSystem.onChunkNotTicking(this.tickingChunkFuture.join().orElseThrow(IllegalStateException::new), this); // Paper
|
|
+ }
|
|
+ // Paper end
|
|
+ this.tickingChunkFuture.complete(ChunkHolder.UNLOADED_LEVEL_CHUNK); this.isTickingReady = false; // Paper - cache chunk ticking stage
|
|
this.tickingChunkFuture = ChunkHolder.UNLOADED_LEVEL_CHUNK_FUTURE;
|
|
}
|
|
|
|
@@ -0,0 +0,0 @@ public class ChunkHolder extends GenerationChunkHolder {
|
|
|
|
this.entityTickingChunkFuture = chunkLoadingManager.prepareEntityTickingChunk(this);
|
|
this.scheduleFullChunkPromotion(chunkLoadingManager, this.entityTickingChunkFuture, executor, FullChunkStatus.ENTITY_TICKING);
|
|
+ // Paper start - cache ticking ready status
|
|
+ this.entityTickingChunkFuture.thenAccept(chunkResult -> {
|
|
+ chunkResult.ifSuccess(chunk -> {
|
|
+ ChunkHolder.this.isEntityTickingReady = true;
|
|
+ ca.spottedleaf.moonrise.common.util.ChunkSystem.onChunkEntityTicking(chunk, this);
|
|
+ });
|
|
+ });
|
|
+ // Paper end
|
|
this.addSaveDependency(this.entityTickingChunkFuture);
|
|
}
|
|
|
|
if (flag4 && !flag5) {
|
|
- this.entityTickingChunkFuture.complete(ChunkHolder.UNLOADED_LEVEL_CHUNK);
|
|
+ // Paper start
|
|
+ if (this.isEntityTickingReady) {
|
|
+ ca.spottedleaf.moonrise.common.util.ChunkSystem.onChunkNotEntityTicking(this.entityTickingChunkFuture.join().orElseThrow(IllegalStateException::new), this);
|
|
+ }
|
|
+ // Paper end
|
|
+ this.entityTickingChunkFuture.complete(ChunkHolder.UNLOADED_LEVEL_CHUNK); this.isEntityTickingReady = false; // Paper - cache chunk ticking stage
|
|
this.entityTickingChunkFuture = ChunkHolder.UNLOADED_LEVEL_CHUNK_FUTURE;
|
|
}
|
|
|
|
diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java
|
|
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
|
|
--- a/src/main/java/net/minecraft/server/level/ChunkMap.java
|
|
+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java
|
|
@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
|
|
};
|
|
// CraftBukkit end
|
|
|
|
+ // Paper start
|
|
+ public final ChunkHolder getUnloadingChunkHolder(int chunkX, int chunkZ) {
|
|
+ return this.pendingUnloads.get(ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkKey(chunkX, chunkZ));
|
|
+ }
|
|
+ // Paper end
|
|
+
|
|
public ChunkMap(ServerLevel world, LevelStorageSource.LevelStorageAccess session, DataFixer dataFixer, StructureTemplateManager structureTemplateManager, Executor executor, BlockableEventLoop<Runnable> mainThreadExecutor, LightChunkGetter chunkProvider, ChunkGenerator chunkGenerator, ChunkProgressListener worldGenerationProgressListener, ChunkStatusUpdateListener chunkStatusChangeListener, Supplier<DimensionDataStorage> persistentStateManagerFactory, int viewDistance, boolean dsync) {
|
|
super(new RegionStorageInfo(session.getLevelId(), world.dimension(), "chunk"), session.getDimensionPath(world.dimension()).resolve("region"), dataFixer, dsync);
|
|
this.visibleChunkMap = this.updatingChunkMap.clone();
|
|
@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
|
|
this.worldGenContext = new WorldGenContext(world, chunkGenerator, structureTemplateManager, this.lightEngine, this.mainThreadMailbox);
|
|
}
|
|
|
|
+ // Paper start
|
|
+ public int getMobCountNear(final ServerPlayer player, final net.minecraft.world.entity.MobCategory mobCategory) {
|
|
+ return -1;
|
|
+ }
|
|
+ // Paper end
|
|
+
|
|
protected ChunkGenerator generator() {
|
|
return this.worldGenContext.generator();
|
|
}
|
|
@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
|
|
};
|
|
|
|
stringbuilder.append("Updating:").append(System.lineSeparator());
|
|
- this.updatingChunkMap.values().forEach(consumer);
|
|
+ ca.spottedleaf.moonrise.common.util.ChunkSystem.getUpdatingChunkHolders(this.level).forEach(consumer); // Paper
|
|
stringbuilder.append("Visible:").append(System.lineSeparator());
|
|
- this.visibleChunkMap.values().forEach(consumer);
|
|
+ ca.spottedleaf.moonrise.common.util.ChunkSystem.getVisibleChunkHolders(this.level).forEach(consumer); // Paper
|
|
CrashReport crashreport = CrashReport.forThrowable(exception, "Chunk loading");
|
|
CrashReportCategory crashreportsystemdetails = crashreport.addCategory("Chunk loading");
|
|
|
|
@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
|
|
holder.setTicketLevel(level);
|
|
} else {
|
|
holder = new ChunkHolder(new ChunkPos(pos), level, this.level, this.lightEngine, this.queueSorter, this);
|
|
+ // Paper start
|
|
+ ca.spottedleaf.moonrise.common.util.ChunkSystem.onChunkHolderCreate(this.level, holder);
|
|
+ // Paper end
|
|
}
|
|
|
|
this.updatingChunkMap.put(pos, holder);
|
|
@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
|
|
|
|
protected void saveAllChunks(boolean flush) {
|
|
if (flush) {
|
|
- List<ChunkHolder> list = this.visibleChunkMap.values().stream().filter(ChunkHolder::wasAccessibleSinceLastSave).peek(ChunkHolder::refreshAccessibility).toList();
|
|
+ List<ChunkHolder> list = ca.spottedleaf.moonrise.common.util.ChunkSystem.getVisibleChunkHolders(this.level).stream().filter(ChunkHolder::wasAccessibleSinceLastSave).peek(ChunkHolder::refreshAccessibility).toList(); // Paper
|
|
MutableBoolean mutableboolean = new MutableBoolean();
|
|
|
|
do {
|
|
@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
|
|
});
|
|
this.flushWorker();
|
|
} else {
|
|
- this.visibleChunkMap.values().forEach(this::saveChunkIfNeeded);
|
|
+ ca.spottedleaf.moonrise.common.util.ChunkSystem.getVisibleChunkHolders(this.level).forEach(this::saveChunkIfNeeded);
|
|
}
|
|
|
|
}
|
|
@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
|
|
}
|
|
|
|
public boolean hasWork() {
|
|
- return this.lightEngine.hasLightWork() || !this.pendingUnloads.isEmpty() || !this.updatingChunkMap.isEmpty() || this.poiManager.hasWork() || !this.toDrop.isEmpty() || !this.unloadQueue.isEmpty() || this.queueSorter.hasWork() || this.distanceManager.hasTickets();
|
|
+ return this.lightEngine.hasLightWork() || !this.pendingUnloads.isEmpty() || ca.spottedleaf.moonrise.common.util.ChunkSystem.hasAnyChunkHolders(this.level) || this.poiManager.hasWork() || !this.toDrop.isEmpty() || !this.unloadQueue.isEmpty() || this.queueSorter.hasWork() || this.distanceManager.hasTickets(); // Paper
|
|
}
|
|
|
|
private void processUnloads(BooleanSupplier shouldKeepTicking) {
|
|
@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
|
|
}
|
|
|
|
int l = 0;
|
|
- ObjectIterator<ChunkHolder> objectiterator = this.visibleChunkMap.values().iterator();
|
|
+ Iterator<ChunkHolder> objectiterator = ca.spottedleaf.moonrise.common.util.ChunkSystem.getVisibleChunkHolders(this.level).iterator(); // Paper
|
|
|
|
while (l < 20 && shouldKeepTicking.getAsBoolean() && objectiterator.hasNext()) {
|
|
if (this.saveChunkIfNeeded((ChunkHolder) objectiterator.next())) {
|
|
@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
|
|
} else {
|
|
ChunkAccess ichunkaccess = holder.getLatestChunk();
|
|
|
|
- if (this.pendingUnloads.remove(pos, holder) && ichunkaccess != null) {
|
|
+ // Paper start
|
|
+ boolean removed;
|
|
+ if ((removed = this.pendingUnloads.remove(pos, holder)) && ichunkaccess != null) {
|
|
+ ca.spottedleaf.moonrise.common.util.ChunkSystem.onChunkHolderDelete(this.level, holder);
|
|
+ // Paper end
|
|
LevelChunk chunk;
|
|
|
|
if (ichunkaccess instanceof LevelChunk) {
|
|
@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
|
|
this.lightEngine.tryScheduleUpdate();
|
|
this.progressListener.onStatusChange(ichunkaccess.getPos(), (ChunkStatus) null);
|
|
this.chunkSaveCooldowns.remove(ichunkaccess.getPos().toLong());
|
|
- }
|
|
+ } else if (removed) { // Paper start
|
|
+ ca.spottedleaf.moonrise.common.util.ChunkSystem.onChunkHolderDelete(this.level, holder);
|
|
+ } // Paper end
|
|
|
|
}
|
|
};
|
|
@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
|
|
}
|
|
}
|
|
|
|
- protected void setServerViewDistance(int watchDistance) {
|
|
+ public void setServerViewDistance(int watchDistance) { // Paper - public
|
|
int j = Mth.clamp(watchDistance, 2, 32);
|
|
|
|
if (j != this.serverViewDistance) {
|
|
@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
|
|
|
|
}
|
|
|
|
- int getPlayerViewDistance(ServerPlayer player) {
|
|
+ public int getPlayerViewDistance(ServerPlayer player) { // Paper - public
|
|
return Mth.clamp(player.requestedViewDistance(), 2, this.serverViewDistance);
|
|
}
|
|
|
|
@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
|
|
}
|
|
|
|
public int size() {
|
|
- return this.visibleChunkMap.size();
|
|
+ return ca.spottedleaf.moonrise.common.util.ChunkSystem.getVisibleChunkHolderCount(this.level); // Paper
|
|
}
|
|
|
|
public DistanceManager getDistanceManager() {
|
|
@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
|
|
}
|
|
|
|
protected Iterable<ChunkHolder> getChunks() {
|
|
- return Iterables.unmodifiableIterable(this.visibleChunkMap.values());
|
|
+ return Iterables.unmodifiableIterable(ca.spottedleaf.moonrise.common.util.ChunkSystem.getVisibleChunkHolders(this.level)); // Paper
|
|
}
|
|
|
|
void dumpChunks(Writer writer) throws IOException {
|
|
CsvOutput csvwriter = CsvOutput.builder().addColumn("x").addColumn("z").addColumn("level").addColumn("in_memory").addColumn("status").addColumn("full_status").addColumn("accessible_ready").addColumn("ticking_ready").addColumn("entity_ticking_ready").addColumn("ticket").addColumn("spawning").addColumn("block_entity_count").addColumn("ticking_ticket").addColumn("ticking_level").addColumn("block_ticks").addColumn("fluid_ticks").build(writer);
|
|
TickingTracker tickingtracker = this.distanceManager.tickingTracker();
|
|
- ObjectBidirectionalIterator objectbidirectionaliterator = this.visibleChunkMap.long2ObjectEntrySet().iterator();
|
|
+ Iterator<ChunkHolder> objectbidirectionaliterator = ca.spottedleaf.moonrise.common.util.ChunkSystem.getVisibleChunkHolders(this.level).iterator(); // Paper
|
|
|
|
while (objectbidirectionaliterator.hasNext()) {
|
|
- Entry<ChunkHolder> entry = (Entry) objectbidirectionaliterator.next();
|
|
- long i = entry.getLongKey();
|
|
+ ChunkHolder playerchunk = objectbidirectionaliterator.next(); // Paper
|
|
+ long i = playerchunk.pos.toLong(); // Paper
|
|
ChunkPos chunkcoordintpair = new ChunkPos(i);
|
|
- ChunkHolder playerchunk = (ChunkHolder) entry.getValue();
|
|
+ // Paper - move up
|
|
Optional<ChunkAccess> optional = Optional.ofNullable(playerchunk.getLatestChunk());
|
|
Optional<LevelChunk> optional1 = optional.flatMap((ichunkaccess) -> {
|
|
return ichunkaccess instanceof LevelChunk ? Optional.of((LevelChunk) ichunkaccess) : Optional.empty();
|
|
@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
|
|
});
|
|
}
|
|
|
|
- private class ChunkDistanceManager extends DistanceManager {
|
|
+ public class ChunkDistanceManager extends DistanceManager { // Paper - public
|
|
|
|
protected ChunkDistanceManager(final Executor workerExecutor, final Executor mainThreadExecutor) {
|
|
super(workerExecutor, mainThreadExecutor);
|
|
diff --git a/src/main/java/net/minecraft/server/level/DistanceManager.java b/src/main/java/net/minecraft/server/level/DistanceManager.java
|
|
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
|
|
--- a/src/main/java/net/minecraft/server/level/DistanceManager.java
|
|
+++ b/src/main/java/net/minecraft/server/level/DistanceManager.java
|
|
@@ -0,0 +0,0 @@ public abstract class DistanceManager {
|
|
}
|
|
|
|
public void removeTicketsOnClosing() {
|
|
- ImmutableSet<TicketType<?>> immutableset = ImmutableSet.of(TicketType.UNKNOWN, TicketType.POST_TELEPORT);
|
|
+ ImmutableSet<TicketType<?>> immutableset = ImmutableSet.of(TicketType.UNKNOWN, TicketType.POST_TELEPORT, TicketType.FUTURE_AWAIT); // Paper - add additional tickets to preserve
|
|
ObjectIterator<Entry<SortedArraySet<Ticket<?>>>> objectiterator = this.tickets.long2ObjectEntrySet().fastIterator();
|
|
|
|
while (objectiterator.hasNext()) {
|
|
diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java
|
|
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
|
|
--- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java
|
|
+++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java
|
|
@@ -0,0 +0,0 @@ import net.minecraft.world.level.storage.LevelStorageSource;
|
|
|
|
public class ServerChunkCache extends ChunkSource {
|
|
|
|
+ public static final org.slf4j.Logger LOGGER = com.mojang.logging.LogUtils.getLogger(); // Paper
|
|
private static final List<ChunkStatus> CHUNK_STATUSES = ChunkStatus.getStatusList();
|
|
private final DistanceManager distanceManager;
|
|
final ServerLevel level;
|
|
@@ -0,0 +0,0 @@ public class ServerChunkCache extends ChunkSource {
|
|
@Nullable
|
|
@VisibleForDebug
|
|
private NaturalSpawner.SpawnState lastSpawnState;
|
|
+ // Paper start
|
|
+ private final ca.spottedleaf.concurrentutil.map.ConcurrentLong2ReferenceChainedHashTable<net.minecraft.world.level.chunk.LevelChunk> fullChunks = new ca.spottedleaf.concurrentutil.map.ConcurrentLong2ReferenceChainedHashTable<>();
|
|
+ long chunkFutureAwaitCounter;
|
|
+ // Paper end
|
|
|
|
public ServerChunkCache(ServerLevel world, LevelStorageSource.LevelStorageAccess session, DataFixer dataFixer, StructureTemplateManager structureTemplateManager, Executor workerExecutor, ChunkGenerator chunkGenerator, int viewDistance, int simulationDistance, boolean dsync, ChunkProgressListener worldGenerationProgressListener, ChunkStatusUpdateListener chunkStatusChangeListener, Supplier<DimensionDataStorage> persistentStateManagerFactory) {
|
|
this.level = world;
|
|
@@ -0,0 +0,0 @@ public class ServerChunkCache extends ChunkSource {
|
|
return chunk.getFullChunkNow() != null;
|
|
}
|
|
// CraftBukkit end
|
|
+ // Paper start
|
|
+ public void addLoadedChunk(LevelChunk chunk) {
|
|
+ this.fullChunks.put(chunk.coordinateKey, chunk);
|
|
+ }
|
|
+
|
|
+ public void removeLoadedChunk(LevelChunk chunk) {
|
|
+ this.fullChunks.remove(chunk.coordinateKey);
|
|
+ }
|
|
+
|
|
+ @Nullable
|
|
+ public ChunkAccess getChunkAtImmediately(int x, int z) {
|
|
+ ChunkHolder holder = this.chunkMap.getVisibleChunkIfPresent(ChunkPos.asLong(x, z));
|
|
+ if (holder == null) {
|
|
+ return null;
|
|
+ }
|
|
+
|
|
+ return holder.getLatestChunk();
|
|
+ }
|
|
+
|
|
+ public <T> void addTicketAtLevel(TicketType<T> ticketType, ChunkPos chunkPos, int ticketLevel, T identifier) {
|
|
+ this.distanceManager.addTicket(ticketType, chunkPos, ticketLevel, identifier);
|
|
+ }
|
|
+
|
|
+ public <T> void removeTicketAtLevel(TicketType<T> ticketType, ChunkPos chunkPos, int ticketLevel, T identifier) {
|
|
+ this.distanceManager.removeTicket(ticketType, chunkPos, ticketLevel, identifier);
|
|
+ }
|
|
+
|
|
+ // "real" get chunk if loaded
|
|
+ // Note: Partially copied from the getChunkAt method below
|
|
+ @Nullable
|
|
+ public LevelChunk getChunkAtIfCachedImmediately(int x, int z) {
|
|
+ long k = ChunkPos.asLong(x, z);
|
|
+
|
|
+ // Note: Bypass cache since we need to check ticket level, and to make this MT-Safe
|
|
+
|
|
+ ChunkHolder playerChunk = this.getVisibleChunkIfPresent(k);
|
|
+ if (playerChunk == null) {
|
|
+ return null;
|
|
+ }
|
|
+
|
|
+ return playerChunk.getFullChunkNowUnchecked();
|
|
+ }
|
|
+
|
|
+ @Nullable
|
|
+ public LevelChunk getChunkAtIfLoadedImmediately(int x, int z) {
|
|
+ return this.fullChunks.get(ChunkPos.asLong(x, z));
|
|
+ }
|
|
+ // Paper end
|
|
|
|
@Override
|
|
public ThreadedLevelLightEngine getLightEngine() {
|
|
@@ -0,0 +0,0 @@ public class ServerChunkCache extends ChunkSource {
|
|
return this.mainThreadProcessor.pollTask();
|
|
}
|
|
|
|
- boolean runDistanceManagerUpdates() {
|
|
+ public boolean runDistanceManagerUpdates() { // Paper - public
|
|
boolean flag = this.distanceManager.runAllUpdates(this.chunkMap);
|
|
boolean flag1 = this.chunkMap.promoteChunkMap();
|
|
|
|
@@ -0,0 +0,0 @@ public class ServerChunkCache extends ChunkSource {
|
|
}
|
|
}
|
|
|
|
+ // Paper start
|
|
+ public boolean isPositionTicking(Entity entity) {
|
|
+ return this.isPositionTicking(ChunkPos.asLong(net.minecraft.util.Mth.floor(entity.getX()) >> 4, net.minecraft.util.Mth.floor(entity.getZ()) >> 4));
|
|
+ }
|
|
+ // Paper end
|
|
+
|
|
public boolean isPositionTicking(long pos) {
|
|
ChunkHolder playerchunk = this.getVisibleChunkIfPresent(pos);
|
|
|
|
diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java
|
|
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
|
|
--- a/src/main/java/net/minecraft/server/level/ServerLevel.java
|
|
+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
|
|
@@ -0,0 +0,0 @@ public class ServerLevel extends Level implements WorldGenLevel {
|
|
return this.convertable.dimensionType;
|
|
}
|
|
|
|
+ // Paper start
|
|
+ public final boolean areChunksLoadedForMove(AABB axisalignedbb) {
|
|
+ // copied code from collision methods, so that we can guarantee that they wont load chunks (we don't override
|
|
+ // ICollisionAccess methods for VoxelShapes)
|
|
+ // be more strict too, add a block (dumb plugins in move events?)
|
|
+ int minBlockX = Mth.floor(axisalignedbb.minX - 1.0E-7D) - 3;
|
|
+ int maxBlockX = Mth.floor(axisalignedbb.maxX + 1.0E-7D) + 3;
|
|
+
|
|
+ int minBlockZ = Mth.floor(axisalignedbb.minZ - 1.0E-7D) - 3;
|
|
+ int maxBlockZ = Mth.floor(axisalignedbb.maxZ + 1.0E-7D) + 3;
|
|
+
|
|
+ int minChunkX = minBlockX >> 4;
|
|
+ int maxChunkX = maxBlockX >> 4;
|
|
+
|
|
+ int minChunkZ = minBlockZ >> 4;
|
|
+ int maxChunkZ = maxBlockZ >> 4;
|
|
+
|
|
+ ServerChunkCache chunkProvider = this.getChunkSource();
|
|
+
|
|
+ for (int cx = minChunkX; cx <= maxChunkX; ++cx) {
|
|
+ for (int cz = minChunkZ; cz <= maxChunkZ; ++cz) {
|
|
+ if (chunkProvider.getChunkAtIfLoadedImmediately(cx, cz) == null) {
|
|
+ return false;
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ return true;
|
|
+ }
|
|
+
|
|
+ public final void loadChunksForMoveAsync(AABB axisalignedbb, ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor.Priority priority,
|
|
+ java.util.function.Consumer<List<net.minecraft.world.level.chunk.ChunkAccess>> onLoad) {
|
|
+ if (Thread.currentThread() != this.thread) {
|
|
+ this.getChunkSource().mainThreadProcessor.execute(() -> {
|
|
+ this.loadChunksForMoveAsync(axisalignedbb, priority, onLoad);
|
|
+ });
|
|
+ return;
|
|
+ }
|
|
+ List<net.minecraft.world.level.chunk.ChunkAccess> ret = new java.util.ArrayList<>();
|
|
+ it.unimi.dsi.fastutil.ints.IntArrayList ticketLevels = new it.unimi.dsi.fastutil.ints.IntArrayList();
|
|
+
|
|
+ int minBlockX = Mth.floor(axisalignedbb.minX - 1.0E-7D) - 3;
|
|
+ int maxBlockX = Mth.floor(axisalignedbb.maxX + 1.0E-7D) + 3;
|
|
+
|
|
+ int minBlockZ = Mth.floor(axisalignedbb.minZ - 1.0E-7D) - 3;
|
|
+ int maxBlockZ = Mth.floor(axisalignedbb.maxZ + 1.0E-7D) + 3;
|
|
+
|
|
+ int minChunkX = minBlockX >> 4;
|
|
+ int maxChunkX = maxBlockX >> 4;
|
|
+
|
|
+ int minChunkZ = minBlockZ >> 4;
|
|
+ int maxChunkZ = maxBlockZ >> 4;
|
|
+
|
|
+ ServerChunkCache chunkProvider = this.getChunkSource();
|
|
+
|
|
+ int requiredChunks = (maxChunkX - minChunkX + 1) * (maxChunkZ - minChunkZ + 1);
|
|
+ int[] loadedChunks = new int[1];
|
|
+
|
|
+ Long holderIdentifier = Long.valueOf(chunkProvider.chunkFutureAwaitCounter++);
|
|
+
|
|
+ java.util.function.Consumer<net.minecraft.world.level.chunk.ChunkAccess> consumer = (net.minecraft.world.level.chunk.ChunkAccess chunk) -> {
|
|
+ if (chunk != null) {
|
|
+ int ticketLevel = Math.max(33, chunkProvider.chunkMap.getUpdatingChunkIfPresent(chunk.getPos().toLong()).getTicketLevel());
|
|
+ ret.add(chunk);
|
|
+ ticketLevels.add(ticketLevel);
|
|
+ chunkProvider.addTicketAtLevel(TicketType.FUTURE_AWAIT, chunk.getPos(), ticketLevel, holderIdentifier);
|
|
+ }
|
|
+ if (++loadedChunks[0] == requiredChunks) {
|
|
+ try {
|
|
+ onLoad.accept(java.util.Collections.unmodifiableList(ret));
|
|
+ } finally {
|
|
+ for (int i = 0, len = ret.size(); i < len; ++i) {
|
|
+ ChunkPos chunkPos = ret.get(i).getPos();
|
|
+ int ticketLevel = ticketLevels.getInt(i);
|
|
+
|
|
+ chunkProvider.addTicketAtLevel(TicketType.UNKNOWN, chunkPos, ticketLevel, chunkPos);
|
|
+ chunkProvider.removeTicketAtLevel(TicketType.FUTURE_AWAIT, chunkPos, ticketLevel, holderIdentifier);
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ };
|
|
+
|
|
+ for (int cx = minChunkX; cx <= maxChunkX; ++cx) {
|
|
+ for (int cz = minChunkZ; cz <= maxChunkZ; ++cz) {
|
|
+ ca.spottedleaf.moonrise.common.util.ChunkSystem.scheduleChunkLoad(
|
|
+ this, cx, cz, net.minecraft.world.level.chunk.status.ChunkStatus.FULL, true, priority, consumer
|
|
+ );
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ // Paper end
|
|
+
|
|
// Add env and gen to constructor, IWorldDataServer -> WorldDataServer
|
|
public ServerLevel(MinecraftServer minecraftserver, Executor executor, LevelStorageSource.LevelStorageAccess convertable_conversionsession, PrimaryLevelData iworlddataserver, ResourceKey<Level> resourcekey, LevelStem worlddimension, ChunkProgressListener worldloadlistener, boolean flag, long i, List<CustomSpawner> list, boolean flag1, @Nullable RandomSequences randomsequences, org.bukkit.World.Environment env, org.bukkit.generator.ChunkGenerator gen, org.bukkit.generator.BiomeProvider biomeProvider) {
|
|
// IRegistryCustom.Dimension iregistrycustom_dimension = minecraftserver.registryAccess(); // CraftBukkit - decompile error
|
|
diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java
|
|
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
|
|
--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java
|
|
+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java
|
|
@@ -0,0 +0,0 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player {
|
|
public boolean sentListPacket = false;
|
|
public String kickLeaveMessage = null; // SPIGOT-3034: Forward leave message to PlayerQuitEvent
|
|
// CraftBukkit end
|
|
+ public boolean isRealPlayer; // Paper
|
|
|
|
public ServerPlayer(MinecraftServer server, ServerLevel world, GameProfile profile, ClientInformation clientOptions) {
|
|
super(world, world.getSharedSpawnPos(), world.getSharedSpawnAngle(), profile);
|
|
diff --git a/src/main/java/net/minecraft/server/level/TicketType.java b/src/main/java/net/minecraft/server/level/TicketType.java
|
|
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
|
|
--- a/src/main/java/net/minecraft/server/level/TicketType.java
|
|
+++ b/src/main/java/net/minecraft/server/level/TicketType.java
|
|
@@ -0,0 +0,0 @@ import net.minecraft.util.Unit;
|
|
import net.minecraft.world.level.ChunkPos;
|
|
|
|
public class TicketType<T> {
|
|
+ public static final TicketType<Long> FUTURE_AWAIT = create("future_await", Long::compareTo); // Paper
|
|
|
|
private final String name;
|
|
private final Comparator<T> comparator;
|
|
diff --git a/src/main/java/net/minecraft/server/level/WorldGenRegion.java b/src/main/java/net/minecraft/server/level/WorldGenRegion.java
|
|
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
|
|
--- a/src/main/java/net/minecraft/server/level/WorldGenRegion.java
|
|
+++ b/src/main/java/net/minecraft/server/level/WorldGenRegion.java
|
|
@@ -0,0 +0,0 @@ public class WorldGenRegion implements WorldGenLevel {
|
|
return k < this.generatingStep.directDependencies().size();
|
|
}
|
|
|
|
+ // Paper start - if loaded util
|
|
+ @Nullable
|
|
+ @Override
|
|
+ public ChunkAccess getChunkIfLoadedImmediately(int x, int z) {
|
|
+ return this.getChunk(x, z, ChunkStatus.FULL, false);
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public final BlockState getBlockStateIfLoaded(BlockPos blockposition) {
|
|
+ ChunkAccess chunk = this.getChunkIfLoadedImmediately(blockposition.getX() >> 4, blockposition.getZ() >> 4);
|
|
+ return chunk == null ? null : chunk.getBlockState(blockposition);
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public final FluidState getFluidIfLoaded(BlockPos blockposition) {
|
|
+ ChunkAccess chunk = this.getChunkIfLoadedImmediately(blockposition.getX() >> 4, blockposition.getZ() >> 4);
|
|
+ return chunk == null ? null : chunk.getFluidState(blockposition);
|
|
+ }
|
|
+ // Paper end
|
|
+
|
|
@Override
|
|
public BlockState getBlockState(BlockPos pos) {
|
|
return this.getChunk(SectionPos.blockToSectionCoord(pos.getX()), SectionPos.blockToSectionCoord(pos.getZ())).getBlockState(pos);
|
|
diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java
|
|
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
|
|
--- a/src/main/java/net/minecraft/server/players/PlayerList.java
|
|
+++ b/src/main/java/net/minecraft/server/players/PlayerList.java
|
|
@@ -0,0 +0,0 @@ public abstract class PlayerList {
|
|
}
|
|
|
|
public void placeNewPlayer(Connection connection, ServerPlayer player, CommonListenerCookie clientData) {
|
|
+ player.isRealPlayer = true; // Paper
|
|
GameProfile gameprofile = player.getGameProfile();
|
|
GameProfileCache usercache = this.server.getProfileCache();
|
|
// Optional optional; // CraftBukkit - decompile error
|
|
diff --git a/src/main/java/net/minecraft/util/thread/BlockableEventLoop.java b/src/main/java/net/minecraft/util/thread/BlockableEventLoop.java
|
|
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
|
|
--- a/src/main/java/net/minecraft/util/thread/BlockableEventLoop.java
|
|
+++ b/src/main/java/net/minecraft/util/thread/BlockableEventLoop.java
|
|
@@ -0,0 +0,0 @@ public abstract class BlockableEventLoop<R extends Runnable> implements Profiler
|
|
runnable.run();
|
|
}
|
|
}
|
|
+ // Paper start
|
|
+ public void scheduleOnMain(Runnable runnable) {
|
|
+ // 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.tell(this.wrapRunnable(runnable));
|
|
+ }
|
|
+ // Paper end
|
|
|
|
@Override
|
|
public void tell(R runnable) {
|
|
diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java
|
|
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
|
|
--- a/src/main/java/net/minecraft/world/entity/Entity.java
|
|
+++ b/src/main/java/net/minecraft/world/entity/Entity.java
|
|
@@ -0,0 +0,0 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
|
|
return this.level.hasChunk((int) Math.floor(this.getX()) >> 4, (int) Math.floor(this.getZ()) >> 4);
|
|
}
|
|
// CraftBukkit end
|
|
+ // Paper start
|
|
+ public final AABB getBoundingBoxAt(double x, double y, double z) {
|
|
+ return this.dimensions.makeBoundingBox(x, y, z);
|
|
+ }
|
|
+ // Paper end
|
|
|
|
public Entity(EntityType<?> type, Level world) {
|
|
this.id = Entity.ENTITY_COUNTER.incrementAndGet();
|
|
diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java
|
|
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
|
|
--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java
|
|
+++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java
|
|
@@ -0,0 +0,0 @@ public abstract class LivingEntity extends Entity implements Attackable {
|
|
public boolean collides = true;
|
|
public Set<UUID> collidableExemptions = new HashSet<>();
|
|
public boolean bukkitPickUpLoot;
|
|
+ public org.bukkit.craftbukkit.entity.CraftLivingEntity getBukkitLivingEntity() { return (org.bukkit.craftbukkit.entity.CraftLivingEntity) super.getBukkitEntity(); } // Paper
|
|
|
|
@Override
|
|
public float getBukkitYaw() {
|
|
diff --git a/src/main/java/net/minecraft/world/item/ItemStack.java b/src/main/java/net/minecraft/world/item/ItemStack.java
|
|
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
|
|
--- a/src/main/java/net/minecraft/world/item/ItemStack.java
|
|
+++ b/src/main/java/net/minecraft/world/item/ItemStack.java
|
|
@@ -0,0 +0,0 @@ public final class ItemStack implements DataComponentHolder {
|
|
}
|
|
}
|
|
|
|
+ // 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.copy());
|
|
+ }
|
|
+ public static ItemStack fromBukkitCopy(org.bukkit.inventory.ItemStack itemstack) {
|
|
+ return CraftItemStack.asNMSCopy(itemstack);
|
|
+ }
|
|
+ private org.bukkit.craftbukkit.inventory.CraftItemStack bukkitStack;
|
|
+ public org.bukkit.inventory.ItemStack getBukkitStack() {
|
|
+ if (bukkitStack == null || bukkitStack.handle != this) {
|
|
+ bukkitStack = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(this);
|
|
+ }
|
|
+ return bukkitStack;
|
|
+ }
|
|
+ // Paper end
|
|
+
|
|
public void applyComponents(DataComponentPatch changes) {
|
|
this.components.applyPatch(changes);
|
|
this.getItem().verifyComponentsAfterLoad(this);
|
|
@@ -0,0 +0,0 @@ public final class ItemStack implements DataComponentHolder {
|
|
// CraftBukkit start
|
|
@Deprecated
|
|
public void setItem(Item item) {
|
|
+ this.bukkitStack = null; // Paper
|
|
this.item = item;
|
|
}
|
|
// CraftBukkit end
|
|
diff --git a/src/main/java/net/minecraft/world/level/BlockGetter.java b/src/main/java/net/minecraft/world/level/BlockGetter.java
|
|
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
|
|
--- a/src/main/java/net/minecraft/world/level/BlockGetter.java
|
|
+++ b/src/main/java/net/minecraft/world/level/BlockGetter.java
|
|
@@ -0,0 +0,0 @@ import javax.annotation.Nullable;
|
|
import net.minecraft.core.BlockPos;
|
|
import net.minecraft.core.Direction;
|
|
import net.minecraft.util.Mth;
|
|
+import net.minecraft.world.level.block.Block;
|
|
import net.minecraft.world.level.block.entity.BlockEntity;
|
|
import net.minecraft.world.level.block.entity.BlockEntityType;
|
|
import net.minecraft.world.level.block.state.BlockState;
|
|
@@ -0,0 +0,0 @@ public interface BlockGetter extends LevelHeightAccessor {
|
|
}
|
|
|
|
BlockState getBlockState(BlockPos pos);
|
|
+ // Paper start - if loaded util
|
|
+ @Nullable BlockState getBlockStateIfLoaded(BlockPos blockposition);
|
|
+
|
|
+ default @Nullable Block getBlockIfLoaded(BlockPos blockposition) {
|
|
+ BlockState type = this.getBlockStateIfLoaded(blockposition);
|
|
+ return type == null ? null : type.getBlock();
|
|
+ }
|
|
+ @Nullable FluidState getFluidIfLoaded(BlockPos blockposition);
|
|
+ // Paper end
|
|
|
|
FluidState getFluidState(BlockPos pos);
|
|
|
|
diff --git a/src/main/java/net/minecraft/world/level/ChunkPos.java b/src/main/java/net/minecraft/world/level/ChunkPos.java
|
|
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
|
|
--- a/src/main/java/net/minecraft/world/level/ChunkPos.java
|
|
+++ b/src/main/java/net/minecraft/world/level/ChunkPos.java
|
|
@@ -0,0 +0,0 @@ public class ChunkPos {
|
|
public static final int REGION_MAX_INDEX = 31;
|
|
public final int x;
|
|
public final int z;
|
|
+ public final long longKey; // Paper
|
|
private static final int HASH_A = 1664525;
|
|
private static final int HASH_C = 1013904223;
|
|
private static final int HASH_Z_XOR = -559038737;
|
|
@@ -0,0 +0,0 @@ public class ChunkPos {
|
|
public ChunkPos(int x, int z) {
|
|
this.x = x;
|
|
this.z = z;
|
|
+ this.longKey = asLong(this.x, this.z); // Paper
|
|
}
|
|
|
|
public ChunkPos(BlockPos pos) {
|
|
this.x = SectionPos.blockToSectionCoord(pos.getX());
|
|
this.z = SectionPos.blockToSectionCoord(pos.getZ());
|
|
+ this.longKey = asLong(this.x, this.z); // Paper
|
|
}
|
|
|
|
public ChunkPos(long pos) {
|
|
this.x = (int)pos;
|
|
this.z = (int)(pos >> 32);
|
|
+ this.longKey = asLong(this.x, this.z); // Paper
|
|
}
|
|
|
|
public static ChunkPos minFromRegion(int x, int z) {
|
|
@@ -0,0 +0,0 @@ public class ChunkPos {
|
|
}
|
|
|
|
public long toLong() {
|
|
- return asLong(this.x, this.z);
|
|
+ return longKey; // Paper
|
|
}
|
|
|
|
public static long asLong(int chunkX, int chunkZ) {
|
|
diff --git a/src/main/java/net/minecraft/world/level/EmptyBlockGetter.java b/src/main/java/net/minecraft/world/level/EmptyBlockGetter.java
|
|
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
|
|
--- a/src/main/java/net/minecraft/world/level/EmptyBlockGetter.java
|
|
+++ b/src/main/java/net/minecraft/world/level/EmptyBlockGetter.java
|
|
@@ -0,0 +0,0 @@ public enum EmptyBlockGetter implements BlockGetter {
|
|
return null;
|
|
}
|
|
|
|
+ // Paper start - If loaded util
|
|
+ @Override
|
|
+ public final FluidState getFluidIfLoaded(BlockPos blockposition) {
|
|
+ return Fluids.EMPTY.defaultFluidState();
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public final BlockState getBlockStateIfLoaded(BlockPos blockposition) {
|
|
+ return Blocks.AIR.defaultBlockState();
|
|
+ }
|
|
+ // Paper end
|
|
+
|
|
@Override
|
|
public BlockState getBlockState(BlockPos pos) {
|
|
return Blocks.AIR.defaultBlockState();
|
|
diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java
|
|
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
|
|
--- a/src/main/java/net/minecraft/world/level/Level.java
|
|
+++ b/src/main/java/net/minecraft/world/level/Level.java
|
|
@@ -0,0 +0,0 @@ import org.bukkit.craftbukkit.CraftServer;
|
|
import org.bukkit.craftbukkit.CraftWorld;
|
|
import org.bukkit.craftbukkit.SpigotTimings; // Spigot
|
|
import org.bukkit.craftbukkit.block.CapturedBlockState;
|
|
+import org.bukkit.craftbukkit.block.CraftBlockState;
|
|
import org.bukkit.craftbukkit.block.data.CraftBlockData;
|
|
import org.bukkit.craftbukkit.util.CraftSpawnCategory;
|
|
import org.bukkit.entity.SpawnCategory;
|
|
@@ -0,0 +0,0 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
|
|
return null;
|
|
}
|
|
|
|
+ // Paper start
|
|
+ public net.minecraft.world.phys.BlockHitResult.Type clipDirect(Vec3 start, Vec3 end, net.minecraft.world.phys.shapes.CollisionContext context) {
|
|
+ // To be patched over
|
|
+ return this.clip(new ClipContext(start, end, ClipContext.Block.COLLIDER, ClipContext.Fluid.NONE, context)).getType();
|
|
+ }
|
|
+ // Paper end
|
|
+
|
|
public boolean isInWorldBounds(BlockPos pos) {
|
|
return !this.isOutsideBuildHeight(pos) && Level.isInWorldBoundsHorizontal(pos);
|
|
}
|
|
@@ -0,0 +0,0 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
|
|
return y < -20000000 || y >= 20000000;
|
|
}
|
|
|
|
- public LevelChunk getChunkAt(BlockPos pos) {
|
|
+ public final LevelChunk getChunkAt(BlockPos pos) { // Paper - help inline
|
|
return this.getChunk(SectionPos.blockToSectionCoord(pos.getX()), SectionPos.blockToSectionCoord(pos.getZ()));
|
|
}
|
|
|
|
@Override
|
|
- public LevelChunk getChunk(int chunkX, int chunkZ) {
|
|
- return (LevelChunk) this.getChunk(chunkX, chunkZ, ChunkStatus.FULL);
|
|
+ public final LevelChunk getChunk(int chunkX, int chunkZ) { // Paper - final to help inline
|
|
+ return (LevelChunk) this.getChunk(chunkX, chunkZ, ChunkStatus.FULL, true); // Paper - avoid a method jump
|
|
}
|
|
|
|
+ // Paper start - if loaded
|
|
+ @Nullable
|
|
+ @Override
|
|
+ public final ChunkAccess getChunkIfLoadedImmediately(int x, int z) {
|
|
+ return ((ServerLevel)this).chunkSource.getChunkAtIfLoadedImmediately(x, z);
|
|
+ }
|
|
+
|
|
+ @Override
|
|
@Nullable
|
|
+ public final BlockState getBlockStateIfLoaded(BlockPos pos) {
|
|
+ // CraftBukkit start - tree generation
|
|
+ if (this.captureTreeGeneration) {
|
|
+ CraftBlockState previous = this.capturedBlockStates.get(pos);
|
|
+ if (previous != null) {
|
|
+ return previous.getHandle();
|
|
+ }
|
|
+ }
|
|
+ // CraftBukkit end
|
|
+ if (this.isOutsideBuildHeight(pos)) {
|
|
+ return Blocks.VOID_AIR.defaultBlockState();
|
|
+ } else {
|
|
+ ChunkAccess chunk = this.getChunkIfLoadedImmediately(pos.getX() >> 4, pos.getZ() >> 4);
|
|
+
|
|
+ return chunk == null ? null : chunk.getBlockState(pos);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public final FluidState getFluidIfLoaded(BlockPos blockposition) {
|
|
+ ChunkAccess chunk = this.getChunkIfLoadedImmediately(blockposition.getX() >> 4, blockposition.getZ() >> 4);
|
|
+
|
|
+ return chunk == null ? null : chunk.getFluidState(blockposition);
|
|
+ }
|
|
+
|
|
@Override
|
|
public ChunkAccess getChunk(int chunkX, int chunkZ, ChunkStatus leastStatus, boolean create) {
|
|
+ // Paper end
|
|
ChunkAccess ichunkaccess = this.getChunkSource().getChunk(chunkX, chunkZ, leastStatus, create);
|
|
|
|
if (ichunkaccess == null && create) {
|
|
@@ -0,0 +0,0 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
|
|
if (this.isOutsideBuildHeight(pos)) {
|
|
return Blocks.VOID_AIR.defaultBlockState();
|
|
} else {
|
|
- LevelChunk chunk = this.getChunk(SectionPos.blockToSectionCoord(pos.getX()), SectionPos.blockToSectionCoord(pos.getZ()));
|
|
+ ChunkAccess chunk = this.getChunk(pos.getX() >> 4, pos.getZ() >> 4, ChunkStatus.FULL, true); // Paper - manually inline to reduce hops and avoid unnecessary null check to reduce total byte code size, this should never return null and if it does we will see it the next line but the real stack trace will matter in the chunk engine
|
|
|
|
return chunk.getBlockState(pos);
|
|
}
|
|
diff --git a/src/main/java/net/minecraft/world/level/LevelReader.java b/src/main/java/net/minecraft/world/level/LevelReader.java
|
|
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
|
|
--- a/src/main/java/net/minecraft/world/level/LevelReader.java
|
|
+++ b/src/main/java/net/minecraft/world/level/LevelReader.java
|
|
@@ -0,0 +0,0 @@ public interface LevelReader extends BlockAndTintGetter, CollisionGetter, Signal
|
|
@Nullable
|
|
ChunkAccess getChunk(int chunkX, int chunkZ, ChunkStatus leastStatus, boolean create);
|
|
|
|
+ @Nullable ChunkAccess getChunkIfLoadedImmediately(int x, int z); // Paper - ifLoaded api (we need this since current impl blocks if the chunk is loading)
|
|
+ @Nullable default ChunkAccess getChunkIfLoadedImmediately(BlockPos pos) { return this.getChunkIfLoadedImmediately(pos.getX() >> 4, pos.getZ() >> 4);}
|
|
+
|
|
@Deprecated
|
|
boolean hasChunk(int chunkX, int chunkZ);
|
|
|
|
diff --git a/src/main/java/net/minecraft/world/level/PathNavigationRegion.java b/src/main/java/net/minecraft/world/level/PathNavigationRegion.java
|
|
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
|
|
--- a/src/main/java/net/minecraft/world/level/PathNavigationRegion.java
|
|
+++ b/src/main/java/net/minecraft/world/level/PathNavigationRegion.java
|
|
@@ -0,0 +0,0 @@ import net.minecraft.core.Holder;
|
|
import net.minecraft.core.SectionPos;
|
|
import net.minecraft.core.registries.Registries;
|
|
import net.minecraft.util.profiling.ProfilerFiller;
|
|
+import net.minecraft.server.level.ServerLevel;
|
|
import net.minecraft.world.entity.Entity;
|
|
import net.minecraft.world.level.biome.Biome;
|
|
import net.minecraft.world.level.biome.Biomes;
|
|
@@ -0,0 +0,0 @@ public class PathNavigationRegion implements BlockGetter, CollisionGetter {
|
|
private ChunkAccess getChunk(int chunkX, int chunkZ) {
|
|
int i = chunkX - this.centerX;
|
|
int j = chunkZ - this.centerZ;
|
|
- if (i >= 0 && i < this.chunks.length && j >= 0 && j < this.chunks[i].length) {
|
|
+ if (i >= 0 && i < this.chunks.length && j >= 0 && j < this.chunks[i].length) { // Paper - if this changes, update getChunkIfLoaded below
|
|
ChunkAccess chunkAccess = this.chunks[i][j];
|
|
return (ChunkAccess)(chunkAccess != null ? chunkAccess : new EmptyLevelChunk(this.level, new ChunkPos(chunkX, chunkZ), this.plains.get()));
|
|
} else {
|
|
@@ -0,0 +0,0 @@ public class PathNavigationRegion implements BlockGetter, CollisionGetter {
|
|
}
|
|
}
|
|
|
|
+ // Paper start - if loaded util
|
|
+ private @Nullable ChunkAccess getChunkIfLoaded(int x, int z) {
|
|
+ // Based on getChunk(int, int)
|
|
+ int xx = x - this.centerX;
|
|
+ int zz = z - this.centerZ;
|
|
+
|
|
+ if (xx >= 0 && xx < this.chunks.length && zz >= 0 && zz < this.chunks[xx].length) {
|
|
+ return this.chunks[xx][zz];
|
|
+ }
|
|
+ return null;
|
|
+ }
|
|
+ @Override
|
|
+ public final FluidState getFluidIfLoaded(BlockPos blockposition) {
|
|
+ ChunkAccess chunk = getChunkIfLoaded(blockposition.getX() >> 4, blockposition.getZ() >> 4);
|
|
+ return chunk == null ? null : chunk.getFluidState(blockposition);
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public final BlockState getBlockStateIfLoaded(BlockPos blockposition) {
|
|
+ ChunkAccess chunk = getChunkIfLoaded(blockposition.getX() >> 4, blockposition.getZ() >> 4);
|
|
+ return chunk == null ? null : chunk.getBlockState(blockposition);
|
|
+ }
|
|
+ // Paper end
|
|
+
|
|
@Override
|
|
public WorldBorder getWorldBorder() {
|
|
return this.level.getWorldBorder();
|
|
diff --git a/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java b/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java
|
|
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
|
|
--- a/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java
|
|
+++ b/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java
|
|
@@ -0,0 +0,0 @@ public abstract class BlockBehaviour implements FeatureElement {
|
|
}
|
|
}
|
|
|
|
+ protected boolean shapeExceedsCube = true; // Paper - moved from actual method to here
|
|
public void initCache() {
|
|
this.fluidState = ((Block) this.owner).getFluidState(this.asState());
|
|
this.isRandomlyTicking = ((Block) this.owner).isRandomlyTicking(this.asState());
|
|
if (!this.getBlock().hasDynamicShape()) {
|
|
this.cache = new BlockBehaviour.BlockStateBase.Cache(this.asState());
|
|
}
|
|
+ this.shapeExceedsCube = this.cache == null || this.cache.largeCollisionShape; // Paper - moved from actual method to here
|
|
|
|
this.legacySolid = this.calculateSolid();
|
|
}
|
|
@@ -0,0 +0,0 @@ public abstract class BlockBehaviour implements FeatureElement {
|
|
return this.getBlock().getOcclusionShape(this.asState(), world, pos);
|
|
}
|
|
|
|
- public boolean hasLargeCollisionShape() {
|
|
- return this.cache == null || this.cache.largeCollisionShape;
|
|
+ public final boolean hasLargeCollisionShape() { // Paper
|
|
+ return this.shapeExceedsCube; // Paper - moved into shape cache init
|
|
}
|
|
|
|
public boolean useShapeForLightOcclusion() {
|
|
diff --git a/src/main/java/net/minecraft/world/level/chunk/ChunkAccess.java b/src/main/java/net/minecraft/world/level/chunk/ChunkAccess.java
|
|
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
|
|
--- a/src/main/java/net/minecraft/world/level/chunk/ChunkAccess.java
|
|
+++ b/src/main/java/net/minecraft/world/level/chunk/ChunkAccess.java
|
|
@@ -0,0 +0,0 @@ public abstract class ChunkAccess implements BlockGetter, BiomeManager.NoiseBiom
|
|
protected final ShortList[] postProcessing;
|
|
protected volatile boolean unsaved;
|
|
private volatile boolean isLightCorrect;
|
|
- protected final ChunkPos chunkPos;
|
|
+ protected final ChunkPos chunkPos; public final long coordinateKey; public final int locX; public final int locZ; // Paper - cache coordinate key
|
|
private long inhabitedTime;
|
|
/** @deprecated */
|
|
@Nullable
|
|
@@ -0,0 +0,0 @@ public abstract class ChunkAccess implements BlockGetter, BiomeManager.NoiseBiom
|
|
// CraftBukkit end
|
|
|
|
public ChunkAccess(ChunkPos pos, UpgradeData upgradeData, LevelHeightAccessor heightLimitView, Registry<Biome> biomeRegistry, long inhabitedTime, @Nullable LevelChunkSection[] sectionArray, @Nullable BlendingData blendingData) {
|
|
- this.chunkPos = pos;
|
|
+ this.locX = pos.x; this.locZ = pos.z; // Paper - reduce need for field lookups
|
|
+ this.chunkPos = pos; this.coordinateKey = ChunkPos.asLong(locX, locZ); // Paper - cache long key
|
|
this.upgradeData = upgradeData;
|
|
this.levelHeightAccessor = heightLimitView;
|
|
this.sections = new LevelChunkSection[heightLimitView.getSectionsCount()];
|
|
diff --git a/src/main/java/net/minecraft/world/level/chunk/EmptyLevelChunk.java b/src/main/java/net/minecraft/world/level/chunk/EmptyLevelChunk.java
|
|
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
|
|
--- a/src/main/java/net/minecraft/world/level/chunk/EmptyLevelChunk.java
|
|
+++ b/src/main/java/net/minecraft/world/level/chunk/EmptyLevelChunk.java
|
|
@@ -0,0 +0,0 @@ public class EmptyLevelChunk extends LevelChunk {
|
|
public BlockState getBlockState(BlockPos pos) {
|
|
return Blocks.VOID_AIR.defaultBlockState();
|
|
}
|
|
+ // Paper start
|
|
+ @Override
|
|
+ public BlockState getBlockState(final int x, final int y, final int z) {
|
|
+ return Blocks.VOID_AIR.defaultBlockState();
|
|
+ }
|
|
+ // Paper end
|
|
|
|
@Nullable
|
|
@Override
|
|
diff --git a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java
|
|
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
|
|
--- a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java
|
|
+++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java
|
|
@@ -0,0 +0,0 @@ public class LevelChunk extends ChunkAccess {
|
|
public boolean needsDecoration;
|
|
// CraftBukkit end
|
|
|
|
+ // Paper start
|
|
+ boolean loadedTicketLevel;
|
|
+ // Paper end
|
|
+
|
|
public LevelChunk(ServerLevel world, ProtoChunk protoChunk, @Nullable LevelChunk.PostLoadProcessor entityLoader) {
|
|
this(world, protoChunk.getPos(), protoChunk.getUpgradeData(), protoChunk.unpackBlockTicks(), protoChunk.unpackFluidTicks(), protoChunk.getInhabitedTime(), protoChunk.getSections(), entityLoader, protoChunk.getBlendingData());
|
|
Iterator iterator = protoChunk.getBlockEntities().values().iterator();
|
|
@@ -0,0 +0,0 @@ public class LevelChunk extends ChunkAccess {
|
|
}
|
|
}
|
|
|
|
+ // Paper start - Perf: Reduce instructions and provide final method
|
|
+ public BlockState getBlockState(final int x, final int y, final int z) {
|
|
+ return this.getBlockStateFinal(x, y, z);
|
|
+ }
|
|
+ public BlockState getBlockStateFinal(final int x, final int y, final int z) {
|
|
+ // Copied and modified from below
|
|
+ final int sectionIndex = this.getSectionIndex(y);
|
|
+ if (sectionIndex < 0 || sectionIndex >= this.sections.length
|
|
+ || this.sections[sectionIndex].nonEmptyBlockCount == 0) {
|
|
+ return Blocks.AIR.defaultBlockState();
|
|
+ }
|
|
+ return this.sections[sectionIndex].states.get((y & 15) << 8 | (z & 15) << 4 | x & 15);
|
|
+ }
|
|
@Override
|
|
public BlockState getBlockState(BlockPos pos) {
|
|
+ if (true) {
|
|
+ return this.getBlockStateFinal(pos.getX(), pos.getY(), pos.getZ());
|
|
+ }
|
|
+ // Paper end - Perf: Reduce instructions and provide final method
|
|
int i = pos.getX();
|
|
int j = pos.getY();
|
|
int k = pos.getZ();
|
|
@@ -0,0 +0,0 @@ public class LevelChunk extends ChunkAccess {
|
|
}
|
|
}
|
|
|
|
+ // Paper start - If loaded util
|
|
+ @Override
|
|
+ public final FluidState getFluidIfLoaded(BlockPos blockposition) {
|
|
+ return this.getFluidState(blockposition);
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public final BlockState getBlockStateIfLoaded(BlockPos blockposition) {
|
|
+ return this.getBlockState(blockposition);
|
|
+ }
|
|
+ // Paper end
|
|
+
|
|
@Override
|
|
public FluidState getFluidState(BlockPos pos) {
|
|
return this.getFluidState(pos.getX(), pos.getY(), pos.getZ());
|
|
@@ -0,0 +0,0 @@ public class LevelChunk extends ChunkAccess {
|
|
|
|
// CraftBukkit start
|
|
public void loadCallback() {
|
|
+ // Paper start
|
|
+ this.loadedTicketLevel = true;
|
|
+ // Paper end
|
|
org.bukkit.Server server = this.level.getCraftServer();
|
|
+ this.level.getChunkSource().addLoadedChunk(this); // Paper
|
|
if (server != null) {
|
|
/*
|
|
* If it's a new world, the first few chunks are generated inside
|
|
@@ -0,0 +0,0 @@ public class LevelChunk extends ChunkAccess {
|
|
server.getPluginManager().callEvent(unloadEvent);
|
|
// note: saving can be prevented, but not forced if no saving is actually required
|
|
this.mustNotSave = !unloadEvent.isSaveChunk();
|
|
+ this.level.getChunkSource().removeLoadedChunk(this); // Paper
|
|
+ // Paper start
|
|
+ this.loadedTicketLevel = false;
|
|
+ // Paper end
|
|
}
|
|
|
|
@Override
|
|
diff --git a/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java
|
|
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
|
|
--- a/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java
|
|
+++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java
|
|
@@ -0,0 +0,0 @@ public class LevelChunkSection {
|
|
public static final int SECTION_HEIGHT = 16;
|
|
public static final int SECTION_SIZE = 4096;
|
|
public static final int BIOME_CONTAINER_BITS = 2;
|
|
- private short nonEmptyBlockCount;
|
|
+ short nonEmptyBlockCount; // Paper - package private
|
|
private short tickingBlockCount;
|
|
private short tickingFluidCount;
|
|
public final PalettedContainer<BlockState> states;
|
|
diff --git a/src/main/java/net/minecraft/world/level/chunk/ProtoChunk.java b/src/main/java/net/minecraft/world/level/chunk/ProtoChunk.java
|
|
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
|
|
--- a/src/main/java/net/minecraft/world/level/chunk/ProtoChunk.java
|
|
+++ b/src/main/java/net/minecraft/world/level/chunk/ProtoChunk.java
|
|
@@ -0,0 +0,0 @@ public class ProtoChunk extends ChunkAccess {
|
|
return new ChunkAccess.TicksToSave(this.blockTicks, this.fluidTicks);
|
|
}
|
|
|
|
+ // Paper start - If loaded util
|
|
+ @Override
|
|
+ public final FluidState getFluidIfLoaded(BlockPos blockposition) {
|
|
+ return this.getFluidState(blockposition);
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public final BlockState getBlockStateIfLoaded(BlockPos blockposition) {
|
|
+ return this.getBlockState(blockposition);
|
|
+ }
|
|
+ // Paper end
|
|
+
|
|
@Override
|
|
public BlockState getBlockState(BlockPos pos) {
|
|
int i = pos.getY();
|
|
diff --git a/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java b/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java
|
|
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
|
|
--- a/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java
|
|
+++ b/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java
|
|
@@ -0,0 +0,0 @@ public class PersistentEntitySectionManager<T extends EntityAccess> implements A
|
|
}
|
|
|
|
private boolean addEntity(T entity, boolean existing) {
|
|
+ // Paper start - chunk system hooks
|
|
+ if (existing) {
|
|
+ // I don't want to know why this is a generic type.
|
|
+ Entity entityCasted = (Entity)entity;
|
|
+ boolean wasRemoved = entityCasted.isRemoved();
|
|
+ boolean screened = ca.spottedleaf.moonrise.common.util.ChunkSystem.screenEntity((net.minecraft.server.level.ServerLevel)entityCasted.level(), entityCasted);
|
|
+ if ((!wasRemoved && entityCasted.isRemoved()) || !screened) {
|
|
+ // removed by callback
|
|
+ return false;
|
|
+ }
|
|
+ }
|
|
+ // Paper end - chunk system hooks
|
|
if (!this.addEntityUuid(entity)) {
|
|
return false;
|
|
} else {
|
|
diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
|
|
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
|
|
--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java
|
|
+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
|
|
@@ -0,0 +0,0 @@ public final class CraftServer implements Server {
|
|
return this.spigot;
|
|
}
|
|
// Spigot end
|
|
+
|
|
+ @Override
|
|
+ public double[] getTPS() {
|
|
+ return new double[]{0, 0, 0}; // TODO
|
|
+ }
|
|
}
|
|
diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
|
|
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
|
|
--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
|
|
+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
|
|
@@ -0,0 +0,0 @@ public class CraftWorld extends CraftRegionAccessor implements World {
|
|
|
|
@Override
|
|
public Chunk[] getLoadedChunks() {
|
|
- Long2ObjectLinkedOpenHashMap<ChunkHolder> chunks = this.world.getChunkSource().chunkMap.visibleChunkMap;
|
|
- return chunks.values().stream().map(ChunkHolder::getFullChunkNow).filter(Objects::nonNull).map(CraftChunk::new).toArray(Chunk[]::new);
|
|
+ List<ChunkHolder> chunks = ca.spottedleaf.moonrise.common.util.ChunkSystem.getVisibleChunkHolders(this.world); // Paper
|
|
+ return chunks.stream().map(ChunkHolder::getFullChunkNow).filter(Objects::nonNull).map(CraftChunk::new).toArray(Chunk[]::new);
|
|
}
|
|
|
|
@Override
|
|
@@ -0,0 +0,0 @@ public class CraftWorld extends CraftRegionAccessor implements World {
|
|
|
|
@Override
|
|
public boolean refreshChunk(int x, int z) {
|
|
- ChunkHolder playerChunk = this.world.getChunkSource().chunkMap.visibleChunkMap.get(ChunkPos.asLong(x, z));
|
|
+ ChunkHolder playerChunk = this.world.getChunkSource().chunkMap.getVisibleChunkIfPresent(ChunkPos.asLong(x, z));
|
|
if (playerChunk == null) return false;
|
|
|
|
playerChunk.getTickingChunkFuture().thenAccept(either -> {
|
|
@@ -0,0 +0,0 @@ public class CraftWorld extends CraftRegionAccessor implements World {
|
|
return this.spigot;
|
|
}
|
|
// Spigot end
|
|
+ // Paper start
|
|
+ public java.util.concurrent.CompletableFuture<Chunk> getChunkAtAsync(int x, int z, boolean gen, boolean urgent) {
|
|
+ if (Bukkit.isPrimaryThread()) {
|
|
+ net.minecraft.world.level.chunk.LevelChunk immediate = this.world.getChunkSource().getChunkAtIfLoadedImmediately(x, z);
|
|
+ if (immediate != null) {
|
|
+ return java.util.concurrent.CompletableFuture.completedFuture(new CraftChunk(immediate));
|
|
+ }
|
|
+ }
|
|
+
|
|
+ ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor.Priority priority;
|
|
+ if (urgent) {
|
|
+ priority = ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor.Priority.HIGHER;
|
|
+ } else {
|
|
+ priority = ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor.Priority.NORMAL;
|
|
+ }
|
|
+
|
|
+ java.util.concurrent.CompletableFuture<Chunk> ret = new java.util.concurrent.CompletableFuture<>();
|
|
+
|
|
+ ca.spottedleaf.moonrise.common.util.ChunkSystem.scheduleChunkLoad(this.getHandle(), x, z, gen, ChunkStatus.FULL, true, priority, (c) -> {
|
|
+ net.minecraft.server.MinecraftServer.getServer().scheduleOnMain(() -> {
|
|
+ net.minecraft.world.level.chunk.LevelChunk chunk = (net.minecraft.world.level.chunk.LevelChunk)c;
|
|
+ ret.complete(chunk == null ? null : new CraftChunk(chunk));
|
|
+ });
|
|
+ });
|
|
+
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public void setViewDistance(final int viewDistance) {
|
|
+ if (viewDistance < 2 || viewDistance > 32) {
|
|
+ throw new IllegalArgumentException("View distance " + viewDistance + " is out of range of [2, 32]");
|
|
+ }
|
|
+ this.getHandle().chunkSource.chunkMap.setServerViewDistance(viewDistance);
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public void setSimulationDistance(final int simulationDistance) {
|
|
+ throw new UnsupportedOperationException("Not implemented yet");
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public int getSendViewDistance() {
|
|
+ return this.getViewDistance();
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public void setSendViewDistance(final int viewDistance) {
|
|
+ throw new UnsupportedOperationException("Not implemented yet");
|
|
+ }
|
|
+ // Paper end
|
|
}
|
|
diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java
|
|
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
|
|
--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java
|
|
+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java
|
|
@@ -0,0 +0,0 @@ public class CraftPlayer extends CraftHumanEntity implements Player {
|
|
return this.spigot;
|
|
}
|
|
// Spigot end
|
|
+
|
|
+ @Override
|
|
+ public int getViewDistance() {
|
|
+ return ca.spottedleaf.moonrise.common.util.ChunkSystem.getLoadViewDistance(this.getHandle()) - 1;
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public void setViewDistance(final int viewDistance) {
|
|
+ throw new UnsupportedOperationException("Not implemented yet");
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public int getSimulationDistance() {
|
|
+ return ca.spottedleaf.moonrise.common.util.ChunkSystem.getTickViewDistance(this.getHandle());
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public void setSimulationDistance(final int simulationDistance) {
|
|
+ throw new UnsupportedOperationException("Not implemented yet");
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public int getSendViewDistance() {
|
|
+ return ca.spottedleaf.moonrise.common.util.ChunkSystem.getSendViewDistance(this.getHandle());
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public void setSendViewDistance(final int viewDistance) {
|
|
+ throw new UnsupportedOperationException("Not implemented yet");
|
|
+ }
|
|
}
|
|
diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java
|
|
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
|
|
--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java
|
|
+++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java
|
|
@@ -0,0 +0,0 @@ import org.jetbrains.annotations.ApiStatus;
|
|
@DelegateDeserialization(ItemStack.class)
|
|
public final class CraftItemStack extends ItemStack {
|
|
|
|
+ // Paper start - MC Utils
|
|
+ public static net.minecraft.world.item.ItemStack unwrap(ItemStack bukkit) {
|
|
+ if (bukkit instanceof CraftItemStack craftItemStack) {
|
|
+ return craftItemStack.handle != null ? craftItemStack.handle : net.minecraft.world.item.ItemStack.EMPTY;
|
|
+ } else {
|
|
+ return asNMSCopy(bukkit);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public static net.minecraft.world.item.ItemStack getOrCloneOnMutation(ItemStack old, ItemStack newInstance) {
|
|
+ return old == newInstance ? unwrap(old) : asNMSCopy(newInstance);
|
|
+ }
|
|
+ // Paper end - MC Utils
|
|
+
|
|
public static net.minecraft.world.item.ItemStack asNMSCopy(ItemStack original) {
|
|
if (original instanceof CraftItemStack) {
|
|
CraftItemStack stack = (CraftItemStack) original;
|
|
diff --git a/src/main/java/org/bukkit/craftbukkit/util/DelegatedGeneratorAccess.java b/src/main/java/org/bukkit/craftbukkit/util/DelegatedGeneratorAccess.java
|
|
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
|
|
--- a/src/main/java/org/bukkit/craftbukkit/util/DelegatedGeneratorAccess.java
|
|
+++ b/src/main/java/org/bukkit/craftbukkit/util/DelegatedGeneratorAccess.java
|
|
@@ -0,0 +0,0 @@ import net.minecraft.world.phys.shapes.VoxelShape;
|
|
import net.minecraft.world.ticks.LevelTickAccess;
|
|
import net.minecraft.world.ticks.TickPriority;
|
|
import org.bukkit.event.entity.CreatureSpawnEvent;
|
|
+import org.jetbrains.annotations.Nullable;
|
|
|
|
public abstract class DelegatedGeneratorAccess implements WorldGenLevel {
|
|
|
|
@@ -0,0 +0,0 @@ public abstract class DelegatedGeneratorAccess implements WorldGenLevel {
|
|
public int getMoonPhase() {
|
|
return this.handle.getMoonPhase();
|
|
}
|
|
+
|
|
+ // Paper start
|
|
+ @Nullable
|
|
+ @Override
|
|
+ public BlockState getBlockStateIfLoaded(final BlockPos blockposition) {
|
|
+ return this.handle.getBlockStateIfLoaded(blockposition);
|
|
+ }
|
|
+
|
|
+ @Nullable
|
|
+ @Override
|
|
+ public FluidState getFluidIfLoaded(final BlockPos blockposition) {
|
|
+ return this.handle.getFluidIfLoaded(blockposition);
|
|
+ }
|
|
+
|
|
+ @Nullable
|
|
+ @Override
|
|
+ public ChunkAccess getChunkIfLoadedImmediately(final int x, final int z) {
|
|
+ return this.handle.getChunkIfLoadedImmediately(x, z);
|
|
+ }
|
|
+ // Paper end
|
|
}
|
|
diff --git a/src/main/java/org/bukkit/craftbukkit/util/DummyGeneratorAccess.java b/src/main/java/org/bukkit/craftbukkit/util/DummyGeneratorAccess.java
|
|
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
|
|
--- a/src/main/java/org/bukkit/craftbukkit/util/DummyGeneratorAccess.java
|
|
+++ b/src/main/java/org/bukkit/craftbukkit/util/DummyGeneratorAccess.java
|
|
@@ -0,0 +0,0 @@ public class DummyGeneratorAccess implements WorldGenLevel {
|
|
public FluidState getFluidState(BlockPos pos) {
|
|
return Fluids.EMPTY.defaultFluidState(); // SPIGOT-6634
|
|
}
|
|
+ // Paper start - if loaded util
|
|
+ @javax.annotation.Nullable
|
|
+ @Override
|
|
+ public ChunkAccess getChunkIfLoadedImmediately(int x, int z) {
|
|
+ throw new UnsupportedOperationException("Not supported yet.");
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public BlockState getBlockStateIfLoaded(BlockPos blockposition) {
|
|
+ throw new UnsupportedOperationException("Not supported yet.");
|
|
+ }
|
|
|
|
+ @Override
|
|
+ public FluidState getFluidIfLoaded(BlockPos blockposition) {
|
|
+ throw new UnsupportedOperationException("Not supported yet.");
|
|
+ }
|
|
+ // Paper end
|
|
@Override
|
|
public WorldBorder getWorldBorder() {
|
|
throw new UnsupportedOperationException("Not supported yet.");
|
|
diff --git a/src/main/java/org/spigotmc/ActivationRange.java b/src/main/java/org/spigotmc/ActivationRange.java
|
|
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
|
|
--- a/src/main/java/org/spigotmc/ActivationRange.java
|
|
+++ b/src/main/java/org/spigotmc/ActivationRange.java
|
|
@@ -0,0 +0,0 @@ public class ActivationRange
|
|
|
|
public enum ActivationType
|
|
{
|
|
+ WATER, // Paper
|
|
+ FLYING_MONSTER, // Paper
|
|
+ VILLAGER, // Paper
|
|
MONSTER,
|
|
ANIMAL,
|
|
RAIDER,
|