Fixes chunk unloading - close #17
This commit is contained in:
@@ -0,0 +1,34 @@
|
|||||||
|
package io.akarin.server.mixin.core;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
import org.spongepowered.asm.mixin.Shadow;
|
||||||
|
|
||||||
|
import net.minecraft.server.AxisAlignedBB;
|
||||||
|
import net.minecraft.server.Entity;
|
||||||
|
import net.minecraft.server.World;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fixes MC-103516(https://bugs.mojang.com/browse/MC-103516)
|
||||||
|
*/
|
||||||
|
@Mixin(value = World.class, remap = false)
|
||||||
|
public abstract class MixinWorld {
|
||||||
|
@Shadow public abstract List<Entity> getEntities(@Nullable Entity entity, AxisAlignedBB box);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if there are no solid, live entities in the specified AxisAlignedBB, excluding the given entity
|
||||||
|
*/
|
||||||
|
public boolean a(AxisAlignedBB box, @Nullable Entity target) { // PAIL: checkNoEntityCollision
|
||||||
|
List<Entity> list = this.getEntities(null, box);
|
||||||
|
|
||||||
|
for (Entity each : list) {
|
||||||
|
if (!each.dead && each.i && each != target && (target == null || !each.x(target))) { // PAIL: preventEntitySpawning - isRidingSameEntity
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,7 +10,9 @@ import org.spongepowered.asm.mixin.Shadow;
|
|||||||
import org.spongepowered.asm.mixin.injection.At;
|
import org.spongepowered.asm.mixin.injection.At;
|
||||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||||
|
|
||||||
|
import it.unimi.dsi.fastutil.longs.Long2ObjectMap.Entry;
|
||||||
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
|
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
|
||||||
|
import it.unimi.dsi.fastutil.objects.ObjectIterator;
|
||||||
import net.minecraft.server.Chunk;
|
import net.minecraft.server.Chunk;
|
||||||
import net.minecraft.server.ChunkProviderServer;
|
import net.minecraft.server.ChunkProviderServer;
|
||||||
import net.minecraft.server.IChunkLoader;
|
import net.minecraft.server.IChunkLoader;
|
||||||
@@ -21,13 +23,10 @@ public abstract class MixinChunkProviderServer {
|
|||||||
@Shadow @Final public WorldServer world;
|
@Shadow @Final public WorldServer world;
|
||||||
@Shadow public Long2ObjectOpenHashMap<Chunk> chunks;
|
@Shadow public Long2ObjectOpenHashMap<Chunk> chunks;
|
||||||
|
|
||||||
public int pendingUnloadChunks; // For keeping unload target-size feature
|
|
||||||
|
|
||||||
public void unload(Chunk chunk) {
|
public void unload(Chunk chunk) {
|
||||||
if (this.world.worldProvider.c(chunk.locX, chunk.locZ)) {
|
if (this.world.worldProvider.c(chunk.locX, chunk.locZ)) {
|
||||||
// Akarin - avoid using the queue and simply check the unloaded flag during unloads
|
// Akarin - avoid using the queue and simply check the unloaded flag during unloads
|
||||||
// this.unloadQueue.add(Long.valueOf(ChunkCoordIntPair.a(chunk.locX, chunk.locZ)));
|
// this.unloadQueue.add(Long.valueOf(ChunkCoordIntPair.a(chunk.locX, chunk.locZ)));
|
||||||
pendingUnloadChunks++;
|
|
||||||
chunk.setShouldUnload(true);
|
chunk.setShouldUnload(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -42,46 +41,41 @@ public abstract class MixinChunkProviderServer {
|
|||||||
long now = System.currentTimeMillis();
|
long now = System.currentTimeMillis();
|
||||||
long unloadAfter = world.paperConfig.delayChunkUnloadsBy;
|
long unloadAfter = world.paperConfig.delayChunkUnloadsBy;
|
||||||
SlackActivityAccountant activityAccountant = world.getMinecraftServer().slackActivityAccountant;
|
SlackActivityAccountant activityAccountant = world.getMinecraftServer().slackActivityAccountant;
|
||||||
Iterator<Chunk> it = chunks.values().iterator();
|
activityAccountant.startActivity(0.5);
|
||||||
|
ObjectIterator<Entry<Chunk>> it = chunks.long2ObjectEntrySet().fastIterator();
|
||||||
|
int remainingChunks = chunks.size();
|
||||||
|
int targetSize = Math.min(remainingChunks - 100, (int) (remainingChunks * UNLOAD_QUEUE_RESIZE_FACTOR)); // Paper - Make more aggressive
|
||||||
|
|
||||||
while (it.hasNext()) {
|
while (it.hasNext()) {
|
||||||
activityAccountant.startActivity(0.5);
|
Entry<Chunk> entry = it.next();
|
||||||
|
Chunk chunk = entry.getValue();
|
||||||
|
if (chunk == null) continue;
|
||||||
|
|
||||||
Chunk chunk = it.next();
|
if (chunk.isUnloading()) {
|
||||||
if (unloadAfter > 0) {
|
if (chunk.scheduledForUnload != null) {
|
||||||
if (chunk.scheduledForUnload != null && now - chunk.scheduledForUnload > unloadAfter) {
|
if (now - chunk.scheduledForUnload > unloadAfter) {
|
||||||
chunk.scheduledForUnload = null;
|
chunk.scheduledForUnload = null;
|
||||||
unload(chunk);
|
} else continue;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
int targetSize = Math.min(pendingUnloadChunks - 100, (int) (pendingUnloadChunks * UNLOAD_QUEUE_RESIZE_FACTOR)); // Paper - Make more aggressive
|
|
||||||
|
|
||||||
if (chunk != null && chunk.isUnloading()) {
|
if (!unloadChunk(chunk, true)) { // Event cancelled
|
||||||
// If a plugin cancelled it, we shouldn't trying unload it for a while
|
// If a plugin cancelled it, we shouldn't trying unload it for a while
|
||||||
chunk.setShouldUnload(false); // Paper
|
chunk.setShouldUnload(false);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (!unloadChunk(chunk, true)) continue; // Event cancelled
|
it.remove(); // Life is strange
|
||||||
it.remove();
|
if (--remainingChunks <= targetSize || activityAccountant.activityTimeIsExhausted()) break; // more slack since the target size not work as intended
|
||||||
|
}
|
||||||
if (--pendingUnloadChunks <= targetSize && activityAccountant.activityTimeIsExhausted()) break;
|
|
||||||
}
|
}
|
||||||
activityAccountant.endActivity();
|
activityAccountant.endActivity();
|
||||||
}
|
|
||||||
this.chunkLoader.b(); // PAIL: chunkTick
|
this.chunkLoader.b(); // PAIL: chunkTick
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Redirect(method = "unloadChunk", at = @At(
|
|
||||||
value = "INVOKE",
|
|
||||||
target = "it/unimi/dsi/fastutil/longs/Long2ObjectOpenHashMap.remove(J)Ljava/lang/Object;"
|
|
||||||
))
|
|
||||||
private Object remove(Long2ObjectOpenHashMap<Chunk> chunks, long chunkHash) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Overwrite
|
@Overwrite
|
||||||
public String getName() {
|
public String getName() {
|
||||||
return "ServerChunkCache: " + chunks.size(); // Akarin - remove unload queue
|
return "ServerChunkCache: " + chunks.size();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -245,7 +245,7 @@ public abstract class MixinChunk implements IMixinChunk {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Akari.isPrimaryThread()) {
|
if (Akari.isPrimaryThread(false)) {
|
||||||
try {
|
try {
|
||||||
lightExecutorService.execute(() -> {
|
lightExecutorService.execute(() -> {
|
||||||
this.checkLightAsync(neighborChunks);
|
this.checkLightAsync(neighborChunks);
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ import net.minecraft.server.IChunkProvider;
|
|||||||
import net.minecraft.server.MinecraftServer;
|
import net.minecraft.server.MinecraftServer;
|
||||||
import net.minecraft.server.World;
|
import net.minecraft.server.World;
|
||||||
|
|
||||||
@Mixin(value = World.class, remap = false)
|
@Mixin(value = World.class, remap = false, priority = 1001)
|
||||||
public abstract class MixinWorld {
|
public abstract class MixinWorld {
|
||||||
@Shadow protected IChunkProvider chunkProvider;
|
@Shadow protected IChunkProvider chunkProvider;
|
||||||
@Shadow int[] J; // PAIL: lightUpdateBlockList
|
@Shadow int[] J; // PAIL: lightUpdateBlockList
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ public class Chunk {
|
|||||||
return removed;
|
return removed;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
final PaperLightingQueue.LightingQueue lightingQueue = new PaperLightingQueue.LightingQueue(this);
|
public final PaperLightingQueue.LightingQueue lightingQueue = new PaperLightingQueue.LightingQueue(this); // Akarin - public
|
||||||
// Paper end
|
// Paper end
|
||||||
private volatile boolean done; // Akarin - volatile
|
private volatile boolean done; // Akarin - volatile
|
||||||
private volatile boolean lit; // Akarin - volatile
|
private volatile boolean lit; // Akarin - volatile
|
||||||
|
|||||||
@@ -0,0 +1,98 @@
|
|||||||
|
package net.minecraft.server;
|
||||||
|
|
||||||
|
import co.aikar.timings.Timing;
|
||||||
|
import it.unimi.dsi.fastutil.objects.ObjectCollection;
|
||||||
|
|
||||||
|
import java.util.ArrayDeque;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <b>Akarin Changes Note</b><br>
|
||||||
|
* <br>
|
||||||
|
* 1) Expose private members<br>
|
||||||
|
* @author cakoyo
|
||||||
|
*/
|
||||||
|
class PaperLightingQueue {
|
||||||
|
private static final long MAX_TIME = (long) (1000000000 / 20 * .95);
|
||||||
|
private static int updatesThisTick;
|
||||||
|
|
||||||
|
|
||||||
|
static void processQueue(long curTime) {
|
||||||
|
updatesThisTick = 0;
|
||||||
|
|
||||||
|
final long startTime = System.nanoTime();
|
||||||
|
final long maxTickTime = MAX_TIME - (startTime - curTime);
|
||||||
|
|
||||||
|
START:
|
||||||
|
for (World world : MinecraftServer.getServer().worlds) {
|
||||||
|
if (!world.paperConfig.queueLightUpdates) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
ObjectCollection<Chunk> loadedChunks = ((WorldServer) world).getChunkProviderServer().chunks.values();
|
||||||
|
for (Chunk chunk : loadedChunks.toArray(new Chunk[loadedChunks.size()])) {
|
||||||
|
if (chunk.lightingQueue.processQueue(startTime, maxTickTime)) {
|
||||||
|
break START;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class LightingQueue extends ArrayDeque<Runnable> { // Akarin - public
|
||||||
|
final private Chunk chunk;
|
||||||
|
|
||||||
|
LightingQueue(Chunk chunk) {
|
||||||
|
super();
|
||||||
|
this.chunk = chunk;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Processes the lighting queue for this chunk
|
||||||
|
*
|
||||||
|
* @param startTime If start Time is 0, we will not limit execution time
|
||||||
|
* @param maxTickTime Maximum time to spend processing lighting updates
|
||||||
|
* @return true to abort processing furthur lighting updates
|
||||||
|
*/
|
||||||
|
private boolean processQueue(long startTime, long maxTickTime) {
|
||||||
|
if (this.isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
try (Timing ignored = chunk.world.timings.lightingQueueTimer.startTiming()) {
|
||||||
|
Runnable lightUpdate;
|
||||||
|
while ((lightUpdate = this.poll()) != null) {
|
||||||
|
lightUpdate.run();
|
||||||
|
if (startTime > 0 && ++PaperLightingQueue.updatesThisTick % 10 == 0 && PaperLightingQueue.updatesThisTick > 10) {
|
||||||
|
if (System.nanoTime() - startTime > maxTickTime) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Flushes lighting updates to unload the chunk
|
||||||
|
*/
|
||||||
|
public void processUnload() { // Akarin - public
|
||||||
|
if (!chunk.world.paperConfig.queueLightUpdates) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
processQueue(0, 0); // No timeout
|
||||||
|
|
||||||
|
final int radius = 1; // TODO: bitflip, why should this ever be 2?
|
||||||
|
for (int x = chunk.locX - radius; x <= chunk.locX + radius; ++x) {
|
||||||
|
for (int z = chunk.locZ - radius; z <= chunk.locZ + radius; ++z) {
|
||||||
|
if (x == chunk.locX && z == chunk.locZ) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
Chunk neighbor = MCUtil.getLoadedChunkWithoutMarkingActive(chunk.world, x, z);
|
||||||
|
if (neighbor != null) {
|
||||||
|
neighbor.lightingQueue.processQueue(0, 0); // No timeout
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,6 +13,7 @@
|
|||||||
"bootstrap.MetricsBootstrap",
|
"bootstrap.MetricsBootstrap",
|
||||||
"bootstrap.MixinRestartCommand",
|
"bootstrap.MixinRestartCommand",
|
||||||
|
|
||||||
|
"core.MixinWorld",
|
||||||
"core.MixinMCUtil",
|
"core.MixinMCUtil",
|
||||||
"core.MixinPlayerList",
|
"core.MixinPlayerList",
|
||||||
"core.MixinCommandBan",
|
"core.MixinCommandBan",
|
||||||
|
|||||||
Reference in New Issue
Block a user