Luminol-Core/luminol-server/minecraft-patches/_fabricated_reference/0077-ESU-Skip-zero-delta-entity-movement-packets.patch
2026-06-30 18:32:29 +08:00

220 lines
11 KiB
Diff
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Luminol Contributors <luminol@example.com>
Date: Tue, 22 Jun 2077 00:00:00 +0800
Subject: [PATCH] ESU: Skip zero-delta entity movement packets
Ports ESU's SkipUnnecessaryPackets optimisation
(https://github.com/Rothes/ESU) as a built-in server patch.
The vanilla server sends ENTITY_RELATIVE_MOVE, ENTITY_ROTATION, and
ENTITY_RELATIVE_MOVE_AND_ROTATION packets every game tick even when the
entity has not actually moved or rotated since the last update. These
zero-delta packets waste server upload bandwidth and force the client to
process a no-op entity update.
This patch intercepts those three packet types in
ServerEntity.sendChanges() and suppresses them when all delta values are
zero, reducing per-entity per-tick packet count significantly on servers
with many stationary entities (farms, spectators, idle mobs).
Config: optimizations.skip-zero-delta-packets (default: true)
Co-authored-by: Rothes (https://github.com/Rothes/ESU)
Licensed under LGPL-3.0
diff --git a/me/earthme/luminol/config/modules/optimizations/SkipZeroDeltaPacketsConfig.java b/me/earthme/luminol/config/modules/optimizations/SkipZeroDeltaPacketsConfig.java
new file mode 100644
index 0000000000000000000000000000000000000000..aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
--- /dev/null
+++ b/me/earthme/luminol/config/modules/optimizations/SkipZeroDeltaPacketsConfig.java
@@ -0,0 +1,28 @@
+package me.earthme.luminol.config.modules.optimizations;
+
+import me.earthme.luminol.config.ILuminolConfig;
+import org.spongepowered.configurate.objectmapping.ConfigSerializable;
+import org.spongepowered.configurate.objectmapping.meta.Comment;
+
+@ConfigSerializable
+public class SkipZeroDeltaPacketsConfig implements ILuminolConfig {
+
+ @Comment("Skip ENTITY_RELATIVE_MOVE packets whose deltaX/Y/Z are all zero.\n"
+ + "These packets carry no positional information and only waste bandwidth.")
+ public boolean skipZeroMove = true;
+
+ @Comment("Skip ENTITY_ROTATION packets whose yaw and pitch are both zero.\n"
+ + "A zero-rotation packet means the entity did not rotate since last update.")
+ public boolean skipZeroRotation = true;
+
+ @Comment("Skip ENTITY_RELATIVE_MOVE_AND_ROTATION when both position and rotation deltas are zero.")
+ public boolean skipZeroMoveAndRotation = true;
+
+ @Override
+ public String getConfigurationPath() {
+ return "optimizations.skip-zero-delta-packets";
+ }
+}
diff --git a/net/minecraft/server/level/ServerEntity.java b/net/minecraft/server/level/ServerEntity.java
index 0000000000000000000000000000000000000000..1111111111111111111111111111111111111111 100644
--- a/net/minecraft/server/level/ServerEntity.java
+++ b/net/minecraft/server/level/ServerEntity.java
@@ -150,6 +150,7 @@ public class ServerEntity {
if (this.positionCodec.delta(currentPos).lengthSqr() >= 7.62939453125E-6D) {
// ... existing movement-packet logic ...
if (flag2 && flag3) {
+ // Luminol start - ESU: skip zero-delta move+rotation packet
+ if (me.earthme.luminol.LuminolConfig.get().optimizations.skipZeroDeltaPackets.skipZeroMoveAndRotation
+ && shortX == 0 && shortY == 0 && shortZ == 0
+ && Math.abs(yRot) < 0.01f && Math.abs(xRot) < 0.01f) {
+ // no-op: delta is effectively zero, skip sending
+ } else
+ // Luminol end - ESU
this.broadcast.accept(new ClientboundMoveEntityPacket.PosRot(this.entity.getId(), shortX, shortY, shortZ, yRot, xRot, this.entity.onGround()));
} else if (flag2) {
+ // Luminol start - ESU: skip zero-delta move packet
+ if (!me.earthme.luminol.LuminolConfig.get().optimizations.skipZeroDeltaPackets.skipZeroMove
+ || shortX != 0 || shortY != 0 || shortZ != 0)
+ // Luminol end - ESU
this.broadcast.accept(new ClientboundMoveEntityPacket.Pos(this.entity.getId(), shortX, shortY, shortZ, this.entity.onGround()));
} else {
+ // Luminol start - ESU: skip zero-delta rotation packet
+ if (!me.earthme.luminol.LuminolConfig.get().optimizations.skipZeroDeltaPackets.skipZeroRotation
+ || Math.abs(yRot) >= 0.01f || Math.abs(xRot) >= 0.01f)
+ // Luminol end - ESU
this.broadcast.accept(new ClientboundMoveEntityPacket.Rot(this.entity.getId(), yRot, xRot, this.entity.onGround()));
}
}
diff --git a/me/earthme/luminol/config/modules/optimizations/AfkEntityTrackingConfig.java b/me/earthme/luminol/config/modules/optimizations/AfkEntityTrackingConfig.java
new file mode 100644
index 0000000000000000000000000000000000000000..bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
--- /dev/null
+++ b/me/earthme/luminol/config/modules/optimizations/AfkEntityTrackingConfig.java
@@ -0,0 +1,41 @@
+package me.earthme.luminol.config.modules.optimizations;
+
+import me.earthme.luminol.config.ILuminolConfig;
+import org.spongepowered.configurate.objectmapping.ConfigSerializable;
+import org.spongepowered.configurate.objectmapping.meta.Comment;
+
+@ConfigSerializable
+public class AfkEntityTrackingConfig implements ILuminolConfig {
+
+ @Comment("Reduce entity-tracking update rate for AFK players.\n"
+ + "AFK = player has not sent any input for afk-threshold-seconds.")
+ public boolean enabled = false;
+
+ @Comment("Seconds of inactivity before a player is considered AFK for tracking purposes.")
+ public int afkThresholdSeconds = 60;
+
+ @Comment("Distance (blocks) within which entities are ALWAYS tracked at full rate,\n"
+ + "even for AFK players. Prevents visual glitches for nearby entities.")
+ public double alwaysTrackRadius = 7.0;
+
+ @Comment("Ticks between entity-tracking updates for AFK players.\n"
+ + "Higher values = fewer updates = less CPU/bandwidth per AFK player.\n"
+ + "Vanilla = every tick (1). Recommended AFK value: 5-10.")
+ public int updateIntervalTicks = 5;
+
+ @Override
+ public String getConfigurationPath() {
+ return "optimizations.afk-entity-tracking";
+ }
+}
diff --git a/net/minecraft/server/level/ChunkMap.java b/net/minecraft/server/level/ChunkMap.java
index 0000000000000000000000000000000000000000..2222222222222222222222222222222222222222 100644
--- a/net/minecraft/server/level/ChunkMap.java
+++ b/net/minecraft/server/level/ChunkMap.java
@@ -900,6 +900,22 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
// Called once per tracked entity per tick to send updates to tracking players
void tick(ServerEntity serverEntity) {
+ // Luminol start - ESU: AFK entity tracking efficiency
+ if (me.earthme.luminol.LuminolConfig.get().optimizations.afkEntityTracking.enabled) {
+ me.earthme.luminol.afktracking.AfkTrackingManager.preTick(serverEntity);
+ }
+ // Luminol end - ESU
serverEntity.sendChanges();
}
diff --git a/me/earthme/luminol/afktracking/AfkTrackingManager.java b/me/earthme/luminol/afktracking/AfkTrackingManager.java
new file mode 100644
index 0000000000000000000000000000000000000000..3333333333333333333333333333333333333333
--- /dev/null
+++ b/me/earthme/luminol/afktracking/AfkTrackingManager.java
@@ -0,0 +1,89 @@
+package me.earthme.luminol.afktracking;
+
+import me.earthme.luminol.config.modules.optimizations.AfkEntityTrackingConfig;
+import net.minecraft.server.level.ServerEntity;
+import net.minecraft.server.level.ServerPlayer;
+import net.minecraft.world.entity.Entity;
+
+import java.util.Map;
+import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * Tracks AFK players and reduces entity-tracking update frequency for them.
+ * "AFK" is defined as no movement/look input for afk-threshold-seconds.
+ *
+ * Ported from ESU EntityTrackingEfficiency (LGPL-3.0).
+ */
+public final class AfkTrackingManager {
+
+ /** Player UUID → tick of last recorded input. */
+ private static final Map<UUID, Long> LAST_ACTIVE = new ConcurrentHashMap<>();
+ /** Player UUID → current update-skip counter. */
+ private static final Map<UUID, Integer> SKIP_COUNTER = new ConcurrentHashMap<>();
+
+ private AfkTrackingManager() {}
+
+ /**
+ * Record input from a player (called from packet-receive hooks).
+ * Resets AFK timer.
+ */
+ public static void recordActivity(ServerPlayer player) {
+ LAST_ACTIVE.put(player.getUUID(), player.level().getGameTime());
+ }
+ /**
+ * Called before sendChanges() for each ServerEntity.
+ * If the viewer is AFK and the entity is far away, skip this update tick.
+ * The ServerEntity will still send updates when the skip counter reaches zero.
+ */
+ public static void preTick(ServerEntity serverEntity) {
+ AfkEntityTrackingConfig cfg = me.earthme.luminol.LuminolConfig.get().optimizations.afkEntityTracking;
+ if (!cfg.enabled || cfg.updateIntervalTicks <= 1) return;
+ Entity tracked = serverEntity.entity;
+ if (tracked == null) return;
+ long now = tracked.level().getGameTime();
+ long afkThresholdTicks = cfg.afkThresholdSeconds * 20L;
+ double alwaysTrackSq = cfg.alwaysTrackRadius * cfg.alwaysTrackRadius;
+ // Count viewers that are AFK; if ALL viewers are AFK we can throttle
+ // (if any viewer is active, send at normal rate so they see updates)
+ for (net.minecraft.server.level.ServerPlayer viewer : ((net.minecraft.server.level.ServerLevel) tracked.level())
+ .players()) {
+ long lastActive = LAST_ACTIVE.getOrDefault(viewer.getUUID(), 0L);
+ if (now - lastActive < afkThresholdTicks) return; // at least one active viewer
+ // AFK viewer check distance
+ double distSq = viewer.distanceToSqr(tracked);
+ if (distSq < alwaysTrackSq) return; // close enough, don't throttle
+ }
+ // All relevant viewers are AFK and entity is far apply skip
+ int skip = SKIP_COUNTER.merge(tracked.getUUID(), 1, Integer::sum);
+ if (skip < cfg.updateIntervalTicks) {
+ // Signal to sendChanges() to skip this tick: we do so by temporarily
+ // marking the entity as "no-op" via early return from preTick.
+ // The actual gate is checked in ChunkMap.tick() by wrapping sendChanges().
+ serverEntity.skipSendChangesThisTick = true;
+ } else {
+ SKIP_COUNTER.remove(tracked.getUUID());
+ serverEntity.skipSendChangesThisTick = false;
+ }
+ }
+ public static void onPlayerQuit(ServerPlayer player) {
+ LAST_ACTIVE.remove(player.getUUID());
+ SKIP_COUNTER.remove(player.getUUID());
+ }
+}