diff --git a/leaf-server/minecraft-patches/features/0085-Multithreaded-Tracker.patch b/leaf-server/minecraft-patches/features/0085-Multithreaded-Tracker.patch index 23247266..8fa0a7e1 100644 --- a/leaf-server/minecraft-patches/features/0085-Multithreaded-Tracker.patch +++ b/leaf-server/minecraft-patches/features/0085-Multithreaded-Tracker.patch @@ -37,7 +37,7 @@ index dd2509996bfd08e8c3f9f2be042229eac6d7692d..a35e9fae8f8da0c42f0616c4f78dc396 private static final byte CHUNK_TICKET_STAGE_NONE = 0; private static final byte CHUNK_TICKET_STAGE_LOADING = 1; diff --git a/net/minecraft/server/level/ChunkMap.java b/net/minecraft/server/level/ChunkMap.java -index 5d9d233e3a568aa6297ed9c703fa450f98158602..4984e57d3b0806164111e79acfc82f6d9b27bfbf 100644 +index 5d9d233e3a568aa6297ed9c703fa450f98158602..fa11526bf799910da9193ae6e3366542d50e7a98 100644 --- a/net/minecraft/server/level/ChunkMap.java +++ b/net/minecraft/server/level/ChunkMap.java @@ -248,6 +248,15 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider @@ -62,7 +62,7 @@ index 5d9d233e3a568aa6297ed9c703fa450f98158602..4984e57d3b0806164111e79acfc82f6d protected void tick() { + // Leaf start - Multithreaded tracker + if (org.dreeam.leaf.config.modules.async.MultithreadedTracker.enabled) { -+ final ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel level = this.level; ++ final ServerLevel level = this.level; + org.dreeam.leaf.async.tracker.MultithreadedTracker.tick(level); + return; + } @@ -70,97 +70,200 @@ index 5d9d233e3a568aa6297ed9c703fa450f98158602..4984e57d3b0806164111e79acfc82f6d // Paper start - optimise entity tracker if (true) { this.newTrackerTick(); -@@ -1073,11 +1089,12 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider +@@ -1073,12 +1089,24 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider final Entity entity; private final int range; SectionPos lastSectionPos; - public final Set seenBy = new it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet<>(); // Paper - Perf: optimise map impl -+ public final Set seenBy = org.dreeam.leaf.config.modules.async.MultithreadedTracker.enabled ? com.google.common.collect.Sets.newConcurrentHashSet() : new it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet<>(); // Paper - Perf: optimise map impl // Leaf - petal - Multithreaded tracker ++ public final Set seenBy = org.dreeam.leaf.config.modules.async.MultithreadedTracker.enabled ? it.unimi.dsi.fastutil.objects.ReferenceSets.synchronize(new it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet<>()) : new it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet<>(); // Paper - Perf: optimise map impl // Leaf - petal - Multithreaded tracker ++ public volatile ServerPlayerConnection[] seenByArray = EMPTY_OBJECT_ARRAY; // Leaf // Paper start - optimise entity tracker private long lastChunkUpdate = -1L; private ca.spottedleaf.moonrise.common.misc.NearbyPlayers.TrackedChunk lastTrackedChunk; -+ public final Object sync = new Object(); // Leaf - Multithreaded tracker ++ public final Object sync = new Object(); // Leaf - Multithreaded tracker ++ // Leaf start - Multithreaded tracker ++ public static final ServerPlayerConnection[] EMPTY_OBJECT_ARRAY = new ServerPlayerConnection[0]; ++ public ServerPlayerConnection[] seenBy() { ++ return seenByArray; ++ } ++ public void seenByUpdated() { ++ this.seenByArray = this.seenBy.toArray(EMPTY_OBJECT_ARRAY); ++ } ++ // Leaf end - Multithreaded tracker ++ @Override public final void moonrise$tick(final ca.spottedleaf.moonrise.common.misc.NearbyPlayers.TrackedChunk chunk) { -@@ -1100,8 +1117,41 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + if (chunk == null) { +@@ -1100,27 +1128,95 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider this.lastTrackedChunk = chunk; final ServerPlayer[] playersRaw = players.getRawDataUnchecked(); -+ // Leaf start - Multithreaded tracker -+ final int playersLen = players.size(); // Ensure length won't change in the future tasks -+ -+ if (org.dreeam.leaf.config.modules.async.MultithreadedTracker.enabled && org.dreeam.leaf.config.modules.async.MultithreadedTracker.compatModeEnabled) { -+ final boolean isServerPlayer = this.entity instanceof ServerPlayer; -+ final boolean isRealPlayer = isServerPlayer && ((ca.spottedleaf.moonrise.patches.chunk_system.player.ChunkSystemServerPlayer) this.entity).moonrise$isRealPlayer(); -+ Runnable updatePlayerTasks = () -> { -+ for (int i = 0; i < playersLen; ++i) { -+ final ServerPlayer player = playersRaw[i]; -+ this.updatePlayer(player); -+ } - +- - for (int i = 0, len = players.size(); i < len; ++i) { -+ if (lastChunkUpdate != currChunkUpdate || lastTrackedChunk != chunk) { -+ // need to purge any players possible not in the chunk list -+ for (final ServerPlayerConnection conn : new java.util.ArrayList<>(this.seenBy)) { -+ final ServerPlayer player = conn.getPlayer(); -+ if (!players.contains(player)) { -+ this.removePlayer(player); -+ } -+ } -+ } -+ }; -+ -+ // Only update asynchronously for real player, and sync update for fake players -+ // This can fix compatibility issue with NPC plugins using real entity type, like Citizens -+ // To prevent visible issue with player type NPCs -+ // btw, still recommend to use packet based NPC plugins, like ZNPC Plus, Adyeshach, Fancy NPC, etc. -+ if (isRealPlayer || !isServerPlayer) { -+ org.dreeam.leaf.async.tracker.MultithreadedTracker.getTrackerExecutor().execute(updatePlayerTasks); -+ } else { -+ updatePlayerTasks.run(); -+ } -+ } else { -+ final int playersLength = Math.min(playersRaw.length, players.size()); -+ for (int i = 0; i < playersLength; ++i) { ++ final int playersLength = Math.min(playersRaw.length, players.size()); // Leaf - Multithreaded tracker ++ for (int i = 0; i < playersLength; ++i) { // Leaf - Multithreaded tracker final ServerPlayer player = playersRaw[i]; this.updatePlayer(player); } -@@ -1115,6 +1165,8 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + + if (lastChunkUpdate != currChunkUpdate || lastTrackedChunk != chunk) { + // need to purge any players possible not in the chunk list +- for (final ServerPlayerConnection conn : new java.util.ArrayList<>(this.seenBy)) { ++ // Leaf start ++ boolean removed = false; ++ for (final ServerPlayerConnection conn : this.seenBy()) { + final ServerPlayer player = conn.getPlayer(); + if (!players.contains(player)) { +- this.removePlayer(player); ++ removed |= this.removePlayerMulti(player); ++ } ++ } ++ if (removed) { ++ this.seenByUpdated(); ++ } ++ // Leaf end ++ } ++ } ++ ++ // Leaf start - Multithreaded tracker ++ public final @Nullable Runnable leafTickCompact(final ca.spottedleaf.moonrise.common.misc.NearbyPlayers.TrackedChunk chunk) { ++ if (chunk == null) { ++ this.moonrise$clearPlayers(); ++ return null; ++ } ++ ++ final ca.spottedleaf.moonrise.common.list.ReferenceList players = chunk.getPlayers(ca.spottedleaf.moonrise.common.misc.NearbyPlayers.NearbyMapType.VIEW_DISTANCE); ++ ++ if (players == null) { ++ this.moonrise$clearPlayers(); ++ return null; ++ } ++ ++ final long lastChunkUpdate = this.lastChunkUpdate; ++ final long currChunkUpdate = chunk.getUpdateCount(); ++ final ca.spottedleaf.moonrise.common.misc.NearbyPlayers.TrackedChunk lastTrackedChunk = this.lastTrackedChunk; ++ this.lastChunkUpdate = currChunkUpdate; ++ this.lastTrackedChunk = chunk; ++ ++ final ServerPlayer[] playersRaw = players.getRawDataUnchecked(); ++ final int playersLen = players.size(); // Ensure length won't change in the future tasks ++ ++ if (!org.dreeam.leaf.config.modules.async.MultithreadedTracker.enabled || !org.dreeam.leaf.config.modules.async.MultithreadedTracker.compatModeEnabled) { ++ throw new IllegalStateException(); ++ } ++ final boolean isServerPlayer = this.entity instanceof ServerPlayer; ++ final boolean isRealPlayer = isServerPlayer && ((ca.spottedleaf.moonrise.patches.chunk_system.player.ChunkSystemServerPlayer) this.entity).moonrise$isRealPlayer(); ++ Runnable updatePlayerTasks = () -> { ++ for (int i = 0; i < playersLen; ++i) { ++ final ServerPlayer player = playersRaw[i]; ++ this.updatePlayer(player); ++ } ++ ++ if (lastChunkUpdate != currChunkUpdate || lastTrackedChunk != chunk) { ++ // need to purge any players possible not in the chunk list ++ boolean removed = false; ++ for (final ServerPlayerConnection conn : this.seenBy()) { ++ final ServerPlayer player = conn.getPlayer(); ++ if (!players.contains(player)) { ++ removed |= this.removePlayerMulti(player); ++ } ++ } ++ if (removed) { ++ this.seenByUpdated(); } } ++ }; ++ ++ // Only update asynchronously for real player, and sync update for fake players ++ // This can fix compatibility issue with NPC plugins using real entity type, like Citizens ++ // To prevent visible issue with player type NPCs ++ // btw, still recommend to use packet based NPC plugins, like ZNPC Plus, Adyeshach, Fancy NPC, etc. ++ if (isRealPlayer || !isServerPlayer) { ++ return updatePlayerTasks; ++ } else { ++ updatePlayerTasks.run(); ++ return null; } -+ } -+ // Leaf end - Multithreaded tracker + } ++ // Leaf end - Multithreaded tracker + + @Override + public final void moonrise$removeNonTickThreadPlayers() { + boolean foundToRemove = false; +- for (final ServerPlayerConnection conn : this.seenBy) { ++ for (final ServerPlayerConnection conn : this.seenBy()) { // Leaf + if (!ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(conn.getPlayer())) { + foundToRemove = true; + break; +@@ -1131,12 +1227,13 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + return; + } + +- for (final ServerPlayerConnection conn : new java.util.ArrayList<>(this.seenBy)) { ++ for (final ServerPlayerConnection conn : this.seenBy()) { // Leaf + ServerPlayer player = conn.getPlayer(); + if (!ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(player)) { +- this.removePlayer(player); ++ this.removePlayerMulti(player); // Leaf + } + } ++ this.seenByUpdated(); // Leaf } @Override -@@ -1176,7 +1228,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider +@@ -1146,10 +1243,11 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + if (this.seenBy.isEmpty()) { + return; + } +- for (final ServerPlayerConnection conn : new java.util.ArrayList<>(this.seenBy)) { ++ for (final ServerPlayerConnection conn : this.seenBy()) { // Leaf + ServerPlayer player = conn.getPlayer(); +- this.removePlayer(player); ++ this.removePlayerMulti(player); // Leaf + } ++ this.seenByUpdated(); // Leaf + } + + @Override +@@ -1176,7 +1274,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider } public void broadcast(Packet packet) { - for (ServerPlayerConnection serverPlayerConnection : this.seenBy) { -+ for (ServerPlayerConnection serverPlayerConnection : this.seenBy.toArray(new ServerPlayerConnection[0])) {// Leaf - petal - Multithreaded tracker ++ for (ServerPlayerConnection serverPlayerConnection : this.seenBy()) { // Leaf - petal - Multithreaded tracker serverPlayerConnection.send(packet); } } -@@ -1189,21 +1241,22 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider +@@ -1189,21 +1287,34 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider } public void broadcastRemoved() { - for (ServerPlayerConnection serverPlayerConnection : this.seenBy) { -+ for (ServerPlayerConnection serverPlayerConnection : this.seenBy.toArray(new ServerPlayerConnection[0])) {// Leaf - petal - Multithreaded tracker ++ for (ServerPlayerConnection serverPlayerConnection : this.seenBy()) { // Leaf - petal - Multithreaded tracker this.serverEntity.removePairing(serverPlayerConnection.getPlayer()); } } ++ // Leaf start ++ public boolean removePlayerMulti(ServerPlayer player) { ++ if (this.seenBy.remove(player.connection)) { ++ this.serverEntity.removePairing(player); ++ return true; ++ } else { ++ return false; ++ } ++ } ++ // Leaf end ++ public void removePlayer(ServerPlayer player) { - org.spigotmc.AsyncCatcher.catchOp("player tracker clear"); // Spigot + //org.spigotmc.AsyncCatcher.catchOp("player tracker clear"); // Spigot // Leaf - petal - Multithreaded tracker - We can remove async too if (this.seenBy.remove(player.connection)) { this.serverEntity.removePairing(player); } ++ this.seenByUpdated(); // Leaf } public void updatePlayer(ServerPlayer player) { @@ -171,6 +274,22 @@ index 5d9d233e3a568aa6297ed9c703fa450f98158602..4984e57d3b0806164111e79acfc82f6d // Paper start - remove allocation of Vec3D here // Vec3 vec3 = player.position().subtract(this.entity.position()); double vec3_dx = player.getX() - this.entity.getX(); +@@ -1231,6 +1342,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + // CraftBukkit end + if (flag) { + if (this.seenBy.add(player.connection)) { ++ this.seenByUpdated(); // Leaf + // Paper start - entity tracking events + if (io.papermc.paper.event.player.PlayerTrackEntityEvent.getHandlerList().getRegisteredListeners().length == 0 || new io.papermc.paper.event.player.PlayerTrackEntityEvent(player.getBukkitEntity(), this.entity.getBukkitEntity()).callEvent()) { + this.serverEntity.addPairing(player); +@@ -1239,6 +1351,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + this.serverEntity.onPlayerAdd(); // Paper - fix desync when a player is added to the tracker + } + } else if (this.seenBy.remove(player.connection)) { ++ this.seenByUpdated(); // Leaf + this.serverEntity.removePairing(player); + } + } diff --git a/net/minecraft/server/level/ServerBossEvent.java b/net/minecraft/server/level/ServerBossEvent.java index f106373ef3ac4a8685c2939c9e8361688a285913..51ae390c68e7a3aa193329cc3bc47ca675930ff2 100644 --- a/net/minecraft/server/level/ServerBossEvent.java @@ -185,10 +304,18 @@ index f106373ef3ac4a8685c2939c9e8361688a285913..51ae390c68e7a3aa193329cc3bc47ca6 public boolean visible = true; diff --git a/net/minecraft/server/level/ServerEntity.java b/net/minecraft/server/level/ServerEntity.java -index d8298c7925e3bcea07ead4d438478cc51abcfa16..be3057119bcbce4a4f72284fa7ba8f60ba43f397 100644 +index d8298c7925e3bcea07ead4d438478cc51abcfa16..e67d87b3043b381d27f75f37e3b7f922e18dcc2d 100644 --- a/net/minecraft/server/level/ServerEntity.java +++ b/net/minecraft/server/level/ServerEntity.java -@@ -110,8 +110,16 @@ public class ServerEntity { +@@ -70,6 +70,7 @@ public class ServerEntity { + private boolean wasOnGround; + @Nullable + private List> trackedDataValues; ++ public boolean wantSendDirtyEntityData = false; // Leaf - Multithreaded tracker + + // CraftBukkit start + private final Set trackedPlayers; +@@ -110,8 +111,16 @@ public class ServerEntity { .forEach( removedPassenger -> { if (removedPassenger instanceof ServerPlayer serverPlayer1) { @@ -207,41 +334,30 @@ index d8298c7925e3bcea07ead4d438478cc51abcfa16..be3057119bcbce4a4f72284fa7ba8f60 } } ); -@@ -434,16 +442,33 @@ public class ServerEntity { - - if (this.entity instanceof LivingEntity) { - Set attributesToSync = ((LivingEntity)this.entity).getAttributes().getAttributesToSync(); -+ // Leaf start - Multithreaded tracker -+ if (org.dreeam.leaf.config.modules.async.MultithreadedTracker.enabled) { -+ synchronized (attributesToSync) { -+ if (!attributesToSync.isEmpty()) { -+ // CraftBukkit start - Send scaled max health -+ if (this.entity instanceof ServerPlayer serverPlayer) { -+ serverPlayer.getBukkitEntity().injectScaledMaxHealth(attributesToSync, false); -+ } -+ // CraftBukkit end -+ this.broadcastAndSend(new ClientboundUpdateAttributesPacket(this.entity.getId(), attributesToSync)); -+ } -+ -+ attributesToSync.clear(); -+ } -+ } else { - if (!attributesToSync.isEmpty()) { - // CraftBukkit start - Send scaled max health - if (this.entity instanceof ServerPlayer serverPlayer) { -- serverPlayer.getBukkitEntity().injectScaledMaxHealth(attributesToSync, false); -+ serverPlayer.getBukkitEntity().injectScaledMaxHealth(attributesToSync, false); // Leaf - Multithreaded tracker - diff on change - } - // CraftBukkit end - this.broadcastAndSend(new ClientboundUpdateAttributesPacket(this.entity.getId(), attributesToSync)); - } - - attributesToSync.clear(); -+ } -+ // Leaf end - Multithreaded tracker - } +@@ -124,7 +133,7 @@ public class ServerEntity { + MapId mapId = itemFrame.cachedMapId; // Paper - Perf: Cache map ids on item frames + MapItemSavedData savedData = MapItem.getSavedData(mapId, this.level); + if (savedData != null) { +- for (final net.minecraft.server.network.ServerPlayerConnection connection : this.trackedPlayers) { // Paper ++ for (final net.minecraft.server.network.ServerPlayerConnection connection : this.trackedPlayers.toArray(ChunkMap.TrackedEntity.EMPTY_OBJECT_ARRAY)) { // Paper // Leaf + final ServerPlayer serverPlayer = connection.getPlayer(); // Paper + savedData.tickCarriedBy(serverPlayer, item); + Packet updatePacket = savedData.getUpdatePacket(mapId, serverPlayer); +@@ -424,7 +433,13 @@ public class ServerEntity { + return Mth.unpackDegrees(this.lastSentYHeadRot); } +- private void sendDirtyEntityData() { ++ public void sendDirtyEntityData() { // Leaf - public ++ // Leaf start - Multithreaded tracker ++ if (Thread.currentThread() instanceof org.dreeam.leaf.async.tracker.MultithreadedTracker.MultithreadedTrackerThread) { ++ wantSendDirtyEntityData = true; ++ return; ++ } ++ // Leaf end - Multithreaded tracker + SynchedEntityData entityData = this.entity.getEntityData(); + List> list = entityData.packDirty(); + if (list != null) { diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java index 275b640f4536366152f59acf071dd4eba15696c8..a669a59a42f814480879a52d2da5e04c636720de 100644 --- a/net/minecraft/server/level/ServerLevel.java @@ -277,98 +393,29 @@ index 04bf8bba0d8c0d5459605253dcc3f135bf43fd95..abe79d07196de0a10a382d4c37161c7e // Paper start - Prevent teleporting dead entities if (this.player.isRemoved()) { LOGGER.info("Attempt to teleport removed player {} restricted", player.getScoreboardName()); -diff --git a/net/minecraft/world/entity/ai/attributes/AttributeInstance.java b/net/minecraft/world/entity/ai/attributes/AttributeInstance.java -index 8013594bb4844e7a8abf28123958e7f632d39341..80c703fe85ec21e4d218823504f4bbf7826b2fa6 100644 ---- a/net/minecraft/world/entity/ai/attributes/AttributeInstance.java -+++ b/net/minecraft/world/entity/ai/attributes/AttributeInstance.java -@@ -24,8 +24,11 @@ public class AttributeInstance { - private final Map> modifiersByOperation = Maps.newEnumMap( - AttributeModifier.Operation.class - ); -- private final Map modifierById = new Object2ObjectArrayMap<>(); -- private final Map permanentModifiers = new Object2ObjectArrayMap<>(); -+ // Leaf start - Multithreaded tracker -+ private final boolean multiThreadedTrackingEnabled = org.dreeam.leaf.config.modules.async.MultithreadedTracker.enabled; -+ private final Map modifierById = multiThreadedTrackingEnabled ? it.unimi.dsi.fastutil.objects.Object2ObjectMaps.synchronize(new Object2ObjectArrayMap<>(), this) : new Object2ObjectArrayMap<>(); -+ private final Map permanentModifiers = multiThreadedTrackingEnabled ? it.unimi.dsi.fastutil.objects.Object2ObjectMaps.synchronize(new Object2ObjectArrayMap<>(), this) : new Object2ObjectArrayMap<>(); -+ // Leaf end - Multithreaded tracker - private double baseValue; - private boolean dirty = true; - private double cachedValue; -@@ -114,7 +117,15 @@ public class AttributeInstance { - } +diff --git a/net/minecraft/world/entity/item/PrimedTnt.java b/net/minecraft/world/entity/item/PrimedTnt.java +index c96f458994818392857642282ec3d492124885da..1caf6846c5057f2c9bcd56ba9f217cc2863cd41b 100644 +--- a/net/minecraft/world/entity/item/PrimedTnt.java ++++ b/net/minecraft/world/entity/item/PrimedTnt.java +@@ -142,12 +142,14 @@ public class PrimedTnt extends Entity implements TraceableEntity { + net.minecraft.network.protocol.game.ClientboundSetEntityMotionPacket velocityPacket = new net.minecraft.network.protocol.game.ClientboundSetEntityMotionPacket(this); + net.minecraft.network.protocol.game.ClientboundTeleportEntityPacket positionPacket = net.minecraft.network.protocol.game.ClientboundTeleportEntityPacket.teleport(this.getId(), net.minecraft.world.entity.PositionMoveRotation.of(this), java.util.Set.of(), this.onGround); - protected void setDirty() { -- this.dirty = true; -+ // Leaf start - Multithreaded tracker -+ if (multiThreadedTrackingEnabled) { -+ synchronized (this) { -+ this.dirty = true; -+ } -+ } else { -+ this.dirty = true; -+ } -+ // Leaf end - Multithreaded tracker - this.onDirty.accept(this); - } - -@@ -141,6 +152,17 @@ public class AttributeInstance { - } - - public double getValue() { -+ // Leaf start - Multithreaded tracker -+ if (multiThreadedTrackingEnabled) { -+ synchronized (this) { -+ if (this.dirty) { -+ this.cachedValue = this.calculateValue(); -+ this.dirty = false; +- ete.seenBy.stream() +- .filter(viewer -> (viewer.getPlayer().getX() - this.getX()) * (viewer.getPlayer().getY() - this.getY()) * (viewer.getPlayer().getZ() - this.getZ()) < 16 * 16) +- .forEach(viewer -> { ++ // Leaf start ++ for (var viewer : ete.seenBy()) { ++ if ((viewer.getPlayer().getX() - this.getX()) * (viewer.getPlayer().getY() - this.getY()) * (viewer.getPlayer().getZ() - this.getZ()) < 16 * 16) { + viewer.send(velocityPacket); + viewer.send(positionPacket); +- }); ++ } + } -+ return this.cachedValue; -+ } -+ } -+ // Leaf end - Multithreaded tracker - if (this.dirty) { - this.cachedValue = this.calculateValue(); - this.dirty = false; -diff --git a/net/minecraft/world/entity/ai/attributes/AttributeMap.java b/net/minecraft/world/entity/ai/attributes/AttributeMap.java -index 89f4c5b2d61e27acd48063f9f24ce9ea91898b8b..d6e7685cb0e9beaa017bcc665f0f1c7c29c2ca5f 100644 ---- a/net/minecraft/world/entity/ai/attributes/AttributeMap.java -+++ b/net/minecraft/world/entity/ai/attributes/AttributeMap.java -@@ -19,11 +19,11 @@ import org.slf4j.Logger; - - public class AttributeMap { - private static final Logger LOGGER = LogUtils.getLogger(); -- // Gale start - Lithium - replace AI attributes with optimized collections -- private final Map, AttributeInstance> attributes = new it.unimi.dsi.fastutil.objects.Reference2ReferenceOpenHashMap<>(0); -- private final Set attributesToSync = new it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet<>(0); -- private final Set attributesToUpdate = new it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet<>(0); -- // Gale end - Lithium - replace AI attributes with optimized collections -+ // Leaf start - Multithreaded tracker -+ private final Map, AttributeInstance> attributes; -+ private final Set attributesToSync; -+ private final Set attributesToUpdate; -+ // Leaf end - Multithreaded tracker - private final AttributeSupplier supplier; - private final java.util.function.Function, AttributeInstance> createInstance; // Gale - Airplane - reduce entity allocations - private final net.minecraft.world.entity.LivingEntity entity; // Purpur - Ridables -@@ -37,6 +37,17 @@ public class AttributeMap { - // Purpur end - Ridables - this.supplier = defaultAttributes; - this.createInstance = holder -> this.supplier.createInstance(this::onAttributeModified, holder); // Gale - Airplane - reduce entity allocations -+ // Leaf start - Multithreaded tracker -+ if (org.dreeam.leaf.config.modules.async.MultithreadedTracker.enabled) { -+ this.attributes = it.unimi.dsi.fastutil.objects.Reference2ReferenceMaps.synchronize(new it.unimi.dsi.fastutil.objects.Reference2ReferenceOpenHashMap<>(0)); -+ this.attributesToSync = it.unimi.dsi.fastutil.objects.ReferenceSets.synchronize(new it.unimi.dsi.fastutil.objects.ReferenceArraySet<>(0)); -+ this.attributesToUpdate = it.unimi.dsi.fastutil.objects.ReferenceSets.synchronize(new it.unimi.dsi.fastutil.objects.ReferenceArraySet<>(0)); -+ } else { -+ this.attributes = new it.unimi.dsi.fastutil.objects.Reference2ReferenceOpenHashMap<>(0); -+ this.attributesToSync = new it.unimi.dsi.fastutil.objects.ReferenceArraySet<>(0); -+ this.attributesToUpdate = new it.unimi.dsi.fastutil.objects.ReferenceArraySet<>(0); -+ } -+ // Leaf end - Multithreaded tracker - } - - private void onAttributeModified(AttributeInstance instance) { ++ // Leaf end + } + } + // Paper end - Option to prevent TNT from moving in water diff --git a/net/minecraft/world/entity/vehicle/NewMinecartBehavior.java b/net/minecraft/world/entity/vehicle/NewMinecartBehavior.java index 724466d14c925704671e510cea1919ee95a2ae02..4426b344677ab9f2753dd2d219921bcb7cf39980 100644 --- a/net/minecraft/world/entity/vehicle/NewMinecartBehavior.java diff --git a/leaf-server/minecraft-patches/features/0143-Optimize-addOrUpdateTransientModifier.patch b/leaf-server/minecraft-patches/features/0143-Optimize-addOrUpdateTransientModifier.patch index 4757c107..087595bb 100644 --- a/leaf-server/minecraft-patches/features/0143-Optimize-addOrUpdateTransientModifier.patch +++ b/leaf-server/minecraft-patches/features/0143-Optimize-addOrUpdateTransientModifier.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Optimize addOrUpdateTransientModifier diff --git a/net/minecraft/world/entity/ai/attributes/AttributeInstance.java b/net/minecraft/world/entity/ai/attributes/AttributeInstance.java -index ceff383d565267edd13a6d9006030b8e1f8053e3..7dae9cc18cd6eede8f1b2196b55103428f35382e 100644 +index 8013594bb4844e7a8abf28123958e7f632d39341..7505485c8965e5492a9d68288596178cfe0971ee 100644 --- a/net/minecraft/world/entity/ai/attributes/AttributeInstance.java +++ b/net/minecraft/world/entity/ai/attributes/AttributeInstance.java -@@ -88,8 +88,13 @@ public class AttributeInstance { +@@ -85,8 +85,13 @@ public class AttributeInstance { } public void addOrUpdateTransientModifier(AttributeModifier modifier) { diff --git a/leaf-server/minecraft-patches/features/0164-Protocol-Core.patch b/leaf-server/minecraft-patches/features/0164-Protocol-Core.patch index a28bedcc..59c3cd8d 100644 --- a/leaf-server/minecraft-patches/features/0164-Protocol-Core.patch +++ b/leaf-server/minecraft-patches/features/0164-Protocol-Core.patch @@ -34,10 +34,10 @@ index 98af1ad020a003db66d7319f33d43deec315aec5..9669036e6b7f1830888e48c99acb01d4 for (int i = 0; i < this.tickables.size(); i++) { this.tickables.get(i).run(); diff --git a/net/minecraft/server/level/ServerEntity.java b/net/minecraft/server/level/ServerEntity.java -index be3057119bcbce4a4f72284fa7ba8f60ba43f397..65caf86832b3f33ad87208eb37b44ef2d8a2ec4e 100644 +index e67d87b3043b381d27f75f37e3b7f922e18dcc2d..d64434d808d164ab1201f244058e2964860a590c 100644 --- a/net/minecraft/server/level/ServerEntity.java +++ b/net/minecraft/server/level/ServerEntity.java -@@ -283,6 +283,7 @@ public class ServerEntity { +@@ -284,6 +284,7 @@ public class ServerEntity { this.entity.hurtMarked = false; this.broadcastAndSend(new ClientboundSetEntityMotionPacket(this.entity)); } diff --git a/leaf-server/minecraft-patches/features/0178-optimize-AttributeMap.patch b/leaf-server/minecraft-patches/features/0178-optimize-AttributeMap.patch new file mode 100644 index 00000000..4c3257d5 --- /dev/null +++ b/leaf-server/minecraft-patches/features/0178-optimize-AttributeMap.patch @@ -0,0 +1,86 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: hayanesuru +Date: Thu, 15 May 2025 21:11:18 +0900 +Subject: [PATCH] optimize AttributeMap + + +diff --git a/net/minecraft/world/entity/ai/attributes/Attribute.java b/net/minecraft/world/entity/ai/attributes/Attribute.java +index f8419dde44ebc7324e783f8bee42132d5ec973c3..1c52a118bad6b4b8abdf2527347ef66888b71f54 100644 +--- a/net/minecraft/world/entity/ai/attributes/Attribute.java ++++ b/net/minecraft/world/entity/ai/attributes/Attribute.java +@@ -16,10 +16,15 @@ public class Attribute { + private boolean syncable; + private final String descriptionId; + private Attribute.Sentiment sentiment = Attribute.Sentiment.POSITIVE; ++ // Leaf start - optimize AttributeMap ++ public final int uid; ++ private static final java.util.concurrent.atomic.AtomicInteger SIZE = new java.util.concurrent.atomic.AtomicInteger(); ++ // Leaf end - optimize AttributeMap + + protected Attribute(String descriptionId, double defaultValue) { + this.defaultValue = defaultValue; + this.descriptionId = descriptionId; ++ this.uid = SIZE.getAndAdd(1); // Leaf - optimize AttributeMap + } + + public double getDefaultValue() { +diff --git a/net/minecraft/world/entity/ai/attributes/AttributeMap.java b/net/minecraft/world/entity/ai/attributes/AttributeMap.java +index 89f4c5b2d61e27acd48063f9f24ce9ea91898b8b..19f7119256ac50ba8db961f179d3fabb9263944e 100644 +--- a/net/minecraft/world/entity/ai/attributes/AttributeMap.java ++++ b/net/minecraft/world/entity/ai/attributes/AttributeMap.java +@@ -20,12 +20,12 @@ import org.slf4j.Logger; + public class AttributeMap { + private static final Logger LOGGER = LogUtils.getLogger(); + // Gale start - Lithium - replace AI attributes with optimized collections +- private final Map, AttributeInstance> attributes = new it.unimi.dsi.fastutil.objects.Reference2ReferenceOpenHashMap<>(0); ++ private final Map, AttributeInstance> attributes = new org.dreeam.leaf.util.map.AttributeInstanceArrayMap(); // Leaf - optimize AttributeMap + private final Set attributesToSync = new it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet<>(0); + private final Set attributesToUpdate = new it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet<>(0); + // Gale end - Lithium - replace AI attributes with optimized collections + private final AttributeSupplier supplier; +- private final java.util.function.Function, AttributeInstance> createInstance; // Gale - Airplane - reduce entity allocations ++ // private final java.util.function.Function, AttributeInstance> createInstance; // Gale - Airplane - reduce entity allocations + private final net.minecraft.world.entity.LivingEntity entity; // Purpur - Ridables + + public AttributeMap(AttributeSupplier supplier) { +@@ -36,7 +36,7 @@ public class AttributeMap { + this.entity = entity; + // Purpur end - Ridables + this.supplier = defaultAttributes; +- this.createInstance = holder -> this.supplier.createInstance(this::onAttributeModified, holder); // Gale - Airplane - reduce entity allocations ++ // this.createInstance = holder -> this.supplier.createInstance(this::onAttributeModified, holder); // Gale - Airplane - reduce entity allocations + } + + private void onAttributeModified(AttributeInstance instance) { +@@ -60,7 +60,17 @@ public class AttributeMap { + + @Nullable + public AttributeInstance getInstance(Holder attribute) { +- return this.attributes.computeIfAbsent(attribute, this.createInstance); // Gale - Airplane - reduce entity allocations - cache lambda, as for some reason java allocates it anyways ++ // Leaf start - optimize AttributeMap ++ AttributeInstance v; ++ if ((v = this.attributes.get(attribute)) == null) { ++ AttributeInstance newValue; ++ if ((newValue = this.supplier.createInstance(this::onAttributeModified, attribute)) != null) { ++ this.attributes.put(attribute, newValue); ++ return newValue; ++ } ++ } ++ return v; ++ // Leaf end - optimize AttributeMap + } + + public boolean hasAttribute(Holder attribute) { +diff --git a/net/minecraft/world/entity/ai/attributes/AttributeSupplier.java b/net/minecraft/world/entity/ai/attributes/AttributeSupplier.java +index 24710041ccbc70e5506d8d89ae34f0141977f209..608edf272735d356215cf0147e65dcef391e1638 100644 +--- a/net/minecraft/world/entity/ai/attributes/AttributeSupplier.java ++++ b/net/minecraft/world/entity/ai/attributes/AttributeSupplier.java +@@ -11,7 +11,7 @@ public class AttributeSupplier { + private final Map, AttributeInstance> instances; + + AttributeSupplier(Map, AttributeInstance> instances) { +- this.instances = instances; ++ this.instances = new org.dreeam.leaf.util.map.AttributeInstanceArrayMap(instances); // Leaf - optimize AttributeMap + } + + public AttributeInstance getAttributeInstance(Holder attribute) { diff --git a/leaf-server/minecraft-patches/features/0179-optimize-getScaledTrackingDistance.patch b/leaf-server/minecraft-patches/features/0179-optimize-getScaledTrackingDistance.patch new file mode 100644 index 00000000..be9b6476 --- /dev/null +++ b/leaf-server/minecraft-patches/features/0179-optimize-getScaledTrackingDistance.patch @@ -0,0 +1,25 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: hayanesuru +Date: Sat, 17 May 2025 19:01:50 +0900 +Subject: [PATCH] optimize getScaledTrackingDistance + + +diff --git a/net/minecraft/server/dedicated/DedicatedServer.java b/net/minecraft/server/dedicated/DedicatedServer.java +index eb0589b203bcf72cd24bb37f2c448c23cb8d6f2b..a1ec070b8620ba34bae9103f19307f42e6ed2a7c 100644 +--- a/net/minecraft/server/dedicated/DedicatedServer.java ++++ b/net/minecraft/server/dedicated/DedicatedServer.java +@@ -838,7 +838,13 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface + + @Override + public int getScaledTrackingDistance(int trackingDistance) { +- return this.getProperties().entityBroadcastRangePercentage * trackingDistance / 100; ++ // Leaf start ++ int p = this.getProperties().entityBroadcastRangePercentage; ++ if (p == 100) { ++ return trackingDistance; ++ } ++ return p * trackingDistance / 100; ++ // Leaf end + } + + @Override diff --git a/leaf-server/minecraft-patches/features/0180-optimize-SynchedEntityData-packDirty.patch b/leaf-server/minecraft-patches/features/0180-optimize-SynchedEntityData-packDirty.patch new file mode 100644 index 00000000..bbfe075e --- /dev/null +++ b/leaf-server/minecraft-patches/features/0180-optimize-SynchedEntityData-packDirty.patch @@ -0,0 +1,27 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: hayanesuru +Date: Sat, 17 May 2025 19:03:31 +0900 +Subject: [PATCH] optimize SynchedEntityData#packDirty + + +diff --git a/net/minecraft/network/syncher/SynchedEntityData.java b/net/minecraft/network/syncher/SynchedEntityData.java +index 3d90f9f1ac1bd281edf6bb0f93ea821657d5bd2f..546c36d0bc14b8db49245ff162be3dbc4d680da6 100644 +--- a/net/minecraft/network/syncher/SynchedEntityData.java ++++ b/net/minecraft/network/syncher/SynchedEntityData.java +@@ -84,7 +84,15 @@ public class SynchedEntityData { + return null; + } else { + this.isDirty = false; +- List> list = new ArrayList<>(); ++ // Leaf start ++ int cap = 0; ++ for (SynchedEntityData.DataItem dataItem : this.itemsById) { ++ if (dataItem.isDirty()) { ++ cap += 1; ++ } ++ } ++ ArrayList> list = new ArrayList<>(cap); ++ // Leaf end + + for (SynchedEntityData.DataItem dataItem : this.itemsById) { + if (dataItem.isDirty()) { diff --git a/leaf-server/paper-patches/features/0024-Multithreaded-Tracker.patch b/leaf-server/paper-patches/features/0024-Multithreaded-Tracker.patch index d968d170..832c367a 100644 --- a/leaf-server/paper-patches/features/0024-Multithreaded-Tracker.patch +++ b/leaf-server/paper-patches/features/0024-Multithreaded-Tracker.patch @@ -40,6 +40,37 @@ index 379c2dc1853e45a96dda9b13bf28b7e08f65658a..361f4de9cdf0f7505628a2fed2a3f536 throw new IllegalStateException(event.getEventName() + " may only be triggered synchronously."); } // Leaves start - skip photographer +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +index edcd209798740f31cb302f36d7864a0d8ea1d561..e2444cc9e28dd432bf3351066b1408102decfa0a 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +@@ -749,7 +749,7 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { + ChunkMap.TrackedEntity entityTracker = world.getChunkSource().chunkMap.entityMap.get(this.getEntityId()); + + if (entityTracker != null) { +- for (ServerPlayerConnection connection : entityTracker.seenBy) { ++ for (ServerPlayerConnection connection : entityTracker.seenBy()) { // Leaf + players.add(connection.getPlayer().getBukkitEntity()); + } + } +@@ -1057,7 +1057,7 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { + } + + // Paper start - resend possibly desynced entity instead of add entity packet +- for (final ServerPlayerConnection connection : entityTracker.seenBy) { ++ for (final ServerPlayerConnection connection : entityTracker.seenBy()) { // Leaf + this.getHandle().resendPossiblyDesyncedEntityData(connection.getPlayer()); + } + // Paper end - resend possibly desynced entity instead of add entity packet +@@ -1245,7 +1245,7 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { + } + + Set set = new java.util.HashSet<>(tracker.seenBy.size()); +- for (net.minecraft.server.network.ServerPlayerConnection connection : tracker.seenBy) { ++ for (net.minecraft.server.network.ServerPlayerConnection connection : tracker.seenBy()) { // Leaf + set.add(connection.getPlayer().getBukkitEntity().getPlayer()); + } + return set; diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java index e52479f3c888268fd1febeb78e9965af834a8ae9..c2552c3706831f7012b5b449fa43c7d5990056a4 100644 --- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java diff --git a/leaf-server/src/main/java/org/dreeam/leaf/async/tracker/MultithreadedTracker.java b/leaf-server/src/main/java/org/dreeam/leaf/async/tracker/MultithreadedTracker.java index 23e6171c..9d78ac41 100644 --- a/leaf-server/src/main/java/org/dreeam/leaf/async/tracker/MultithreadedTracker.java +++ b/leaf-server/src/main/java/org/dreeam/leaf/async/tracker/MultithreadedTracker.java @@ -2,12 +2,14 @@ package org.dreeam.leaf.async.tracker; import ca.spottedleaf.moonrise.common.list.ReferenceList; import ca.spottedleaf.moonrise.common.misc.NearbyPlayers; -import ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel; import ca.spottedleaf.moonrise.patches.chunk_system.level.entity.server.ServerEntityLookup; import ca.spottedleaf.moonrise.patches.entity_tracker.EntityTrackerEntity; import com.google.common.util.concurrent.ThreadFactoryBuilder; +import it.unimi.dsi.fastutil.objects.ReferenceArrayList; +import net.minecraft.Util; import net.minecraft.server.level.ChunkMap; import net.minecraft.server.level.FullChunkStatus; +import net.minecraft.server.level.ServerEntity; import net.minecraft.server.level.ServerLevel; import net.minecraft.world.entity.Entity; import org.apache.logging.log4j.LogManager; @@ -49,11 +51,7 @@ public class MultithreadedTracker { } } - public static Executor getTrackerExecutor() { - return TRACKER_EXECUTOR; - } - - public static void tick(ChunkSystemServerLevel level) { + public static void tick(ServerLevel level) { try { if (!org.dreeam.leaf.config.modules.async.MultithreadedTracker.compatModeEnabled) { tickAsync(level); @@ -65,7 +63,7 @@ public class MultithreadedTracker { } } - private static void tickAsync(ChunkSystemServerLevel level) { + private static void tickAsync(ServerLevel level) { final NearbyPlayers nearbyPlayers = level.moonrise$getNearbyPlayers(); final ServerEntityLookup entityLookup = (ServerEntityLookup) level.moonrise$getEntityLookup(); @@ -74,6 +72,7 @@ public class MultithreadedTracker { // Move tracking to off-main TRACKER_EXECUTOR.execute(() -> { + ReferenceArrayList sendDirty = new ReferenceArrayList<>(); for (final Entity entity : trackerEntitiesRaw) { if (entity == null) continue; @@ -85,18 +84,26 @@ public class MultithreadedTracker { synchronized (tracker.sync) { tracker.moonrise$tick(nearbyPlayers.getChunk(entity.chunkPosition())); tracker.serverEntity.sendChanges(); + if (tracker.serverEntity.wantSendDirtyEntityData) { + tracker.serverEntity.wantSendDirtyEntityData = false; + sendDirty.add(tracker.serverEntity); + } } } + if (!sendDirty.isEmpty()) { + level.getServer().execute(() -> sendDirty.forEach(ServerEntity::sendDirtyEntityData)); + } }); } - private static void tickAsyncWithCompatMode(ChunkSystemServerLevel level) { + private static void tickAsyncWithCompatMode(ServerLevel level) { final NearbyPlayers nearbyPlayers = level.moonrise$getNearbyPlayers(); final ServerEntityLookup entityLookup = (ServerEntityLookup) level.moonrise$getEntityLookup(); final ReferenceList trackerEntities = entityLookup.trackerEntities; final Entity[] trackerEntitiesRaw = trackerEntities.getRawDataUnchecked(); final Runnable[] sendChangesTasks = new Runnable[trackerEntitiesRaw.length]; + final Runnable[] tickTask = new Runnable[trackerEntitiesRaw.length]; int index = 0; for (final Entity entity : trackerEntitiesRaw) { @@ -106,17 +113,40 @@ public class MultithreadedTracker { if (tracker == null) continue; - tracker.moonrise$tick(nearbyPlayers.getChunk(entity.chunkPosition())); - sendChangesTasks[index++] = () -> tracker.serverEntity.sendChanges(); // Collect send changes to task array + synchronized (tracker.sync) { + tickTask[index] = tracker.leafTickCompact(nearbyPlayers.getChunk(entity.chunkPosition())); + sendChangesTasks[index] = () -> tracker.serverEntity.sendChanges(); // Collect send changes to task array + } + index++; } // batch submit tasks TRACKER_EXECUTOR.execute(() -> { + for (final Runnable tick : tickTask) { + if (tick == null) continue; + + tick.run(); + } for (final Runnable sendChanges : sendChangesTasks) { if (sendChanges == null) continue; sendChanges.run(); } + ReferenceArrayList sendDirty = new ReferenceArrayList<>(); + for (final Entity entity : trackerEntitiesRaw) { + if (entity == null) continue; + + final ChunkMap.TrackedEntity tracker = ((EntityTrackerEntity) entity).moonrise$getTrackedEntity(); + + if (tracker == null) continue; + if (tracker.serverEntity.wantSendDirtyEntityData) { + tracker.serverEntity.wantSendDirtyEntityData = false; + sendDirty.add(tracker.serverEntity); + } + } + if (!sendDirty.isEmpty()) { + level.getServer().execute(() -> sendDirty.forEach(ServerEntity::sendDirtyEntityData)); + } }); } @@ -161,10 +191,11 @@ public class MultithreadedTracker { private static @NotNull ThreadFactory getThreadFactory() { return new ThreadFactoryBuilder() - .setThreadFactory(MultithreadedTrackerThread::new) - .setNameFormat(THREAD_PREFIX + " Thread - %d") - .setPriority(Thread.NORM_PRIORITY - 2) - .build(); + .setThreadFactory(MultithreadedTrackerThread::new) + .setNameFormat(THREAD_PREFIX + " Thread - %d") + .setPriority(Thread.NORM_PRIORITY - 2) + .setUncaughtExceptionHandler(Util::onThreadException) + .build(); } private static @NotNull RejectedExecutionHandler getRejectedPolicy() { diff --git a/leaf-server/src/main/java/org/dreeam/leaf/protocol/DoABarrelRollProtocol.java b/leaf-server/src/main/java/org/dreeam/leaf/protocol/DoABarrelRollProtocol.java index cdee8b2a..48c2a35d 100644 --- a/leaf-server/src/main/java/org/dreeam/leaf/protocol/DoABarrelRollProtocol.java +++ b/leaf-server/src/main/java/org/dreeam/leaf/protocol/DoABarrelRollProtocol.java @@ -151,7 +151,7 @@ public class DoABarrelRollProtocol implements Protocol { } var payload = new RollSyncS2CPacket(player.getId(), isRolling, roll); var packet = Protocols.createPacket(payload); - for (ServerPlayerConnection seenBy : player.moonrise$getTrackedEntity().seenBy.toArray(new ServerPlayerConnection[0])) { + for (ServerPlayerConnection seenBy : player.moonrise$getTrackedEntity().seenBy()) { if (seenBy instanceof ServerGamePacketListenerImpl conn && getHandshakeState(conn).state == HandshakeState.ACCEPTED) { seenBy.send(packet); diff --git a/leaf-server/src/main/java/org/dreeam/leaf/util/map/AttributeInstanceArrayMap.java b/leaf-server/src/main/java/org/dreeam/leaf/util/map/AttributeInstanceArrayMap.java new file mode 100644 index 00000000..13c865eb --- /dev/null +++ b/leaf-server/src/main/java/org/dreeam/leaf/util/map/AttributeInstanceArrayMap.java @@ -0,0 +1,311 @@ +package org.dreeam.leaf.util.map; + +import net.minecraft.core.Holder; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.world.entity.ai.attributes.Attribute; +import net.minecraft.world.entity.ai.attributes.AttributeInstance; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.*; +import java.util.AbstractMap.SimpleEntry; + +/// fast array backend map with O(1) get & put & remove +public class AttributeInstanceArrayMap implements Map, AttributeInstance>, Cloneable { + private int size = 0; + private transient AttributeInstance[] a = new AttributeInstance[32]; + private transient KeySet keys; + private transient Values values; + private transient EntrySet entries; + + public AttributeInstanceArrayMap() { + if (BuiltInRegistries.ATTRIBUTE.size() != 32) { + throw new IllegalStateException("Registered custom attribute"); + } + } + + public AttributeInstanceArrayMap(final @NotNull Map, AttributeInstance> m) { + this(); + for (AttributeInstance e : m.values()) { + setByIndex(e.getAttribute().value().uid, e); + } + } + + private void setByIndex(int index, @Nullable AttributeInstance instance) { + boolean empty = a[index] == null; + if (instance == null) { + if (!empty) { + size--; + a[index] = null; + } + } else { + if (empty) { + size++; + } + a[index] = instance; + } + } + + @Override + public final int size() { + return size; + } + + @Override + public final boolean isEmpty() { + return size == 0; + } + + @Override + public final boolean containsKey(Object key) { + if (key instanceof Holder holder && holder.value() instanceof Attribute attribute) { + int uid = attribute.uid; + return uid >= 0 && uid < a.length && a[uid] != null; + } + return false; + } + + @Override + public final boolean containsValue(Object value) { + for (final AttributeInstance instance : a) { + if (Objects.equals(value, instance)) { + return true; + } + } + return false; + } + + @Override + public final AttributeInstance get(Object key) { + return key instanceof Holder holder && holder.value() instanceof Attribute attribute ? a[attribute.uid] : null; + } + + @Override + public final AttributeInstance put(@NotNull Holder key, AttributeInstance value) { + int uid = key.value().uid; + AttributeInstance prev = a[uid]; + setByIndex(uid, value); + return prev; + } + + @Override + public final AttributeInstance remove(Object key) { + if (!(key instanceof Holder holder) || !(holder.value() instanceof Attribute attribute)) return null; + int uid = attribute.uid; + AttributeInstance prev = a[uid]; + setByIndex(uid, null); + return prev; + } + + @Override + public final void putAll(@NotNull Map, ? extends AttributeInstance> m) { + m.forEach(this::put); + } + + @Override + public final void clear() { + Arrays.fill(a, null); + size = 0; + } + + @Override + public final @NotNull Set> keySet() { + if (keys == null) { + keys = new KeySet(); + } + return keys; + } + + @Override + public final @NotNull Collection values() { + if (values == null) { + values = new Values(); + } + return values; + } + + @Override + public final @NotNull Set, AttributeInstance>> entrySet() { + if (entries == null) { + entries = new EntrySet(); + } + return entries; + } + + @Override + public final boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof Map other)) return false; + return entrySet().equals(other.entrySet()); + } + + @Override + public final int hashCode() { + return entrySet().hashCode(); + } + + @Override + public AttributeInstanceArrayMap clone() { + AttributeInstanceArrayMap c; + try { + c = (AttributeInstanceArrayMap) super.clone(); + } catch (CloneNotSupportedException cantHappen) { + throw new InternalError(); + } + c.a = a.clone(); + c.entries = null; + c.keys = null; + c.values = null; + return c; + } + + private final class KeySet extends AbstractSet> { + @Override + public @NotNull Iterator> iterator() { + return new KeyIterator(); + } + + @Override + public int size() { + return size; + } + + @Override + public boolean contains(Object o) { + return AttributeInstanceArrayMap.this.containsKey(o); + } + } + + private final class KeyIterator implements Iterator> { + private int currentIndex = -1; + private int nextIndex = findNextOccupied(0); + + @Override + public boolean hasNext() { + return nextIndex != -1; + } + + @Override + public Holder next() { + if (!hasNext()) throw new NoSuchElementException(); + currentIndex = nextIndex; + nextIndex = findNextOccupied(nextIndex + 1); + return BuiltInRegistries.ATTRIBUTE.asHolderIdMap().byIdOrThrow(currentIndex); + } + + @Override + public void remove() { + if (currentIndex == -1) throw new IllegalStateException(); + setByIndex(currentIndex, null); + currentIndex = -1; + } + } + + private final class Values extends AbstractCollection { + @Override + public @NotNull Iterator iterator() { + return new ValueIterator(); + } + + @Override + public int size() { + return size; + } + + @Override + public boolean contains(Object o) { + return containsValue(o); + } + } + + private final class ValueIterator implements Iterator { + private int currentIndex = -1; + private int nextIndex = findNextOccupied(0); + + @Override + public boolean hasNext() { + return nextIndex != -1; + } + + @Override + public AttributeInstance next() { + if (!hasNext()) throw new NoSuchElementException(); + currentIndex = nextIndex; + AttributeInstance value = a[nextIndex]; + nextIndex = findNextOccupied(nextIndex + 1); + return value; + } + + @Override + public void remove() { + if (currentIndex == -1) throw new IllegalStateException(); + setByIndex(currentIndex, null); + currentIndex = -1; + } + } + + private final class EntrySet extends AbstractSet, AttributeInstance>> { + @Override + public @NotNull Iterator, AttributeInstance>> iterator() { + return new EntryIterator(); + } + + @Override + public int size() { + return size; + } + + @Override + public boolean contains(Object o) { + if (!(o instanceof Entry e)) { + return false; + } + return Objects.equals(get(e.getKey()), e.getValue()); + } + } + + private final class EntryIterator implements Iterator, AttributeInstance>> { + private int currentIndex = -1; + private int nextIndex = findNextOccupied(0); + + @Override + public boolean hasNext() { + return nextIndex != -1; + } + + @Override + public Entry, AttributeInstance> next() { + if (!hasNext()) throw new NoSuchElementException(); + currentIndex = nextIndex; + Holder key = BuiltInRegistries.ATTRIBUTE.asHolderIdMap().byIdOrThrow(nextIndex); + AttributeInstance value = a[nextIndex]; + nextIndex = findNextOccupied(nextIndex + 1); + return new SimpleEntry<>(key, value) { + @Override + public AttributeInstance setValue(AttributeInstance newValue) { + AttributeInstance old = put(key, newValue); + super.setValue(newValue); + return old; + } + }; + } + + @Override + public void remove() { + if (currentIndex == -1) { + throw new IllegalStateException(); + } + setByIndex(currentIndex, null); + currentIndex = -1; + } + } + + private int findNextOccupied(int start) { + for (int i = start; i < a.length; i++) { + if (a[i] != null) { + return i; + } + } + return -1; + } +}