diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index efbef54..2d8175c 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -13,7 +13,9 @@ jobs: steps: - name: Setup Action uses: actions/checkout@v4 - + with: + fetch-depth: 0 + - name: Setup Gradle uses: gradle/gradle-build-action@v2 diff --git a/divinemc-server/minecraft-patches/features/0015-Lag-Compensation.patch b/divinemc-server/minecraft-patches/features/0015-Lag-Compensation.patch new file mode 100644 index 0000000..eaf523c --- /dev/null +++ b/divinemc-server/minecraft-patches/features/0015-Lag-Compensation.patch @@ -0,0 +1,56 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: NONPLAYT <76615486+NONPLAYT@users.noreply.github.com> +Date: Sun, 19 Jan 2025 02:48:59 +0300 +Subject: [PATCH] Lag Compensation + + +diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java +index b3f2e7bb4519cc078d3cede11bce232f1255c6a0..d7da8622264f5bf676bcdb0b535d5c9ceedc2025 100644 +--- a/net/minecraft/server/MinecraftServer.java ++++ b/net/minecraft/server/MinecraftServer.java +@@ -1570,6 +1570,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop withinManhattan(BlockPos pos, int xSize, int ySize, int zSize) { ++ // DivineMC start - lithium: cached iterate outwards ++ if (pos != space.bxteam.divinemc.util.lithium.IterateOutwardsCache.POS_ZERO) { ++ final it.unimi.dsi.fastutil.longs.LongList positions = xSize == 8 && ySize == 4 && zSize == 8 ? HOGLIN_PIGLIN_CACHE : ITERATE_OUTWARDS_CACHE.getOrCompute(xSize, ySize, zSize); ++ return new space.bxteam.divinemc.util.lithium.LongList2BlockPosMutableIterable(pos, positions); ++ } ++ // DivineMC end - lithium: cached iterate outwards + int i = xSize + ySize + zSize; + int x1 = pos.getX(); + int y1 = pos.getY(); diff --git a/divinemc-server/minecraft-patches/sources/net/minecraft/world/entity/Entity.java.patch b/divinemc-server/minecraft-patches/sources/net/minecraft/world/entity/Entity.java.patch index 714c8df..521b130 100644 --- a/divinemc-server/minecraft-patches/sources/net/minecraft/world/entity/Entity.java.patch +++ b/divinemc-server/minecraft-patches/sources/net/minecraft/world/entity/Entity.java.patch @@ -21,6 +21,63 @@ 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"); +@@ -2161,30 +_,42 @@ + return new Vec3(this.xOld, this.yOld, this.zOld); + } + +- public float distanceTo(Entity entity) { +- float f = (float)(this.getX() - entity.getX()); +- float f1 = (float)(this.getY() - entity.getY()); +- float f2 = (float)(this.getZ() - entity.getZ()); +- return Mth.sqrt(f * f + f1 * f1 + f2 * f2); ++ // DivineMC start - Optimize distanceTo ++ public final float distanceTo(Entity entity) { ++ final double dx = this.getX() - entity.getX(); ++ final double dy = this.getY() - entity.getY(); ++ final double dz = this.getZ() - entity.getZ(); ++ return (float) Math.sqrt(Boolean.parseBoolean(System.getProperty("DivineMC.enableFMA")) ++ ? Math.fma(dx, dx, Math.fma(dy, dy, dz * dz)) ++ : dx * dx + dy * dy + dz * dz); + } ++ // DivineMC end - Optimize distanceTo + +- public double distanceToSqr(double x, double y, double z) { +- double d = this.getX() - x; +- double d1 = this.getY() - y; +- double d2 = this.getZ() - z; +- return d * d + d1 * d1 + d2 * d2; ++ // DivineMC start - Optimize distanceToSqr ++ public final double distanceToSqr(final double x, final double y, final double z) { ++ final double dx = this.getX() - x; ++ final double dy = this.getY() - y; ++ final double dz = this.getZ() - z; ++ return Boolean.parseBoolean(System.getProperty("DivineMC.enableFMA")) ++ ? Math.fma(dx, dx, Math.fma(dy, dy, dz * dz)) ++ : dx * dx + dy * dy + dz * dz; + } ++ // DivineMC end - Optimize distanceToSqr + + public double distanceToSqr(Entity entity) { + return this.distanceToSqr(entity.position()); + } + ++ // DivineMC start - Optimize distanceToSqr + public double distanceToSqr(Vec3 vec) { +- double d = this.getX() - vec.x; +- double d1 = this.getY() - vec.y; +- double d2 = this.getZ() - vec.z; +- return d * d + d1 * d1 + d2 * d2; ++ final double dx = this.getX() - vec.x; ++ final double dy = this.getY() - vec.y; ++ final double dz = this.getZ() - vec.z; ++ return Boolean.parseBoolean(System.getProperty("DivineMC.enableFMA")) ++ ? Math.fma(dx, dx, Math.fma(dy, dy, dz * dz)) ++ : dx * dx + dy * dy + dz * dz; + } ++ // DivineMC end - Optimize distanceToSqr + + public void playerTouch(Player player) { + } @@ -4251,6 +_,7 @@ } diff --git a/divinemc-server/src/main/java/space/bxteam/divinemc/configuration/DivineConfig.java b/divinemc-server/src/main/java/space/bxteam/divinemc/configuration/DivineConfig.java index 6e19675..fc81ae5 100644 --- a/divinemc-server/src/main/java/space/bxteam/divinemc/configuration/DivineConfig.java +++ b/divinemc-server/src/main/java/space/bxteam/divinemc/configuration/DivineConfig.java @@ -148,8 +148,14 @@ public class DivineConfig { } public static boolean optimizedDragonRespawn = true; + public static boolean lagCompensationEnabled = false; + public static boolean lagCompensationEnableForWater = false; + public static boolean lagCompensationEnableForLava = false; private static void optimizations() { optimizedDragonRespawn = getBoolean("settings.optimizations.optimized-dragon-respawn", optimizedDragonRespawn); + lagCompensationEnabled = getBoolean("settings.optimizations.lag-compensation.enabled", lagCompensationEnabled); + lagCompensationEnableForWater = getBoolean("settings.optimizations.lag-compensation.enable-for-water", lagCompensationEnableForWater); + lagCompensationEnableForLava = getBoolean("settings.optimizations.lag-compensation.enable-for-lava", lagCompensationEnableForLava); } public static boolean disableNonEditableSignWarning = true; diff --git a/divinemc-server/src/main/java/space/bxteam/divinemc/util/LagCompensation.java b/divinemc-server/src/main/java/space/bxteam/divinemc/util/LagCompensation.java new file mode 100644 index 0000000..cca36ce --- /dev/null +++ b/divinemc-server/src/main/java/space/bxteam/divinemc/util/LagCompensation.java @@ -0,0 +1,113 @@ +package space.bxteam.divinemc.util; + +import it.unimi.dsi.fastutil.doubles.DoubleArrayList; + +import java.util.Collections; +import java.util.List; + +public class LagCompensation { + public static float tt20(float ticks, boolean limitZero) { + float newTicks = (float) rawTT20(ticks); + + if (limitZero) return newTicks > 0 ? newTicks : 1; + else return newTicks; + } + + public static int tt20(int ticks, boolean limitZero) { + int newTicks = (int) Math.ceil(rawTT20(ticks)); + + if (limitZero) return newTicks > 0 ? newTicks : 1; + else return newTicks; + } + + public static double tt20(double ticks, boolean limitZero) { + double newTicks = rawTT20(ticks); + + if (limitZero) return newTicks > 0 ? newTicks : 1; + else return newTicks; + } + + public static double rawTT20(double ticks) { + return ticks == 0 ? 0 : ticks * TPSCalculator.getMostAccurateTPS() / TPSCalculator.MAX_TPS; + } + + public static class TPSCalculator { + public static Long lastTick; + public static Long currentTick; + private static double allMissedTicks = 0; + private static final List tpsHistory = Collections.synchronizedList(new DoubleArrayList()); + private static final int historyLimit = 40; + + public static final int MAX_TPS = 20; + public static final int FULL_TICK = 50; + + private TPSCalculator() {} + + public static void onTick() { + if (currentTick != null) { + lastTick = currentTick; + } + + currentTick = System.currentTimeMillis(); + addToHistory(getTPS()); + clearMissedTicks(); + missedTick(); + } + + private static void addToHistory(double tps) { + if (tpsHistory.size() >= historyLimit) { + tpsHistory.removeFirst(); + } + + tpsHistory.add(tps); + } + + public static long getMSPT() { + return currentTick - lastTick; + } + + public static double getAverageTPS() { + double sum = 0.0; + for (double value : tpsHistory) { + sum += value; + } + return tpsHistory.isEmpty() ? 0.1 : sum / tpsHistory.size(); + } + + public static double getTPS() { + if (lastTick == null) return -1; + if (getMSPT() <= 0) return 0.1; + + double tps = 1000 / (double) getMSPT(); + return tps > MAX_TPS ? MAX_TPS : tps; + } + + public static void missedTick() { + if (lastTick == null) return; + + long mspt = getMSPT() <= 0 ? 50 : getMSPT(); + double missedTicks = (mspt / (double) FULL_TICK) - 1; + allMissedTicks += missedTicks <= 0 ? 0 : missedTicks; + } + + public static double getMostAccurateTPS() { + return Math.min(getTPS(), getAverageTPS()); + } + + public double getAllMissedTicks() { + return allMissedTicks; + } + + public static int applicableMissedTicks() { + return (int) Math.floor(allMissedTicks); + } + + public static void clearMissedTicks() { + allMissedTicks -= applicableMissedTicks(); + } + + public void resetMissedTicks() { + allMissedTicks = 0; + } + } +} diff --git a/divinemc-server/src/main/java/space/bxteam/divinemc/util/lithium/IterateOutwardsCache.java b/divinemc-server/src/main/java/space/bxteam/divinemc/util/lithium/IterateOutwardsCache.java new file mode 100644 index 0000000..989f198 --- /dev/null +++ b/divinemc-server/src/main/java/space/bxteam/divinemc/util/lithium/IterateOutwardsCache.java @@ -0,0 +1,69 @@ +package space.bxteam.divinemc.util.lithium; + +import it.unimi.dsi.fastutil.longs.LongArrayList; +import it.unimi.dsi.fastutil.longs.LongList; +import java.util.Iterator; +import java.util.Random; +import java.util.concurrent.ConcurrentHashMap; +import net.minecraft.core.BlockPos; + +/** + * @author 2No2Name, original implemenation by SuperCoder7979 and Gegy1000 + */ +public class IterateOutwardsCache { + // POS_ZERO must not be replaced with BlockPos.ORIGIN, otherwise iterateOutwards at BlockPos.ORIGIN will not use the cache + public static final BlockPos POS_ZERO = new BlockPos(0,0,0); + + private final ConcurrentHashMap table; + private final int capacity; + private final Random random; + + public IterateOutwardsCache(int capacity) { + this.capacity = capacity; + this.table = new ConcurrentHashMap<>(31); + this.random = new Random(); + } + + private void fillPositionsWithIterateOutwards(LongList entry, int xRange, int yRange, int zRange) { + // Add all positions to the cached list + for (BlockPos pos : BlockPos.withinManhattan(POS_ZERO, xRange, yRange, zRange)) { + entry.add(pos.asLong()); + } + } + + public LongList getOrCompute(int xRange, int yRange, int zRange) { + long key = BlockPos.asLong(xRange, yRange, zRange); + + LongArrayList entry = this.table.get(key); + if (entry != null) { + return entry; + } + + // Cache miss: compute and store + entry = new LongArrayList(128); + + this.fillPositionsWithIterateOutwards(entry, xRange, yRange, zRange); + + // decrease the array size, as of now it won't be modified anymore anyways + entry.trim(); + + // this might overwrite an entry as the same entry could have been computed and added during this thread's computation + // we do not use computeIfAbsent, as it can delay other threads for too long + Object previousEntry = this.table.put(key, entry); + + if (previousEntry == null && this.table.size() > this.capacity) { + // prevent a memory leak by randomly removing about 1/8th of the elements when the exceed the desired capacity is exceeded + final Iterator iterator = this.table.keySet().iterator(); + // prevent an unlikely infinite loop caused by another thread filling the table concurrently using counting + for (int i = -this.capacity; iterator.hasNext() && i < 5; i++) { + Long key2 = iterator.next(); + // random is not threadsafe, but it doesn't matter here, because we don't need quality random numbers + if (this.random.nextInt(8) == 0 && key2 != key) { + iterator.remove(); + } + } + } + + return entry; + } +} diff --git a/divinemc-server/src/main/java/space/bxteam/divinemc/util/lithium/LongList2BlockPosMutableIterable.java b/divinemc-server/src/main/java/space/bxteam/divinemc/util/lithium/LongList2BlockPosMutableIterable.java new file mode 100644 index 0000000..0572a21 --- /dev/null +++ b/divinemc-server/src/main/java/space/bxteam/divinemc/util/lithium/LongList2BlockPosMutableIterable.java @@ -0,0 +1,43 @@ +package space.bxteam.divinemc.util.lithium; + +import it.unimi.dsi.fastutil.longs.LongIterator; +import it.unimi.dsi.fastutil.longs.LongList; +import java.util.Iterator; +import net.minecraft.core.BlockPos; + +/** + * @author 2No2Name + */ +public class LongList2BlockPosMutableIterable implements Iterable { + private final LongList positions; + private final int xOffset, yOffset, zOffset; + + public LongList2BlockPosMutableIterable(BlockPos offset, LongList posList) { + this.xOffset = offset.getX(); + this.yOffset = offset.getY(); + this.zOffset = offset.getZ(); + this.positions = posList; + } + + @Override + public Iterator iterator() { + return new Iterator() { + private final LongIterator it = LongList2BlockPosMutableIterable.this.positions.iterator(); + private final BlockPos.MutableBlockPos pos = new BlockPos.MutableBlockPos(); + + @Override + public boolean hasNext() { + return it.hasNext(); + } + + @Override + public net.minecraft.core.BlockPos next() { + long nextPos = this.it.nextLong(); + return this.pos.set( + LongList2BlockPosMutableIterable.this.xOffset + BlockPos.getX(nextPos), + LongList2BlockPosMutableIterable.this.yOffset + BlockPos.getY(nextPos), + LongList2BlockPosMutableIterable.this.zOffset + BlockPos.getZ(nextPos)); + } + }; + } +}