Skip to content

Commit 45d6b0c

Browse files
committed
Fixes chunk unloading - close #17
1 parent d596840 commit 45d6b0c

File tree

7 files changed

+159
-32
lines changed

7 files changed

+159
-32
lines changed
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package io.akarin.server.mixin.core;
2+
3+
import java.util.List;
4+
5+
import javax.annotation.Nullable;
6+
7+
import org.spongepowered.asm.mixin.Mixin;
8+
import org.spongepowered.asm.mixin.Shadow;
9+
10+
import net.minecraft.server.AxisAlignedBB;
11+
import net.minecraft.server.Entity;
12+
import net.minecraft.server.World;
13+
14+
/**
15+
* Fixes MC-103516(https://bugs.mojang.com/browse/MC-103516)
16+
*/
17+
@Mixin(value = World.class, remap = false)
18+
public abstract class MixinWorld {
19+
@Shadow public abstract List<Entity> getEntities(@Nullable Entity entity, AxisAlignedBB box);
20+
21+
/**
22+
* Returns true if there are no solid, live entities in the specified AxisAlignedBB, excluding the given entity
23+
*/
24+
public boolean a(AxisAlignedBB box, @Nullable Entity target) { // PAIL: checkNoEntityCollision
25+
List<Entity> list = this.getEntities(null, box);
26+
27+
for (Entity each : list) {
28+
if (!each.dead && each.i && each != target && (target == null || !each.x(target))) { // PAIL: preventEntitySpawning - isRidingSameEntity
29+
return false;
30+
}
31+
}
32+
return true;
33+
}
34+
}

sources/src/main/java/io/akarin/server/mixin/cps/MixinChunkProviderServer.java

Lines changed: 23 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@
1010
import org.spongepowered.asm.mixin.injection.At;
1111
import org.spongepowered.asm.mixin.injection.Redirect;
1212

13+
import it.unimi.dsi.fastutil.longs.Long2ObjectMap.Entry;
1314
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
15+
import it.unimi.dsi.fastutil.objects.ObjectIterator;
1416
import net.minecraft.server.Chunk;
1517
import net.minecraft.server.ChunkProviderServer;
1618
import net.minecraft.server.IChunkLoader;
@@ -21,13 +23,10 @@ public abstract class MixinChunkProviderServer {
2123
@Shadow @Final public WorldServer world;
2224
@Shadow public Long2ObjectOpenHashMap<Chunk> chunks;
2325

24-
public int pendingUnloadChunks; // For keeping unload target-size feature
25-
2626
public void unload(Chunk chunk) {
2727
if (this.world.worldProvider.c(chunk.locX, chunk.locZ)) {
2828
// Akarin - avoid using the queue and simply check the unloaded flag during unloads
2929
// this.unloadQueue.add(Long.valueOf(ChunkCoordIntPair.a(chunk.locX, chunk.locZ)));
30-
pendingUnloadChunks++;
3130
chunk.setShouldUnload(true);
3231
}
3332
}
@@ -42,46 +41,41 @@ public boolean unloadChunks() {
4241
long now = System.currentTimeMillis();
4342
long unloadAfter = world.paperConfig.delayChunkUnloadsBy;
4443
SlackActivityAccountant activityAccountant = world.getMinecraftServer().slackActivityAccountant;
45-
Iterator<Chunk> it = chunks.values().iterator();
44+
activityAccountant.startActivity(0.5);
45+
ObjectIterator<Entry<Chunk>> it = chunks.long2ObjectEntrySet().fastIterator();
46+
int remainingChunks = chunks.size();
47+
int targetSize = Math.min(remainingChunks - 100, (int) (remainingChunks * UNLOAD_QUEUE_RESIZE_FACTOR)); // Paper - Make more aggressive
4648

4749
while (it.hasNext()) {
48-
activityAccountant.startActivity(0.5);
50+
Entry<Chunk> entry = it.next();
51+
Chunk chunk = entry.getValue();
52+
if (chunk == null) continue;
4953

50-
Chunk chunk = it.next();
51-
if (unloadAfter > 0) {
52-
if (chunk.scheduledForUnload != null && now - chunk.scheduledForUnload > unloadAfter) {
53-
chunk.scheduledForUnload = null;
54-
unload(chunk);
54+
if (chunk.isUnloading()) {
55+
if (chunk.scheduledForUnload != null) {
56+
if (now - chunk.scheduledForUnload > unloadAfter) {
57+
chunk.scheduledForUnload = null;
58+
} else continue;
5559
}
56-
}
57-
int targetSize = Math.min(pendingUnloadChunks - 100, (int) (pendingUnloadChunks * UNLOAD_QUEUE_RESIZE_FACTOR)); // Paper - Make more aggressive
58-
59-
if (chunk != null && chunk.isUnloading()) {
60-
// If a plugin cancelled it, we shouldn't trying unload it for a while
61-
chunk.setShouldUnload(false); // Paper
6260

63-
if (!unloadChunk(chunk, true)) continue; // Event cancelled
64-
it.remove();
61+
if (!unloadChunk(chunk, true)) { // Event cancelled
62+
// If a plugin cancelled it, we shouldn't trying unload it for a while
63+
chunk.setShouldUnload(false);
64+
continue;
65+
}
6566

66-
if (--pendingUnloadChunks <= targetSize && activityAccountant.activityTimeIsExhausted()) break;
67+
it.remove(); // Life is strange
68+
if (--remainingChunks <= targetSize || activityAccountant.activityTimeIsExhausted()) break; // more slack since the target size not work as intended
6769
}
68-
activityAccountant.endActivity();
6970
}
71+
activityAccountant.endActivity();
7072
this.chunkLoader.b(); // PAIL: chunkTick
7173
}
7274
return false;
7375
}
7476

75-
@Redirect(method = "unloadChunk", at = @At(
76-
value = "INVOKE",
77-
target = "it/unimi/dsi/fastutil/longs/Long2ObjectOpenHashMap.remove(J)Ljava/lang/Object;"
78-
))
79-
private Object remove(Long2ObjectOpenHashMap<Chunk> chunks, long chunkHash) {
80-
return null;
81-
}
82-
8377
@Overwrite
8478
public String getName() {
85-
return "ServerChunkCache: " + chunks.size(); // Akarin - remove unload queue
79+
return "ServerChunkCache: " + chunks.size();
8680
}
8781
}

sources/src/main/java/io/akarin/server/mixin/lighting/MixinChunk.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -245,7 +245,7 @@ private void checkLightHead(CallbackInfo ci) {
245245
return;
246246
}
247247

248-
if (Akari.isPrimaryThread()) {
248+
if (Akari.isPrimaryThread(false)) {
249249
try {
250250
lightExecutorService.execute(() -> {
251251
this.checkLightAsync(neighborChunks);

sources/src/main/java/io/akarin/server/mixin/lighting/MixinWorld.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
import net.minecraft.server.MinecraftServer;
3535
import net.minecraft.server.World;
3636

37-
@Mixin(value = World.class, remap = false)
37+
@Mixin(value = World.class, remap = false, priority = 1001)
3838
public abstract class MixinWorld {
3939
@Shadow protected IChunkProvider chunkProvider;
4040
@Shadow int[] J; // PAIL: lightUpdateBlockList

sources/src/main/java/net/minecraft/server/Chunk.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ public TileEntity remove(Object key) {
7171
return removed;
7272
}
7373
}
74-
final PaperLightingQueue.LightingQueue lightingQueue = new PaperLightingQueue.LightingQueue(this);
74+
public final PaperLightingQueue.LightingQueue lightingQueue = new PaperLightingQueue.LightingQueue(this); // Akarin - public
7575
// Paper end
7676
private volatile boolean done; // Akarin - volatile
7777
private volatile boolean lit; // Akarin - volatile
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
package net.minecraft.server;
2+
3+
import co.aikar.timings.Timing;
4+
import it.unimi.dsi.fastutil.objects.ObjectCollection;
5+
6+
import java.util.ArrayDeque;
7+
8+
/**
9+
* <b>Akarin Changes Note</b><br>
10+
* <br>
11+
* 1) Expose private members<br>
12+
* @author cakoyo
13+
*/
14+
class PaperLightingQueue {
15+
private static final long MAX_TIME = (long) (1000000000 / 20 * .95);
16+
private static int updatesThisTick;
17+
18+
19+
static void processQueue(long curTime) {
20+
updatesThisTick = 0;
21+
22+
final long startTime = System.nanoTime();
23+
final long maxTickTime = MAX_TIME - (startTime - curTime);
24+
25+
START:
26+
for (World world : MinecraftServer.getServer().worlds) {
27+
if (!world.paperConfig.queueLightUpdates) {
28+
continue;
29+
}
30+
31+
ObjectCollection<Chunk> loadedChunks = ((WorldServer) world).getChunkProviderServer().chunks.values();
32+
for (Chunk chunk : loadedChunks.toArray(new Chunk[loadedChunks.size()])) {
33+
if (chunk.lightingQueue.processQueue(startTime, maxTickTime)) {
34+
break START;
35+
}
36+
}
37+
}
38+
}
39+
40+
public static class LightingQueue extends ArrayDeque<Runnable> { // Akarin - public
41+
final private Chunk chunk;
42+
43+
LightingQueue(Chunk chunk) {
44+
super();
45+
this.chunk = chunk;
46+
}
47+
48+
/**
49+
* Processes the lighting queue for this chunk
50+
*
51+
* @param startTime If start Time is 0, we will not limit execution time
52+
* @param maxTickTime Maximum time to spend processing lighting updates
53+
* @return true to abort processing furthur lighting updates
54+
*/
55+
private boolean processQueue(long startTime, long maxTickTime) {
56+
if (this.isEmpty()) {
57+
return false;
58+
}
59+
try (Timing ignored = chunk.world.timings.lightingQueueTimer.startTiming()) {
60+
Runnable lightUpdate;
61+
while ((lightUpdate = this.poll()) != null) {
62+
lightUpdate.run();
63+
if (startTime > 0 && ++PaperLightingQueue.updatesThisTick % 10 == 0 && PaperLightingQueue.updatesThisTick > 10) {
64+
if (System.nanoTime() - startTime > maxTickTime) {
65+
return true;
66+
}
67+
}
68+
}
69+
}
70+
71+
return false;
72+
}
73+
74+
/**
75+
* Flushes lighting updates to unload the chunk
76+
*/
77+
public void processUnload() { // Akarin - public
78+
if (!chunk.world.paperConfig.queueLightUpdates) {
79+
return;
80+
}
81+
processQueue(0, 0); // No timeout
82+
83+
final int radius = 1; // TODO: bitflip, why should this ever be 2?
84+
for (int x = chunk.locX - radius; x <= chunk.locX + radius; ++x) {
85+
for (int z = chunk.locZ - radius; z <= chunk.locZ + radius; ++z) {
86+
if (x == chunk.locX && z == chunk.locZ) {
87+
continue;
88+
}
89+
90+
Chunk neighbor = MCUtil.getLoadedChunkWithoutMarkingActive(chunk.world, x, z);
91+
if (neighbor != null) {
92+
neighbor.lightingQueue.processQueue(0, 0); // No timeout
93+
}
94+
}
95+
}
96+
}
97+
}
98+
}

sources/src/main/resources/mixins.akarin.core.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
"bootstrap.MetricsBootstrap",
1414
"bootstrap.MixinRestartCommand",
1515

16+
"core.MixinWorld",
1617
"core.MixinMCUtil",
1718
"core.MixinPlayerList",
1819
"core.MixinCommandBan",

0 commit comments

Comments
 (0)