Port Chunk System Rewrite

This commit is contained in:
Spottedleaf
2024-05-25 20:54:06 -07:00
parent 66f63e1296
commit 63a54a6f8a
114 changed files with 20158 additions and 1315 deletions

View File

@@ -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',

View File

@@ -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";
}

View File

@@ -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;
}
};
}
}

View File

@@ -0,0 +1,48 @@
package ca.spottedleaf.moonrise.common.map;
import it.unimi.dsi.fastutil.longs.Long2BooleanFunction;
import it.unimi.dsi.fastutil.longs.Long2BooleanLinkedOpenHashMap;
public final class SynchronisedLong2BooleanMap {
private final Long2BooleanLinkedOpenHashMap map = new Long2BooleanLinkedOpenHashMap();
private final int limit;
public SynchronisedLong2BooleanMap(final int limit) {
this.limit = limit;
}
// must hold lock on map
private void purgeEntries() {
while (this.map.size() > this.limit) {
this.map.removeLastBoolean();
}
}
public boolean remove(final long key) {
synchronized (this.map) {
return this.map.remove(key);
}
}
// note:
public boolean getOrCompute(final long key, final Long2BooleanFunction ifAbsent) {
synchronized (this.map) {
if (this.map.containsKey(key)) {
return this.map.getAndMoveToFirst(key);
}
}
final boolean put = ifAbsent.get(key);
synchronized (this.map) {
if (this.map.containsKey(key)) {
return this.map.getAndMoveToFirst(key);
}
this.map.putAndMoveToFirst(key, put);
this.purgeEntries();
return put;
}
}
}

View File

@@ -0,0 +1,47 @@
package ca.spottedleaf.moonrise.common.map;
import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap;
import java.util.function.BiFunction;
public final class SynchronisedLong2ObjectMap<V> {
private final Long2ObjectLinkedOpenHashMap<V> map = new Long2ObjectLinkedOpenHashMap<>();
private final int limit;
public SynchronisedLong2ObjectMap(final int limit) {
this.limit = limit;
}
// must hold lock on map
private void purgeEntries() {
while (this.map.size() > this.limit) {
this.map.removeLast();
}
}
public V get(final long key) {
synchronized (this.map) {
return this.map.getAndMoveToFirst(key);
}
}
public V put(final long key, final V value) {
synchronized (this.map) {
final V ret = this.map.putAndMoveToFirst(key, value);
this.purgeEntries();
return ret;
}
}
public V compute(final long key, final BiFunction<? super Long, ? super V, ? extends V> remappingFunction) {
synchronized (this.map) {
// first, compute the value - if one is added, it will be at the last entry
this.map.compute(key, remappingFunction);
// move the entry to first, just in case it was added at last
final V ret = this.map.getAndMoveToFirst(key);
// now purge the last entries
this.purgeEntries();
return ret;
}
}
}

View File

@@ -0,0 +1,75 @@
package ca.spottedleaf.moonrise.common.misc;
public final class AllocatingRateLimiter {
// max difference granularity in ns
private final long maxGranularity;
private double allocation = 0.0;
private long lastAllocationUpdate;
// the carry is used to store the remainder of the last take, so that the take amount remains the same (minus floating point error)
// over any time period using take regardless of the number of take calls or the intervals between the take calls
// i.e. take obtains 3.5 elements, stores 0.5 to this field for the next take() call to use and returns 3
private double takeCarry = 0.0;
private long lastTakeUpdate;
public AllocatingRateLimiter(final long maxGranularity) {
this.maxGranularity = maxGranularity;
}
public void reset(final long time) {
this.allocation = 0.0;
this.lastAllocationUpdate = time;
this.takeCarry = 0.0;
this.lastTakeUpdate = time;
}
// rate in units/s, and time in ns
public void tickAllocation(final long time, final double rate, final double maxAllocation) {
final long diff = Math.min(this.maxGranularity, time - this.lastAllocationUpdate);
this.lastAllocationUpdate = time;
this.allocation = Math.min(maxAllocation - this.takeCarry, this.allocation + rate * (diff*1.0E-9D));
}
public long previewAllocation(final long time, final double rate, final long maxTake) {
if (maxTake < 1L) {
return 0L;
}
final long diff = Math.min(this.maxGranularity, time - this.lastTakeUpdate);
// note: abs(takeCarry) <= 1.0
final double take = Math.min(
Math.min((double)maxTake - this.takeCarry, this.allocation),
rate * (diff*1.0E-9)
);
return (long)Math.floor(this.takeCarry + take);
}
// rate in units/s, and time in ns
public long takeAllocation(final long time, final double rate, final long maxTake) {
if (maxTake < 1L) {
return 0L;
}
double ret = this.takeCarry;
final long diff = Math.min(this.maxGranularity, time - this.lastTakeUpdate);
this.lastTakeUpdate = time;
// note: abs(takeCarry) <= 1.0
final double take = Math.min(
Math.min((double)maxTake - this.takeCarry, this.allocation),
rate * (diff*1.0E-9)
);
ret += take;
this.allocation -= take;
final long retInteger = (long)Math.floor(ret);
this.takeCarry = ret - (double)retInteger;
return retInteger;
}
}

View File

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

View File

@@ -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<>();
}

View File

@@ -0,0 +1,9 @@
package ca.spottedleaf.moonrise.common.util;
public final class MoonriseConstants {
public static final int MAX_VIEW_DISTANCE = 32;
private MoonriseConstants() {}
}

View File

@@ -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();
}
}

View File

@@ -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();
}

View File

@@ -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);
}
}

View File

@@ -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();
}
}

View File

@@ -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;
}
}

View File

@@ -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();
}
}

View File

@@ -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) {}
}

View File

@@ -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();
}
}

View File

@@ -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);
}
};
}
}

View File

@@ -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());
}
}

View File

@@ -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();
}
}

View File

@@ -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);
}
}

View File

@@ -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();
}
}

View File

@@ -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();
}
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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?
}

View File

@@ -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);
}
}

View File

@@ -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();
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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?
}

View File

@@ -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
}
}

View File

@@ -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();
}
}

View File

@@ -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();
}
}
}

View File

@@ -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;
}
}

View File

@@ -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();
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
});
}
}

View File

@@ -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();
}
}

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -225,6 +225,9 @@ public abstract class LevelLightEngineMixin implements LightEventListener, StarL
}
break;
}
default: {
throw new IllegalStateException("Unknown light type: " + lightType);
}
}
}

View File

@@ -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();
}
}

View File

@@ -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 {
/*

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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();
}
}

View File

@@ -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() {}
}

View File

@@ -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() {}
}

View File

@@ -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() {}
}

View File

@@ -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
) {}

View File

@@ -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();
}

View File

@@ -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;
}

View File

@@ -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;
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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();
}

View File

@@ -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();
}

View File

@@ -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();
}

View File

@@ -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();
}

View File

@@ -0,0 +1,7 @@
package ca.spottedleaf.moonrise.patches.chunk_system.level.chunk;
public interface ChunkSystemLevelChunk {
public boolean moonrise$isPostProcessingDone();
}

View File

@@ -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;
}
}
}
}

View File

@@ -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);
}
}
}
}

View File

@@ -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) {}
}
}

View File

@@ -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
}
}

View File

@@ -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);
}

View File

@@ -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();
}

View File

@@ -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;
}
}

View File

@@ -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;
}

View File

@@ -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();
}

View File

@@ -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;
}
}
}

View File

@@ -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]);
}
}
}

View File

@@ -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);
}

View File

@@ -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;
}
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}
}

View File

@@ -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();
}
}
}

View File

@@ -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() + "}";
}
}

View File

@@ -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);
}
}

View File

@@ -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();
}
}
}
}
}

View File

@@ -0,0 +1,7 @@
package ca.spottedleaf.moonrise.patches.chunk_system.server;
public interface ChunkSystemMinecraftServer {
public void moonrise$setChunkSystemCrash(final Throwable throwable);
}

View File

@@ -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