MC Utils
== AT == public net.minecraft.server.level.ServerChunkCache mainThread public net.minecraft.server.level.ServerLevel chunkSource public org.bukkit.craftbukkit.inventory.CraftItemStack handle public net.minecraft.server.level.ChunkMap getVisibleChunkIfPresent(J)Lnet/minecraft/server/level/ChunkHolder; public net.minecraft.server.level.ServerChunkCache mainThreadProcessor public net.minecraft.server.level.ServerChunkCache$MainThreadExecutor public net.minecraft.world.level.chunk.LevelChunkSection states
This commit is contained in:
@@ -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"));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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];
|
||||
}
|
||||
}
|
||||
@@ -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];
|
||||
}
|
||||
}
|
||||
@@ -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];
|
||||
}
|
||||
}
|
||||
@@ -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];
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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() {}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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() {}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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() {}
|
||||
}
|
||||
@@ -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() {}
|
||||
|
||||
}
|
||||
@@ -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('}');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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('}');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -215,7 +215,7 @@ public class GlobalConfiguration extends ConfigurationPart {
|
||||
|
||||
@PostProcess
|
||||
private void postProcess() {
|
||||
|
||||
ca.spottedleaf.moonrise.common.util.MoonriseCommon.adjustWorkerThreads(this.workerThreads, this.ioThreads);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
205
paper-server/src/main/java/io/papermc/paper/util/MCUtil.java
Normal file
205
paper-server/src/main/java/io/papermc/paper/util/MCUtil.java
Normal 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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.");
|
||||
|
||||
@@ -35,6 +35,9 @@ public class ActivationRange
|
||||
|
||||
public enum ActivationType
|
||||
{
|
||||
WATER, // Paper
|
||||
FLYING_MONSTER, // Paper
|
||||
VILLAGER, // Paper
|
||||
MONSTER,
|
||||
ANIMAL,
|
||||
RAIDER,
|
||||
|
||||
Reference in New Issue
Block a user