Compare commits

..

4 Commits

Author SHA1 Message Date
MrHua269
818e1132df Updated Upstream(Folia) 2025-06-12 08:03:39 +08:00
MrHua269
4cb423d24a Buffered linear region format 1.0 2025-06-12 07:34:35 +08:00
Helvetica Volubi
6eebbe4221 fix: refix a bug in remove config 2025-06-09 21:47:07 +08:00
Helvetica Volubi
85f3e2875f fix: fix a bug in remove config 2025-06-09 19:18:12 +08:00
23 changed files with 718 additions and 83 deletions

View File

@@ -2,7 +2,7 @@ group = me.earthme.luminol
version=1.21.5-R0.1-SNAPSHOT
mcVersion=1.21.5
foliaRef=3aba0068ded3f23bf8fa5cac6c70cb48f1d70478
foliaRef=77316d48f7d96ed7812cf36036fb85d790d20e6e
org.gradle.configuration-cache=true
org.gradle.caching=true

View File

@@ -1,17 +1,17 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: MrHua269 <wangxyper@163.com>
Date: Thu, 30 Jan 2025 09:29:03 +0800
From: MrHua269 <mrhua269@gmail.com>
Date: Thu, 12 Jun 2025 08:00:15 +0800
Subject: [PATCH] Purpur Lobotomize stuck villagers
diff --git a/src/main/java/org/bukkit/entity/Villager.java b/src/main/java/org/bukkit/entity/Villager.java
index 02b86d9615f8150b13ff0beefd5ca502c0494f99..3a444609ea9fdeee9057d593fbd4d38cc9e1ad68 100644
index 4d88bb2eaa43709fb6103a6f77d8c01e83bfe743..60b87d52c20cec947b196f47fc333bc643accbd2 100644
--- a/src/main/java/org/bukkit/entity/Villager.java
+++ b/src/main/java/org/bukkit/entity/Villager.java
@@ -391,4 +391,13 @@ public interface Villager extends AbstractVillager {
* reputation regardless of its impact and the player associated.
@@ -408,4 +408,13 @@ public interface Villager extends AbstractVillager {
* Demand is still updated even if all events are canceled.
*/
public void clearReputations();
public void restock();
+ // Purpur start
+
+ /**

View File

@@ -187,10 +187,10 @@ index bfd904e468bbf2cc1a5b3353d3a69ad5087c81ae..116933975ac975bb5a801be81e1c0e9b
+ // KioCG end
}
diff --git a/net/minecraft/world/entity/Entity.java b/net/minecraft/world/entity/Entity.java
index b5838a0320c729778f27f0d6a623eed4ef7c3a52..9122a78d08863cbc7321b7f7f2b6614e70dca846 100644
index 99301832bbb90f4ab00963f9062c54e829cc813b..46359300e533221cdc2d8aff9f9b98afe593c92b 100644
--- a/net/minecraft/world/entity/Entity.java
+++ b/net/minecraft/world/entity/Entity.java
@@ -5957,4 +5957,6 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
@@ -5961,4 +5961,6 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
return ((ServerLevel) this.level()).isPositionEntityTicking(this.blockPosition());
}
// Paper end - Expose entity id counter

View File

@@ -19,10 +19,10 @@ index ec4047312cf17f3ba91348ac8d71f747202bef87..56d00caf9db21798fdcbd6ec2cd84a84
);
});
diff --git a/net/minecraft/world/entity/Entity.java b/net/minecraft/world/entity/Entity.java
index b5838a0320c729778f27f0d6a623eed4ef7c3a52..18b04e13d0347935888b3f01fe35a39aeb49d94a 100644
index 46359300e533221cdc2d8aff9f9b98afe593c92b..cb347fe9e2876f3b26f004785c882f5faef560d7 100644
--- a/net/minecraft/world/entity/Entity.java
+++ b/net/minecraft/world/entity/Entity.java
@@ -4108,6 +4108,31 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
@@ -4112,6 +4112,31 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
}
// TODO any events that can modify go HERE
@@ -54,7 +54,7 @@ index b5838a0320c729778f27f0d6a623eed4ef7c3a52..18b04e13d0347935888b3f01fe35a39a
// check for same region
if (destination == this.level()) {
@@ -4224,7 +4249,18 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
@@ -4228,7 +4253,18 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
// we just select the spawn position
case END: {
if (destination.getTypeKey() == net.minecraft.world.level.dimension.LevelStem.END) {
@@ -74,7 +74,7 @@ index b5838a0320c729778f27f0d6a623eed4ef7c3a52..18b04e13d0347935888b3f01fe35a39a
// need to load chunks so we can create the platform
destination.moonrise$loadChunksAsync(
targetPos, 16, // load 16 blocks to be safe from block physics
@@ -4245,7 +4281,18 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
@@ -4249,7 +4285,18 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
}
);
} else {
@@ -94,7 +94,7 @@ index b5838a0320c729778f27f0d6a623eed4ef7c3a52..18b04e13d0347935888b3f01fe35a39a
// need to load chunk for heightmap
destination.moonrise$loadChunksAsync(
spawnPos, 0,
@@ -4296,8 +4343,18 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
@@ -4300,8 +4347,18 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
WorldBorder destinationBorder = destination.getWorldBorder();
double dimensionScale = net.minecraft.world.level.dimension.DimensionType.getTeleportationScale(origin.dimensionType(), destination.dimensionType());
@@ -114,7 +114,7 @@ index b5838a0320c729778f27f0d6a623eed4ef7c3a52..18b04e13d0347935888b3f01fe35a39a
ca.spottedleaf.concurrentutil.completable.CallbackCompletable<BlockUtil.FoundRectangle> portalFound
= new ca.spottedleaf.concurrentutil.completable.CallbackCompletable<>();
@@ -4434,6 +4491,15 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
@@ -4438,6 +4495,15 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
if (!this.canPortalAsync(destination, takePassengers)) {
return false;
}
@@ -130,7 +130,7 @@ index b5838a0320c729778f27f0d6a623eed4ef7c3a52..18b04e13d0347935888b3f01fe35a39a
Vec3 initialPosition = this.position();
ChunkPos initialPositionChunk = new ChunkPos(
@@ -4501,6 +4567,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
@@ -4505,6 +4571,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
if (teleportComplete != null) {
teleportComplete.accept(teleported);
}

View File

@@ -10,10 +10,10 @@ VMP (https://github.com/RelativityMC/VMP-fabric)
Licensed under: MIT (https://opensource.org/licenses/MIT)
diff --git a/net/minecraft/world/entity/Entity.java b/net/minecraft/world/entity/Entity.java
index 18b04e13d0347935888b3f01fe35a39aeb49d94a..97ef9c15618168a9b4f169a03f15d4e3e326f4f6 100644
index cb347fe9e2876f3b26f004785c882f5faef560d7..95fd1fc621a01d4a2a97e78f471a1d1a599db612 100644
--- a/net/minecraft/world/entity/Entity.java
+++ b/net/minecraft/world/entity/Entity.java
@@ -1081,7 +1081,14 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
@@ -1085,7 +1085,14 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
private double moveStartZ;
// Paper end - detailed watchdog information
@@ -28,7 +28,7 @@ index 18b04e13d0347935888b3f01fe35a39aeb49d94a..97ef9c15618168a9b4f169a03f15d4e3
final Vec3 originalMovement = movement; // Paper - Expose pre-collision velocity
// Paper start - detailed watchdog information
ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread("Cannot move an entity off-main");
@@ -5048,6 +5055,11 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
@@ -5052,6 +5059,11 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
}
public final void setBoundingBox(AABB bb) {

View File

@@ -8,10 +8,10 @@ As part of: Kaiiju (https://github.com/KaiijuMC/Kaiiju/blob/c2b7aec8f7b418a39a2e
Licensed under: GPL-3.0 (https://github.com/KaiijuMC/Kaiiju/blob/c2b7aec8f7b418a39a2ec408e6411e6f752379da/LICENSE)
diff --git a/net/minecraft/world/entity/Entity.java b/net/minecraft/world/entity/Entity.java
index 97ef9c15618168a9b4f169a03f15d4e3e326f4f6..96790ead67c29013302341422fd23d2cefd720bf 100644
index 95fd1fc621a01d4a2a97e78f471a1d1a599db612..75b5856b892272aaa70616c35dea75ba3ac76058 100644
--- a/net/minecraft/world/entity/Entity.java
+++ b/net/minecraft/world/entity/Entity.java
@@ -4273,14 +4273,18 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
@@ -4277,14 +4277,18 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
targetPos, 16, // load 16 blocks to be safe from block physics
ca.spottedleaf.concurrentutil.util.Priority.HIGH,
(chunks) -> {
@@ -34,7 +34,7 @@ index 97ef9c15618168a9b4f169a03f15d4e3e326f4f6..96790ead67c29013302341422fd23d2c
TeleportTransition.PLAY_PORTAL_SOUND.then(TeleportTransition.PLACE_PORTAL_TICKET),
org.bukkit.event.player.PlayerTeleportEvent.TeleportCause.END_PORTAL
)
@@ -4306,11 +4310,15 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
@@ -4310,11 +4314,15 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
ca.spottedleaf.concurrentutil.util.Priority.HIGH,
(chunks) -> {
BlockPos adjustedSpawn = destination.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, spawnPos);
@@ -52,7 +52,7 @@ index 97ef9c15618168a9b4f169a03f15d4e3e326f4f6..96790ead67c29013302341422fd23d2c
Relative.union(Relative.DELTA, Relative.ROTATION),
TeleportTransition.PLAY_PORTAL_SOUND.then(TeleportTransition.PLACE_PORTAL_TICKET),
org.bukkit.event.player.PlayerTeleportEvent.TeleportCause.END_PORTAL
@@ -4507,6 +4515,10 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
@@ -4511,6 +4519,10 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
return false;
}
// Luminol end
@@ -63,7 +63,7 @@ index 97ef9c15618168a9b4f169a03f15d4e3e326f4f6..96790ead67c29013302341422fd23d2c
Vec3 initialPosition = this.position();
ChunkPos initialPositionChunk = new ChunkPos(
@@ -4571,8 +4583,12 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
@@ -4575,8 +4587,12 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
info.postTeleportTransition().onTransition(teleported);
}

View File

@@ -46,7 +46,7 @@ index 8329bc0cf531a1317ff8e213e948019d28df1eea..84a6bf575902676fc06211562b578064
} else {entity.inactiveTick();} // Paper - EAR 2
profilerFiller.pop();
diff --git a/net/minecraft/world/entity/Entity.java b/net/minecraft/world/entity/Entity.java
index 96790ead67c29013302341422fd23d2cefd720bf..416d096cb4dfafeea625e70cdb420669442fdea9 100644
index 75b5856b892272aaa70616c35dea75ba3ac76058..cf634aa6898ccdb53a34a410266aeecb86c5f0de 100644
--- a/net/minecraft/world/entity/Entity.java
+++ b/net/minecraft/world/entity/Entity.java
@@ -350,6 +350,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
@@ -57,7 +57,7 @@ index 96790ead67c29013302341422fd23d2cefd720bf..416d096cb4dfafeea625e70cdb420669
public void inactiveTick() {
}
@@ -3223,6 +3224,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
@@ -3227,6 +3228,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
} else {
if (this.portalProcess == null || !this.portalProcess.isSamePortal(portal)) {
this.portalProcess = new PortalProcessor(portal, pos.immutable());

View File

@@ -6,10 +6,10 @@ Subject: [PATCH] Fix off tickregion sync teleport
Folis's teleportAsync implementation has some checks missing during the sync teleportation checks, if we are teleport to the edge of the tickregion, it is still asserting that we are in the same tickregion and moved us directly, but there is actually some logics is already touching the stuff out of current tickregion.So we added some new edge checks to the sync teleportation checks which will check the tickregion belonging in a shape of cycle which is in min(entity's bounding box + simulate distance, 6) of radius to fix that issue
diff --git a/net/minecraft/world/entity/Entity.java b/net/minecraft/world/entity/Entity.java
index 416d096cb4dfafeea625e70cdb420669442fdea9..dbc8d2729782dd073a03682d4e8c96c4e03ce1e6 100644
index cf634aa6898ccdb53a34a410266aeecb86c5f0de..4743de5c39ee52bdde1f70e340d547a6cc7f4ead 100644
--- a/net/minecraft/world/entity/Entity.java
+++ b/net/minecraft/world/entity/Entity.java
@@ -4024,6 +4024,21 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
@@ -4028,6 +4028,21 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
this.resetStoredPositions();
}
@@ -31,7 +31,7 @@ index 416d096cb4dfafeea625e70cdb420669442fdea9..dbc8d2729782dd073a03682d4e8c96c4
protected final void transform(TeleportTransition telpeort) {
PositionMoveRotation move = PositionMoveRotation.calculateAbsolute(
PositionMoveRotation.of(this), PositionMoveRotation.of(telpeort), telpeort.relatives()
@@ -4146,7 +4161,8 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
@@ -4150,7 +4165,8 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
// check for same region
if (destination == this.level()) {
Vec3 currPos = this.position();

View File

@@ -6,10 +6,10 @@ Subject: [PATCH] Teleport async if entity was moving to another region at once
On folia, entity usually cannot move out of the tickregion, but sometimes it actually does(like some end pearl gun that can shoot an end pearl to the block faraway than 10000 blocks even more). To fix this, we added a temporary fix which teleport these entities to the destination instead running its move logics so that we could ensure anything is under control.But one thing need to consider is that teleportAsync is actually calling halfway of the entity tick and there is still something running when teleportAsync called, which is actually modified the entity in another thread, so there is still need an improvement
diff --git a/net/minecraft/world/entity/Entity.java b/net/minecraft/world/entity/Entity.java
index dbc8d2729782dd073a03682d4e8c96c4e03ce1e6..a33994df200a93d7b9721cbe13d898d96af70844 100644
index 4743de5c39ee52bdde1f70e340d547a6cc7f4ead..a023cc399fc8bfee7771e5ca6716578f89b7072f 100644
--- a/net/minecraft/world/entity/Entity.java
+++ b/net/minecraft/world/entity/Entity.java
@@ -1084,6 +1084,10 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
@@ -1088,6 +1088,10 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
private boolean boundingBoxChanged = false; // Gale - VMP - skip entity move if movement is zero
@@ -20,7 +20,7 @@ index dbc8d2729782dd073a03682d4e8c96c4e03ce1e6..a33994df200a93d7b9721cbe13d898d9
public void move(MoverType type, Vec3 movement) {
// Gale start - VMP - skip entity move if movement is zero
if (!this.boundingBoxChanged && movement.equals(Vec3.ZERO)) {
@@ -1099,6 +1103,32 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
@@ -1103,6 +1107,32 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
this.moveStartZ = this.getZ();
this.moveVector = movement;
}

View File

@@ -1,39 +0,0 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: MrHua269 <mrhua269@gmail.com>
Date: Sun, 8 Jun 2025 22:11:27 +0800
Subject: [PATCH] Paper Backport fix for MC-296337
A part of Paper(https://github.com/PaperMC/Paper)
Diff was taken from : https://github.com/PaperMC/Paper/pull/12619/commits/9d0aef3b61f6e45ccee02ef9830f8402ada8d340
Original patch license: GPLv3 (https://github.com/PaperMC/Paper/blob/main/licenses/GPL.md)
Co-authored-by: electronicboy
diff --git a/net/minecraft/world/entity/Entity.java b/net/minecraft/world/entity/Entity.java
index b163c43f5398b9f38c75ae7af6a3015b686624ce..0d509b187c0ec357a7ccdf1e8b1f56d1e44398b9 100644
--- a/net/minecraft/world/entity/Entity.java
+++ b/net/minecraft/world/entity/Entity.java
@@ -361,6 +361,11 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
public final AABB getBoundingBoxAt(double x, double y, double z) {
return this.dimensions.makeBoundingBox(x, y, z);
}
+ // MC-296337
+ protected void clearMovementsThisTick() {
+ this.movementThisTick.clear();
+ }
+ // Paper end
// Paper end
// Paper start - rewrite chunk system
private final boolean isHardColliding = this.moonrise$isHardCollidingUncached();
diff --git a/net/minecraft/world/entity/vehicle/AbstractMinecart.java b/net/minecraft/world/entity/vehicle/AbstractMinecart.java
index 99617c08cbd989092ba357d8df928786fd04c89a..47490f6152cb1394a448ebc803c973b22da24149 100644
--- a/net/minecraft/world/entity/vehicle/AbstractMinecart.java
+++ b/net/minecraft/world/entity/vehicle/AbstractMinecart.java
@@ -429,6 +429,7 @@ public abstract class AbstractMinecart extends VehicleEntity {
public void applyEffectsFromBlocks() {
if (!useExperimentalMovement(this.level())) {
this.applyEffectsFromBlocks(this.position(), this.position());
+ this.clearMovementsThisTick(); // Paper - MC-296337
} else {
super.applyEffectsFromBlocks();
}

View File

@@ -18,7 +18,7 @@ index a0b84535a9d3833d4df692b85b272f145559dd80..c2ba46408b5ad727d7a17f21d47b2898
return;
}
diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
index 1091d2747c04166447540b37d86f51fe2591adc8..e0cee79b949fd2a4684bdfe7aa257c2ea96914b0 100644
index 7c30289ff28c4f0b91597da9c4aa192e7ff559cc..51543cc35e9158c3c083f4082304ecd4da5cf0a2 100644
--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java
+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
@@ -314,7 +314,7 @@ public final class CraftServer implements Server {

View File

@@ -23,7 +23,7 @@ index 631bec0adee5b01bfb931c25195b949eaf2efd27..05d364bcadb137af4e1a8c955643b7dc
@Override
diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
index e0cee79b949fd2a4684bdfe7aa257c2ea96914b0..047369190b8d03c5765595ba898da3a5c463a6c0 100644
index 51543cc35e9158c3c083f4082304ecd4da5cf0a2..e86347245b674b552a8d9e5a109ec8a41999f7ee 100644
--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java
+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
@@ -1392,7 +1392,11 @@ public final class CraftServer implements Server {

View File

@@ -1,16 +1,16 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: MrHua269 <wangxyper@163.com>
Date: Tue, 11 Feb 2025 12:01:39 +0800
From: MrHua269 <mrhua269@gmail.com>
Date: Thu, 12 Jun 2025 07:59:03 +0800
Subject: [PATCH] Purpur Lobotomize stuck villagers
diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftVillager.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftVillager.java
index e86f69f75d406b81d9ca32f9cad5e31cb8c55b54..9ca1d60cb7f3e65cdf491bd65c1c0c38cd2c73e8 100644
index 2ec652c1675a999d7cf157a5a002aba9d58afa0d..49bfeb81bdc998afc4aa55939840ac75397d8530 100644
--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftVillager.java
+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftVillager.java
@@ -381,4 +381,11 @@ public class CraftVillager extends CraftAbstractVillager implements Villager {
public void clearReputations() {
getHandle().getGossips().gossips.clear();
@@ -391,4 +391,11 @@ public class CraftVillager extends CraftAbstractVillager implements Villager {
public void restock() {
getHandle().restock();
}
+
+ // Purpur start

View File

@@ -1,6 +1,6 @@
--- /dev/null
+++ b/src/main/java/me/earthme/luminol/config/LuminolConfig.java
@@ -1,0 +_,379 @@
@@ -1,0 +_,382 @@
+package me.earthme.luminol.config;
+
+import com.electronwill.nightconfig.core.UnmodifiableConfig;
@@ -167,7 +167,10 @@
+ if (!removed && configFileInstance.get(fullConfigKeyName) != null) break;
+ }
+ }
+ if (removed) continue;
+ if (removed) {
+ configFileInstance.remove("removed");
+ continue;
+ }
+ if (configFileInstance.get(fullConfigKeyName) != null) continue;
+ if (currentValue == null) {
+ throw new UnsupportedOperationException("Config " + singleConfigModule.getBaseName() + "tried to add an null default value!");

View File

@@ -0,0 +1,632 @@
--- /dev/null
+++ b/src/main/java/me/earthme/luminol/data/BufferedLinearRegionFile.java
@@ -1,0 +_,629 @@
+package me.earthme.luminol.data;
+
+import abomination.IRegionFile;
+import ca.spottedleaf.moonrise.patches.chunk_system.io.MoonriseRegionFileIO;
+import me.earthme.luminol.utils.DirectBufferReleaser;
+import net.jpountz.xxhash.XXHash32;
+import net.jpountz.xxhash.XXHashFactory;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.world.level.ChunkPos;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.io.*;
+import java.nio.ByteBuffer;
+import java.nio.channels.FileChannel;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardOpenOption;
+import java.util.concurrent.locks.ReadWriteLock;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+
+public class BufferedLinearRegionFile implements IRegionFile {
+ private static final double AUTO_COMPACT_PERCENT = 3.0 / 5.0; // 60 %
+ private static final long AUTO_COMPACT_SIZE = 1024 * 1024; // 1 MiB
+
+ private static final long SUPER_BLOCK = 0x1145141919810L;
+ private static final int HASH_SEED = 0x0721; // (∠・ω< )⌒★
+ private static final byte VERSION = 0x01; // ver 1.0
+
+ private final Path filePath;
+
+ private final ReadWriteLock fileAccessLock = new ReentrantReadWriteLock();
+ private final XXHash32 xxHash32 = XXHashFactory.fastestInstance().hash32();
+ private final Sector[] sectors = new Sector[1024];
+ private long currentAcquiredIndex = this.headerSize();
+ private byte compressionLevel = 6;
+ private int xxHash32Seed = HASH_SEED;
+ private FileChannel channel;
+
+ public BufferedLinearRegionFile(Path filePath, int compressionLevel) throws IOException {
+ this(filePath);
+
+ this.compressionLevel = (byte) compressionLevel;
+ }
+
+ public BufferedLinearRegionFile(Path filePath) throws IOException {
+ this.channel = FileChannel.open(
+ filePath,
+ StandardOpenOption.CREATE,
+ StandardOpenOption.WRITE,
+ StandardOpenOption.READ
+ );
+ this.filePath = filePath;
+
+ // fill default sectors
+ for (int i = 0; i < 1024; i++) {
+ this.sectors[i] = new Sector(i, this.headerSize(), 0);
+ }
+
+ // load sectors
+ this.readHeaders();
+ }
+
+ private void readHeaders() throws IOException {
+ if (this.channel.size() < this.headerSize()) {
+ return;
+ }
+
+ final ByteBuffer buffer = ByteBuffer.allocateDirect(this.headerSize());
+ this.channel.read(buffer, 0);
+ buffer.flip();
+
+ if (buffer.getLong() != SUPER_BLOCK || buffer.get() != VERSION) {
+ throw new IOException("Invalid file format or version mismatch");
+ }
+
+ this.compressionLevel = buffer.get(); // Compression level (not used)
+ this.xxHash32Seed = buffer.getInt(); // XXHash32 seed
+ this.currentAcquiredIndex = buffer.getLong(); // Acquired index
+
+ for (Sector sector : this.sectors) {
+ sector.restoreFrom(buffer);
+ if (sector.hasData()) {
+ // recompute if acquired index is corrupted
+ this.currentAcquiredIndex = Math.max(this.currentAcquiredIndex, sector.offset + sector.length);
+ }
+ }
+
+ DirectBufferReleaser.clean(buffer);
+ }
+
+ private void writeHeaders() throws IOException {
+ final ByteBuffer buffer = ByteBuffer.allocateDirect(this.headerSize());
+
+ buffer.putLong(SUPER_BLOCK); // Magic
+ buffer.put(VERSION); // Version
+ buffer.put(this.compressionLevel); // Compression level
+ buffer.putInt(this.xxHash32Seed); // XXHash32 seed
+ buffer.putLong(this.currentAcquiredIndex); // Acquired index
+
+ for (Sector sector : this.sectors) {
+ // encode each sector
+ buffer.put(sector.getEncoded());
+ }
+
+ buffer.flip();
+
+ long offset = 0;
+ while (buffer.hasRemaining()) {
+ offset += this.channel.write(buffer, offset);
+ }
+
+ DirectBufferReleaser.clean(buffer);
+ }
+
+ private int sectorSize() {
+ return this.sectors.length * Sector.sizeOfSingle();
+ }
+
+ private int headerSize() {
+ int result = 0;
+
+ result += Long.BYTES; // Magic
+ result += Byte.BYTES; // Version
+ result += Byte.BYTES; // Compression level
+ result += Integer.BYTES; // XXHash32 seed
+ result += Long.BYTES; // Acquired index
+ result += this.sectorSize(); // Sectors
+
+ return result;
+ }
+
+ private void flushInternal() throws IOException {
+ // save headers
+ this.writeHeaders();
+
+ long spareSize = this.channel.size();
+
+ spareSize -= this.headerSize();
+ for (Sector sector : this.sectors) {
+ spareSize -= sector.length;
+ }
+
+ long sectorSize = 0;
+ for (Sector sector : this.sectors) {
+ sectorSize += sector.length;
+ }
+
+ // try auto compact to clean the garbage area
+ if (spareSize > AUTO_COMPACT_SIZE && (double)spareSize > ((double)sectorSize) * AUTO_COMPACT_PERCENT) {
+ this.compact();
+ }
+ }
+
+ private void closeInternal() throws IOException {
+ this.writeHeaders();
+ this.channel.force(true);
+ // force compact
+ this.compact();
+ this.channel.close();
+ }
+
+ private void compact() throws IOException {
+ this.writeHeaders(); // save headers for compact
+ this.channel.force(true);
+ try (FileChannel tempChannel = FileChannel.open(
+ new File(this.filePath.toString() + ".tmp").toPath(),
+ StandardOpenOption.CREATE,
+ StandardOpenOption.WRITE,
+ StandardOpenOption.READ
+ )){
+ // get the latest head in file
+ final ByteBuffer headerBuffer = ByteBuffer.allocateDirect(this.headerSize());
+ this.channel.read(headerBuffer, 0);
+ headerBuffer.flip();
+
+ long offsetHeader = 0;
+ while (headerBuffer.hasRemaining()) {
+ offsetHeader += tempChannel.write(headerBuffer, offsetHeader);
+ }
+ DirectBufferReleaser.clean(headerBuffer);
+
+ int offsetPointer = this.headerSize();
+ for (Sector sector : this.sectors) {
+ // skip cleared or no data-contained sectors
+ if (!sector.hasData()) {
+ continue;
+ }
+
+ // only read the available data
+ final ByteBuffer sectorData = sector.read(this.channel);
+ final int length = sectorData.remaining();
+
+ // recalculate the offset and length
+ final Sector newRecalculated = new Sector(sector.index, offsetPointer, length);
+ offsetPointer += length;
+ this.sectors[sector.index] = newRecalculated; // update sector infos
+
+ newRecalculated.hasData = true;
+
+ long offset = newRecalculated.offset;
+ while (sectorData.hasRemaining()) {
+ offset += tempChannel.write(sectorData, offset);
+ }
+
+ DirectBufferReleaser.clean(sectorData);
+ }
+
+ tempChannel.force(true);
+ this.currentAcquiredIndex = tempChannel.size();
+ }
+
+ Files.move(
+ new File(this.filePath.toString() + ".tmp").toPath(),
+ this.filePath,
+ java.nio.file.StandardCopyOption.REPLACE_EXISTING
+ );
+
+ this.reopenChannel();
+ this.writeHeaders();
+ }
+
+ private void reopenChannel() throws IOException {
+ if (this.channel.isOpen()) {
+ this.channel.close();
+ }
+
+ this.channel = FileChannel.open(
+ filePath,
+ StandardOpenOption.CREATE,
+ StandardOpenOption.WRITE,
+ StandardOpenOption.READ
+ );
+ }
+
+ private void writeChunkDataRaw(int chunkOrdinal, ByteBuffer chunkData) throws IOException {
+ final Sector sector = this.sectors[chunkOrdinal];
+
+ sector.store(chunkData, this.channel);
+ }
+
+ private @Nullable ByteBuffer readChunkDataRaw(int chunkOrdinal) throws IOException {
+ final Sector sector = this.sectors[chunkOrdinal];
+
+ if (!sector.hasData()) {
+ return null;
+ }
+
+ return sector.read(this.channel);
+ }
+
+ private void clearChunkData(int chunkOrdinal) throws IOException {
+ final Sector sector = this.sectors[chunkOrdinal];
+
+ sector.clear();
+
+ this.writeHeaders();
+ }
+
+ private static int getChunkIndex(int x, int z) {
+ return (x & 31) + ((z & 31) << 5);
+ }
+
+ private boolean hasData(int chunkOriginal) {
+ return this.sectors[chunkOriginal].hasData();
+ }
+
+ private void writeChunk(int x, int z, @NotNull ByteBuffer data) throws IOException {
+ final int chunkIndex = getChunkIndex(x, z);
+
+ final int oldPositionOfData = data.position();
+ final int xxHash32OfData = this.xxHash32.hash(data, this.xxHash32Seed);
+ data.position(oldPositionOfData);
+
+ final ByteBuffer compressedData = this.compress(this.ensureDirectBuffer(data));
+ // uncompressed length + timestamp + xxhash32
+ final ByteBuffer chunkSectionBuilder = ByteBuffer.allocateDirect(compressedData.remaining() + 4 + 8 + 4);
+
+ chunkSectionBuilder.putInt(data.remaining()); // Uncompressed length
+ chunkSectionBuilder.putLong(System.nanoTime()); // Timestamp
+ chunkSectionBuilder.putInt(xxHash32OfData); // xxHash32 of the original data
+ chunkSectionBuilder.put(compressedData); // Compressed data
+ chunkSectionBuilder.flip();
+
+ this.writeChunkDataRaw(chunkIndex, chunkSectionBuilder);
+ DirectBufferReleaser.clean(chunkSectionBuilder);
+ }
+
+ private @Nullable ByteBuffer readChunk(int x, int z) throws IOException {
+ final ByteBuffer compressed = this.readChunkDataRaw(getChunkIndex(x, z));
+
+ if (compressed == null) {
+ return null;
+ }
+
+ final int uncompressedLength = compressed.getInt(); // compressed length
+ final long timestamp = compressed.getLong(); // TODO use this timestamp for something?
+ final int dataXXHash32 = compressed.getInt(); // XXHash32 for validation
+
+ final ByteBuffer decompressed = this.decompress(this.ensureDirectBuffer(compressed), uncompressedLength);
+
+ DirectBufferReleaser.clean(compressed);
+
+ final IOException xxHash32CheckFailedEx = this.checkXXHash32(dataXXHash32, decompressed);
+ if (xxHash32CheckFailedEx != null) {
+ throw xxHash32CheckFailedEx; // prevent from loading
+ }
+
+ return decompressed;
+ }
+
+ private @NotNull ByteBuffer ensureDirectBuffer(@NotNull ByteBuffer buffer) {
+ if (buffer.isDirect()) {
+ return buffer;
+ }
+
+ ByteBuffer direct = ByteBuffer.allocateDirect(buffer.remaining());
+ int originalPosition = buffer.position();
+ direct.put(buffer);
+ direct.flip();
+ buffer.position(originalPosition);
+
+ return direct;
+ }
+
+ private @NotNull ByteBuffer compress(@NotNull ByteBuffer input) throws IOException {
+ final int originalPosition = input.position();
+ final int originalLimit = input.limit();
+
+ try {
+ byte[] inputArray;
+ int inputLength = input.remaining();
+ if (input.hasArray()) {
+ inputArray = input.array();
+ int arrayOffset = input.arrayOffset() + input.position();
+ if (arrayOffset != 0 || inputLength != inputArray.length) {
+ byte[] temp = new byte[inputLength];
+ System.arraycopy(inputArray, arrayOffset, temp, 0, inputLength);
+ inputArray = temp;
+ }
+ } else {
+ inputArray = new byte[inputLength];
+ input.get(inputArray);
+ input.position(originalPosition);
+ }
+
+ byte[] compressed = com.github.luben.zstd.Zstd.compress(inputArray, this.compressionLevel);
+
+ ByteBuffer result = ByteBuffer.allocateDirect(compressed.length);
+ result.put(compressed);
+ result.flip();
+
+ return result;
+
+ } catch (Exception e) {
+ throw new IOException("Compression failed for input size: " + input.remaining(), e);
+ } finally {
+ input.position(originalPosition);
+ input.limit(originalLimit);
+ }
+ }
+
+ private @NotNull ByteBuffer decompress(@NotNull ByteBuffer input, int originalSize) throws IOException {
+ final int originalPosition = input.position();
+ final int originalLimit = input.limit();
+
+ try {
+ byte[] inputArray;
+ int inputLength = input.remaining();
+
+ if (input.hasArray()) {
+ inputArray = input.array();
+ int arrayOffset = input.arrayOffset() + input.position();
+ if (arrayOffset != 0 || inputLength != inputArray.length) {
+ byte[] temp = new byte[inputLength];
+ System.arraycopy(inputArray, arrayOffset, temp, 0, inputLength);
+ inputArray = temp;
+ }
+ } else {
+ inputArray = new byte[inputLength];
+ input.get(inputArray);
+ input.position(originalPosition);
+ }
+
+ byte[] decompressed = com.github.luben.zstd.Zstd.decompress(inputArray, originalSize);
+
+ if (decompressed.length != originalSize) {
+ throw new IOException("Decompression size mismatch: expected " +
+ originalSize + ", got " + decompressed.length);
+ }
+
+ ByteBuffer result = ByteBuffer.allocateDirect(originalSize);
+ result.put(decompressed);
+ result.flip();
+
+ return result;
+
+ } catch (Exception e) {
+ throw new IOException("Decompression failed", e);
+ } finally {
+ input.position(originalPosition);
+ input.limit(originalLimit);
+ }
+ }
+
+ private @Nullable IOException checkXXHash32(long originalXXHash32, @NotNull ByteBuffer input) {
+ final int oldPositionOfInput = input.position();
+ final int currentXXHash32 = this.xxHash32.hash(input, this.xxHash32Seed);
+ input.position(oldPositionOfInput);
+
+ if (originalXXHash32 != currentXXHash32) {
+ return new IOException("XXHash32 check failed ! Expected: " + originalXXHash32 + ",but got: " + currentXXHash32);
+ }
+
+ return null;
+ }
+
+ @Override
+ public Path getPath() {
+ return this.filePath;
+ }
+
+ @Override
+ public DataInputStream getChunkDataInputStream(@NotNull ChunkPos pos) throws IOException {
+ this.fileAccessLock.readLock().lock();
+ try {
+ final ByteBuffer data = this.readChunk(pos.x, pos.z);
+
+ if (data == null) {
+ return null;
+ }
+
+ final byte[] dataBytes = new byte[data.remaining()];
+ data.get(dataBytes);
+
+ DirectBufferReleaser.clean(data);
+
+ return new DataInputStream(new ByteArrayInputStream(dataBytes));
+ }finally {
+ this.fileAccessLock.readLock().unlock();
+ }
+ }
+
+ @Override
+ public boolean doesChunkExist(@NotNull ChunkPos pos) {
+ this.fileAccessLock.readLock().lock();
+ try {
+ return this.hasData(getChunkIndex(pos.x, pos.z));
+ }finally {
+ this.fileAccessLock.readLock().unlock();
+ }
+ }
+
+ @Override
+ public DataOutputStream getChunkDataOutputStream(ChunkPos pos) {
+ return new DataOutputStream(new ChunkBufferHelper(pos));
+ }
+
+ @Override
+ public void clear(@NotNull ChunkPos pos) throws IOException {
+ this.fileAccessLock.writeLock().lock();
+ try {
+ this.clearChunkData(getChunkIndex(pos.x, pos.z));
+ }finally {
+ this.fileAccessLock.writeLock().unlock();
+ }
+ }
+
+ @Override
+ public boolean hasChunk(@NotNull ChunkPos pos) {
+ this.fileAccessLock.readLock().lock();
+ try {
+ return this.hasData(getChunkIndex(pos.x, pos.z));
+ }finally {
+ this.fileAccessLock.readLock().unlock();
+ }
+ }
+
+ @Override
+ public void write(@NotNull ChunkPos pos, ByteBuffer buf) throws IOException {
+ this.fileAccessLock.writeLock().lock();
+ try {
+ this.writeChunk(pos.x, pos.z, buf);
+ }finally {
+ this.fileAccessLock.writeLock().unlock();
+ }
+ }
+
+ // MCC 的玩意,这东西也用不上给Linear了()
+ @Override
+ public CompoundTag getOversizedData(int x, int z) {
+ return null;
+ }
+
+ @Override
+ public boolean isOversized(int x, int z) {
+ return false;
+ }
+
+ @Override
+ public boolean recalculateHeader() {
+ return false;
+ }
+
+ @Override
+ public void setOversized(int x, int z, boolean oversized) {
+
+ }
+ // MCC end
+
+ @Override
+ public MoonriseRegionFileIO.RegionDataController.WriteData moonrise$startWrite(CompoundTag data, ChunkPos pos) {
+ final DataOutputStream out = this.getChunkDataOutputStream(pos);
+
+ return new ca.spottedleaf.moonrise.patches.chunk_system.io.MoonriseRegionFileIO.RegionDataController.WriteData(
+ data, ca.spottedleaf.moonrise.patches.chunk_system.io.MoonriseRegionFileIO.RegionDataController.WriteData.WriteResult.WRITE,
+ out, regionFile -> out.close()
+ );
+ }
+
+ @Override
+ public void flush() throws IOException {
+ this.fileAccessLock.writeLock().lock();
+ try {
+ this.flushInternal();
+ }finally {
+ this.fileAccessLock.writeLock().unlock();
+ }
+ }
+
+ @Override
+ public void close() throws IOException {
+ this.fileAccessLock.writeLock().lock();
+ try {
+ this.closeInternal();
+ }finally {
+ this.fileAccessLock.writeLock().unlock();
+ }
+ }
+
+ private class Sector{
+ private final int index;
+ private long offset;
+ private long length;
+ private boolean hasData = false;
+
+ private Sector(int index, long offset, long length) {
+ this.index = index;
+ this.offset = offset;
+ this.length = length;
+ }
+
+ public @NotNull ByteBuffer read(@NotNull FileChannel channel) throws IOException {
+ final ByteBuffer result = ByteBuffer.allocateDirect((int) this.length);
+
+ channel.read(result, this.offset);
+ result.flip();
+
+ return result;
+ }
+
+ public void store(@NotNull ByteBuffer newData, @NotNull FileChannel channel) throws IOException {
+ this.hasData = true;
+ this.length = newData.remaining();
+ this.offset = currentAcquiredIndex;
+
+ BufferedLinearRegionFile.this.currentAcquiredIndex += this.length;
+
+ long offset = this.offset;
+ while (newData.hasRemaining()) {
+ offset += channel.write(newData, offset);
+ }
+ }
+
+ private @NotNull ByteBuffer getEncoded() {
+ final ByteBuffer buffer = ByteBuffer.allocateDirect(sizeOfSingle());
+
+ buffer.putLong(this.offset);
+ buffer.putLong(this.length);
+ buffer.put((byte) (this.hasData ? 1 : 0));
+ buffer.flip();
+
+ return buffer;
+ }
+
+ public void restoreFrom(@NotNull ByteBuffer buffer) {
+ this.offset = buffer.getLong();
+ this.length = buffer.getLong();
+ this.hasData = buffer.get() == 1;
+
+ if (this.length < 0 || this.offset < 0) {
+ throw new IllegalStateException("Invalid sector data: " + this);
+ }
+ }
+
+ public void clear() {
+ this.hasData = false;
+ }
+
+ public boolean hasData() {
+ return this.hasData;
+ }
+
+ static int sizeOfSingle() {
+ // offset + length hasData
+ return Long.BYTES * 2 + 1;
+ }
+ }
+
+ private class ChunkBufferHelper extends ByteArrayOutputStream {
+ private final ChunkPos pos;
+
+ private ChunkBufferHelper(ChunkPos pos) {
+ this.pos = pos;
+ }
+
+ @Override
+ public void close() throws IOException {
+ BufferedLinearRegionFile.this.fileAccessLock.writeLock().lock();
+ try {
+ ByteBuffer bytebuffer = ByteBuffer.wrap(this.buf, 0, this.count);
+
+ BufferedLinearRegionFile.this.writeChunk(this.pos.x, this.pos.z, bytebuffer);
+ }finally {
+ BufferedLinearRegionFile.this.fileAccessLock.writeLock().unlock();
+ }
+ }
+ }
+}

View File

@@ -0,0 +1,37 @@
--- /dev/null
+++ b/src/main/java/me/earthme/luminol/utils/DirectBufferReleaser.java
@@ -1,0 +_,34 @@
+package me.earthme.luminol.utils;
+
+import org.jetbrains.annotations.NotNull;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.nio.ByteBuffer;
+
+public class DirectBufferReleaser {
+ private static final Method CLEANER_METHOD;
+ private static final Object UNSAFE;
+
+ static {
+ try {
+ Class<?> unsafeClass = Class.forName("sun.misc.Unsafe");
+ Field theUnsafe = unsafeClass.getDeclaredField("theUnsafe");
+ theUnsafe.setAccessible(true);
+ UNSAFE = theUnsafe.get(null);
+ CLEANER_METHOD = unsafeClass.getMethod("invokeCleaner", ByteBuffer.class);
+ } catch (Exception ex) {
+ throw new RuntimeException("Unsafe init failed", ex);
+ }
+ }
+
+ public static boolean clean(@NotNull ByteBuffer buffer) {
+ if (!buffer.isDirect()) return false;
+ try {
+ CLEANER_METHOD.invoke(UNSAFE, buffer);
+ return true;
+ } catch (Exception e) {
+ return false;
+ }
+ }
+}

View File

@@ -1,16 +1,18 @@
--- /dev/null
+++ b/src/main/java/me/earthme/luminol/utils/EnumRegionFormat.java
@@ -1,0 +_,40 @@
@@ -1,0 +_,42 @@
+package me.earthme.luminol.utils;
+
+import abomination.LinearRegionFile;
+import me.earthme.luminol.config.modules.misc.RegionFormatConfig;
+import me.earthme.luminol.data.BufferedLinearRegionFile;
+import net.minecraft.world.level.chunk.storage.RegionFile;
+import org.jetbrains.annotations.Nullable;
+
+public enum EnumRegionFormat {
+ MCA("mca", "mca", (info) -> new RegionFile(info.info(), info.filePath(), info.folder(), info.sync())),
+ LINEAR_V2("linear_v2", "linear", (info) -> new LinearRegionFile(info.info(), info.filePath(), info.folder(), info.sync(), RegionFormatConfig.linearCompressionLevel));
+ LINEAR_V2("linear_v2", "linear", (info) -> new LinearRegionFile(info.info(), info.filePath(), info.folder(), info.sync(), RegionFormatConfig.linearCompressionLevel)),
+ B_LINEAR("b_linear", "b_linear", (info) -> new BufferedLinearRegionFile(info.filePath(), RegionFormatConfig.linearCompressionLevel));
+
+ private final String name;
+ private final String argument;