Port Chunk System Rewrite
This commit is contained in:
@@ -33,6 +33,11 @@ dependencies {
|
||||
mappings loom.officialMojangMappings()
|
||||
modImplementation "net.fabricmc:fabric-loader:${project.loader_version}"
|
||||
|
||||
implementation(
|
||||
group: 'ca.spottedleaf',
|
||||
name: 'concurrentutil',
|
||||
version: '0.0.1-SNAPSHOT'
|
||||
)
|
||||
shadow(
|
||||
group: 'ca.spottedleaf',
|
||||
name: 'concurrentutil',
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
package ca.spottedleaf.moonrise.common.config;
|
||||
|
||||
public final class PlaceholderConfig {
|
||||
|
||||
public static double chunkLoadingBasic$playerMaxChunkSendRate = -1.0;
|
||||
public static double chunkLoadingBasic$playerMaxChunkLoadRate = -1.0;
|
||||
public static double chunkLoadingBasic$playerMaxChunkGenerateRate = -1.0;
|
||||
|
||||
public static boolean chunkLoadingAdvanced$autoConfigSendDistance = true;
|
||||
public static int chunkLoadingAdvanced$playerMaxConcurrentChunkLoads = 0;
|
||||
public static int chunkLoadingAdvanced$playerMaxConcurrentChunkGenerates = 0;
|
||||
|
||||
public static int autoSaveInterval = 60 * 5 * 20; // 5 mins
|
||||
public static int maxAutoSaveChunksPerTick = 12;
|
||||
|
||||
public static int chunkSystemIOThreads = -1;
|
||||
public static int chunkSystemThreads = -1;
|
||||
public static String chunkSystemGenParallelism = "default";
|
||||
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
package ca.spottedleaf.moonrise.common.list;
|
||||
|
||||
import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import java.util.Arrays;
|
||||
import java.util.Iterator;
|
||||
import java.util.NoSuchElementException;
|
||||
|
||||
// list with O(1) remove & contains
|
||||
|
||||
/**
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
public final class EntityList implements Iterable<Entity> {
|
||||
|
||||
protected final Int2IntOpenHashMap entityToIndex = new Int2IntOpenHashMap(2, 0.8f);
|
||||
{
|
||||
this.entityToIndex.defaultReturnValue(Integer.MIN_VALUE);
|
||||
}
|
||||
|
||||
protected static final Entity[] EMPTY_LIST = new Entity[0];
|
||||
|
||||
protected Entity[] entities = EMPTY_LIST;
|
||||
protected int count;
|
||||
|
||||
public int size() {
|
||||
return this.count;
|
||||
}
|
||||
|
||||
public boolean contains(final Entity entity) {
|
||||
return this.entityToIndex.containsKey(entity.getId());
|
||||
}
|
||||
|
||||
public boolean remove(final Entity entity) {
|
||||
final int index = this.entityToIndex.remove(entity.getId());
|
||||
if (index == Integer.MIN_VALUE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// move the entity at the end to this index
|
||||
final int endIndex = --this.count;
|
||||
final Entity end = this.entities[endIndex];
|
||||
if (index != endIndex) {
|
||||
// not empty after this call
|
||||
this.entityToIndex.put(end.getId(), index); // update index
|
||||
}
|
||||
this.entities[index] = end;
|
||||
this.entities[endIndex] = null;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean add(final Entity entity) {
|
||||
final int count = this.count;
|
||||
final int currIndex = this.entityToIndex.putIfAbsent(entity.getId(), count);
|
||||
|
||||
if (currIndex != Integer.MIN_VALUE) {
|
||||
return false; // already in this list
|
||||
}
|
||||
|
||||
Entity[] list = this.entities;
|
||||
|
||||
if (list.length == count) {
|
||||
// resize required
|
||||
list = this.entities = Arrays.copyOf(list, (int)Math.max(4L, count * 2L)); // overflow results in negative
|
||||
}
|
||||
|
||||
list[count] = entity;
|
||||
this.count = count + 1;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public Entity getChecked(final int index) {
|
||||
if (index < 0 || index >= this.count) {
|
||||
throw new IndexOutOfBoundsException("Index: " + index + " is out of bounds, size: " + this.count);
|
||||
}
|
||||
return this.entities[index];
|
||||
}
|
||||
|
||||
public Entity getUnchecked(final int index) {
|
||||
return this.entities[index];
|
||||
}
|
||||
|
||||
public Entity[] getRawData() {
|
||||
return this.entities;
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
this.entityToIndex.clear();
|
||||
Arrays.fill(this.entities, 0, this.count, null);
|
||||
this.count = 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<Entity> iterator() {
|
||||
return new Iterator<Entity>() {
|
||||
|
||||
Entity lastRet;
|
||||
int current;
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return this.current < EntityList.this.count;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Entity next() {
|
||||
if (this.current >= EntityList.this.count) {
|
||||
throw new NoSuchElementException();
|
||||
}
|
||||
return this.lastRet = EntityList.this.entities[this.current++];
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove() {
|
||||
final Entity lastRet = this.lastRet;
|
||||
|
||||
if (lastRet == null) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
this.lastRet = null;
|
||||
|
||||
EntityList.this.remove(lastRet);
|
||||
--this.current;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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,232 @@
|
||||
package ca.spottedleaf.moonrise.common.misc;
|
||||
|
||||
import ca.spottedleaf.concurrentutil.util.IntegerUtil;
|
||||
|
||||
public abstract class SingleUserAreaMap<T> {
|
||||
|
||||
private 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;
|
||||
}
|
||||
|
||||
/* 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,11 @@
|
||||
package ca.spottedleaf.moonrise.common.real_dumb_shit;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
public class HolderCompletableFuture<T> extends CompletableFuture<T> {
|
||||
|
||||
public final List<Runnable> toExecute = new ArrayList<>();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package ca.spottedleaf.moonrise.common.util;
|
||||
|
||||
public final class MoonriseConstants {
|
||||
|
||||
public static final int MAX_VIEW_DISTANCE = 32;
|
||||
|
||||
private MoonriseConstants() {}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
package ca.spottedleaf.moonrise.common.util;
|
||||
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.level.ChunkPos;
|
||||
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 ServerLevel 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 ServerLevel 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 ServerLevel 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 ServerLevel 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 ServerLevel world, final double blockX, final double blockZ, final String reason) {
|
||||
if (!isTickThreadFor(world, blockX, blockZ)) {
|
||||
LOGGER.error("Thread " + Thread.currentThread().getName() + " failed main thread check: " + reason, new Throwable());
|
||||
throw new IllegalStateException(reason);
|
||||
}
|
||||
}
|
||||
|
||||
public final int id; /* We don't override getId as the spec requires that it be unique (with respect to all other threads) */
|
||||
|
||||
private static final AtomicInteger ID_GENERATOR = new AtomicInteger();
|
||||
|
||||
public TickThread(final String name) {
|
||||
this(null, name);
|
||||
}
|
||||
|
||||
public TickThread(final Runnable run, final String name) {
|
||||
this(run, name, ID_GENERATOR.incrementAndGet());
|
||||
}
|
||||
|
||||
private TickThread(final Runnable run, final String name, final int id) {
|
||||
super(run, name);
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public static TickThread getCurrentTickThread() {
|
||||
return (TickThread)Thread.currentThread();
|
||||
}
|
||||
|
||||
public static boolean isTickThread() {
|
||||
return Thread.currentThread() instanceof TickThread;
|
||||
}
|
||||
|
||||
public static boolean isShutdownThread() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public static boolean isTickThreadFor(final ServerLevel world, final BlockPos pos) {
|
||||
return isTickThread();
|
||||
}
|
||||
|
||||
public static boolean isTickThreadFor(final ServerLevel world, final ChunkPos pos) {
|
||||
return isTickThread();
|
||||
}
|
||||
|
||||
public static boolean isTickThreadFor(final ServerLevel world, final Vec3 pos) {
|
||||
return isTickThread();
|
||||
}
|
||||
|
||||
public static boolean isTickThreadFor(final ServerLevel world, final int chunkX, final int chunkZ) {
|
||||
return isTickThread();
|
||||
}
|
||||
|
||||
public static boolean isTickThreadFor(final ServerLevel world, final AABB aabb) {
|
||||
return isTickThread();
|
||||
}
|
||||
|
||||
public static boolean isTickThreadFor(final ServerLevel world, final double blockX, final double blockZ) {
|
||||
return isTickThread();
|
||||
}
|
||||
|
||||
public static boolean isTickThreadFor(final ServerLevel world, final Vec3 position, final Vec3 deltaMovement, final int buffer) {
|
||||
return isTickThread();
|
||||
}
|
||||
|
||||
public static boolean isTickThreadFor(final ServerLevel world, final int fromChunkX, final int fromChunkZ, final int toChunkX, final int toChunkZ) {
|
||||
return isTickThread();
|
||||
}
|
||||
|
||||
public static boolean isTickThreadFor(final ServerLevel world, final int chunkX, final int chunkZ, final int radius) {
|
||||
return isTickThread();
|
||||
}
|
||||
|
||||
public static boolean isTickThreadFor(final Entity entity) {
|
||||
return isTickThread();
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package ca.spottedleaf.moonrise.common.util;
|
||||
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.LevelHeightAccessor;
|
||||
|
||||
public final class WorldUtil {
|
||||
@@ -40,6 +41,13 @@ public final class WorldUtil {
|
||||
return (getMaxSection(world) << 4) | 15;
|
||||
}
|
||||
|
||||
public static String getWorldName(final Level world) {
|
||||
if (world == null) {
|
||||
return "null world";
|
||||
}
|
||||
return world.dimension().toString();
|
||||
}
|
||||
|
||||
private WorldUtil() {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
package ca.spottedleaf.moonrise.mixin.chunk_system;
|
||||
|
||||
import ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemLevelReader;
|
||||
import net.minecraft.world.level.LevelReader;
|
||||
import net.minecraft.world.level.StructureManager;
|
||||
import net.minecraft.world.level.chunk.ChunkAccess;
|
||||
import net.minecraft.world.level.chunk.ChunkGenerator;
|
||||
import net.minecraft.world.level.chunk.status.ChunkStatus;
|
||||
import net.minecraft.world.level.levelgen.RandomState;
|
||||
import net.minecraft.world.level.levelgen.blending.Blender;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
@Mixin(ChunkGenerator.class)
|
||||
public abstract class ChunkGeneratorMixin {
|
||||
|
||||
/**
|
||||
* @reason Pass the supplier to the mixin below so that we can change the executor to the parameter provided
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Redirect(
|
||||
method = "createBiomes",
|
||||
at = @At(
|
||||
value = "INVOKE",
|
||||
target = "Ljava/util/concurrent/CompletableFuture;supplyAsync(Ljava/util/function/Supplier;Ljava/util/concurrent/Executor;)Ljava/util/concurrent/CompletableFuture;"
|
||||
)
|
||||
)
|
||||
private <U> CompletableFuture<U> passSupplier(Supplier<U> supplier, Executor executor) {
|
||||
return (CompletableFuture<U>)CompletableFuture.completedFuture(supplier);
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Retrieve the supplier from the mixin above so that we can change the executor to the parameter provided
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Inject(
|
||||
method = "createBiomes",
|
||||
cancellable = true,
|
||||
at = @At(
|
||||
value = "RETURN"
|
||||
)
|
||||
)
|
||||
private void unpackSupplier(Executor executor, RandomState randomState, Blender blender,
|
||||
StructureManager structureManager, ChunkAccess chunkAccess,
|
||||
CallbackInfoReturnable<CompletableFuture<ChunkAccess>> cir) {
|
||||
cir.setReturnValue(
|
||||
CompletableFuture.supplyAsync(((CompletableFuture<Supplier<ChunkAccess>>)(CompletableFuture)cir.getReturnValue()).join(), executor)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Bypass thread checks on sync load by using syncLoadNonFull
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Redirect(
|
||||
method = "getStructureGeneratingAt",
|
||||
at = @At(
|
||||
value = "INVOKE",
|
||||
target = "Lnet/minecraft/world/level/LevelReader;getChunk(IILnet/minecraft/world/level/chunk/status/ChunkStatus;)Lnet/minecraft/world/level/chunk/ChunkAccess;"
|
||||
)
|
||||
)
|
||||
private static ChunkAccess redirectToNonSyncLoad(final LevelReader instance, final int x, final int z,
|
||||
final ChunkStatus toStatus) {
|
||||
return ((ChunkSystemLevelReader)instance).moonrise$syncLoadNonFull(x, z, toStatus);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,504 @@
|
||||
package ca.spottedleaf.moonrise.mixin.chunk_system;
|
||||
|
||||
import ca.spottedleaf.moonrise.common.list.ReferenceList;
|
||||
import ca.spottedleaf.moonrise.common.util.WorldUtil;
|
||||
import ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel;
|
||||
import ca.spottedleaf.moonrise.patches.chunk_system.level.chunk.ChunkSystemChunkHolder;
|
||||
import ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkHolderManager;
|
||||
import ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkTaskScheduler;
|
||||
import ca.spottedleaf.moonrise.patches.chunk_system.scheduling.NewChunkHolder;
|
||||
import com.mojang.datafixers.util.Pair;
|
||||
import net.minecraft.server.level.ChunkHolder;
|
||||
import net.minecraft.server.level.ChunkMap;
|
||||
import net.minecraft.server.level.ChunkResult;
|
||||
import net.minecraft.server.level.FullChunkStatus;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.server.level.TicketType;
|
||||
import net.minecraft.world.level.ChunkPos;
|
||||
import net.minecraft.world.level.chunk.ChunkAccess;
|
||||
import net.minecraft.world.level.chunk.ImposterProtoChunk;
|
||||
import net.minecraft.world.level.chunk.LevelChunk;
|
||||
import net.minecraft.world.level.chunk.status.ChunkStatus;
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Overwrite;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
@Mixin(ChunkHolder.class)
|
||||
public abstract class ChunkHolderMixin implements ChunkSystemChunkHolder {
|
||||
|
||||
@Shadow
|
||||
@Final
|
||||
private ChunkPos pos;
|
||||
|
||||
@Shadow
|
||||
@Final
|
||||
private ChunkHolder.PlayerProvider playerProvider;
|
||||
|
||||
@Shadow
|
||||
@Final
|
||||
public static CompletableFuture<ChunkResult<ChunkAccess>> UNLOADED_CHUNK_FUTURE;
|
||||
|
||||
|
||||
@Unique
|
||||
private NewChunkHolder newChunkHolder;
|
||||
|
||||
@Unique
|
||||
private ReferenceList<ServerPlayer> playersSentChunkTo;
|
||||
|
||||
@Unique
|
||||
private ChunkMap getChunkMap() {
|
||||
return (ChunkMap)this.playerProvider;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final NewChunkHolder moonrise$getRealChunkHolder() {
|
||||
return this.newChunkHolder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void moonrise$setRealChunkHolder(final NewChunkHolder newChunkHolder) {
|
||||
this.newChunkHolder = newChunkHolder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void moonrise$addReceivedChunk(final ServerPlayer player) {
|
||||
if (!this.playersSentChunkTo.add(player)) {
|
||||
throw new IllegalStateException("Already sent chunk " + this.pos + " in world '" + WorldUtil.getWorldName(this.getChunkMap().level) + "' to player " + player);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void moonrise$removeReceivedChunk(final ServerPlayer player) {
|
||||
if (!this.playersSentChunkTo.remove(player)) {
|
||||
throw new IllegalStateException("Already sent chunk " + this.pos + " in world '" + WorldUtil.getWorldName(this.getChunkMap().level) + "' to player " + player);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final boolean moonrise$hasChunkBeenSent() {
|
||||
return this.playersSentChunkTo.size() != 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final boolean moonrise$hasChunkBeenSent(final ServerPlayer to) {
|
||||
return this.playersSentChunkTo.contains(to);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final List<ServerPlayer> moonrise$getPlayers(final boolean onlyOnWatchDistanceEdge) {
|
||||
final List<ServerPlayer> ret = new ArrayList<>();
|
||||
final ServerPlayer[] raw = this.playersSentChunkTo.getRawDataUnchecked();
|
||||
for (int i = 0, len = this.playersSentChunkTo.size(); i < len; ++i) {
|
||||
final ServerPlayer player = raw[i];
|
||||
if (onlyOnWatchDistanceEdge && !((ChunkSystemServerLevel)this.getChunkMap().level).moonrise$getPlayerChunkLoader().isChunkSent(player, this.pos.x, this.pos.z, onlyOnWatchDistanceEdge)) {
|
||||
continue;
|
||||
}
|
||||
ret.add(player);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
@Unique
|
||||
private static final ServerPlayer[] EMPTY_PLAYER_ARRAY = new ServerPlayer[0];
|
||||
|
||||
/**
|
||||
* @reason Initialise our fields
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Inject(
|
||||
method = "<init>",
|
||||
at = @At(
|
||||
value = "RETURN"
|
||||
)
|
||||
)
|
||||
private void initFields(final CallbackInfo ci) {
|
||||
this.playersSentChunkTo = new ReferenceList<>(EMPTY_PLAYER_ARRAY, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Chunk system is not built on futures anymore, use {@link ChunkTaskScheduler}
|
||||
* schedule methods to await for a chunk load
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Overwrite
|
||||
public CompletableFuture<ChunkResult<ChunkAccess>> getFutureIfPresentUnchecked(final ChunkStatus chunkStatus) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Chunk system is not built on futures anymore, use {@link ChunkTaskScheduler}
|
||||
* schedule methods to await for a chunk load
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Overwrite
|
||||
public CompletableFuture<ChunkResult<ChunkAccess>> getFutureIfPresent(final ChunkStatus chunkStatus) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Chunk system is not built on futures anymore, use {@link ChunkTaskScheduler}
|
||||
* schedule methods to await for a chunk load
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Overwrite
|
||||
public CompletableFuture<ChunkResult<ChunkAccess>> getTickingChunkFuture() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Chunk system is not built on futures anymore, use {@link ChunkTaskScheduler}
|
||||
* schedule methods to await for a chunk load
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Overwrite
|
||||
public CompletableFuture<ChunkResult<ChunkAccess>> getEntityTickingChunkFuture() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Chunk system is not built on futures anymore, use {@link ChunkTaskScheduler}
|
||||
* schedule methods to await for a chunk load
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Overwrite
|
||||
public CompletableFuture<ChunkResult<ChunkAccess>> getFullChunkFuture() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Route to new chunk holder
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Overwrite
|
||||
public LevelChunk getTickingChunk() {
|
||||
if (this.newChunkHolder.isTickingReady()) {
|
||||
if (this.newChunkHolder.getCurrentChunk() instanceof LevelChunk levelChunk) {
|
||||
return levelChunk;
|
||||
} // else: race condition: chunk unload
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Chunk system is not built on futures anymore, and I am pretty sure this is a disgusting hack for a problem
|
||||
* that doesn't even exist.
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Overwrite
|
||||
public CompletableFuture<?> getChunkSendSyncFuture() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Unique
|
||||
private boolean isRadiusLoaded(final int radius) {
|
||||
final ChunkHolderManager manager = ((ChunkSystemServerLevel)this.getChunkMap().level).moonrise$getChunkTaskScheduler()
|
||||
.chunkHolderManager;
|
||||
final ChunkPos pos = this.pos;
|
||||
final int chunkX = pos.x;
|
||||
final int chunkZ = pos.z;
|
||||
for (int dz = -radius; dz <= radius; ++dz) {
|
||||
for (int dx = -radius; dx <= radius; ++dx) {
|
||||
if ((dx | dz) == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
final NewChunkHolder holder = manager.getChunkHolder(dx + chunkX, dz + chunkZ);
|
||||
|
||||
if (holder == null || !holder.isFullChunkReady()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Chunk sending may now occur for non-ticking chunks, provided that both the 1 radius neighbours are FULL
|
||||
* and post-processing is ran.
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Overwrite
|
||||
public LevelChunk getChunkToSend() {
|
||||
final LevelChunk ret = this.moonrise$getFullChunk();
|
||||
if (ret != null && this.isRadiusLoaded(1)) {
|
||||
return ret;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final LevelChunk moonrise$getFullChunk() {
|
||||
if (this.newChunkHolder.isFullChunkReady()) {
|
||||
if (this.newChunkHolder.getCurrentChunk() instanceof LevelChunk levelChunk) {
|
||||
return levelChunk;
|
||||
} // else: race condition: chunk unload
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Route to new chunk holder
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Overwrite
|
||||
public ChunkStatus getLastAvailableStatus() {
|
||||
final NewChunkHolder.ChunkCompletion lastCompletion = this.newChunkHolder.getLastChunkCompletion();
|
||||
return lastCompletion == null ? null : lastCompletion.genStatus();
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Route to new chunk holder
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Overwrite
|
||||
public ChunkAccess getLastAvailable() {
|
||||
final NewChunkHolder.ChunkCompletion lastCompletion = this.newChunkHolder.getLastChunkCompletion();
|
||||
return lastCompletion == null ? null : lastCompletion.chunk();
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Chunk system is not built on futures anymore, unloading is now checked via {@link NewChunkHolder#isSafeToUnload()}
|
||||
* while holding chunk system locks.
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Overwrite
|
||||
public CompletableFuture<ChunkAccess> getChunkToSave() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason need to reroute getTickingChunk to getChunkToSend, as we do not bring all sent chunks to ticking
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Redirect(
|
||||
method = "blockChanged",
|
||||
at = @At(
|
||||
value = "INVOKE",
|
||||
target = "Lnet/minecraft/server/level/ChunkHolder;getTickingChunk()Lnet/minecraft/world/level/chunk/LevelChunk;")
|
||||
)
|
||||
private LevelChunk redirectBlockUpdate(final ChunkHolder instance) {
|
||||
if (this.playersSentChunkTo.size() == 0) {
|
||||
// no players to sent to, so don't need to update anything
|
||||
return null;
|
||||
}
|
||||
return this.getChunkToSend();
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Need to reroute getFutureIfPresent to new chunk system call
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Redirect(
|
||||
method = "sectionLightChanged",
|
||||
at = @At(
|
||||
value = "INVOKE",
|
||||
target = "Lnet/minecraft/server/level/ChunkHolder;getFutureIfPresent(Lnet/minecraft/world/level/chunk/status/ChunkStatus;)Ljava/util/concurrent/CompletableFuture;"
|
||||
)
|
||||
)
|
||||
private CompletableFuture<ChunkResult<ChunkAccess>> redirectLightUpdate(final ChunkHolder instance,
|
||||
final ChunkStatus chunkStatus) {
|
||||
final NewChunkHolder.ChunkCompletion chunkCompletion = this.newChunkHolder.getLastChunkCompletion();
|
||||
if (chunkCompletion == null || !chunkCompletion.genStatus().isOrAfter(ChunkStatus.INITIALIZE_LIGHT)) {
|
||||
return UNLOADED_CHUNK_FUTURE;
|
||||
}
|
||||
|
||||
return CompletableFuture.completedFuture(ChunkResult.of(chunkCompletion.chunk()));
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason need to reroute getTickingChunk to getChunkToSend, as we do not bring all sent chunks to ticking
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Redirect(
|
||||
method = "sectionLightChanged",
|
||||
at = @At(
|
||||
value = "INVOKE",
|
||||
target = "Lnet/minecraft/server/level/ChunkHolder;getTickingChunk()Lnet/minecraft/world/level/chunk/LevelChunk;"
|
||||
)
|
||||
)
|
||||
private LevelChunk redirectLightUpdate(final ChunkHolder instance) {
|
||||
return this.getChunkToSend();
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Redirect player retrieval to the sent player list, as we do not maintain the Vanilla hook
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Redirect(
|
||||
method = "broadcastChanges",
|
||||
at = @At(
|
||||
value = "INVOKE",
|
||||
target = "Lnet/minecraft/server/level/ChunkHolder$PlayerProvider;getPlayers(Lnet/minecraft/world/level/ChunkPos;Z)Ljava/util/List;")
|
||||
)
|
||||
private List<ServerPlayer> redirectPlayerRetrieval(final ChunkHolder.PlayerProvider instance, final ChunkPos chunkPos,
|
||||
final boolean onlyOnWatchDistanceEdge) {
|
||||
return this.moonrise$getPlayers(onlyOnWatchDistanceEdge);
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Chunk system is not built on futures anymore, use {@link ChunkTaskScheduler}
|
||||
* schedule methods to await for a chunk load
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Overwrite
|
||||
public CompletableFuture<ChunkResult<ChunkAccess>> getOrScheduleFuture(final ChunkStatus chunkStatus,
|
||||
final ChunkMap chunkMap) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Chunk system is not built on futures anymore, use ticket levels to prevent chunk unloading.
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Overwrite
|
||||
public void addSaveDependency(final String string, final CompletableFuture<?> completableFuture) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Chunk system is not built on futures anymore, use ticket levels to prevent chunk unloading.
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Overwrite
|
||||
public void updateChunkToSave(CompletableFuture<? extends ChunkResult<? extends ChunkAccess>> completableFuture,
|
||||
final String string) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Chunk system is not built on futures anymore, and I am pretty sure this is a disgusting hack for a problem
|
||||
* that doesn't even exist.
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Overwrite
|
||||
public void addSendDependency(final CompletableFuture<?> completableFuture) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Route to new chunk holder
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Overwrite
|
||||
public FullChunkStatus getFullStatus() {
|
||||
return this.newChunkHolder.getChunkStatus();
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Route to new chunk holder
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Overwrite
|
||||
public int getTicketLevel() {
|
||||
return this.newChunkHolder.getTicketLevel();
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Set chunk priority instead in the new chunk system
|
||||
* @author Spottedleaf
|
||||
* @see ChunkTaskScheduler
|
||||
*/
|
||||
@Overwrite
|
||||
public int getQueueLevel() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Set chunk priority instead in the new chunk system
|
||||
* @author Spottedleaf
|
||||
* @see ChunkTaskScheduler
|
||||
*/
|
||||
@Overwrite
|
||||
public void setQueueLevel(int i) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Use ticket system to control ticket levels
|
||||
* @author Spottedleaf
|
||||
* @see net.minecraft.server.level.ServerChunkCache#addRegionTicket(TicketType, ChunkPos, int, Object)
|
||||
*/
|
||||
@Overwrite
|
||||
public void setTicketLevel(int i) {
|
||||
// don't throw, this is called during construction of ChunkHolder
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Chunk system is not built on futures anymore
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Overwrite
|
||||
public void scheduleFullChunkPromotion(final ChunkMap chunkMap,
|
||||
final CompletableFuture<ChunkResult<LevelChunk>> completableFuture,
|
||||
final Executor executor, final FullChunkStatus fullChunkStatus) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Chunk system is not built on futures anymore
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Overwrite
|
||||
public void demoteFullChunk(final ChunkMap chunkMap, final FullChunkStatus fullChunkStatus) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Chunk system hooks for ticket level updating now in {@link NewChunkHolder#processTicketLevelUpdate(List, List)}
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Overwrite
|
||||
public void updateFutures(final ChunkMap chunkMap, final Executor executor) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason New chunk system has no equivalent, as chunks should be saved according to their dirty flag to ensure
|
||||
* that all unsaved data is not lost.
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Overwrite
|
||||
public boolean wasAccessibleSinceLastSave() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason New chunk system has no equivalent, as chunks should be saved according to their dirty flag to ensure
|
||||
* that all unsaved data is not lost.
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Overwrite
|
||||
public void refreshAccessibility() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Chunk system is not built on futures anymore
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Overwrite
|
||||
public void replaceProtoChunk(final ImposterProtoChunk imposterProtoChunk) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Chunk system is not built on futures anymore
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Overwrite
|
||||
public List<Pair<ChunkStatus, CompletableFuture<ChunkResult<ChunkAccess>>>> getAllFutures() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package ca.spottedleaf.moonrise.mixin.chunk_system;
|
||||
|
||||
import ca.spottedleaf.moonrise.patches.chunk_system.level.chunk.ChunkSystemDistanceManager;
|
||||
import net.minecraft.server.level.ChunkMap;
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Overwrite;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
@Mixin(ChunkMap.DistanceManager.class)
|
||||
public abstract class ChunkMap$DistanceManagerMixin extends net.minecraft.server.level.DistanceManager implements ChunkSystemDistanceManager {
|
||||
|
||||
@Shadow
|
||||
@Final
|
||||
ChunkMap field_17443;
|
||||
|
||||
protected ChunkMap$DistanceManagerMixin(Executor executor, Executor executor2) {
|
||||
super(executor, executor2);
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Destroy old chunk system hooks
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Override
|
||||
@Overwrite
|
||||
public boolean isChunkToRemove(final long pos) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public final ChunkMap moonrise$getChunkMap() {
|
||||
return this.field_17443;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,561 @@
|
||||
package ca.spottedleaf.moonrise.mixin.chunk_system;
|
||||
|
||||
import ca.spottedleaf.moonrise.common.util.MoonriseConstants;
|
||||
import ca.spottedleaf.moonrise.patches.chunk_system.ChunkSystem;
|
||||
import ca.spottedleaf.moonrise.patches.chunk_system.io.RegionFileIOThread;
|
||||
import ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel;
|
||||
import ca.spottedleaf.moonrise.patches.chunk_system.level.chunk.ChunkSystemChunkHolder;
|
||||
import ca.spottedleaf.moonrise.patches.chunk_system.player.RegionizedPlayerChunkLoader;
|
||||
import ca.spottedleaf.moonrise.patches.chunk_system.scheduling.NewChunkHolder;
|
||||
import com.mojang.datafixers.DataFixer;
|
||||
import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.nbt.StreamTagVisitor;
|
||||
import net.minecraft.server.level.ChunkHolder;
|
||||
import net.minecraft.server.level.ChunkMap;
|
||||
import net.minecraft.server.level.ChunkResult;
|
||||
import net.minecraft.server.level.ChunkTaskPriorityQueueSorter;
|
||||
import net.minecraft.server.level.ChunkTrackingView;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.util.Mth;
|
||||
import net.minecraft.util.thread.ProcessorHandle;
|
||||
import net.minecraft.world.level.ChunkPos;
|
||||
import net.minecraft.world.level.chunk.ChunkAccess;
|
||||
import net.minecraft.world.level.chunk.LevelChunk;
|
||||
import net.minecraft.world.level.chunk.status.ChunkStatus;
|
||||
import net.minecraft.world.level.chunk.storage.ChunkScanAccess;
|
||||
import net.minecraft.world.level.chunk.storage.ChunkStorage;
|
||||
import net.minecraft.world.level.chunk.storage.RegionStorageInfo;
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Overwrite;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
import java.io.IOException;
|
||||
import java.io.Writer;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.function.BooleanSupplier;
|
||||
import java.util.function.IntFunction;
|
||||
import java.util.function.IntSupplier;
|
||||
|
||||
@Mixin(ChunkMap.class)
|
||||
public abstract class ChunkMapMixin extends ChunkStorage implements ChunkHolder.PlayerProvider {
|
||||
|
||||
@Shadow
|
||||
@Final
|
||||
public ServerLevel level;
|
||||
|
||||
@Shadow
|
||||
private Long2ObjectLinkedOpenHashMap<ChunkHolder> updatingChunkMap;
|
||||
|
||||
@Shadow
|
||||
private volatile Long2ObjectLinkedOpenHashMap<ChunkHolder> visibleChunkMap;
|
||||
|
||||
@Shadow
|
||||
private ChunkTaskPriorityQueueSorter queueSorter;
|
||||
|
||||
@Shadow
|
||||
private ProcessorHandle<ChunkTaskPriorityQueueSorter.Message<Runnable>> worldgenMailbox;
|
||||
|
||||
@Shadow
|
||||
private ProcessorHandle<ChunkTaskPriorityQueueSorter.Message<Runnable>> mainThreadMailbox;
|
||||
|
||||
@Shadow
|
||||
private int serverViewDistance;
|
||||
|
||||
public ChunkMapMixin(RegionStorageInfo regionStorageInfo, Path path, DataFixer dataFixer, boolean bl) {
|
||||
super(regionStorageInfo, path, dataFixer, bl);
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Destroy old chunk system hooks
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Inject(
|
||||
method = "<init>",
|
||||
at = @At(
|
||||
value = "RETURN"
|
||||
)
|
||||
)
|
||||
private void constructor(final CallbackInfo ci) {
|
||||
// intentionally destroy old chunk system hooks
|
||||
this.updatingChunkMap = null;
|
||||
this.visibleChunkMap = null;
|
||||
this.queueSorter = null;
|
||||
this.worldgenMailbox = null;
|
||||
this.mainThreadMailbox = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Route to new chunk system hooks
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Overwrite
|
||||
public boolean isChunkTracked(final ServerPlayer player, final int chunkX, final int chunkZ) {
|
||||
return ((ChunkSystemServerLevel)this.level).moonrise$getPlayerChunkLoader().isChunkSent(player, chunkX, chunkZ);
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Route to new chunk system hooks
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Overwrite
|
||||
public boolean isChunkOnTrackedBorder(final ServerPlayer player, final int chunkX, final int chunkZ) {
|
||||
return ((ChunkSystemServerLevel)this.level).moonrise$getPlayerChunkLoader().isChunkSent(player, chunkX, chunkZ, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Route to new chunk system hooks
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Overwrite
|
||||
public ChunkHolder getUpdatingChunkIfPresent(final long pos) {
|
||||
final NewChunkHolder holder = ((ChunkSystemServerLevel)this.level).moonrise$getChunkTaskScheduler().chunkHolderManager.getChunkHolder(pos);
|
||||
return holder == null ? null : holder.vanillaChunkHolder;
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Route to new chunk system hooks
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Overwrite
|
||||
public ChunkHolder getVisibleChunkIfPresent(final long pos) {
|
||||
final NewChunkHolder holder = ((ChunkSystemServerLevel)this.level).moonrise$getChunkTaskScheduler().chunkHolderManager.getChunkHolder(pos);
|
||||
return holder == null ? null : holder.vanillaChunkHolder;
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Destroy old chunk system hooks
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Overwrite
|
||||
public IntSupplier getChunkQueueLevel(final long pos) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Destroy old chunk system hooks
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Overwrite
|
||||
public CompletableFuture<ChunkResult<List<ChunkAccess>>> getChunkRangeFuture(final ChunkHolder centerChunk,
|
||||
final int margin,
|
||||
final IntFunction<ChunkStatus> distanceToStatus) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Destroy old chunk system hooks
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Overwrite
|
||||
public CompletableFuture<ChunkResult<List<ChunkAccess>>> prepareEntityTickingChunk(final ChunkHolder chunk) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Destroy old chunk system hooks
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Overwrite
|
||||
public ChunkHolder updateChunkScheduling(final long pos, final int level, final ChunkHolder holder,
|
||||
final int newLevel) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Destroy old chunk system hooks
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Override
|
||||
@Overwrite
|
||||
public void close() throws IOException {
|
||||
throw new UnsupportedOperationException("Use ServerChunkCache#close");
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Route to new chunk system and handle close-save operations
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Overwrite
|
||||
public void saveAllChunks(final boolean flush) {
|
||||
final boolean shutdown = ((ChunkSystemServerLevel)this.level).moonrise$isMarkedClosing();
|
||||
|
||||
if (!shutdown) {
|
||||
((ChunkSystemServerLevel)this.level).moonrise$getChunkTaskScheduler().chunkHolderManager.saveAllChunks(
|
||||
flush, false, false
|
||||
);
|
||||
} else {
|
||||
((ChunkSystemServerLevel)this.level).moonrise$getChunkTaskScheduler().chunkHolderManager.close(
|
||||
true, true
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Destroy old chunk system hooks
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Overwrite
|
||||
public boolean hasWork() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Route to new chunk unloading code
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Overwrite
|
||||
public void processUnloads(final BooleanSupplier shouldKeepTicking) {
|
||||
((ChunkSystemServerLevel)this.level).moonrise$getChunkTaskScheduler().chunkHolderManager.processUnloads();
|
||||
((ChunkSystemServerLevel)this.level).moonrise$getChunkTaskScheduler().chunkHolderManager.autoSave();
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Destroy old chunk system hooks
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Overwrite
|
||||
public void scheduleUnload(final long pos, final ChunkHolder holder) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Replaced by concurrent map, removing the need for this logic.
|
||||
* A side-note of this logic is that expensive map copying is no longer performed.
|
||||
* @author Spottedleaf
|
||||
* @see ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkHolderManager#chunkHolders
|
||||
*/
|
||||
@Overwrite
|
||||
public boolean promoteChunkMap() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Destroy old chunk system hooks
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Overwrite
|
||||
public CompletableFuture<ChunkResult<ChunkAccess>> schedule(final ChunkHolder holder, final ChunkStatus requiredStatus) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Destroy old chunk system hooks
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Overwrite
|
||||
public CompletableFuture<ChunkResult<ChunkAccess>> scheduleChunkLoad(final ChunkPos pos) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Destroy old chunk system hooks
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Overwrite
|
||||
public CompletableFuture<ChunkResult<ChunkAccess>> scheduleChunkGeneration(final ChunkHolder holder,
|
||||
final ChunkStatus requiredStatus) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Destroy old chunk system hooks
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Overwrite
|
||||
public CompletableFuture<ChunkResult<ChunkAccess>> protoChunkToFullChunk(final ChunkHolder chunkHolder,
|
||||
final ChunkAccess chunkAccess) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Destroy old chunk system hooks
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Overwrite
|
||||
public CompletableFuture<ChunkResult<LevelChunk>> prepareTickingChunk(final ChunkHolder holder) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Destroy old chunk system hooks
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Overwrite
|
||||
public void onChunkReadyToSend(final LevelChunk chunk) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Destroy old chunk system hooks
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Overwrite
|
||||
public CompletableFuture<ChunkResult<LevelChunk>> prepareAccessibleChunk(final ChunkHolder holder) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Destroy old chunk system hooks
|
||||
* @author Spottedleaf
|
||||
* @see NewChunkHolder#save(boolean)
|
||||
*/
|
||||
@Overwrite
|
||||
public boolean saveChunkIfNeeded(final ChunkHolder chunkHolder) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Destroy old chunk system hooks
|
||||
* @author Spottedleaf
|
||||
* @see NewChunkHolder#save(boolean)
|
||||
*/
|
||||
@Overwrite
|
||||
public boolean save(final ChunkAccess chunk) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Destroy old chunk system hooks
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Overwrite
|
||||
public boolean isExistingChunkFull(final ChunkPos pos) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Route to new player chunk loader
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Overwrite
|
||||
public void setServerViewDistance(final int watchDistance) {
|
||||
final int clamped = Mth.clamp(watchDistance, 2, MoonriseConstants.MAX_VIEW_DISTANCE);
|
||||
if (clamped == this.serverViewDistance) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.serverViewDistance = clamped;
|
||||
((ChunkSystemServerLevel)this.level).moonrise$getPlayerChunkLoader().setLoadDistance(this.serverViewDistance + 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Route to new player chunk loader
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Overwrite
|
||||
public int getPlayerViewDistance(final ServerPlayer player) {
|
||||
return ChunkSystem.getSendViewDistance(player);
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Destroy old chunk system hooks
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Overwrite
|
||||
public void markChunkPendingToSend(final ServerPlayer player, final ChunkPos pos) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Destroy old chunk system hooks
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Overwrite
|
||||
public static void markChunkPendingToSend(final ServerPlayer player, final LevelChunk chunk) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Destroy old chunk system hooks
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Overwrite
|
||||
public static void dropChunk(final ServerPlayer player, final ChunkPos pos) {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Destroy old chunk system hooks
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Overwrite
|
||||
public void dumpChunks(final Writer writer) throws IOException {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Optional<CompoundTag>> read(final ChunkPos pos) {
|
||||
if (!RegionFileIOThread.isRegionFileThread()) {
|
||||
try {
|
||||
return CompletableFuture.completedFuture(
|
||||
Optional.ofNullable(
|
||||
RegionFileIOThread.loadData(
|
||||
this.level, pos.x, pos.z, RegionFileIOThread.RegionFileType.CHUNK_DATA,
|
||||
RegionFileIOThread.getIOBlockingPriorityForCurrentThread()
|
||||
)
|
||||
)
|
||||
);
|
||||
} catch (final Throwable thr) {
|
||||
return CompletableFuture.failedFuture(thr);
|
||||
}
|
||||
}
|
||||
return super.read(pos);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Void> write(final ChunkPos pos, final CompoundTag tag) {
|
||||
if (!RegionFileIOThread.isRegionFileThread()) {
|
||||
RegionFileIOThread.scheduleSave(
|
||||
this.level, pos.x, pos.z, tag,
|
||||
RegionFileIOThread.RegionFileType.CHUNK_DATA);
|
||||
return null;
|
||||
}
|
||||
super.write(pos, tag);
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void flushWorker() {
|
||||
RegionFileIOThread.flush();
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason New player chunk loader handles this, and redirect to the distance map add
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Redirect(
|
||||
method = "updatePlayerStatus",
|
||||
at = @At(
|
||||
value = "INVOKE",
|
||||
target = "Lnet/minecraft/server/level/ChunkMap;updateChunkTracking(Lnet/minecraft/server/level/ServerPlayer;)V"
|
||||
)
|
||||
)
|
||||
private void avoidUpdateChunkTrackingInUpdate(final ChunkMap instance, final ServerPlayer serverPlayer) {
|
||||
ChunkSystem.addPlayerToDistanceMaps(this.level, serverPlayer);
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason updateChunkTracking is not needed, the player chunk loader has its own tick hook elsewhere
|
||||
* @author Spottedleaf
|
||||
* @see RegionizedPlayerChunkLoader#tick()
|
||||
*/
|
||||
@Redirect(
|
||||
method = "tick()V",
|
||||
at = @At(
|
||||
value = "INVOKE",
|
||||
target = "Lnet/minecraft/server/level/ChunkMap;updateChunkTracking(Lnet/minecraft/server/level/ServerPlayer;)V"
|
||||
)
|
||||
)
|
||||
private void skipChunkTrackingInTick(final ChunkMap instance, final ServerPlayer serverPlayer) {}
|
||||
|
||||
/**
|
||||
* @reason New player chunk loader handles this, and redirect to the distance map remove
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Redirect(
|
||||
method = "updatePlayerStatus",
|
||||
at = @At(
|
||||
value = "INVOKE",
|
||||
target = "Lnet/minecraft/server/level/ChunkMap;applyChunkTrackingView(Lnet/minecraft/server/level/ServerPlayer;Lnet/minecraft/server/level/ChunkTrackingView;)V"
|
||||
)
|
||||
)
|
||||
private void avoidApplyChunkTrackingViewInUpdate(final ChunkMap instance, final ServerPlayer serverPlayer,
|
||||
final ChunkTrackingView chunkTrackingView) {
|
||||
ChunkSystem.removePlayerFromDistanceMaps(this.level, serverPlayer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook into move call so that we can run callbacks on chunk position change
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Inject(
|
||||
method = "move",
|
||||
at = @At(
|
||||
value = "RETURN"
|
||||
)
|
||||
)
|
||||
private void updateMapsHook(final ServerPlayer player, final CallbackInfo ci) {
|
||||
ChunkSystem.updateMaps(this.level, player);
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason New player chunk loader handles this
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Redirect(
|
||||
method = "move",
|
||||
at = @At(
|
||||
value = "INVOKE",
|
||||
target = "Lnet/minecraft/server/level/ChunkMap;updateChunkTracking(Lnet/minecraft/server/level/ServerPlayer;)V"
|
||||
)
|
||||
)
|
||||
private void avoidSetChunkTrackingViewInMove(final ChunkMap instance, final ServerPlayer serverPlayer) {}
|
||||
|
||||
/**
|
||||
* @reason Destroy old chunk system hooks
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Overwrite
|
||||
public void updateChunkTracking(final ServerPlayer player) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Destroy old chunk system hooks
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Overwrite
|
||||
public void applyChunkTrackingView(final ServerPlayer player, final ChunkTrackingView chunkFilter) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Route to new player chunk loader
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Override
|
||||
@Overwrite
|
||||
public List<ServerPlayer> getPlayers(final ChunkPos chunkPos, final boolean onlyOnWatchDistanceEdge) {
|
||||
final ChunkHolder holder = this.getVisibleChunkIfPresent(chunkPos.toLong());
|
||||
if (holder == null) {
|
||||
return new ArrayList<>();
|
||||
} else {
|
||||
return ((ChunkSystemChunkHolder)holder).moonrise$getPlayers(onlyOnWatchDistanceEdge);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason See {@link ChunkHolderMixin#addSendDependency(CompletableFuture)}
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Overwrite
|
||||
public void waitForLightBeforeSending(final ChunkPos centerPos, final int radius) {}
|
||||
|
||||
/**
|
||||
* @reason Route to new chunk system
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Overwrite
|
||||
public int size() {
|
||||
return ((ChunkSystemServerLevel)this.level).moonrise$getChunkTaskScheduler().chunkHolderManager.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Route to new chunk system
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Overwrite
|
||||
public Iterable<ChunkHolder> getChunks() {
|
||||
return ((ChunkSystemServerLevel)this.level).moonrise$getChunkTaskScheduler().chunkHolderManager.getOldChunkHoldersIterable();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package ca.spottedleaf.moonrise.mixin.chunk_system;
|
||||
|
||||
import net.minecraft.core.SectionPos;
|
||||
import net.minecraft.world.entity.ai.village.poi.PoiManager;
|
||||
import net.minecraft.world.level.chunk.LevelChunkSection;
|
||||
import net.minecraft.world.level.chunk.storage.ChunkSerializer;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
|
||||
@Mixin(ChunkSerializer.class)
|
||||
public abstract class ChunkSerializerMixin {
|
||||
|
||||
/**
|
||||
* @reason Chunk system handles this during full transition
|
||||
* @author Spottedleaf
|
||||
* @see ca.spottedleaf.moonrise.patches.chunk_system.scheduling.task.ChunkFullTask
|
||||
*/
|
||||
@Redirect(
|
||||
method = "read",
|
||||
at = @At(
|
||||
value = "INVOKE",
|
||||
target = "Lnet/minecraft/world/entity/ai/village/poi/PoiManager;checkConsistencyWithBlocks(Lnet/minecraft/core/SectionPos;Lnet/minecraft/world/level/chunk/LevelChunkSection;)V"
|
||||
)
|
||||
)
|
||||
private static void skipConsistencyCheck(PoiManager instance, SectionPos sectionPos, LevelChunkSection levelChunkSection) {}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
package ca.spottedleaf.moonrise.mixin.chunk_system;
|
||||
|
||||
import ca.spottedleaf.moonrise.patches.chunk_system.level.chunk.ChunkSystemChunkStatus;
|
||||
import net.minecraft.world.level.chunk.status.ChunkStatus;
|
||||
import net.minecraft.world.level.chunk.status.ChunkStatusTasks;
|
||||
import net.minecraft.world.level.chunk.status.ChunkType;
|
||||
import net.minecraft.world.level.levelgen.Heightmap;
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
import java.util.EnumSet;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
@Mixin(ChunkStatus.class)
|
||||
public abstract class ChunkStatusMixin implements ChunkSystemChunkStatus {
|
||||
|
||||
@Unique
|
||||
private boolean isParallelCapable;
|
||||
|
||||
@Unique
|
||||
private boolean emptyLoadTask;
|
||||
|
||||
@Unique
|
||||
private int writeRadius;
|
||||
|
||||
@Unique
|
||||
private int loadRadius;
|
||||
|
||||
@Unique
|
||||
private ChunkStatus nextStatus;
|
||||
|
||||
@Unique
|
||||
private AtomicBoolean warnedAboutNoImmediateComplete;
|
||||
|
||||
@Override
|
||||
public final boolean moonrise$isParallelCapable() {
|
||||
return this.isParallelCapable;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void moonrise$setParallelCapable(final boolean value) {
|
||||
this.isParallelCapable = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final int moonrise$getWriteRadius() {
|
||||
return this.writeRadius;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void moonrise$setWriteRadius(final int value) {
|
||||
this.writeRadius = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final int moonrise$getLoadRadius() {
|
||||
return this.loadRadius;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void moonrise$setLoadRadius(final int value) {
|
||||
this.loadRadius = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final ChunkStatus moonrise$getNextStatus() {
|
||||
return this.nextStatus;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final boolean moonrise$isEmptyLoadStatus() {
|
||||
return this.emptyLoadTask;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void moonrise$setEmptyLoadStatus(final boolean value) {
|
||||
this.emptyLoadTask = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final boolean moonrise$isEmptyGenStatus() {
|
||||
return (Object)this == ChunkStatus.EMPTY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final AtomicBoolean moonrise$getWarnedAboutNoImmediateComplete() {
|
||||
return this.warnedAboutNoImmediateComplete;
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Initialise default values for fields and nextStatus
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Inject(
|
||||
method = "<init>",
|
||||
at = @At(
|
||||
value = "RETURN"
|
||||
)
|
||||
)
|
||||
private void initFields(ChunkStatus prevStatus, int i, boolean bl, EnumSet<Heightmap.Types> enumSet, ChunkType chunkType,
|
||||
ChunkStatus.GenerationTask generationTask, ChunkStatus.LoadingTask loadingTask,
|
||||
CallbackInfo ci) {
|
||||
this.isParallelCapable = false;
|
||||
this.writeRadius = -1;
|
||||
this.loadRadius = 0;
|
||||
this.nextStatus = (ChunkStatus)(Object)this;
|
||||
if (prevStatus != null) {
|
||||
((ChunkStatusMixin)(Object)prevStatus).nextStatus = (ChunkStatus)(Object)this;
|
||||
}
|
||||
this.warnedAboutNoImmediateComplete = new AtomicBoolean();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,191 @@
|
||||
package ca.spottedleaf.moonrise.mixin.chunk_system;
|
||||
|
||||
import ca.spottedleaf.moonrise.patches.chunk_system.storage.ChunkSystemChunkStorage;
|
||||
import com.mojang.logging.LogUtils;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.world.level.ChunkPos;
|
||||
import net.minecraft.world.level.chunk.storage.ChunkScanAccess;
|
||||
import net.minecraft.world.level.chunk.storage.ChunkStorage;
|
||||
import net.minecraft.world.level.chunk.storage.IOWorker;
|
||||
import net.minecraft.world.level.chunk.storage.RegionFileStorage;
|
||||
import net.minecraft.world.level.levelgen.structure.LegacyStructureDataHandler;
|
||||
import org.slf4j.Logger;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Overwrite;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
import java.io.IOException;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
@Mixin(ChunkStorage.class)
|
||||
public abstract class ChunkStorageMixin implements ChunkSystemChunkStorage, AutoCloseable {
|
||||
|
||||
@Shadow
|
||||
private IOWorker worker;
|
||||
|
||||
@Unique
|
||||
private static final Logger LOGGER = LogUtils.getLogger();
|
||||
|
||||
@Unique
|
||||
private RegionFileStorage storage;
|
||||
|
||||
/**
|
||||
* @reason Destroy old IO worker field after retrieving region storage from it
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Inject(
|
||||
method = "<init>",
|
||||
at = @At(
|
||||
value = "RETURN"
|
||||
)
|
||||
)
|
||||
private void initHook(final CallbackInfo ci) {
|
||||
this.storage = this.worker.storage;
|
||||
this.worker = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final RegionFileStorage moonrise$getRegionStorage() {
|
||||
return this.storage;
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason The code using this method only uses it to avoid retrieving possibly empty biome blending data. There is
|
||||
* no actual cost to retrieve this data, but there is an obvious significant penalty to loading the NBT data
|
||||
* from disk directly to check (even _if_ cached).
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Overwrite
|
||||
public boolean isOldChunkAround(final ChunkPos pos, final int radius) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @reason Legacy data is accessed by multiple threads, and so it should be synchronised correctly.
|
||||
* The initialisation code is oddly initialised correctly, but not the actual accesses after.
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Redirect(
|
||||
method = "upgradeChunkTag",
|
||||
at = @At(
|
||||
value = "INVOKE",
|
||||
target = "Lnet/minecraft/world/level/levelgen/structure/LegacyStructureDataHandler;updateFromLegacy(Lnet/minecraft/nbt/CompoundTag;)Lnet/minecraft/nbt/CompoundTag;"
|
||||
)
|
||||
)
|
||||
private CompoundTag synchroniseLegacyDataUpgrade(final LegacyStructureDataHandler instance, final CompoundTag compoundTag) {
|
||||
synchronized (instance) {
|
||||
return instance.updateFromLegacy(compoundTag);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Redirect to use the raw storage. It is expected that {@link net.minecraft.server.level.ChunkMap}
|
||||
* overrides to route to the RegionFile IO thread, as ChunkStorage may be initialised directly when
|
||||
* forceUpgrading. The IO threads are not capable of servicing requests during forceUpgrading, as a
|
||||
* world is not initialised.
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Redirect(
|
||||
method = "read",
|
||||
at = @At(
|
||||
value = "INVOKE",
|
||||
target = "Lnet/minecraft/world/level/chunk/storage/IOWorker;loadAsync(Lnet/minecraft/world/level/ChunkPos;)Ljava/util/concurrent/CompletableFuture;"
|
||||
)
|
||||
)
|
||||
private CompletableFuture<Optional<CompoundTag>> redirectLoad(final IOWorker instance, final ChunkPos chunkPos) {
|
||||
try {
|
||||
return CompletableFuture.completedFuture(Optional.ofNullable(this.storage.read(chunkPos)));
|
||||
} catch (final Throwable throwable) {
|
||||
return CompletableFuture.failedFuture(throwable);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Redirect to use the raw storage. It is expected that {@link net.minecraft.server.level.ChunkMap}
|
||||
* overrides to route to the RegionFile IO thread, as ChunkStorage may be initialised directly when
|
||||
* forceUpgrading. The IO threads are not capable of servicing requests during forceUpgrading, as a
|
||||
* world is not initialised.
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Redirect(
|
||||
method = "write",
|
||||
at = @At(
|
||||
value = "INVOKE",
|
||||
target = "Lnet/minecraft/world/level/chunk/storage/IOWorker;store(Lnet/minecraft/world/level/ChunkPos;Lnet/minecraft/nbt/CompoundTag;)Ljava/util/concurrent/CompletableFuture;"
|
||||
)
|
||||
)
|
||||
private CompletableFuture<Void> redirectWrite(final IOWorker instance, final ChunkPos chunkPos,
|
||||
final CompoundTag compoundTag) {
|
||||
try {
|
||||
this.storage.write(chunkPos, compoundTag);
|
||||
return CompletableFuture.completedFuture(null);
|
||||
} catch (final Throwable throwable) {
|
||||
return CompletableFuture.failedFuture(throwable);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Legacy data is accessed by multiple threads, and so it should be synchronised correctly.
|
||||
* The initialisation code is oddly initialised correctly, but not the actual accesses after.
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Redirect(
|
||||
method = "handleLegacyStructureIndex",
|
||||
at = @At(
|
||||
value = "INVOKE",
|
||||
target = "Lnet/minecraft/world/level/levelgen/structure/LegacyStructureDataHandler;removeIndex(J)V"
|
||||
)
|
||||
)
|
||||
private void synchroniseLegacyDataWrite(final LegacyStructureDataHandler instance,
|
||||
final long pos) {
|
||||
synchronized (instance) {
|
||||
instance.removeIndex(pos);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Redirect to flush the storage directly
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Overwrite
|
||||
public void flushWorker() {
|
||||
try {
|
||||
this.storage.flush();
|
||||
} catch (final IOException ex) {
|
||||
LOGGER.error("Failed to flush chunk storage", ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Redirect to close the storage directly
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Override
|
||||
@Overwrite
|
||||
public void close() throws Exception {
|
||||
this.storage.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Redirect to access the storage directly
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Overwrite
|
||||
public ChunkScanAccess chunkScanner() {
|
||||
// TODO ChunkMap implementation?
|
||||
return (chunkPos, streamTagVisitor) -> {
|
||||
try {
|
||||
this.storage.scanChunk(chunkPos, streamTagVisitor);
|
||||
return java.util.concurrent.CompletableFuture.completedFuture(null);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
package ca.spottedleaf.moonrise.mixin.chunk_system;
|
||||
|
||||
import ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemLevel;
|
||||
import ca.spottedleaf.moonrise.patches.chunk_system.level.entity.client.ClientEntityLookup;
|
||||
import net.minecraft.client.multiplayer.ClientLevel;
|
||||
import net.minecraft.client.multiplayer.ClientPacketListener;
|
||||
import net.minecraft.client.renderer.LevelRenderer;
|
||||
import net.minecraft.core.Holder;
|
||||
import net.minecraft.core.RegistryAccess;
|
||||
import net.minecraft.resources.ResourceKey;
|
||||
import net.minecraft.util.profiling.ProfilerFiller;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.level.ChunkPos;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.dimension.DimensionType;
|
||||
import net.minecraft.world.level.entity.EntityAccess;
|
||||
import net.minecraft.world.level.entity.LevelEntityGetter;
|
||||
import net.minecraft.world.level.entity.TransientEntitySectionManager;
|
||||
import net.minecraft.world.level.storage.WritableLevelData;
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Overwrite;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
@Mixin(ClientLevel.class)
|
||||
public abstract class ClientLevelMixin extends Level implements ChunkSystemLevel {
|
||||
|
||||
@Shadow
|
||||
private TransientEntitySectionManager<Entity> entityStorage;
|
||||
|
||||
protected ClientLevelMixin(WritableLevelData writableLevelData, ResourceKey<Level> resourceKey, RegistryAccess registryAccess, Holder<DimensionType> holder, Supplier<ProfilerFiller> supplier, boolean bl, boolean bl2, long l, int i) {
|
||||
super(writableLevelData, resourceKey, registryAccess, holder, supplier, bl, bl2, l, i);
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Initialise fields / destroy entity manager state
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Inject(
|
||||
method = "<init>",
|
||||
at = @At(
|
||||
value = "RETURN"
|
||||
)
|
||||
)
|
||||
private void init(ClientPacketListener clientPacketListener, ClientLevel.ClientLevelData clientLevelData,
|
||||
ResourceKey<Level> resourceKey, Holder<DimensionType> holder, int i, int j, Supplier<ProfilerFiller> supplier,
|
||||
LevelRenderer levelRenderer, boolean bl, long l, CallbackInfo ci) {
|
||||
this.entityStorage = null;
|
||||
|
||||
this.moonrise$setEntityLookup(new ClientEntityLookup(this, ((ClientLevel)(Object)this).new EntityCallbacks()));
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Redirect to new entity manager
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Overwrite
|
||||
public int getEntityCount() {
|
||||
return this.moonrise$getEntityLookup().getEntityCount();
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Redirect to new entity manager
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Redirect(
|
||||
method = "addEntity",
|
||||
at = @At(
|
||||
value = "INVOKE",
|
||||
target = "Lnet/minecraft/world/level/entity/TransientEntitySectionManager;addEntity(Lnet/minecraft/world/level/entity/EntityAccess;)V"
|
||||
)
|
||||
)
|
||||
private <T extends EntityAccess> void addEntityHook(final TransientEntitySectionManager<T> instance, final T entityAccess) {
|
||||
this.moonrise$getEntityLookup().addNewEntity((Entity)entityAccess);
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Redirect to new entity manager
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Redirect(
|
||||
method = "getEntities()Lnet/minecraft/world/level/entity/LevelEntityGetter;",
|
||||
at = @At(
|
||||
value = "INVOKE",
|
||||
target = "Lnet/minecraft/world/level/entity/TransientEntitySectionManager;getEntityGetter()Lnet/minecraft/world/level/entity/LevelEntityGetter;"
|
||||
)
|
||||
)
|
||||
private LevelEntityGetter<Entity> redirectGetEntities(final TransientEntitySectionManager<Entity> instance) {
|
||||
return this.moonrise$getEntityLookup();
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Redirect to new entity manager
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Redirect(
|
||||
method = "gatherChunkSourceStats",
|
||||
at = @At(
|
||||
value = "INVOKE",
|
||||
target = "Lnet/minecraft/world/level/entity/TransientEntitySectionManager;gatherStats()Ljava/lang/String;"
|
||||
)
|
||||
)
|
||||
private String redirectGatherChunkSourceStats(final TransientEntitySectionManager<Entity> instance) {
|
||||
return this.moonrise$getEntityLookup().getDebugInfo();
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Redirect to new entity manager
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Redirect(
|
||||
method = "unload",
|
||||
at = @At(
|
||||
value = "INVOKE",
|
||||
target = "Lnet/minecraft/world/level/entity/TransientEntitySectionManager;stopTicking(Lnet/minecraft/world/level/ChunkPos;)V"
|
||||
)
|
||||
)
|
||||
private <T extends EntityAccess> void chunkUnloadHook(final TransientEntitySectionManager<T> instance,
|
||||
final ChunkPos pos) {
|
||||
((ClientEntityLookup)this.moonrise$getEntityLookup()).markNonTicking(pos.toLong());
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Redirect to new entity manager
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Redirect(
|
||||
method = "onChunkLoaded",
|
||||
at = @At(
|
||||
value = "INVOKE",
|
||||
target = "Lnet/minecraft/world/level/entity/TransientEntitySectionManager;startTicking(Lnet/minecraft/world/level/ChunkPos;)V"
|
||||
)
|
||||
)
|
||||
private <T extends EntityAccess> void chunkLoadHook(final TransientEntitySectionManager<T> instance, final ChunkPos pos) {
|
||||
((ClientEntityLookup)this.moonrise$getEntityLookup()).markTicking(pos.toLong());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,342 @@
|
||||
package ca.spottedleaf.moonrise.mixin.chunk_system;
|
||||
|
||||
import ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel;
|
||||
import ca.spottedleaf.moonrise.patches.chunk_system.level.chunk.ChunkSystemDistanceManager;
|
||||
import ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkHolderManager;
|
||||
import ca.spottedleaf.moonrise.patches.chunk_system.scheduling.NewChunkHolder;
|
||||
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.longs.LongSet;
|
||||
import net.minecraft.server.level.ChunkHolder;
|
||||
import net.minecraft.server.level.ChunkLevel;
|
||||
import net.minecraft.server.level.ChunkMap;
|
||||
import net.minecraft.server.level.ChunkTaskPriorityQueueSorter;
|
||||
import net.minecraft.server.level.DistanceManager;
|
||||
import net.minecraft.server.level.FullChunkStatus;
|
||||
import net.minecraft.server.level.Ticket;
|
||||
import net.minecraft.server.level.TicketType;
|
||||
import net.minecraft.server.level.TickingTracker;
|
||||
import net.minecraft.util.SortedArraySet;
|
||||
import net.minecraft.util.thread.ProcessorHandle;
|
||||
import net.minecraft.world.level.ChunkPos;
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Overwrite;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
@Mixin(DistanceManager.class)
|
||||
public abstract class DistanceManagerMixin implements ChunkSystemDistanceManager {
|
||||
|
||||
@Shadow
|
||||
Long2ObjectOpenHashMap<SortedArraySet<Ticket<?>>> tickets;
|
||||
|
||||
@Shadow
|
||||
private DistanceManager.ChunkTicketTracker ticketTracker;
|
||||
|
||||
@Shadow
|
||||
private TickingTracker tickingTicketsTracker;
|
||||
|
||||
@Shadow
|
||||
private DistanceManager.PlayerTicketTracker playerTicketManager;
|
||||
|
||||
@Shadow
|
||||
Set<ChunkHolder> chunksToUpdateFutures;
|
||||
|
||||
@Shadow
|
||||
ChunkTaskPriorityQueueSorter ticketThrottler;
|
||||
|
||||
@Shadow
|
||||
ProcessorHandle<ChunkTaskPriorityQueueSorter.Message<Runnable>> ticketThrottlerInput;
|
||||
|
||||
@Shadow
|
||||
ProcessorHandle<ChunkTaskPriorityQueueSorter.Release> ticketThrottlerReleaser;
|
||||
|
||||
@Shadow
|
||||
LongSet ticketsToRelease;
|
||||
|
||||
@Shadow
|
||||
Executor mainThreadExecutor;
|
||||
|
||||
@Shadow
|
||||
@Final
|
||||
private DistanceManager.FixedPlayerDistanceChunkTracker naturalSpawnChunkCounter;
|
||||
|
||||
@Shadow
|
||||
private int simulationDistance;
|
||||
|
||||
@Override
|
||||
public ChunkMap moonrise$getChunkMap() {
|
||||
throw new AbstractMethodError();
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Destroy old chunk system state to prevent it from being used
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Inject(
|
||||
method = "<init>",
|
||||
at = @At(
|
||||
value = "RETURN"
|
||||
)
|
||||
)
|
||||
private void destroyFields(final Executor executor, final Executor executor2,
|
||||
final CallbackInfo ci) {
|
||||
this.tickets = null;
|
||||
this.ticketTracker = null;
|
||||
this.tickingTicketsTracker = null;
|
||||
this.playerTicketManager = null;
|
||||
this.chunksToUpdateFutures = null;
|
||||
this.ticketThrottler = null;
|
||||
this.ticketThrottlerInput = null;
|
||||
this.ticketThrottlerReleaser = null;
|
||||
this.ticketsToRelease = null;
|
||||
this.mainThreadExecutor = null;
|
||||
this.simulationDistance = -1;
|
||||
}
|
||||
|
||||
@Unique
|
||||
private ChunkHolderManager getChunkHolderManager() {
|
||||
return ((ChunkSystemServerLevel)this.moonrise$getChunkMap().level).moonrise$getChunkTaskScheduler().chunkHolderManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Route to new chunk system
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Overwrite
|
||||
public void purgeStaleTickets() {
|
||||
this.getChunkHolderManager().tick();
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Route to new chunk system
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Overwrite
|
||||
public boolean runAllUpdates(final ChunkMap chunkStorage) {
|
||||
return this.getChunkHolderManager().processTicketUpdates();
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Route to new chunk system
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Overwrite
|
||||
public void addTicket(final long pos, final Ticket<?> ticket) {
|
||||
this.getChunkHolderManager().addTicketAtLevel((TicketType)ticket.getType(), pos, ticket.getTicketLevel(), ticket.key);
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Route to new chunk system
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Overwrite
|
||||
public void removeTicket(final long pos, final Ticket<?> ticket) {
|
||||
this.getChunkHolderManager().removeTicketAtLevel((TicketType)ticket.getType(), pos, ticket.getTicketLevel(), ticket.key);
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Route to new chunk system
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Overwrite
|
||||
public <T> void addRegionTicket(final TicketType<T> type, final ChunkPos pos, final int radius, final T identifier) {
|
||||
this.getChunkHolderManager().addTicketAtLevel(type, pos, ChunkLevel.byStatus(FullChunkStatus.FULL) - radius, identifier);
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Route to new chunk system
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Overwrite
|
||||
public <T> void removeRegionTicket(final TicketType<T> type, final ChunkPos pos, final int radius, final T identifier) {
|
||||
this.getChunkHolderManager().removeTicketAtLevel(type, pos, ChunkLevel.byStatus(FullChunkStatus.FULL) - radius, identifier);
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Route to new chunk system
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Overwrite
|
||||
public void updateChunkForced(final ChunkPos pos, final boolean forced) {
|
||||
if (forced) {
|
||||
this.getChunkHolderManager().addTicketAtLevel(TicketType.FORCED, pos, ChunkMap.FORCED_TICKET_LEVEL, pos);
|
||||
} else {
|
||||
this.getChunkHolderManager().removeTicketAtLevel(TicketType.FORCED, pos, ChunkMap.FORCED_TICKET_LEVEL, pos);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Remove old chunk system hooks
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Redirect(
|
||||
method = "addPlayer",
|
||||
at = @At(
|
||||
value = "INVOKE",
|
||||
target = "Lnet/minecraft/server/level/DistanceManager$PlayerTicketTracker;update(JIZ)V"
|
||||
)
|
||||
)
|
||||
private void skipTickingTicketTrackerAdd(final DistanceManager.PlayerTicketTracker instance, final long l,
|
||||
final int i, final boolean b) {}
|
||||
|
||||
/**
|
||||
* @reason Remove old chunk system hooks
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Redirect(
|
||||
method = "addPlayer",
|
||||
at = @At(
|
||||
value = "INVOKE",
|
||||
target = "Lnet/minecraft/server/level/TickingTracker;addTicket(Lnet/minecraft/server/level/TicketType;Lnet/minecraft/world/level/ChunkPos;ILjava/lang/Object;)V"
|
||||
)
|
||||
)
|
||||
private <T> void skipTickingTicketTrackerAdd(final TickingTracker instance, final TicketType<T> ticketType,
|
||||
final ChunkPos chunkPos, final int i, final T object) {}
|
||||
|
||||
/**
|
||||
* @reason Remove old chunk system hooks
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Redirect(
|
||||
method = "addPlayer",
|
||||
at = @At(
|
||||
value = "INVOKE",
|
||||
target = "Lnet/minecraft/server/level/DistanceManager;getPlayerTicketLevel()I"
|
||||
)
|
||||
)
|
||||
private int skipTicketLevelAdd(final DistanceManager instance) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Remove old chunk system hooks
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Redirect(
|
||||
method = "removePlayer",
|
||||
at = @At(
|
||||
value = "INVOKE",
|
||||
target = "Lnet/minecraft/server/level/DistanceManager$PlayerTicketTracker;update(JIZ)V"
|
||||
)
|
||||
)
|
||||
private void skipTickingTicketTrackerRemove(final DistanceManager.PlayerTicketTracker instance, final long l,
|
||||
final int i, final boolean b) {}
|
||||
|
||||
/**
|
||||
* @reason Remove old chunk system hooks
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Redirect(
|
||||
method = "removePlayer",
|
||||
at = @At(
|
||||
value = "INVOKE",
|
||||
target = "Lnet/minecraft/server/level/TickingTracker;removeTicket(Lnet/minecraft/server/level/TicketType;Lnet/minecraft/world/level/ChunkPos;ILjava/lang/Object;)V"
|
||||
)
|
||||
)
|
||||
private <T> void skipTickingTicketTrackerRemove(final TickingTracker instance, final TicketType<T> ticketType,
|
||||
final ChunkPos chunkPos, final int i, final T object) {}
|
||||
|
||||
/**
|
||||
* @reason Remove old chunk system hooks
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Redirect(
|
||||
method = "removePlayer",
|
||||
at = @At(
|
||||
value = "INVOKE",
|
||||
target = "Lnet/minecraft/server/level/DistanceManager;getPlayerTicketLevel()I"
|
||||
)
|
||||
)
|
||||
private int skipTicketLevelRemove(final DistanceManager instance) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Destroy old chunk system hooks
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Overwrite
|
||||
public int getPlayerTicketLevel() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Route to new chunk system
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Overwrite
|
||||
public boolean inEntityTickingRange(final long pos) {
|
||||
final NewChunkHolder chunkHolder = this.getChunkHolderManager().getChunkHolder(pos);
|
||||
return chunkHolder != null && chunkHolder.isEntityTickingReady();
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Route to new chunk system
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Overwrite
|
||||
public boolean inBlockTickingRange(final long pos) {
|
||||
final NewChunkHolder chunkHolder = this.getChunkHolderManager().getChunkHolder(pos);
|
||||
return chunkHolder != null && chunkHolder.isTickingReady();
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Route to new chunk system
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Overwrite
|
||||
public String getTicketDebugString(final long pos) {
|
||||
return this.getChunkHolderManager().getTicketDebugString(pos);
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Route to new chunk system
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Overwrite
|
||||
public void updatePlayerTickets(final int viewDistance) {
|
||||
this.moonrise$getChunkMap().setServerViewDistance(viewDistance);
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Route to new chunk system
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Overwrite
|
||||
public void updateSimulationDistance(final int simulationDistance) {
|
||||
((ChunkSystemServerLevel)this.moonrise$getChunkMap().level).moonrise$getPlayerChunkLoader().setTickDistance(simulationDistance);
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Route to new chunk system
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Overwrite
|
||||
public String getDebugStatus() {
|
||||
return "No DistanceManager stats available";
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason This hack is not required anymore, see {@link MinecraftServerMixin}
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Overwrite
|
||||
public void removeTicketsOnClosing() {}
|
||||
|
||||
/**
|
||||
* @reason This hack is not required anymore, see {@link MinecraftServerMixin}
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Overwrite
|
||||
public boolean hasTickets() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package ca.spottedleaf.moonrise.mixin.chunk_system;
|
||||
|
||||
import ca.spottedleaf.moonrise.patches.chunk_system.world.ChunkSystemEntityGetter;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.level.EntityGetter;
|
||||
import net.minecraft.world.phys.AABB;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import java.util.List;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
@Mixin(EntityGetter.class)
|
||||
public interface EntityGetterMixin extends ChunkSystemEntityGetter {
|
||||
|
||||
@Shadow
|
||||
List<Entity> getEntities(Entity entity, AABB aABB, Predicate<? super Entity> predicate);
|
||||
|
||||
@Override
|
||||
default List<Entity> moonrise$getHardCollidingEntities(final Entity entity, final AABB box, final Predicate<? super Entity> predicate) {
|
||||
return this.getEntities(entity, box, predicate);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,215 @@
|
||||
package ca.spottedleaf.moonrise.mixin.chunk_system;
|
||||
|
||||
import ca.spottedleaf.moonrise.patches.chunk_system.entity.ChunkSystemEntity;
|
||||
import ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemLevel;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import net.minecraft.server.level.FullChunkStatus;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
@Mixin(Entity.class)
|
||||
public abstract class EntityMixin implements ChunkSystemEntity {
|
||||
|
||||
@Shadow
|
||||
private ImmutableList<Entity> passengers;
|
||||
|
||||
@Shadow
|
||||
protected abstract Stream<Entity> getIndirectPassengersStream();
|
||||
|
||||
@Shadow
|
||||
@Final
|
||||
private static Logger LOGGER;
|
||||
|
||||
@Shadow
|
||||
private Level level;
|
||||
|
||||
@Shadow
|
||||
@Nullable
|
||||
private Entity.RemovalReason removalReason;
|
||||
|
||||
|
||||
@Unique
|
||||
private final boolean isHardColliding = this.moonrise$isHardCollidingUncached();
|
||||
|
||||
@Unique
|
||||
private FullChunkStatus chunkStatus;
|
||||
|
||||
@Unique
|
||||
private int sectionX;
|
||||
|
||||
@Unique
|
||||
private int sectionY;
|
||||
|
||||
@Unique
|
||||
private int sectionZ;
|
||||
|
||||
@Unique
|
||||
private boolean updatingSectionStatus;
|
||||
|
||||
@Override
|
||||
public final boolean moonrise$isHardColliding() {
|
||||
return this.isHardColliding;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final FullChunkStatus moonrise$getChunkStatus() {
|
||||
return this.chunkStatus;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void moonrise$setChunkStatus(final FullChunkStatus status) {
|
||||
this.chunkStatus = status;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final int moonrise$getSectionX() {
|
||||
return this.sectionX;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void moonrise$setSectionX(final int x) {
|
||||
this.sectionX = x;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final int moonrise$getSectionY() {
|
||||
return this.sectionY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void moonrise$setSectionY(final int y) {
|
||||
this.sectionY = y;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final int moonrise$getSectionZ() {
|
||||
return this.sectionZ;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void moonrise$setSectionZ(final int z) {
|
||||
this.sectionZ = z;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final boolean moonrise$isUpdatingSectionStatus() {
|
||||
return this.updatingSectionStatus;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void moonrise$setUpdatingSectionStatus(final boolean to) {
|
||||
this.updatingSectionStatus = to;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final boolean moonrise$hasAnyPlayerPassengers() {
|
||||
if (this.passengers.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
return this.getIndirectPassengersStream().anyMatch((entity) -> entity instanceof Player);
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Initialise fields
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Inject(
|
||||
method = "<init>",
|
||||
at = @At(
|
||||
value = "RETURN"
|
||||
)
|
||||
)
|
||||
private void initHook(final CallbackInfo ci) {
|
||||
this.sectionX = this.sectionY = this.sectionZ = Integer.MIN_VALUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Stop bad mods from moving entities during section status updates, which otherwise would cause CMEs
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Inject(
|
||||
method = "setPosRaw",
|
||||
cancellable = true,
|
||||
at = @At(
|
||||
value = "HEAD"
|
||||
)
|
||||
)
|
||||
private void checkUpdatingStatusPoi(final double x, final double y, final double z, final CallbackInfo ci) {
|
||||
if (this.updatingSectionStatus) {
|
||||
LOGGER.error(
|
||||
"Refusing to update position for entity " + this + " to position " + new Vec3(x, y, z)
|
||||
+ " since it is processing a section status update", new Throwable()
|
||||
);
|
||||
ci.cancel();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Stop bad mods from removing entities during section status updates, which otherwise would cause CMEs
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Inject(
|
||||
method = "setRemoved",
|
||||
cancellable = true,
|
||||
at = @At(
|
||||
value = "HEAD"
|
||||
)
|
||||
)
|
||||
private void checkCanRemove(final CallbackInfo ci) {
|
||||
if (!((ChunkSystemLevel)this.level).moonrise$getEntityLookup().canRemoveEntity((Entity)(Object)this)) {
|
||||
LOGGER.warn("Entity " + this + " is currently prevented from being removed from the world since it is processing section status updates", new Throwable());
|
||||
ci.cancel();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Don't adjust passenger state when unloading, it's just not safe (and messes with our logic in entity chunk unload)
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Redirect(
|
||||
method = "setRemoved",
|
||||
at = @At(
|
||||
value = "INVOKE",
|
||||
target = "Ljava/util/List;forEach(Ljava/util/function/Consumer;)V"
|
||||
)
|
||||
)
|
||||
private void avoidDismountOnUnload(final List<Entity> instance, final Consumer<? super Entity> consumer) {
|
||||
if (this.removalReason == Entity.RemovalReason.UNLOADED_TO_CHUNK) {
|
||||
return;
|
||||
}
|
||||
|
||||
instance.forEach(consumer);
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason We should not save entities with any player passengers
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Redirect(
|
||||
method = "shouldBeSaved",
|
||||
at = @At(
|
||||
value = "INVOKE",
|
||||
target = "Lnet/minecraft/world/entity/Entity;hasExactlyOnePlayerPassenger()Z"
|
||||
)
|
||||
)
|
||||
private boolean properlyCheckPlayers(final Entity instance) {
|
||||
return ((ChunkSystemEntity)instance).moonrise$hasAnyPlayerPassengers();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
package ca.spottedleaf.moonrise.mixin.chunk_system;
|
||||
|
||||
import ca.spottedleaf.moonrise.common.list.IteratorSafeOrderedReferenceSet;
|
||||
import ca.spottedleaf.moonrise.common.util.TickThread;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.level.entity.EntityTickList;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Overwrite;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
@Mixin(EntityTickList.class)
|
||||
public abstract class EntityTickListMixin {
|
||||
|
||||
@Shadow
|
||||
private Int2ObjectMap<Entity> active;
|
||||
|
||||
@Shadow
|
||||
private Int2ObjectMap<Entity> passive;
|
||||
|
||||
@Unique
|
||||
private IteratorSafeOrderedReferenceSet<Entity> entities;
|
||||
|
||||
/**
|
||||
* @reason Initialise new fields and destroy old state
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Inject(
|
||||
method = "<init>",
|
||||
at = @At(
|
||||
value = "RETURN"
|
||||
)
|
||||
)
|
||||
private void initHook(final CallbackInfo ci) {
|
||||
this.active = null;
|
||||
this.passive = null;
|
||||
this.entities = new IteratorSafeOrderedReferenceSet<>();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @reason Do not delay removals
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Overwrite
|
||||
public void ensureActiveIsNotIterated() {}
|
||||
|
||||
/**
|
||||
* @reason Route to new entity list
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Redirect(
|
||||
method = "add",
|
||||
at = @At(
|
||||
value = "INVOKE",
|
||||
target = "Lit/unimi/dsi/fastutil/ints/Int2ObjectMap;put(ILjava/lang/Object;)Ljava/lang/Object;"
|
||||
)
|
||||
)
|
||||
private <V> V hookAdd(final Int2ObjectMap<V> instance, final int key, final V value) {
|
||||
this.entities.add((Entity)value);
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Route to new entity list
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Inject(
|
||||
method = "remove",
|
||||
at = @At(
|
||||
value = "INVOKE",
|
||||
target = "Lit/unimi/dsi/fastutil/ints/Int2ObjectMap;remove(I)Ljava/lang/Object;"
|
||||
)
|
||||
)
|
||||
private void hookRemove(final Entity entity, final CallbackInfo ci) {
|
||||
this.entities.remove(entity);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @reason Avoid NPE on accessing old state
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Redirect(
|
||||
method = "remove",
|
||||
at = @At(
|
||||
value = "INVOKE",
|
||||
target = "Lit/unimi/dsi/fastutil/ints/Int2ObjectMap;remove(I)Ljava/lang/Object;"
|
||||
)
|
||||
)
|
||||
private <V> V hookRemoveAvoidNPE(final Int2ObjectMap<V> instance, final int key) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Route to new entity list
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Overwrite
|
||||
public boolean contains(Entity entity) {
|
||||
return this.entities.contains(entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Route to new entity list
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Overwrite
|
||||
public void forEach(final Consumer<Entity> action) {
|
||||
// To ensure nothing weird happens with dimension travelling, do not iterate over new entries...
|
||||
// (by dfl iterator() is configured to not iterate over new entries)
|
||||
final IteratorSafeOrderedReferenceSet.Iterator<Entity> iterator = this.entities.iterator();
|
||||
try {
|
||||
while (iterator.hasNext()) {
|
||||
action.accept(iterator.next());
|
||||
}
|
||||
} finally {
|
||||
iterator.finishedIterating();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
package ca.spottedleaf.moonrise.mixin.chunk_system;
|
||||
|
||||
import ca.spottedleaf.moonrise.patches.chunk_system.level.chunk.ChunkSystemLevelChunk;
|
||||
import ca.spottedleaf.moonrise.patches.chunk_system.ticks.ChunkSystemLevelChunkTicks;
|
||||
import net.minecraft.core.Registry;
|
||||
import net.minecraft.world.level.ChunkPos;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.LevelHeightAccessor;
|
||||
import net.minecraft.world.level.biome.Biome;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import net.minecraft.world.level.chunk.ChunkAccess;
|
||||
import net.minecraft.world.level.chunk.LevelChunk;
|
||||
import net.minecraft.world.level.chunk.LevelChunkSection;
|
||||
import net.minecraft.world.level.chunk.UpgradeData;
|
||||
import net.minecraft.world.level.levelgen.blending.BlendingData;
|
||||
import net.minecraft.world.level.material.Fluid;
|
||||
import net.minecraft.world.ticks.LevelChunkTicks;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
@Mixin(LevelChunk.class)
|
||||
public abstract class LevelChunkMixin extends ChunkAccess implements ChunkSystemLevelChunk {
|
||||
|
||||
@Shadow
|
||||
@Final
|
||||
private LevelChunkTicks<Block> blockTicks;
|
||||
|
||||
@Shadow
|
||||
@Final
|
||||
private LevelChunkTicks<Fluid> fluidTicks;
|
||||
|
||||
@Shadow
|
||||
@Final
|
||||
Level level;
|
||||
|
||||
public LevelChunkMixin(ChunkPos chunkPos, UpgradeData upgradeData, LevelHeightAccessor levelHeightAccessor, Registry<Biome> registry, long l, @Nullable LevelChunkSection[] levelChunkSections, @Nullable BlendingData blendingData) {
|
||||
super(chunkPos, upgradeData, levelHeightAccessor, registry, l, levelChunkSections, blendingData);
|
||||
}
|
||||
|
||||
@Unique
|
||||
private boolean postProcessingDone;
|
||||
|
||||
@Override
|
||||
public final boolean moonrise$isPostProcessingDone() {
|
||||
return this.postProcessingDone;
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Hook to set {@link #postProcessingDone} to {@code true} when post-processing completes to avoid invoking
|
||||
* this function many times by the player chunk loader.
|
||||
* @author Spottedlef
|
||||
*/
|
||||
@Inject(
|
||||
method = "postProcessGeneration",
|
||||
at = @At(
|
||||
value = "RETURN"
|
||||
)
|
||||
)
|
||||
private void finishPostProcessing(final CallbackInfo ci) {
|
||||
this.postProcessingDone = true;
|
||||
}
|
||||
|
||||
// add support for dirty scheduled chunk ticks
|
||||
@Override
|
||||
public boolean isUnsaved() {
|
||||
final long gameTime = this.level.getGameTime();
|
||||
if (((ChunkSystemLevelChunkTicks)this.blockTicks).moonrise$isDirty(gameTime)
|
||||
|| ((ChunkSystemLevelChunkTicks)this.fluidTicks).moonrise$isDirty(gameTime)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return super.isUnsaved();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUnsaved(final boolean needsSaving) {
|
||||
if (!needsSaving) {
|
||||
((ChunkSystemLevelChunkTicks)this.blockTicks).moonrise$clearDirty();
|
||||
((ChunkSystemLevelChunkTicks)this.fluidTicks).moonrise$clearDirty();
|
||||
}
|
||||
super.setUnsaved(needsSaving);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
package ca.spottedleaf.moonrise.mixin.chunk_system;
|
||||
|
||||
import ca.spottedleaf.moonrise.patches.chunk_system.ticks.ChunkSystemLevelChunkTicks;
|
||||
import net.minecraft.nbt.ListTag;
|
||||
import net.minecraft.world.ticks.LevelChunkTicks;
|
||||
import net.minecraft.world.ticks.SavedTick;
|
||||
import net.minecraft.world.ticks.ScheduledTick;
|
||||
import net.minecraft.world.ticks.SerializableTickContainer;
|
||||
import net.minecraft.world.ticks.TickContainerAccess;
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||
import java.util.List;
|
||||
import java.util.Queue;
|
||||
import java.util.function.Function;
|
||||
|
||||
@Mixin(LevelChunkTicks.class)
|
||||
public abstract class LevelChunkTicksMixin<T> implements ChunkSystemLevelChunkTicks, SerializableTickContainer<T>, TickContainerAccess<T> {
|
||||
|
||||
@Shadow
|
||||
@Final
|
||||
private Queue<ScheduledTick<T>> tickQueue;
|
||||
|
||||
@Shadow
|
||||
private List<SavedTick<T>> pendingTicks;
|
||||
|
||||
/*
|
||||
* Since ticks are saved using relative delays, we need to consider the entire tick list dirty when there are scheduled ticks
|
||||
* and the last saved tick is not equal to the current tick
|
||||
*/
|
||||
/*
|
||||
* In general, it would be nice to be able to "re-pack" ticks once the chunk becomes non-ticking again, but that is a
|
||||
* bit out of scope for the chunk system
|
||||
*/
|
||||
|
||||
|
||||
@Unique
|
||||
private boolean dirty;
|
||||
|
||||
@Unique
|
||||
private long lastSaved;
|
||||
|
||||
/**
|
||||
* @reason Hook to init fields
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Inject(
|
||||
method = "<init>()V",
|
||||
at = @At(
|
||||
value = "RETURN"
|
||||
)
|
||||
)
|
||||
private void init(final CallbackInfo ci) {
|
||||
this.lastSaved = Long.MIN_VALUE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final boolean moonrise$isDirty(final long tick) {
|
||||
return this.dirty || (!this.tickQueue.isEmpty() && tick != this.lastSaved);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void moonrise$clearDirty() {
|
||||
this.dirty = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Set dirty when a scheduled tick is removed
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Inject(
|
||||
method = "poll",
|
||||
at = @At(
|
||||
value = "INVOKE",
|
||||
target = "Ljava/util/Set;remove(Ljava/lang/Object;)Z"
|
||||
)
|
||||
)
|
||||
private void pollHook(final CallbackInfoReturnable<ScheduledTick<T>> cir) {
|
||||
this.dirty = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Set dirty when a tick is scheduled
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Inject(
|
||||
method = "schedule",
|
||||
at = @At(
|
||||
value = "INVOKE",
|
||||
target = "Lnet/minecraft/world/ticks/LevelChunkTicks;scheduleUnchecked(Lnet/minecraft/world/ticks/ScheduledTick;)V"
|
||||
)
|
||||
)
|
||||
private void scheduleHook(final CallbackInfo ci) {
|
||||
this.dirty = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Set dirty when a tick is removed
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Inject(
|
||||
method = "removeIf",
|
||||
at = @At(
|
||||
value = "INVOKE",
|
||||
target = "Ljava/util/Iterator;remove()V"
|
||||
)
|
||||
)
|
||||
private void removeHook(final CallbackInfo ci) {
|
||||
this.dirty = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Update last save tick
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Inject(
|
||||
method = "save(JLjava/util/function/Function;)Lnet/minecraft/nbt/ListTag;",
|
||||
at = @At(
|
||||
value = "HEAD"
|
||||
)
|
||||
)
|
||||
private void saveHook(final long time, final Function<T, String> idFunction, final CallbackInfoReturnable<ListTag> cir) {
|
||||
this.lastSaved = time;
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Update last save to current tick when first unpacking the chunk data
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Inject(
|
||||
method = "unpack",
|
||||
at = @At(
|
||||
value = "HEAD"
|
||||
)
|
||||
)
|
||||
private void unpackHook(final long tick, final CallbackInfo ci) {
|
||||
if (this.pendingTicks == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.lastSaved = tick;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,210 @@
|
||||
package ca.spottedleaf.moonrise.mixin.chunk_system;
|
||||
|
||||
import ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemLevel;
|
||||
import ca.spottedleaf.moonrise.patches.chunk_system.level.entity.EntityLookup;
|
||||
import ca.spottedleaf.moonrise.patches.chunk_system.level.entity.dfl.DefaultEntityLookup;
|
||||
import ca.spottedleaf.moonrise.patches.chunk_system.world.ChunkSystemEntityGetter;
|
||||
import net.minecraft.server.level.FullChunkStatus;
|
||||
import net.minecraft.util.profiling.ProfilerFiller;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.entity.EntityType;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.LevelAccessor;
|
||||
import net.minecraft.world.level.chunk.ChunkAccess;
|
||||
import net.minecraft.world.level.chunk.LevelChunk;
|
||||
import net.minecraft.world.level.chunk.status.ChunkStatus;
|
||||
import net.minecraft.world.level.entity.EntityTypeTest;
|
||||
import net.minecraft.world.phys.AABB;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Overwrite;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
@Mixin(Level.class)
|
||||
public abstract class LevelMixin implements ChunkSystemLevel, ChunkSystemEntityGetter, LevelAccessor, AutoCloseable {
|
||||
|
||||
@Shadow
|
||||
public abstract ProfilerFiller getProfiler();
|
||||
|
||||
|
||||
@Unique
|
||||
private EntityLookup entityLookup;
|
||||
|
||||
@Override
|
||||
public final EntityLookup moonrise$getEntityLookup() {
|
||||
return this.entityLookup;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void moonrise$setEntityLookup(final EntityLookup entityLookup) {
|
||||
if (this.entityLookup != null && !(this.entityLookup instanceof DefaultEntityLookup)) {
|
||||
throw new IllegalStateException("Entity lookup already initialised");
|
||||
}
|
||||
this.entityLookup = entityLookup;
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Default initialise entity lookup incase mods extend Level
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Inject(
|
||||
method = "<init>",
|
||||
at = @At(
|
||||
value = "RETURN"
|
||||
)
|
||||
)
|
||||
private void initHook(final CallbackInfo ci) {
|
||||
this.entityLookup = new DefaultEntityLookup((Level)(Object)this);
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Route to faster lookup
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Overwrite
|
||||
@Override
|
||||
public List<Entity> getEntities(final Entity entity, final AABB boundingBox, final Predicate<? super Entity> predicate) {
|
||||
this.getProfiler().incrementCounter("getEntities");
|
||||
final List<Entity> ret = new ArrayList<>();
|
||||
|
||||
((ChunkSystemLevel)this).moonrise$getEntityLookup().getEntities(entity, boundingBox, ret, predicate);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Route to faster lookup
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Overwrite
|
||||
public <T extends Entity> void getEntities(final EntityTypeTest<Entity, T> entityTypeTest,
|
||||
final AABB boundingBox, final Predicate<? super T> predicate,
|
||||
final List<? super T> into, final int maxCount) {
|
||||
this.getProfiler().incrementCounter("getEntities");
|
||||
|
||||
if (entityTypeTest instanceof EntityType<T> byType) {
|
||||
if (maxCount != Integer.MAX_VALUE) {
|
||||
((ChunkSystemLevel)this).moonrise$getEntityLookup().getEntities(byType, boundingBox, into, predicate, maxCount);
|
||||
return;
|
||||
} else {
|
||||
((ChunkSystemLevel)this).moonrise$getEntityLookup().getEntities(byType, boundingBox, into, predicate);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (entityTypeTest == null) {
|
||||
if (maxCount != Integer.MAX_VALUE) {
|
||||
((ChunkSystemLevel)this).moonrise$getEntityLookup().getEntities((Entity)null, boundingBox, (List)into, (Predicate)predicate, maxCount);
|
||||
return;
|
||||
} else {
|
||||
((ChunkSystemLevel)this).moonrise$getEntityLookup().getEntities((Entity)null, boundingBox, (List)into, (Predicate)predicate);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
final Class<? extends Entity> base = entityTypeTest.getBaseClass();
|
||||
|
||||
final Predicate<? super T> modifiedPredicate;
|
||||
if (predicate == null) {
|
||||
modifiedPredicate = (final T obj) -> {
|
||||
return entityTypeTest.tryCast(obj) != null;
|
||||
};
|
||||
} else {
|
||||
modifiedPredicate = (final Entity obj) -> {
|
||||
final T casted = entityTypeTest.tryCast(obj);
|
||||
if (casted == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return predicate.test(casted);
|
||||
};
|
||||
}
|
||||
|
||||
if (base == null || base == Entity.class) {
|
||||
if (maxCount != Integer.MAX_VALUE) {
|
||||
((ChunkSystemLevel)this).moonrise$getEntityLookup().getEntities((Entity)null, boundingBox, (List)into, (Predicate)modifiedPredicate, maxCount);
|
||||
return;
|
||||
} else {
|
||||
((ChunkSystemLevel)this).moonrise$getEntityLookup().getEntities((Entity)null, boundingBox, (List)into, (Predicate)modifiedPredicate);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if (maxCount != Integer.MAX_VALUE) {
|
||||
((ChunkSystemLevel)this).moonrise$getEntityLookup().getEntities(base, null, boundingBox, (List)into, (Predicate)modifiedPredicate, maxCount);
|
||||
return;
|
||||
} else {
|
||||
((ChunkSystemLevel)this).moonrise$getEntityLookup().getEntities(base, null, boundingBox, (List)into, (Predicate)modifiedPredicate);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Route to faster lookup
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Override
|
||||
public final <T extends Entity> List<T> getEntitiesOfClass(final Class<T> entityClass, final AABB boundingBox, final Predicate<? super T> predicate) {
|
||||
this.getProfiler().incrementCounter("getEntities");
|
||||
final List<T> ret = new ArrayList<>();
|
||||
|
||||
((ChunkSystemLevel)this).moonrise$getEntityLookup().getEntities(entityClass, null, boundingBox, ret, predicate);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Route to faster lookup
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Override
|
||||
public final List<Entity> moonrise$getHardCollidingEntities(final Entity entity, final AABB box, final Predicate<? super Entity> predicate) {
|
||||
this.getProfiler().incrementCounter("getEntities");
|
||||
final List<Entity> ret = new ArrayList<>();
|
||||
|
||||
((ChunkSystemLevel)this).moonrise$getEntityLookup().getHardCollidingEntities(entity, box, ret, predicate);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LevelChunk moonrise$getFullChunkIfLoaded(final int chunkX, final int chunkZ) {
|
||||
return this.getChunkSource().getChunk(chunkX, chunkZ, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChunkAccess moonrise$getAnyChunkIfLoaded(final int chunkX, final int chunkZ) {
|
||||
return this.getChunkSource().getChunk(chunkX, chunkZ, ChunkStatus.EMPTY, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChunkAccess moonrise$getSpecificChunkIfLoaded(final int chunkX, final int chunkZ, final ChunkStatus leastStatus) {
|
||||
return this.getChunkSource().getChunk(chunkX, chunkZ, leastStatus, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Allow block updates in non-ticking chunks, as new chunk system sends non-ticking chunks to clients
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Redirect(
|
||||
method = "setBlock(Lnet/minecraft/core/BlockPos;Lnet/minecraft/world/level/block/state/BlockState;II)Z",
|
||||
at = @At(
|
||||
value = "INVOKE",
|
||||
target = "Lnet/minecraft/server/level/FullChunkStatus;isOrAfter(Lnet/minecraft/server/level/FullChunkStatus;)Z"
|
||||
)
|
||||
)
|
||||
private boolean sendUpdatesForFullChunks(final FullChunkStatus instance,
|
||||
final FullChunkStatus fullChunkStatus) {
|
||||
|
||||
return instance.isOrAfter(FullChunkStatus.FULL);
|
||||
}
|
||||
|
||||
// TODO: Thread.currentThread() != this.thread to TickThread?
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package ca.spottedleaf.moonrise.mixin.chunk_system;
|
||||
|
||||
import ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemLevelReader;
|
||||
import net.minecraft.world.level.BlockAndTintGetter;
|
||||
import net.minecraft.world.level.CollisionGetter;
|
||||
import net.minecraft.world.level.LevelReader;
|
||||
import net.minecraft.world.level.SignalGetter;
|
||||
import net.minecraft.world.level.biome.BiomeManager;
|
||||
import net.minecraft.world.level.chunk.ChunkAccess;
|
||||
import net.minecraft.world.level.chunk.status.ChunkStatus;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
|
||||
@Mixin(LevelReader.class)
|
||||
public interface LevelReaderMixin extends ChunkSystemLevelReader, BlockAndTintGetter, CollisionGetter, SignalGetter, BiomeManager.NoiseBiomeSource {
|
||||
|
||||
@Override
|
||||
public default ChunkAccess moonrise$syncLoadNonFull(final int chunkX, final int chunkZ, final ChunkStatus status) {
|
||||
if (status == null || status.isOrAfter(ChunkStatus.FULL)) {
|
||||
throw new IllegalArgumentException("Status: " + status.toString());
|
||||
}
|
||||
return ((LevelReader)this).getChunk(chunkX, chunkZ, status, true);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
package ca.spottedleaf.moonrise.mixin.chunk_system;
|
||||
|
||||
import ca.spottedleaf.moonrise.common.util.TickThread;
|
||||
import ca.spottedleaf.moonrise.patches.chunk_system.io.RegionFileIOThread;
|
||||
import ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel;
|
||||
import ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkTaskScheduler;
|
||||
import ca.spottedleaf.moonrise.patches.chunk_system.server.ChunkSystemMinecraftServer;
|
||||
import net.minecraft.commands.CommandSource;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.server.ServerInfo;
|
||||
import net.minecraft.server.TickTask;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.util.thread.ReentrantBlockableEventLoop;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
@Mixin(MinecraftServer.class)
|
||||
public abstract class MinecraftServerMixin extends ReentrantBlockableEventLoop<TickTask> implements ChunkSystemMinecraftServer, ServerInfo, CommandSource, AutoCloseable {
|
||||
|
||||
@Shadow
|
||||
public abstract Iterable<ServerLevel> getAllLevels();
|
||||
|
||||
@Shadow
|
||||
public abstract boolean saveAllChunks(boolean bl, boolean bl2, boolean bl3);
|
||||
|
||||
|
||||
public MinecraftServerMixin(String string) {
|
||||
super(string);
|
||||
}
|
||||
|
||||
@Unique
|
||||
private volatile Throwable chunkSystemCrash;
|
||||
|
||||
@Override
|
||||
public final void moonrise$setChunkSystemCrash(final Throwable throwable) {
|
||||
this.chunkSystemCrash = throwable;
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Force response to chunk system crash
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Inject(
|
||||
method = "runServer",
|
||||
at = @At(
|
||||
value = "INVOKE",
|
||||
target = "Lnet/minecraft/server/MinecraftServer;tickServer(Ljava/util/function/BooleanSupplier;)V",
|
||||
shift = At.Shift.AFTER
|
||||
)
|
||||
)
|
||||
private void hookChunkSystemCrash(final CallbackInfo ci) {
|
||||
final Throwable crash = this.chunkSystemCrash;
|
||||
if (crash != null) {
|
||||
this.chunkSystemCrash = null;
|
||||
throw new RuntimeException("Chunk system crash propagated to tick()", crash);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Initialise chunk system threads hook
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Inject(
|
||||
method = "spin",
|
||||
at = @At(
|
||||
value = "HEAD"
|
||||
)
|
||||
)
|
||||
private static <S> void initHook(Function<Thread, S> function, CallbackInfoReturnable<S> cir) {
|
||||
// TODO better place?
|
||||
ChunkTaskScheduler.init();
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Make server thread an instance of TickThread for thread checks
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Redirect(
|
||||
method = "spin",
|
||||
at = @At(
|
||||
value = "NEW",
|
||||
target = "(Ljava/lang/Runnable;Ljava/lang/String;)Ljava/lang/Thread;"
|
||||
)
|
||||
)
|
||||
private static Thread createTickThread(final Runnable target, final String name) {
|
||||
return new TickThread(target, name);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @reason Close logic is re-written so that we do not wait for tasks to complete and unload everything
|
||||
* but rather we halt all task processing and then save.
|
||||
* The reason this is done is that the server may not be in a state where the chunk system can
|
||||
* complete its tasks, which would prevent the saving of any data. The new close logic will ensure
|
||||
* that if the system is deadlocked that both a full save will occur and that the server will halt.
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Redirect(
|
||||
method = "stopServer",
|
||||
at = @At(
|
||||
value = "INVOKE",
|
||||
target = "Ljava/util/stream/Stream;anyMatch(Ljava/util/function/Predicate;)Z",
|
||||
ordinal = 0
|
||||
)
|
||||
)
|
||||
private boolean doNotWaitChunkSystemShutdown(final Stream<ServerLevel> instance, final Predicate<? super ServerLevel> predicate) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Mark all ServerLevel instances as being closed so that saveAllChunks can perform a close-save operation.
|
||||
* Additionally, sets force = true, so that the save call is guaranteed to be converted into a close-save call.
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Redirect(
|
||||
method = "stopServer",
|
||||
at = @At(
|
||||
value = "INVOKE",
|
||||
target = "Lnet/minecraft/server/MinecraftServer;saveAllChunks(ZZZ)Z"
|
||||
)
|
||||
)
|
||||
private boolean markClosed(final MinecraftServer instance, boolean bl, boolean bl2, boolean bl3) {
|
||||
for (final ServerLevel world : this.getAllLevels()) {
|
||||
((ChunkSystemServerLevel)world).moonrise$setMarkedClosing(true);
|
||||
}
|
||||
// !log, flush, force
|
||||
return this.saveAllChunks(false, true, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Close is handled above
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Redirect(
|
||||
method = "stopServer",
|
||||
at = @At(
|
||||
value = "INVOKE",
|
||||
target = "Lnet/minecraft/server/level/ServerLevel;close()V",
|
||||
ordinal = 0
|
||||
)
|
||||
)
|
||||
private void noOpClose(final ServerLevel instance) {}
|
||||
|
||||
/**
|
||||
* @reason Halt regionfile threads after everything is closed
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Inject(
|
||||
method = "stopServer",
|
||||
at = @At(
|
||||
value = "RETURN"
|
||||
)
|
||||
)
|
||||
private void closeIOThreads(final CallbackInfo ci) {
|
||||
// TODO reinit code needs to be put somewhere
|
||||
RegionFileIOThread.deinit();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
package ca.spottedleaf.moonrise.mixin.chunk_system;
|
||||
|
||||
import ca.spottedleaf.moonrise.common.real_dumb_shit.HolderCompletableFuture;
|
||||
import net.minecraft.world.level.StructureManager;
|
||||
import net.minecraft.world.level.chunk.ChunkAccess;
|
||||
import net.minecraft.world.level.levelgen.NoiseBasedChunkGenerator;
|
||||
import net.minecraft.world.level.levelgen.RandomState;
|
||||
import net.minecraft.world.level.levelgen.blending.Blender;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
@Mixin(NoiseBasedChunkGenerator.class)
|
||||
public abstract class NoiseBasedChunkGeneratorMixin {
|
||||
|
||||
/**
|
||||
* @reason Pass the supplier to the mixin below so that we can change the executor to the parameter provided
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Redirect(
|
||||
method = "createBiomes",
|
||||
at = @At(
|
||||
value = "INVOKE",
|
||||
target = "Ljava/util/concurrent/CompletableFuture;supplyAsync(Ljava/util/function/Supplier;Ljava/util/concurrent/Executor;)Ljava/util/concurrent/CompletableFuture;"
|
||||
)
|
||||
)
|
||||
private <U> CompletableFuture<U> passSupplierBiomes(Supplier<U> supplier, Executor executor) {
|
||||
return (CompletableFuture<U>)CompletableFuture.completedFuture(supplier);
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Retrieve the supplier from the mixin above so that we can change the executor to the parameter provided
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Inject(
|
||||
method = "createBiomes",
|
||||
cancellable = true,
|
||||
at = @At(
|
||||
value = "RETURN"
|
||||
)
|
||||
)
|
||||
private void unpackSupplierBiomes(Executor executor, RandomState randomState, Blender blender,
|
||||
StructureManager structureManager, ChunkAccess chunkAccess,
|
||||
CallbackInfoReturnable<CompletableFuture<ChunkAccess>> cir) {
|
||||
cir.setReturnValue(
|
||||
CompletableFuture.supplyAsync(((CompletableFuture<Supplier<ChunkAccess>>)(CompletableFuture)cir.getReturnValue()).join(), executor)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @reason Pass the executor tasks to the mixin below so that we can change the executor to the parameter provided
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Redirect(
|
||||
method = "fillFromNoise",
|
||||
at = @At(
|
||||
value = "INVOKE",
|
||||
target = "Ljava/util/concurrent/CompletableFuture;supplyAsync(Ljava/util/function/Supplier;Ljava/util/concurrent/Executor;)Ljava/util/concurrent/CompletableFuture;"
|
||||
)
|
||||
)
|
||||
private <U> CompletableFuture<U> passSupplierNoise(Supplier<U> supplier, Executor executor) {
|
||||
final HolderCompletableFuture<U> ret = new HolderCompletableFuture<>();
|
||||
|
||||
ret.toExecute.add(() -> {
|
||||
try {
|
||||
ret.complete(supplier.get());
|
||||
} catch (final Throwable throwable) {
|
||||
ret.completeExceptionally(throwable);
|
||||
}
|
||||
});
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Retrieve the executor tasks from the mixin above so that we can change the executor to the parameter provided
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Redirect(
|
||||
method = "fillFromNoise",
|
||||
at = @At(
|
||||
value = "INVOKE",
|
||||
target = "Ljava/util/concurrent/CompletableFuture;whenCompleteAsync(Ljava/util/function/BiConsumer;Ljava/util/concurrent/Executor;)Ljava/util/concurrent/CompletableFuture;"
|
||||
)
|
||||
)
|
||||
private <T> CompletableFuture<T> unpackSupplierNoise(final CompletableFuture<T> instance, final BiConsumer<? super T, ? super Throwable> action,
|
||||
final Executor executor) {
|
||||
final HolderCompletableFuture<T> casted = (HolderCompletableFuture<T>)instance;
|
||||
|
||||
for (final Runnable run : casted.toExecute) {
|
||||
executor.execute(run);
|
||||
}
|
||||
|
||||
// note: executor is the parameter we want
|
||||
return instance.whenCompleteAsync(action, executor);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package ca.spottedleaf.moonrise.mixin.chunk_system;
|
||||
|
||||
import ca.spottedleaf.moonrise.common.util.MoonriseConstants;
|
||||
import net.minecraft.client.Options;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.Constant;
|
||||
import org.spongepowered.asm.mixin.injection.ModifyConstant;
|
||||
|
||||
@Mixin(Options.class)
|
||||
public abstract class OptionsMixin {
|
||||
|
||||
/**
|
||||
* @reason Allow higher view distances
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@ModifyConstant(
|
||||
method = "<init>",
|
||||
constant = @Constant(
|
||||
intValue = 32, ordinal = 1
|
||||
)
|
||||
)
|
||||
private int replaceViewDistanceConstant(final int constant) {
|
||||
return MoonriseConstants.MAX_VIEW_DISTANCE;
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Allow higher view distances
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@ModifyConstant(
|
||||
method = "<init>",
|
||||
constant = @Constant(
|
||||
intValue = 32, ordinal = 2
|
||||
)
|
||||
)
|
||||
private int replaceSimulationDistanceConstant(final int constant) {
|
||||
return MoonriseConstants.MAX_VIEW_DISTANCE;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package ca.spottedleaf.moonrise.mixin.chunk_system;
|
||||
|
||||
import ca.spottedleaf.moonrise.patches.chunk_system.player.ChunkSystemServerPlayer;
|
||||
import net.minecraft.network.Connection;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.server.network.CommonListenerCookie;
|
||||
import net.minecraft.server.players.PlayerList;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
@Mixin(PlayerList.class)
|
||||
public abstract class PlayerListMixin {
|
||||
|
||||
/**
|
||||
* @reason Mark the player as "real", which enables chunk loading
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Inject(
|
||||
method = "placeNewPlayer",
|
||||
at = @At(
|
||||
value = "HEAD"
|
||||
)
|
||||
)
|
||||
private void initRealPlayer(final Connection connection, final ServerPlayer serverPlayer,
|
||||
final CommonListenerCookie commonListenerCookie, final CallbackInfo ci) {
|
||||
((ChunkSystemServerPlayer)serverPlayer).moonrise$setRealPlayer(true);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,278 @@
|
||||
package ca.spottedleaf.moonrise.mixin.chunk_system;
|
||||
|
||||
import ca.spottedleaf.moonrise.common.misc.Delayed26WayDistancePropagator3D;
|
||||
import ca.spottedleaf.moonrise.common.util.CoordinateUtils;
|
||||
import ca.spottedleaf.moonrise.common.util.TickThread;
|
||||
import ca.spottedleaf.moonrise.common.util.WorldUtil;
|
||||
import ca.spottedleaf.moonrise.patches.chunk_system.io.RegionFileIOThread;
|
||||
import ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel;
|
||||
import ca.spottedleaf.moonrise.patches.chunk_system.level.poi.ChunkSystemPoiManager;
|
||||
import ca.spottedleaf.moonrise.patches.chunk_system.level.poi.ChunkSystemPoiSection;
|
||||
import ca.spottedleaf.moonrise.patches.chunk_system.level.poi.PoiChunk;
|
||||
import ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkHolderManager;
|
||||
import com.mojang.datafixers.DataFixer;
|
||||
import com.mojang.serialization.Codec;
|
||||
import net.minecraft.core.RegistryAccess;
|
||||
import net.minecraft.core.SectionPos;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.world.entity.ai.village.poi.PoiManager;
|
||||
import net.minecraft.world.entity.ai.village.poi.PoiSection;
|
||||
import net.minecraft.world.level.ChunkPos;
|
||||
import net.minecraft.world.level.LevelHeightAccessor;
|
||||
import net.minecraft.world.level.chunk.ChunkAccess;
|
||||
import net.minecraft.world.level.chunk.LevelChunkSection;
|
||||
import net.minecraft.world.level.chunk.storage.RegionStorageInfo;
|
||||
import net.minecraft.world.level.chunk.storage.SectionStorage;
|
||||
import net.minecraft.world.level.chunk.storage.SimpleRegionStorage;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Overwrite;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Optional;
|
||||
import java.util.function.BooleanSupplier;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
@Mixin(PoiManager.class)
|
||||
public abstract class PoiManagerMixin extends SectionStorage<PoiSection> implements ChunkSystemPoiManager {
|
||||
@Shadow
|
||||
abstract boolean isVillageCenter(long l);
|
||||
|
||||
@Shadow
|
||||
public abstract void checkConsistencyWithBlocks(SectionPos sectionPos, LevelChunkSection levelChunkSection);
|
||||
|
||||
public PoiManagerMixin(SimpleRegionStorage simpleRegionStorage, Function<Runnable, Codec<PoiSection>> function, Function<Runnable, PoiSection> function2, RegistryAccess registryAccess, LevelHeightAccessor levelHeightAccessor) {
|
||||
super(simpleRegionStorage, function, function2, registryAccess, levelHeightAccessor);
|
||||
}
|
||||
|
||||
@Unique
|
||||
private ServerLevel world;
|
||||
|
||||
// the vanilla tracker needs to be replaced because it does not support level removes, and we need level removes
|
||||
// to support poi unloading
|
||||
@Unique
|
||||
private Delayed26WayDistancePropagator3D villageDistanceTracker;
|
||||
|
||||
@Unique
|
||||
private static final int POI_DATA_SOURCE = 7;
|
||||
|
||||
@Unique
|
||||
private static int convertBetweenLevels(final int level) {
|
||||
return POI_DATA_SOURCE - level;
|
||||
}
|
||||
|
||||
@Unique
|
||||
private void updateDistanceTracking(long section) {
|
||||
if (this.isVillageCenter(section)) {
|
||||
this.villageDistanceTracker.setSource(section, POI_DATA_SOURCE);
|
||||
} else {
|
||||
this.villageDistanceTracker.removeSource(section);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Initialise fields
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Inject(
|
||||
method = "<init>",
|
||||
at = @At(
|
||||
value = "RETURN"
|
||||
)
|
||||
)
|
||||
private void initHook(RegionStorageInfo regionStorageInfo, Path path, DataFixer dataFixer, boolean bl, RegistryAccess registryAccess,
|
||||
LevelHeightAccessor levelHeightAccessor, CallbackInfo ci) {
|
||||
this.world = (ServerLevel)levelHeightAccessor;
|
||||
this.villageDistanceTracker = new Delayed26WayDistancePropagator3D();
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Replace vanilla tracker
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Overwrite
|
||||
public int sectionsToVillage(final SectionPos pos) {
|
||||
this.villageDistanceTracker.propagateUpdates(); // Paper - replace distance tracking util
|
||||
return convertBetweenLevels(this.villageDistanceTracker.getLevel(CoordinateUtils.getChunkSectionKey(pos))); // Paper - replace distance tracking util
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Replace vanilla tracker and avoid superclass poi data writing (which is now handled by chunk autosave)
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Overwrite
|
||||
public void tick(final BooleanSupplier shouldKeepTicking) {
|
||||
this.villageDistanceTracker.propagateUpdates();
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Replace vanilla tracker, mark poi chunk as dirty
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Override
|
||||
@Overwrite
|
||||
public void setDirty(final long pos) {
|
||||
final int chunkX = CoordinateUtils.getChunkSectionX(pos);
|
||||
final int chunkZ = CoordinateUtils.getChunkSectionZ(pos);
|
||||
final ChunkHolderManager manager = ((ChunkSystemServerLevel)this.world).moonrise$getChunkTaskScheduler().chunkHolderManager;
|
||||
final PoiChunk chunk = manager.getPoiChunkIfLoaded(chunkX, chunkZ, false);
|
||||
if (chunk != null) {
|
||||
chunk.setDirty(true);
|
||||
}
|
||||
this.updateDistanceTracking(pos);
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Replace vanilla tracker
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Override
|
||||
@Overwrite
|
||||
public void onSectionLoad(final long pos) {
|
||||
this.updateDistanceTracking(pos);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<PoiSection> get(final long pos) {
|
||||
final int chunkX = CoordinateUtils.getChunkSectionX(pos);
|
||||
final int chunkY = CoordinateUtils.getChunkSectionY(pos);
|
||||
final int chunkZ = CoordinateUtils.getChunkSectionZ(pos);
|
||||
|
||||
TickThread.ensureTickThread(this.world, chunkX, chunkZ, "Accessing poi chunk off-main");
|
||||
|
||||
final ChunkHolderManager manager = ((ChunkSystemServerLevel)this.world).moonrise$getChunkTaskScheduler().chunkHolderManager;
|
||||
final PoiChunk ret = manager.getPoiChunkIfLoaded(chunkX, chunkZ, true);
|
||||
|
||||
return ret == null ? Optional.empty() : ret.getSectionForVanilla(chunkY);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<PoiSection> getOrLoad(final long pos) {
|
||||
final int chunkX = CoordinateUtils.getChunkSectionX(pos);
|
||||
final int chunkY = CoordinateUtils.getChunkSectionY(pos);
|
||||
final int chunkZ = CoordinateUtils.getChunkSectionZ(pos);
|
||||
|
||||
TickThread.ensureTickThread(this.world, chunkX, chunkZ, "Accessing poi chunk off-main");
|
||||
|
||||
final ChunkHolderManager manager = ((ChunkSystemServerLevel)this.world).moonrise$getChunkTaskScheduler().chunkHolderManager;
|
||||
|
||||
if (chunkY >= WorldUtil.getMinSection(this.world) && chunkY <= WorldUtil.getMaxSection(this.world)) {
|
||||
final PoiChunk ret = manager.getPoiChunkIfLoaded(chunkX, chunkZ, true);
|
||||
if (ret != null) {
|
||||
return ret.getSectionForVanilla(chunkY);
|
||||
} else {
|
||||
return manager.loadPoiChunk(chunkX, chunkZ).getSectionForVanilla(chunkY);
|
||||
}
|
||||
}
|
||||
// retain vanilla behavior: do not load section if out of bounds!
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected PoiSection getOrCreate(final long pos) {
|
||||
final int chunkX = CoordinateUtils.getChunkSectionX(pos);
|
||||
final int chunkY = CoordinateUtils.getChunkSectionY(pos);
|
||||
final int chunkZ = CoordinateUtils.getChunkSectionZ(pos);
|
||||
|
||||
TickThread.ensureTickThread(this.world, chunkX, chunkZ, "Accessing poi chunk off-main");
|
||||
|
||||
final ChunkHolderManager manager = ((ChunkSystemServerLevel)this.world).moonrise$getChunkTaskScheduler().chunkHolderManager;
|
||||
|
||||
final PoiChunk ret = manager.getPoiChunkIfLoaded(chunkX, chunkZ, true);
|
||||
if (ret != null) {
|
||||
return ret.getOrCreateSection(chunkY);
|
||||
} else {
|
||||
return manager.loadPoiChunk(chunkX, chunkZ).getOrCreateSection(chunkY);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final ServerLevel moonrise$getWorld() {
|
||||
return this.world;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void moonrise$onUnload(final long coordinate) { // Paper - rewrite chunk system
|
||||
final int chunkX = CoordinateUtils.getChunkX(coordinate);
|
||||
final int chunkZ = CoordinateUtils.getChunkZ(coordinate);
|
||||
TickThread.ensureTickThread(this.world, chunkX, chunkZ, "Unloading poi chunk off-main");
|
||||
for (int section = this.levelHeightAccessor.getMinSection(); section < this.levelHeightAccessor.getMaxSection(); ++section) {
|
||||
final long sectionPos = SectionPos.asLong(chunkX, section, chunkZ);
|
||||
this.updateDistanceTracking(sectionPos);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void moonrise$loadInPoiChunk(final PoiChunk poiChunk) {
|
||||
final int chunkX = poiChunk.chunkX;
|
||||
final int chunkZ = poiChunk.chunkZ;
|
||||
TickThread.ensureTickThread(this.world, chunkX, chunkZ, "Loading poi chunk off-main");
|
||||
for (int sectionY = this.levelHeightAccessor.getMinSection(); sectionY < this.levelHeightAccessor.getMaxSection(); ++sectionY) {
|
||||
final PoiSection section = poiChunk.getSection(sectionY);
|
||||
if (section != null && !((ChunkSystemPoiSection)section).moonrise$isEmpty()) {
|
||||
this.onSectionLoad(SectionPos.asLong(chunkX, sectionY, chunkZ));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void moonrise$checkConsistency(final ChunkAccess chunk) {
|
||||
final int chunkX = chunk.getPos().x;
|
||||
final int chunkZ = chunk.getPos().z;
|
||||
|
||||
final int minY = WorldUtil.getMinSection(chunk);
|
||||
final int maxY = WorldUtil.getMaxSection(chunk);
|
||||
final LevelChunkSection[] sections = chunk.getSections();
|
||||
for (int section = minY; section <= maxY; ++section) {
|
||||
this.checkConsistencyWithBlocks(SectionPos.of(chunkX, section, chunkZ), sections[section - minY]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason The loaded field is unused, so adding entries needlessly consumes memory.
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Redirect(
|
||||
method = "ensureLoadedAndValid",
|
||||
at = @At(
|
||||
value = "INVOKE",
|
||||
target = "Ljava/util/stream/Stream;filter(Ljava/util/function/Predicate;)Ljava/util/stream/Stream;",
|
||||
ordinal = 1
|
||||
)
|
||||
)
|
||||
private <T> Stream<T> skipLoadedSet(final Stream<T> instance, final Predicate<? super T> predicate) {
|
||||
return instance;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void moonrise$close() throws IOException {}
|
||||
|
||||
@Override
|
||||
public final CompoundTag moonrise$read(final int chunkX, final int chunkZ) throws IOException {
|
||||
if (!RegionFileIOThread.isRegionFileThread()) {
|
||||
return RegionFileIOThread.loadData(
|
||||
this.world, chunkX, chunkZ, RegionFileIOThread.RegionFileType.POI_DATA,
|
||||
RegionFileIOThread.getIOBlockingPriorityForCurrentThread()
|
||||
);
|
||||
}
|
||||
return this.moonrise$getRegionStorage().read(new ChunkPos(chunkX, chunkZ));
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void moonrise$write(final int chunkX, final int chunkZ, final CompoundTag data) throws IOException {
|
||||
if (!RegionFileIOThread.isRegionFileThread()) {
|
||||
RegionFileIOThread.scheduleSave(this.world, chunkX, chunkZ, data, RegionFileIOThread.RegionFileType.POI_DATA);
|
||||
return;
|
||||
}
|
||||
this.moonrise$getRegionStorage().write(new ChunkPos(chunkX, chunkZ), data);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package ca.spottedleaf.moonrise.mixin.chunk_system;
|
||||
|
||||
import ca.spottedleaf.moonrise.patches.chunk_system.level.poi.ChunkSystemPoiSection;
|
||||
import it.unimi.dsi.fastutil.shorts.Short2ObjectMap;
|
||||
import net.minecraft.core.Holder;
|
||||
import net.minecraft.world.entity.ai.village.poi.PoiRecord;
|
||||
import net.minecraft.world.entity.ai.village.poi.PoiSection;
|
||||
import net.minecraft.world.entity.ai.village.poi.PoiType;
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
@Mixin(PoiSection.class)
|
||||
public abstract class PoiSectionMixin implements ChunkSystemPoiSection {
|
||||
|
||||
@Shadow
|
||||
private boolean isValid;
|
||||
|
||||
@Shadow
|
||||
@Final
|
||||
private Short2ObjectMap<PoiRecord> records;
|
||||
|
||||
@Shadow
|
||||
@Final
|
||||
public Map<Holder<PoiType>, Set<PoiRecord>> byType;
|
||||
|
||||
|
||||
@Unique
|
||||
private Optional<PoiSection> noAllocOptional;
|
||||
|
||||
/**
|
||||
* @reason Initialise fields
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Inject(
|
||||
method = "<init>(Ljava/lang/Runnable;ZLjava/util/List;)V",
|
||||
at = @At(
|
||||
value = "RETURN"
|
||||
)
|
||||
)
|
||||
private void init(final CallbackInfo ci) {
|
||||
this.noAllocOptional = Optional.of((PoiSection)(Object)this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final boolean moonrise$isEmpty() {
|
||||
return this.isValid && this.records.isEmpty() && this.byType.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public final Optional<PoiSection> moonrise$asOptional() {
|
||||
return this.noAllocOptional;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package ca.spottedleaf.moonrise.mixin.chunk_system;
|
||||
|
||||
import net.minecraft.world.level.chunk.storage.RegionFile;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
|
||||
@Mixin(RegionFile.class)
|
||||
public abstract class RegionFileMixin {
|
||||
|
||||
// TODO can't really add synchronized to methods, can we?
|
||||
|
||||
}
|
||||
@@ -0,0 +1,306 @@
|
||||
package ca.spottedleaf.moonrise.mixin.chunk_system;
|
||||
|
||||
import ca.spottedleaf.moonrise.patches.chunk_system.io.ChunkSystemRegionFileStorage;
|
||||
import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.longs.LongLinkedOpenHashSet;
|
||||
import net.minecraft.FileUtil;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.nbt.StreamTagVisitor;
|
||||
import net.minecraft.util.ExceptionCollector;
|
||||
import net.minecraft.world.level.ChunkPos;
|
||||
import net.minecraft.world.level.chunk.storage.RegionFile;
|
||||
import net.minecraft.world.level.chunk.storage.RegionFileStorage;
|
||||
import net.minecraft.world.level.chunk.storage.RegionStorageInfo;
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Overwrite;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||
import org.spongepowered.asm.mixin.injection.callback.LocalCapture;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
@Mixin(RegionFileStorage.class)
|
||||
public abstract class RegionFileStorageMixin implements ChunkSystemRegionFileStorage, AutoCloseable {
|
||||
|
||||
@Shadow
|
||||
@Final
|
||||
private Long2ObjectLinkedOpenHashMap<RegionFile> regionCache;
|
||||
|
||||
@Shadow
|
||||
@Final
|
||||
private static int MAX_CACHE_SIZE;
|
||||
|
||||
@Shadow
|
||||
@Final
|
||||
private Path folder;
|
||||
|
||||
@Shadow
|
||||
@Final
|
||||
private boolean sync;
|
||||
|
||||
@Shadow
|
||||
@Final
|
||||
private RegionStorageInfo info;
|
||||
|
||||
|
||||
@Unique
|
||||
private static final int REGION_SHIFT = 5;
|
||||
|
||||
@Unique
|
||||
private static final int MAX_NON_EXISTING_CACHE = 1024 * 64;
|
||||
|
||||
@Unique
|
||||
private final LongLinkedOpenHashSet nonExistingRegionFiles = new LongLinkedOpenHashSet(MAX_NON_EXISTING_CACHE+1);
|
||||
|
||||
@Unique
|
||||
private static String getRegionFileName(final int chunkX, final int chunkZ) {
|
||||
return "r." + (chunkX >> REGION_SHIFT) + "." + (chunkZ >> REGION_SHIFT) + ".mca";
|
||||
}
|
||||
|
||||
@Unique
|
||||
private boolean doesRegionFilePossiblyExist(final long position) {
|
||||
synchronized (this.nonExistingRegionFiles) {
|
||||
if (this.nonExistingRegionFiles.contains(position)) {
|
||||
this.nonExistingRegionFiles.addAndMoveToFirst(position);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@Unique
|
||||
private void createRegionFile(final long position) {
|
||||
synchronized (this.nonExistingRegionFiles) {
|
||||
this.nonExistingRegionFiles.remove(position);
|
||||
}
|
||||
}
|
||||
|
||||
@Unique
|
||||
private void markNonExisting(final long position) {
|
||||
synchronized (this.nonExistingRegionFiles) {
|
||||
if (this.nonExistingRegionFiles.addAndMoveToFirst(position)) {
|
||||
while (this.nonExistingRegionFiles.size() >= MAX_NON_EXISTING_CACHE) {
|
||||
this.nonExistingRegionFiles.removeLastLong();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final boolean moonrise$doesRegionFileNotExistNoIO(final int chunkX, final int chunkZ) {
|
||||
return !this.doesRegionFilePossiblyExist(ChunkPos.asLong(chunkX >> REGION_SHIFT, chunkZ >> REGION_SHIFT));
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized final RegionFile moonrise$getRegionFileIfLoaded(final int chunkX, final int chunkZ) {
|
||||
return this.regionCache.getAndMoveToFirst(ChunkPos.asLong(chunkX >> REGION_SHIFT, chunkZ >> REGION_SHIFT));
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized final RegionFile moonrise$getRegionFileIfExists(final int chunkX, final int chunkZ) throws IOException {
|
||||
final long key = ChunkPos.asLong(chunkX >> REGION_SHIFT, chunkZ >> REGION_SHIFT);
|
||||
|
||||
RegionFile ret = this.regionCache.getAndMoveToFirst(key);
|
||||
if (ret != null) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (!this.doesRegionFilePossiblyExist(key)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (this.regionCache.size() >= MAX_CACHE_SIZE) {
|
||||
this.regionCache.removeLast().close();
|
||||
}
|
||||
|
||||
final Path regionPath = this.folder.resolve(getRegionFileName(chunkX, chunkZ));
|
||||
|
||||
if (!Files.exists(regionPath)) {
|
||||
this.markNonExisting(key);
|
||||
return null;
|
||||
}
|
||||
|
||||
this.createRegionFile(key);
|
||||
|
||||
FileUtil.createDirectoriesSafe(this.folder);
|
||||
|
||||
ret = new RegionFile(this.info, regionPath, this.folder, this.sync);
|
||||
|
||||
this.regionCache.putAndMoveToFirst(key, ret);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Make this method thread-safe, and add in support for storing when regionfiles do not exist
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Overwrite
|
||||
public final RegionFile getRegionFile(final ChunkPos chunkPos) throws IOException {
|
||||
synchronized (this) {
|
||||
final long key = ChunkPos.asLong(chunkPos.x >> REGION_SHIFT, chunkPos.z >> REGION_SHIFT);
|
||||
|
||||
RegionFile ret = this.regionCache.getAndMoveToFirst(key);
|
||||
if (ret != null) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (this.regionCache.size() >= MAX_CACHE_SIZE) {
|
||||
this.regionCache.removeLast().close();
|
||||
}
|
||||
|
||||
final Path regionPath = this.folder.resolve(getRegionFileName(chunkPos.x, chunkPos.z));
|
||||
|
||||
this.createRegionFile(key);
|
||||
|
||||
FileUtil.createDirectoriesSafe(this.folder);
|
||||
|
||||
ret = new RegionFile(this.info, regionPath, this.folder, this.sync);
|
||||
|
||||
this.regionCache.putAndMoveToFirst(key, ret);
|
||||
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Make this method thread-safe
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Override
|
||||
@Overwrite
|
||||
public void close() throws IOException {
|
||||
synchronized (this) {
|
||||
final ExceptionCollector<IOException> exceptionCollector = new ExceptionCollector<>();
|
||||
for (final RegionFile regionFile : this.regionCache.values()) {
|
||||
try {
|
||||
regionFile.close();
|
||||
} catch (final IOException ex) {
|
||||
exceptionCollector.add(ex);
|
||||
}
|
||||
}
|
||||
|
||||
exceptionCollector.throwIfPresent();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Make this method thread-safe
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Overwrite
|
||||
public void flush() throws IOException {
|
||||
synchronized (this) {
|
||||
final ExceptionCollector<IOException> exceptionCollector = new ExceptionCollector<>();
|
||||
for (final RegionFile regionFile : this.regionCache.values()) {
|
||||
try {
|
||||
regionFile.flush();
|
||||
} catch (final IOException ex) {
|
||||
exceptionCollector.add(ex);
|
||||
}
|
||||
}
|
||||
|
||||
exceptionCollector.throwIfPresent();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Avoid creating RegionFiles on read when they do not exist
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Redirect(
|
||||
method = "read",
|
||||
at = @At(
|
||||
value = "INVOKE",
|
||||
target = "Lnet/minecraft/world/level/chunk/storage/RegionFileStorage;getRegionFile(Lnet/minecraft/world/level/ChunkPos;)Lnet/minecraft/world/level/chunk/storage/RegionFile;"
|
||||
)
|
||||
)
|
||||
private RegionFile avoidCreatingReadRegionFile(final RegionFileStorage instance, final ChunkPos chunkPos) throws IOException {
|
||||
return ((RegionFileStorageMixin)(Object)instance).moonrise$getRegionFileIfExists(chunkPos.x, chunkPos.z);
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Avoid creating RegionFiles on read when they do not exist, this hook is required to exit early when
|
||||
* the RegionFile does not exist.
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Inject(
|
||||
method = "read",
|
||||
cancellable = true,
|
||||
locals = LocalCapture.CAPTURE_FAILHARD,
|
||||
at = @At(
|
||||
value = "INVOKE",
|
||||
target = "Lnet/minecraft/world/level/chunk/storage/RegionFile;getChunkDataInputStream(Lnet/minecraft/world/level/ChunkPos;)Ljava/io/DataInputStream;"
|
||||
)
|
||||
)
|
||||
private void avoidCreatingReadRegionFileExit(final ChunkPos chunkPos, final CallbackInfoReturnable<CompoundTag> cir,
|
||||
final RegionFile regionFile) {
|
||||
if (regionFile == null) {
|
||||
cir.setReturnValue(null);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Avoid creating RegionFiles on scan when they do not exist
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Redirect(
|
||||
method = "scanChunk",
|
||||
at = @At(
|
||||
value = "INVOKE",
|
||||
target = "Lnet/minecraft/world/level/chunk/storage/RegionFileStorage;getRegionFile(Lnet/minecraft/world/level/ChunkPos;)Lnet/minecraft/world/level/chunk/storage/RegionFile;"
|
||||
)
|
||||
)
|
||||
private RegionFile avoidCreatingScanRegionFile(final RegionFileStorage instance, final ChunkPos chunkPos) throws IOException {
|
||||
return ((RegionFileStorageMixin)(Object)instance).moonrise$getRegionFileIfExists(chunkPos.x, chunkPos.z);
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Avoid creating RegionFiles on scan when they do not exist, this hook is required to exit early when
|
||||
* the RegionFile does not exist.
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Inject(
|
||||
method = "scanChunk",
|
||||
cancellable = true,
|
||||
locals = LocalCapture.CAPTURE_FAILHARD,
|
||||
at = @At(
|
||||
value = "INVOKE",
|
||||
target = "Lnet/minecraft/world/level/chunk/storage/RegionFile;getChunkDataInputStream(Lnet/minecraft/world/level/ChunkPos;)Ljava/io/DataInputStream;"
|
||||
)
|
||||
)
|
||||
private void avoidCreatingScanRegionFileExit(final ChunkPos chunkPos, final StreamTagVisitor streamTagVisitor,
|
||||
final CallbackInfo ci, final RegionFile regionFile) {
|
||||
if (regionFile == null) {
|
||||
ci.cancel();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Avoid creating RegionFiles on write when the input value is null (indicating a delete operation)
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Inject(
|
||||
method = "write",
|
||||
cancellable = true,
|
||||
at = @At(
|
||||
value = "HEAD"
|
||||
)
|
||||
)
|
||||
private void avoidCreatingWriteRegionFile(final ChunkPos chunkPos, final CompoundTag compoundTag, final CallbackInfo ci) throws IOException {
|
||||
if (compoundTag == null && this.moonrise$getRegionFileIfExists(chunkPos.x, chunkPos.z) == null) {
|
||||
ci.cancel();
|
||||
return;
|
||||
}
|
||||
// double reading the RegionFile is fine, as the result is cached
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
package ca.spottedleaf.moonrise.mixin.chunk_system;
|
||||
|
||||
import ca.spottedleaf.moonrise.patches.chunk_system.level.storage.ChunkSystemSectionStorage;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.nbt.Tag;
|
||||
import net.minecraft.resources.RegistryOps;
|
||||
import net.minecraft.world.level.ChunkPos;
|
||||
import net.minecraft.world.level.chunk.storage.RegionFileStorage;
|
||||
import net.minecraft.world.level.chunk.storage.SectionStorage;
|
||||
import net.minecraft.world.level.chunk.storage.SimpleRegionStorage;
|
||||
import org.slf4j.Logger;
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Overwrite;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
import java.io.IOException;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
@Mixin(SectionStorage.class)
|
||||
public abstract class SectionStorageMixin implements ChunkSystemSectionStorage, AutoCloseable {
|
||||
|
||||
@Shadow
|
||||
private SimpleRegionStorage simpleRegionStorage;
|
||||
|
||||
@Shadow
|
||||
@Final
|
||||
private static Logger LOGGER;
|
||||
|
||||
|
||||
@Unique
|
||||
private RegionFileStorage storage;
|
||||
|
||||
@Override
|
||||
public final RegionFileStorage moonrise$getRegionStorage() {
|
||||
return this.storage;
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Retrieve storage from IOWorker, and then nuke it
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Inject(
|
||||
method = "<init>",
|
||||
at = @At(
|
||||
value = "RETURN"
|
||||
)
|
||||
)
|
||||
private void initHook(final CallbackInfo ci) {
|
||||
this.storage = this.simpleRegionStorage.worker.storage;
|
||||
this.simpleRegionStorage = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Route to new chunk system hook
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Overwrite
|
||||
public final CompletableFuture<Optional<CompoundTag>> tryRead(final ChunkPos pos) {
|
||||
try {
|
||||
return CompletableFuture.completedFuture(Optional.ofNullable(this.moonrise$read(pos.x, pos.z)));
|
||||
} catch (final Throwable thr) {
|
||||
return CompletableFuture.failedFuture(thr);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Destroy old chunk system hook
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Overwrite
|
||||
public void readColumn(final ChunkPos pos, final RegistryOps<Tag> ops, final CompoundTag data) {
|
||||
throw new IllegalStateException("Only chunk system can load in state, offending class:" + this.getClass().getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Route to new chunk system hook
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Redirect(
|
||||
method = "writeColumn(Lnet/minecraft/world/level/ChunkPos;)V",
|
||||
at = @At(
|
||||
value = "INVOKE",
|
||||
target = "Lnet/minecraft/world/level/chunk/storage/SimpleRegionStorage;write(Lnet/minecraft/world/level/ChunkPos;Lnet/minecraft/nbt/CompoundTag;)Ljava/util/concurrent/CompletableFuture;"
|
||||
)
|
||||
)
|
||||
private CompletableFuture<Void> redirectWrite(final SimpleRegionStorage instance, final ChunkPos pos,
|
||||
final CompoundTag tag) {
|
||||
try {
|
||||
this.moonrise$write(pos.x, pos.z, tag);
|
||||
} catch (final IOException ex) {
|
||||
LOGGER.error("Error writing poi chunk data to disk for chunk " + pos, ex);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Route to new chunk system hook
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Redirect(
|
||||
method = "close",
|
||||
at = @At(
|
||||
value = "INVOKE",
|
||||
target = "Lnet/minecraft/world/level/chunk/storage/SimpleRegionStorage;close()V"
|
||||
)
|
||||
)
|
||||
private void redirectClose(final SimpleRegionStorage instance) throws IOException {
|
||||
this.moonrise$close();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package ca.spottedleaf.moonrise.mixin.chunk_system;
|
||||
|
||||
import ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel;
|
||||
import net.minecraft.server.level.ServerChunkCache;
|
||||
import net.minecraft.util.thread.BlockableEventLoop;
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Overwrite;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
|
||||
@Mixin(ServerChunkCache.MainThreadExecutor.class)
|
||||
public abstract class ServerChunkCache$MainThreadExecutorMixin extends BlockableEventLoop<Runnable> {
|
||||
|
||||
@Shadow
|
||||
@Final
|
||||
ServerChunkCache field_18810;
|
||||
|
||||
protected ServerChunkCache$MainThreadExecutorMixin(String string) {
|
||||
super(string);
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Support new chunk system
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Override
|
||||
@Overwrite
|
||||
public boolean pollTask() {
|
||||
final ServerChunkCache serverChunkCache = this.field_18810;
|
||||
if (serverChunkCache.runDistanceManagerUpdates()) {
|
||||
return true;
|
||||
} else {
|
||||
return super.pollTask() | ((ChunkSystemServerLevel)serverChunkCache.level).moonrise$getChunkTaskScheduler().executeMainThreadTask();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,254 @@
|
||||
package ca.spottedleaf.moonrise.mixin.chunk_system;
|
||||
|
||||
import ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor;
|
||||
import ca.spottedleaf.moonrise.common.util.CoordinateUtils;
|
||||
import ca.spottedleaf.moonrise.common.util.TickThread;
|
||||
import ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel;
|
||||
import ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkHolderManager;
|
||||
import ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkTaskScheduler;
|
||||
import ca.spottedleaf.moonrise.patches.chunk_system.scheduling.NewChunkHolder;
|
||||
import net.minecraft.server.level.ChunkHolder;
|
||||
import net.minecraft.server.level.ChunkLevel;
|
||||
import net.minecraft.server.level.ChunkResult;
|
||||
import net.minecraft.server.level.FullChunkStatus;
|
||||
import net.minecraft.server.level.ServerChunkCache;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.world.level.chunk.ChunkAccess;
|
||||
import net.minecraft.world.level.chunk.ChunkSource;
|
||||
import net.minecraft.world.level.chunk.LevelChunk;
|
||||
import net.minecraft.world.level.chunk.LightChunk;
|
||||
import net.minecraft.world.level.chunk.status.ChunkStatus;
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Overwrite;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
@Mixin(ServerChunkCache.class)
|
||||
public abstract class ServerChunkCacheMixin extends ChunkSource {
|
||||
|
||||
@Shadow
|
||||
@Final
|
||||
public ServerChunkCache.MainThreadExecutor mainThreadProcessor;
|
||||
|
||||
|
||||
@Shadow
|
||||
@Final
|
||||
public ServerLevel level;
|
||||
|
||||
|
||||
@Unique
|
||||
private ChunkAccess syncLoad(final int chunkX, final int chunkZ, final ChunkStatus toStatus) {
|
||||
final ChunkTaskScheduler chunkTaskScheduler = ((ChunkSystemServerLevel)this.level).moonrise$getChunkTaskScheduler();
|
||||
final CompletableFuture<ChunkAccess> completable = new CompletableFuture<>();
|
||||
chunkTaskScheduler.scheduleChunkLoad(
|
||||
chunkX, chunkZ, toStatus, true, PrioritisedExecutor.Priority.BLOCKING,
|
||||
completable::complete
|
||||
);
|
||||
|
||||
if (TickThread.isTickThreadFor(this.level, chunkX, chunkZ)) {
|
||||
this.mainThreadProcessor.managedBlock(completable::isDone);
|
||||
}
|
||||
|
||||
final ChunkAccess ret = completable.join();
|
||||
if (ret == null) {
|
||||
throw new IllegalStateException("Chunk not loaded when requested");
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Optimise impl and support new chunk system
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Override
|
||||
@Overwrite
|
||||
public ChunkAccess getChunk(final int chunkX, final int chunkZ, final ChunkStatus toStatus,
|
||||
final boolean load) {
|
||||
final ChunkTaskScheduler chunkTaskScheduler = ((ChunkSystemServerLevel)this.level).moonrise$getChunkTaskScheduler();
|
||||
final ChunkHolderManager chunkHolderManager = chunkTaskScheduler.chunkHolderManager;
|
||||
|
||||
final NewChunkHolder currentChunk = chunkHolderManager.getChunkHolder(CoordinateUtils.getChunkKey(chunkX, chunkZ));
|
||||
|
||||
if (toStatus == ChunkStatus.FULL) {
|
||||
if (currentChunk != null && currentChunk.isFullChunkReady() && (currentChunk.getCurrentChunk() instanceof LevelChunk fullChunk)) {
|
||||
return fullChunk;
|
||||
} else if (!load) {
|
||||
return null;
|
||||
}
|
||||
return this.syncLoad(chunkX, chunkZ, toStatus);
|
||||
} else {
|
||||
final NewChunkHolder.ChunkCompletion lastCompletion;
|
||||
if (currentChunk != null && (lastCompletion = currentChunk.getLastChunkCompletion()) != null &&
|
||||
lastCompletion.genStatus().isOrAfter(toStatus)) {
|
||||
return lastCompletion.chunk();
|
||||
} else if (!load) {
|
||||
return null;
|
||||
}
|
||||
return this.syncLoad(chunkX, chunkZ, toStatus);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Support new chunk system
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Override
|
||||
@Overwrite
|
||||
public LevelChunk getChunkNow(final int chunkX, final int chunkZ) {
|
||||
return ((ChunkSystemServerLevel)this.level).moonrise$getFullChunkIfLoaded(chunkX, chunkZ);
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Support new chunk system
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Override
|
||||
@Overwrite
|
||||
public boolean hasChunk(final int chunkX, final int chunkZ) {
|
||||
return this.getChunkNow(chunkX, chunkZ) != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Support new chunk system
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Overwrite
|
||||
public CompletableFuture<ChunkResult<ChunkAccess>> getChunkFutureMainThread(final int chunkX, final int chunkZ,
|
||||
final ChunkStatus toStatus,
|
||||
final boolean create) {
|
||||
TickThread.ensureTickThread(this.level, chunkX, chunkZ, "Scheduling chunk load off-main");
|
||||
|
||||
final int minLevel = ChunkLevel.byStatus(toStatus);
|
||||
final NewChunkHolder chunkHolder = ((ChunkSystemServerLevel)this.level).moonrise$getChunkTaskScheduler().chunkHolderManager.getChunkHolder(chunkX, chunkZ);
|
||||
|
||||
final boolean needsFullScheduling = toStatus == ChunkStatus.FULL && (chunkHolder == null || !chunkHolder.getChunkStatus().isOrAfter(FullChunkStatus.FULL));
|
||||
|
||||
if ((chunkHolder == null || chunkHolder.getTicketLevel() > minLevel || needsFullScheduling) && !create) {
|
||||
return ChunkHolder.UNLOADED_CHUNK_FUTURE;
|
||||
}
|
||||
|
||||
final NewChunkHolder.ChunkCompletion chunkCompletion = chunkHolder == null ? null : chunkHolder.getLastChunkCompletion();
|
||||
if (needsFullScheduling || chunkCompletion == null || !chunkCompletion.genStatus().isOrAfter(toStatus)) {
|
||||
// schedule
|
||||
CompletableFuture<ChunkResult<ChunkAccess>> ret = new CompletableFuture<>();
|
||||
Consumer<ChunkAccess> complete = (ChunkAccess chunk) -> {
|
||||
if (chunk == null) {
|
||||
ret.complete(ChunkHolder.UNLOADED_CHUNK);
|
||||
} else {
|
||||
ret.complete(ChunkResult.of(chunk));
|
||||
}
|
||||
};
|
||||
|
||||
((ChunkSystemServerLevel)this.level).moonrise$getChunkTaskScheduler().scheduleChunkLoad(
|
||||
chunkX, chunkZ, toStatus, true,
|
||||
PrioritisedExecutor.Priority.HIGHER,
|
||||
complete
|
||||
);
|
||||
|
||||
return ret;
|
||||
} else {
|
||||
// can return now
|
||||
return CompletableFuture.completedFuture(ChunkResult.of(chunkCompletion.chunk()));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Support new chunk system
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Override
|
||||
@Overwrite
|
||||
public LightChunk getChunkForLighting(final int chunkX, final int chunkZ) {
|
||||
final NewChunkHolder newChunkHolder = ((ChunkSystemServerLevel)this.level).moonrise$getChunkTaskScheduler().chunkHolderManager.getChunkHolder(chunkX, chunkZ);
|
||||
if (newChunkHolder == null) {
|
||||
return null;
|
||||
}
|
||||
final NewChunkHolder.ChunkCompletion lastCompletion = newChunkHolder.getLastChunkCompletion();
|
||||
if (lastCompletion == null || !lastCompletion.genStatus().isOrAfter(ChunkStatus.INITIALIZE_LIGHT)) {
|
||||
return null;
|
||||
}
|
||||
return lastCompletion.chunk();
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Support new chunk system
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Overwrite
|
||||
public boolean runDistanceManagerUpdates() {
|
||||
return ((ChunkSystemServerLevel)this.level).moonrise$getChunkTaskScheduler().chunkHolderManager.processTicketUpdates();
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Support new chunk system
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Overwrite
|
||||
public boolean isPositionTicking(final long los) {
|
||||
final NewChunkHolder newChunkHolder = ((ChunkSystemServerLevel)this.level).moonrise$getChunkTaskScheduler().chunkHolderManager.getChunkHolder(los);
|
||||
return newChunkHolder != null && newChunkHolder.isTickingReady();
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Support new chunk system
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Override
|
||||
@Overwrite
|
||||
public void close() throws IOException {
|
||||
((ChunkSystemServerLevel)this.level).moonrise$getChunkTaskScheduler().chunkHolderManager.close(true, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Add hook to tick player chunk loader
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Inject(
|
||||
method = "tick",
|
||||
at = @At(
|
||||
value = "INVOKE",
|
||||
target = "Lnet/minecraft/server/level/ServerChunkCache;tickChunks()V"
|
||||
)
|
||||
)
|
||||
private void tickHook(final CallbackInfo ci) {
|
||||
((ChunkSystemServerLevel)this.level).moonrise$getPlayerChunkLoader().tick();
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Support new chunk system
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Overwrite
|
||||
public void getFullChunk(final long pos, final Consumer<LevelChunk> consumer) {
|
||||
final LevelChunk fullChunk = this.getChunkNow(CoordinateUtils.getChunkX(pos), CoordinateUtils.getChunkZ(pos));
|
||||
if (fullChunk != null) {
|
||||
consumer.accept(fullChunk);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Do not run distance manager updates on save. They are not required to run in the new chunk system.
|
||||
* Additionally, distance manager updates may not complete if some error has occurred in the propagator
|
||||
* code or there is deadlock. Thus, on shutdown we want to avoid stalling.
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Redirect(
|
||||
method = "save",
|
||||
at = @At(
|
||||
value = "INVOKE",
|
||||
target = "Lnet/minecraft/server/level/ServerChunkCache;runDistanceManagerUpdates()Z"
|
||||
)
|
||||
)
|
||||
private boolean skipSaveTicketUpdates(final ServerChunkCache instance) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,604 @@
|
||||
package ca.spottedleaf.moonrise.mixin.chunk_system;
|
||||
|
||||
import ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor;
|
||||
import ca.spottedleaf.moonrise.common.util.CoordinateUtils;
|
||||
import ca.spottedleaf.moonrise.patches.chunk_system.io.RegionFileIOThread;
|
||||
import ca.spottedleaf.moonrise.patches.chunk_system.io.datacontroller.ChunkDataController;
|
||||
import ca.spottedleaf.moonrise.patches.chunk_system.io.datacontroller.EntityDataController;
|
||||
import ca.spottedleaf.moonrise.patches.chunk_system.io.datacontroller.PoiDataController;
|
||||
import ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemLevelReader;
|
||||
import ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel;
|
||||
import ca.spottedleaf.moonrise.patches.chunk_system.level.entity.server.ServerEntityLookup;
|
||||
import ca.spottedleaf.moonrise.patches.chunk_system.player.RegionizedPlayerChunkLoader;
|
||||
import ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkHolderManager;
|
||||
import ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkTaskScheduler;
|
||||
import ca.spottedleaf.moonrise.patches.chunk_system.scheduling.NewChunkHolder;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Holder;
|
||||
import net.minecraft.core.RegistryAccess;
|
||||
import net.minecraft.resources.ResourceKey;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.server.level.ChunkMap;
|
||||
import net.minecraft.server.level.DistanceManager;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.server.level.progress.ChunkProgressListener;
|
||||
import net.minecraft.util.profiling.ProfilerFiller;
|
||||
import net.minecraft.world.RandomSequences;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.level.ChunkPos;
|
||||
import net.minecraft.world.level.CustomSpawner;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.WorldGenLevel;
|
||||
import net.minecraft.world.level.chunk.ChunkAccess;
|
||||
import net.minecraft.world.level.chunk.LevelChunk;
|
||||
import net.minecraft.world.level.chunk.status.ChunkStatus;
|
||||
import net.minecraft.world.level.chunk.storage.RegionStorageInfo;
|
||||
import net.minecraft.world.level.dimension.DimensionType;
|
||||
import net.minecraft.world.level.dimension.LevelStem;
|
||||
import net.minecraft.world.level.entity.EntityAccess;
|
||||
import net.minecraft.world.level.entity.LevelEntityGetter;
|
||||
import net.minecraft.world.level.entity.PersistentEntitySectionManager;
|
||||
import net.minecraft.world.level.storage.LevelStorageSource;
|
||||
import net.minecraft.world.level.storage.ServerLevelData;
|
||||
import net.minecraft.world.level.storage.WritableLevelData;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Overwrite;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
import java.io.Writer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
@Mixin(ServerLevel.class)
|
||||
public abstract class ServerLevelMixin extends Level implements ChunkSystemServerLevel, ChunkSystemLevelReader, WorldGenLevel {
|
||||
|
||||
@Shadow
|
||||
private PersistentEntitySectionManager<Entity> entityManager;
|
||||
|
||||
protected ServerLevelMixin(WritableLevelData writableLevelData, ResourceKey<Level> resourceKey, RegistryAccess registryAccess, Holder<DimensionType> holder, Supplier<ProfilerFiller> supplier, boolean bl, boolean bl2, long l, int i) {
|
||||
super(writableLevelData, resourceKey, registryAccess, holder, supplier, bl, bl2, l, i);
|
||||
}
|
||||
|
||||
@Unique
|
||||
private boolean markedClosing;
|
||||
|
||||
@Unique
|
||||
private final RegionizedPlayerChunkLoader.ViewDistanceHolder viewDistanceHolder = new RegionizedPlayerChunkLoader.ViewDistanceHolder();
|
||||
|
||||
@Unique
|
||||
private final RegionizedPlayerChunkLoader chunkLoader = new RegionizedPlayerChunkLoader((ServerLevel)(Object)this);
|
||||
|
||||
@Unique
|
||||
private EntityDataController entityDataController;
|
||||
|
||||
@Unique
|
||||
private PoiDataController poiDataController;
|
||||
|
||||
@Unique
|
||||
private ChunkDataController chunkDataController;
|
||||
|
||||
@Unique
|
||||
private ChunkTaskScheduler chunkTaskScheduler;
|
||||
|
||||
/**
|
||||
* @reason Initialise fields / destroy entity manager state
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Inject(
|
||||
method = "<init>",
|
||||
at = @At(
|
||||
value = "RETURN"
|
||||
)
|
||||
)
|
||||
private void init(MinecraftServer minecraftServer, Executor executor,
|
||||
LevelStorageSource.LevelStorageAccess levelStorageAccess, ServerLevelData serverLevelData,
|
||||
ResourceKey<Level> resourceKey, LevelStem levelStem, ChunkProgressListener chunkProgressListener,
|
||||
boolean bl, long l, List<CustomSpawner> list, boolean bl2, RandomSequences randomSequences,
|
||||
CallbackInfo ci) {
|
||||
this.entityManager = null;
|
||||
|
||||
this.entityDataController = new EntityDataController(
|
||||
new EntityDataController.EntityRegionFileStorage(
|
||||
new RegionStorageInfo(levelStorageAccess.getLevelId(), resourceKey, "entities"),
|
||||
levelStorageAccess.getDimensionPath(resourceKey).resolve("entities"),
|
||||
minecraftServer.forceSynchronousWrites()
|
||||
)
|
||||
);
|
||||
this.poiDataController = new PoiDataController((ServerLevel)(Object)this);
|
||||
this.chunkDataController = new ChunkDataController((ServerLevel)(Object)this);
|
||||
this.moonrise$setEntityLookup(new ServerEntityLookup((ServerLevel)(Object)this, ((ServerLevel)(Object)this).new EntityCallbacks()));
|
||||
this.chunkTaskScheduler = new ChunkTaskScheduler((ServerLevel)(Object)this, ChunkTaskScheduler.workerThreads);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final LevelChunk moonrise$getFullChunkIfLoaded(final int chunkX, final int chunkZ) {
|
||||
final NewChunkHolder newChunkHolder = this.moonrise$getChunkTaskScheduler().chunkHolderManager.getChunkHolder(CoordinateUtils.getChunkKey(chunkX, chunkZ));
|
||||
if (!newChunkHolder.isFullChunkReady()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (newChunkHolder.getCurrentChunk() instanceof LevelChunk levelChunk) {
|
||||
return levelChunk;
|
||||
}
|
||||
// race condition: chunk unloaded, only happens off-main
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final ChunkAccess moonrise$getAnyChunkIfLoaded(final int chunkX, final int chunkZ) {
|
||||
final NewChunkHolder newChunkHolder = this.moonrise$getChunkTaskScheduler().chunkHolderManager.getChunkHolder(CoordinateUtils.getChunkKey(chunkX, chunkZ));
|
||||
if (newChunkHolder == null) {
|
||||
return null;
|
||||
}
|
||||
final NewChunkHolder.ChunkCompletion lastCompletion = newChunkHolder.getLastChunkCompletion();
|
||||
return lastCompletion == null ? null : lastCompletion.chunk();
|
||||
}
|
||||
|
||||
@Override
|
||||
public final ChunkAccess moonrise$getSpecificChunkIfLoaded(final int chunkX, final int chunkZ, final ChunkStatus leastStatus) {
|
||||
final NewChunkHolder newChunkHolder = this.moonrise$getChunkTaskScheduler().chunkHolderManager.getChunkHolder(chunkX, chunkZ);
|
||||
if (newChunkHolder == null) {
|
||||
return null;
|
||||
}
|
||||
final NewChunkHolder.ChunkCompletion lastCompletion = newChunkHolder.getLastChunkCompletion();
|
||||
return lastCompletion == null || !lastCompletion.genStatus().isOrAfter(leastStatus) ? null : lastCompletion.chunk();
|
||||
}
|
||||
|
||||
@Override
|
||||
public final ChunkAccess moonrise$syncLoadNonFull(final int chunkX, final int chunkZ, final ChunkStatus status) {
|
||||
return this.moonrise$getChunkTaskScheduler().syncLoadNonFull(chunkX, chunkZ, status);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final ChunkTaskScheduler moonrise$getChunkTaskScheduler() {
|
||||
return this.chunkTaskScheduler;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final RegionFileIOThread.ChunkDataController moonrise$getChunkDataController() {
|
||||
return this.chunkDataController;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final RegionFileIOThread.ChunkDataController moonrise$getPoiChunkDataController() {
|
||||
return this.poiDataController;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final RegionFileIOThread.ChunkDataController moonrise$getEntityChunkDataController() {
|
||||
return this.entityDataController;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final int moonrise$getRegionChunkShift() {
|
||||
// current default in Folia
|
||||
// note that there is no actual regionizing taking place in Moonrise...
|
||||
return 2;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final boolean moonrise$isMarkedClosing() {
|
||||
return this.markedClosing;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void moonrise$setMarkedClosing(final boolean value) {
|
||||
this.markedClosing = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final RegionizedPlayerChunkLoader moonrise$getPlayerChunkLoader() {
|
||||
return this.chunkLoader;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void moonrise$loadChunksAsync(final BlockPos pos, final int radiusBlocks,
|
||||
final PrioritisedExecutor.Priority priority,
|
||||
final Consumer<List<ChunkAccess>> onLoad) {
|
||||
this.moonrise$loadChunksAsync(
|
||||
(pos.getX() - radiusBlocks) >> 4,
|
||||
(pos.getX() + radiusBlocks) >> 4,
|
||||
(pos.getZ() - radiusBlocks) >> 4,
|
||||
(pos.getZ() + radiusBlocks) >> 4,
|
||||
priority, onLoad
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void moonrise$loadChunksAsync(final BlockPos pos, final int radiusBlocks,
|
||||
final ChunkStatus chunkStatus, final PrioritisedExecutor.Priority priority,
|
||||
final Consumer<List<ChunkAccess>> onLoad) {
|
||||
this.moonrise$loadChunksAsync(
|
||||
(pos.getX() - radiusBlocks) >> 4,
|
||||
(pos.getX() + radiusBlocks) >> 4,
|
||||
(pos.getZ() - radiusBlocks) >> 4,
|
||||
(pos.getZ() + radiusBlocks) >> 4,
|
||||
chunkStatus, priority, onLoad
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void moonrise$loadChunksAsync(final int minChunkX, final int maxChunkX, final int minChunkZ, final int maxChunkZ,
|
||||
final PrioritisedExecutor.Priority priority,
|
||||
final Consumer<List<ChunkAccess>> onLoad) {
|
||||
this.moonrise$loadChunksAsync(minChunkX, maxChunkX, minChunkZ, maxChunkZ, ChunkStatus.FULL, priority, onLoad);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void moonrise$loadChunksAsync(final int minChunkX, final int maxChunkX, final int minChunkZ, final int maxChunkZ,
|
||||
final ChunkStatus chunkStatus, final PrioritisedExecutor.Priority priority,
|
||||
final Consumer<List<ChunkAccess>> onLoad) {
|
||||
final ChunkTaskScheduler chunkTaskScheduler = this.moonrise$getChunkTaskScheduler();
|
||||
final ChunkHolderManager chunkHolderManager = chunkTaskScheduler.chunkHolderManager;
|
||||
|
||||
final int requiredChunks = (maxChunkX - minChunkX + 1) * (maxChunkZ - minChunkZ + 1);
|
||||
final AtomicInteger loadedChunks = new AtomicInteger();
|
||||
final Long holderIdentifier = ChunkTaskScheduler.getNextChunkLoadId();
|
||||
final int ticketLevel = ChunkTaskScheduler.getTicketLevel(chunkStatus);
|
||||
|
||||
final List<ChunkAccess> ret = new ArrayList<>(requiredChunks);
|
||||
|
||||
final Consumer<ChunkAccess> consumer = (final ChunkAccess chunk) -> {
|
||||
if (chunk != null) {
|
||||
synchronized (ret) {
|
||||
ret.add(chunk);
|
||||
}
|
||||
chunkHolderManager.addTicketAtLevel(ChunkTaskScheduler.CHUNK_LOAD, chunk.getPos(), ticketLevel, holderIdentifier);
|
||||
}
|
||||
if (loadedChunks.incrementAndGet() == requiredChunks) {
|
||||
try {
|
||||
onLoad.accept(java.util.Collections.unmodifiableList(ret));
|
||||
} finally {
|
||||
for (int i = 0, len = ret.size(); i < len; ++i) {
|
||||
final ChunkPos chunkPos = ret.get(i).getPos();
|
||||
|
||||
chunkHolderManager.removeTicketAtLevel(ChunkTaskScheduler.CHUNK_LOAD, chunkPos, ticketLevel, holderIdentifier);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
for (int cx = minChunkX; cx <= maxChunkX; ++cx) {
|
||||
for (int cz = minChunkZ; cz <= maxChunkZ; ++cz) {
|
||||
chunkTaskScheduler.scheduleChunkLoad(cx, cz, chunkStatus, true, priority, consumer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final RegionizedPlayerChunkLoader.ViewDistanceHolder moonrise$getViewDistanceHolder() {
|
||||
return this.viewDistanceHolder;
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Entities are guaranteed to be ticking in the new chunk system
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Redirect(
|
||||
method = "method_31420",
|
||||
at = @At(
|
||||
value = "INVOKE",
|
||||
target = "Lnet/minecraft/server/level/DistanceManager;inEntityTickingRange(J)Z"
|
||||
)
|
||||
)
|
||||
private boolean shortCircuitTickCheck(final DistanceManager instance, final long chunk) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason This logic is handled by the chunk system
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Redirect(
|
||||
method = "tick",
|
||||
at = @At(
|
||||
value = "INVOKE",
|
||||
target = "Lnet/minecraft/world/level/entity/PersistentEntitySectionManager;tick()V"
|
||||
)
|
||||
)
|
||||
private void redirectEntityManagerTick(final PersistentEntitySectionManager<Entity> instance) {}
|
||||
|
||||
/**
|
||||
* @reason Optimise implementation and route to new chunk system
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Override
|
||||
@Overwrite
|
||||
public boolean shouldTickBlocksAt(final long chunkPos) {
|
||||
final NewChunkHolder holder = this.moonrise$getChunkTaskScheduler().chunkHolderManager.getChunkHolder(chunkPos);
|
||||
return holder != null && holder.isTickingReady();
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason saveAll handled by ServerChunkCache#save
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Redirect(
|
||||
method = "save",
|
||||
at = @At(
|
||||
value = "INVOKE",
|
||||
target = "Lnet/minecraft/world/level/entity/PersistentEntitySectionManager;saveAll()V"
|
||||
)
|
||||
)
|
||||
private void redirectSaveAll(final PersistentEntitySectionManager<Entity> instance) {}
|
||||
|
||||
/**
|
||||
* @reason autoSave handled by ServerChunkCache#save
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Redirect(
|
||||
method = "save",
|
||||
at = @At(
|
||||
value = "INVOKE",
|
||||
target = "Lnet/minecraft/world/level/entity/PersistentEntitySectionManager;autoSave()V"
|
||||
)
|
||||
)
|
||||
private void redirectAutoSave(final PersistentEntitySectionManager<Entity> instance) {}
|
||||
|
||||
/**
|
||||
* @reason Redirect to new entity manager
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Redirect(
|
||||
method = "addPlayer",
|
||||
at = @At(
|
||||
value = "INVOKE",
|
||||
target = "Lnet/minecraft/world/level/entity/PersistentEntitySectionManager;addNewEntity(Lnet/minecraft/world/level/entity/EntityAccess;)Z"
|
||||
)
|
||||
)
|
||||
private <T extends EntityAccess> boolean redirectAddPlayerEntity(final PersistentEntitySectionManager<T> instance, final T entity) {
|
||||
return this.moonrise$getEntityLookup().addNewEntity((Entity)entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Redirect to new entity manager
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Redirect(
|
||||
method = "addEntity",
|
||||
at = @At(
|
||||
value = "INVOKE",
|
||||
target = "Lnet/minecraft/world/level/entity/PersistentEntitySectionManager;addNewEntity(Lnet/minecraft/world/level/entity/EntityAccess;)Z"
|
||||
)
|
||||
)
|
||||
private <T extends EntityAccess> boolean redirectAddEntityEntity(final PersistentEntitySectionManager<T> instance, final T entity) {
|
||||
return this.moonrise$getEntityLookup().addNewEntity((Entity)entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Redirect to new entity manager
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Overwrite
|
||||
public boolean tryAddFreshEntityWithPassengers(Entity entity) {
|
||||
final Stream<UUID> stream = entity.getSelfAndPassengers().map(Entity::getUUID);
|
||||
if (stream.anyMatch(this.moonrise$getEntityLookup()::hasEntity)) {
|
||||
return false;
|
||||
} else {
|
||||
this.addFreshEntityWithPassengers(entity);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Redirect to new entity manager
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Redirect(
|
||||
method = "saveDebugReport",
|
||||
at = @At(
|
||||
value = "INVOKE",
|
||||
target = "Lnet/minecraft/world/level/entity/PersistentEntitySectionManager;gatherStats()Ljava/lang/String;"
|
||||
)
|
||||
)
|
||||
private String redirectDebugStats(final PersistentEntitySectionManager<Entity> instance) {
|
||||
return this.moonrise$getEntityLookup().getDebugInfo();
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason dumpChunks not implemented
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Redirect(
|
||||
method = "saveDebugReport",
|
||||
at = @At(
|
||||
value = "INVOKE",
|
||||
target = "Lnet/minecraft/server/level/ChunkMap;dumpChunks(Ljava/io/Writer;)V"
|
||||
)
|
||||
)
|
||||
private void redirectChunkMapDebug(final ChunkMap instance, final Writer writer) {}
|
||||
|
||||
/**
|
||||
* @reason dumpSections not implemented
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Redirect(
|
||||
method = "saveDebugReport",
|
||||
at = @At(
|
||||
value = "INVOKE",
|
||||
target = "Lnet/minecraft/world/level/entity/PersistentEntitySectionManager;dumpSections(Ljava/io/Writer;)V"
|
||||
)
|
||||
)
|
||||
private void redirectEntityManagerDebug(final PersistentEntitySectionManager<Entity> instance, final Writer writer) {}
|
||||
|
||||
/**
|
||||
* @reason Redirect to new entity manager
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Redirect(
|
||||
method = "getWatchdogStats",
|
||||
at = @At(
|
||||
value = "INVOKE",
|
||||
target = "Lnet/minecraft/world/level/entity/PersistentEntitySectionManager;gatherStats()Ljava/lang/String;"
|
||||
)
|
||||
)
|
||||
private String redirectWatchdogStats1(final PersistentEntitySectionManager<Entity> instance) {
|
||||
return this.moonrise$getEntityLookup().getDebugInfo();
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Redirect to new entity manager
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Redirect(
|
||||
method = "getWatchdogStats",
|
||||
at = @At(
|
||||
value = "INVOKE",
|
||||
target = "Lnet/minecraft/world/level/entity/PersistentEntitySectionManager;getEntityGetter()Lnet/minecraft/world/level/entity/LevelEntityGetter;"
|
||||
)
|
||||
)
|
||||
private LevelEntityGetter<Entity> redirectWatchdogStats2(final PersistentEntitySectionManager<Entity> instance) {
|
||||
return this.moonrise$getEntityLookup();
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Redirect to new entity manager
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Redirect(
|
||||
method = "getEntities()Lnet/minecraft/world/level/entity/LevelEntityGetter;",
|
||||
at = @At(
|
||||
value = "INVOKE",
|
||||
target = "Lnet/minecraft/world/level/entity/PersistentEntitySectionManager;getEntityGetter()Lnet/minecraft/world/level/entity/LevelEntityGetter;"
|
||||
)
|
||||
)
|
||||
private LevelEntityGetter<Entity> redirectGetEntities(final PersistentEntitySectionManager<Entity> instance) {
|
||||
return this.moonrise$getEntityLookup();
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Redirect to new entity manager
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Redirect(
|
||||
method = "addLegacyChunkEntities",
|
||||
at = @At(
|
||||
value = "INVOKE",
|
||||
target = "Lnet/minecraft/world/level/entity/PersistentEntitySectionManager;addLegacyChunkEntities(Ljava/util/stream/Stream;)V"
|
||||
)
|
||||
)
|
||||
private void redirectLegacyChunkEntities(final PersistentEntitySectionManager<Entity> instance,
|
||||
final Stream<Entity> stream) {
|
||||
this.moonrise$getEntityLookup().addLegacyChunkEntities(stream.toList(), null); // TODO
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Redirect to new entity manager
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Redirect(
|
||||
method = "addWorldGenChunkEntities",
|
||||
at = @At(
|
||||
value = "INVOKE",
|
||||
target = "Lnet/minecraft/world/level/entity/PersistentEntitySectionManager;addWorldGenChunkEntities(Ljava/util/stream/Stream;)V"
|
||||
)
|
||||
)
|
||||
private void redirectWorldGenChunkEntities(final PersistentEntitySectionManager<Entity> instance,
|
||||
final Stream<Entity> stream) {
|
||||
this.moonrise$getEntityLookup().addWorldGenChunkEntities(stream.toList(), null); // TODO
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Level close now handles this
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Redirect(
|
||||
method = "close",
|
||||
at = @At(
|
||||
value = "INVOKE",
|
||||
target = "Lnet/minecraft/world/level/entity/PersistentEntitySectionManager;close()V"
|
||||
)
|
||||
)
|
||||
private void redirectClose(final PersistentEntitySectionManager<Entity> instance) {}
|
||||
|
||||
/**
|
||||
* @reason Redirect to new entity manager
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Redirect(
|
||||
method = "gatherChunkSourceStats",
|
||||
at = @At(
|
||||
value = "INVOKE",
|
||||
target = "Lnet/minecraft/world/level/entity/PersistentEntitySectionManager;gatherStats()Ljava/lang/String;"
|
||||
)
|
||||
)
|
||||
private String redirectGatherChunkSourceStats(final PersistentEntitySectionManager<Entity> instance) {
|
||||
return this.moonrise$getEntityLookup().getDebugInfo();
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Redirect to chunk system
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Overwrite
|
||||
public boolean areEntitiesLoaded(final long chunkPos) {
|
||||
// chunk loading guarantees entity loading
|
||||
return this.moonrise$getAnyChunkIfLoaded(CoordinateUtils.getChunkX(chunkPos), CoordinateUtils.getChunkZ(chunkPos)) != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Redirect to chunk system
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Overwrite
|
||||
public boolean isPositionTickingWithEntitiesLoaded(final long chunkPos) {
|
||||
final NewChunkHolder chunkHolder = this.moonrise$getChunkTaskScheduler().chunkHolderManager.getChunkHolder(chunkPos);
|
||||
// isTicking implies the chunk is loaded, and the chunk is loaded now implies the entities are loaded
|
||||
return chunkHolder != null && chunkHolder.isTickingReady();
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Redirect to chunk system
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Overwrite
|
||||
public boolean isPositionEntityTicking(BlockPos pos) {
|
||||
final NewChunkHolder chunkHolder = this.moonrise$getChunkTaskScheduler().chunkHolderManager.getChunkHolder(CoordinateUtils.getChunkKey(pos));
|
||||
return chunkHolder != null && chunkHolder.isEntityTickingReady();
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Redirect to chunk system
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Overwrite
|
||||
public boolean isNaturalSpawningAllowed(final BlockPos pos) {
|
||||
final NewChunkHolder chunkHolder = this.moonrise$getChunkTaskScheduler().chunkHolderManager.getChunkHolder(CoordinateUtils.getChunkKey(pos));
|
||||
return chunkHolder != null && chunkHolder.isEntityTickingReady();
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Redirect to chunk system
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Overwrite
|
||||
public boolean isNaturalSpawningAllowed(final ChunkPos pos) {
|
||||
final NewChunkHolder chunkHolder = this.moonrise$getChunkTaskScheduler().chunkHolderManager.getChunkHolder(CoordinateUtils.getChunkKey(pos));
|
||||
return chunkHolder != null && chunkHolder.isEntityTickingReady();
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Redirect to new entity manager
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Redirect(
|
||||
method = "method_54438",
|
||||
at = @At(
|
||||
value = "INVOKE",
|
||||
target = "Lnet/minecraft/world/level/entity/PersistentEntitySectionManager;count()I"
|
||||
)
|
||||
)
|
||||
private int redirectCrashCount(final PersistentEntitySectionManager<Entity> instance) {
|
||||
return this.moonrise$getEntityLookup().getEntityCount();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package ca.spottedleaf.moonrise.mixin.chunk_system;
|
||||
|
||||
import ca.spottedleaf.moonrise.patches.chunk_system.player.ChunkSystemServerPlayer;
|
||||
import ca.spottedleaf.moonrise.patches.chunk_system.player.RegionizedPlayerChunkLoader;
|
||||
import com.mojang.authlib.GameProfile;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraft.world.level.Level;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
@Mixin(ServerPlayer.class)
|
||||
public abstract class ServerPlayerMixin extends Player implements ChunkSystemServerPlayer {
|
||||
public ServerPlayerMixin(Level level, BlockPos blockPos, float f, GameProfile gameProfile) {
|
||||
super(level, blockPos, f, gameProfile);
|
||||
}
|
||||
|
||||
@Unique
|
||||
private boolean isRealPlayer;
|
||||
|
||||
@Unique
|
||||
private RegionizedPlayerChunkLoader.PlayerChunkLoaderData chunkLoader;
|
||||
|
||||
@Unique
|
||||
private RegionizedPlayerChunkLoader.ViewDistanceHolder viewDistanceHolder;
|
||||
|
||||
/**
|
||||
* @reason Initialise fields
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Inject(
|
||||
method = "<init>",
|
||||
at = @At(
|
||||
value = "RETURN"
|
||||
)
|
||||
)
|
||||
private void init(final CallbackInfo ci) {
|
||||
this.viewDistanceHolder = new RegionizedPlayerChunkLoader.ViewDistanceHolder();
|
||||
}
|
||||
|
||||
@Override
|
||||
public final boolean moonrise$isRealPlayer() {
|
||||
return this.isRealPlayer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void moonrise$setRealPlayer(final boolean real) {
|
||||
this.isRealPlayer = real;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final RegionizedPlayerChunkLoader.PlayerChunkLoaderData moonrise$getChunkLoader() {
|
||||
return this.chunkLoader;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void moonrise$setChunkLoader(final RegionizedPlayerChunkLoader.PlayerChunkLoaderData loader) {
|
||||
this.chunkLoader = loader;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final RegionizedPlayerChunkLoader.ViewDistanceHolder moonrise$getViewDistanceHolder() {
|
||||
return this.viewDistanceHolder;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
package ca.spottedleaf.moonrise.mixin.chunk_system;
|
||||
|
||||
import ca.spottedleaf.moonrise.patches.chunk_system.util.ChunkSystemSortedArraySet;
|
||||
import net.minecraft.util.SortedArraySet;
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import java.util.AbstractSet;
|
||||
import java.util.Arrays;
|
||||
import java.util.Comparator;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
@Mixin(SortedArraySet.class)
|
||||
public abstract class SortedArraySetMixin<T> extends AbstractSet<T> implements ChunkSystemSortedArraySet<T> {
|
||||
|
||||
@Shadow
|
||||
int size;
|
||||
|
||||
@Shadow
|
||||
T[] contents;
|
||||
|
||||
@Shadow
|
||||
@Final
|
||||
private Comparator<T> comparator;
|
||||
|
||||
@Shadow
|
||||
protected abstract int findIndex(T object);
|
||||
|
||||
@Shadow
|
||||
private static int getInsertionPosition(int i) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Shadow
|
||||
protected abstract void addInternal(T object, int i);
|
||||
|
||||
@Shadow
|
||||
abstract void removeInternal(int i);
|
||||
|
||||
|
||||
@Override
|
||||
public final boolean removeIf(final Predicate<? super T> filter) {
|
||||
// prev. impl used an iterator, which could be n^2 and creates garbage
|
||||
int i = 0;
|
||||
final int len = this.size;
|
||||
final T[] backingArray = this.contents;
|
||||
|
||||
for (;;) {
|
||||
if (i >= len) {
|
||||
return false;
|
||||
}
|
||||
if (!filter.test(backingArray[i])) {
|
||||
++i;
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// we only want to write back to backingArray if we really need to
|
||||
|
||||
int lastIndex = i; // this is where new elements are shifted to
|
||||
|
||||
for (; i < len; ++i) {
|
||||
final T curr = backingArray[i];
|
||||
if (!filter.test(curr)) { // if test throws we're screwed
|
||||
backingArray[lastIndex++] = curr;
|
||||
}
|
||||
}
|
||||
|
||||
// cleanup end
|
||||
Arrays.fill(backingArray, lastIndex, len, null);
|
||||
this.size = lastIndex;
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final T moonrise$replace(final T object) {
|
||||
final int index = this.findIndex(object);
|
||||
if (index >= 0) {
|
||||
final T old = this.contents[index];
|
||||
this.contents[index] = object;
|
||||
return old;
|
||||
} else {
|
||||
this.addInternal(object, getInsertionPosition(index));
|
||||
return object;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final T moonrise$removeAndGet(final T object) {
|
||||
int i = this.findIndex(object);
|
||||
if (i >= 0) {
|
||||
final T ret = this.contents[i];
|
||||
this.removeInternal(i);
|
||||
return ret;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final SortedArraySet<T> moonrise$copy() {
|
||||
final SortedArraySet<T> ret = SortedArraySet.create(this.comparator, 0);
|
||||
|
||||
((SortedArraySetMixin<T>)(Object)ret).size = this.size;
|
||||
((SortedArraySetMixin<T>)(Object)ret).contents = Arrays.copyOf(this.contents, this.size);
|
||||
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
package ca.spottedleaf.moonrise.mixin.chunk_system;
|
||||
|
||||
import ca.spottedleaf.moonrise.common.map.SynchronisedLong2BooleanMap;
|
||||
import ca.spottedleaf.moonrise.common.map.SynchronisedLong2ObjectMap;
|
||||
import com.mojang.datafixers.DataFixer;
|
||||
import it.unimi.dsi.fastutil.longs.Long2BooleanMap;
|
||||
import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.objects.Object2IntMap;
|
||||
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
|
||||
import net.minecraft.core.RegistryAccess;
|
||||
import net.minecraft.resources.ResourceKey;
|
||||
import net.minecraft.world.level.ChunkPos;
|
||||
import net.minecraft.world.level.LevelHeightAccessor;
|
||||
import net.minecraft.world.level.biome.BiomeSource;
|
||||
import net.minecraft.world.level.chunk.ChunkGenerator;
|
||||
import net.minecraft.world.level.chunk.storage.ChunkScanAccess;
|
||||
import net.minecraft.world.level.levelgen.RandomState;
|
||||
import net.minecraft.world.level.levelgen.structure.Structure;
|
||||
import net.minecraft.world.level.levelgen.structure.StructureCheck;
|
||||
import net.minecraft.world.level.levelgen.structure.StructureCheckResult;
|
||||
import net.minecraft.world.level.levelgen.structure.placement.StructurePlacement;
|
||||
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplateManager;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Overwrite;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
@Mixin(StructureCheck.class)
|
||||
public abstract class StructureCheckMixin {
|
||||
|
||||
@Shadow
|
||||
private Long2ObjectMap<Object2IntMap<Structure>> loadedChunks;
|
||||
|
||||
@Shadow
|
||||
private Map<Structure, Long2BooleanMap> featureChecks;
|
||||
|
||||
@Shadow
|
||||
protected abstract boolean canCreateStructure(ChunkPos chunkPos, Structure structure);
|
||||
|
||||
@Shadow
|
||||
private static Object2IntMap<Structure> deduplicateEmptyMap(Object2IntMap<Structure> object2IntMap) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
// make sure to purge entries from the maps to prevent memory leaks
|
||||
@Unique
|
||||
private static final int CHUNK_TOTAL_LIMIT = 50 * (2 * 100 + 1) * (2 * 100 + 1); // cache 50 structure lookups
|
||||
@Unique
|
||||
private static final int PER_FEATURE_CHECK_LIMIT = 50 * (2 * 100 + 1) * (2 * 100 + 1); // cache 50 structure lookups
|
||||
@Unique
|
||||
private final SynchronisedLong2ObjectMap<Object2IntMap<Structure>> loadedChunksSafe = new SynchronisedLong2ObjectMap<>(CHUNK_TOTAL_LIMIT);
|
||||
@Unique
|
||||
private final ConcurrentHashMap<Structure, SynchronisedLong2BooleanMap> featureChecksSafe = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* @reason Initialise fields and destroy old state
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Inject(
|
||||
method = "<init>",
|
||||
at = @At(
|
||||
value = "RETURN"
|
||||
)
|
||||
)
|
||||
private void initHook(ChunkScanAccess chunkScanAccess, RegistryAccess registryAccess,
|
||||
StructureTemplateManager structureTemplateManager, ResourceKey resourceKey,
|
||||
ChunkGenerator chunkGenerator, RandomState randomState, LevelHeightAccessor levelHeightAccessor,
|
||||
BiomeSource biomeSource, long l, DataFixer dataFixer, CallbackInfo ci) {
|
||||
this.loadedChunks = null;
|
||||
this.featureChecks = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Redirect to new map
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Redirect(
|
||||
method = "checkStart",
|
||||
at = @At(
|
||||
value = "INVOKE",
|
||||
target = "Lit/unimi/dsi/fastutil/longs/Long2ObjectMap;get(J)Ljava/lang/Object;"
|
||||
)
|
||||
)
|
||||
private <V> V redirectCachedGet(final Long2ObjectMap<V> instance, final long pos) {
|
||||
return (V)this.loadedChunksSafe.get(pos);
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Redirect to new map
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Inject(
|
||||
method = "checkStart",
|
||||
cancellable = true,
|
||||
at = @At(
|
||||
value = "INVOKE",
|
||||
target = "Ljava/util/Map;computeIfAbsent(Ljava/lang/Object;Ljava/util/function/Function;)Ljava/lang/Object;"
|
||||
)
|
||||
)
|
||||
private void redirectUncached(final ChunkPos pos, final Structure structure, final StructurePlacement structurePlacement,
|
||||
final boolean bl, final CallbackInfoReturnable<StructureCheckResult> cir) {
|
||||
final boolean ret = this.featureChecksSafe
|
||||
.computeIfAbsent(structure, structure2 -> new SynchronisedLong2BooleanMap(PER_FEATURE_CHECK_LIMIT))
|
||||
.getOrCompute(pos.toLong(), chunkPos -> this.canCreateStructure(pos, structure));
|
||||
cir.setReturnValue(!ret ? StructureCheckResult.START_NOT_PRESENT : StructureCheckResult.CHUNK_LOAD_NEEDED);
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Redirect to new map
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Overwrite
|
||||
public void storeFullResults(final long pos, final Object2IntMap<Structure> referencesByStructure) {
|
||||
this.loadedChunksSafe.put(pos, deduplicateEmptyMap(referencesByStructure));
|
||||
// once we insert into loadedChunks, we don't really need to be very careful about removing everything
|
||||
// from this map, as everything that checks this map uses loadedChunks first
|
||||
// so, one way or another it's a race condition that doesn't matter
|
||||
for (SynchronisedLong2BooleanMap value : this.featureChecksSafe.values()) {
|
||||
value.remove(pos);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Redirect to new map
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Overwrite
|
||||
public void incrementReference(final ChunkPos pos, final Structure structure) {
|
||||
this.loadedChunksSafe.compute(pos.toLong(), (posx, referencesByStructure) -> { // Paper start - rewrite chunk system - synchronise this class
|
||||
// make this COW so that we do not mutate state that may be currently in use
|
||||
if (referencesByStructure == null) {
|
||||
referencesByStructure = new Object2IntOpenHashMap<>();
|
||||
} else {
|
||||
referencesByStructure = referencesByStructure instanceof Object2IntOpenHashMap<Structure> fastClone ? fastClone.clone() : new Object2IntOpenHashMap<>(referencesByStructure);
|
||||
}
|
||||
// Paper end - rewrite chunk system - synchronise this class
|
||||
|
||||
referencesByStructure.computeInt(structure, (feature, references) -> references == null ? 1 : references + 1);
|
||||
return referencesByStructure;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
package ca.spottedleaf.moonrise.mixin.chunk_system;
|
||||
|
||||
import ca.spottedleaf.moonrise.patches.chunk_system.ticket.ChunkSystemTicket;
|
||||
import net.minecraft.server.level.Ticket;
|
||||
import net.minecraft.server.level.TicketType;
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Overwrite;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
|
||||
@Mixin(Ticket.class)
|
||||
public abstract class TicketMixin<T> implements ChunkSystemTicket<T>, Comparable<Ticket<?>> {
|
||||
|
||||
@Shadow
|
||||
@Final
|
||||
private TicketType<T> type;
|
||||
|
||||
@Shadow
|
||||
@Final
|
||||
private int ticketLevel;
|
||||
|
||||
@Shadow
|
||||
@Final
|
||||
public T key;
|
||||
|
||||
|
||||
@Unique
|
||||
private long removeDelay;
|
||||
|
||||
@Override
|
||||
public final long moonrise$getRemoveDelay() {
|
||||
return this.removeDelay;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void moonrise$setRemoveDelay(final long removeDelay) {
|
||||
this.removeDelay = removeDelay;
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Change debug to include remove delay
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Overwrite
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Ticket[" + this.type + " " + this.ticketLevel + " (" + this.key + ")] to die in " + this.removeDelay;
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Remove old chunk system hook
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Overwrite
|
||||
public void setCreatedTick(final long tickCreated) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Remove old chunk system hook
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Overwrite
|
||||
public boolean timedOut(final long currentTick) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
package ca.spottedleaf.moonrise.mixin.collisions;
|
||||
|
||||
import ca.spottedleaf.moonrise.patches.chunk_system.world.ChunkSystemEntityGetter;
|
||||
import ca.spottedleaf.moonrise.patches.collisions.CollisionUtil;
|
||||
import ca.spottedleaf.moonrise.patches.collisions.entity.CollisionEntity;
|
||||
import ca.spottedleaf.moonrise.patches.chunk_system.entity.ChunkSystemEntity;
|
||||
import ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape;
|
||||
import ca.spottedleaf.moonrise.patches.collisions.world.CollisionEntityGetter;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.level.EntityGetter;
|
||||
import net.minecraft.world.phys.AABB;
|
||||
@@ -17,7 +17,7 @@ import java.util.List;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
@Mixin(EntityGetter.class)
|
||||
public interface EntityGetterMixin extends CollisionEntityGetter {
|
||||
public interface EntityGetterMixin {
|
||||
|
||||
@Shadow
|
||||
List<Entity> getEntities(final Entity entity, final AABB box, final Predicate<? super Entity> predicate);
|
||||
@@ -44,10 +44,10 @@ public interface EntityGetterMixin extends CollisionEntityGetter {
|
||||
box = box.inflate(-CollisionUtil.COLLISION_EPSILON, -CollisionUtil.COLLISION_EPSILON, -CollisionUtil.COLLISION_EPSILON);
|
||||
|
||||
final List<Entity> entities;
|
||||
if (entity != null && ((CollisionEntity)entity).moonrise$isHardColliding()) {
|
||||
if (entity != null && ((ChunkSystemEntity)entity).moonrise$isHardColliding()) {
|
||||
entities = this.getEntities(entity, box, null);
|
||||
} else {
|
||||
entities = this.moonrise$getHardCollidingEntities(entity, box, null);
|
||||
entities = ((ChunkSystemEntityGetter)this).moonrise$getHardCollidingEntities(entity, box, null);
|
||||
}
|
||||
|
||||
final List<VoxelShape> ret = new ArrayList<>(Math.min(25, entities.size()));
|
||||
@@ -67,11 +67,6 @@ public interface EntityGetterMixin extends CollisionEntityGetter {
|
||||
return ret;
|
||||
}
|
||||
|
||||
@Override
|
||||
default List<Entity> moonrise$getHardCollidingEntities(final Entity entity, final AABB box, final Predicate<? super Entity> predicate) {
|
||||
return this.getEntities(entity, box, predicate);
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Use faster intersection checks
|
||||
* @author Spottedleaf
|
||||
|
||||
@@ -3,7 +3,6 @@ package ca.spottedleaf.moonrise.mixin.collisions;
|
||||
import ca.spottedleaf.moonrise.common.util.CoordinateUtils;
|
||||
import ca.spottedleaf.moonrise.patches.collisions.CollisionUtil;
|
||||
import ca.spottedleaf.moonrise.patches.collisions.block.CollisionBlockState;
|
||||
import ca.spottedleaf.moonrise.patches.collisions.entity.CollisionEntity;
|
||||
import ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape;
|
||||
import ca.spottedleaf.moonrise.patches.collisions.util.EmptyStreamForMoveCall;
|
||||
import net.minecraft.CrashReport;
|
||||
@@ -36,7 +35,7 @@ import java.util.List;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
@Mixin(Entity.class)
|
||||
public abstract class EntityMixin implements CollisionEntity {
|
||||
public abstract class EntityMixin {
|
||||
|
||||
@Shadow
|
||||
private Level level;
|
||||
@@ -79,6 +78,7 @@ public abstract class EntityMixin implements CollisionEntity {
|
||||
|
||||
@Shadow
|
||||
public boolean wasOnFire;
|
||||
|
||||
@Shadow
|
||||
public boolean isInPowderSnow;
|
||||
|
||||
@@ -91,14 +91,6 @@ public abstract class EntityMixin implements CollisionEntity {
|
||||
@Shadow
|
||||
protected abstract void onInsideBlock(BlockState blockState);
|
||||
|
||||
@Unique
|
||||
private final boolean isHardColliding = this.moonrise$isHardCollidingUncached();
|
||||
|
||||
@Override
|
||||
public final boolean moonrise$isHardColliding() {
|
||||
return this.isHardColliding;
|
||||
}
|
||||
|
||||
/**
|
||||
* @author Spottedleaf
|
||||
* @reason Optimise entire method
|
||||
|
||||
@@ -5,15 +5,12 @@ import ca.spottedleaf.moonrise.patches.chunk_getblock.GetBlockChunk;
|
||||
import ca.spottedleaf.moonrise.patches.collisions.CollisionUtil;
|
||||
import ca.spottedleaf.moonrise.patches.collisions.block.CollisionBlockState;
|
||||
import ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape;
|
||||
import ca.spottedleaf.moonrise.patches.collisions.slices.EntityLookup;
|
||||
import ca.spottedleaf.moonrise.patches.collisions.world.CollisionEntityGetter;
|
||||
import ca.spottedleaf.moonrise.patches.collisions.world.CollisionLevel;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.util.Mth;
|
||||
import net.minecraft.util.profiling.ProfilerFiller;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.entity.EntityType;
|
||||
import net.minecraft.world.level.ClipContext;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.LevelAccessor;
|
||||
@@ -24,7 +21,6 @@ import net.minecraft.world.level.chunk.LevelChunk;
|
||||
import net.minecraft.world.level.chunk.LevelChunkSection;
|
||||
import net.minecraft.world.level.chunk.PalettedContainer;
|
||||
import net.minecraft.world.level.chunk.status.ChunkStatus;
|
||||
import net.minecraft.world.level.entity.EntityTypeTest;
|
||||
import net.minecraft.world.level.material.FluidState;
|
||||
import net.minecraft.world.level.material.Fluids;
|
||||
import net.minecraft.world.phys.AABB;
|
||||
@@ -34,7 +30,6 @@ import net.minecraft.world.phys.shapes.BooleanOp;
|
||||
import net.minecraft.world.phys.shapes.Shapes;
|
||||
import net.minecraft.world.phys.shapes.VoxelShape;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Overwrite;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
@@ -43,10 +38,9 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
@Mixin(Level.class)
|
||||
public abstract class LevelMixin implements CollisionLevel, CollisionEntityGetter, LevelAccessor, AutoCloseable {
|
||||
public abstract class LevelMixin implements CollisionLevel, LevelAccessor, AutoCloseable {
|
||||
|
||||
@Shadow
|
||||
public abstract ProfilerFiller getProfiler();
|
||||
@@ -56,20 +50,12 @@ public abstract class LevelMixin implements CollisionLevel, CollisionEntityGette
|
||||
|
||||
|
||||
|
||||
@Unique
|
||||
private final EntityLookup collisionLookup = new EntityLookup((Level)(Object)this);
|
||||
|
||||
@Unique
|
||||
private int minSection;
|
||||
|
||||
@Unique
|
||||
private int maxSection;
|
||||
|
||||
@Override
|
||||
public final EntityLookup moonrise$getCollisionLookup() {
|
||||
return this.collisionLookup;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final int moonrise$getMinSection() {
|
||||
return this.minSection;
|
||||
@@ -95,115 +81,6 @@ public abstract class LevelMixin implements CollisionLevel, CollisionEntityGette
|
||||
this.maxSection = WorldUtil.getMaxSection(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Route to faster lookup
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Overwrite
|
||||
@Override
|
||||
public List<Entity> getEntities(final Entity entity, final AABB boundingBox, final Predicate<? super Entity> predicate) {
|
||||
this.getProfiler().incrementCounter("getEntities");
|
||||
final List<Entity> ret = new ArrayList<>();
|
||||
|
||||
this.collisionLookup.getEntities(entity, boundingBox, ret, predicate);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Route to faster lookup
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Overwrite
|
||||
public <T extends Entity> void getEntities(final EntityTypeTest<Entity, T> entityTypeTest,
|
||||
final AABB boundingBox, final Predicate<? super T> predicate,
|
||||
final List<? super T> into, final int maxCount) {
|
||||
this.getProfiler().incrementCounter("getEntities");
|
||||
if (entityTypeTest instanceof EntityType<T> byType) {
|
||||
if (maxCount != Integer.MAX_VALUE) {
|
||||
this.collisionLookup.getEntities(byType, boundingBox, into, predicate, maxCount);
|
||||
return;
|
||||
} else {
|
||||
this.collisionLookup.getEntities(byType, boundingBox, into, predicate);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (entityTypeTest == null) {
|
||||
if (maxCount != Integer.MAX_VALUE) {
|
||||
this.collisionLookup.getEntities((Entity)null, boundingBox, (List)into, (Predicate)predicate, maxCount);
|
||||
return;
|
||||
} else {
|
||||
this.collisionLookup.getEntities((Entity)null, boundingBox, (List)into, (Predicate)predicate);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
final Class<? extends Entity> base = entityTypeTest.getBaseClass();
|
||||
|
||||
final Predicate<? super T> modifiedPredicate;
|
||||
if (predicate == null) {
|
||||
modifiedPredicate = (final T obj) -> {
|
||||
return entityTypeTest.tryCast(obj) != null;
|
||||
};
|
||||
} else {
|
||||
modifiedPredicate = (final Entity obj) -> {
|
||||
final T casted = entityTypeTest.tryCast(obj);
|
||||
if (casted == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return predicate.test(casted);
|
||||
};
|
||||
}
|
||||
|
||||
if (base == null || base == Entity.class) {
|
||||
if (maxCount != Integer.MAX_VALUE) {
|
||||
this.collisionLookup.getEntities((Entity)null, boundingBox, (List)into, (Predicate)modifiedPredicate, maxCount);
|
||||
return;
|
||||
} else {
|
||||
this.collisionLookup.getEntities((Entity)null, boundingBox, (List)into, (Predicate)modifiedPredicate);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if (maxCount != Integer.MAX_VALUE) {
|
||||
this.collisionLookup.getEntities(base, null, boundingBox, (List)into, (Predicate)modifiedPredicate, maxCount);
|
||||
return;
|
||||
} else {
|
||||
this.collisionLookup.getEntities(base, null, boundingBox, (List)into, (Predicate)modifiedPredicate);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Route to faster lookup
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Override
|
||||
public final <T extends Entity> List<T> getEntitiesOfClass(final Class<T> entityClass, final AABB boundingBox, final Predicate<? super T> predicate) {
|
||||
this.getProfiler().incrementCounter("getEntities");
|
||||
final List<T> ret = new ArrayList<>();
|
||||
|
||||
this.collisionLookup.getEntities(entityClass, null, boundingBox, ret, predicate);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Route to faster lookup
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Override
|
||||
public final List<Entity> moonrise$getHardCollidingEntities(final Entity entity, final AABB box, final Predicate<? super Entity> predicate) {
|
||||
this.getProfiler().incrementCounter("getEntities");
|
||||
final List<Entity> ret = new ArrayList<>();
|
||||
|
||||
this.collisionLookup.getHardCollidingEntities(entity, box, ret, predicate);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Route to faster lookup.
|
||||
* See {@link EntityGetterMixin#isUnobstructed(Entity, VoxelShape)} for expected behavior
|
||||
|
||||
@@ -1,59 +0,0 @@
|
||||
package ca.spottedleaf.moonrise.mixin.collisions;
|
||||
|
||||
import ca.spottedleaf.moonrise.patches.collisions.world.CollisionLevel;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.level.entity.EntityAccess;
|
||||
import net.minecraft.world.level.entity.PersistentEntitySectionManager;
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
@Mixin(PersistentEntitySectionManager.Callback.class)
|
||||
public abstract class PersistentEntitySectionManagerCallbackMixin<T extends EntityAccess> {
|
||||
|
||||
@Shadow
|
||||
@Final
|
||||
private T entity;
|
||||
|
||||
@Shadow
|
||||
private long currentSectionKey;
|
||||
|
||||
/**
|
||||
* @reason Hook into our entity slices
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Inject(
|
||||
method = "onMove",
|
||||
at = @At(
|
||||
value = "INVOKE",
|
||||
target = "Lnet/minecraft/world/level/entity/EntitySection;remove(Lnet/minecraft/world/level/entity/EntityAccess;)Z"
|
||||
)
|
||||
)
|
||||
private void changeSections(final CallbackInfo ci) {
|
||||
final Entity entity = (Entity)this.entity;
|
||||
|
||||
final long currentChunk = this.currentSectionKey;
|
||||
|
||||
((CollisionLevel)entity.level()).moonrise$getCollisionLookup().moveEntity(entity, currentChunk);
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Hook into our entity slices
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Inject(
|
||||
method = "onRemove",
|
||||
at = @At(
|
||||
value = "INVOKE",
|
||||
target = "Lnet/minecraft/world/level/entity/EntitySection;remove(Lnet/minecraft/world/level/entity/EntityAccess;)Z"
|
||||
)
|
||||
)
|
||||
private void onRemoved(final CallbackInfo ci) {
|
||||
final Entity entity = (Entity)this.entity;
|
||||
|
||||
((CollisionLevel)entity.level()).moonrise$getCollisionLookup().removeEntity(entity);
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
package ca.spottedleaf.moonrise.mixin.collisions;
|
||||
|
||||
import ca.spottedleaf.moonrise.patches.collisions.world.CollisionLevel;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.level.entity.EntityAccess;
|
||||
import net.minecraft.world.level.entity.PersistentEntitySectionManager;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||
|
||||
@Mixin(PersistentEntitySectionManager.class)
|
||||
public abstract class PersistentEntitySectionManagerMixin<T extends EntityAccess> {
|
||||
|
||||
/**
|
||||
* @reason Hook into our entity slices
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Inject(
|
||||
method = "addEntity",
|
||||
at = @At(
|
||||
value = "INVOKE",
|
||||
target = "Lnet/minecraft/world/level/entity/EntitySection;add(Lnet/minecraft/world/level/entity/EntityAccess;)V"
|
||||
)
|
||||
)
|
||||
private void addEntity(final T entityAccess, final boolean onDisk, final CallbackInfoReturnable<Boolean> cir) {
|
||||
final Entity entity = (Entity)entityAccess;
|
||||
|
||||
((CollisionLevel)entity.level()).moonrise$getCollisionLookup().addEntity(entity);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
package ca.spottedleaf.moonrise.mixin.collisions;
|
||||
|
||||
import ca.spottedleaf.moonrise.patches.collisions.entity.CollisionEntity;
|
||||
import ca.spottedleaf.moonrise.patches.chunk_system.entity.ChunkSystemEntity;
|
||||
import net.minecraft.server.level.ServerEntity;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
@@ -32,7 +32,7 @@ public abstract class ServerEntityMixin {
|
||||
)
|
||||
)
|
||||
private void forceHardCollideTeleport(final CallbackInfo ci) {
|
||||
if (((CollisionEntity)this.entity).moonrise$isHardColliding()) {
|
||||
if (((ChunkSystemEntity)this.entity).moonrise$isHardColliding()) {
|
||||
this.teleportDelay = 9999;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,59 +0,0 @@
|
||||
package ca.spottedleaf.moonrise.mixin.collisions;
|
||||
|
||||
import ca.spottedleaf.moonrise.patches.collisions.world.CollisionLevel;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.level.entity.EntityAccess;
|
||||
import net.minecraft.world.level.entity.TransientEntitySectionManager;
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
@Mixin(TransientEntitySectionManager.Callback.class)
|
||||
public abstract class TransientEntitySectionManagerCallbackMixin<T extends EntityAccess> {
|
||||
|
||||
@Shadow
|
||||
@Final
|
||||
private T entity;
|
||||
|
||||
@Shadow
|
||||
private long currentSectionKey;
|
||||
|
||||
/**
|
||||
* @reason Hook into our entity slices
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Inject(
|
||||
method = "onMove",
|
||||
at = @At(
|
||||
value = "INVOKE",
|
||||
target = "Lnet/minecraft/world/level/entity/EntitySection;remove(Lnet/minecraft/world/level/entity/EntityAccess;)Z"
|
||||
)
|
||||
)
|
||||
private void changeSections(final CallbackInfo ci) {
|
||||
final Entity entity = (Entity)this.entity;
|
||||
|
||||
final long currentChunk = this.currentSectionKey;
|
||||
|
||||
((CollisionLevel)entity.level()).moonrise$getCollisionLookup().moveEntity(entity, currentChunk);
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Hook into our entity slices
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Inject(
|
||||
method = "onRemove",
|
||||
at = @At(
|
||||
value = "INVOKE",
|
||||
target = "Lnet/minecraft/world/level/entity/EntitySection;remove(Lnet/minecraft/world/level/entity/EntityAccess;)Z"
|
||||
)
|
||||
)
|
||||
private void onRemoved(final CallbackInfo ci) {
|
||||
final Entity entity = (Entity)this.entity;
|
||||
|
||||
((CollisionLevel)entity.level()).moonrise$getCollisionLookup().removeEntity(entity);
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
package ca.spottedleaf.moonrise.mixin.collisions;
|
||||
|
||||
import ca.spottedleaf.moonrise.patches.collisions.world.CollisionLevel;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.level.entity.EntityAccess;
|
||||
import net.minecraft.world.level.entity.TransientEntitySectionManager;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
@Mixin(TransientEntitySectionManager.class)
|
||||
public abstract class TransientEntitySectionManagerMixin<T extends EntityAccess> {
|
||||
|
||||
/**
|
||||
* @reason Hook into our entity slices
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Inject(
|
||||
method = "addEntity",
|
||||
at = @At(
|
||||
value = "INVOKE",
|
||||
target = "Lnet/minecraft/world/level/entity/EntitySection;add(Lnet/minecraft/world/level/entity/EntityAccess;)V"
|
||||
)
|
||||
)
|
||||
private void addEntity(final T entityAccess, final CallbackInfo ci) {
|
||||
final Entity entity = (Entity)entityAccess;
|
||||
|
||||
((CollisionLevel)entity.level()).moonrise$getCollisionLookup().addEntity(entity);
|
||||
}
|
||||
}
|
||||
@@ -225,6 +225,9 @@ public abstract class LevelLightEngineMixin implements LightEventListener, StarL
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
throw new IllegalStateException("Unknown light type: " + lightType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
package ca.spottedleaf.moonrise.mixin.starlight.lightengine;
|
||||
|
||||
import ca.spottedleaf.moonrise.common.util.CoordinateUtils;
|
||||
import ca.spottedleaf.moonrise.patches.starlight.light.StarLightEngine;
|
||||
import ca.spottedleaf.moonrise.patches.starlight.light.StarLightInterface;
|
||||
import ca.spottedleaf.moonrise.patches.starlight.light.StarLightLightingProvider;
|
||||
import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.SectionPos;
|
||||
import net.minecraft.server.level.ChunkMap;
|
||||
import net.minecraft.server.level.ChunkTaskPriorityQueueSorter;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.server.level.ThreadedLevelLightEngine;
|
||||
import net.minecraft.util.thread.ProcessorHandle;
|
||||
import net.minecraft.util.thread.ProcessorMailbox;
|
||||
import net.minecraft.world.level.ChunkPos;
|
||||
import net.minecraft.world.level.LightLayer;
|
||||
import net.minecraft.world.level.chunk.ChunkAccess;
|
||||
@@ -18,39 +17,37 @@ import net.minecraft.world.level.chunk.LightChunkGetter;
|
||||
import net.minecraft.world.level.chunk.status.ChunkStatus;
|
||||
import net.minecraft.world.level.lighting.LevelLightEngine;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Overwrite;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.util.function.IntSupplier;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
@Mixin(ThreadedLevelLightEngine.class)
|
||||
public abstract class ThreadedLevelLightEngineMixin extends LevelLightEngine implements StarLightLightingProvider {
|
||||
|
||||
@Final
|
||||
@Shadow
|
||||
private ChunkMap chunkMap;
|
||||
|
||||
@Final
|
||||
@Shadow
|
||||
private static Logger LOGGER;
|
||||
private ProcessorMailbox<Runnable> taskMailbox;
|
||||
|
||||
@Shadow
|
||||
public abstract void tryScheduleUpdate();
|
||||
private ProcessorHandle<ChunkTaskPriorityQueueSorter.Message<Runnable>> sorterMailbox;
|
||||
|
||||
public ThreadedLevelLightEngineMixin(final LightChunkGetter chunkProvider, final boolean hasBlockLight, final boolean hasSkyLight) {
|
||||
super(chunkProvider, hasBlockLight, hasSkyLight);
|
||||
}
|
||||
|
||||
@Unique
|
||||
private final Long2IntOpenHashMap chunksBeingWorkedOn = new Long2IntOpenHashMap();
|
||||
private final AtomicLong chunkWorkCounter = new AtomicLong();
|
||||
|
||||
@Unique
|
||||
private void queueTaskForSection(final int chunkX, final int chunkY, final int chunkZ,
|
||||
final Supplier<StarLightInterface.LightQueue.ChunkTasks> runnable) {
|
||||
final Supplier<StarLightInterface.LightQueue.ChunkTasks> supplier) {
|
||||
final ServerLevel world = (ServerLevel)this.starlight$getLightEngine().getWorld();
|
||||
|
||||
final ChunkAccess center = this.starlight$getLightEngine().getAnyChunkNow(chunkX, chunkZ);
|
||||
@@ -60,58 +57,78 @@ public abstract class ThreadedLevelLightEngineMixin extends LevelLightEngine imp
|
||||
return;
|
||||
}
|
||||
|
||||
if (center.getStatus() != ChunkStatus.FULL) {
|
||||
// do not keep chunk loaded, we are probably in a gen thread
|
||||
// if we proceed to add a ticket the chunk will be loaded, which is not what we want (avoid cascading gen)
|
||||
runnable.get();
|
||||
return;
|
||||
}
|
||||
final StarLightInterface.ServerLightQueue.ServerChunkTasks scheduledTask = (StarLightInterface.ServerLightQueue.ServerChunkTasks)supplier.get();
|
||||
|
||||
if (!world.getChunkSource().chunkMap.mainThreadExecutor.isSameThread()) {
|
||||
// ticket logic is not safe to run off-main, re-schedule
|
||||
world.getChunkSource().chunkMap.mainThreadExecutor.execute(() -> {
|
||||
this.queueTaskForSection(chunkX, chunkY, chunkZ, runnable);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
final long key = CoordinateUtils.getChunkKey(chunkX, chunkZ);
|
||||
|
||||
final StarLightInterface.LightQueue.ChunkTasks updateFuture = runnable.get();
|
||||
|
||||
if (updateFuture == null) {
|
||||
if (scheduledTask == null) {
|
||||
// not scheduled
|
||||
return;
|
||||
}
|
||||
|
||||
if (updateFuture.isTicketAdded) {
|
||||
if (!scheduledTask.markTicketAdded()) {
|
||||
// ticket already added
|
||||
return;
|
||||
}
|
||||
updateFuture.isTicketAdded = true;
|
||||
|
||||
final int references = this.chunksBeingWorkedOn.addTo(key, 1);
|
||||
if (references == 0) {
|
||||
final ChunkPos pos = new ChunkPos(chunkX, chunkZ);
|
||||
world.getChunkSource().addRegionTicket(StarLightInterface.CHUNK_WORK_TICKET, pos, 0, pos);
|
||||
}
|
||||
final Long ticketId = Long.valueOf(this.chunkWorkCounter.getAndIncrement());
|
||||
final ChunkPos pos = new ChunkPos(chunkX, chunkZ);
|
||||
world.getChunkSource().addRegionTicket(StarLightInterface.CHUNK_WORK_TICKET, pos, StarLightInterface.REGION_LIGHT_TICKET_LEVEL, ticketId);
|
||||
|
||||
updateFuture.onComplete.thenAcceptAsync((final Void ignore) -> {
|
||||
final int newReferences = this.chunksBeingWorkedOn.get(key);
|
||||
if (newReferences == 1) {
|
||||
this.chunksBeingWorkedOn.remove(key);
|
||||
final ChunkPos pos = new ChunkPos(chunkX, chunkZ);
|
||||
world.getChunkSource().removeRegionTicket(StarLightInterface.CHUNK_WORK_TICKET, pos, 0, pos);
|
||||
} else {
|
||||
this.chunksBeingWorkedOn.put(key, newReferences - 1);
|
||||
}
|
||||
}, world.getChunkSource().chunkMap.mainThreadExecutor).whenComplete((final Void ignore, final Throwable thr) -> {
|
||||
if (thr != null) {
|
||||
LOGGER.error("Failed to remove ticket level for post chunk task " + new ChunkPos(chunkX, chunkZ), thr);
|
||||
}
|
||||
scheduledTask.queueOrRunTask(() -> {
|
||||
world.getChunkSource().removeRegionTicket(StarLightInterface.CHUNK_WORK_TICKET, pos, StarLightInterface.REGION_LIGHT_TICKET_LEVEL, ticketId);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Destroy old chunk system hook
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Inject(
|
||||
method = "<init>",
|
||||
at = @At(
|
||||
value = "RETURN"
|
||||
)
|
||||
)
|
||||
private void initHook(final CallbackInfo ci) {
|
||||
this.taskMailbox = null;
|
||||
this.sorterMailbox = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Destroy old chunk system hook
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Overwrite
|
||||
public void addTask(final int x, final int z, final ThreadedLevelLightEngine.TaskType type,
|
||||
final Runnable task) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Destroy old chunk system hook
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Overwrite
|
||||
public void addTask(final int x, final int z, final IntSupplier ticketLevelSupplier,
|
||||
final ThreadedLevelLightEngine.TaskType type, final Runnable task) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Chunk system schedules light tasks immediately
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Overwrite
|
||||
public void tryScheduleUpdate() {}
|
||||
|
||||
/**
|
||||
* @reason Destroy old chunk system hook
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Overwrite
|
||||
public void runUpdate() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Redirect scheduling call away from the vanilla light engine, as well as enforce
|
||||
* that chunk neighbours are loaded before the processing can occur
|
||||
@@ -121,7 +138,7 @@ public abstract class ThreadedLevelLightEngineMixin extends LevelLightEngine imp
|
||||
public void checkBlock(final BlockPos pos) {
|
||||
final BlockPos posCopy = pos.immutable();
|
||||
this.queueTaskForSection(posCopy.getX() >> 4, posCopy.getY() >> 4, posCopy.getZ() >> 4, () -> {
|
||||
return this.starlight$getLightEngine().blockChange(posCopy);
|
||||
return ThreadedLevelLightEngineMixin.this.starlight$getLightEngine().blockChange(posCopy);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -141,7 +158,7 @@ public abstract class ThreadedLevelLightEngineMixin extends LevelLightEngine imp
|
||||
@Overwrite
|
||||
public void updateSectionStatus(final SectionPos pos, final boolean notReady) {
|
||||
this.queueTaskForSection(pos.getX(), pos.getY(), pos.getZ(), () -> {
|
||||
return this.starlight$getLightEngine().sectionChange(pos, notReady);
|
||||
return ThreadedLevelLightEngineMixin.this.starlight$getLightEngine().sectionChange(pos, notReady);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -192,36 +209,11 @@ public abstract class ThreadedLevelLightEngineMixin extends LevelLightEngine imp
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Route to new logic to either light or just load the data
|
||||
* @reason Chunk system patch replaces the vanilla scheduling entirely
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Overwrite
|
||||
public CompletableFuture<ChunkAccess> lightChunk(final ChunkAccess chunk, final boolean lit) {
|
||||
final ChunkPos chunkPos = chunk.getPos();
|
||||
|
||||
return CompletableFuture.supplyAsync(() -> {
|
||||
final Boolean[] emptySections = StarLightEngine.getEmptySectionsForChunk(chunk);
|
||||
if (!lit) {
|
||||
chunk.setLightCorrect(false);
|
||||
this.starlight$getLightEngine().lightChunk(chunk, emptySections);
|
||||
chunk.setLightCorrect(true);
|
||||
} else {
|
||||
this.starlight$getLightEngine().forceLoadInChunk(chunk, emptySections);
|
||||
// can't really force the chunk to be edged checked, as we need neighbouring chunks - but we don't have
|
||||
// them, so if it's not loaded then i guess we can't do edge checks. later loads of the chunk should
|
||||
// catch what we miss here.
|
||||
this.starlight$getLightEngine().checkChunkEdges(chunkPos.x, chunkPos.z);
|
||||
}
|
||||
|
||||
this.chunkMap.releaseLightTicket(chunkPos);
|
||||
return chunk;
|
||||
}, (runnable) -> {
|
||||
this.starlight$getLightEngine().scheduleChunkLight(chunkPos, runnable);
|
||||
this.tryScheduleUpdate();
|
||||
}).whenComplete((final ChunkAccess c, final Throwable throwable) -> {
|
||||
if (throwable != null) {
|
||||
LOGGER.error("Failed to light chunk " + chunkPos, throwable);
|
||||
}
|
||||
});
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
@Mixin(value = ClientPacketListener.class, priority = 1001)
|
||||
@Mixin(ClientPacketListener.class)
|
||||
public abstract class ClientPacketListenerMixin implements ClientGamePacketListener {
|
||||
|
||||
/*
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
package ca.spottedleaf.moonrise.mixin.starlight.world;
|
||||
|
||||
import ca.spottedleaf.moonrise.patches.starlight.world.StarlightWorld;
|
||||
import net.minecraft.client.multiplayer.ClientChunkCache;
|
||||
import net.minecraft.client.multiplayer.ClientLevel;
|
||||
import net.minecraft.core.Holder;
|
||||
import net.minecraft.core.RegistryAccess;
|
||||
import net.minecraft.resources.ResourceKey;
|
||||
import net.minecraft.util.profiling.ProfilerFiller;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.chunk.ChunkAccess;
|
||||
import net.minecraft.world.level.chunk.LevelChunk;
|
||||
import net.minecraft.world.level.dimension.DimensionType;
|
||||
import net.minecraft.world.level.storage.WritableLevelData;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
@Mixin(ClientLevel.class)
|
||||
public abstract class ClientLevelMixin extends Level implements StarlightWorld {
|
||||
|
||||
protected ClientLevelMixin(WritableLevelData writableLevelData, ResourceKey<Level> resourceKey, RegistryAccess registryAccess, Holder<DimensionType> holder, Supplier<ProfilerFiller> supplier, boolean bl, boolean bl2, long l, int i) {
|
||||
super(writableLevelData, resourceKey, registryAccess, holder, supplier, bl, bl2, l, i);
|
||||
}
|
||||
|
||||
@Shadow
|
||||
public abstract ClientChunkCache getChunkSource();
|
||||
|
||||
@Override
|
||||
public final LevelChunk starlight$getChunkAtImmediately(final int chunkX, final int chunkZ) {
|
||||
return this.getChunkSource().getChunk(chunkX, chunkZ, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final ChunkAccess starlight$getAnyChunkImmediately(final int chunkX, final int chunkZ) {
|
||||
return this.getChunkSource().getChunk(chunkX, chunkZ, false);
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
package ca.spottedleaf.moonrise.mixin.starlight.world;
|
||||
|
||||
import ca.spottedleaf.moonrise.patches.starlight.world.StarlightWorld;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.LevelAccessor;
|
||||
import net.minecraft.world.level.chunk.ChunkAccess;
|
||||
import net.minecraft.world.level.chunk.status.ChunkStatus;
|
||||
import net.minecraft.world.level.chunk.LevelChunk;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
|
||||
@Mixin(Level.class)
|
||||
public abstract class LevelMixin implements LevelAccessor, AutoCloseable, StarlightWorld {
|
||||
|
||||
@Override
|
||||
public LevelChunk starlight$getChunkAtImmediately(final int chunkX, final int chunkZ) {
|
||||
return this.getChunkSource().getChunk(chunkX, chunkZ, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChunkAccess starlight$getAnyChunkImmediately(final int chunkX, final int chunkZ) {
|
||||
return this.getChunkSource().getChunk(chunkX, chunkX, ChunkStatus.EMPTY, false);
|
||||
}
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
package ca.spottedleaf.moonrise.mixin.starlight.world;
|
||||
|
||||
import ca.spottedleaf.moonrise.common.util.CoordinateUtils;
|
||||
import ca.spottedleaf.moonrise.patches.starlight.world.StarlightWorld;
|
||||
import net.minecraft.core.Holder;
|
||||
import net.minecraft.core.RegistryAccess;
|
||||
import net.minecraft.resources.ResourceKey;
|
||||
import net.minecraft.server.level.ChunkHolder;
|
||||
import net.minecraft.server.level.ChunkMap;
|
||||
import net.minecraft.server.level.ChunkResult;
|
||||
import net.minecraft.server.level.ServerChunkCache;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.util.profiling.ProfilerFiller;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.WorldGenLevel;
|
||||
import net.minecraft.world.level.chunk.ChunkAccess;
|
||||
import net.minecraft.world.level.chunk.status.ChunkStatus;
|
||||
import net.minecraft.world.level.chunk.LevelChunk;
|
||||
import net.minecraft.world.level.dimension.DimensionType;
|
||||
import net.minecraft.world.level.storage.WritableLevelData;
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
@Mixin(ServerLevel.class)
|
||||
public abstract class ServerWorldMixin extends Level implements WorldGenLevel, StarlightWorld {
|
||||
|
||||
@Shadow
|
||||
@Final
|
||||
private ServerChunkCache chunkSource;
|
||||
|
||||
protected ServerWorldMixin(WritableLevelData writableLevelData, ResourceKey<Level> resourceKey, RegistryAccess registryAccess, Holder<DimensionType> holder, Supplier<ProfilerFiller> supplier, boolean bl, boolean bl2, long l, int i) {
|
||||
super(writableLevelData, resourceKey, registryAccess, holder, supplier, bl, bl2, l, i);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final LevelChunk starlight$getChunkAtImmediately(final int chunkX, final int chunkZ) {
|
||||
final ChunkMap storage = this.chunkSource.chunkMap;
|
||||
final ChunkHolder holder = storage.getVisibleChunkIfPresent(CoordinateUtils.getChunkKey(chunkX, chunkZ));
|
||||
|
||||
if (holder == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final ChunkResult<ChunkAccess> result = holder.getFutureIfPresentUnchecked(ChunkStatus.FULL).getNow(null);
|
||||
|
||||
return result == null ? null : (LevelChunk)result.orElse(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final ChunkAccess starlight$getAnyChunkImmediately(final int chunkX, final int chunkZ) {
|
||||
final ChunkMap storage = this.chunkSource.chunkMap;
|
||||
final ChunkHolder holder = storage.getVisibleChunkIfPresent(CoordinateUtils.getChunkKey(chunkX, chunkZ));
|
||||
|
||||
return holder == null ? null : holder.getLastAvailable();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
package ca.spottedleaf.moonrise.patches.chunk_system;
|
||||
|
||||
import ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor;
|
||||
import ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel;
|
||||
import ca.spottedleaf.moonrise.patches.chunk_system.level.chunk.ChunkSystemLevelChunk;
|
||||
import ca.spottedleaf.moonrise.patches.chunk_system.player.RegionizedPlayerChunkLoader;
|
||||
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();
|
||||
|
||||
public static void scheduleChunkTask(final ServerLevel level, final int chunkX, final int chunkZ, final Runnable run) {
|
||||
scheduleChunkTask(level, chunkX, chunkZ, run, PrioritisedExecutor.Priority.NORMAL);
|
||||
}
|
||||
|
||||
public static void scheduleChunkTask(final ServerLevel level, final int chunkX, final int chunkZ, final Runnable run, final PrioritisedExecutor.Priority priority) {
|
||||
((ChunkSystemServerLevel)level).moonrise$getChunkTaskScheduler().scheduleChunkTask(chunkX, chunkZ, run, priority);
|
||||
}
|
||||
|
||||
public static void scheduleChunkLoad(final ServerLevel level, final int chunkX, final int chunkZ, final boolean gen,
|
||||
final ChunkStatus toStatus, final boolean addTicket, final PrioritisedExecutor.Priority priority,
|
||||
final Consumer<ChunkAccess> onComplete) {
|
||||
((ChunkSystemServerLevel)level).moonrise$getChunkTaskScheduler().scheduleChunkLoad(chunkX, chunkZ, gen, toStatus, addTicket, priority, onComplete);
|
||||
}
|
||||
|
||||
// Paper - rewrite chunk system
|
||||
public static void scheduleChunkLoad(final ServerLevel level, final int chunkX, final int chunkZ, final ChunkStatus toStatus,
|
||||
final boolean addTicket, final PrioritisedExecutor.Priority priority, final Consumer<ChunkAccess> onComplete) {
|
||||
((ChunkSystemServerLevel)level).moonrise$getChunkTaskScheduler().scheduleChunkLoad(chunkX, chunkZ, toStatus, addTicket, priority, onComplete);
|
||||
}
|
||||
|
||||
public static void scheduleTickingState(final ServerLevel level, final int chunkX, final int chunkZ,
|
||||
final FullChunkStatus toStatus, final boolean addTicket,
|
||||
final PrioritisedExecutor.Priority priority, final Consumer<LevelChunk> onComplete) {
|
||||
((ChunkSystemServerLevel)level).moonrise$getChunkTaskScheduler().scheduleTickingState(chunkX, chunkZ, toStatus, addTicket, priority, onComplete);
|
||||
}
|
||||
|
||||
public static List<ChunkHolder> getVisibleChunkHolders(final ServerLevel level) {
|
||||
return ((ChunkSystemServerLevel)level).moonrise$getChunkTaskScheduler().chunkHolderManager.getOldChunkHolders();
|
||||
}
|
||||
|
||||
public static List<ChunkHolder> getUpdatingChunkHolders(final ServerLevel level) {
|
||||
return ((ChunkSystemServerLevel)level).moonrise$getChunkTaskScheduler().chunkHolderManager.getOldChunkHolders();
|
||||
}
|
||||
|
||||
public static int getVisibleChunkHolderCount(final ServerLevel level) {
|
||||
return ((ChunkSystemServerLevel)level).moonrise$getChunkTaskScheduler().chunkHolderManager.size();
|
||||
}
|
||||
|
||||
public static int getUpdatingChunkHolderCount(final ServerLevel level) {
|
||||
return ((ChunkSystemServerLevel)level).moonrise$getChunkTaskScheduler().chunkHolderManager.size();
|
||||
}
|
||||
|
||||
public static boolean hasAnyChunkHolders(final ServerLevel level) {
|
||||
return getUpdatingChunkHolderCount(level) != 0;
|
||||
}
|
||||
|
||||
public static void onEntityPreAdd(final ServerLevel level, final Entity entity) {
|
||||
|
||||
}
|
||||
|
||||
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) {
|
||||
if (!((ChunkSystemLevelChunk)chunk).moonrise$isPostProcessingDone()) {
|
||||
chunk.postProcessGeneration();
|
||||
}
|
||||
((ServerLevel)chunk.getLevel()).startTickingChunk(chunk);
|
||||
((ServerLevel)chunk.getLevel()).getChunkSource().chunkMap.tickingGenerated.incrementAndGet();
|
||||
}
|
||||
|
||||
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 null;
|
||||
}
|
||||
|
||||
public static int getSendViewDistance(final ServerPlayer player) {
|
||||
return RegionizedPlayerChunkLoader.getAPISendViewDistance(player);
|
||||
}
|
||||
|
||||
public static int getLoadViewDistance(final ServerPlayer player) {
|
||||
return RegionizedPlayerChunkLoader.getLoadViewDistance(player);
|
||||
}
|
||||
|
||||
public static int getTickViewDistance(final ServerPlayer player) {
|
||||
return RegionizedPlayerChunkLoader.getAPITickViewDistance(player);
|
||||
}
|
||||
|
||||
public static void addPlayerToDistanceMaps(final ServerLevel world, final ServerPlayer player) {
|
||||
((ChunkSystemServerLevel)world).moonrise$getPlayerChunkLoader().addPlayer(player);
|
||||
}
|
||||
|
||||
public static void removePlayerFromDistanceMaps(final ServerLevel world, final ServerPlayer player) {
|
||||
((ChunkSystemServerLevel)world).moonrise$getPlayerChunkLoader().removePlayer(player);
|
||||
}
|
||||
|
||||
public static void updateMaps(final ServerLevel world, final ServerPlayer player) {
|
||||
((ChunkSystemServerLevel)world).moonrise$getPlayerChunkLoader().updatePlayer(player);
|
||||
}
|
||||
|
||||
private ChunkSystem() {}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package ca.spottedleaf.moonrise.patches.chunk_system;
|
||||
|
||||
import net.minecraft.SharedConstants;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.nbt.Tag;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.util.datafix.DataFixTypes;
|
||||
|
||||
public final class ChunkSystemConverters {
|
||||
|
||||
// See SectionStorage#getVersion
|
||||
private static final int DEFAULT_POI_DATA_VERSION = 1945;
|
||||
|
||||
private static final int DEFAULT_ENTITY_CHUNK_DATA_VERSION = -1;
|
||||
|
||||
private static int getCurrentVersion() {
|
||||
return SharedConstants.getCurrentVersion().getDataVersion().getVersion();
|
||||
}
|
||||
|
||||
private static int getDataVersion(final CompoundTag data, final int dfl) {
|
||||
return !data.contains(SharedConstants.DATA_VERSION_TAG, Tag.TAG_ANY_NUMERIC)
|
||||
? dfl : data.getInt(SharedConstants.DATA_VERSION_TAG);
|
||||
}
|
||||
|
||||
public static CompoundTag convertPoiCompoundTag(final CompoundTag data, final ServerLevel world) {
|
||||
final int dataVersion = getDataVersion(data, DEFAULT_POI_DATA_VERSION);
|
||||
|
||||
return DataFixTypes.POI_CHUNK.update(world.getServer().getFixerUpper(), data, dataVersion, getCurrentVersion());
|
||||
}
|
||||
|
||||
public static CompoundTag convertEntityChunkCompoundTag(final CompoundTag data, final ServerLevel world) {
|
||||
final int dataVersion = getDataVersion(data, DEFAULT_ENTITY_CHUNK_DATA_VERSION);
|
||||
|
||||
return DataFixTypes.ENTITY_CHUNK.update(world.getServer().getFixerUpper(), data, dataVersion, getCurrentVersion());
|
||||
}
|
||||
|
||||
private ChunkSystemConverters() {}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package ca.spottedleaf.moonrise.patches.chunk_system;
|
||||
|
||||
import ca.spottedleaf.moonrise.patches.chunk_system.async_save.AsyncChunkSaveData;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.world.level.chunk.ChunkAccess;
|
||||
|
||||
public final class ChunkSystemFeatures {
|
||||
|
||||
public static boolean supportsAsyncChunkSave() {
|
||||
// uncertain how to properly pass AsyncSaveData to ChunkSerializer#write
|
||||
// additionally, there may be mods hooking into the write() call which may not be thread-safe to call
|
||||
return false;
|
||||
}
|
||||
|
||||
public static AsyncChunkSaveData getAsyncSaveData(final ServerLevel world, final ChunkAccess chunk) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
public static CompoundTag saveChunkAsync(final ServerLevel world, final ChunkAccess chunk, final AsyncChunkSaveData asyncSaveData) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
public static boolean forceNoSave(final ChunkAccess chunk) {
|
||||
// support for CB chunk mustNotSave
|
||||
return false;
|
||||
}
|
||||
|
||||
public static boolean supportsAsyncChunkDeserialization() {
|
||||
// as it stands, the current problem with supporting this in Moonrise is that we are unsure that any mods
|
||||
// hooking into ChunkSerializer#read() are thread-safe to call
|
||||
return false;
|
||||
}
|
||||
|
||||
private ChunkSystemFeatures() {}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package ca.spottedleaf.moonrise.patches.chunk_system.async_save;
|
||||
|
||||
import net.minecraft.nbt.ListTag;
|
||||
import net.minecraft.nbt.Tag;
|
||||
|
||||
public record AsyncChunkSaveData(
|
||||
Tag blockTickList, // non-null if we had to go to the server's tick list
|
||||
Tag fluidTickList, // non-null if we had to go to the server's tick list
|
||||
ListTag blockEntities,
|
||||
long worldTime
|
||||
) {}
|
||||
@@ -0,0 +1,39 @@
|
||||
package ca.spottedleaf.moonrise.patches.chunk_system.entity;
|
||||
|
||||
import net.minecraft.server.level.FullChunkStatus;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.entity.monster.Shulker;
|
||||
import net.minecraft.world.entity.vehicle.AbstractMinecart;
|
||||
import net.minecraft.world.entity.vehicle.Boat;
|
||||
|
||||
public interface ChunkSystemEntity {
|
||||
|
||||
public boolean moonrise$isHardColliding();
|
||||
|
||||
// for mods to override
|
||||
public default boolean moonrise$isHardCollidingUncached() {
|
||||
return this instanceof Boat || this instanceof AbstractMinecart || this instanceof Shulker || ((Entity)this).canBeCollidedWith();
|
||||
}
|
||||
|
||||
public FullChunkStatus moonrise$getChunkStatus();
|
||||
|
||||
public void moonrise$setChunkStatus(final FullChunkStatus status);
|
||||
|
||||
public int moonrise$getSectionX();
|
||||
|
||||
public void moonrise$setSectionX(final int x);
|
||||
|
||||
public int moonrise$getSectionY();
|
||||
|
||||
public void moonrise$setSectionY(final int y);
|
||||
|
||||
public int moonrise$getSectionZ();
|
||||
|
||||
public void moonrise$setSectionZ(final int z);
|
||||
|
||||
public boolean moonrise$isUpdatingSectionStatus();
|
||||
|
||||
public void moonrise$setUpdatingSectionStatus(final boolean to);
|
||||
|
||||
public boolean moonrise$hasAnyPlayerPassengers();
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package ca.spottedleaf.moonrise.patches.chunk_system.io;
|
||||
|
||||
import net.minecraft.world.level.chunk.storage.RegionFile;
|
||||
import java.io.IOException;
|
||||
|
||||
public interface ChunkSystemRegionFileStorage {
|
||||
|
||||
public boolean moonrise$doesRegionFileNotExistNoIO(final int chunkX, final int chunkZ);
|
||||
|
||||
public RegionFile moonrise$getRegionFileIfLoaded(final int chunkX, final int chunkZ);
|
||||
|
||||
public RegionFile moonrise$getRegionFileIfExists(final int chunkX, final int chunkZ) throws IOException;
|
||||
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,56 @@
|
||||
package ca.spottedleaf.moonrise.patches.chunk_system.io.datacontroller;
|
||||
|
||||
import ca.spottedleaf.moonrise.patches.chunk_system.io.RegionFileIOThread;
|
||||
import ca.spottedleaf.moonrise.patches.chunk_system.storage.ChunkSystemChunkStorage;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.world.level.ChunkPos;
|
||||
import net.minecraft.world.level.chunk.storage.RegionFileStorage;
|
||||
import java.io.IOException;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.CompletionException;
|
||||
|
||||
public final class ChunkDataController extends RegionFileIOThread.ChunkDataController {
|
||||
|
||||
private final ServerLevel world;
|
||||
|
||||
public ChunkDataController(final ServerLevel world) {
|
||||
super(RegionFileIOThread.RegionFileType.CHUNK_DATA);
|
||||
this.world = world;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RegionFileStorage getCache() {
|
||||
return ((ChunkSystemChunkStorage)this.world.getChunkSource().chunkMap).moonrise$getRegionStorage();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeData(final int chunkX, final int chunkZ, final CompoundTag compound) throws IOException {
|
||||
final CompletableFuture<Void> future = this.world.getChunkSource().chunkMap.write(new ChunkPos(chunkX, chunkZ), compound);
|
||||
|
||||
try {
|
||||
if (future != null) {
|
||||
// rets non-null when sync writing (i.e. future should be completed here)
|
||||
future.join();
|
||||
}
|
||||
} catch (final CompletionException ex) {
|
||||
if (ex.getCause() instanceof IOException ioException) {
|
||||
throw ioException;
|
||||
}
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompoundTag readData(final int chunkX, final int chunkZ) throws IOException {
|
||||
try {
|
||||
return this.world.getChunkSource().chunkMap.read(new ChunkPos(chunkX, chunkZ)).join().orElse(null);
|
||||
} catch (final CompletionException ex) {
|
||||
if (ex.getCause() instanceof IOException ioException) {
|
||||
throw ioException;
|
||||
}
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package ca.spottedleaf.moonrise.patches.chunk_system.io.datacontroller;
|
||||
|
||||
import ca.spottedleaf.moonrise.patches.chunk_system.io.RegionFileIOThread;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.world.level.ChunkPos;
|
||||
import net.minecraft.world.level.chunk.storage.EntityStorage;
|
||||
import net.minecraft.world.level.chunk.storage.RegionFileStorage;
|
||||
import net.minecraft.world.level.chunk.storage.RegionStorageInfo;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
|
||||
public final class EntityDataController extends RegionFileIOThread.ChunkDataController {
|
||||
|
||||
private final EntityRegionFileStorage storage;
|
||||
|
||||
public EntityDataController(final EntityRegionFileStorage storage) {
|
||||
super(RegionFileIOThread.RegionFileType.ENTITY_DATA);
|
||||
this.storage = storage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RegionFileStorage getCache() {
|
||||
return this.storage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeData(final int chunkX, final int chunkZ, final CompoundTag compound) throws IOException {
|
||||
this.storage.write(new ChunkPos(chunkX, chunkZ), compound);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompoundTag readData(final int chunkX, final int chunkZ) throws IOException {
|
||||
return this.storage.read(new ChunkPos(chunkX, chunkZ));
|
||||
}
|
||||
|
||||
public static final class EntityRegionFileStorage extends RegionFileStorage {
|
||||
|
||||
public EntityRegionFileStorage(final RegionStorageInfo regionStorageInfo, final Path directory,
|
||||
final boolean dsync) {
|
||||
super(regionStorageInfo, directory, dsync);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(final ChunkPos pos, final CompoundTag nbt) throws IOException {
|
||||
final ChunkPos nbtPos = nbt == null ? null : EntityStorage.readChunkPos(nbt);
|
||||
if (nbtPos != null && !pos.equals(nbtPos)) {
|
||||
throw new IllegalArgumentException(
|
||||
"Entity chunk coordinate and serialized data do not have matching coordinates, trying to serialize coordinate " + pos.toString()
|
||||
+ " but compound says coordinate is " + nbtPos + " for world: " + this
|
||||
);
|
||||
}
|
||||
super.write(pos, nbt);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package ca.spottedleaf.moonrise.patches.chunk_system.io.datacontroller;
|
||||
|
||||
import ca.spottedleaf.moonrise.patches.chunk_system.io.RegionFileIOThread;
|
||||
import ca.spottedleaf.moonrise.patches.chunk_system.level.storage.ChunkSystemSectionStorage;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.world.level.chunk.storage.RegionFileStorage;
|
||||
import java.io.IOException;
|
||||
|
||||
public final class PoiDataController extends RegionFileIOThread.ChunkDataController {
|
||||
|
||||
private final ServerLevel world;
|
||||
|
||||
public PoiDataController(final ServerLevel world) {
|
||||
super(RegionFileIOThread.RegionFileType.POI_DATA);
|
||||
this.world = world;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RegionFileStorage getCache() {
|
||||
return ((ChunkSystemSectionStorage)this.world.getPoiManager()).moonrise$getRegionStorage();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeData(final int chunkX, final int chunkZ, final CompoundTag compound) throws IOException {
|
||||
((ChunkSystemSectionStorage)this.world.getPoiManager()).moonrise$write(chunkX, chunkZ, compound);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompoundTag readData(final int chunkX, final int chunkZ) throws IOException {
|
||||
return ((ChunkSystemSectionStorage)this.world.getPoiManager()).moonrise$read(chunkX, chunkZ);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package ca.spottedleaf.moonrise.patches.chunk_system.level;
|
||||
|
||||
import ca.spottedleaf.moonrise.patches.chunk_system.level.entity.EntityLookup;
|
||||
import net.minecraft.world.level.chunk.ChunkAccess;
|
||||
import net.minecraft.world.level.chunk.LevelChunk;
|
||||
import net.minecraft.world.level.chunk.status.ChunkStatus;
|
||||
|
||||
public interface ChunkSystemLevel {
|
||||
|
||||
public EntityLookup moonrise$getEntityLookup();
|
||||
|
||||
public void moonrise$setEntityLookup(final EntityLookup entityLookup);
|
||||
|
||||
public LevelChunk moonrise$getFullChunkIfLoaded(final int chunkX, final int chunkZ);
|
||||
|
||||
public ChunkAccess moonrise$getAnyChunkIfLoaded(final int chunkX, final int chunkZ);
|
||||
|
||||
public ChunkAccess moonrise$getSpecificChunkIfLoaded(final int chunkX, final int chunkZ, final ChunkStatus leastStatus);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package ca.spottedleaf.moonrise.patches.chunk_system.level;
|
||||
|
||||
import net.minecraft.world.level.chunk.ChunkAccess;
|
||||
import net.minecraft.world.level.chunk.status.ChunkStatus;
|
||||
|
||||
public interface ChunkSystemLevelReader {
|
||||
|
||||
public ChunkAccess moonrise$syncLoadNonFull(final int chunkX, final int chunkZ, final ChunkStatus status);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package ca.spottedleaf.moonrise.patches.chunk_system.level;
|
||||
|
||||
import ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor;
|
||||
import ca.spottedleaf.moonrise.patches.chunk_system.io.RegionFileIOThread;
|
||||
import ca.spottedleaf.moonrise.patches.chunk_system.player.RegionizedPlayerChunkLoader;
|
||||
import ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkTaskScheduler;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.world.level.chunk.ChunkAccess;
|
||||
import net.minecraft.world.level.chunk.status.ChunkStatus;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public interface ChunkSystemServerLevel extends ChunkSystemLevel {
|
||||
|
||||
public ChunkTaskScheduler moonrise$getChunkTaskScheduler();
|
||||
|
||||
public RegionFileIOThread.ChunkDataController moonrise$getChunkDataController();
|
||||
|
||||
public RegionFileIOThread.ChunkDataController moonrise$getPoiChunkDataController();
|
||||
|
||||
public RegionFileIOThread.ChunkDataController moonrise$getEntityChunkDataController();
|
||||
|
||||
public int moonrise$getRegionChunkShift();
|
||||
|
||||
public boolean moonrise$isMarkedClosing();
|
||||
|
||||
public void moonrise$setMarkedClosing(final boolean value);
|
||||
|
||||
public RegionizedPlayerChunkLoader moonrise$getPlayerChunkLoader();
|
||||
|
||||
public void moonrise$loadChunksAsync(final BlockPos pos, final int radiusBlocks,
|
||||
final PrioritisedExecutor.Priority priority,
|
||||
final Consumer<List<ChunkAccess>> onLoad);
|
||||
|
||||
public void moonrise$loadChunksAsync(final BlockPos pos, final int radiusBlocks,
|
||||
final ChunkStatus chunkStatus, final PrioritisedExecutor.Priority priority,
|
||||
final Consumer<List<ChunkAccess>> onLoad);
|
||||
|
||||
public void moonrise$loadChunksAsync(final int minChunkX, final int maxChunkX, final int minChunkZ, final int maxChunkZ,
|
||||
final PrioritisedExecutor.Priority priority,
|
||||
final Consumer<List<ChunkAccess>> onLoad);
|
||||
|
||||
public void moonrise$loadChunksAsync(final int minChunkX, final int maxChunkX, final int minChunkZ, final int maxChunkZ,
|
||||
final ChunkStatus chunkStatus, final PrioritisedExecutor.Priority priority,
|
||||
final Consumer<List<ChunkAccess>> onLoad);
|
||||
|
||||
public RegionizedPlayerChunkLoader.ViewDistanceHolder moonrise$getViewDistanceHolder();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package ca.spottedleaf.moonrise.patches.chunk_system.level.chunk;
|
||||
|
||||
import ca.spottedleaf.moonrise.patches.chunk_system.scheduling.NewChunkHolder;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.world.level.chunk.LevelChunk;
|
||||
import java.util.List;
|
||||
|
||||
public interface ChunkSystemChunkHolder {
|
||||
|
||||
public NewChunkHolder moonrise$getRealChunkHolder();
|
||||
|
||||
public void moonrise$setRealChunkHolder(final NewChunkHolder newChunkHolder);
|
||||
|
||||
public void moonrise$addReceivedChunk(final ServerPlayer player);
|
||||
|
||||
public void moonrise$removeReceivedChunk(final ServerPlayer player);
|
||||
|
||||
public boolean moonrise$hasChunkBeenSent();
|
||||
|
||||
public boolean moonrise$hasChunkBeenSent(final ServerPlayer to);
|
||||
|
||||
public List<ServerPlayer> moonrise$getPlayers(final boolean onlyOnWatchDistanceEdge);
|
||||
|
||||
public LevelChunk moonrise$getFullChunk();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package ca.spottedleaf.moonrise.patches.chunk_system.level.chunk;
|
||||
|
||||
import net.minecraft.world.level.chunk.status.ChunkStatus;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
public interface ChunkSystemChunkStatus {
|
||||
|
||||
public boolean moonrise$isParallelCapable();
|
||||
|
||||
public void moonrise$setParallelCapable(final boolean value);
|
||||
|
||||
public int moonrise$getWriteRadius();
|
||||
|
||||
public void moonrise$setWriteRadius(final int value);
|
||||
|
||||
public int moonrise$getLoadRadius();
|
||||
|
||||
public void moonrise$setLoadRadius(final int value);
|
||||
|
||||
public ChunkStatus moonrise$getNextStatus();
|
||||
|
||||
public boolean moonrise$isEmptyLoadStatus();
|
||||
|
||||
public void moonrise$setEmptyLoadStatus(final boolean value);
|
||||
|
||||
public boolean moonrise$isEmptyGenStatus();
|
||||
|
||||
public AtomicBoolean moonrise$getWarnedAboutNoImmediateComplete();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package ca.spottedleaf.moonrise.patches.chunk_system.level.chunk;
|
||||
|
||||
import net.minecraft.server.level.ChunkMap;
|
||||
|
||||
public interface ChunkSystemDistanceManager {
|
||||
|
||||
public ChunkMap moonrise$getChunkMap();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package ca.spottedleaf.moonrise.patches.chunk_system.level.chunk;
|
||||
|
||||
public interface ChunkSystemLevelChunk {
|
||||
|
||||
public boolean moonrise$isPostProcessingDone();
|
||||
|
||||
}
|
||||
@@ -1,18 +1,27 @@
|
||||
package ca.spottedleaf.moonrise.patches.collisions.slices;
|
||||
package ca.spottedleaf.moonrise.patches.chunk_system.level.entity;
|
||||
|
||||
import ca.spottedleaf.moonrise.common.list.ReferenceList;
|
||||
import ca.spottedleaf.moonrise.common.util.WorldUtil;
|
||||
import ca.spottedleaf.moonrise.patches.collisions.entity.CollisionEntity;
|
||||
import ca.spottedleaf.moonrise.common.list.EntityList;
|
||||
import ca.spottedleaf.moonrise.patches.chunk_system.entity.ChunkSystemEntity;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import it.unimi.dsi.fastutil.objects.Reference2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.nbt.ListTag;
|
||||
import net.minecraft.nbt.NbtUtils;
|
||||
import net.minecraft.nbt.Tag;
|
||||
import net.minecraft.server.level.FullChunkStatus;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.util.Mth;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.entity.EntityType;
|
||||
import net.minecraft.world.entity.boss.EnderDragonPart;
|
||||
import net.minecraft.world.entity.boss.enderdragon.EnderDragon;
|
||||
import net.minecraft.world.level.ChunkPos;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.chunk.storage.EntityStorage;
|
||||
import net.minecraft.world.level.entity.Visibility;
|
||||
import net.minecraft.world.phys.AABB;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
@@ -20,46 +29,205 @@ import java.util.function.Predicate;
|
||||
|
||||
public final class ChunkEntitySlices {
|
||||
|
||||
protected final int minSection;
|
||||
protected final int maxSection;
|
||||
public final int minSection;
|
||||
public final int maxSection;
|
||||
public final int chunkX;
|
||||
public final int chunkZ;
|
||||
protected final Level world;
|
||||
public final Level world;
|
||||
|
||||
protected final EntityCollectionBySection allEntities;
|
||||
protected final EntityCollectionBySection hardCollidingEntities;
|
||||
protected final Reference2ObjectOpenHashMap<EntityType<?>, EntityCollectionBySection> entitiesByType;
|
||||
protected final Reference2ObjectOpenHashMap<Class<? extends Entity>, EntityCollectionBySection> entitiesByClass;
|
||||
protected final ReferenceList<Entity> entities = new ReferenceList<>();
|
||||
private final EntityCollectionBySection allEntities;
|
||||
private final EntityCollectionBySection hardCollidingEntities;
|
||||
private final Reference2ObjectOpenHashMap<Class<? extends Entity>, EntityCollectionBySection> entitiesByClass;
|
||||
private final Reference2ObjectOpenHashMap<EntityType<?>, EntityCollectionBySection> entitiesByType;
|
||||
private final EntityList entities = new EntityList();
|
||||
|
||||
public Visibility sectionVisibility = Visibility.TRACKED; // TODO
|
||||
public FullChunkStatus status;
|
||||
|
||||
public ChunkEntitySlices(final Level world, final int chunkX, final int chunkZ) { // inclusive, inclusive
|
||||
this.minSection = WorldUtil.getMinSection(world);
|
||||
this.maxSection = WorldUtil.getMaxSection(world);
|
||||
private boolean isTransient;
|
||||
|
||||
public boolean isTransient() {
|
||||
return this.isTransient;
|
||||
}
|
||||
|
||||
public void setTransient(final boolean value) {
|
||||
this.isTransient = value;
|
||||
}
|
||||
|
||||
public ChunkEntitySlices(final Level world, final int chunkX, final int chunkZ, final FullChunkStatus status,
|
||||
final int minSection, final int maxSection) { // inclusive, inclusive
|
||||
this.minSection = minSection;
|
||||
this.maxSection = maxSection;
|
||||
this.chunkX = chunkX;
|
||||
this.chunkZ = chunkZ;
|
||||
this.world = world;
|
||||
|
||||
this.allEntities = new EntityCollectionBySection(this);
|
||||
this.hardCollidingEntities = new EntityCollectionBySection(this);
|
||||
this.entitiesByType = new Reference2ObjectOpenHashMap<>();
|
||||
this.entitiesByClass = new Reference2ObjectOpenHashMap<>();
|
||||
this.entitiesByType = new Reference2ObjectOpenHashMap<>();
|
||||
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
public static List<Entity> readEntities(final ServerLevel world, final CompoundTag compoundTag) {
|
||||
// TODO check this and below on update for format changes
|
||||
return EntityType.loadEntitiesRecursive(compoundTag.getList("Entities", 10), world).collect(ImmutableList.toImmutableList());
|
||||
}
|
||||
|
||||
// Paper start - rewrite chunk system
|
||||
public static void copyEntities(final CompoundTag from, final CompoundTag into) {
|
||||
if (from == null) {
|
||||
return;
|
||||
}
|
||||
final ListTag entitiesFrom = from.getList("Entities", Tag.TAG_COMPOUND);
|
||||
if (entitiesFrom == null || entitiesFrom.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
final ListTag entitiesInto = into.getList("Entities", Tag.TAG_COMPOUND);
|
||||
into.put("Entities", entitiesInto); // this is in case into doesn't have any entities
|
||||
entitiesInto.addAll(0, entitiesFrom);
|
||||
}
|
||||
|
||||
public static CompoundTag saveEntityChunk(final List<Entity> entities, final ChunkPos chunkPos, final ServerLevel world) {
|
||||
return saveEntityChunk0(entities, chunkPos, world, false);
|
||||
}
|
||||
|
||||
public static CompoundTag saveEntityChunk0(final List<Entity> entities, final ChunkPos chunkPos, final ServerLevel world, final boolean force) {
|
||||
if (!force && entities.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final ListTag entitiesTag = new ListTag();
|
||||
for (final Entity entity : entities) {
|
||||
CompoundTag compoundTag = new CompoundTag();
|
||||
if (entity.save(compoundTag)) {
|
||||
entitiesTag.add(compoundTag);
|
||||
}
|
||||
}
|
||||
final CompoundTag ret = NbtUtils.addCurrentDataVersion(new CompoundTag());
|
||||
ret.put("Entities", entitiesTag);
|
||||
EntityStorage.writeChunkPos(ret, chunkPos);
|
||||
|
||||
return !force && entitiesTag.isEmpty() ? null : ret;
|
||||
}
|
||||
|
||||
public CompoundTag save() {
|
||||
final int len = this.entities.size();
|
||||
if (len == 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final Entity[] rawData = this.entities.getRawData();
|
||||
final List<Entity> collectedEntities = new ArrayList<>(len);
|
||||
for (int i = 0; i < len; ++i) {
|
||||
final Entity entity = rawData[i];
|
||||
if (entity.shouldBeSaved()) {
|
||||
collectedEntities.add(entity);
|
||||
}
|
||||
}
|
||||
|
||||
if (collectedEntities.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return saveEntityChunk(collectedEntities, new ChunkPos(this.chunkX, this.chunkZ), (ServerLevel)this.world);
|
||||
}
|
||||
|
||||
// returns true if this chunk has transient entities remaining
|
||||
public boolean unload() {
|
||||
final int len = this.entities.size();
|
||||
final Entity[] collectedEntities = Arrays.copyOf(this.entities.getRawData(), len);
|
||||
|
||||
for (int i = 0; i < len; ++i) {
|
||||
final Entity entity = collectedEntities[i];
|
||||
if (entity.isRemoved()) {
|
||||
// removed by us below
|
||||
continue;
|
||||
}
|
||||
if (entity.shouldBeSaved()) {
|
||||
entity.setRemoved(Entity.RemovalReason.UNLOADED_TO_CHUNK);
|
||||
if (entity.isVehicle()) {
|
||||
// we cannot assume that these entities are contained within this chunk, because entities can
|
||||
// desync - so we need to remove them all
|
||||
for (final Entity passenger : entity.getIndirectPassengers()) {
|
||||
passenger.setRemoved(Entity.RemovalReason.UNLOADED_TO_CHUNK);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return this.entities.size() != 0;
|
||||
}
|
||||
|
||||
private List<Entity> getAllEntities() {
|
||||
final int len = this.entities.size();
|
||||
if (len == 0) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
final Entity[] rawData = this.entities.getRawData();
|
||||
final List<Entity> collectedEntities = new ArrayList<>(len);
|
||||
for (int i = 0; i < len; ++i) {
|
||||
collectedEntities.add(rawData[i]);
|
||||
}
|
||||
|
||||
return collectedEntities;
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return this.entities.size() == 0;
|
||||
}
|
||||
|
||||
public void mergeInto(final ChunkEntitySlices slices) {
|
||||
final Entity[] entities = this.entities.getRawData();
|
||||
for (int i = 0, size = Math.min(entities.length, this.entities.size()); i < size; ++i) {
|
||||
final Entity entity = entities[i];
|
||||
slices.addEntity(entity, ((ChunkSystemEntity)entity).moonrise$getSectionY());
|
||||
}
|
||||
}
|
||||
|
||||
private boolean preventStatusUpdates;
|
||||
public boolean startPreventingStatusUpdates() {
|
||||
final boolean ret = this.preventStatusUpdates;
|
||||
this.preventStatusUpdates = true;
|
||||
return ret;
|
||||
}
|
||||
|
||||
public boolean isPreventingStatusUpdates() {
|
||||
return this.preventStatusUpdates;
|
||||
}
|
||||
|
||||
public void stopPreventingStatusUpdates(final boolean prev) {
|
||||
this.preventStatusUpdates = prev;
|
||||
}
|
||||
|
||||
public void updateStatus(final FullChunkStatus status, final EntityLookup lookup) {
|
||||
this.status = status;
|
||||
|
||||
final Entity[] entities = this.entities.getRawData();
|
||||
|
||||
for (int i = 0, size = this.entities.size(); i < size; ++i) {
|
||||
final Entity entity = entities[i];
|
||||
|
||||
final Visibility oldVisibility = EntityLookup.getEntityStatus(entity);
|
||||
((ChunkSystemEntity)entity).moonrise$setChunkStatus(status);
|
||||
final Visibility newVisibility = EntityLookup.getEntityStatus(entity);
|
||||
|
||||
lookup.entityStatusChange(entity, this, oldVisibility, newVisibility, false, false, false);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean addEntity(final Entity entity, final int chunkSection) {
|
||||
if (!this.entities.add(entity)) {
|
||||
return false;
|
||||
}
|
||||
((ChunkSystemEntity)entity).moonrise$setChunkStatus(this.status);
|
||||
final int sectionIndex = chunkSection - this.minSection;
|
||||
|
||||
this.allEntities.addEntity(entity, sectionIndex);
|
||||
|
||||
if (((CollisionEntity)entity).moonrise$isHardColliding()) {
|
||||
if (((ChunkSystemEntity)entity).moonrise$isHardColliding()) {
|
||||
this.hardCollidingEntities.addEntity(entity, sectionIndex);
|
||||
}
|
||||
|
||||
@@ -87,11 +255,12 @@ public final class ChunkEntitySlices {
|
||||
if (!this.entities.remove(entity)) {
|
||||
return false;
|
||||
}
|
||||
((ChunkSystemEntity)entity).moonrise$setChunkStatus(null);
|
||||
final int sectionIndex = chunkSection - this.minSection;
|
||||
|
||||
this.allEntities.removeEntity(entity, sectionIndex);
|
||||
|
||||
if (((CollisionEntity)entity).moonrise$isHardColliding()) {
|
||||
if (((ChunkSystemEntity)entity).moonrise$isHardColliding()) {
|
||||
this.hardCollidingEntities.removeEntity(entity, sectionIndex);
|
||||
}
|
||||
|
||||
@@ -198,13 +367,13 @@ public final class ChunkEntitySlices {
|
||||
}
|
||||
}
|
||||
|
||||
protected static final class BasicEntityList<E extends Entity> {
|
||||
private static final class BasicEntityList<E extends Entity> {
|
||||
|
||||
protected static final Entity[] EMPTY = new Entity[0];
|
||||
protected static final int DEFAULT_CAPACITY = 4;
|
||||
private static final Entity[] EMPTY = new Entity[0];
|
||||
private static final int DEFAULT_CAPACITY = 4;
|
||||
|
||||
protected E[] storage;
|
||||
protected int size;
|
||||
private E[] storage;
|
||||
private int size;
|
||||
|
||||
public BasicEntityList() {
|
||||
this(0);
|
||||
@@ -274,19 +443,17 @@ public final class ChunkEntitySlices {
|
||||
}
|
||||
}
|
||||
|
||||
protected static final class EntityCollectionBySection {
|
||||
private static final class EntityCollectionBySection {
|
||||
|
||||
protected final ChunkEntitySlices manager;
|
||||
protected final long[] nonEmptyBitset;
|
||||
protected final BasicEntityList<Entity>[] entitiesBySection;
|
||||
protected int count;
|
||||
private final ChunkEntitySlices slices;
|
||||
private final BasicEntityList<Entity>[] entitiesBySection;
|
||||
private int count;
|
||||
|
||||
public EntityCollectionBySection(final ChunkEntitySlices manager) {
|
||||
this.manager = manager;
|
||||
public EntityCollectionBySection(final ChunkEntitySlices slices) {
|
||||
this.slices = slices;
|
||||
|
||||
final int sectionCount = manager.maxSection - manager.minSection + 1;
|
||||
final int sectionCount = slices.maxSection - slices.minSection + 1;
|
||||
|
||||
this.nonEmptyBitset = new long[(sectionCount + (Long.SIZE - 1)) >>> 6]; // (sectionCount + (Long.SIZE - 1)) / Long.SIZE
|
||||
this.entitiesBySection = new BasicEntityList[sectionCount];
|
||||
}
|
||||
|
||||
@@ -299,7 +466,6 @@ public final class ChunkEntitySlices {
|
||||
|
||||
if (list == null) {
|
||||
this.entitiesBySection[sectionIndex] = list = new BasicEntityList<>();
|
||||
this.nonEmptyBitset[sectionIndex >>> 6] |= (1L << (sectionIndex & (Long.SIZE - 1)));
|
||||
}
|
||||
|
||||
list.add(entity);
|
||||
@@ -317,7 +483,6 @@ public final class ChunkEntitySlices {
|
||||
|
||||
if (list.isEmpty()) {
|
||||
this.entitiesBySection[sectionIndex] = null;
|
||||
this.nonEmptyBitset[sectionIndex >>> 6] ^= (1L << (sectionIndex & (Long.SIZE - 1)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -326,8 +491,8 @@ public final class ChunkEntitySlices {
|
||||
return;
|
||||
}
|
||||
|
||||
final int minSection = this.manager.minSection;
|
||||
final int maxSection = this.manager.maxSection;
|
||||
final int minSection = this.slices.minSection;
|
||||
final int maxSection = this.slices.maxSection;
|
||||
|
||||
final int min = Mth.clamp(Mth.floor(box.minY - 2.0) >> 4, minSection, maxSection);
|
||||
final int max = Mth.clamp(Mth.floor(box.maxY + 2.0) >> 4, minSection, maxSection);
|
||||
@@ -365,8 +530,8 @@ public final class ChunkEntitySlices {
|
||||
return false;
|
||||
}
|
||||
|
||||
final int minSection = this.manager.minSection;
|
||||
final int maxSection = this.manager.maxSection;
|
||||
final int minSection = this.slices.minSection;
|
||||
final int maxSection = this.slices.maxSection;
|
||||
|
||||
final int min = Mth.clamp(Mth.floor(box.minY - 2.0) >> 4, minSection, maxSection);
|
||||
final int max = Mth.clamp(Mth.floor(box.maxY + 2.0) >> 4, minSection, maxSection);
|
||||
@@ -409,8 +574,8 @@ public final class ChunkEntitySlices {
|
||||
return;
|
||||
}
|
||||
|
||||
final int minSection = this.manager.minSection;
|
||||
final int maxSection = this.manager.maxSection;
|
||||
final int minSection = this.slices.minSection;
|
||||
final int maxSection = this.slices.maxSection;
|
||||
|
||||
final int min = Mth.clamp(Mth.floor(box.minY - 2.0) >> 4, minSection, maxSection);
|
||||
final int max = Mth.clamp(Mth.floor(box.maxY + 2.0) >> 4, minSection, maxSection);
|
||||
@@ -460,8 +625,8 @@ public final class ChunkEntitySlices {
|
||||
return false;
|
||||
}
|
||||
|
||||
final int minSection = this.manager.minSection;
|
||||
final int maxSection = this.manager.maxSection;
|
||||
final int minSection = this.slices.minSection;
|
||||
final int maxSection = this.slices.maxSection;
|
||||
|
||||
final int min = Mth.clamp(Mth.floor(box.minY - 2.0) >> 4, minSection, maxSection);
|
||||
final int max = Mth.clamp(Mth.floor(box.maxY + 2.0) >> 4, minSection, maxSection);
|
||||
@@ -519,8 +684,8 @@ public final class ChunkEntitySlices {
|
||||
return;
|
||||
}
|
||||
|
||||
final int minSection = this.manager.minSection;
|
||||
final int maxSection = this.manager.maxSection;
|
||||
final int minSection = this.slices.minSection;
|
||||
final int maxSection = this.slices.maxSection;
|
||||
|
||||
final int min = Mth.clamp(Mth.floor(box.minY - 2.0) >> 4, minSection, maxSection);
|
||||
final int max = Mth.clamp(Mth.floor(box.maxY + 2.0) >> 4, minSection, maxSection);
|
||||
@@ -570,8 +735,8 @@ public final class ChunkEntitySlices {
|
||||
return false;
|
||||
}
|
||||
|
||||
final int minSection = this.manager.minSection;
|
||||
final int maxSection = this.manager.maxSection;
|
||||
final int minSection = this.slices.minSection;
|
||||
final int maxSection = this.slices.maxSection;
|
||||
|
||||
final int min = Mth.clamp(Mth.floor(box.minY - 2.0) >> 4, minSection, maxSection);
|
||||
final int max = Mth.clamp(Mth.floor(box.maxY + 2.0) >> 4, minSection, maxSection);
|
||||
@@ -623,4 +788,4 @@ public final class ChunkEntitySlices {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,81 @@
|
||||
package ca.spottedleaf.moonrise.patches.chunk_system.level.entity.client;
|
||||
|
||||
import ca.spottedleaf.moonrise.common.util.CoordinateUtils;
|
||||
import ca.spottedleaf.moonrise.common.util.WorldUtil;
|
||||
import ca.spottedleaf.moonrise.patches.chunk_system.level.entity.ChunkEntitySlices;
|
||||
import ca.spottedleaf.moonrise.patches.chunk_system.level.entity.EntityLookup;
|
||||
import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
|
||||
import net.minecraft.server.level.FullChunkStatus;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.entity.LevelCallback;
|
||||
|
||||
public final class ClientEntityLookup extends EntityLookup {
|
||||
|
||||
private final LongOpenHashSet tickingChunks = new LongOpenHashSet();
|
||||
|
||||
public ClientEntityLookup(final Level world, final LevelCallback<Entity> worldCallback) {
|
||||
super(world, worldCallback);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Boolean blockTicketUpdates() {
|
||||
// not present on client
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setBlockTicketUpdates(Boolean value) {
|
||||
// not present on client
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void checkThread(final int chunkX, final int chunkZ, final String reason) {
|
||||
// TODO implement?
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void checkThread(final Entity entity, final String reason) {
|
||||
// TODO implement?
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ChunkEntitySlices createEntityChunk(final int chunkX, final int chunkZ, final boolean transientChunk) {
|
||||
final boolean ticking = this.tickingChunks.contains(CoordinateUtils.getChunkKey(chunkX, chunkZ));
|
||||
|
||||
final ChunkEntitySlices ret = new ChunkEntitySlices(
|
||||
this.world, chunkX, chunkZ,
|
||||
ticking ? FullChunkStatus.ENTITY_TICKING : FullChunkStatus.FULL, WorldUtil.getMinSection(this.world), WorldUtil.getMaxSection(this.world)
|
||||
);
|
||||
|
||||
// note: not handled by superclass
|
||||
this.addChunk(chunkX, chunkZ, ret);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onEmptySlices(final int chunkX, final int chunkZ) {
|
||||
this.removeChunk(chunkX, chunkZ);
|
||||
}
|
||||
|
||||
public void markTicking(final long pos) {
|
||||
if (this.tickingChunks.add(pos)) {
|
||||
final int chunkX = CoordinateUtils.getChunkX(pos);
|
||||
final int chunkZ = CoordinateUtils.getChunkZ(pos);
|
||||
if (this.getChunk(chunkX, chunkZ) != null) {
|
||||
this.chunkStatusChange(chunkX, chunkZ, FullChunkStatus.ENTITY_TICKING);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void markNonTicking(final long pos) {
|
||||
if (this.tickingChunks.remove(pos)) {
|
||||
final int chunkX = CoordinateUtils.getChunkX(pos);
|
||||
final int chunkZ = CoordinateUtils.getChunkZ(pos);
|
||||
if (this.getChunk(chunkX, chunkZ) != null) {
|
||||
this.chunkStatusChange(chunkX, chunkZ, FullChunkStatus.FULL);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
package ca.spottedleaf.moonrise.patches.chunk_system.level.entity.dfl;
|
||||
|
||||
import ca.spottedleaf.moonrise.common.util.CoordinateUtils;
|
||||
import ca.spottedleaf.moonrise.common.util.WorldUtil;
|
||||
import ca.spottedleaf.moonrise.patches.chunk_system.level.entity.ChunkEntitySlices;
|
||||
import ca.spottedleaf.moonrise.patches.chunk_system.level.entity.EntityLookup;
|
||||
import net.minecraft.server.level.FullChunkStatus;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.entity.LevelCallback;
|
||||
|
||||
public final class DefaultEntityLookup extends EntityLookup {
|
||||
public DefaultEntityLookup(final Level world) {
|
||||
super(world, new DefaultLevelCallback());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Boolean blockTicketUpdates() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setBlockTicketUpdates(final Boolean value) {}
|
||||
|
||||
@Override
|
||||
protected void checkThread(final int chunkX, final int chunkZ, final String reason) {}
|
||||
|
||||
@Override
|
||||
protected void checkThread(final Entity entity, final String reason) {}
|
||||
|
||||
@Override
|
||||
protected ChunkEntitySlices createEntityChunk(final int chunkX, final int chunkZ, final boolean transientChunk) {
|
||||
final ChunkEntitySlices ret = new ChunkEntitySlices(
|
||||
this.world, chunkX, chunkZ, FullChunkStatus.FULL,
|
||||
WorldUtil.getMinSection(this.world), WorldUtil.getMaxSection(this.world)
|
||||
);
|
||||
|
||||
// note: not handled by superclass
|
||||
this.addChunk(chunkX, chunkZ, ret);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onEmptySlices(final int chunkX, final int chunkZ) {
|
||||
this.removeChunk(chunkX, chunkZ);
|
||||
}
|
||||
|
||||
protected static final class DefaultLevelCallback implements LevelCallback<Entity> {
|
||||
|
||||
@Override
|
||||
public void onCreated(final Entity entity) {}
|
||||
|
||||
@Override
|
||||
public void onDestroyed(final Entity entity) {}
|
||||
|
||||
@Override
|
||||
public void onTickingStart(final Entity entity) {}
|
||||
|
||||
@Override
|
||||
public void onTickingEnd(final Entity entity) {}
|
||||
|
||||
@Override
|
||||
public void onTrackingStart(final Entity entity) {}
|
||||
|
||||
@Override
|
||||
public void onTrackingEnd(final Entity entity) {}
|
||||
|
||||
@Override
|
||||
public void onSectionChange(final Entity entity) {}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package ca.spottedleaf.moonrise.patches.chunk_system.level.entity.server;
|
||||
|
||||
import ca.spottedleaf.moonrise.common.util.TickThread;
|
||||
import ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel;
|
||||
import ca.spottedleaf.moonrise.patches.chunk_system.level.entity.ChunkEntitySlices;
|
||||
import ca.spottedleaf.moonrise.patches.chunk_system.level.entity.EntityLookup;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.level.entity.LevelCallback;
|
||||
|
||||
public final class ServerEntityLookup extends EntityLookup {
|
||||
|
||||
private final ServerLevel serverWorld;
|
||||
|
||||
public ServerEntityLookup(final ServerLevel world, final LevelCallback<Entity> worldCallback) {
|
||||
super(world, worldCallback);
|
||||
this.serverWorld = world;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Boolean blockTicketUpdates() {
|
||||
return ((ChunkSystemServerLevel)this.serverWorld).moonrise$getChunkTaskScheduler().chunkHolderManager.blockTicketUpdates();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setBlockTicketUpdates(final Boolean value) {
|
||||
((ChunkSystemServerLevel)this.serverWorld).moonrise$getChunkTaskScheduler().chunkHolderManager.unblockTicketUpdates(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void checkThread(final int chunkX, final int chunkZ, final String reason) {
|
||||
TickThread.ensureTickThread(this.serverWorld, chunkX, chunkZ, reason);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void checkThread(final Entity entity, final String reason) {
|
||||
TickThread.ensureTickThread(entity, reason);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ChunkEntitySlices createEntityChunk(final int chunkX, final int chunkZ, final boolean transientChunk) {
|
||||
// loadInEntityChunk will call addChunk for us
|
||||
return ((ChunkSystemServerLevel)this.serverWorld).moonrise$getChunkTaskScheduler().chunkHolderManager
|
||||
.getOrCreateEntityChunk(chunkX, chunkZ, transientChunk);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onEmptySlices(final int chunkX, final int chunkZ) {
|
||||
// entity slices unloading is managed by ticket levels in chunk system
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package ca.spottedleaf.moonrise.patches.chunk_system.level.poi;
|
||||
|
||||
import ca.spottedleaf.moonrise.patches.chunk_system.level.storage.ChunkSystemSectionStorage;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.world.level.chunk.ChunkAccess;
|
||||
|
||||
public interface ChunkSystemPoiManager extends ChunkSystemSectionStorage {
|
||||
|
||||
public ServerLevel moonrise$getWorld();
|
||||
|
||||
public void moonrise$onUnload(final long coordinate);
|
||||
|
||||
public void moonrise$loadInPoiChunk(final PoiChunk poiChunk);
|
||||
|
||||
public void moonrise$checkConsistency(final ChunkAccess chunk);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package ca.spottedleaf.moonrise.patches.chunk_system.level.poi;
|
||||
|
||||
import net.minecraft.world.entity.ai.village.poi.PoiSection;
|
||||
import java.util.Optional;
|
||||
|
||||
public interface ChunkSystemPoiSection {
|
||||
|
||||
public boolean moonrise$isEmpty();
|
||||
|
||||
public Optional<PoiSection> moonrise$asOptional();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,212 @@
|
||||
package ca.spottedleaf.moonrise.patches.chunk_system.level.poi;
|
||||
|
||||
import ca.spottedleaf.moonrise.common.util.CoordinateUtils;
|
||||
import ca.spottedleaf.moonrise.common.util.TickThread;
|
||||
import ca.spottedleaf.moonrise.common.util.WorldUtil;
|
||||
import com.mojang.serialization.Codec;
|
||||
import com.mojang.serialization.DataResult;
|
||||
import net.minecraft.SharedConstants;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.nbt.NbtOps;
|
||||
import net.minecraft.nbt.Tag;
|
||||
import net.minecraft.resources.RegistryOps;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.world.entity.ai.village.poi.PoiManager;
|
||||
import net.minecraft.world.entity.ai.village.poi.PoiSection;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import java.util.Optional;
|
||||
|
||||
public final class PoiChunk {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(PoiChunk.class);
|
||||
|
||||
public final ServerLevel world;
|
||||
public final int chunkX;
|
||||
public final int chunkZ;
|
||||
public final int minSection;
|
||||
public final int maxSection;
|
||||
|
||||
private final PoiSection[] sections;
|
||||
|
||||
private boolean isDirty;
|
||||
private boolean loaded;
|
||||
|
||||
public PoiChunk(final ServerLevel world, final int chunkX, final int chunkZ, final int minSection, final int maxSection) {
|
||||
this(world, chunkX, chunkZ, minSection, maxSection, new PoiSection[maxSection - minSection + 1]);
|
||||
}
|
||||
|
||||
public PoiChunk(final ServerLevel world, final int chunkX, final int chunkZ, final int minSection, final int maxSection, final PoiSection[] sections) {
|
||||
this.world = world;
|
||||
this.chunkX = chunkX;
|
||||
this.chunkZ = chunkZ;
|
||||
this.minSection = minSection;
|
||||
this.maxSection = maxSection;
|
||||
this.sections = sections;
|
||||
if (this.sections.length != (maxSection - minSection + 1)) {
|
||||
throw new IllegalStateException("Incorrect length used, expected " + (maxSection - minSection + 1) + ", got " + this.sections.length);
|
||||
}
|
||||
}
|
||||
|
||||
public void load() {
|
||||
TickThread.ensureTickThread(this.world, this.chunkX, this.chunkZ, "Loading in poi chunk off-main");
|
||||
if (this.loaded) {
|
||||
return;
|
||||
}
|
||||
this.loaded = true;
|
||||
((ChunkSystemPoiManager)this.world.getChunkSource().getPoiManager()).moonrise$loadInPoiChunk(this);
|
||||
}
|
||||
|
||||
public boolean isLoaded() {
|
||||
return this.loaded;
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
for (final PoiSection section : this.sections) {
|
||||
if (section != null && !((ChunkSystemPoiSection)section).moonrise$isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public PoiSection getOrCreateSection(final int chunkY) {
|
||||
if (chunkY >= this.minSection && chunkY <= this.maxSection) {
|
||||
final int idx = chunkY - this.minSection;
|
||||
final PoiSection ret = this.sections[idx];
|
||||
if (ret != null) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
final PoiManager poiManager = this.world.getPoiManager();
|
||||
final long key = CoordinateUtils.getChunkSectionKey(this.chunkX, chunkY, this.chunkZ);
|
||||
|
||||
return this.sections[idx] = new PoiSection(() -> {
|
||||
poiManager.setDirty(key);
|
||||
});
|
||||
}
|
||||
throw new IllegalArgumentException("chunkY is out of bounds, chunkY: " + chunkY + " outside [" + this.minSection + "," + this.maxSection + "]");
|
||||
}
|
||||
|
||||
public PoiSection getSection(final int chunkY) {
|
||||
if (chunkY >= this.minSection && chunkY <= this.maxSection) {
|
||||
return this.sections[chunkY - this.minSection];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public Optional<PoiSection> getSectionForVanilla(final int chunkY) {
|
||||
if (chunkY >= this.minSection && chunkY <= this.maxSection) {
|
||||
final PoiSection ret = this.sections[chunkY - this.minSection];
|
||||
return ret == null ? Optional.empty() : ((ChunkSystemPoiSection)ret).moonrise$asOptional();
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
public boolean isDirty() {
|
||||
return this.isDirty;
|
||||
}
|
||||
|
||||
public void setDirty(final boolean dirty) {
|
||||
this.isDirty = dirty;
|
||||
}
|
||||
|
||||
// returns null if empty
|
||||
public CompoundTag save() {
|
||||
final RegistryOps<Tag> registryOps = RegistryOps.create(NbtOps.INSTANCE, this.world.registryAccess());
|
||||
|
||||
final CompoundTag ret = new CompoundTag();
|
||||
final CompoundTag sections = new CompoundTag();
|
||||
ret.put("Sections", sections);
|
||||
|
||||
ret.putInt("DataVersion", SharedConstants.getCurrentVersion().getDataVersion().getVersion());
|
||||
|
||||
final ServerLevel world = this.world;
|
||||
final PoiManager poiManager = world.getPoiManager();
|
||||
final int chunkX = this.chunkX;
|
||||
final int chunkZ = this.chunkZ;
|
||||
|
||||
for (int sectionY = this.minSection; sectionY <= this.maxSection; ++sectionY) {
|
||||
final PoiSection section = this.sections[sectionY - this.minSection];
|
||||
if (section == null || ((ChunkSystemPoiSection)section).moonrise$isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
final long key = CoordinateUtils.getChunkSectionKey(chunkX, sectionY, chunkZ);
|
||||
// codecs are honestly such a fucking disaster. What the fuck is this trash?
|
||||
final Codec<PoiSection> codec = PoiSection.codec(() -> {
|
||||
poiManager.setDirty(key);
|
||||
});
|
||||
|
||||
final DataResult<Tag> serializedResult = codec.encodeStart(registryOps, section);
|
||||
final int finalSectionY = sectionY;
|
||||
final Tag serialized = serializedResult.resultOrPartial((final String description) -> {
|
||||
LOGGER.error("Failed to serialize poi chunk for world: " + WorldUtil.getWorldName(world) + ", chunk: (" + chunkX + "," + finalSectionY + "," + chunkZ + "); description: " + description);
|
||||
}).orElse(null);
|
||||
if (serialized == null) {
|
||||
// failed, should be logged from the resultOrPartial
|
||||
continue;
|
||||
}
|
||||
|
||||
sections.put(Integer.toString(sectionY), serialized);
|
||||
}
|
||||
|
||||
return sections.isEmpty() ? null : ret;
|
||||
}
|
||||
|
||||
public static PoiChunk empty(final ServerLevel world, final int chunkX, final int chunkZ) {
|
||||
final PoiChunk ret = new PoiChunk(world, chunkX, chunkZ, WorldUtil.getMinSection(world), WorldUtil.getMaxSection(world));
|
||||
ret.loaded = true;
|
||||
return ret;
|
||||
}
|
||||
|
||||
public static PoiChunk parse(final ServerLevel world, final int chunkX, final int chunkZ, final CompoundTag data) {
|
||||
final PoiChunk ret = empty(world, chunkX, chunkZ);
|
||||
|
||||
final RegistryOps<Tag> registryOps = RegistryOps.create(NbtOps.INSTANCE, world.registryAccess());
|
||||
|
||||
final CompoundTag sections = data.getCompound("Sections");
|
||||
|
||||
if (sections.isEmpty()) {
|
||||
// nothing to parse
|
||||
return ret;
|
||||
}
|
||||
|
||||
final PoiManager poiManager = world.getPoiManager();
|
||||
|
||||
boolean readAnything = false;
|
||||
|
||||
for (int sectionY = ret.minSection; sectionY <= ret.maxSection; ++sectionY) {
|
||||
final String key = Integer.toString(sectionY);
|
||||
if (!sections.contains(key)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
final long coordinateKey = CoordinateUtils.getChunkSectionKey(chunkX, sectionY, chunkZ);
|
||||
// codecs are honestly such a fucking disaster. What the fuck is this trash?
|
||||
final Codec<PoiSection> codec = PoiSection.codec(() -> {
|
||||
poiManager.setDirty(coordinateKey);
|
||||
});
|
||||
|
||||
final CompoundTag section = sections.getCompound(key);
|
||||
final DataResult<PoiSection> deserializeResult = codec.parse(registryOps, section);
|
||||
final int finalSectionY = sectionY;
|
||||
final PoiSection deserialized = deserializeResult.resultOrPartial((final String description) -> {
|
||||
LOGGER.error("Failed to deserialize poi chunk for world: " + WorldUtil.getWorldName(world) + ", chunk: (" + chunkX + "," + finalSectionY + "," + chunkZ + "); description: " + description);
|
||||
}).orElse(null);
|
||||
|
||||
if (deserialized == null || ((ChunkSystemPoiSection)deserialized).moonrise$isEmpty()) {
|
||||
// completely empty, no point in storing this
|
||||
continue;
|
||||
}
|
||||
|
||||
readAnything = true;
|
||||
ret.sections[sectionY - ret.minSection] = deserialized;
|
||||
}
|
||||
|
||||
ret.loaded = !readAnything; // Set loaded to false if we read anything to ensure proper callbacks to PoiManager are made on #load
|
||||
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package ca.spottedleaf.moonrise.patches.chunk_system.level.storage;
|
||||
|
||||
import com.mojang.serialization.Dynamic;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.nbt.Tag;
|
||||
import net.minecraft.world.level.chunk.storage.RegionFileStorage;
|
||||
import java.io.IOException;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
public interface ChunkSystemSectionStorage {
|
||||
|
||||
public CompoundTag moonrise$read(final int chunkX, final int chunkZ) throws IOException;
|
||||
|
||||
public void moonrise$write(final int chunkX, final int chunkZ, final CompoundTag data) throws IOException;
|
||||
|
||||
public RegionFileStorage moonrise$getRegionStorage();
|
||||
|
||||
public void moonrise$close() throws IOException;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package ca.spottedleaf.moonrise.patches.chunk_system.player;
|
||||
|
||||
public interface ChunkSystemServerPlayer {
|
||||
|
||||
public boolean moonrise$isRealPlayer();
|
||||
|
||||
public void moonrise$setRealPlayer(final boolean real);
|
||||
|
||||
public RegionizedPlayerChunkLoader.PlayerChunkLoaderData moonrise$getChunkLoader();
|
||||
|
||||
public void moonrise$setChunkLoader(final RegionizedPlayerChunkLoader.PlayerChunkLoaderData loader);
|
||||
|
||||
public RegionizedPlayerChunkLoader.ViewDistanceHolder moonrise$getViewDistanceHolder();
|
||||
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,140 @@
|
||||
package ca.spottedleaf.moonrise.patches.chunk_system.queue;
|
||||
|
||||
import ca.spottedleaf.concurrentutil.map.ConcurrentLong2ReferenceChainedHashTable;
|
||||
import ca.spottedleaf.moonrise.common.util.CoordinateUtils;
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import it.unimi.dsi.fastutil.longs.LongIterator;
|
||||
import it.unimi.dsi.fastutil.longs.LongLinkedOpenHashSet;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
public final class ChunkUnloadQueue {
|
||||
|
||||
public final int coordinateShift;
|
||||
private final AtomicLong orderGenerator = new AtomicLong();
|
||||
private final ConcurrentLong2ReferenceChainedHashTable<UnloadSection> unloadSections = new ConcurrentLong2ReferenceChainedHashTable<>();
|
||||
|
||||
/*
|
||||
* Note: write operations do not occur in parallel for any given section.
|
||||
* Note: coordinateShift <= region shift in order for retrieveForCurrentRegion() to function correctly
|
||||
*/
|
||||
|
||||
public ChunkUnloadQueue(final int coordinateShift) {
|
||||
this.coordinateShift = coordinateShift;
|
||||
}
|
||||
|
||||
public static record SectionToUnload(int sectionX, int sectionZ, long order, int count) {}
|
||||
|
||||
public List<SectionToUnload> retrieveForAllRegions() {
|
||||
final List<SectionToUnload> ret = new ArrayList<>();
|
||||
|
||||
for (final Iterator<ConcurrentLong2ReferenceChainedHashTable.TableEntry<UnloadSection>> iterator = this.unloadSections.entryIterator(); iterator.hasNext();) {
|
||||
final ConcurrentLong2ReferenceChainedHashTable.TableEntry<UnloadSection> entry = iterator.next();
|
||||
final long key = entry.getKey();
|
||||
final UnloadSection section = entry.getValue();
|
||||
final int sectionX = CoordinateUtils.getChunkX(key);
|
||||
final int sectionZ = CoordinateUtils.getChunkZ(key);
|
||||
|
||||
ret.add(new SectionToUnload(sectionX, sectionZ, section.order, section.chunks.size()));
|
||||
}
|
||||
|
||||
ret.sort((final SectionToUnload s1, final SectionToUnload s2) -> {
|
||||
return Long.compare(s1.order, s2.order);
|
||||
});
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
public UnloadSection getSectionUnsynchronized(final int sectionX, final int sectionZ) {
|
||||
return this.unloadSections.get(CoordinateUtils.getChunkKey(sectionX, sectionZ));
|
||||
}
|
||||
|
||||
public UnloadSection removeSection(final int sectionX, final int sectionZ) {
|
||||
return this.unloadSections.remove(CoordinateUtils.getChunkKey(sectionX, sectionZ));
|
||||
}
|
||||
|
||||
// write operation
|
||||
public boolean addChunk(final int chunkX, final int chunkZ) {
|
||||
// write operations do not occur in parallel for a given section
|
||||
final int shift = this.coordinateShift;
|
||||
final int sectionX = chunkX >> shift;
|
||||
final int sectionZ = chunkZ >> shift;
|
||||
final long chunkKey = CoordinateUtils.getChunkKey(chunkX, chunkZ);
|
||||
|
||||
UnloadSection section = this.unloadSections.get(chunkKey);
|
||||
if (section == null) {
|
||||
section = new UnloadSection(this.orderGenerator.getAndIncrement());
|
||||
this.unloadSections.put(chunkKey, section);
|
||||
}
|
||||
|
||||
return section.chunks.add(chunkKey);
|
||||
}
|
||||
|
||||
// write operation
|
||||
public boolean removeChunk(final int chunkX, final int chunkZ) {
|
||||
// write operations do not occur in parallel for a given section
|
||||
final int shift = this.coordinateShift;
|
||||
final int sectionX = chunkX >> shift;
|
||||
final int sectionZ = chunkZ >> shift;
|
||||
final long chunkKey = CoordinateUtils.getChunkKey(chunkX, chunkZ);
|
||||
|
||||
final UnloadSection section = this.unloadSections.get(chunkKey);
|
||||
|
||||
if (section == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!section.chunks.remove(chunkKey)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (section.chunks.isEmpty()) {
|
||||
this.unloadSections.remove(chunkKey);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public JsonElement toDebugJson() {
|
||||
final JsonArray ret = new JsonArray();
|
||||
|
||||
for (final SectionToUnload section : this.retrieveForAllRegions()) {
|
||||
final JsonObject sectionJson = new JsonObject();
|
||||
ret.add(sectionJson);
|
||||
|
||||
sectionJson.addProperty("sectionX", section.sectionX());
|
||||
sectionJson.addProperty("sectionZ", section.sectionX());
|
||||
sectionJson.addProperty("order", section.order());
|
||||
|
||||
final JsonArray coordinates = new JsonArray();
|
||||
sectionJson.add("coordinates", coordinates);
|
||||
|
||||
final UnloadSection actualSection = this.getSectionUnsynchronized(section.sectionX(), section.sectionZ());
|
||||
for (final LongIterator iterator = actualSection.chunks.clone().iterator(); iterator.hasNext();) {
|
||||
final long coordinate = iterator.nextLong();
|
||||
|
||||
final JsonObject coordinateJson = new JsonObject();
|
||||
coordinates.add(coordinateJson);
|
||||
|
||||
coordinateJson.addProperty("chunkX", Integer.valueOf(CoordinateUtils.getChunkX(coordinate)));
|
||||
coordinateJson.addProperty("chunkZ", Integer.valueOf(CoordinateUtils.getChunkZ(coordinate)));
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
public static final class UnloadSection {
|
||||
|
||||
public final long order;
|
||||
public final LongLinkedOpenHashSet chunks = new LongLinkedOpenHashSet();
|
||||
|
||||
public UnloadSection(final long order) {
|
||||
this.order = order;
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,919 @@
|
||||
package ca.spottedleaf.moonrise.patches.chunk_system.scheduling;
|
||||
|
||||
import ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor;
|
||||
import ca.spottedleaf.concurrentutil.executor.standard.PrioritisedThreadPool;
|
||||
import ca.spottedleaf.concurrentutil.executor.standard.PrioritisedThreadedTaskQueue;
|
||||
import ca.spottedleaf.concurrentutil.lock.ReentrantAreaLock;
|
||||
import ca.spottedleaf.concurrentutil.util.ConcurrentUtil;
|
||||
import ca.spottedleaf.moonrise.common.config.PlaceholderConfig;
|
||||
import ca.spottedleaf.moonrise.common.util.CoordinateUtils;
|
||||
import ca.spottedleaf.moonrise.common.util.TickThread;
|
||||
import ca.spottedleaf.moonrise.common.util.WorldUtil;
|
||||
import ca.spottedleaf.moonrise.patches.chunk_system.io.RegionFileIOThread;
|
||||
import ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel;
|
||||
import ca.spottedleaf.moonrise.patches.chunk_system.level.chunk.ChunkSystemChunkStatus;
|
||||
import ca.spottedleaf.moonrise.patches.chunk_system.scheduling.executor.RadiusAwarePrioritisedExecutor;
|
||||
import ca.spottedleaf.moonrise.patches.chunk_system.scheduling.task.ChunkFullTask;
|
||||
import ca.spottedleaf.moonrise.patches.chunk_system.scheduling.task.ChunkLightTask;
|
||||
import ca.spottedleaf.moonrise.patches.chunk_system.scheduling.task.ChunkLoadTask;
|
||||
import ca.spottedleaf.moonrise.patches.chunk_system.scheduling.task.ChunkProgressionTask;
|
||||
import ca.spottedleaf.moonrise.patches.chunk_system.scheduling.task.ChunkUpgradeGenericStatusTask;
|
||||
import ca.spottedleaf.moonrise.patches.chunk_system.server.ChunkSystemMinecraftServer;
|
||||
import ca.spottedleaf.moonrise.patches.chunk_system.util.ParallelSearchRadiusIteration;
|
||||
import net.minecraft.CrashReport;
|
||||
import net.minecraft.CrashReportCategory;
|
||||
import net.minecraft.ReportedException;
|
||||
import net.minecraft.server.level.ChunkMap;
|
||||
import net.minecraft.server.level.FullChunkStatus;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.server.level.TicketType;
|
||||
import net.minecraft.world.level.ChunkPos;
|
||||
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 org.slf4j.LoggerFactory;
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public final class ChunkTaskScheduler {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(ChunkTaskScheduler.class);
|
||||
|
||||
static int newChunkSystemIOThreads;
|
||||
static int newChunkSystemWorkerThreads;
|
||||
static int newChunkSystemGenParallelism;
|
||||
static int newChunkSystemLoadParallelism;
|
||||
|
||||
public static PrioritisedThreadPool workerThreads;
|
||||
|
||||
private static boolean initialised = false;
|
||||
|
||||
public static void init() {
|
||||
if (initialised) {
|
||||
return;
|
||||
}
|
||||
initialised = true;
|
||||
newChunkSystemIOThreads = PlaceholderConfig.chunkSystemIOThreads;
|
||||
newChunkSystemWorkerThreads = PlaceholderConfig.chunkSystemThreads;
|
||||
if (newChunkSystemIOThreads < 0) {
|
||||
newChunkSystemIOThreads = 1;
|
||||
} else {
|
||||
newChunkSystemIOThreads = Math.max(1, newChunkSystemIOThreads);
|
||||
}
|
||||
int defaultWorkerThreads = Runtime.getRuntime().availableProcessors() / 2;
|
||||
if (defaultWorkerThreads <= 4) {
|
||||
defaultWorkerThreads = defaultWorkerThreads <= 3 ? 1 : 2;
|
||||
} else {
|
||||
defaultWorkerThreads = defaultWorkerThreads / 2;
|
||||
}
|
||||
defaultWorkerThreads = Integer.getInteger("Moonrise.WorkerThreadCount", Integer.valueOf(defaultWorkerThreads));
|
||||
|
||||
if (newChunkSystemWorkerThreads < 0) {
|
||||
newChunkSystemWorkerThreads = defaultWorkerThreads;
|
||||
} else {
|
||||
newChunkSystemWorkerThreads = Math.max(1, newChunkSystemWorkerThreads);
|
||||
}
|
||||
|
||||
String newChunkSystemGenParallelism = PlaceholderConfig.chunkSystemGenParallelism;
|
||||
if (newChunkSystemGenParallelism.equalsIgnoreCase("default")) {
|
||||
newChunkSystemGenParallelism = "true";
|
||||
}
|
||||
boolean useParallelGen;
|
||||
if (newChunkSystemGenParallelism.equalsIgnoreCase("on") || newChunkSystemGenParallelism.equalsIgnoreCase("enabled")
|
||||
|| newChunkSystemGenParallelism.equalsIgnoreCase("true")) {
|
||||
useParallelGen = true;
|
||||
} else if (newChunkSystemGenParallelism.equalsIgnoreCase("off") || newChunkSystemGenParallelism.equalsIgnoreCase("disabled")
|
||||
|| newChunkSystemGenParallelism.equalsIgnoreCase("false")) {
|
||||
useParallelGen = false;
|
||||
} else {
|
||||
throw new IllegalStateException("Invalid option for gen-parallelism: must be one of [on, off, enabled, disabled, true, false, default]");
|
||||
}
|
||||
|
||||
ChunkTaskScheduler.newChunkSystemGenParallelism = useParallelGen ? newChunkSystemWorkerThreads : 1;
|
||||
ChunkTaskScheduler.newChunkSystemLoadParallelism = newChunkSystemWorkerThreads;
|
||||
|
||||
RegionFileIOThread.init(newChunkSystemIOThreads);
|
||||
workerThreads = new PrioritisedThreadPool(
|
||||
"Paper Chunk System Worker Pool", newChunkSystemWorkerThreads,
|
||||
(final Thread thread, final Integer id) -> {
|
||||
thread.setPriority(Thread.NORM_PRIORITY - 2);
|
||||
thread.setName("Moonrise Chunk System Worker #" + id.intValue());
|
||||
thread.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
|
||||
@Override
|
||||
public void uncaughtException(final Thread thread, final Throwable throwable) {
|
||||
LOGGER.error("Uncaught exception in thread " + thread.getName(), throwable);
|
||||
}
|
||||
});
|
||||
}, (long)(20.0e6)); // 20ms
|
||||
|
||||
LOGGER.info("Chunk system is using " + newChunkSystemIOThreads + " I/O threads, " + newChunkSystemWorkerThreads + " worker threads, and gen parallelism of " + ChunkTaskScheduler.newChunkSystemGenParallelism + " threads");
|
||||
}
|
||||
|
||||
public static final TicketType<Long> CHUNK_LOAD = TicketType.create("chunk_system:chunk_load", Long::compareTo);
|
||||
private static final AtomicLong CHUNK_LOAD_IDS = new AtomicLong();
|
||||
|
||||
public static Long getNextChunkLoadId() {
|
||||
return Long.valueOf(CHUNK_LOAD_IDS.getAndIncrement());
|
||||
}
|
||||
|
||||
public static final TicketType<Long> NON_FULL_CHUNK_LOAD = TicketType.create("chunk_system:non_full_load", Long::compareTo);
|
||||
private static final AtomicLong NON_FULL_CHUNK_LOAD_IDS = new AtomicLong();
|
||||
|
||||
public static Long getNextNonFullLoadId() {
|
||||
return Long.valueOf(NON_FULL_CHUNK_LOAD_IDS.getAndIncrement());
|
||||
}
|
||||
|
||||
public static final TicketType<Long> ENTITY_LOAD = TicketType.create("chunk_system:entity_load", Long::compareTo);
|
||||
private static final AtomicLong ENTITY_LOAD_IDS = new AtomicLong();
|
||||
|
||||
public static Long getNextEntityLoadId() {
|
||||
return Long.valueOf(ENTITY_LOAD_IDS.getAndIncrement());
|
||||
}
|
||||
|
||||
public static final TicketType<Long> POI_LOAD = TicketType.create("chunk_system:poi_load", Long::compareTo);
|
||||
private static final AtomicLong POI_LOAD_IDS = new AtomicLong();
|
||||
|
||||
public static Long getNextPoiLoadId() {
|
||||
return Long.valueOf(POI_LOAD_IDS.getAndIncrement());
|
||||
}
|
||||
|
||||
|
||||
public static int getTicketLevel(final ChunkStatus status) {
|
||||
return ChunkHolderManager.FULL_LOADED_TICKET_LEVEL + ChunkStatus.getDistance(status);
|
||||
}
|
||||
|
||||
public final ServerLevel world;
|
||||
public final PrioritisedThreadPool workers;
|
||||
public final RadiusAwarePrioritisedExecutor radiusAwareScheduler;
|
||||
public final PrioritisedThreadPool.PrioritisedPoolExecutor parallelGenExecutor;
|
||||
private final PrioritisedThreadPool.PrioritisedPoolExecutor radiusAwareGenExecutor;
|
||||
public final PrioritisedThreadPool.PrioritisedPoolExecutor loadExecutor;
|
||||
|
||||
private final PrioritisedThreadedTaskQueue mainThreadExecutor = new PrioritisedThreadedTaskQueue();
|
||||
|
||||
public final ChunkHolderManager chunkHolderManager;
|
||||
|
||||
static {
|
||||
((ChunkSystemChunkStatus)ChunkStatus.EMPTY).moonrise$setWriteRadius(0);
|
||||
((ChunkSystemChunkStatus)ChunkStatus.STRUCTURE_STARTS).moonrise$setWriteRadius(0);
|
||||
((ChunkSystemChunkStatus)ChunkStatus.STRUCTURE_REFERENCES).moonrise$setWriteRadius(0);
|
||||
((ChunkSystemChunkStatus)ChunkStatus.BIOMES).moonrise$setWriteRadius(0);
|
||||
((ChunkSystemChunkStatus)ChunkStatus.NOISE).moonrise$setWriteRadius(0);
|
||||
((ChunkSystemChunkStatus)ChunkStatus.SURFACE).moonrise$setWriteRadius(0);
|
||||
((ChunkSystemChunkStatus)ChunkStatus.CARVERS).moonrise$setWriteRadius(0);
|
||||
((ChunkSystemChunkStatus)ChunkStatus.FEATURES).moonrise$setWriteRadius(1);
|
||||
((ChunkSystemChunkStatus)ChunkStatus.INITIALIZE_LIGHT).moonrise$setWriteRadius(0);
|
||||
((ChunkSystemChunkStatus)ChunkStatus.LIGHT).moonrise$setWriteRadius(2);
|
||||
((ChunkSystemChunkStatus)ChunkStatus.SPAWN).moonrise$setWriteRadius(0);
|
||||
((ChunkSystemChunkStatus)ChunkStatus.FULL).moonrise$setWriteRadius(0);
|
||||
|
||||
((ChunkSystemChunkStatus)ChunkStatus.EMPTY).moonrise$setEmptyLoadStatus(true);
|
||||
((ChunkSystemChunkStatus)ChunkStatus.STRUCTURE_REFERENCES).moonrise$setEmptyLoadStatus(true);
|
||||
((ChunkSystemChunkStatus)ChunkStatus.BIOMES).moonrise$setEmptyLoadStatus(true);
|
||||
((ChunkSystemChunkStatus)ChunkStatus.NOISE).moonrise$setEmptyLoadStatus(true);
|
||||
((ChunkSystemChunkStatus)ChunkStatus.SURFACE).moonrise$setEmptyLoadStatus(true);
|
||||
((ChunkSystemChunkStatus)ChunkStatus.CARVERS).moonrise$setEmptyLoadStatus(true);
|
||||
((ChunkSystemChunkStatus)ChunkStatus.FEATURES).moonrise$setEmptyLoadStatus(true);
|
||||
((ChunkSystemChunkStatus)ChunkStatus.SPAWN).moonrise$setEmptyLoadStatus(true);
|
||||
|
||||
/*
|
||||
It's important that the neighbour read radius is taken into account. If _any_ later status is using some chunk as
|
||||
a neighbour, it must be also safe if that neighbour is being generated. i.e for any status later than FEATURES,
|
||||
for a status to be parallel safe it must not read the block data from its neighbours.
|
||||
*/
|
||||
final List<ChunkStatus> parallelCapableStatus = Arrays.asList(
|
||||
// No-op executor.
|
||||
ChunkStatus.EMPTY,
|
||||
|
||||
// This is parallel capable, as CB has fixed the concurrency issue with stronghold generations.
|
||||
// Does not touch neighbour chunks.
|
||||
ChunkStatus.STRUCTURE_STARTS,
|
||||
|
||||
// Surprisingly this is parallel capable. It is simply reading the already-created structure starts
|
||||
// into the structure references for the chunk. So while it reads from it neighbours, its neighbours
|
||||
// will not change, even if executed in parallel.
|
||||
ChunkStatus.STRUCTURE_REFERENCES,
|
||||
|
||||
// Safe. Mojang runs it in parallel as well.
|
||||
ChunkStatus.BIOMES,
|
||||
|
||||
// Safe. Mojang runs it in parallel as well.
|
||||
ChunkStatus.NOISE,
|
||||
|
||||
// Parallel safe. Only touches the target chunk. Biome retrieval is now noise based, which is
|
||||
// completely thread-safe.
|
||||
ChunkStatus.SURFACE,
|
||||
|
||||
// No global state is modified in the carvers. It only touches the specified chunk. So it is parallel safe.
|
||||
ChunkStatus.CARVERS,
|
||||
|
||||
// FEATURES is not parallel safe. It writes to neighbours.
|
||||
|
||||
// no-op executor
|
||||
ChunkStatus.INITIALIZE_LIGHT
|
||||
|
||||
// LIGHT is not parallel safe. It also doesn't run on the generation executor, so no point.
|
||||
|
||||
// Only writes to the specified chunk. State is not read by later statuses. Parallel safe.
|
||||
// Note: it may look unsafe because it writes to a worldgenregion, but the region size is always 0 -
|
||||
// see the task margin.
|
||||
// However, if the neighbouring FEATURES chunk is unloaded, but then fails to load in again (for whatever
|
||||
// reason), then it would write to this chunk - and since this status reads blocks from itself, it's not
|
||||
// safe to execute this in parallel.
|
||||
// SPAWN
|
||||
|
||||
// FULL is executed on main.
|
||||
);
|
||||
|
||||
for (final ChunkStatus status : parallelCapableStatus) {
|
||||
((ChunkSystemChunkStatus)status).moonrise$setParallelCapable(true);
|
||||
}
|
||||
}
|
||||
|
||||
private static final int[] ACCESS_RADIUS_TABLE = new int[ChunkStatus.getStatusList().size()];
|
||||
private static final int[] MAX_ACCESS_RADIUS_TABLE = new int[ACCESS_RADIUS_TABLE.length];
|
||||
static {
|
||||
Arrays.fill(ACCESS_RADIUS_TABLE, -1);
|
||||
}
|
||||
|
||||
private static int getAccessRadius0(final ChunkStatus genStatus) {
|
||||
if (genStatus == ChunkStatus.EMPTY) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
final int radius = Math.max(((ChunkSystemChunkStatus)genStatus).moonrise$getLoadRadius(), genStatus.getRange());
|
||||
int maxRange = radius;
|
||||
|
||||
for (int dist = 1; dist <= radius; ++dist) {
|
||||
final ChunkStatus requiredNeighbourStatus = ChunkStatus.getStatusAroundFullChunk(ChunkStatus.getDistance(genStatus) + dist);
|
||||
final int rad = ACCESS_RADIUS_TABLE[requiredNeighbourStatus.getIndex()];
|
||||
if (rad == -1) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
|
||||
maxRange = Math.max(maxRange, dist + rad);
|
||||
}
|
||||
|
||||
return maxRange;
|
||||
}
|
||||
|
||||
private static int maxAccessRadius;
|
||||
|
||||
static {
|
||||
final List<ChunkStatus> statuses = ChunkStatus.getStatusList();
|
||||
for (int i = 0, len = statuses.size(); i < len; ++i) {
|
||||
ACCESS_RADIUS_TABLE[i] = getAccessRadius0(statuses.get(i));
|
||||
}
|
||||
int max = 0;
|
||||
for (int i = 0, len = statuses.size(); i < len; ++i) {
|
||||
MAX_ACCESS_RADIUS_TABLE[i] = max = Math.max(ACCESS_RADIUS_TABLE[i], max);
|
||||
}
|
||||
maxAccessRadius = max;
|
||||
}
|
||||
|
||||
public static int getMaxAccessRadius() {
|
||||
return maxAccessRadius;
|
||||
}
|
||||
|
||||
public static int getAccessRadius(final ChunkStatus genStatus) {
|
||||
return ACCESS_RADIUS_TABLE[genStatus.getIndex()];
|
||||
}
|
||||
|
||||
public static int getAccessRadius(final FullChunkStatus status) {
|
||||
return (status.ordinal() - 1) + getAccessRadius(ChunkStatus.FULL);
|
||||
}
|
||||
|
||||
|
||||
public final ReentrantAreaLock schedulingLockArea;
|
||||
private final int lockShift;
|
||||
|
||||
public final int getChunkSystemLockShift() {
|
||||
return this.lockShift;
|
||||
}
|
||||
|
||||
public ChunkTaskScheduler(final ServerLevel world, final PrioritisedThreadPool workers) {
|
||||
this.world = world;
|
||||
this.workers = workers;
|
||||
// must be >= region shift (in paper, doesn't exist) and must be >= ticket propagator section shift
|
||||
// it must be >= region shift since the regioniser assumes ticket updates do not occur in parallel for the region sections
|
||||
// it must be >= ticket propagator section shift so that the ticket propagator can assume that owning a position implies owning
|
||||
// the entire section
|
||||
// we just take the max, as we want the smallest shift that satisfies these properties
|
||||
this.lockShift = Math.max(((ChunkSystemServerLevel)world).moonrise$getRegionChunkShift(), ThreadedTicketLevelPropagator.SECTION_SHIFT);
|
||||
this.schedulingLockArea = new ReentrantAreaLock(this.getChunkSystemLockShift());
|
||||
|
||||
final String worldName = WorldUtil.getWorldName(world);
|
||||
this.parallelGenExecutor = workers.createExecutor("Chunk parallel generation executor for world '" + worldName + "'", 1, Math.max(1, newChunkSystemGenParallelism));
|
||||
this.radiusAwareGenExecutor =
|
||||
newChunkSystemGenParallelism <= 1 ? this.parallelGenExecutor : workers.createExecutor("Chunk radius aware generator for world '" + worldName + "'", 1, newChunkSystemGenParallelism);
|
||||
this.loadExecutor = workers.createExecutor("Chunk load executor for world '" + worldName + "'", 1, newChunkSystemLoadParallelism);
|
||||
this.radiusAwareScheduler = new RadiusAwarePrioritisedExecutor(this.radiusAwareGenExecutor, Math.max(1, newChunkSystemGenParallelism));
|
||||
this.chunkHolderManager = new ChunkHolderManager(world, this);
|
||||
}
|
||||
|
||||
private final AtomicBoolean failedChunkSystem = new AtomicBoolean();
|
||||
|
||||
public static Object stringIfNull(final Object obj) {
|
||||
return obj == null ? "null" : obj;
|
||||
}
|
||||
|
||||
public void unrecoverableChunkSystemFailure(final int chunkX, final int chunkZ, final Map<String, Object> objectsOfInterest, final Throwable thr) {
|
||||
final NewChunkHolder holder = this.chunkHolderManager.getChunkHolder(chunkX, chunkZ);
|
||||
LOGGER.error("Chunk system error at chunk (" + chunkX + "," + chunkZ + "), holder: " + holder + ", exception:", new Throwable(thr));
|
||||
|
||||
if (this.failedChunkSystem.getAndSet(true)) {
|
||||
return;
|
||||
}
|
||||
|
||||
final ReportedException reportedException = thr instanceof ReportedException ? (ReportedException)thr : new ReportedException(new CrashReport("Chunk system error", thr));
|
||||
|
||||
CrashReportCategory crashReportCategory = reportedException.getReport().addCategory("Chunk system details");
|
||||
crashReportCategory.setDetail("Chunk coordinate", new ChunkPos(chunkX, chunkZ).toString());
|
||||
crashReportCategory.setDetail("ChunkHolder", Objects.toString(holder));
|
||||
crashReportCategory.setDetail("unrecoverableChunkSystemFailure caller thread", Thread.currentThread().getName());
|
||||
|
||||
crashReportCategory = reportedException.getReport().addCategory("Chunk System Objects of Interest");
|
||||
for (final Map.Entry<String, Object> entry : objectsOfInterest.entrySet()) {
|
||||
if (entry.getValue() instanceof Throwable thrObject) {
|
||||
crashReportCategory.setDetailError(Objects.toString(entry.getKey()), thrObject);
|
||||
} else {
|
||||
crashReportCategory.setDetail(Objects.toString(entry.getKey()), Objects.toString(entry.getValue()));
|
||||
}
|
||||
}
|
||||
|
||||
final Runnable crash = () -> {
|
||||
throw new RuntimeException("Chunk system crash propagated from unrecoverableChunkSystemFailure", reportedException);
|
||||
};
|
||||
|
||||
// this may not be good enough, specifically thanks to stupid ass plugins swallowing exceptions
|
||||
this.scheduleChunkTask(chunkX, chunkZ, crash, PrioritisedExecutor.Priority.BLOCKING);
|
||||
// so, make the main thread pick it up
|
||||
((ChunkSystemMinecraftServer)this.world.getServer()).moonrise$setChunkSystemCrash(new RuntimeException("Chunk system crash propagated from unrecoverableChunkSystemFailure", reportedException));
|
||||
}
|
||||
|
||||
public boolean executeMainThreadTask() {
|
||||
TickThread.ensureTickThread("Cannot execute main thread task off-main");
|
||||
return this.mainThreadExecutor.executeTask();
|
||||
}
|
||||
|
||||
public void raisePriority(final int x, final int z, final PrioritisedExecutor.Priority priority) {
|
||||
this.chunkHolderManager.raisePriority(x, z, priority);
|
||||
}
|
||||
|
||||
public void setPriority(final int x, final int z, final PrioritisedExecutor.Priority priority) {
|
||||
this.chunkHolderManager.setPriority(x, z, priority);
|
||||
}
|
||||
|
||||
public void lowerPriority(final int x, final int z, final PrioritisedExecutor.Priority priority) {
|
||||
this.chunkHolderManager.lowerPriority(x, z, priority);
|
||||
}
|
||||
|
||||
public void scheduleTickingState(final int chunkX, final int chunkZ, final FullChunkStatus toStatus,
|
||||
final boolean addTicket, final PrioritisedExecutor.Priority priority,
|
||||
final Consumer<LevelChunk> onComplete) {
|
||||
if (!TickThread.isTickThread()) {
|
||||
this.scheduleChunkTask(chunkX, chunkZ, () -> {
|
||||
ChunkTaskScheduler.this.scheduleTickingState(chunkX, chunkZ, toStatus, addTicket, priority, onComplete);
|
||||
}, priority);
|
||||
return;
|
||||
}
|
||||
final int accessRadius = getAccessRadius(toStatus);
|
||||
if (this.chunkHolderManager.ticketLockArea.isHeldByCurrentThread(chunkX, chunkZ, accessRadius)) {
|
||||
throw new IllegalStateException("Cannot schedule chunk load during ticket level update");
|
||||
}
|
||||
if (this.schedulingLockArea.isHeldByCurrentThread(chunkX, chunkZ, accessRadius)) {
|
||||
throw new IllegalStateException("Cannot schedule chunk loading recursively");
|
||||
}
|
||||
|
||||
if (toStatus == FullChunkStatus.INACCESSIBLE) {
|
||||
throw new IllegalArgumentException("Cannot wait for INACCESSIBLE status");
|
||||
}
|
||||
|
||||
final int minLevel = 33 - (toStatus.ordinal() - 1);
|
||||
final Long chunkReference = addTicket ? getNextChunkLoadId() : null;
|
||||
final long chunkKey = CoordinateUtils.getChunkKey(chunkX, chunkZ);
|
||||
|
||||
if (addTicket) {
|
||||
this.chunkHolderManager.addTicketAtLevel(CHUNK_LOAD, chunkKey, minLevel, chunkReference);
|
||||
this.chunkHolderManager.processTicketUpdates();
|
||||
}
|
||||
|
||||
final Consumer<LevelChunk> loadCallback = (final LevelChunk chunk) -> {
|
||||
try {
|
||||
if (onComplete != null) {
|
||||
onComplete.accept(chunk);
|
||||
}
|
||||
} finally {
|
||||
if (addTicket) {
|
||||
ChunkTaskScheduler.this.chunkHolderManager.removeTicketAtLevel(CHUNK_LOAD, chunkKey, minLevel, chunkReference);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
final boolean scheduled;
|
||||
final LevelChunk chunk;
|
||||
final ReentrantAreaLock.Node ticketLock = this.chunkHolderManager.ticketLockArea.lock(chunkX, chunkZ, accessRadius);
|
||||
try {
|
||||
final ReentrantAreaLock.Node schedulingLock = this.schedulingLockArea.lock(chunkX, chunkZ, accessRadius);
|
||||
try {
|
||||
final NewChunkHolder chunkHolder = this.chunkHolderManager.getChunkHolder(chunkKey);
|
||||
if (chunkHolder == null || chunkHolder.getTicketLevel() > minLevel) {
|
||||
scheduled = false;
|
||||
chunk = null;
|
||||
} else {
|
||||
final FullChunkStatus currStatus = chunkHolder.getChunkStatus();
|
||||
if (currStatus.isOrAfter(toStatus)) {
|
||||
scheduled = false;
|
||||
chunk = (LevelChunk)chunkHolder.getCurrentChunk();
|
||||
} else {
|
||||
scheduled = true;
|
||||
chunk = null;
|
||||
|
||||
final int radius = toStatus.ordinal() - 1; // 0 -> BORDER, 1 -> TICKING, 2 -> ENTITY_TICKING
|
||||
for (int dz = -radius; dz <= radius; ++dz) {
|
||||
for (int dx = -radius; dx <= radius; ++dx) {
|
||||
final NewChunkHolder neighbour =
|
||||
(dx | dz) == 0 ? chunkHolder : this.chunkHolderManager.getChunkHolder(dx + chunkX, dz + chunkZ);
|
||||
if (neighbour != null) {
|
||||
neighbour.raisePriority(priority);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ticket level should schedule for us
|
||||
chunkHolder.addFullStatusConsumer(toStatus, loadCallback);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
this.schedulingLockArea.unlock(schedulingLock);
|
||||
}
|
||||
} finally {
|
||||
this.chunkHolderManager.ticketLockArea.unlock(ticketLock);
|
||||
}
|
||||
|
||||
if (!scheduled) {
|
||||
// couldn't schedule
|
||||
try {
|
||||
loadCallback.accept(chunk);
|
||||
} catch (final Throwable thr) {
|
||||
LOGGER.error("Failed to process chunk full status callback", thr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void scheduleChunkLoad(final int chunkX, final int chunkZ, final boolean gen, final ChunkStatus toStatus, final boolean addTicket,
|
||||
final PrioritisedExecutor.Priority priority, final Consumer<ChunkAccess> onComplete) {
|
||||
if (gen) {
|
||||
this.scheduleChunkLoad(chunkX, chunkZ, toStatus, addTicket, priority, onComplete);
|
||||
return;
|
||||
}
|
||||
this.scheduleChunkLoad(chunkX, chunkZ, ChunkStatus.EMPTY, addTicket, priority, (final ChunkAccess chunk) -> {
|
||||
if (chunk == null) {
|
||||
onComplete.accept(null);
|
||||
} else {
|
||||
if (chunk.getStatus().isOrAfter(toStatus)) {
|
||||
this.scheduleChunkLoad(chunkX, chunkZ, toStatus, addTicket, priority, onComplete);
|
||||
} else {
|
||||
onComplete.accept(null);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// only appropriate to use with syncLoadNonFull
|
||||
public boolean beginChunkLoadForNonFullSync(final int chunkX, final int chunkZ, final ChunkStatus toStatus,
|
||||
final PrioritisedExecutor.Priority priority) {
|
||||
final int accessRadius = getAccessRadius(toStatus);
|
||||
final long chunkKey = CoordinateUtils.getChunkKey(chunkX, chunkZ);
|
||||
final int minLevel = ChunkTaskScheduler.getTicketLevel(toStatus);
|
||||
final List<ChunkProgressionTask> tasks = new ArrayList<>();
|
||||
final ReentrantAreaLock.Node ticketLock = this.chunkHolderManager.ticketLockArea.lock(chunkX, chunkZ, accessRadius); // Folia - use area based lock to reduce contention
|
||||
try {
|
||||
final ReentrantAreaLock.Node schedulingLock = this.schedulingLockArea.lock(chunkX, chunkZ, accessRadius); // Folia - use area based lock to reduce contention
|
||||
try {
|
||||
final NewChunkHolder chunkHolder = this.chunkHolderManager.getChunkHolder(chunkKey);
|
||||
if (chunkHolder == null || chunkHolder.getTicketLevel() > minLevel) {
|
||||
return false;
|
||||
} else {
|
||||
final ChunkStatus genStatus = chunkHolder.getCurrentGenStatus();
|
||||
if (genStatus != null && genStatus.isOrAfter(toStatus)) {
|
||||
return true;
|
||||
} else {
|
||||
chunkHolder.raisePriority(priority);
|
||||
|
||||
if (!chunkHolder.upgradeGenTarget(toStatus)) {
|
||||
this.schedule(chunkX, chunkZ, toStatus, chunkHolder, tasks);
|
||||
}
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
this.schedulingLockArea.unlock(schedulingLock);
|
||||
}
|
||||
} finally {
|
||||
this.chunkHolderManager.ticketLockArea.unlock(ticketLock);
|
||||
}
|
||||
|
||||
for (int i = 0, len = tasks.size(); i < len; ++i) {
|
||||
tasks.get(i).schedule();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Note: on Moonrise the non-full sync load requires blocking on managedBlock, but this is fine since there is only
|
||||
// one main thread. On Folia, it is required that the non-full load can occur completely asynchronously to avoid deadlock
|
||||
// between regions
|
||||
public ChunkAccess syncLoadNonFull(final int chunkX, final int chunkZ, final ChunkStatus status) {
|
||||
if (status == null || status.isOrAfter(ChunkStatus.FULL)) {
|
||||
throw new IllegalArgumentException("Status: " + status);
|
||||
}
|
||||
ChunkAccess loaded = ((ChunkSystemServerLevel)this.world).moonrise$getSpecificChunkIfLoaded(chunkX, chunkZ, status);
|
||||
if (loaded != null) {
|
||||
return loaded;
|
||||
}
|
||||
|
||||
final Long ticketId = getNextNonFullLoadId();
|
||||
final int ticketLevel = getTicketLevel(status);
|
||||
this.chunkHolderManager.addTicketAtLevel(NON_FULL_CHUNK_LOAD, chunkX, chunkZ, ticketLevel, ticketId);
|
||||
this.chunkHolderManager.processTicketUpdates();
|
||||
|
||||
this.beginChunkLoadForNonFullSync(chunkX, chunkZ, status, PrioritisedExecutor.Priority.BLOCKING);
|
||||
|
||||
// we could do a simple spinwait here, since we do not need to process tasks while performing this load
|
||||
// but we process tasks only because it's a better use of the time spent
|
||||
this.world.getChunkSource().mainThreadProcessor.managedBlock(() -> {
|
||||
return ((ChunkSystemServerLevel)this.world).moonrise$getSpecificChunkIfLoaded(chunkX, chunkZ, status) != null;
|
||||
});
|
||||
|
||||
loaded = ((ChunkSystemServerLevel)this.world).moonrise$getSpecificChunkIfLoaded(chunkX, chunkZ, status);
|
||||
|
||||
this.chunkHolderManager.removeTicketAtLevel(NON_FULL_CHUNK_LOAD, chunkX, chunkZ, ticketLevel, ticketId);
|
||||
|
||||
if (loaded == null) {
|
||||
throw new IllegalStateException("Expected chunk to be loaded for status " + status);
|
||||
}
|
||||
|
||||
return loaded;
|
||||
}
|
||||
|
||||
public void scheduleChunkLoad(final int chunkX, final int chunkZ, final ChunkStatus toStatus, final boolean addTicket,
|
||||
final PrioritisedExecutor.Priority priority, final Consumer<ChunkAccess> onComplete) {
|
||||
if (!TickThread.isTickThread()) {
|
||||
this.scheduleChunkTask(chunkX, chunkZ, () -> {
|
||||
ChunkTaskScheduler.this.scheduleChunkLoad(chunkX, chunkZ, toStatus, addTicket, priority, onComplete);
|
||||
}, priority);
|
||||
return;
|
||||
}
|
||||
final int accessRadius = getAccessRadius(toStatus);
|
||||
if (this.chunkHolderManager.ticketLockArea.isHeldByCurrentThread(chunkX, chunkZ, accessRadius)) {
|
||||
throw new IllegalStateException("Cannot schedule chunk load during ticket level update");
|
||||
}
|
||||
if (this.schedulingLockArea.isHeldByCurrentThread(chunkX, chunkZ, accessRadius)) {
|
||||
throw new IllegalStateException("Cannot schedule chunk loading recursively");
|
||||
}
|
||||
|
||||
if (toStatus == ChunkStatus.FULL) {
|
||||
this.scheduleTickingState(chunkX, chunkZ, FullChunkStatus.FULL, addTicket, priority, (Consumer)onComplete);
|
||||
return;
|
||||
}
|
||||
|
||||
final int minLevel = ChunkTaskScheduler.getTicketLevel(toStatus);
|
||||
final Long chunkReference = addTicket ? getNextChunkLoadId() : null;
|
||||
final long chunkKey = CoordinateUtils.getChunkKey(chunkX, chunkZ);
|
||||
|
||||
if (addTicket) {
|
||||
this.chunkHolderManager.addTicketAtLevel(CHUNK_LOAD, chunkKey, minLevel, chunkReference);
|
||||
this.chunkHolderManager.processTicketUpdates();
|
||||
}
|
||||
|
||||
final Consumer<ChunkAccess> loadCallback = (final ChunkAccess chunk) -> {
|
||||
try {
|
||||
if (onComplete != null) {
|
||||
onComplete.accept(chunk);
|
||||
}
|
||||
} finally {
|
||||
if (addTicket) {
|
||||
ChunkTaskScheduler.this.chunkHolderManager.removeTicketAtLevel(CHUNK_LOAD, chunkKey, minLevel, chunkReference);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
final List<ChunkProgressionTask> tasks = new ArrayList<>();
|
||||
|
||||
final boolean scheduled;
|
||||
final ChunkAccess chunk;
|
||||
final ReentrantAreaLock.Node ticketLock = this.chunkHolderManager.ticketLockArea.lock(chunkX, chunkZ, accessRadius);
|
||||
try {
|
||||
final ReentrantAreaLock.Node schedulingLock = this.schedulingLockArea.lock(chunkX, chunkZ, accessRadius);
|
||||
try {
|
||||
final NewChunkHolder chunkHolder = this.chunkHolderManager.getChunkHolder(chunkKey);
|
||||
if (chunkHolder == null || chunkHolder.getTicketLevel() > minLevel) {
|
||||
scheduled = false;
|
||||
chunk = null;
|
||||
} else {
|
||||
final ChunkStatus genStatus = chunkHolder.getCurrentGenStatus();
|
||||
if (genStatus != null && genStatus.isOrAfter(toStatus)) {
|
||||
scheduled = false;
|
||||
chunk = chunkHolder.getCurrentChunk();
|
||||
} else {
|
||||
scheduled = true;
|
||||
chunk = null;
|
||||
chunkHolder.raisePriority(priority);
|
||||
|
||||
if (!chunkHolder.upgradeGenTarget(toStatus)) {
|
||||
this.schedule(chunkX, chunkZ, toStatus, chunkHolder, tasks);
|
||||
}
|
||||
chunkHolder.addStatusConsumer(toStatus, loadCallback);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
this.schedulingLockArea.unlock(schedulingLock);
|
||||
}
|
||||
} finally {
|
||||
this.chunkHolderManager.ticketLockArea.unlock(ticketLock);
|
||||
}
|
||||
|
||||
for (int i = 0, len = tasks.size(); i < len; ++i) {
|
||||
tasks.get(i).schedule();
|
||||
}
|
||||
|
||||
if (!scheduled) {
|
||||
// couldn't schedule
|
||||
try {
|
||||
loadCallback.accept(chunk);
|
||||
} catch (final Throwable thr) {
|
||||
LOGGER.error("Failed to process chunk status callback", thr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private ChunkProgressionTask createTask(final int chunkX, final int chunkZ, final ChunkAccess chunk,
|
||||
final NewChunkHolder chunkHolder, final List<ChunkAccess> neighbours,
|
||||
final ChunkStatus toStatus, final PrioritisedExecutor.Priority initialPriority) {
|
||||
if (toStatus == ChunkStatus.EMPTY) {
|
||||
return new ChunkLoadTask(this, this.world, chunkX, chunkZ, chunkHolder, initialPriority);
|
||||
}
|
||||
if (toStatus == ChunkStatus.LIGHT) {
|
||||
return new ChunkLightTask(this, this.world, chunkX, chunkZ, chunk, initialPriority);
|
||||
}
|
||||
if (toStatus == ChunkStatus.FULL) {
|
||||
return new ChunkFullTask(this, this.world, chunkX, chunkZ, chunkHolder, chunk, initialPriority);
|
||||
}
|
||||
|
||||
return new ChunkUpgradeGenericStatusTask(this, this.world, chunkX, chunkZ, chunk, neighbours, toStatus, initialPriority);
|
||||
}
|
||||
|
||||
ChunkProgressionTask schedule(final int chunkX, final int chunkZ, final ChunkStatus targetStatus, final NewChunkHolder chunkHolder,
|
||||
final List<ChunkProgressionTask> allTasks) {
|
||||
return this.schedule(chunkX, chunkZ, targetStatus, chunkHolder, allTasks, chunkHolder.getEffectivePriority(PrioritisedExecutor.Priority.NORMAL));
|
||||
}
|
||||
|
||||
// rets new task scheduled for the _specified_ chunk
|
||||
// note: this must hold the scheduling lock
|
||||
// minPriority is only used to pass the priority through to neighbours, as priority calculation has not yet been done
|
||||
// schedule will ignore the generation target, so it should be checked by the caller to ensure the target is not regressed!
|
||||
private ChunkProgressionTask schedule(final int chunkX, final int chunkZ, final ChunkStatus targetStatus,
|
||||
final NewChunkHolder chunkHolder, final List<ChunkProgressionTask> allTasks,
|
||||
final PrioritisedExecutor.Priority minPriority) {
|
||||
if (!this.schedulingLockArea.isHeldByCurrentThread(chunkX, chunkZ, getAccessRadius(targetStatus))) {
|
||||
throw new IllegalStateException("Not holding scheduling lock");
|
||||
}
|
||||
|
||||
if (chunkHolder.hasGenerationTask()) {
|
||||
chunkHolder.upgradeGenTarget(targetStatus);
|
||||
return null;
|
||||
}
|
||||
|
||||
final PrioritisedExecutor.Priority requestedPriority = PrioritisedExecutor.Priority.max(
|
||||
minPriority, chunkHolder.getEffectivePriority(PrioritisedExecutor.Priority.NORMAL)
|
||||
);
|
||||
final ChunkStatus currentGenStatus = chunkHolder.getCurrentGenStatus();
|
||||
final ChunkAccess chunk = chunkHolder.getCurrentChunk();
|
||||
|
||||
if (currentGenStatus == null) {
|
||||
// not yet loaded
|
||||
final ChunkProgressionTask task = this.createTask(
|
||||
chunkX, chunkZ, chunk, chunkHolder, Collections.emptyList(), ChunkStatus.EMPTY, requestedPriority
|
||||
);
|
||||
|
||||
allTasks.add(task);
|
||||
|
||||
final List<NewChunkHolder> chunkHolderNeighbours = new ArrayList<>(1);
|
||||
chunkHolderNeighbours.add(chunkHolder);
|
||||
|
||||
chunkHolder.setGenerationTarget(targetStatus);
|
||||
chunkHolder.setGenerationTask(task, ChunkStatus.EMPTY, chunkHolderNeighbours);
|
||||
|
||||
return task;
|
||||
}
|
||||
|
||||
if (currentGenStatus.isOrAfter(targetStatus)) {
|
||||
// nothing to do
|
||||
return null;
|
||||
}
|
||||
|
||||
// we know for sure now that we want to schedule _something_, so set the target
|
||||
chunkHolder.setGenerationTarget(targetStatus);
|
||||
|
||||
final ChunkStatus chunkRealStatus = chunk.getStatus();
|
||||
final ChunkStatus toStatus = ((ChunkSystemChunkStatus)currentGenStatus).moonrise$getNextStatus();
|
||||
|
||||
// if this chunk has already generated up to or past the specified status, then we don't
|
||||
// need the neighbours AT ALL.
|
||||
final int neighbourReadRadius = chunkRealStatus.isOrAfter(toStatus) ? ((ChunkSystemChunkStatus)toStatus).moonrise$getLoadRadius() : toStatus.getRange();
|
||||
|
||||
boolean unGeneratedNeighbours = false;
|
||||
|
||||
if (neighbourReadRadius > 0) {
|
||||
final ChunkMap chunkMap = this.world.getChunkSource().chunkMap;
|
||||
for (final long pos : ParallelSearchRadiusIteration.getSearchIteration(neighbourReadRadius)) {
|
||||
final int x = CoordinateUtils.getChunkX(pos);
|
||||
final int z = CoordinateUtils.getChunkZ(pos);
|
||||
final int radius = Math.max(Math.abs(x), Math.abs(z));
|
||||
final ChunkStatus requiredNeighbourStatus = chunkMap.getDependencyStatus(toStatus, radius);
|
||||
|
||||
unGeneratedNeighbours |= this.checkNeighbour(
|
||||
chunkX + x, chunkZ + z, requiredNeighbourStatus, chunkHolder, allTasks, requestedPriority
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (unGeneratedNeighbours) {
|
||||
// can't schedule, but neighbour completion will schedule for us when they're ALL done
|
||||
|
||||
// propagate our priority to neighbours
|
||||
chunkHolder.recalculateNeighbourPriorities();
|
||||
return null;
|
||||
}
|
||||
|
||||
// need to gather neighbours
|
||||
|
||||
final List<ChunkAccess> neighbours;
|
||||
final List<NewChunkHolder> chunkHolderNeighbours;
|
||||
if (neighbourReadRadius <= 0) {
|
||||
neighbours = new ArrayList<>(1);
|
||||
chunkHolderNeighbours = new ArrayList<>(1);
|
||||
neighbours.add(chunk);
|
||||
chunkHolderNeighbours.add(chunkHolder);
|
||||
} else {
|
||||
// the iteration order is _very_ important, as all generation statuses expect a certain order such that:
|
||||
// chunkAtRelative = neighbours.get(relX + relZ * (2 * radius + 1))
|
||||
neighbours = new ArrayList<>((2 * neighbourReadRadius + 1) * (2 * neighbourReadRadius + 1));
|
||||
chunkHolderNeighbours = new ArrayList<>((2 * neighbourReadRadius + 1) * (2 * neighbourReadRadius + 1));
|
||||
for (int dz = -neighbourReadRadius; dz <= neighbourReadRadius; ++dz) {
|
||||
for (int dx = -neighbourReadRadius; dx <= neighbourReadRadius; ++dx) {
|
||||
final NewChunkHolder holder = (dx | dz) == 0 ? chunkHolder : this.chunkHolderManager.getChunkHolder(dx + chunkX, dz + chunkZ);
|
||||
neighbours.add(holder.getChunkForNeighbourAccess());
|
||||
chunkHolderNeighbours.add(holder);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final ChunkProgressionTask task = this.createTask(
|
||||
chunkX, chunkZ, chunk, chunkHolder, neighbours, toStatus,
|
||||
chunkHolder.getEffectivePriority(PrioritisedExecutor.Priority.NORMAL)
|
||||
);
|
||||
allTasks.add(task);
|
||||
|
||||
chunkHolder.setGenerationTask(task, toStatus, chunkHolderNeighbours);
|
||||
|
||||
return task;
|
||||
}
|
||||
|
||||
// rets true if the neighbour is not at the required status, false otherwise
|
||||
private boolean checkNeighbour(final int chunkX, final int chunkZ, final ChunkStatus requiredStatus, final NewChunkHolder center,
|
||||
final List<ChunkProgressionTask> tasks, final PrioritisedExecutor.Priority minPriority) {
|
||||
final NewChunkHolder chunkHolder = this.chunkHolderManager.getChunkHolder(chunkX, chunkZ);
|
||||
|
||||
if (chunkHolder == null) {
|
||||
throw new IllegalStateException("Missing chunkholder when required");
|
||||
}
|
||||
|
||||
final ChunkStatus holderStatus = chunkHolder.getCurrentGenStatus();
|
||||
if (holderStatus != null && holderStatus.isOrAfter(requiredStatus)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (chunkHolder.hasFailedGeneration()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
center.addGenerationBlockingNeighbour(chunkHolder);
|
||||
chunkHolder.addWaitingNeighbour(center, requiredStatus);
|
||||
|
||||
if (chunkHolder.upgradeGenTarget(requiredStatus)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// not at status required, so we need to schedule its generation
|
||||
this.schedule(
|
||||
chunkX, chunkZ, requiredStatus, chunkHolder, tasks, minPriority
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Chunk tasks must be tied to coordinates in the future
|
||||
*/
|
||||
@Deprecated
|
||||
public PrioritisedExecutor.PrioritisedTask scheduleChunkTask(final Runnable run) {
|
||||
return this.scheduleChunkTask(run, PrioritisedExecutor.Priority.NORMAL);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Chunk tasks must be tied to coordinates in the future
|
||||
*/
|
||||
@Deprecated
|
||||
public PrioritisedExecutor.PrioritisedTask scheduleChunkTask(final Runnable run, final PrioritisedExecutor.Priority priority) {
|
||||
return this.mainThreadExecutor.queueRunnable(run, priority);
|
||||
}
|
||||
|
||||
public PrioritisedExecutor.PrioritisedTask createChunkTask(final int chunkX, final int chunkZ, final Runnable run) {
|
||||
return this.createChunkTask(chunkX, chunkZ, run, PrioritisedExecutor.Priority.NORMAL);
|
||||
}
|
||||
|
||||
public PrioritisedExecutor.PrioritisedTask createChunkTask(final int chunkX, final int chunkZ, final Runnable run,
|
||||
final PrioritisedExecutor.Priority priority) {
|
||||
return this.mainThreadExecutor.createTask(run, priority);
|
||||
}
|
||||
|
||||
public PrioritisedExecutor.PrioritisedTask scheduleChunkTask(final int chunkX, final int chunkZ, final Runnable run) {
|
||||
return this.mainThreadExecutor.queueRunnable(run);
|
||||
}
|
||||
|
||||
public PrioritisedExecutor.PrioritisedTask scheduleChunkTask(final int chunkX, final int chunkZ, final Runnable run,
|
||||
final PrioritisedExecutor.Priority priority) {
|
||||
return this.mainThreadExecutor.queueRunnable(run, priority);
|
||||
}
|
||||
|
||||
public boolean halt(final boolean sync, final long maxWaitNS) {
|
||||
this.radiusAwareGenExecutor.halt();
|
||||
this.parallelGenExecutor.halt();
|
||||
this.loadExecutor.halt();
|
||||
final long time = System.nanoTime();
|
||||
if (sync) {
|
||||
for (long failures = 9L;; failures = ConcurrentUtil.linearLongBackoff(failures, 500_000L, 50_000_000L)) {
|
||||
if (
|
||||
!this.radiusAwareGenExecutor.isActive() &&
|
||||
!this.parallelGenExecutor.isActive() &&
|
||||
!this.loadExecutor.isActive()
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
if ((System.nanoTime() - time) >= maxWaitNS) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static final ArrayDeque<ChunkInfo> WAITING_CHUNKS = new ArrayDeque<>(); // stack
|
||||
|
||||
public static final class ChunkInfo {
|
||||
|
||||
public final int chunkX;
|
||||
public final int chunkZ;
|
||||
public final ServerLevel world;
|
||||
|
||||
public ChunkInfo(final int chunkX, final int chunkZ, final ServerLevel world) {
|
||||
this.chunkX = chunkX;
|
||||
this.chunkZ = chunkZ;
|
||||
this.world = world;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "[( " + this.chunkX + "," + this.chunkZ + ") in '" + WorldUtil.getWorldName(this.world) + "']";
|
||||
}
|
||||
}
|
||||
|
||||
public static void pushChunkWait(final ServerLevel world, final int chunkX, final int chunkZ) {
|
||||
synchronized (WAITING_CHUNKS) {
|
||||
WAITING_CHUNKS.push(new ChunkInfo(chunkX, chunkZ, world));
|
||||
}
|
||||
}
|
||||
|
||||
public static void popChunkWait() {
|
||||
synchronized (WAITING_CHUNKS) {
|
||||
WAITING_CHUNKS.pop();
|
||||
}
|
||||
}
|
||||
|
||||
public static ChunkInfo[] getChunkInfos() {
|
||||
synchronized (WAITING_CHUNKS) {
|
||||
return WAITING_CHUNKS.toArray(new ChunkInfo[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,215 @@
|
||||
package ca.spottedleaf.moonrise.patches.chunk_system.scheduling;
|
||||
|
||||
import ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor;
|
||||
import ca.spottedleaf.concurrentutil.util.ConcurrentUtil;
|
||||
import java.lang.invoke.VarHandle;
|
||||
|
||||
public abstract class PriorityHolder {
|
||||
|
||||
protected volatile int priority;
|
||||
protected static final VarHandle PRIORITY_HANDLE = ConcurrentUtil.getVarHandle(PriorityHolder.class, "priority", int.class);
|
||||
|
||||
protected static final int PRIORITY_SCHEDULED = Integer.MIN_VALUE >>> 0;
|
||||
protected static final int PRIORITY_EXECUTED = Integer.MIN_VALUE >>> 1;
|
||||
|
||||
protected final int getPriorityVolatile() {
|
||||
return (int)PRIORITY_HANDLE.getVolatile((PriorityHolder)this);
|
||||
}
|
||||
|
||||
protected final int compareAndExchangePriorityVolatile(final int expect, final int update) {
|
||||
return (int)PRIORITY_HANDLE.compareAndExchange((PriorityHolder)this, (int)expect, (int)update);
|
||||
}
|
||||
|
||||
protected final int getAndOrPriorityVolatile(final int val) {
|
||||
return (int)PRIORITY_HANDLE.getAndBitwiseOr((PriorityHolder)this, (int)val);
|
||||
}
|
||||
|
||||
protected final void setPriorityPlain(final int val) {
|
||||
PRIORITY_HANDLE.set((PriorityHolder)this, (int)val);
|
||||
}
|
||||
|
||||
protected PriorityHolder(final PrioritisedExecutor.Priority priority) {
|
||||
if (!PrioritisedExecutor.Priority.isValidPriority(priority)) {
|
||||
throw new IllegalArgumentException("Invalid priority " + priority);
|
||||
}
|
||||
this.setPriorityPlain(priority.priority);
|
||||
}
|
||||
|
||||
// used only for debug json
|
||||
public boolean isScheduled() {
|
||||
return (this.getPriorityVolatile() & PRIORITY_SCHEDULED) != 0;
|
||||
}
|
||||
|
||||
// returns false if cancelled
|
||||
public boolean markExecuting() {
|
||||
return (this.getAndOrPriorityVolatile(PRIORITY_EXECUTED) & PRIORITY_EXECUTED) == 0;
|
||||
}
|
||||
|
||||
public boolean isMarkedExecuted() {
|
||||
return (this.getPriorityVolatile() & PRIORITY_EXECUTED) != 0;
|
||||
}
|
||||
|
||||
public void cancel() {
|
||||
if ((this.getAndOrPriorityVolatile(PRIORITY_EXECUTED) & PRIORITY_EXECUTED) != 0) {
|
||||
// cancelled already
|
||||
return;
|
||||
}
|
||||
this.cancelScheduled();
|
||||
}
|
||||
|
||||
public void schedule() {
|
||||
int priority = this.getPriorityVolatile();
|
||||
|
||||
if ((priority & PRIORITY_SCHEDULED) != 0) {
|
||||
throw new IllegalStateException("schedule() called twice");
|
||||
}
|
||||
|
||||
if ((priority & PRIORITY_EXECUTED) != 0) {
|
||||
// cancelled
|
||||
return;
|
||||
}
|
||||
|
||||
this.scheduleTask(PrioritisedExecutor.Priority.getPriority(priority));
|
||||
|
||||
int failures = 0;
|
||||
for (;;) {
|
||||
if (priority == (priority = this.compareAndExchangePriorityVolatile(priority, priority | PRIORITY_SCHEDULED))) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ((priority & PRIORITY_SCHEDULED) != 0) {
|
||||
throw new IllegalStateException("schedule() called twice");
|
||||
}
|
||||
|
||||
if ((priority & PRIORITY_EXECUTED) != 0) {
|
||||
// cancelled or executed
|
||||
return;
|
||||
}
|
||||
|
||||
this.setPriorityScheduled(PrioritisedExecutor.Priority.getPriority(priority));
|
||||
|
||||
++failures;
|
||||
for (int i = 0; i < failures; ++i) {
|
||||
ConcurrentUtil.backoff();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public final PrioritisedExecutor.Priority getPriority() {
|
||||
final int ret = this.getPriorityVolatile();
|
||||
if ((ret & PRIORITY_EXECUTED) != 0) {
|
||||
return PrioritisedExecutor.Priority.COMPLETING;
|
||||
}
|
||||
if ((ret & PRIORITY_SCHEDULED) != 0) {
|
||||
return this.getScheduledPriority();
|
||||
}
|
||||
return PrioritisedExecutor.Priority.getPriority(ret);
|
||||
}
|
||||
|
||||
public final void lowerPriority(final PrioritisedExecutor.Priority priority) {
|
||||
if (!PrioritisedExecutor.Priority.isValidPriority(priority)) {
|
||||
throw new IllegalArgumentException("Invalid priority " + priority);
|
||||
}
|
||||
|
||||
int failures = 0;
|
||||
for (int curr = this.getPriorityVolatile();;) {
|
||||
if ((curr & PRIORITY_EXECUTED) != 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ((curr & PRIORITY_SCHEDULED) != 0) {
|
||||
this.lowerPriorityScheduled(priority);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!priority.isLowerPriority(curr)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (curr == (curr = this.compareAndExchangePriorityVolatile(curr, priority.priority))) {
|
||||
return;
|
||||
}
|
||||
|
||||
// failed, retry
|
||||
|
||||
++failures;
|
||||
for (int i = 0; i < failures; ++i) {
|
||||
ConcurrentUtil.backoff();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public final void setPriority(final PrioritisedExecutor.Priority priority) {
|
||||
if (!PrioritisedExecutor.Priority.isValidPriority(priority)) {
|
||||
throw new IllegalArgumentException("Invalid priority " + priority);
|
||||
}
|
||||
|
||||
int failures = 0;
|
||||
for (int curr = this.getPriorityVolatile();;) {
|
||||
if ((curr & PRIORITY_EXECUTED) != 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ((curr & PRIORITY_SCHEDULED) != 0) {
|
||||
this.setPriorityScheduled(priority);
|
||||
return;
|
||||
}
|
||||
|
||||
if (curr == (curr = this.compareAndExchangePriorityVolatile(curr, priority.priority))) {
|
||||
return;
|
||||
}
|
||||
|
||||
// failed, retry
|
||||
|
||||
++failures;
|
||||
for (int i = 0; i < failures; ++i) {
|
||||
ConcurrentUtil.backoff();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public final void raisePriority(final PrioritisedExecutor.Priority priority) {
|
||||
if (!PrioritisedExecutor.Priority.isValidPriority(priority)) {
|
||||
throw new IllegalArgumentException("Invalid priority " + priority);
|
||||
}
|
||||
|
||||
int failures = 0;
|
||||
for (int curr = this.getPriorityVolatile();;) {
|
||||
if ((curr & PRIORITY_EXECUTED) != 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ((curr & PRIORITY_SCHEDULED) != 0) {
|
||||
this.raisePriorityScheduled(priority);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!priority.isHigherPriority(curr)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (curr == (curr = this.compareAndExchangePriorityVolatile(curr, priority.priority))) {
|
||||
return;
|
||||
}
|
||||
|
||||
// failed, retry
|
||||
|
||||
++failures;
|
||||
for (int i = 0; i < failures; ++i) {
|
||||
ConcurrentUtil.backoff();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract void cancelScheduled();
|
||||
|
||||
protected abstract PrioritisedExecutor.Priority getScheduledPriority();
|
||||
|
||||
protected abstract void scheduleTask(final PrioritisedExecutor.Priority priority);
|
||||
|
||||
protected abstract void lowerPriorityScheduled(final PrioritisedExecutor.Priority priority);
|
||||
|
||||
protected abstract void setPriorityScheduled(final PrioritisedExecutor.Priority priority);
|
||||
|
||||
protected abstract void raisePriorityScheduled(final PrioritisedExecutor.Priority priority);
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,668 @@
|
||||
package ca.spottedleaf.moonrise.patches.chunk_system.scheduling.executor;
|
||||
|
||||
import ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor;
|
||||
import ca.spottedleaf.moonrise.common.util.CoordinateUtils;
|
||||
import it.unimi.dsi.fastutil.longs.Long2ReferenceOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.PriorityQueue;
|
||||
|
||||
public class RadiusAwarePrioritisedExecutor {
|
||||
|
||||
private static final Comparator<DependencyNode> DEPENDENCY_NODE_COMPARATOR = (final DependencyNode t1, final DependencyNode t2) -> {
|
||||
return Long.compare(t1.id, t2.id);
|
||||
};
|
||||
|
||||
private final DependencyTree[] queues = new DependencyTree[PrioritisedExecutor.Priority.TOTAL_SCHEDULABLE_PRIORITIES];
|
||||
private static final int NO_TASKS_QUEUED = -1;
|
||||
private int selectedQueue = NO_TASKS_QUEUED;
|
||||
private boolean canQueueTasks = true;
|
||||
|
||||
public RadiusAwarePrioritisedExecutor(final PrioritisedExecutor executor, final int maxToSchedule) {
|
||||
for (int i = 0; i < this.queues.length; ++i) {
|
||||
this.queues[i] = new DependencyTree(this, executor, maxToSchedule, i);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean canQueueTasks() {
|
||||
return this.canQueueTasks;
|
||||
}
|
||||
|
||||
private List<PrioritisedExecutor.PrioritisedTask> treeFinished() {
|
||||
this.canQueueTasks = true;
|
||||
for (int priority = 0; priority < this.queues.length; ++priority) {
|
||||
final DependencyTree queue = this.queues[priority];
|
||||
if (queue.hasWaitingTasks()) {
|
||||
final List<PrioritisedExecutor.PrioritisedTask> ret = queue.tryPushTasks();
|
||||
|
||||
if (ret == null || ret.isEmpty()) {
|
||||
// this happens when the tasks in the wait queue were purged
|
||||
// in this case, the queue was actually empty, we just had to purge it
|
||||
// if we set the selected queue without scheduling any tasks, the queue will never be unselected
|
||||
// as that requires a scheduled task completing...
|
||||
continue;
|
||||
}
|
||||
|
||||
this.selectedQueue = priority;
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
this.selectedQueue = NO_TASKS_QUEUED;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private List<PrioritisedExecutor.PrioritisedTask> queue(final Task task, final PrioritisedExecutor.Priority priority) {
|
||||
final int priorityId = priority.priority;
|
||||
final DependencyTree queue = this.queues[priorityId];
|
||||
|
||||
final DependencyNode node = new DependencyNode(task, queue);
|
||||
|
||||
if (task.dependencyNode != null) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
task.dependencyNode = node;
|
||||
|
||||
queue.pushNode(node);
|
||||
|
||||
if (this.selectedQueue == NO_TASKS_QUEUED) {
|
||||
this.canQueueTasks = true;
|
||||
this.selectedQueue = priorityId;
|
||||
return queue.tryPushTasks();
|
||||
}
|
||||
|
||||
if (!this.canQueueTasks) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (PrioritisedExecutor.Priority.isHigherPriority(priorityId, this.selectedQueue)) {
|
||||
// prevent the lower priority tree from queueing more tasks
|
||||
this.canQueueTasks = false;
|
||||
return null;
|
||||
}
|
||||
|
||||
// priorityId != selectedQueue: lower priority, don't care - treeFinished will pick it up
|
||||
return priorityId == this.selectedQueue ? queue.tryPushTasks() : null;
|
||||
}
|
||||
|
||||
public PrioritisedExecutor.PrioritisedTask createTask(final int chunkX, final int chunkZ, final int radius,
|
||||
final Runnable run, final PrioritisedExecutor.Priority priority) {
|
||||
if (radius < 0) {
|
||||
throw new IllegalArgumentException("Radius must be > 0: " + radius);
|
||||
}
|
||||
return new Task(this, chunkX, chunkZ, radius, run, priority);
|
||||
}
|
||||
|
||||
public PrioritisedExecutor.PrioritisedTask createTask(final int chunkX, final int chunkZ, final int radius,
|
||||
final Runnable run) {
|
||||
return this.createTask(chunkX, chunkZ, radius, run, PrioritisedExecutor.Priority.NORMAL);
|
||||
}
|
||||
|
||||
public PrioritisedExecutor.PrioritisedTask queueTask(final int chunkX, final int chunkZ, final int radius,
|
||||
final Runnable run, final PrioritisedExecutor.Priority priority) {
|
||||
final PrioritisedExecutor.PrioritisedTask ret = this.createTask(chunkX, chunkZ, radius, run, priority);
|
||||
|
||||
ret.queue();
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
public PrioritisedExecutor.PrioritisedTask queueTask(final int chunkX, final int chunkZ, final int radius,
|
||||
final Runnable run) {
|
||||
final PrioritisedExecutor.PrioritisedTask ret = this.createTask(chunkX, chunkZ, radius, run);
|
||||
|
||||
ret.queue();
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
public PrioritisedExecutor.PrioritisedTask createInfiniteRadiusTask(final Runnable run, final PrioritisedExecutor.Priority priority) {
|
||||
return new Task(this, 0, 0, -1, run, priority);
|
||||
}
|
||||
|
||||
public PrioritisedExecutor.PrioritisedTask createInfiniteRadiusTask(final Runnable run) {
|
||||
return this.createInfiniteRadiusTask(run, PrioritisedExecutor.Priority.NORMAL);
|
||||
}
|
||||
|
||||
public PrioritisedExecutor.PrioritisedTask queueInfiniteRadiusTask(final Runnable run, final PrioritisedExecutor.Priority priority) {
|
||||
final PrioritisedExecutor.PrioritisedTask ret = this.createInfiniteRadiusTask(run, priority);
|
||||
|
||||
ret.queue();
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
public PrioritisedExecutor.PrioritisedTask queueInfiniteRadiusTask(final Runnable run) {
|
||||
final PrioritisedExecutor.PrioritisedTask ret = this.createInfiniteRadiusTask(run, PrioritisedExecutor.Priority.NORMAL);
|
||||
|
||||
ret.queue();
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
// all accesses must be synchronised by the radius aware object
|
||||
private static final class DependencyTree {
|
||||
|
||||
private final RadiusAwarePrioritisedExecutor scheduler;
|
||||
private final PrioritisedExecutor executor;
|
||||
private final int maxToSchedule;
|
||||
private final int treeIndex;
|
||||
|
||||
private int currentlyExecuting;
|
||||
private long idGenerator;
|
||||
|
||||
private final PriorityQueue<DependencyNode> awaiting = new PriorityQueue<>(DEPENDENCY_NODE_COMPARATOR);
|
||||
|
||||
private final PriorityQueue<DependencyNode> infiniteRadius = new PriorityQueue<>(DEPENDENCY_NODE_COMPARATOR);
|
||||
private boolean isInfiniteRadiusScheduled;
|
||||
|
||||
private final Long2ReferenceOpenHashMap<DependencyNode> nodeByPosition = new Long2ReferenceOpenHashMap<>();
|
||||
|
||||
public DependencyTree(final RadiusAwarePrioritisedExecutor scheduler, final PrioritisedExecutor executor,
|
||||
final int maxToSchedule, final int treeIndex) {
|
||||
this.scheduler = scheduler;
|
||||
this.executor = executor;
|
||||
this.maxToSchedule = maxToSchedule;
|
||||
this.treeIndex = treeIndex;
|
||||
}
|
||||
|
||||
public boolean hasWaitingTasks() {
|
||||
return !this.awaiting.isEmpty() || !this.infiniteRadius.isEmpty();
|
||||
}
|
||||
|
||||
private long nextId() {
|
||||
return this.idGenerator++;
|
||||
}
|
||||
|
||||
private boolean isExecutingAnyTasks() {
|
||||
return this.currentlyExecuting != 0;
|
||||
}
|
||||
|
||||
private void pushNode(final DependencyNode node) {
|
||||
if (!node.task.isFiniteRadius()) {
|
||||
this.infiniteRadius.add(node);
|
||||
return;
|
||||
}
|
||||
|
||||
// set up dependency for node
|
||||
final Task task = node.task;
|
||||
|
||||
final int centerX = task.chunkX;
|
||||
final int centerZ = task.chunkZ;
|
||||
final int radius = task.radius;
|
||||
|
||||
final int minX = centerX - radius;
|
||||
final int maxX = centerX + radius;
|
||||
|
||||
final int minZ = centerZ - radius;
|
||||
final int maxZ = centerZ + radius;
|
||||
|
||||
ReferenceOpenHashSet<DependencyNode> parents = null;
|
||||
for (int currZ = minZ; currZ <= maxZ; ++currZ) {
|
||||
for (int currX = minX; currX <= maxX; ++currX) {
|
||||
final DependencyNode dependency = this.nodeByPosition.put(CoordinateUtils.getChunkKey(currX, currZ), node);
|
||||
if (dependency != null) {
|
||||
if (parents == null) {
|
||||
parents = new ReferenceOpenHashSet<>();
|
||||
}
|
||||
if (parents.add(dependency)) {
|
||||
// added a dependency, so we need to add as a child to the dependency
|
||||
if (dependency.children == null) {
|
||||
dependency.children = new ArrayList<>();
|
||||
}
|
||||
dependency.children.add(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (parents == null) {
|
||||
// no dependencies, add straight to awaiting
|
||||
this.awaiting.add(node);
|
||||
} else {
|
||||
node.parents = parents.size();
|
||||
// we will be added to awaiting once we have no parents
|
||||
}
|
||||
}
|
||||
|
||||
// called only when a node is returned after being executed
|
||||
private List<PrioritisedExecutor.PrioritisedTask> returnNode(final DependencyNode node) {
|
||||
final Task task = node.task;
|
||||
|
||||
// now that the task is completed, we can push its children to the awaiting queue
|
||||
this.pushChildren(node);
|
||||
|
||||
if (task.isFiniteRadius()) {
|
||||
// remove from dependency map
|
||||
this.removeNodeFromMap(node);
|
||||
} else {
|
||||
// mark as no longer executing infinite radius
|
||||
if (!this.isInfiniteRadiusScheduled) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
this.isInfiniteRadiusScheduled = false;
|
||||
}
|
||||
|
||||
// decrement executing count, we are done executing this task
|
||||
--this.currentlyExecuting;
|
||||
|
||||
if (this.currentlyExecuting == 0) {
|
||||
return this.scheduler.treeFinished();
|
||||
}
|
||||
|
||||
return this.scheduler.canQueueTasks() ? this.tryPushTasks() : null;
|
||||
}
|
||||
|
||||
private List<PrioritisedExecutor.PrioritisedTask> tryPushTasks() {
|
||||
// tasks are not queued, but only created here - we do hold the lock for the map
|
||||
List<PrioritisedExecutor.PrioritisedTask> ret = null;
|
||||
PrioritisedExecutor.PrioritisedTask pushedTask;
|
||||
while ((pushedTask = this.tryPushTask()) != null) {
|
||||
if (ret == null) {
|
||||
ret = new ArrayList<>();
|
||||
}
|
||||
ret.add(pushedTask);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
private void removeNodeFromMap(final DependencyNode node) {
|
||||
final Task task = node.task;
|
||||
|
||||
final int centerX = task.chunkX;
|
||||
final int centerZ = task.chunkZ;
|
||||
final int radius = task.radius;
|
||||
|
||||
final int minX = centerX - radius;
|
||||
final int maxX = centerX + radius;
|
||||
|
||||
final int minZ = centerZ - radius;
|
||||
final int maxZ = centerZ + radius;
|
||||
|
||||
for (int currZ = minZ; currZ <= maxZ; ++currZ) {
|
||||
for (int currX = minX; currX <= maxX; ++currX) {
|
||||
this.nodeByPosition.remove(CoordinateUtils.getChunkKey(currX, currZ), node);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void pushChildren(final DependencyNode node) {
|
||||
// add all the children that we can into awaiting
|
||||
final List<DependencyNode> children = node.children;
|
||||
if (children != null) {
|
||||
for (int i = 0, len = children.size(); i < len; ++i) {
|
||||
final DependencyNode child = children.get(i);
|
||||
int newParents = --child.parents;
|
||||
if (newParents == 0) {
|
||||
// no more dependents, we can push to awaiting
|
||||
// even if the child is purged, we need to push it so that its children will be pushed
|
||||
this.awaiting.add(child);
|
||||
} else if (newParents < 0) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private DependencyNode pollAwaiting() {
|
||||
final DependencyNode ret = this.awaiting.poll();
|
||||
if (ret == null) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (ret.parents != 0) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
|
||||
if (ret.purged) {
|
||||
// need to manually remove from state here
|
||||
this.pushChildren(ret);
|
||||
this.removeNodeFromMap(ret);
|
||||
} // else: delay children push until the task has finished
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
private DependencyNode pollInfinite() {
|
||||
return this.infiniteRadius.poll();
|
||||
}
|
||||
|
||||
public PrioritisedExecutor.PrioritisedTask tryPushTask() {
|
||||
if (this.currentlyExecuting >= this.maxToSchedule || this.isInfiniteRadiusScheduled) {
|
||||
return null;
|
||||
}
|
||||
|
||||
DependencyNode firstInfinite;
|
||||
while ((firstInfinite = this.infiniteRadius.peek()) != null && firstInfinite.purged) {
|
||||
this.pollInfinite();
|
||||
}
|
||||
|
||||
DependencyNode firstAwaiting;
|
||||
while ((firstAwaiting = this.awaiting.peek()) != null && firstAwaiting.purged) {
|
||||
this.pollAwaiting();
|
||||
}
|
||||
|
||||
if (firstInfinite == null && firstAwaiting == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// firstAwaiting compared to firstInfinite
|
||||
final int compare;
|
||||
|
||||
if (firstAwaiting == null) {
|
||||
// we choose first infinite, or infinite < awaiting
|
||||
compare = 1;
|
||||
} else if (firstInfinite == null) {
|
||||
// we choose first awaiting, or awaiting < infinite
|
||||
compare = -1;
|
||||
} else {
|
||||
compare = DEPENDENCY_NODE_COMPARATOR.compare(firstAwaiting, firstInfinite);
|
||||
}
|
||||
|
||||
if (compare >= 0) {
|
||||
if (this.currentlyExecuting != 0) {
|
||||
// don't queue infinite task while other tasks are executing in parallel
|
||||
return null;
|
||||
}
|
||||
++this.currentlyExecuting;
|
||||
this.pollInfinite();
|
||||
this.isInfiniteRadiusScheduled = true;
|
||||
return firstInfinite.task.pushTask(this.executor);
|
||||
} else {
|
||||
++this.currentlyExecuting;
|
||||
this.pollAwaiting();
|
||||
return firstAwaiting.task.pushTask(this.executor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static final class DependencyNode {
|
||||
|
||||
private final Task task;
|
||||
private final DependencyTree tree;
|
||||
|
||||
// dependency tree fields
|
||||
// (must hold lock on the scheduler to use)
|
||||
// null is the same as empty, we just use it so that we don't allocate the set unless we need to
|
||||
private List<DependencyNode> children;
|
||||
// 0 indicates that this task is considered "awaiting"
|
||||
private int parents;
|
||||
// false -> scheduled and not cancelled
|
||||
// true -> scheduled but cancelled
|
||||
private boolean purged;
|
||||
private final long id;
|
||||
|
||||
public DependencyNode(final Task task, final DependencyTree tree) {
|
||||
this.task = task;
|
||||
this.id = tree.nextId();
|
||||
this.tree = tree;
|
||||
}
|
||||
}
|
||||
|
||||
private static final class Task implements PrioritisedExecutor.PrioritisedTask, Runnable {
|
||||
|
||||
// task specific fields
|
||||
private final RadiusAwarePrioritisedExecutor scheduler;
|
||||
private final int chunkX;
|
||||
private final int chunkZ;
|
||||
private final int radius;
|
||||
private Runnable run;
|
||||
private PrioritisedExecutor.Priority priority;
|
||||
|
||||
private DependencyNode dependencyNode;
|
||||
private PrioritisedExecutor.PrioritisedTask queuedTask;
|
||||
|
||||
private Task(final RadiusAwarePrioritisedExecutor scheduler, final int chunkX, final int chunkZ, final int radius,
|
||||
final Runnable run, final PrioritisedExecutor.Priority priority) {
|
||||
this.scheduler = scheduler;
|
||||
this.chunkX = chunkX;
|
||||
this.chunkZ = chunkZ;
|
||||
this.radius = radius;
|
||||
this.run = run;
|
||||
this.priority = priority;
|
||||
}
|
||||
|
||||
private boolean isFiniteRadius() {
|
||||
return this.radius >= 0;
|
||||
}
|
||||
|
||||
private PrioritisedExecutor.PrioritisedTask pushTask(final PrioritisedExecutor executor) {
|
||||
return this.queuedTask = executor.createTask(this, this.priority);
|
||||
}
|
||||
|
||||
private void executeTask() {
|
||||
final Runnable run = this.run;
|
||||
this.run = null;
|
||||
run.run();
|
||||
}
|
||||
|
||||
private static void scheduleTasks(final List<PrioritisedExecutor.PrioritisedTask> toSchedule) {
|
||||
if (toSchedule != null) {
|
||||
for (int i = 0, len = toSchedule.size(); i < len; ++i) {
|
||||
toSchedule.get(i).queue();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void returnNode() {
|
||||
final List<PrioritisedExecutor.PrioritisedTask> toSchedule;
|
||||
synchronized (this.scheduler) {
|
||||
final DependencyNode node = this.dependencyNode;
|
||||
this.dependencyNode = null;
|
||||
toSchedule = node.tree.returnNode(node);
|
||||
}
|
||||
|
||||
scheduleTasks(toSchedule);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
final Runnable run = this.run;
|
||||
this.run = null;
|
||||
try {
|
||||
run.run();
|
||||
} finally {
|
||||
this.returnNode();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean queue() {
|
||||
final List<PrioritisedExecutor.PrioritisedTask> toSchedule;
|
||||
synchronized (this.scheduler) {
|
||||
if (this.queuedTask != null || this.dependencyNode != null || this.priority == PrioritisedExecutor.Priority.COMPLETING) {
|
||||
return false;
|
||||
}
|
||||
|
||||
toSchedule = this.scheduler.queue(this, this.priority);
|
||||
}
|
||||
|
||||
scheduleTasks(toSchedule);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean cancel() {
|
||||
final PrioritisedExecutor.PrioritisedTask task;
|
||||
synchronized (this.scheduler) {
|
||||
if ((task = this.queuedTask) == null) {
|
||||
if (this.priority == PrioritisedExecutor.Priority.COMPLETING) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this.priority = PrioritisedExecutor.Priority.COMPLETING;
|
||||
if (this.dependencyNode != null) {
|
||||
this.dependencyNode.purged = true;
|
||||
this.dependencyNode = null;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (task.cancel()) {
|
||||
// must manually return the node
|
||||
this.run = null;
|
||||
this.returnNode();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean execute() {
|
||||
final PrioritisedExecutor.PrioritisedTask task;
|
||||
synchronized (this.scheduler) {
|
||||
if ((task = this.queuedTask) == null) {
|
||||
if (this.priority == PrioritisedExecutor.Priority.COMPLETING) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this.priority = PrioritisedExecutor.Priority.COMPLETING;
|
||||
if (this.dependencyNode != null) {
|
||||
this.dependencyNode.purged = true;
|
||||
this.dependencyNode = null;
|
||||
}
|
||||
// fall through to execution logic
|
||||
}
|
||||
}
|
||||
|
||||
if (task != null) {
|
||||
// will run the return node logic automatically
|
||||
return task.execute();
|
||||
} else {
|
||||
// don't run node removal/insertion logic, we aren't actually removed from the dependency tree
|
||||
this.executeTask();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public PrioritisedExecutor.Priority getPriority() {
|
||||
final PrioritisedExecutor.PrioritisedTask task;
|
||||
synchronized (this.scheduler) {
|
||||
if ((task = this.queuedTask) == null) {
|
||||
return this.priority;
|
||||
}
|
||||
}
|
||||
|
||||
return task.getPriority();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean setPriority(final PrioritisedExecutor.Priority priority) {
|
||||
if (!PrioritisedExecutor.Priority.isValidPriority(priority)) {
|
||||
throw new IllegalArgumentException("Invalid priority " + priority);
|
||||
}
|
||||
|
||||
final PrioritisedExecutor.PrioritisedTask task;
|
||||
List<PrioritisedExecutor.PrioritisedTask> toSchedule = null;
|
||||
synchronized (this.scheduler) {
|
||||
if ((task = this.queuedTask) == null) {
|
||||
if (this.priority == PrioritisedExecutor.Priority.COMPLETING) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.priority == priority) {
|
||||
return true;
|
||||
}
|
||||
|
||||
this.priority = priority;
|
||||
if (this.dependencyNode != null) {
|
||||
// need to re-insert node
|
||||
this.dependencyNode.purged = true;
|
||||
this.dependencyNode = null;
|
||||
toSchedule = this.scheduler.queue(this, priority);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (task != null) {
|
||||
return task.setPriority(priority);
|
||||
}
|
||||
|
||||
scheduleTasks(toSchedule);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean raisePriority(final PrioritisedExecutor.Priority priority) {
|
||||
if (!PrioritisedExecutor.Priority.isValidPriority(priority)) {
|
||||
throw new IllegalArgumentException("Invalid priority " + priority);
|
||||
}
|
||||
|
||||
final PrioritisedExecutor.PrioritisedTask task;
|
||||
List<PrioritisedExecutor.PrioritisedTask> toSchedule = null;
|
||||
synchronized (this.scheduler) {
|
||||
if ((task = this.queuedTask) == null) {
|
||||
if (this.priority == PrioritisedExecutor.Priority.COMPLETING) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.priority.isHigherOrEqualPriority(priority)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
this.priority = priority;
|
||||
if (this.dependencyNode != null) {
|
||||
// need to re-insert node
|
||||
this.dependencyNode.purged = true;
|
||||
this.dependencyNode = null;
|
||||
toSchedule = this.scheduler.queue(this, priority);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (task != null) {
|
||||
return task.raisePriority(priority);
|
||||
}
|
||||
|
||||
scheduleTasks(toSchedule);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean lowerPriority(final PrioritisedExecutor.Priority priority) {
|
||||
if (!PrioritisedExecutor.Priority.isValidPriority(priority)) {
|
||||
throw new IllegalArgumentException("Invalid priority " + priority);
|
||||
}
|
||||
|
||||
final PrioritisedExecutor.PrioritisedTask task;
|
||||
List<PrioritisedExecutor.PrioritisedTask> toSchedule = null;
|
||||
synchronized (this.scheduler) {
|
||||
if ((task = this.queuedTask) == null) {
|
||||
if (this.priority == PrioritisedExecutor.Priority.COMPLETING) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.priority.isLowerOrEqualPriority(priority)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
this.priority = priority;
|
||||
if (this.dependencyNode != null) {
|
||||
// need to re-insert node
|
||||
this.dependencyNode.purged = true;
|
||||
this.dependencyNode = null;
|
||||
toSchedule = this.scheduler.queue(this, priority);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (task != null) {
|
||||
return task.lowerPriority(priority);
|
||||
}
|
||||
|
||||
scheduleTasks(toSchedule);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
package ca.spottedleaf.moonrise.patches.chunk_system.scheduling.task;
|
||||
|
||||
import ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor;
|
||||
import ca.spottedleaf.concurrentutil.util.ConcurrentUtil;
|
||||
import ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel;
|
||||
import ca.spottedleaf.moonrise.patches.chunk_system.level.poi.ChunkSystemPoiManager;
|
||||
import ca.spottedleaf.moonrise.patches.chunk_system.level.poi.PoiChunk;
|
||||
import ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkTaskScheduler;
|
||||
import ca.spottedleaf.moonrise.patches.chunk_system.scheduling.NewChunkHolder;
|
||||
import net.minecraft.server.level.ChunkMap;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.world.level.chunk.ChunkAccess;
|
||||
import net.minecraft.world.level.chunk.ImposterProtoChunk;
|
||||
import net.minecraft.world.level.chunk.LevelChunk;
|
||||
import net.minecraft.world.level.chunk.ProtoChunk;
|
||||
import net.minecraft.world.level.chunk.status.ChunkStatus;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import java.lang.invoke.VarHandle;
|
||||
|
||||
public final class ChunkFullTask extends ChunkProgressionTask implements Runnable {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(ChunkFullTask.class);
|
||||
|
||||
private final NewChunkHolder chunkHolder;
|
||||
private final ChunkAccess fromChunk;
|
||||
private final PrioritisedExecutor.PrioritisedTask convertToFullTask;
|
||||
|
||||
public ChunkFullTask(final ChunkTaskScheduler scheduler, final ServerLevel world, final int chunkX, final int chunkZ,
|
||||
final NewChunkHolder chunkHolder, final ChunkAccess fromChunk, final PrioritisedExecutor.Priority priority) {
|
||||
super(scheduler, world, chunkX, chunkZ);
|
||||
this.chunkHolder = chunkHolder;
|
||||
this.fromChunk = fromChunk;
|
||||
this.convertToFullTask = scheduler.createChunkTask(chunkX, chunkZ, this, priority);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChunkStatus getTargetStatus() {
|
||||
return ChunkStatus.FULL;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
// See Vanilla protoChunkToFullChunk for what this function should be doing
|
||||
final LevelChunk chunk;
|
||||
try {
|
||||
// moved from the load from nbt stage into here
|
||||
final PoiChunk poiChunk = this.chunkHolder.getPoiChunk();
|
||||
if (poiChunk == null) {
|
||||
LOGGER.error("Expected poi chunk to be loaded with chunk for task " + this.toString());
|
||||
} else {
|
||||
poiChunk.load();
|
||||
((ChunkSystemPoiManager)this.world.getPoiManager()).moonrise$checkConsistency(this.fromChunk);
|
||||
}
|
||||
|
||||
if (this.fromChunk instanceof ImposterProtoChunk wrappedFull) {
|
||||
chunk = wrappedFull.getWrapped();
|
||||
} else {
|
||||
final ServerLevel world = this.world;
|
||||
final ProtoChunk protoChunk = (ProtoChunk)this.fromChunk;
|
||||
chunk = new LevelChunk(this.world, protoChunk, (final LevelChunk unused) -> {
|
||||
ChunkMap.postLoadProtoChunk(world, protoChunk.getEntities());
|
||||
});
|
||||
}
|
||||
|
||||
final NewChunkHolder chunkHolder = this.chunkHolder;
|
||||
|
||||
chunk.setFullStatus(chunkHolder::getChunkStatus);
|
||||
chunk.runPostLoad();
|
||||
// Unlike Vanilla, we load the entity chunk here, as we load the NBT in empty status (unlike Vanilla)
|
||||
// This brings entity addition back in line with older versions of the game
|
||||
// Since we load the NBT in the empty status, this will never block for I/O
|
||||
((ChunkSystemServerLevel)this.world).moonrise$getChunkTaskScheduler().chunkHolderManager.getOrCreateEntityChunk(this.chunkX, this.chunkZ, false);
|
||||
|
||||
// we don't need the entitiesInLevel, not sure why it's there
|
||||
chunk.setLoaded(true);
|
||||
chunk.registerAllBlockEntitiesAfterLevelLoad();
|
||||
chunk.registerTickContainerInLevel(this.world);
|
||||
} catch (final Throwable throwable) {
|
||||
this.complete(null, throwable);
|
||||
return;
|
||||
}
|
||||
this.complete(chunk, null);
|
||||
}
|
||||
|
||||
protected volatile boolean scheduled;
|
||||
protected static final VarHandle SCHEDULED_HANDLE = ConcurrentUtil.getVarHandle(ChunkFullTask.class, "scheduled", boolean.class);
|
||||
|
||||
@Override
|
||||
public boolean isScheduled() {
|
||||
return this.scheduled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void schedule() {
|
||||
if ((boolean)SCHEDULED_HANDLE.getAndSet((ChunkFullTask)this, true)) {
|
||||
throw new IllegalStateException("Cannot double call schedule()");
|
||||
}
|
||||
this.convertToFullTask.queue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancel() {
|
||||
if (this.convertToFullTask.cancel()) {
|
||||
this.complete(null, null);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public PrioritisedExecutor.Priority getPriority() {
|
||||
return this.convertToFullTask.getPriority();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void lowerPriority(final PrioritisedExecutor.Priority priority) {
|
||||
if (!PrioritisedExecutor.Priority.isValidPriority(priority)) {
|
||||
throw new IllegalArgumentException("Invalid priority " + priority);
|
||||
}
|
||||
this.convertToFullTask.lowerPriority(priority);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPriority(final PrioritisedExecutor.Priority priority) {
|
||||
if (!PrioritisedExecutor.Priority.isValidPriority(priority)) {
|
||||
throw new IllegalArgumentException("Invalid priority " + priority);
|
||||
}
|
||||
this.convertToFullTask.setPriority(priority);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void raisePriority(final PrioritisedExecutor.Priority priority) {
|
||||
if (!PrioritisedExecutor.Priority.isValidPriority(priority)) {
|
||||
throw new IllegalArgumentException("Invalid priority " + priority);
|
||||
}
|
||||
this.convertToFullTask.raisePriority(priority);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,181 @@
|
||||
package ca.spottedleaf.moonrise.patches.chunk_system.scheduling.task;
|
||||
|
||||
import ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor;
|
||||
import ca.spottedleaf.moonrise.common.util.WorldUtil;
|
||||
import ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkTaskScheduler;
|
||||
import ca.spottedleaf.moonrise.patches.chunk_system.scheduling.PriorityHolder;
|
||||
import ca.spottedleaf.moonrise.patches.starlight.light.StarLightEngine;
|
||||
import ca.spottedleaf.moonrise.patches.starlight.light.StarLightInterface;
|
||||
import ca.spottedleaf.moonrise.patches.starlight.light.StarLightLightingProvider;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.world.level.ChunkPos;
|
||||
import net.minecraft.world.level.chunk.ChunkAccess;
|
||||
import net.minecraft.world.level.chunk.ProtoChunk;
|
||||
import net.minecraft.world.level.chunk.status.ChunkStatus;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import java.util.function.BooleanSupplier;
|
||||
|
||||
public final class ChunkLightTask extends ChunkProgressionTask {
|
||||
|
||||
private static final Logger LOGGER = LogManager.getLogger();
|
||||
|
||||
private final ChunkAccess fromChunk;
|
||||
|
||||
private final LightTaskPriorityHolder priorityHolder;
|
||||
|
||||
public ChunkLightTask(final ChunkTaskScheduler scheduler, final ServerLevel world, final int chunkX, final int chunkZ,
|
||||
final ChunkAccess chunk, final PrioritisedExecutor.Priority priority) {
|
||||
super(scheduler, world, chunkX, chunkZ);
|
||||
if (!PrioritisedExecutor.Priority.isValidPriority(priority)) {
|
||||
throw new IllegalArgumentException("Invalid priority " + priority);
|
||||
}
|
||||
this.priorityHolder = new LightTaskPriorityHolder(priority, this);
|
||||
this.fromChunk = chunk;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isScheduled() {
|
||||
return this.priorityHolder.isScheduled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChunkStatus getTargetStatus() {
|
||||
return ChunkStatus.LIGHT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void schedule() {
|
||||
this.priorityHolder.schedule();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancel() {
|
||||
this.priorityHolder.cancel();
|
||||
}
|
||||
|
||||
@Override
|
||||
public PrioritisedExecutor.Priority getPriority() {
|
||||
return this.priorityHolder.getPriority();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void lowerPriority(final PrioritisedExecutor.Priority priority) {
|
||||
this.priorityHolder.raisePriority(priority);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPriority(final PrioritisedExecutor.Priority priority) {
|
||||
this.priorityHolder.setPriority(priority);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void raisePriority(final PrioritisedExecutor.Priority priority) {
|
||||
this.priorityHolder.raisePriority(priority);
|
||||
}
|
||||
|
||||
private static final class LightTaskPriorityHolder extends PriorityHolder {
|
||||
|
||||
private final ChunkLightTask task;
|
||||
|
||||
private LightTaskPriorityHolder(final PrioritisedExecutor.Priority priority, final ChunkLightTask task) {
|
||||
super(priority);
|
||||
this.task = task;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void cancelScheduled() {
|
||||
final ChunkLightTask task = this.task;
|
||||
task.complete(null, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected PrioritisedExecutor.Priority getScheduledPriority() {
|
||||
final ChunkLightTask task = this.task;
|
||||
return ((StarLightLightingProvider)task.world.getChunkSource().getLightEngine()).starlight$getLightEngine().getServerLightQueue().getPriority(task.chunkX, task.chunkZ);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void scheduleTask(final PrioritisedExecutor.Priority priority) {
|
||||
final ChunkLightTask task = this.task;
|
||||
final StarLightInterface starLightInterface = ((StarLightLightingProvider)task.world.getChunkSource().getLightEngine()).starlight$getLightEngine();
|
||||
final StarLightInterface.ServerLightQueue lightQueue = starLightInterface.getServerLightQueue();
|
||||
lightQueue.queueChunkLightTask(new ChunkPos(task.chunkX, task.chunkZ), new LightTask(starLightInterface, task), priority);
|
||||
lightQueue.setPriority(task.chunkX, task.chunkZ, priority);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void lowerPriorityScheduled(final PrioritisedExecutor.Priority priority) {
|
||||
final ChunkLightTask task = this.task;
|
||||
final StarLightInterface starLightInterface = ((StarLightLightingProvider)task.world.getChunkSource().getLightEngine()).starlight$getLightEngine();
|
||||
final StarLightInterface.ServerLightQueue lightQueue = starLightInterface.getServerLightQueue();
|
||||
lightQueue.lowerPriority(task.chunkX, task.chunkZ, priority);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setPriorityScheduled(final PrioritisedExecutor.Priority priority) {
|
||||
final ChunkLightTask task = this.task;
|
||||
final StarLightInterface starLightInterface = ((StarLightLightingProvider)task.world.getChunkSource().getLightEngine()).starlight$getLightEngine();
|
||||
final StarLightInterface.ServerLightQueue lightQueue = starLightInterface.getServerLightQueue();
|
||||
lightQueue.setPriority(task.chunkX, task.chunkZ, priority);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void raisePriorityScheduled(final PrioritisedExecutor.Priority priority) {
|
||||
final ChunkLightTask task = this.task;
|
||||
final StarLightInterface starLightInterface = ((StarLightLightingProvider)task.world.getChunkSource().getLightEngine()).starlight$getLightEngine();
|
||||
final StarLightInterface.ServerLightQueue lightQueue = starLightInterface.getServerLightQueue();
|
||||
lightQueue.raisePriority(task.chunkX, task.chunkZ, priority);
|
||||
}
|
||||
}
|
||||
|
||||
private static final class LightTask implements BooleanSupplier {
|
||||
|
||||
private final StarLightInterface lightEngine;
|
||||
private final ChunkLightTask task;
|
||||
|
||||
public LightTask(final StarLightInterface lightEngine, final ChunkLightTask task) {
|
||||
this.lightEngine = lightEngine;
|
||||
this.task = task;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getAsBoolean() {
|
||||
final ChunkLightTask task = this.task;
|
||||
// executed on light thread
|
||||
if (!task.priorityHolder.markExecuting()) {
|
||||
// cancelled
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
final Boolean[] emptySections = StarLightEngine.getEmptySectionsForChunk(task.fromChunk);
|
||||
|
||||
if (task.fromChunk.isLightCorrect() && task.fromChunk.getStatus().isOrAfter(ChunkStatus.LIGHT)) {
|
||||
this.lightEngine.forceLoadInChunk(task.fromChunk, emptySections);
|
||||
this.lightEngine.checkChunkEdges(task.chunkX, task.chunkZ);
|
||||
} else {
|
||||
task.fromChunk.setLightCorrect(false);
|
||||
this.lightEngine.lightChunk(task.fromChunk, emptySections);
|
||||
task.fromChunk.setLightCorrect(true);
|
||||
}
|
||||
// we need to advance status
|
||||
if (task.fromChunk instanceof ProtoChunk chunk && chunk.getStatus() == ChunkStatus.LIGHT.getParent()) {
|
||||
chunk.setStatus(ChunkStatus.LIGHT);
|
||||
}
|
||||
} catch (final Throwable thr) {
|
||||
LOGGER.fatal(
|
||||
"Failed to light chunk " + task.fromChunk.getPos().toString()
|
||||
+ " in world '" + WorldUtil.getWorldName(this.lightEngine.getWorld()) + "'", thr
|
||||
);
|
||||
|
||||
task.complete(null, thr);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
task.complete(task.fromChunk, null);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,487 @@
|
||||
package ca.spottedleaf.moonrise.patches.chunk_system.scheduling.task;
|
||||
|
||||
import ca.spottedleaf.concurrentutil.collection.MultiThreadedQueue;
|
||||
import ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor;
|
||||
import ca.spottedleaf.concurrentutil.lock.ReentrantAreaLock;
|
||||
import ca.spottedleaf.concurrentutil.util.ConcurrentUtil;
|
||||
import ca.spottedleaf.moonrise.patches.chunk_system.ChunkSystemConverters;
|
||||
import ca.spottedleaf.moonrise.patches.chunk_system.ChunkSystemFeatures;
|
||||
import ca.spottedleaf.moonrise.patches.chunk_system.io.RegionFileIOThread;
|
||||
import ca.spottedleaf.moonrise.patches.chunk_system.level.poi.PoiChunk;
|
||||
import ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkTaskScheduler;
|
||||
import ca.spottedleaf.moonrise.patches.chunk_system.scheduling.NewChunkHolder;
|
||||
import net.minecraft.core.registries.Registries;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.world.level.ChunkPos;
|
||||
import net.minecraft.world.level.chunk.ChunkAccess;
|
||||
import net.minecraft.world.level.chunk.ProtoChunk;
|
||||
import net.minecraft.world.level.chunk.UpgradeData;
|
||||
import net.minecraft.world.level.chunk.status.ChunkStatus;
|
||||
import net.minecraft.world.level.chunk.storage.ChunkSerializer;
|
||||
import net.minecraft.world.level.levelgen.blending.BlendingData;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import java.lang.invoke.VarHandle;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public final class ChunkLoadTask extends ChunkProgressionTask {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(ChunkLoadTask.class);
|
||||
|
||||
private final NewChunkHolder chunkHolder;
|
||||
private final ChunkDataLoadTask loadTask;
|
||||
|
||||
private volatile boolean cancelled;
|
||||
private NewChunkHolder.GenericDataLoadTaskCallback entityLoadTask;
|
||||
private NewChunkHolder.GenericDataLoadTaskCallback poiLoadTask;
|
||||
private GenericDataLoadTask.TaskResult<ChunkAccess, Throwable> loadResult;
|
||||
private final AtomicInteger taskCountToComplete = new AtomicInteger(3); // one for poi, one for entity, and one for chunk data
|
||||
|
||||
public ChunkLoadTask(final ChunkTaskScheduler scheduler, final ServerLevel world, final int chunkX, final int chunkZ,
|
||||
final NewChunkHolder chunkHolder, final PrioritisedExecutor.Priority priority) {
|
||||
super(scheduler, world, chunkX, chunkZ);
|
||||
this.chunkHolder = chunkHolder;
|
||||
this.loadTask = new ChunkDataLoadTask(scheduler, world, chunkX, chunkZ, priority);
|
||||
this.loadTask.addCallback((final GenericDataLoadTask.TaskResult<ChunkAccess, Throwable> result) -> {
|
||||
ChunkLoadTask.this.loadResult = result; // must be before getAndDecrement
|
||||
ChunkLoadTask.this.tryCompleteLoad();
|
||||
});
|
||||
}
|
||||
|
||||
private void tryCompleteLoad() {
|
||||
final int count = this.taskCountToComplete.decrementAndGet();
|
||||
if (count == 0) {
|
||||
final GenericDataLoadTask.TaskResult<ChunkAccess, Throwable> result = this.cancelled ? null : this.loadResult; // only after the getAndDecrement
|
||||
ChunkLoadTask.this.complete(result == null ? null : result.left(), result == null ? null : result.right());
|
||||
} else if (count < 0) {
|
||||
throw new IllegalStateException("Called tryCompleteLoad() too many times");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChunkStatus getTargetStatus() {
|
||||
return ChunkStatus.EMPTY;
|
||||
}
|
||||
|
||||
private boolean scheduled;
|
||||
|
||||
@Override
|
||||
public boolean isScheduled() {
|
||||
return this.scheduled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void schedule() {
|
||||
final NewChunkHolder.GenericDataLoadTaskCallback entityLoadTask;
|
||||
final NewChunkHolder.GenericDataLoadTaskCallback poiLoadTask;
|
||||
|
||||
final Consumer<GenericDataLoadTask.TaskResult<?, ?>> scheduleLoadTask = (final GenericDataLoadTask.TaskResult<?, ?> result) -> {
|
||||
ChunkLoadTask.this.tryCompleteLoad();
|
||||
};
|
||||
|
||||
// NOTE: it is IMPOSSIBLE for getOrLoadEntityData/getOrLoadPoiData to complete synchronously, because
|
||||
// they must schedule a task to off main or to on main to complete
|
||||
final ReentrantAreaLock.Node schedulingLock = this.scheduler.schedulingLockArea.lock(this.chunkX, this.chunkZ);
|
||||
try {
|
||||
if (this.scheduled) {
|
||||
throw new IllegalStateException("schedule() called twice");
|
||||
}
|
||||
this.scheduled = true;
|
||||
if (this.cancelled) {
|
||||
return;
|
||||
}
|
||||
if (!this.chunkHolder.isEntityChunkNBTLoaded()) {
|
||||
entityLoadTask = this.chunkHolder.getOrLoadEntityData((Consumer)scheduleLoadTask);
|
||||
} else {
|
||||
entityLoadTask = null;
|
||||
this.tryCompleteLoad();
|
||||
}
|
||||
|
||||
if (!this.chunkHolder.isPoiChunkLoaded()) {
|
||||
poiLoadTask = this.chunkHolder.getOrLoadPoiData((Consumer)scheduleLoadTask);
|
||||
} else {
|
||||
poiLoadTask = null;
|
||||
this.tryCompleteLoad();
|
||||
}
|
||||
|
||||
this.entityLoadTask = entityLoadTask;
|
||||
this.poiLoadTask = poiLoadTask;
|
||||
} finally {
|
||||
this.scheduler.schedulingLockArea.unlock(schedulingLock);
|
||||
}
|
||||
|
||||
if (entityLoadTask != null) {
|
||||
entityLoadTask.schedule();
|
||||
}
|
||||
|
||||
if (poiLoadTask != null) {
|
||||
poiLoadTask.schedule();
|
||||
}
|
||||
|
||||
this.loadTask.schedule(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancel() {
|
||||
// must be before load task access, so we can synchronise with the writes to the fields
|
||||
final boolean scheduled;
|
||||
final ReentrantAreaLock.Node schedulingLock = this.scheduler.schedulingLockArea.lock(this.chunkX, this.chunkZ);
|
||||
try {
|
||||
// must read field here, as it may be written later conucrrently -
|
||||
// we need to know if we scheduled _before_ cancellation
|
||||
scheduled = this.scheduled;
|
||||
this.cancelled = true;
|
||||
} finally {
|
||||
this.scheduler.schedulingLockArea.unlock(schedulingLock);
|
||||
}
|
||||
|
||||
/*
|
||||
Note: The entityLoadTask/poiLoadTask do not complete when cancelled,
|
||||
so we need to manually try to complete in those cases
|
||||
It is also important to note that we set the cancelled field first, just in case
|
||||
the chunk load task attempts to complete with a non-null value
|
||||
*/
|
||||
|
||||
if (scheduled) {
|
||||
// since we scheduled, we need to cancel the tasks
|
||||
if (this.entityLoadTask != null) {
|
||||
if (this.entityLoadTask.cancel()) {
|
||||
this.tryCompleteLoad();
|
||||
}
|
||||
}
|
||||
if (this.poiLoadTask != null) {
|
||||
if (this.poiLoadTask.cancel()) {
|
||||
this.tryCompleteLoad();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// since nothing was scheduled, we need to decrement the task count here ourselves
|
||||
|
||||
// for entity load task
|
||||
this.tryCompleteLoad();
|
||||
|
||||
// for poi load task
|
||||
this.tryCompleteLoad();
|
||||
}
|
||||
this.loadTask.cancel();
|
||||
}
|
||||
|
||||
@Override
|
||||
public PrioritisedExecutor.Priority getPriority() {
|
||||
return this.loadTask.getPriority();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void lowerPriority(final PrioritisedExecutor.Priority priority) {
|
||||
final EntityDataLoadTask entityLoad = this.chunkHolder.getEntityDataLoadTask();
|
||||
if (entityLoad != null) {
|
||||
entityLoad.lowerPriority(priority);
|
||||
}
|
||||
|
||||
final PoiDataLoadTask poiLoad = this.chunkHolder.getPoiDataLoadTask();
|
||||
|
||||
if (poiLoad != null) {
|
||||
poiLoad.lowerPriority(priority);
|
||||
}
|
||||
|
||||
this.loadTask.lowerPriority(priority);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPriority(final PrioritisedExecutor.Priority priority) {
|
||||
final EntityDataLoadTask entityLoad = this.chunkHolder.getEntityDataLoadTask();
|
||||
if (entityLoad != null) {
|
||||
entityLoad.setPriority(priority);
|
||||
}
|
||||
|
||||
final PoiDataLoadTask poiLoad = this.chunkHolder.getPoiDataLoadTask();
|
||||
|
||||
if (poiLoad != null) {
|
||||
poiLoad.setPriority(priority);
|
||||
}
|
||||
|
||||
this.loadTask.setPriority(priority);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void raisePriority(final PrioritisedExecutor.Priority priority) {
|
||||
final EntityDataLoadTask entityLoad = this.chunkHolder.getEntityDataLoadTask();
|
||||
if (entityLoad != null) {
|
||||
entityLoad.raisePriority(priority);
|
||||
}
|
||||
|
||||
final PoiDataLoadTask poiLoad = this.chunkHolder.getPoiDataLoadTask();
|
||||
|
||||
if (poiLoad != null) {
|
||||
poiLoad.raisePriority(priority);
|
||||
}
|
||||
|
||||
this.loadTask.raisePriority(priority);
|
||||
}
|
||||
|
||||
protected static abstract class CallbackDataLoadTask<OnMain,FinalCompletion> extends GenericDataLoadTask<OnMain,FinalCompletion> {
|
||||
|
||||
private TaskResult<FinalCompletion, Throwable> result;
|
||||
private final MultiThreadedQueue<Consumer<TaskResult<FinalCompletion, Throwable>>> waiters = new MultiThreadedQueue<>();
|
||||
|
||||
protected volatile boolean completed;
|
||||
protected static final VarHandle COMPLETED_HANDLE = ConcurrentUtil.getVarHandle(CallbackDataLoadTask.class, "completed", boolean.class);
|
||||
|
||||
protected CallbackDataLoadTask(final ChunkTaskScheduler scheduler, final ServerLevel world, final int chunkX,
|
||||
final int chunkZ, final RegionFileIOThread.RegionFileType type,
|
||||
final PrioritisedExecutor.Priority priority) {
|
||||
super(scheduler, world, chunkX, chunkZ, type, priority);
|
||||
}
|
||||
|
||||
public void addCallback(final Consumer<TaskResult<FinalCompletion, Throwable>> consumer) {
|
||||
if (!this.waiters.add(consumer)) {
|
||||
try {
|
||||
consumer.accept(this.result);
|
||||
} catch (final Throwable throwable) {
|
||||
this.scheduler.unrecoverableChunkSystemFailure(this.chunkX, this.chunkZ, Map.of(
|
||||
"Consumer", ChunkTaskScheduler.stringIfNull(consumer),
|
||||
"Completed throwable", ChunkTaskScheduler.stringIfNull(this.result.right()),
|
||||
"CallbackDataLoadTask impl", this.getClass().getName()
|
||||
), throwable);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onComplete(final TaskResult<FinalCompletion, Throwable> result) {
|
||||
if ((boolean)COMPLETED_HANDLE.getAndSet((CallbackDataLoadTask)this, (boolean)true)) {
|
||||
throw new IllegalStateException("Already completed");
|
||||
}
|
||||
this.result = result;
|
||||
Consumer<TaskResult<FinalCompletion, Throwable>> consumer;
|
||||
while ((consumer = this.waiters.pollOrBlockAdds()) != null) {
|
||||
try {
|
||||
consumer.accept(result);
|
||||
} catch (final Throwable throwable) {
|
||||
this.scheduler.unrecoverableChunkSystemFailure(this.chunkX, this.chunkZ, Map.of(
|
||||
"Consumer", ChunkTaskScheduler.stringIfNull(consumer),
|
||||
"Completed throwable", ChunkTaskScheduler.stringIfNull(result.right()),
|
||||
"CallbackDataLoadTask impl", this.getClass().getName()
|
||||
), throwable);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static final class ChunkDataLoadTask extends CallbackDataLoadTask<CompoundTag, ChunkAccess> {
|
||||
private ChunkDataLoadTask(final ChunkTaskScheduler scheduler, final ServerLevel world, final int chunkX,
|
||||
final int chunkZ, final PrioritisedExecutor.Priority priority) {
|
||||
super(scheduler, world, chunkX, chunkZ, RegionFileIOThread.RegionFileType.CHUNK_DATA, priority);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean hasOffMain() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean hasOnMain() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected PrioritisedExecutor.PrioritisedTask createOffMain(final Runnable run, final PrioritisedExecutor.Priority priority) {
|
||||
return this.scheduler.loadExecutor.createTask(run, priority);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected PrioritisedExecutor.PrioritisedTask createOnMain(final Runnable run, final PrioritisedExecutor.Priority priority) {
|
||||
return this.scheduler.createChunkTask(this.chunkX, this.chunkZ, run, priority);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected TaskResult<ChunkAccess, Throwable> completeOnMainOffMain(final CompoundTag data, final Throwable throwable) {
|
||||
if (throwable != null) {
|
||||
return new TaskResult<>(null, throwable);
|
||||
}
|
||||
if (data == null) {
|
||||
return new TaskResult<>(this.getEmptyChunk(), null);
|
||||
}
|
||||
|
||||
if (ChunkSystemFeatures.supportsAsyncChunkDeserialization()) {
|
||||
return this.deserialize(data);
|
||||
}
|
||||
// need to deserialize on main thread
|
||||
return null;
|
||||
}
|
||||
|
||||
private ProtoChunk getEmptyChunk() {
|
||||
return new ProtoChunk(
|
||||
new ChunkPos(this.chunkX, this.chunkZ), UpgradeData.EMPTY, this.world,
|
||||
this.world.registryAccess().registryOrThrow(Registries.BIOME), (BlendingData)null
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected TaskResult<CompoundTag, Throwable> runOffMain(final CompoundTag data, final Throwable throwable) {
|
||||
if (throwable != null) {
|
||||
LOGGER.error("Failed to load chunk data for task: " + this.toString() + ", chunk data will be lost", throwable);
|
||||
return new TaskResult<>(null, null);
|
||||
}
|
||||
|
||||
if (data == null) {
|
||||
return new TaskResult<>(null, null);
|
||||
}
|
||||
|
||||
try {
|
||||
// run converters
|
||||
final CompoundTag converted = this.world.getChunkSource().chunkMap.upgradeChunkTag(data);
|
||||
|
||||
return new TaskResult<>(converted, null);
|
||||
} catch (final Throwable thr2) {
|
||||
LOGGER.error("Failed to parse chunk data for task: " + this.toString() + ", chunk data will be lost", thr2);
|
||||
return new TaskResult<>(null, null);
|
||||
}
|
||||
}
|
||||
|
||||
private TaskResult<ChunkAccess, Throwable> deserialize(final CompoundTag data) {
|
||||
try {
|
||||
final ChunkAccess deserialized = ChunkSerializer.read(
|
||||
this.world, this.world.getPoiManager(), new ChunkPos(this.chunkX, this.chunkZ), data
|
||||
);
|
||||
return new TaskResult<>(deserialized, null);
|
||||
} catch (final Throwable thr2) {
|
||||
LOGGER.error("Failed to parse chunk data for task: " + this.toString() + ", chunk data will be lost", thr2);
|
||||
return new TaskResult<>(this.getEmptyChunk(), null);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected TaskResult<ChunkAccess, Throwable> runOnMain(final CompoundTag data, final Throwable throwable) {
|
||||
// data != null && throwable == null
|
||||
if (ChunkSystemFeatures.supportsAsyncChunkDeserialization()) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
return this.deserialize(data);
|
||||
}
|
||||
}
|
||||
|
||||
public static final class PoiDataLoadTask extends CallbackDataLoadTask<PoiChunk, PoiChunk> {
|
||||
|
||||
public PoiDataLoadTask(final ChunkTaskScheduler scheduler, final ServerLevel world, final int chunkX,
|
||||
final int chunkZ, final PrioritisedExecutor.Priority priority) {
|
||||
super(scheduler, world, chunkX, chunkZ, RegionFileIOThread.RegionFileType.POI_DATA, priority);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean hasOffMain() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean hasOnMain() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected PrioritisedExecutor.PrioritisedTask createOffMain(final Runnable run, final PrioritisedExecutor.Priority priority) {
|
||||
return this.scheduler.loadExecutor.createTask(run, priority);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected PrioritisedExecutor.PrioritisedTask createOnMain(final Runnable run, final PrioritisedExecutor.Priority priority) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected TaskResult<PoiChunk, Throwable> completeOnMainOffMain(final PoiChunk data, final Throwable throwable) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected TaskResult<PoiChunk, Throwable> runOffMain(final CompoundTag data, final Throwable throwable) {
|
||||
if (throwable != null) {
|
||||
LOGGER.error("Failed to load poi data for task: " + this.toString() + ", poi data will be lost", throwable);
|
||||
return new TaskResult<>(PoiChunk.empty(this.world, this.chunkX, this.chunkZ), null);
|
||||
}
|
||||
|
||||
if (data == null || data.isEmpty()) {
|
||||
// nothing to do
|
||||
return new TaskResult<>(PoiChunk.empty(this.world, this.chunkX, this.chunkZ), null);
|
||||
}
|
||||
|
||||
try {
|
||||
// run converters
|
||||
final CompoundTag converted = ChunkSystemConverters.convertPoiCompoundTag(data, this.world);
|
||||
|
||||
// now we need to parse it
|
||||
return new TaskResult<>(PoiChunk.parse(this.world, this.chunkX, this.chunkZ, converted), null);
|
||||
} catch (final Throwable thr2) {
|
||||
LOGGER.error("Failed to run parse poi data for task: " + this.toString() + ", poi data will be lost", thr2);
|
||||
return new TaskResult<>(PoiChunk.empty(this.world, this.chunkX, this.chunkZ), null);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected TaskResult<PoiChunk, Throwable> runOnMain(final PoiChunk data, final Throwable throwable) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
|
||||
public static final class EntityDataLoadTask extends CallbackDataLoadTask<CompoundTag, CompoundTag> {
|
||||
|
||||
public EntityDataLoadTask(final ChunkTaskScheduler scheduler, final ServerLevel world, final int chunkX,
|
||||
final int chunkZ, final PrioritisedExecutor.Priority priority) {
|
||||
super(scheduler, world, chunkX, chunkZ, RegionFileIOThread.RegionFileType.ENTITY_DATA, priority);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean hasOffMain() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean hasOnMain() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected PrioritisedExecutor.PrioritisedTask createOffMain(final Runnable run, final PrioritisedExecutor.Priority priority) {
|
||||
return this.scheduler.loadExecutor.createTask(run, priority);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected PrioritisedExecutor.PrioritisedTask createOnMain(final Runnable run, final PrioritisedExecutor.Priority priority) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected TaskResult<CompoundTag, Throwable> completeOnMainOffMain(final CompoundTag data, final Throwable throwable) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected TaskResult<CompoundTag, Throwable> runOffMain(final CompoundTag data, final Throwable throwable) {
|
||||
if (throwable != null) {
|
||||
LOGGER.error("Failed to load entity data for task: " + this.toString() + ", entity data will be lost", throwable);
|
||||
return new TaskResult<>(null, null);
|
||||
}
|
||||
|
||||
if (data == null || data.isEmpty()) {
|
||||
// nothing to do
|
||||
return new TaskResult<>(null, null);
|
||||
}
|
||||
|
||||
try {
|
||||
return new TaskResult<>(ChunkSystemConverters.convertEntityChunkCompoundTag(data, this.world), null);
|
||||
} catch (final Throwable thr2) {
|
||||
LOGGER.error("Failed to run converters for entity data for task: " + this.toString() + ", entity data will be lost", thr2);
|
||||
return new TaskResult<>(null, thr2);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected TaskResult<CompoundTag, Throwable> runOnMain(final CompoundTag data, final Throwable throwable) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
package ca.spottedleaf.moonrise.patches.chunk_system.scheduling.task;
|
||||
|
||||
import ca.spottedleaf.concurrentutil.collection.MultiThreadedQueue;
|
||||
import ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor;
|
||||
import ca.spottedleaf.concurrentutil.util.ConcurrentUtil;
|
||||
import ca.spottedleaf.moonrise.common.util.WorldUtil;
|
||||
import ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkTaskScheduler;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.world.level.chunk.ChunkAccess;
|
||||
import net.minecraft.world.level.chunk.status.ChunkStatus;
|
||||
import java.lang.invoke.VarHandle;
|
||||
import java.util.Map;
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
public abstract class ChunkProgressionTask {
|
||||
|
||||
private final MultiThreadedQueue<BiConsumer<ChunkAccess, Throwable>> waiters = new MultiThreadedQueue<>();
|
||||
private ChunkAccess completedChunk;
|
||||
private Throwable completedThrowable;
|
||||
|
||||
protected final ChunkTaskScheduler scheduler;
|
||||
protected final ServerLevel world;
|
||||
protected final int chunkX;
|
||||
protected final int chunkZ;
|
||||
|
||||
protected volatile boolean completed;
|
||||
protected static final VarHandle COMPLETED_HANDLE = ConcurrentUtil.getVarHandle(ChunkProgressionTask.class, "completed", boolean.class);
|
||||
|
||||
protected ChunkProgressionTask(final ChunkTaskScheduler scheduler, final ServerLevel world, final int chunkX, final int chunkZ) {
|
||||
this.scheduler = scheduler;
|
||||
this.world = world;
|
||||
this.chunkX = chunkX;
|
||||
this.chunkZ = chunkZ;
|
||||
}
|
||||
|
||||
// Used only for debug json
|
||||
public abstract boolean isScheduled();
|
||||
|
||||
// Note: It is the responsibility of the task to set the chunk's status once it has completed
|
||||
public abstract ChunkStatus getTargetStatus();
|
||||
|
||||
/* Only executed once */
|
||||
/* Implementations must be prepared to handle cases where cancel() is called before schedule() */
|
||||
public abstract void schedule();
|
||||
|
||||
/* May be called multiple times */
|
||||
public abstract void cancel();
|
||||
|
||||
public abstract PrioritisedExecutor.Priority getPriority();
|
||||
|
||||
/* Schedule lock is always held for the priority update calls */
|
||||
|
||||
public abstract void lowerPriority(final PrioritisedExecutor.Priority priority);
|
||||
|
||||
public abstract void setPriority(final PrioritisedExecutor.Priority priority);
|
||||
|
||||
public abstract void raisePriority(final PrioritisedExecutor.Priority priority);
|
||||
|
||||
public final void onComplete(final BiConsumer<ChunkAccess, Throwable> onComplete) {
|
||||
if (!this.waiters.add(onComplete)) {
|
||||
try {
|
||||
onComplete.accept(this.completedChunk, this.completedThrowable);
|
||||
} catch (final Throwable throwable) {
|
||||
this.scheduler.unrecoverableChunkSystemFailure(this.chunkX, this.chunkZ, Map.of(
|
||||
"Consumer", ChunkTaskScheduler.stringIfNull(onComplete),
|
||||
"Completed throwable", ChunkTaskScheduler.stringIfNull(this.completedThrowable)
|
||||
), throwable);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected final void complete(final ChunkAccess chunk, final Throwable throwable) {
|
||||
try {
|
||||
this.complete0(chunk, throwable);
|
||||
} catch (final Throwable thr2) {
|
||||
this.scheduler.unrecoverableChunkSystemFailure(this.chunkX, this.chunkZ, Map.of(
|
||||
"Completed throwable", ChunkTaskScheduler.stringIfNull(throwable)
|
||||
), thr2);
|
||||
}
|
||||
}
|
||||
|
||||
private void complete0(final ChunkAccess chunk, final Throwable throwable) {
|
||||
if ((boolean)COMPLETED_HANDLE.getAndSet((ChunkProgressionTask)this, (boolean)true)) {
|
||||
throw new IllegalStateException("Already completed");
|
||||
}
|
||||
this.completedChunk = chunk;
|
||||
this.completedThrowable = throwable;
|
||||
|
||||
BiConsumer<ChunkAccess, Throwable> consumer;
|
||||
while ((consumer = this.waiters.pollOrBlockAdds()) != null) {
|
||||
consumer.accept(chunk, throwable);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ChunkProgressionTask{class: " + this.getClass().getName() + ", for world: " + WorldUtil.getWorldName(this.world) +
|
||||
", chunk: (" + this.chunkX + "," + this.chunkZ + "), hashcode: " + System.identityHashCode(this) + ", priority: " + this.getPriority() +
|
||||
", status: " + this.getTargetStatus().toString() + ", scheduled: " + this.isScheduled() + "}";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,219 @@
|
||||
package ca.spottedleaf.moonrise.patches.chunk_system.scheduling.task;
|
||||
|
||||
import ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor;
|
||||
import ca.spottedleaf.concurrentutil.util.ConcurrentUtil;
|
||||
import ca.spottedleaf.moonrise.common.util.WorldUtil;
|
||||
import ca.spottedleaf.moonrise.patches.chunk_system.level.chunk.ChunkSystemChunkStatus;
|
||||
import ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkTaskScheduler;
|
||||
import net.minecraft.server.level.ChunkHolder;
|
||||
import net.minecraft.server.level.ChunkMap;
|
||||
import net.minecraft.server.level.ServerChunkCache;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.world.level.chunk.ChunkAccess;
|
||||
import net.minecraft.world.level.chunk.ProtoChunk;
|
||||
import net.minecraft.world.level.chunk.status.ChunkStatus;
|
||||
import net.minecraft.world.level.chunk.status.WorldGenContext;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import java.lang.invoke.VarHandle;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
public final class ChunkUpgradeGenericStatusTask extends ChunkProgressionTask implements Runnable {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(ChunkUpgradeGenericStatusTask.class);
|
||||
|
||||
private final ChunkAccess fromChunk;
|
||||
private final ChunkStatus fromStatus;
|
||||
private final ChunkStatus toStatus;
|
||||
private final List<ChunkAccess> neighbours;
|
||||
|
||||
private final PrioritisedExecutor.PrioritisedTask generateTask;
|
||||
|
||||
public ChunkUpgradeGenericStatusTask(final ChunkTaskScheduler scheduler, final ServerLevel world, final int chunkX,
|
||||
final int chunkZ, final ChunkAccess chunk, final List<ChunkAccess> neighbours,
|
||||
final ChunkStatus toStatus, final PrioritisedExecutor.Priority priority) {
|
||||
super(scheduler, world, chunkX, chunkZ);
|
||||
if (!PrioritisedExecutor.Priority.isValidPriority(priority)) {
|
||||
throw new IllegalArgumentException("Invalid priority " + priority);
|
||||
}
|
||||
this.fromChunk = chunk;
|
||||
this.fromStatus = chunk.getStatus();
|
||||
this.toStatus = toStatus;
|
||||
this.neighbours = neighbours;
|
||||
if (((ChunkSystemChunkStatus)this.toStatus).moonrise$isParallelCapable()) {
|
||||
this.generateTask = this.scheduler.parallelGenExecutor.createTask(this, priority);
|
||||
} else {
|
||||
final int writeRadius = ((ChunkSystemChunkStatus)this.toStatus).moonrise$getWriteRadius();
|
||||
if (writeRadius < 0) {
|
||||
this.generateTask = this.scheduler.radiusAwareScheduler.createInfiniteRadiusTask(this, priority);
|
||||
} else {
|
||||
this.generateTask = this.scheduler.radiusAwareScheduler.createTask(chunkX, chunkZ, writeRadius, this, priority);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChunkStatus getTargetStatus() {
|
||||
return this.toStatus;
|
||||
}
|
||||
|
||||
private boolean isEmptyTask() {
|
||||
// must use fromStatus here to avoid any race condition with run() overwriting the status
|
||||
final boolean generation = !this.fromStatus.isOrAfter(this.toStatus);
|
||||
return (generation && ((ChunkSystemChunkStatus)this.toStatus).moonrise$isEmptyGenStatus()) || (!generation && ((ChunkSystemChunkStatus)this.toStatus).moonrise$isEmptyLoadStatus());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
final ChunkAccess chunk = this.fromChunk;
|
||||
|
||||
final ServerChunkCache serverChunkCache = this.world.getChunkSource();
|
||||
final ChunkMap chunkMap = serverChunkCache.chunkMap;
|
||||
|
||||
final CompletableFuture<ChunkAccess> completeFuture;
|
||||
|
||||
final boolean generation;
|
||||
boolean completing = false;
|
||||
|
||||
// note: should optimise the case where the chunk does not need to execute the status, because
|
||||
// schedule() calls this synchronously if it will run through that path
|
||||
|
||||
final WorldGenContext ctx = new WorldGenContext(
|
||||
this.world,
|
||||
chunkMap.generator,
|
||||
chunkMap.worldGenContext.structureManager(),
|
||||
serverChunkCache.getLightEngine()
|
||||
);
|
||||
try {
|
||||
generation = !chunk.getStatus().isOrAfter(this.toStatus);
|
||||
if (generation) {
|
||||
if (((ChunkSystemChunkStatus)this.toStatus).moonrise$isEmptyGenStatus()) {
|
||||
if (chunk instanceof ProtoChunk) {
|
||||
((ProtoChunk)chunk).setStatus(this.toStatus);
|
||||
}
|
||||
completing = true;
|
||||
this.complete(chunk, null);
|
||||
return;
|
||||
}
|
||||
completeFuture = this.toStatus.generate(ctx, Runnable::run, null, this.neighbours)
|
||||
.whenComplete((final ChunkAccess either, final Throwable throwable) -> {
|
||||
if (either instanceof ProtoChunk proto) {
|
||||
proto.setStatus(ChunkUpgradeGenericStatusTask.this.toStatus);
|
||||
}
|
||||
}
|
||||
);
|
||||
} else {
|
||||
if (((ChunkSystemChunkStatus)this.toStatus).moonrise$isEmptyLoadStatus()) {
|
||||
completing = true;
|
||||
this.complete(chunk, null);
|
||||
return;
|
||||
}
|
||||
completeFuture = this.toStatus.load(ctx, null, chunk);
|
||||
}
|
||||
} catch (final Throwable throwable) {
|
||||
if (!completing) {
|
||||
this.complete(null, throwable);
|
||||
return;
|
||||
}
|
||||
|
||||
this.scheduler.unrecoverableChunkSystemFailure(this.chunkX, this.chunkZ, Map.of(
|
||||
"Target status", ChunkTaskScheduler.stringIfNull(this.toStatus),
|
||||
"From status", ChunkTaskScheduler.stringIfNull(this.fromStatus),
|
||||
"Generation task", this
|
||||
), throwable);
|
||||
|
||||
LOGGER.error(
|
||||
"Failed to complete status for chunk: status:" + this.toStatus + ", chunk: (" + this.chunkX +
|
||||
"," + this.chunkZ + "), world: " + WorldUtil.getWorldName(this.world),
|
||||
throwable
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!completeFuture.isDone() && !((ChunkSystemChunkStatus)this.toStatus).moonrise$getWarnedAboutNoImmediateComplete().getAndSet(true)) {
|
||||
LOGGER.warn("Future status not complete after scheduling: " + this.toStatus.toString() + ", generate: " + generation);
|
||||
}
|
||||
|
||||
final ChunkAccess newChunk;
|
||||
|
||||
try {
|
||||
newChunk = completeFuture.join();
|
||||
} catch (final Throwable throwable) {
|
||||
this.complete(null, throwable);
|
||||
return;
|
||||
}
|
||||
|
||||
if (newChunk == null) {
|
||||
this.complete(null,
|
||||
new IllegalStateException(
|
||||
"Chunk for status: " + ChunkUpgradeGenericStatusTask.this.toStatus.toString()
|
||||
+ ", generation: " + generation + " should not be null! Future: " + completeFuture
|
||||
).fillInStackTrace()
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
this.complete(newChunk, null);
|
||||
}
|
||||
|
||||
private volatile boolean scheduled;
|
||||
private static final VarHandle SCHEDULED_HANDLE = ConcurrentUtil.getVarHandle(ChunkUpgradeGenericStatusTask.class, "scheduled", boolean.class);
|
||||
|
||||
@Override
|
||||
public boolean isScheduled() {
|
||||
return this.scheduled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void schedule() {
|
||||
if ((boolean)SCHEDULED_HANDLE.getAndSet((ChunkUpgradeGenericStatusTask)this, true)) {
|
||||
throw new IllegalStateException("Cannot double call schedule()");
|
||||
}
|
||||
if (this.isEmptyTask()) {
|
||||
if (this.generateTask.cancel()) {
|
||||
this.run();
|
||||
}
|
||||
} else {
|
||||
this.generateTask.queue();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancel() {
|
||||
if (this.generateTask.cancel()) {
|
||||
this.complete(null, null);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public PrioritisedExecutor.Priority getPriority() {
|
||||
return this.generateTask.getPriority();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void lowerPriority(final PrioritisedExecutor.Priority priority) {
|
||||
if (!PrioritisedExecutor.Priority.isValidPriority(priority)) {
|
||||
throw new IllegalArgumentException("Invalid priority " + priority);
|
||||
}
|
||||
this.generateTask.lowerPriority(priority);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPriority(final PrioritisedExecutor.Priority priority) {
|
||||
if (!PrioritisedExecutor.Priority.isValidPriority(priority)) {
|
||||
throw new IllegalArgumentException("Invalid priority " + priority);
|
||||
}
|
||||
this.generateTask.setPriority(priority);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void raisePriority(final PrioritisedExecutor.Priority priority) {
|
||||
if (!PrioritisedExecutor.Priority.isValidPriority(priority)) {
|
||||
throw new IllegalArgumentException("Invalid priority " + priority);
|
||||
}
|
||||
this.generateTask.raisePriority(priority);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,673 @@
|
||||
package ca.spottedleaf.moonrise.patches.chunk_system.scheduling.task;
|
||||
|
||||
import ca.spottedleaf.concurrentutil.completable.Completable;
|
||||
import ca.spottedleaf.concurrentutil.executor.Cancellable;
|
||||
import ca.spottedleaf.concurrentutil.executor.standard.DelayedPrioritisedTask;
|
||||
import ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor;
|
||||
import ca.spottedleaf.concurrentutil.util.ConcurrentUtil;
|
||||
import ca.spottedleaf.moonrise.common.util.WorldUtil;
|
||||
import ca.spottedleaf.moonrise.patches.chunk_system.io.RegionFileIOThread;
|
||||
import ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel;
|
||||
import ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkTaskScheduler;
|
||||
import ca.spottedleaf.moonrise.patches.chunk_system.scheduling.NewChunkHolder;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import java.lang.invoke.VarHandle;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
public abstract class GenericDataLoadTask<OnMain,FinalCompletion> {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(GenericDataLoadTask.class);
|
||||
|
||||
protected static final CompoundTag CANCELLED_DATA = new CompoundTag();
|
||||
|
||||
// reference count is the upper 32 bits
|
||||
protected final AtomicLong stageAndReferenceCount = new AtomicLong(STAGE_NOT_STARTED);
|
||||
|
||||
protected static final long STAGE_MASK = 0xFFFFFFFFL;
|
||||
protected static final long STAGE_CANCELLED = 0xFFFFFFFFL;
|
||||
protected static final long STAGE_NOT_STARTED = 0L;
|
||||
protected static final long STAGE_LOADING = 1L;
|
||||
protected static final long STAGE_PROCESSING = 2L;
|
||||
protected static final long STAGE_COMPLETED = 3L;
|
||||
|
||||
// for loading data off disk
|
||||
protected final LoadDataFromDiskTask loadDataFromDiskTask;
|
||||
// processing off-main
|
||||
protected final PrioritisedExecutor.PrioritisedTask processOffMain;
|
||||
// processing on-main
|
||||
protected final PrioritisedExecutor.PrioritisedTask processOnMain;
|
||||
|
||||
protected final ChunkTaskScheduler scheduler;
|
||||
protected final ServerLevel world;
|
||||
protected final int chunkX;
|
||||
protected final int chunkZ;
|
||||
protected final RegionFileIOThread.RegionFileType type;
|
||||
|
||||
public GenericDataLoadTask(final ChunkTaskScheduler scheduler, final ServerLevel world, final int chunkX,
|
||||
final int chunkZ, final RegionFileIOThread.RegionFileType type,
|
||||
final PrioritisedExecutor.Priority priority) {
|
||||
this.scheduler = scheduler;
|
||||
this.world = world;
|
||||
this.chunkX = chunkX;
|
||||
this.chunkZ = chunkZ;
|
||||
this.type = type;
|
||||
|
||||
final ProcessOnMainTask mainTask;
|
||||
if (this.hasOnMain()) {
|
||||
mainTask = new ProcessOnMainTask();
|
||||
this.processOnMain = this.createOnMain(mainTask, priority);
|
||||
} else {
|
||||
mainTask = null;
|
||||
this.processOnMain = null;
|
||||
}
|
||||
|
||||
final ProcessOffMainTask offMainTask;
|
||||
if (this.hasOffMain()) {
|
||||
offMainTask = new ProcessOffMainTask(mainTask);
|
||||
this.processOffMain = this.createOffMain(offMainTask, priority);
|
||||
} else {
|
||||
offMainTask = null;
|
||||
this.processOffMain = null;
|
||||
}
|
||||
|
||||
if (this.processOffMain == null && this.processOnMain == null) {
|
||||
throw new IllegalStateException("Illegal class implementation: " + this.getClass().getName() + ", should be able to schedule at least one task!");
|
||||
}
|
||||
|
||||
this.loadDataFromDiskTask = new LoadDataFromDiskTask(world, chunkX, chunkZ, type, new DataLoadCallback(offMainTask, mainTask), priority);
|
||||
}
|
||||
|
||||
public static final record TaskResult<L, R>(L left, R right) {}
|
||||
|
||||
protected abstract boolean hasOffMain();
|
||||
|
||||
protected abstract boolean hasOnMain();
|
||||
|
||||
protected abstract PrioritisedExecutor.PrioritisedTask createOffMain(final Runnable run, final PrioritisedExecutor.Priority priority);
|
||||
|
||||
protected abstract PrioritisedExecutor.PrioritisedTask createOnMain(final Runnable run, final PrioritisedExecutor.Priority priority);
|
||||
|
||||
protected abstract TaskResult<OnMain, Throwable> runOffMain(final CompoundTag data, final Throwable throwable);
|
||||
|
||||
protected abstract TaskResult<FinalCompletion, Throwable> runOnMain(final OnMain data, final Throwable throwable);
|
||||
|
||||
protected abstract void onComplete(final TaskResult<FinalCompletion,Throwable> result);
|
||||
|
||||
protected abstract TaskResult<FinalCompletion, Throwable> completeOnMainOffMain(final OnMain data, final Throwable throwable);
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "GenericDataLoadTask{class: " + this.getClass().getName() + ", world: " + WorldUtil.getWorldName(this.world) +
|
||||
", chunk: (" + this.chunkX + "," + this.chunkZ + "), hashcode: " + System.identityHashCode(this) + ", priority: " + this.getPriority() +
|
||||
", type: " + this.type.toString() + "}";
|
||||
}
|
||||
|
||||
public PrioritisedExecutor.Priority getPriority() {
|
||||
if (this.processOnMain != null) {
|
||||
return this.processOnMain.getPriority();
|
||||
} else {
|
||||
return this.processOffMain.getPriority();
|
||||
}
|
||||
}
|
||||
|
||||
public void lowerPriority(final PrioritisedExecutor.Priority priority) {
|
||||
// can't lower I/O tasks, we don't know what they affect
|
||||
if (this.processOffMain != null) {
|
||||
this.processOffMain.lowerPriority(priority);
|
||||
}
|
||||
if (this.processOnMain != null) {
|
||||
this.processOnMain.lowerPriority(priority);
|
||||
}
|
||||
}
|
||||
|
||||
public void setPriority(final PrioritisedExecutor.Priority priority) {
|
||||
// can't lower I/O tasks, we don't know what they affect
|
||||
this.loadDataFromDiskTask.raisePriority(priority);
|
||||
if (this.processOffMain != null) {
|
||||
this.processOffMain.setPriority(priority);
|
||||
}
|
||||
if (this.processOnMain != null) {
|
||||
this.processOnMain.setPriority(priority);
|
||||
}
|
||||
}
|
||||
|
||||
public void raisePriority(final PrioritisedExecutor.Priority priority) {
|
||||
// can't lower I/O tasks, we don't know what they affect
|
||||
this.loadDataFromDiskTask.raisePriority(priority);
|
||||
if (this.processOffMain != null) {
|
||||
this.processOffMain.raisePriority(priority);
|
||||
}
|
||||
if (this.processOnMain != null) {
|
||||
this.processOnMain.raisePriority(priority);
|
||||
}
|
||||
}
|
||||
|
||||
// returns whether scheduleNow() needs to be called
|
||||
public boolean schedule(final boolean delay) {
|
||||
if (this.stageAndReferenceCount.get() != STAGE_NOT_STARTED ||
|
||||
!this.stageAndReferenceCount.compareAndSet(STAGE_NOT_STARTED, (1L << 32) | STAGE_LOADING)) {
|
||||
// try and increment reference count
|
||||
int failures = 0;
|
||||
for (long curr = this.stageAndReferenceCount.get();;) {
|
||||
if ((curr & STAGE_MASK) == STAGE_CANCELLED || (curr & STAGE_MASK) == STAGE_COMPLETED) {
|
||||
// cancelled or completed, nothing to do here
|
||||
return false;
|
||||
}
|
||||
|
||||
if (curr == (curr = this.stageAndReferenceCount.compareAndExchange(curr, curr + (1L << 32)))) {
|
||||
// successful
|
||||
return false;
|
||||
}
|
||||
|
||||
++failures;
|
||||
for (int i = 0; i < failures; ++i) {
|
||||
ConcurrentUtil.backoff();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!delay) {
|
||||
this.scheduleNow();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public void scheduleNow() {
|
||||
this.loadDataFromDiskTask.schedule(); // will schedule the rest
|
||||
}
|
||||
|
||||
// assumes the current stage cannot be completed
|
||||
// returns false if cancelled, returns true if can proceed
|
||||
private boolean advanceStage(final long expect, final long to) {
|
||||
int failures = 0;
|
||||
for (long curr = this.stageAndReferenceCount.get();;) {
|
||||
if ((curr & STAGE_MASK) != expect) {
|
||||
// must be cancelled
|
||||
return false;
|
||||
}
|
||||
|
||||
final long newVal = (curr & ~STAGE_MASK) | to;
|
||||
if (curr == (curr = this.stageAndReferenceCount.compareAndExchange(curr, newVal))) {
|
||||
return true;
|
||||
}
|
||||
|
||||
++failures;
|
||||
for (int i = 0; i < failures; ++i) {
|
||||
ConcurrentUtil.backoff();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean cancel() {
|
||||
int failures = 0;
|
||||
for (long curr = this.stageAndReferenceCount.get();;) {
|
||||
if ((curr & STAGE_MASK) == STAGE_COMPLETED || (curr & STAGE_MASK) == STAGE_CANCELLED) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((curr & STAGE_MASK) == STAGE_NOT_STARTED || (curr & ~STAGE_MASK) == (1L << 32)) {
|
||||
// no other references, so we can cancel
|
||||
final long newVal = STAGE_CANCELLED;
|
||||
if (curr == (curr = this.stageAndReferenceCount.compareAndExchange(curr, newVal))) {
|
||||
this.loadDataFromDiskTask.cancel();
|
||||
if (this.processOffMain != null) {
|
||||
this.processOffMain.cancel();
|
||||
}
|
||||
if (this.processOnMain != null) {
|
||||
this.processOnMain.cancel();
|
||||
}
|
||||
this.onComplete(null);
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
if ((curr & ~STAGE_MASK) == (0L << 32)) {
|
||||
throw new IllegalStateException("Reference count cannot be zero here");
|
||||
}
|
||||
// just decrease the reference count
|
||||
final long newVal = curr - (1L << 32);
|
||||
if (curr == (curr = this.stageAndReferenceCount.compareAndExchange(curr, newVal))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
++failures;
|
||||
for (int i = 0; i < failures; ++i) {
|
||||
ConcurrentUtil.backoff();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final class DataLoadCallback implements BiConsumer<CompoundTag, Throwable> {
|
||||
|
||||
private final ProcessOffMainTask offMainTask;
|
||||
private final ProcessOnMainTask onMainTask;
|
||||
|
||||
public DataLoadCallback(final ProcessOffMainTask offMainTask, final ProcessOnMainTask onMainTask) {
|
||||
this.offMainTask = offMainTask;
|
||||
this.onMainTask = onMainTask;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void accept(final CompoundTag compoundTag, final Throwable throwable) {
|
||||
if (GenericDataLoadTask.this.stageAndReferenceCount.get() == STAGE_CANCELLED) {
|
||||
// don't try to schedule further
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
if (compoundTag == CANCELLED_DATA) {
|
||||
// cancelled, except this isn't possible
|
||||
LOGGER.error("Data callback says cancelled, but stage does not?");
|
||||
return;
|
||||
}
|
||||
|
||||
// get off of the regionfile callback ASAP, no clue what locks are held right now...
|
||||
if (GenericDataLoadTask.this.processOffMain != null) {
|
||||
this.offMainTask.data = compoundTag;
|
||||
this.offMainTask.throwable = throwable;
|
||||
GenericDataLoadTask.this.processOffMain.queue();
|
||||
return;
|
||||
} else {
|
||||
// no off-main task, so go straight to main
|
||||
this.onMainTask.data = (OnMain)compoundTag;
|
||||
this.onMainTask.throwable = throwable;
|
||||
GenericDataLoadTask.this.processOnMain.queue();
|
||||
}
|
||||
} catch (final Throwable thr2) {
|
||||
LOGGER.error("Failed I/O callback for task: " + GenericDataLoadTask.this.toString(), thr2);
|
||||
GenericDataLoadTask.this.scheduler.unrecoverableChunkSystemFailure(
|
||||
GenericDataLoadTask.this.chunkX, GenericDataLoadTask.this.chunkZ, Map.of(
|
||||
"Callback throwable", ChunkTaskScheduler.stringIfNull(throwable)
|
||||
), thr2
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final class ProcessOffMainTask implements Runnable {
|
||||
|
||||
private CompoundTag data;
|
||||
private Throwable throwable;
|
||||
private final ProcessOnMainTask schedule;
|
||||
|
||||
public ProcessOffMainTask(final ProcessOnMainTask schedule) {
|
||||
this.schedule = schedule;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
if (!GenericDataLoadTask.this.advanceStage(STAGE_LOADING, this.schedule == null ? STAGE_COMPLETED : STAGE_PROCESSING)) {
|
||||
// cancelled
|
||||
return;
|
||||
}
|
||||
final TaskResult<OnMain, Throwable> newData = GenericDataLoadTask.this.runOffMain(this.data, this.throwable);
|
||||
|
||||
if (GenericDataLoadTask.this.stageAndReferenceCount.get() == STAGE_CANCELLED) {
|
||||
// don't try to schedule further
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.schedule != null) {
|
||||
final TaskResult<FinalCompletion, Throwable> syncComplete = GenericDataLoadTask.this.completeOnMainOffMain(newData.left, newData.right);
|
||||
|
||||
if (syncComplete != null) {
|
||||
if (GenericDataLoadTask.this.advanceStage(STAGE_PROCESSING, STAGE_COMPLETED)) {
|
||||
GenericDataLoadTask.this.onComplete(syncComplete);
|
||||
} // else: cancelled
|
||||
return;
|
||||
}
|
||||
|
||||
this.schedule.data = newData.left;
|
||||
this.schedule.throwable = newData.right;
|
||||
|
||||
GenericDataLoadTask.this.processOnMain.queue();
|
||||
} else {
|
||||
GenericDataLoadTask.this.onComplete((TaskResult<FinalCompletion, Throwable>)newData);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final class ProcessOnMainTask implements Runnable {
|
||||
|
||||
private OnMain data;
|
||||
private Throwable throwable;
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
if (!GenericDataLoadTask.this.advanceStage(STAGE_PROCESSING, STAGE_COMPLETED)) {
|
||||
// cancelled
|
||||
return;
|
||||
}
|
||||
final TaskResult<FinalCompletion, Throwable> result = GenericDataLoadTask.this.runOnMain(this.data, this.throwable);
|
||||
|
||||
GenericDataLoadTask.this.onComplete(result);
|
||||
}
|
||||
}
|
||||
|
||||
protected static final class LoadDataFromDiskTask {
|
||||
|
||||
private volatile int priority;
|
||||
private static final VarHandle PRIORITY_HANDLE = ConcurrentUtil.getVarHandle(LoadDataFromDiskTask.class, "priority", int.class);
|
||||
|
||||
private static final int PRIORITY_EXECUTED = Integer.MIN_VALUE >>> 0;
|
||||
private static final int PRIORITY_LOAD_SCHEDULED = Integer.MIN_VALUE >>> 1;
|
||||
private static final int PRIORITY_UNLOAD_SCHEDULED = Integer.MIN_VALUE >>> 2;
|
||||
|
||||
private static final int PRIORITY_FLAGS = ~Character.MAX_VALUE;
|
||||
|
||||
private final int getPriorityVolatile() {
|
||||
return (int)PRIORITY_HANDLE.getVolatile((LoadDataFromDiskTask)this);
|
||||
}
|
||||
|
||||
private final int compareAndExchangePriorityVolatile(final int expect, final int update) {
|
||||
return (int)PRIORITY_HANDLE.compareAndExchange((LoadDataFromDiskTask)this, (int)expect, (int)update);
|
||||
}
|
||||
|
||||
private final int getAndOrPriorityVolatile(final int val) {
|
||||
return (int)PRIORITY_HANDLE.getAndBitwiseOr((LoadDataFromDiskTask)this, (int)val);
|
||||
}
|
||||
|
||||
private final void setPriorityPlain(final int val) {
|
||||
PRIORITY_HANDLE.set((LoadDataFromDiskTask)this, (int)val);
|
||||
}
|
||||
|
||||
private final ServerLevel world;
|
||||
private final int chunkX;
|
||||
private final int chunkZ;
|
||||
|
||||
private final RegionFileIOThread.RegionFileType type;
|
||||
private Cancellable dataLoadTask;
|
||||
private Cancellable dataUnloadCancellable;
|
||||
private DelayedPrioritisedTask dataUnloadTask;
|
||||
|
||||
private final BiConsumer<CompoundTag, Throwable> onComplete;
|
||||
private final AtomicBoolean scheduled = new AtomicBoolean();
|
||||
|
||||
// onComplete should be caller sensitive, it may complete synchronously with schedule() - which does
|
||||
// hold a priority lock.
|
||||
public LoadDataFromDiskTask(final ServerLevel world, final int chunkX, final int chunkZ,
|
||||
final RegionFileIOThread.RegionFileType type,
|
||||
final BiConsumer<CompoundTag, Throwable> onComplete,
|
||||
final PrioritisedExecutor.Priority priority) {
|
||||
if (!PrioritisedExecutor.Priority.isValidPriority(priority)) {
|
||||
throw new IllegalArgumentException("Invalid priority " + priority);
|
||||
}
|
||||
this.world = world;
|
||||
this.chunkX = chunkX;
|
||||
this.chunkZ = chunkZ;
|
||||
this.type = type;
|
||||
this.onComplete = onComplete;
|
||||
this.setPriorityPlain(priority.priority);
|
||||
}
|
||||
|
||||
private void complete(final CompoundTag data, final Throwable throwable) {
|
||||
try {
|
||||
this.onComplete.accept(data, throwable);
|
||||
} catch (final Throwable thr2) {
|
||||
((ChunkSystemServerLevel)this.world).moonrise$getChunkTaskScheduler().unrecoverableChunkSystemFailure(this.chunkX, this.chunkZ, Map.of(
|
||||
"Completed throwable", ChunkTaskScheduler.stringIfNull(throwable),
|
||||
"Regionfile type", ChunkTaskScheduler.stringIfNull(this.type)
|
||||
), thr2);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean markExecuting() {
|
||||
return (this.getAndOrPriorityVolatile(PRIORITY_EXECUTED) & PRIORITY_EXECUTED) == 0;
|
||||
}
|
||||
|
||||
private boolean isMarkedExecuted() {
|
||||
return (this.getPriorityVolatile() & PRIORITY_EXECUTED) != 0;
|
||||
}
|
||||
|
||||
public void lowerPriority(final PrioritisedExecutor.Priority priority) {
|
||||
if (!PrioritisedExecutor.Priority.isValidPriority(priority)) {
|
||||
throw new IllegalArgumentException("Invalid priority " + priority);
|
||||
}
|
||||
|
||||
int failures = 0;
|
||||
for (int curr = this.getPriorityVolatile();;) {
|
||||
if ((curr & PRIORITY_EXECUTED) != 0) {
|
||||
// cancelled or executed
|
||||
return;
|
||||
}
|
||||
|
||||
if ((curr & PRIORITY_LOAD_SCHEDULED) != 0) {
|
||||
RegionFileIOThread.lowerPriority(this.world, this.chunkX, this.chunkZ, this.type, priority);
|
||||
return;
|
||||
}
|
||||
|
||||
if ((curr & PRIORITY_UNLOAD_SCHEDULED) != 0) {
|
||||
if (this.dataUnloadTask != null) {
|
||||
this.dataUnloadTask.lowerPriority(priority);
|
||||
}
|
||||
// no return - we need to propagate priority
|
||||
}
|
||||
|
||||
if (!priority.isHigherPriority(curr & ~PRIORITY_FLAGS)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (curr == (curr = this.compareAndExchangePriorityVolatile(curr, priority.priority | (curr & PRIORITY_FLAGS)))) {
|
||||
return;
|
||||
}
|
||||
|
||||
// failed, retry
|
||||
|
||||
++failures;
|
||||
for (int i = 0; i < failures; ++i) {
|
||||
ConcurrentUtil.backoff();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void setPriority(final PrioritisedExecutor.Priority priority) {
|
||||
if (!PrioritisedExecutor.Priority.isValidPriority(priority)) {
|
||||
throw new IllegalArgumentException("Invalid priority " + priority);
|
||||
}
|
||||
|
||||
int failures = 0;
|
||||
for (int curr = this.getPriorityVolatile();;) {
|
||||
if ((curr & PRIORITY_EXECUTED) != 0) {
|
||||
// cancelled or executed
|
||||
return;
|
||||
}
|
||||
|
||||
if ((curr & PRIORITY_LOAD_SCHEDULED) != 0) {
|
||||
RegionFileIOThread.setPriority(this.world, this.chunkX, this.chunkZ, this.type, priority);
|
||||
return;
|
||||
}
|
||||
|
||||
if ((curr & PRIORITY_UNLOAD_SCHEDULED) != 0) {
|
||||
if (this.dataUnloadTask != null) {
|
||||
this.dataUnloadTask.setPriority(priority);
|
||||
}
|
||||
// no return - we need to propagate priority
|
||||
}
|
||||
|
||||
if (curr == (curr = this.compareAndExchangePriorityVolatile(curr, priority.priority | (curr & PRIORITY_FLAGS)))) {
|
||||
return;
|
||||
}
|
||||
|
||||
// failed, retry
|
||||
|
||||
++failures;
|
||||
for (int i = 0; i < failures; ++i) {
|
||||
ConcurrentUtil.backoff();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void raisePriority(final PrioritisedExecutor.Priority priority) {
|
||||
if (!PrioritisedExecutor.Priority.isValidPriority(priority)) {
|
||||
throw new IllegalArgumentException("Invalid priority " + priority);
|
||||
}
|
||||
|
||||
int failures = 0;
|
||||
for (int curr = this.getPriorityVolatile();;) {
|
||||
if ((curr & PRIORITY_EXECUTED) != 0) {
|
||||
// cancelled or executed
|
||||
return;
|
||||
}
|
||||
|
||||
if ((curr & PRIORITY_LOAD_SCHEDULED) != 0) {
|
||||
RegionFileIOThread.raisePriority(this.world, this.chunkX, this.chunkZ, this.type, priority);
|
||||
return;
|
||||
}
|
||||
|
||||
if ((curr & PRIORITY_UNLOAD_SCHEDULED) != 0) {
|
||||
if (this.dataUnloadTask != null) {
|
||||
this.dataUnloadTask.raisePriority(priority);
|
||||
}
|
||||
// no return - we need to propagate priority
|
||||
}
|
||||
|
||||
if (!priority.isLowerPriority(curr & ~PRIORITY_FLAGS)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (curr == (curr = this.compareAndExchangePriorityVolatile(curr, priority.priority | (curr & PRIORITY_FLAGS)))) {
|
||||
return;
|
||||
}
|
||||
|
||||
// failed, retry
|
||||
|
||||
++failures;
|
||||
for (int i = 0; i < failures; ++i) {
|
||||
ConcurrentUtil.backoff();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void cancel() {
|
||||
if ((this.getAndOrPriorityVolatile(PRIORITY_EXECUTED) & PRIORITY_EXECUTED) != 0) {
|
||||
// cancelled or executed already
|
||||
return;
|
||||
}
|
||||
|
||||
// OK if we miss the field read, the task cannot complete if the cancelled bit is set and
|
||||
// the write to dataLoadTask will check for the cancelled bit
|
||||
if (this.dataUnloadCancellable != null) {
|
||||
this.dataUnloadCancellable.cancel();
|
||||
}
|
||||
|
||||
if (this.dataLoadTask != null) {
|
||||
this.dataLoadTask.cancel();
|
||||
}
|
||||
|
||||
this.complete(CANCELLED_DATA, null);
|
||||
}
|
||||
|
||||
public void schedule() {
|
||||
if (this.scheduled.getAndSet(true)) {
|
||||
throw new IllegalStateException("schedule() called twice");
|
||||
}
|
||||
int priority = this.getPriorityVolatile();
|
||||
|
||||
if ((priority & PRIORITY_EXECUTED) != 0) {
|
||||
// cancelled
|
||||
return;
|
||||
}
|
||||
|
||||
final BiConsumer<CompoundTag, Throwable> consumer = (final CompoundTag data, final Throwable thr) -> {
|
||||
// because cancelScheduled() cannot actually stop this task from executing in every case, we need
|
||||
// to mark complete here to ensure we do not double complete
|
||||
if (LoadDataFromDiskTask.this.markExecuting()) {
|
||||
LoadDataFromDiskTask.this.complete(data, thr);
|
||||
} // else: cancelled
|
||||
};
|
||||
|
||||
final PrioritisedExecutor.Priority initialPriority = PrioritisedExecutor.Priority.getPriority(priority);
|
||||
boolean scheduledUnload = false;
|
||||
|
||||
final NewChunkHolder holder = ((ChunkSystemServerLevel)this.world).moonrise$getChunkTaskScheduler().chunkHolderManager.getChunkHolder(this.chunkX, this.chunkZ);
|
||||
if (holder != null) {
|
||||
final BiConsumer<CompoundTag, Throwable> unloadConsumer = (final CompoundTag data, final Throwable thr) -> {
|
||||
if (data != null) {
|
||||
consumer.accept(data, null);
|
||||
} else {
|
||||
// need to schedule task
|
||||
LoadDataFromDiskTask.this.schedule(false, consumer, PrioritisedExecutor.Priority.getPriority(LoadDataFromDiskTask.this.getPriorityVolatile() & ~PRIORITY_FLAGS));
|
||||
}
|
||||
};
|
||||
Cancellable unloadCancellable = null;
|
||||
CompoundTag syncComplete = null;
|
||||
final NewChunkHolder.UnloadTask unloadTask = holder.getUnloadTask(this.type); // can be null if no task exists
|
||||
final Completable<CompoundTag> unloadCompletable = unloadTask == null ? null : unloadTask.completable();
|
||||
if (unloadCompletable != null) {
|
||||
unloadCancellable = unloadCompletable.addAsynchronousWaiter(unloadConsumer);
|
||||
if (unloadCancellable == null) {
|
||||
syncComplete = unloadCompletable.getResult();
|
||||
}
|
||||
}
|
||||
|
||||
if (syncComplete != null) {
|
||||
consumer.accept(syncComplete, null);
|
||||
return;
|
||||
}
|
||||
|
||||
if (unloadCancellable != null) {
|
||||
scheduledUnload = true;
|
||||
this.dataUnloadCancellable = unloadCancellable;
|
||||
this.dataUnloadTask = unloadTask.task();
|
||||
}
|
||||
}
|
||||
|
||||
this.schedule(scheduledUnload, consumer, initialPriority);
|
||||
}
|
||||
|
||||
private void schedule(final boolean scheduledUnload, final BiConsumer<CompoundTag, Throwable> consumer, final PrioritisedExecutor.Priority initialPriority) {
|
||||
int priority = this.getPriorityVolatile();
|
||||
|
||||
if ((priority & PRIORITY_EXECUTED) != 0) {
|
||||
// cancelled
|
||||
return;
|
||||
}
|
||||
|
||||
if (!scheduledUnload) {
|
||||
this.dataLoadTask = RegionFileIOThread.loadDataAsync(
|
||||
this.world, this.chunkX, this.chunkZ, this.type, consumer,
|
||||
initialPriority.isHigherPriority(PrioritisedExecutor.Priority.NORMAL), initialPriority
|
||||
);
|
||||
}
|
||||
|
||||
int failures = 0;
|
||||
for (;;) {
|
||||
if (priority == (priority = this.compareAndExchangePriorityVolatile(priority, priority | (scheduledUnload ? PRIORITY_UNLOAD_SCHEDULED : PRIORITY_LOAD_SCHEDULED)))) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ((priority & PRIORITY_EXECUTED) != 0) {
|
||||
// cancelled or executed
|
||||
if (this.dataUnloadCancellable != null) {
|
||||
this.dataUnloadCancellable.cancel();
|
||||
}
|
||||
|
||||
if (this.dataLoadTask != null) {
|
||||
this.dataLoadTask.cancel();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (scheduledUnload) {
|
||||
if (this.dataUnloadTask != null) {
|
||||
this.dataUnloadTask.setPriority(PrioritisedExecutor.Priority.getPriority(priority & ~PRIORITY_FLAGS));
|
||||
}
|
||||
} else {
|
||||
RegionFileIOThread.setPriority(this.world, this.chunkX, this.chunkZ, this.type, PrioritisedExecutor.Priority.getPriority(priority & ~PRIORITY_FLAGS));
|
||||
}
|
||||
|
||||
++failures;
|
||||
for (int i = 0; i < failures; ++i) {
|
||||
ConcurrentUtil.backoff();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package ca.spottedleaf.moonrise.patches.chunk_system.server;
|
||||
|
||||
public interface ChunkSystemMinecraftServer {
|
||||
|
||||
public void moonrise$setChunkSystemCrash(final Throwable throwable);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package ca.spottedleaf.moonrise.patches.chunk_system.storage;
|
||||
|
||||
import net.minecraft.world.level.chunk.storage.RegionFileStorage;
|
||||
|
||||
public interface ChunkSystemChunkStorage {
|
||||
|
||||
public RegionFileStorage moonrise$getRegionStorage();
|
||||
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user