== 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
This commit is contained in:
Aikar
2016-03-28 20:55:47 -04:00
parent a82a09d198
commit b01c811c2f
81 changed files with 6261 additions and 473 deletions

View File

@@ -0,0 +1,117 @@
package ca.spottedleaf.moonrise.common;
import com.mojang.datafixers.DSL;
import com.mojang.datafixers.DataFixer;
import net.minecraft.core.BlockPos;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.server.level.ChunkHolder;
import net.minecraft.server.level.GenerationChunkHolder;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.chunk.ChunkAccess;
import net.minecraft.world.level.chunk.LevelChunk;
import net.minecraft.world.level.chunk.ProtoChunk;
import net.minecraft.world.level.chunk.storage.SerializableChunkData;
import net.minecraft.world.level.entity.EntityTypeTest;
import net.minecraft.world.phys.AABB;
import java.util.List;
import java.util.ServiceLoader;
import java.util.function.Predicate;
public interface PlatformHooks {
public static PlatformHooks get() {
return Holder.INSTANCE;
}
public String getBrand();
public int getLightEmission(final BlockState blockState, final BlockGetter world, final BlockPos pos);
public Predicate<BlockState> maybeHasLightEmission();
public boolean hasCurrentlyLoadingChunk();
public LevelChunk getCurrentlyLoadingChunk(final GenerationChunkHolder holder);
public void setCurrentlyLoading(final GenerationChunkHolder holder, final LevelChunk levelChunk);
public void chunkFullStatusComplete(final LevelChunk newChunk, final ProtoChunk original);
public boolean allowAsyncTicketUpdates();
public void onChunkHolderTicketChange(final ServerLevel world, final ChunkHolder holder, final int oldLevel, final int newLevel);
public void chunkUnloadFromWorld(final LevelChunk chunk);
public void chunkSyncSave(final ServerLevel world, final ChunkAccess chunk, final SerializableChunkData data);
public void onChunkWatch(final ServerLevel world, final LevelChunk chunk, final ServerPlayer player);
public void onChunkUnWatch(final ServerLevel world, final ChunkPos chunk, final ServerPlayer player);
public void addToGetEntities(final Level world, final Entity entity, final AABB boundingBox, final Predicate<? super Entity> predicate,
final List<Entity> into);
public <T extends Entity> void addToGetEntities(final Level world, final EntityTypeTest<Entity, T> entityTypeTest,
final AABB boundingBox, final Predicate<? super T> predicate,
final List<? super T> into, final int maxCount);
public void entityMove(final Entity entity, final long oldSection, final long newSection);
public boolean screenEntity(final ServerLevel world, final Entity entity, final boolean fromDisk, final boolean event);
public boolean configFixMC224294();
public boolean configAutoConfigSendDistance();
public double configPlayerMaxLoadRate();
public double configPlayerMaxGenRate();
public double configPlayerMaxSendRate();
public int configPlayerMaxConcurrentLoads();
public int configPlayerMaxConcurrentGens();
public long configAutoSaveInterval(final ServerLevel world);
public int configMaxAutoSavePerTick(final ServerLevel world);
public boolean configFixMC159283();
// support for CB chunk mustNotSave
public boolean forceNoSave(final ChunkAccess chunk);
public CompoundTag convertNBT(final DSL.TypeReference type, final DataFixer dataFixer, final CompoundTag nbt,
final int fromVersion, final int toVersion);
public boolean hasMainChunkLoadHook();
public void mainChunkLoad(final ChunkAccess chunk, final SerializableChunkData chunkData);
public List<Entity> modifySavedEntities(final ServerLevel world, final int chunkX, final int chunkZ, final List<Entity> entities);
public void unloadEntity(final Entity entity);
public void postLoadProtoChunk(final ServerLevel world, final ProtoChunk chunk);
public int modifyEntityTrackingRange(final Entity entity, final int currentRange);
public static final class Holder {
private Holder() {
}
private static final PlatformHooks INSTANCE;
static {
INSTANCE = ServiceLoader.load(PlatformHooks.class, PlatformHooks.class.getClassLoader()).findFirst()
.orElseThrow(() -> new RuntimeException("Failed to locate PlatformHooks"));
}
}
}

View File

@@ -0,0 +1,128 @@
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> {
private final Int2IntOpenHashMap entityToIndex = new Int2IntOpenHashMap(2, 0.8f);
{
this.entityToIndex.defaultReturnValue(Integer.MIN_VALUE);
}
private static final Entity[] EMPTY_LIST = new Entity[0];
private Entity[] entities = EMPTY_LIST;
private 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<>() {
private Entity lastRet;
private 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;
}
};
}
}

View File

@@ -0,0 +1,77 @@
package ca.spottedleaf.moonrise.common.list;
import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
import java.util.Arrays;
public final class IntList {
private final Int2IntOpenHashMap map = new Int2IntOpenHashMap();
{
this.map.defaultReturnValue(Integer.MIN_VALUE);
}
private static final int[] EMPTY_LIST = new int[0];
private int[] byIndex = EMPTY_LIST;
private int count;
public int size() {
return this.count;
}
public void setMinCapacity(final int len) {
final int[] byIndex = this.byIndex;
if (byIndex.length < len) {
this.byIndex = Arrays.copyOf(byIndex, len);
}
}
public int getRaw(final int index) {
return this.byIndex[index];
}
public boolean add(final int value) {
final int count = this.count;
final int currIndex = this.map.putIfAbsent(value, count);
if (currIndex != Integer.MIN_VALUE) {
return false; // already in this list
}
int[] list = this.byIndex;
if (list.length == count) {
// resize required
list = this.byIndex = Arrays.copyOf(list, (int)Math.max(4L, count * 2L)); // overflow results in negative
}
list[count] = value;
this.count = count + 1;
return true;
}
public boolean remove(final int value) {
final int index = this.map.remove(value);
if (index == Integer.MIN_VALUE) {
return false;
}
// move the entry at the end to this index
final int endIndex = --this.count;
final int end = this.byIndex[endIndex];
if (index != endIndex) {
// not empty after this call
this.map.put(end, index);
}
this.byIndex[index] = end;
this.byIndex[endIndex] = 0;
return true;
}
public void clear() {
this.count = 0;
this.map.clear();
}
}

View File

@@ -0,0 +1,312 @@
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();
}
}
}

View File

@@ -0,0 +1,142 @@
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;
}
};
}
}

View File

@@ -0,0 +1,77 @@
package ca.spottedleaf.moonrise.common.list;
import it.unimi.dsi.fastutil.shorts.Short2ShortOpenHashMap;
import java.util.Arrays;
public final class ShortList {
private final Short2ShortOpenHashMap map = new Short2ShortOpenHashMap();
{
this.map.defaultReturnValue(Short.MIN_VALUE);
}
private static final short[] EMPTY_LIST = new short[0];
private short[] byIndex = EMPTY_LIST;
private short count;
public int size() {
return (int)this.count;
}
public short getRaw(final int index) {
return this.byIndex[index];
}
public void setMinCapacity(final int len) {
final short[] byIndex = this.byIndex;
if (byIndex.length < len) {
this.byIndex = Arrays.copyOf(byIndex, len);
}
}
public boolean add(final short value) {
final int count = (int)this.count;
final short currIndex = this.map.putIfAbsent(value, (short)count);
if (currIndex != Short.MIN_VALUE) {
return false; // already in this list
}
short[] list = this.byIndex;
if (list.length == count) {
// resize required
list = this.byIndex = Arrays.copyOf(list, (int)Math.max(4L, count * 2L)); // overflow results in negative
}
list[count] = value;
this.count = (short)(count + 1);
return true;
}
public boolean remove(final short value) {
final short index = this.map.remove(value);
if (index == Short.MIN_VALUE) {
return false;
}
// move the entry at the end to this index
final short endIndex = --this.count;
final short end = this.byIndex[endIndex];
if (index != endIndex) {
// not empty after this call
this.map.put(end, index);
}
this.byIndex[(int)index] = end;
this.byIndex[(int)endIndex] = (short)0;
return true;
}
public void clear() {
this.count = (short)0;
this.map.clear();
}
}

View File

@@ -0,0 +1,117 @@
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;
}
}

View File

@@ -0,0 +1,77 @@
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];
}
}

View File

@@ -0,0 +1,74 @@
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];
}
}

View File

@@ -0,0 +1,77 @@
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];
}
}

View File

@@ -0,0 +1,76 @@
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];
}
}

View File

@@ -0,0 +1,48 @@
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;
}
}
}

View File

@@ -0,0 +1,47 @@
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;
}
}
}

View File

@@ -0,0 +1,75 @@
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;
}
}

View File

@@ -0,0 +1,297 @@
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();
}
}

View File

@@ -0,0 +1,718 @@
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;
}
}
}

View File

@@ -0,0 +1,22 @@
package ca.spottedleaf.moonrise.common.misc;
import ca.spottedleaf.concurrentutil.util.ConcurrentUtil;
import java.lang.invoke.VarHandle;
public final class LazyRunnable implements Runnable {
private volatile Runnable toRun;
private static final VarHandle TO_RUN_HANDLE = ConcurrentUtil.getVarHandle(LazyRunnable.class, "toRun", Runnable.class);
public void setRunnable(final Runnable run) {
final Runnable prev = (Runnable)TO_RUN_HANDLE.compareAndExchange(this, (Runnable)null, run);
if (prev != null) {
throw new IllegalStateException("Runnable already set");
}
}
@Override
public void run() {
((Runnable)TO_RUN_HANDLE.getVolatile(this)).run();
}
}

View File

@@ -0,0 +1,99 @@
package ca.spottedleaf.moonrise.common.misc;
import ca.spottedleaf.concurrentutil.util.IntPairUtil;
import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap;
import it.unimi.dsi.fastutil.longs.LongSet;
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 LongSet getPositions() {
return this.positions.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);
}
}
}
}

View File

@@ -0,0 +1,248 @@
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;
}
}

View File

@@ -0,0 +1,68 @@
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;
}
}

View File

@@ -0,0 +1,288 @@
package ca.spottedleaf.moonrise.common.util;
import ca.spottedleaf.concurrentutil.util.Priority;
import ca.spottedleaf.moonrise.common.PlatformHooks;
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, Priority.NORMAL);
}
public static void scheduleChunkTask(final ServerLevel level, final int chunkX, final int chunkZ, final Runnable run, final 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 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 Priority priority, final Consumer<ChunkAccess> onComplete) {
if (!org.bukkit.Bukkit.isOwnedByCurrentRegion(level.getWorld(), chunkX, chunkZ)) {
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, Priority.HIGHEST);
});
}
public static void scheduleTickingState(final ServerLevel level, final int chunkX, final int chunkZ,
final FullChunkStatus toStatus, final boolean addTicket,
final 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.isOwnedByCurrentRegion(level.getWorld(), chunkX, chunkZ)) {
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, 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, final boolean fromDisk, final boolean event) {
if (!PlatformHooks.get().screenEntity(level, entity, fromDisk, event)) {
return false;
}
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 getViewDistance(player);
}
public static int getViewDistance(final ServerPlayer player) {
final ServerLevel level = player.serverLevel();
if (level == null) {
return org.bukkit.Bukkit.getViewDistance();
}
return level.chunkSource.chunkMap.serverViewDistance;
}
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() {}
}

View File

@@ -0,0 +1,129 @@
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();
}
}

View File

@@ -0,0 +1,109 @@
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() {}
}

View File

@@ -0,0 +1,34 @@
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);
}
}
}

View File

@@ -0,0 +1,14 @@
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
// https://github.com/FabricMC/Mixin/pull/147
public static long[] clone(final long[] values) {
return values.clone();
}
public static byte[] clone(final byte[] values) {
return values.clone();
}
}

View File

@@ -0,0 +1,101 @@
package ca.spottedleaf.moonrise.common.util;
import ca.spottedleaf.concurrentutil.executor.thread.PrioritisedThreadPool;
import ca.spottedleaf.moonrise.common.PlatformHooks;
import com.mojang.logging.LogUtils;
import org.slf4j.Logger;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
public final class MoonriseCommon {
private static final Logger LOGGER = LogUtils.getClassLogger();
public static final PrioritisedThreadPool WORKER_POOL = new PrioritisedThreadPool(
new Consumer<>() {
private final AtomicInteger idGenerator = new AtomicInteger();
@Override
public void accept(Thread thread) {
thread.setDaemon(true);
thread.setName(PlatformHooks.get().getBrand() + " Common Worker #" + this.idGenerator.getAndIncrement());
thread.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
@Override
public void uncaughtException(final Thread thread, final Throwable throwable) {
LOGGER.error("Uncaught exception in thread " + thread.getName(), throwable);
}
});
}
}
);
public static final long WORKER_QUEUE_HOLD_TIME = (long)(20.0e6); // 20ms
public static final int CLIENT_DIVISION = 0;
public static final PrioritisedThreadPool.ExecutorGroup RENDER_EXECUTOR_GROUP = MoonriseCommon.WORKER_POOL.createExecutorGroup(CLIENT_DIVISION, 0);
public static final int SERVER_DIVISION = 1;
public static final PrioritisedThreadPool.ExecutorGroup PARALLEL_GEN_GROUP = MoonriseCommon.WORKER_POOL.createExecutorGroup(SERVER_DIVISION, 0);
public static final PrioritisedThreadPool.ExecutorGroup RADIUS_AWARE_GROUP = MoonriseCommon.WORKER_POOL.createExecutorGroup(SERVER_DIVISION, 0);
public static final PrioritisedThreadPool.ExecutorGroup LOAD_GROUP = MoonriseCommon.WORKER_POOL.createExecutorGroup(SERVER_DIVISION, 0);
public static void adjustWorkerThreads(final int configWorkerThreads, final int configIoThreads) {
int defaultWorkerThreads = Runtime.getRuntime().availableProcessors() / 2;
if (defaultWorkerThreads <= 4) {
defaultWorkerThreads = defaultWorkerThreads <= 3 ? 1 : 2;
} else {
defaultWorkerThreads = defaultWorkerThreads / 2;
}
defaultWorkerThreads = Integer.getInteger(PlatformHooks.get().getBrand() + ".WorkerThreadCount", Integer.valueOf(defaultWorkerThreads));
int workerThreads = configWorkerThreads;
if (workerThreads <= 0) {
workerThreads = defaultWorkerThreads;
}
final int ioThreads = Math.max(1, configIoThreads);
WORKER_POOL.adjustThreadCount(workerThreads);
IO_POOL.adjustThreadCount(ioThreads);
LOGGER.info(PlatformHooks.get().getBrand() + " is using " + workerThreads + " worker threads, " + ioThreads + " I/O threads");
}
public static final PrioritisedThreadPool IO_POOL = new PrioritisedThreadPool(
new Consumer<>() {
private final AtomicInteger idGenerator = new AtomicInteger();
@Override
public void accept(final Thread thread) {
thread.setDaemon(true);
thread.setName(PlatformHooks.get().getBrand() + " I/O Worker #" + this.idGenerator.getAndIncrement());
thread.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
@Override
public void uncaughtException(final Thread thread, final Throwable throwable) {
LOGGER.error("Uncaught exception in thread " + thread.getName(), throwable);
}
});
}
}
);
public static final long IO_QUEUE_HOLD_TIME = (long)(100.0e6); // 100ms
public static final PrioritisedThreadPool.ExecutorGroup CLIENT_PROFILER_IO_GROUP = IO_POOL.createExecutorGroup(CLIENT_DIVISION, 0);
public static final PrioritisedThreadPool.ExecutorGroup SERVER_REGION_IO_GROUP = IO_POOL.createExecutorGroup(SERVER_DIVISION, 0);
public static void haltExecutors() {
MoonriseCommon.WORKER_POOL.shutdown(false);
LOGGER.info("Awaiting termination of worker pool for up to 60s...");
if (!MoonriseCommon.WORKER_POOL.join(TimeUnit.SECONDS.toMillis(60L))) {
LOGGER.error("Worker pool did not shut down in time!");
MoonriseCommon.WORKER_POOL.halt(false);
}
MoonriseCommon.IO_POOL.shutdown(false);
LOGGER.info("Awaiting termination of I/O pool for up to 60s...");
if (!MoonriseCommon.IO_POOL.join(TimeUnit.SECONDS.toMillis(60L))) {
LOGGER.error("I/O pool did not shut down in time!");
MoonriseCommon.IO_POOL.halt(false);
}
}
private MoonriseCommon() {}
}

View File

@@ -0,0 +1,11 @@
package ca.spottedleaf.moonrise.common.util;
import ca.spottedleaf.moonrise.common.PlatformHooks;
public final class MoonriseConstants {
public static final int MAX_VIEW_DISTANCE = Integer.getInteger(PlatformHooks.get().getBrand() + ".MaxViewDistance", 32);
private MoonriseConstants() {}
}

View File

@@ -0,0 +1,105 @@
package ca.spottedleaf.moonrise.common.util;
import net.minecraft.util.Mth;
import net.minecraft.util.RandomSource;
import net.minecraft.world.level.levelgen.BitRandomSource;
import net.minecraft.world.level.levelgen.MarsagliaPolarGaussian;
import net.minecraft.world.level.levelgen.PositionalRandomFactory;
/**
* Avoid costly CAS of superclass + division in nextInt
*/
public final class SimpleThreadUnsafeRandom implements BitRandomSource {
private static final long MULTIPLIER = 25214903917L;
private static final long ADDEND = 11L;
private static final int BITS = 48;
private static final long MASK = (1L << BITS) - 1L;
private long value;
private final MarsagliaPolarGaussian gaussianSource = new MarsagliaPolarGaussian(this);
public SimpleThreadUnsafeRandom(final long seed) {
this.setSeed(seed);
}
@Override
public void setSeed(final long seed) {
this.value = (seed ^ MULTIPLIER) & MASK;
this.gaussianSource.reset();
}
private long advanceSeed() {
return this.value = ((this.value * MULTIPLIER) + ADDEND) & MASK;
}
@Override
public int next(final int bits) {
return (int)(this.advanceSeed() >>> (BITS - bits));
}
@Override
public int nextInt() {
final long seed = this.advanceSeed();
return (int)(seed >>> (BITS - Integer.SIZE));
}
@Override
public int nextInt(final int bound) {
if (bound <= 0) {
throw new IllegalArgumentException();
}
// https://lemire.me/blog/2016/06/27/a-fast-alternative-to-the-modulo-reduction/
final long value = this.advanceSeed() >>> (BITS - Integer.SIZE);
return (int)((value * (long)bound) >>> Integer.SIZE);
}
@Override
public double nextGaussian() {
return this.gaussianSource.nextGaussian();
}
@Override
public RandomSource fork() {
return new SimpleThreadUnsafeRandom(this.nextLong());
}
@Override
public PositionalRandomFactory forkPositional() {
return new SimpleRandomPositionalFactory(this.nextLong());
}
public static final class SimpleRandomPositionalFactory implements PositionalRandomFactory {
private final long seed;
public SimpleRandomPositionalFactory(final long seed) {
this.seed = seed;
}
public long getSeed() {
return this.seed;
}
@Override
public RandomSource fromHashOf(final String string) {
return new SimpleThreadUnsafeRandom((long)string.hashCode() ^ this.seed);
}
@Override
public RandomSource fromSeed(final long seed) {
return new SimpleThreadUnsafeRandom(seed);
}
@Override
public RandomSource at(final int x, final int y, final int z) {
return new SimpleThreadUnsafeRandom(Mth.getSeed(x, y, z) ^ this.seed);
}
@Override
public void parityConfigString(final StringBuilder stringBuilder) {
stringBuilder.append("SimpleRandomPositionalFactory{").append(this.seed).append('}');
}
}
}

View File

@@ -0,0 +1,94 @@
package ca.spottedleaf.moonrise.common.util;
import net.minecraft.util.Mth;
import net.minecraft.util.RandomSource;
import net.minecraft.world.level.levelgen.BitRandomSource;
import net.minecraft.world.level.levelgen.MarsagliaPolarGaussian;
import net.minecraft.world.level.levelgen.PositionalRandomFactory;
/**
* Avoid costly CAS of superclass
*/
public final class ThreadUnsafeRandom implements BitRandomSource {
private static final long MULTIPLIER = 25214903917L;
private static final long ADDEND = 11L;
private static final int BITS = 48;
private static final long MASK = (1L << BITS) - 1L;
private long value;
private final MarsagliaPolarGaussian gaussianSource = new MarsagliaPolarGaussian(this);
public ThreadUnsafeRandom(final long seed) {
this.setSeed(seed);
}
@Override
public void setSeed(final long seed) {
this.value = (seed ^ MULTIPLIER) & MASK;
this.gaussianSource.reset();
}
private long advanceSeed() {
return this.value = ((this.value * MULTIPLIER) + ADDEND) & MASK;
}
@Override
public int next(final int bits) {
return (int)(this.advanceSeed() >>> (BITS - bits));
}
@Override
public int nextInt() {
final long seed = this.advanceSeed();
return (int)(seed >>> (BITS - Integer.SIZE));
}
@Override
public double nextGaussian() {
return this.gaussianSource.nextGaussian();
}
@Override
public RandomSource fork() {
return new ThreadUnsafeRandom(this.nextLong());
}
@Override
public PositionalRandomFactory forkPositional() {
return new ThreadUnsafeRandomPositionalFactory(this.nextLong());
}
public static final class ThreadUnsafeRandomPositionalFactory implements PositionalRandomFactory {
private final long seed;
public ThreadUnsafeRandomPositionalFactory(final long seed) {
this.seed = seed;
}
public long getSeed() {
return this.seed;
}
@Override
public RandomSource fromHashOf(final String string) {
return new ThreadUnsafeRandom((long)string.hashCode() ^ this.seed);
}
@Override
public RandomSource fromSeed(final long seed) {
return new ThreadUnsafeRandom(seed);
}
@Override
public RandomSource at(final int x, final int y, final int z) {
return new ThreadUnsafeRandom(Mth.getSeed(x, y, z) ^ this.seed);
}
@Override
public void parityConfigString(final StringBuilder stringBuilder) {
stringBuilder.append("ThreadUnsafeRandomPositionalFactory{").append(this.seed).append('}');
}
}
}

View File

@@ -0,0 +1,143 @@
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(null, run, name);
}
public TickThread(final ThreadGroup group, final Runnable run, final String name) {
this(group, run, name, ID_GENERATOR.incrementAndGet());
}
private TickThread(final ThreadGroup group, final Runnable run, final String name, final int id) {
super(group, 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();
}
}

View File

@@ -0,0 +1,62 @@
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.getMaxSectionY();
}
public static int getMaxSection(final Level world) {
return world.getMaxSectionY();
}
public static int getMinSection(final LevelHeightAccessor world) {
return world.getMinSectionY();
}
public static int getMinSection(final Level world) {
return world.getMinSectionY();
}
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();
}
}

View File

@@ -0,0 +1,240 @@
package ca.spottedleaf.moonrise.paper;
import ca.spottedleaf.moonrise.common.PlatformHooks;
import com.mojang.datafixers.DSL;
import com.mojang.datafixers.DataFixer;
import com.mojang.serialization.Dynamic;
import java.util.Collection;
import net.minecraft.core.BlockPos;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.NbtOps;
import net.minecraft.server.level.ChunkHolder;
import net.minecraft.server.level.GenerationChunkHolder;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.boss.EnderDragonPart;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.chunk.ChunkAccess;
import net.minecraft.world.level.chunk.LevelChunk;
import net.minecraft.world.level.chunk.ProtoChunk;
import net.minecraft.world.level.chunk.storage.SerializableChunkData;
import net.minecraft.world.level.entity.EntityTypeTest;
import net.minecraft.world.phys.AABB;
import java.util.List;
import java.util.function.Predicate;
public final class PaperHooks implements PlatformHooks {
@Override
public String getBrand() {
return "Paper";
}
@Override
public int getLightEmission(final BlockState blockState, final BlockGetter world, final BlockPos pos) {
return blockState.getLightEmission();
}
@Override
public Predicate<BlockState> maybeHasLightEmission() {
return (final BlockState state) -> {
return state.getLightEmission() != 0;
};
}
@Override
public boolean hasCurrentlyLoadingChunk() {
return false;
}
@Override
public LevelChunk getCurrentlyLoadingChunk(final GenerationChunkHolder holder) {
return null;
}
@Override
public void setCurrentlyLoading(final GenerationChunkHolder holder, final LevelChunk levelChunk) {
}
@Override
public void chunkFullStatusComplete(final LevelChunk newChunk, final ProtoChunk original) {
}
@Override
public boolean allowAsyncTicketUpdates() {
return true;
}
@Override
public void onChunkHolderTicketChange(final ServerLevel world, final ChunkHolder holder, final int oldLevel, final int newLevel) {
}
@Override
public void chunkUnloadFromWorld(final LevelChunk chunk) {
}
@Override
public void chunkSyncSave(final ServerLevel world, final ChunkAccess chunk, final SerializableChunkData data) {
}
@Override
public void onChunkWatch(final ServerLevel world, final LevelChunk chunk, final ServerPlayer player) {
}
@Override
public void onChunkUnWatch(final ServerLevel world, final ChunkPos chunk, final ServerPlayer player) {
}
@Override
public void addToGetEntities(final Level world, final Entity entity, final AABB boundingBox, final Predicate<? super Entity> predicate, final List<Entity> into) {
final Collection<EnderDragonPart> parts = world.dragonParts();
if (parts.isEmpty()) {
return;
}
for (final EnderDragonPart part : parts) {
if (part != entity && part.getBoundingBox().intersects(boundingBox) && (predicate == null || predicate.test(part))) {
into.add(part);
}
}
}
@Override
public <T extends Entity> void addToGetEntities(final Level world, final EntityTypeTest<Entity, T> entityTypeTest, final AABB boundingBox, final Predicate<? super T> predicate, final List<? super T> into, final int maxCount) {
if (into.size() >= maxCount) {
// fix neoforge issue: do not add if list is already full
return;
}
final Collection<EnderDragonPart> parts = world.dragonParts();
if (parts.isEmpty()) {
return;
}
for (final EnderDragonPart part : parts) {
if (!part.getBoundingBox().intersects(boundingBox)) {
continue;
}
final T casted = (T)entityTypeTest.tryCast(part);
if (casted != null && (predicate == null || predicate.test(casted))) {
into.add(casted);
if (into.size() >= maxCount) {
break;
}
}
}
}
@Override
public void entityMove(final Entity entity, final long oldSection, final long newSection) {
}
@Override
public boolean screenEntity(final ServerLevel world, final Entity entity, final boolean fromDisk, final boolean event) {
return true;
}
@Override
public boolean configFixMC224294() {
return true;
}
@Override
public boolean configAutoConfigSendDistance() {
return io.papermc.paper.configuration.GlobalConfiguration.get().chunkLoadingAdvanced.autoConfigSendDistance;
}
@Override
public double configPlayerMaxLoadRate() {
return io.papermc.paper.configuration.GlobalConfiguration.get().chunkLoadingBasic.playerMaxChunkLoadRate;
}
@Override
public double configPlayerMaxGenRate() {
return io.papermc.paper.configuration.GlobalConfiguration.get().chunkLoadingBasic.playerMaxChunkGenerateRate;
}
@Override
public double configPlayerMaxSendRate() {
return io.papermc.paper.configuration.GlobalConfiguration.get().chunkLoadingBasic.playerMaxChunkSendRate;
}
@Override
public int configPlayerMaxConcurrentLoads() {
return io.papermc.paper.configuration.GlobalConfiguration.get().chunkLoadingAdvanced.playerMaxConcurrentChunkLoads;
}
@Override
public int configPlayerMaxConcurrentGens() {
return io.papermc.paper.configuration.GlobalConfiguration.get().chunkLoadingAdvanced.playerMaxConcurrentChunkGenerates;
}
@Override
public long configAutoSaveInterval(final ServerLevel world) {
return world.paperConfig().chunks.autoSaveInterval.value();
}
@Override
public int configMaxAutoSavePerTick(final ServerLevel world) {
return world.paperConfig().chunks.maxAutoSaveChunksPerTick;
}
@Override
public boolean configFixMC159283() {
return true;
}
@Override
public boolean forceNoSave(final ChunkAccess chunk) {
return chunk instanceof LevelChunk levelChunk && levelChunk.mustNotSave;
}
@Override
public CompoundTag convertNBT(final DSL.TypeReference type, final DataFixer dataFixer, final CompoundTag nbt,
final int fromVersion, final int toVersion) {
return (CompoundTag)dataFixer.update(
type, new Dynamic<>(NbtOps.INSTANCE, nbt), fromVersion, toVersion
).getValue();
}
@Override
public boolean hasMainChunkLoadHook() {
return false;
}
@Override
public void mainChunkLoad(final ChunkAccess chunk, final SerializableChunkData chunkData) {
}
@Override
public List<Entity> modifySavedEntities(final ServerLevel world, final int chunkX, final int chunkZ, final List<Entity> entities) {
return entities;
}
@Override
public void unloadEntity(final Entity entity) {
entity.setRemoved(Entity.RemovalReason.UNLOADED_TO_CHUNK, org.bukkit.event.entity.EntityRemoveEvent.Cause.UNLOAD);
}
@Override
public void postLoadProtoChunk(final ServerLevel world, final ProtoChunk chunk) {
net.minecraft.world.level.chunk.status.ChunkStatusTasks.postLoadProtoChunk(world, chunk.getEntities());
}
@Override
public int modifyEntityTrackingRange(final Entity entity, final int currentRange) {
return org.spigotmc.TrackingRange.getEntityTrackingRange(entity, currentRange);
}
}

View File

@@ -215,7 +215,7 @@ public class GlobalConfiguration extends ConfigurationPart {
@PostProcess
private void postProcess() {
ca.spottedleaf.moonrise.common.util.MoonriseCommon.adjustWorkerThreads(this.workerThreads, this.ioThreads);
}
}

View File

@@ -0,0 +1,129 @@
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));
}
}

View File

@@ -0,0 +1,205 @@
package io.papermc.paper.util;
import com.google.common.collect.Collections2;
import com.google.common.collect.Lists;
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.Collection;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Vec3i;
import net.minecraft.resources.ResourceKey;
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.NamespacedKey;
import org.bukkit.craftbukkit.util.CraftNamespacedKey;
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 ExecutorService ASYNC_EXECUTOR = Executors.newFixedThreadPool(2, 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) {
ASYNC_EXECUTOR.execute(run);
}
public static <T> ResourceKey<T> toResourceKey(
final ResourceKey<? extends net.minecraft.core.Registry<T>> registry,
final NamespacedKey namespacedKey
) {
return ResourceKey.create(registry, CraftNamespacedKey.toMinecraft(namespacedKey));
}
public static NamespacedKey fromResourceKey(final ResourceKey<?> key) {
return CraftNamespacedKey.fromMinecraft(key.location());
}
public static <A, M> List<A> transformUnmodifiable(final List<? extends M> nms, final Function<? super M, ? extends A> converter) {
return Collections.unmodifiableList(Lists.transform(nms, converter::apply));
}
public static <A, M> Collection<A> transformUnmodifiable(final Collection<? extends M> nms, final Function<? super M, ? extends A> converter) {
return Collections.unmodifiableCollection(Collections2.transform(nms, converter::apply));
}
public static <A, M, C extends Collection<M>> void addAndConvert(final C target, final Collection<A> toAdd, final Function<? super A, ? extends M> converter) {
for (final A value : toAdd) {
target.add(converter.apply(value));
}
}
}

View File

@@ -0,0 +1,45 @@
package io.papermc.paper.util;
import com.google.common.collect.ForwardingSet;
import java.util.Collection;
import java.util.Set;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
@NullMarked
public class SizeLimitedSet<E> extends ForwardingSet<E> {
private final Set<E> delegate;
private final int maxSize;
public SizeLimitedSet(final Set<E> delegate, final int maxSize) {
this.delegate = delegate;
this.maxSize = maxSize;
}
@Override
public boolean add(final E element) {
if (this.size() >= this.maxSize) {
return false;
}
return super.add(element);
}
@Override
public boolean addAll(final Collection<? extends @Nullable E> collection) {
if ((collection.size() + this.size()) >= this.maxSize) {
return false;
}
boolean edited = false;
for (final E element : collection) {
edited |= super.add(element);
}
return edited;
}
@Override
protected Set<E> delegate() {
return this.delegate;
}
}

View File

@@ -0,0 +1,24 @@
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);
}
}

View File

@@ -2627,4 +2627,9 @@ public final class CraftServer implements Server {
return this.spigot;
}
// Spigot end
@Override
public double[] getTPS() {
return new double[]{0, 0, 0}; // TODO
}
}

View File

@@ -257,8 +257,8 @@ 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
@@ -335,7 +335,7 @@ 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 -> {
@@ -2115,4 +2115,51 @@ public class CraftWorld extends CraftRegionAccessor implements World {
return this.spigot;
}
// Spigot end
// Paper start
@Override
public void getChunkAtAsync(int x, int z, boolean gen, boolean urgent, @NotNull Consumer<? super Chunk> cb) {
ca.spottedleaf.moonrise.common.util.ChunkSystem.scheduleChunkLoad(
this.getHandle(), x, z, gen, ChunkStatus.FULL, true,
urgent ? ca.spottedleaf.concurrentutil.util.Priority.HIGHER : ca.spottedleaf.concurrentutil.util.Priority.NORMAL,
(ChunkAccess chunk) -> {
cb.accept(chunk == null ? null : new CraftChunk((net.minecraft.world.level.chunk.LevelChunk)chunk));
}
);
}
@Override
public void getChunksAtAsync(int minX, int minZ, int maxX, int maxZ, boolean urgent, Runnable cb) {
this.getHandle().loadChunks(
minX, minZ, maxX, maxZ,
urgent ? ca.spottedleaf.concurrentutil.util.Priority.HIGHER : ca.spottedleaf.concurrentutil.util.Priority.NORMAL,
(List<ChunkAccess> chunks) -> {
cb.run();
}
);
}
@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
}

View File

@@ -2432,4 +2432,34 @@ public class CraftPlayer extends CraftHumanEntity implements Player {
return this.spigot;
}
// Spigot end
@Override
public int getViewDistance() {
return ca.spottedleaf.moonrise.common.util.ChunkSystem.getViewDistance(this.getHandle());
}
@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");
}
}

View File

@@ -31,6 +31,20 @@ 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;

View File

@@ -40,6 +40,17 @@ public final class CraftLocation {
return new BlockPos(location.getBlockX(), location.getBlockY(), location.getBlockZ());
}
// Paper start
public static net.minecraft.core.GlobalPos toGlobalPos(Location location) {
return net.minecraft.core.GlobalPos.of(((org.bukkit.craftbukkit.CraftWorld) location.getWorld()).getHandle().dimension(), toBlockPosition(location));
}
public static Location fromGlobalPos(net.minecraft.core.GlobalPos globalPos) {
BlockPos pos = globalPos.pos();
return new org.bukkit.Location(net.minecraft.server.MinecraftServer.getServer().getLevel(globalPos.dimension()).getWorld(), pos.getX(), pos.getY(), pos.getZ());
}
// Paper end
public static Vec3 toVec3D(Location location) {
return new Vec3(location.getX(), location.getY(), location.getZ());
}

View File

@@ -56,6 +56,7 @@ 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 {
@@ -788,4 +789,25 @@ public abstract class DelegatedGeneratorAccess implements WorldGenLevel {
public boolean isFluidAtPosition(BlockPos pos, Predicate<FluidState> state) {
return this.handle.isFluidAtPosition(pos, state);
}
// 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
}

View File

@@ -212,7 +212,23 @@ 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.");

View File

@@ -35,6 +35,9 @@ public class ActivationRange
public enum ActivationType
{
WATER, // Paper
FLYING_MONSTER, // Paper
VILLAGER, // Paper
MONSTER,
ANIMAL,
RAIDER,