diff --git a/divinemc-server/minecraft-patches/features/0007-Multithreaded-Tracker.patch b/divinemc-server/minecraft-patches/features/0007-Multithreaded-Tracker.patch index a24e956..ddd9e6b 100644 --- a/divinemc-server/minecraft-patches/features/0007-Multithreaded-Tracker.patch +++ b/divinemc-server/minecraft-patches/features/0007-Multithreaded-Tracker.patch @@ -4,6 +4,28 @@ Date: Tue, 28 Jan 2025 01:18:49 +0300 Subject: [PATCH] Multithreaded Tracker +diff --git a/ca/spottedleaf/moonrise/common/misc/NearbyPlayers.java b/ca/spottedleaf/moonrise/common/misc/NearbyPlayers.java +index 1b8193587814225c2ef2c5d9e667436eb50ff6c5..93272808d94e81d31af728ebe85df9a2bc7aedab 100644 +--- a/ca/spottedleaf/moonrise/common/misc/NearbyPlayers.java ++++ b/ca/spottedleaf/moonrise/common/misc/NearbyPlayers.java +@@ -60,7 +60,16 @@ public final class NearbyPlayers { + + private final ServerLevel world; + private final Reference2ReferenceOpenHashMap players = new Reference2ReferenceOpenHashMap<>(); +- private final Long2ReferenceOpenHashMap byChunk = new Long2ReferenceOpenHashMap<>(); ++ // DivineMC start - Multithreaded Tracker ++ private final it.unimi.dsi.fastutil.longs.Long2ReferenceMap byChunk; ++ { ++ if (org.bxteam.divinemc.config.DivineConfig.AsyncCategory.multithreadedEnabled) { ++ byChunk = it.unimi.dsi.fastutil.longs.Long2ReferenceMaps.synchronize(new Long2ReferenceOpenHashMap<>()); ++ } else { ++ byChunk = new Long2ReferenceOpenHashMap<>(); ++ } ++ } ++ // DivineMC end - Multithreaded Tracker + private final Long2ReferenceOpenHashMap>[] directByChunk = new Long2ReferenceOpenHashMap[TOTAL_MAP_TYPES]; + { + for (int i = 0; i < this.directByChunk.length; ++i) { diff --git a/ca/spottedleaf/moonrise/patches/chunk_system/player/RegionizedPlayerChunkLoader.java b/ca/spottedleaf/moonrise/patches/chunk_system/player/RegionizedPlayerChunkLoader.java index bdc1200ef5317fdaf58973bf580b0a672aee800f..dc2b3ccf7810731c0e2c90e5a476c1c8203a1fb7 100644 --- a/ca/spottedleaf/moonrise/patches/chunk_system/player/RegionizedPlayerChunkLoader.java @@ -21,17 +43,51 @@ index bdc1200ef5317fdaf58973bf580b0a672aee800f..dc2b3ccf7810731c0e2c90e5a476c1c8 private static final byte CHUNK_TICKET_STAGE_NONE = 0; private static final byte CHUNK_TICKET_STAGE_LOADING = 1; +diff --git a/net/minecraft/network/protocol/game/ClientboundUpdateAttributesPacket.java b/net/minecraft/network/protocol/game/ClientboundUpdateAttributesPacket.java +index 9c0c99b936b4a82ebfe924866e53ec71f7bbe9ad..01ed1e3572e9c2ccfd19df117cda0d5cf65b9bcb 100644 +--- a/net/minecraft/network/protocol/game/ClientboundUpdateAttributesPacket.java ++++ b/net/minecraft/network/protocol/game/ClientboundUpdateAttributesPacket.java +@@ -32,6 +32,7 @@ public class ClientboundUpdateAttributesPacket implements Packet seenBy = new it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet<>(); // Paper - Perf: optimise map impl + // DivineMC start - Multithreaded tracker -+ public final Set seenBy = org.bxteam.divinemc.config.DivineConfig.AsyncCategory.multithreadedEnabled -+ ? com.google.common.collect.Sets.newConcurrentHashSet() -+ : new it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet<>(); // Paper - Perf: optimise map impl ++ public static final ServerPlayerConnection[] EMPTY_OBJECT_ARRAY = new ServerPlayerConnection[0]; ++ private final it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet nonSyncSeenBy = new it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet<>() { ++ @Override ++ public boolean add(ServerPlayerConnection serverPlayerConnection) { ++ seenByUpdated = true; ++ return super.add(serverPlayerConnection); ++ } ++ ++ @Override ++ public boolean remove(Object k) { ++ seenByUpdated = true; ++ return super.remove(k); ++ } ++ ++ @Override ++ public void clear() { ++ seenByUpdated = true; ++ super.clear(); ++ } ++ }; ++ public final Set seenBy = org.bxteam.divinemc.config.DivineConfig.AsyncCategory.multithreadedEnabled ? it.unimi.dsi.fastutil.objects.ReferenceSets.synchronize(nonSyncSeenBy) : nonSyncSeenBy; // Paper - Perf: optimise map impl ++ private volatile boolean seenByUpdated = true; ++ private volatile ServerPlayerConnection[] seenByArray = EMPTY_OBJECT_ARRAY; ++ ++ public ServerPlayerConnection[] seenBy() { ++ if (!seenByUpdated) { ++ return seenByArray; ++ } else { ++ return seenBy.toArray(EMPTY_OBJECT_ARRAY); ++ } ++ } ++ ++ public void seenByUpdated() { ++ this.seenByArray = this.seenBy.toArray(EMPTY_OBJECT_ARRAY); ++ seenByUpdated = false; ++ } + // DivineMC end - Multithreaded tracker // Paper start - optimise entity tracker private long lastChunkUpdate = -1L; -@@ -1162,21 +1173,55 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider +@@ -1162,22 +1216,92 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider this.lastTrackedChunk = chunk; final ServerPlayer[] playersRaw = players.getRawDataUnchecked(); -+ final int playersLen = players.size(); // Ensure length won't change in the future tasks -+ -+ // DivineMC start - Multithreaded tracker -+ if (org.bxteam.divinemc.config.DivineConfig.AsyncCategory.multithreadedEnabled && org.bxteam.divinemc.config.DivineConfig.AsyncCategory.multithreadedCompatModeEnabled) { -+ 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); -+ } ++ final int playersLength = Math.min(playersRaw.length, players.size()); // DivineMC - Multithreaded tracker - for (int i = 0, len = players.size(); i < len; ++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 -+ for (final ServerPlayerConnection conn : new java.util.ArrayList<>(this.seenBy)) { -+ final ServerPlayer player = conn.getPlayer(); -+ if (!players.contains(player)) { -+ this.removePlayer(player); -+ } -+ } ++ for (int i = 0; i < playersLength; ++i) { // DivineMC - Multithreaded tracker + final ServerPlayer player = playersRaw[i]; + this.updatePlayer(player); + } + + 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)) { ++ // DivineMC start - Multithreaded tracker ++ 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); + } -+ }; -+ -+ // 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.bxteam.divinemc.entity.tracking.MultithreadedTracker.getTrackerExecutor().execute(updatePlayerTasks); -+ } else { -+ updatePlayerTasks.run(); + } -+ } else { -+ for (int i = 0, len = players.size(); i < len; ++i) { ++ ++ if (removed) { ++ this.seenByUpdated(); ++ } ++ // DivineMC end - Multithreaded tracker ++ } ++ } ++ ++ // DivineMC start - Multithreaded tracker ++ public final @Nullable Runnable tickCompact(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.bxteam.divinemc.config.DivineConfig.AsyncCategory.multithreadedEnabled || !org.bxteam.divinemc.config.DivineConfig.AsyncCategory.multithreadedCompatModeEnabled) { ++ 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 -- for (final ServerPlayerConnection conn : new java.util.ArrayList<>(this.seenBy)) { -- final ServerPlayer player = conn.getPlayer(); -- if (!players.contains(player)) { -- this.removePlayer(player); ++ + 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)) { ++ 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(); } } ++ }; ++ ++ // 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; } -+ // DivineMC end - Multithreaded tracker + } ++ // DivineMC end - Multithreaded tracker + + @Override + public final void moonrise$removeNonTickThreadPlayers() { +@@ -1193,12 +1317,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()) { // DivineMC - Multithreaded tracker + ServerPlayer player = conn.getPlayer(); + if (!ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(player)) { +- this.removePlayer(player); ++ this.removePlayerMulti(player); // DivineMC - Multithreaded tracker + } + } ++ this.seenByUpdated(); // DivineMC - Multithreaded tracker } @Override -@@ -1238,7 +1283,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider +@@ -1208,10 +1333,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()) { // DivineMC - Multithreaded tracker + ServerPlayer player = conn.getPlayer(); +- this.removePlayer(player); ++ this.removePlayerMulti(player); // DivineMC - Multithreaded tracker + } ++ this.seenByUpdated(); // DivineMC - Multithreaded tracker + } + + @Override +@@ -1238,7 +1364,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])) { // DivineMC - Multithreaded tracker ++ for (ServerPlayerConnection serverPlayerConnection : this.seenBy()) { // DivineMC - Multithreaded tracker serverPlayerConnection.send(packet); } } -@@ -1259,21 +1304,20 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider +@@ -1259,21 +1385,32 @@ 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])) { // DivineMC - Multithreaded tracker ++ for (ServerPlayerConnection serverPlayerConnection : this.seenBy()) { // DivineMC - Multithreaded tracker this.serverEntity.removePairing(serverPlayerConnection.getPlayer()); } } ++ // DivineMC start - Multithreaded tracker ++ public boolean removePlayerMulti(ServerPlayer player) { ++ if (this.seenBy.remove(player.connection)) { ++ this.serverEntity.removePairing(player); ++ return true; ++ } else { ++ return false; ++ } ++ } ++ // DivineMC end - Multithreaded tracker ++ public void removePlayer(ServerPlayer player) { - org.spigotmc.AsyncCatcher.catchOp("player tracker clear"); // Spigot if (this.seenBy.remove(player.connection)) { this.serverEntity.removePairing(player); } ++ this.seenByUpdated(); // DivineMC - Multithreaded tracker } public void updatePlayer(ServerPlayer player) { @@ -151,6 +312,22 @@ index edda52a8430386238be4963e8ea2406f0c2d4df3..687af4f52dc3ae5564079b6782b63a42 // Paper start - remove allocation of Vec3D here // Vec3 vec3 = player.position().subtract(this.entity.position()); double vec3_dx = player.getX() - this.entity.getX(); +@@ -1301,6 +1438,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + // CraftBukkit end + if (flag) { + if (this.seenBy.add(player.connection)) { ++ this.seenByUpdated(); // DivineMC - Multithreaded tracker + // 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); +@@ -1309,6 +1447,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(); // DivineMC - Multithreaded tracker + this.serverEntity.removePairing(player); + } + } diff --git a/net/minecraft/server/level/ServerBossEvent.java b/net/minecraft/server/level/ServerBossEvent.java index f106373ef3ac4a8685c2939c9e8361688a285913..b844b6dd89bc53b74c0d1bdbf4657c115a892dc7 100644 --- a/net/minecraft/server/level/ServerBossEvent.java @@ -169,39 +346,40 @@ index f106373ef3ac4a8685c2939c9e8361688a285913..b844b6dd89bc53b74c0d1bdbf4657c11 public boolean visible = true; diff --git a/net/minecraft/server/level/ServerEntity.java b/net/minecraft/server/level/ServerEntity.java -index e96d4dee14c05f2fa329bfb1588ec795d4e3d730..fa0fd2a7f15a076ac981f85f4f249a15a4ab512e 100644 +index e96d4dee14c05f2fa329bfb1588ec795d4e3d730..917029d96afb5843276f4fa4ee37292327aea626 100644 --- a/net/minecraft/server/level/ServerEntity.java +++ b/net/minecraft/server/level/ServerEntity.java -@@ -417,12 +417,13 @@ public class ServerEntity { - if (this.entity instanceof LivingEntity) { - Set attributesToSync = ((LivingEntity)this.entity).getAttributes().getAttributesToSync(); - if (!attributesToSync.isEmpty()) { -+ final Set copy = new it.unimi.dsi.fastutil.objects.ObjectOpenHashSet<>(attributesToSync); // DivineMC - Multithreaded tracker - // CraftBukkit start - Send scaled max health - if (this.entity instanceof ServerPlayer serverPlayer) { -- serverPlayer.getBukkitEntity().injectScaledMaxHealth(attributesToSync, false); -+ serverPlayer.getBukkitEntity().injectScaledMaxHealth(copy, false); // DivineMC - Multithreaded tracker - } +@@ -134,7 +134,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 // DivineMC - Multithreaded tracker + final ServerPlayer serverPlayer = connection.getPlayer(); // Paper + savedData.tickCarriedBy(serverPlayer, item); + Packet updatePacket = savedData.getUpdatePacket(mapId, serverPlayer); +@@ -424,8 +424,6 @@ public class ServerEntity { // CraftBukkit end -- this.broadcastAndSend(new ClientboundUpdateAttributesPacket(this.entity.getId(), attributesToSync)); -+ this.broadcastAndSend(new ClientboundUpdateAttributesPacket(this.entity.getId(), copy)); // DivineMC - Multithreaded tracker + this.broadcastAndSend(new ClientboundUpdateAttributesPacket(this.entity.getId(), attributesToSync)); } +- +- attributesToSync.clear(); + } + } - attributesToSync.clear(); diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java -index 9dcb9e5ecc31fcc3fc7547a47ec98d2689698769..dda62ab1d718d22e24ed5c88674d7a3d9dd4323e 100644 +index 9dcb9e5ecc31fcc3fc7547a47ec98d2689698769..2560799fe6ec006916a2bc9915355a358ab6c8bb 100644 --- a/net/minecraft/server/level/ServerLevel.java +++ b/net/minecraft/server/level/ServerLevel.java -@@ -2504,7 +2504,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe +@@ -2504,7 +2504,6 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe @Override public LevelEntityGetter getEntities() { - org.spigotmc.AsyncCatcher.catchOp("Chunk getEntities call"); // Spigot -+ //org.spigotmc.AsyncCatcher.catchOp("Chunk getEntities call"); // DivineMC - Multithreaded tracker return this.moonrise$getEntityLookup(); // Paper - rewrite chunk system } -@@ -2771,7 +2771,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe +@@ -2771,7 +2770,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe } map.carriedByPlayers.remove(player); @@ -211,41 +389,142 @@ index 9dcb9e5ecc31fcc3fc7547a47ec98d2689698769..dda62ab1d718d22e24ed5c88674d7a3d } } diff --git a/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index 5c8489b6cecf83c087d648ae2c10243ec0b4b614..e1e5e9d49439355d5f52a90e308cbad3ad74b41f 100644 +index a8bca25578d6428565bff853332a1bd868e9f740..34571dd4683c6d32b909d36436db8f5a4ce2628f 100644 --- a/net/minecraft/server/network/ServerGamePacketListenerImpl.java +++ b/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -1917,7 +1917,7 @@ public class ServerGamePacketListenerImpl +@@ -1917,7 +1917,6 @@ public class ServerGamePacketListenerImpl } public void internalTeleport(PositionMoveRotation posMoveRotation, Set relatives) { - org.spigotmc.AsyncCatcher.catchOp("teleport"); // Paper -+ //org.spigotmc.AsyncCatcher.catchOp("teleport"); // DivineMC - Multithreaded tracker // 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/LivingEntity.java b/net/minecraft/world/entity/LivingEntity.java +index e3babfa292556c5f5a208536a3f869dc71b82498..b8bd792293cb5985db5e5dfa5644930971a34632 100644 +--- a/net/minecraft/world/entity/LivingEntity.java ++++ b/net/minecraft/world/entity/LivingEntity.java +@@ -1335,13 +1335,13 @@ public abstract class LivingEntity extends Entity implements Attackable, Waypoin + } + + private void refreshDirtyAttributes() { +- Set attributesToUpdate = this.getAttributes().getAttributesToUpdate(); ++ // DivineMC start - Multithreaded tracker ++ int[] attributesToUpdate = this.getAttributes().getAttributesToUpdateIds(); + +- for (AttributeInstance attributeInstance : attributesToUpdate) { +- this.onAttributeUpdated(attributeInstance.getAttribute()); ++ for (int attribute : attributesToUpdate) { ++ this.onAttributeUpdated(net.minecraft.core.registries.BuiltInRegistries.ATTRIBUTE.get(attribute).orElseThrow()); + } +- +- attributesToUpdate.clear(); ++ // DivineMC end - Multithreaded tracker + } + + protected void onAttributeUpdated(Holder attribute) { +diff --git a/net/minecraft/world/entity/ai/attributes/Attribute.java b/net/minecraft/world/entity/ai/attributes/Attribute.java +index f8419dde44ebc7324e783f8bee42132d5ec973c3..776445dd2dfee9d386ccb3ad17746d4c405a96b9 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; ++ // DivineMC start - Multithreaded Tracker ++ public final int uid; ++ private static final java.util.concurrent.atomic.AtomicInteger SIZE = new java.util.concurrent.atomic.AtomicInteger(); ++ // DivineMC end - Multithreaded Tracker + + protected Attribute(String descriptionId, double defaultValue) { + this.defaultValue = defaultValue; + this.descriptionId = descriptionId; ++ this.uid = SIZE.getAndAdd(1); + } + + public double getDefaultValue() { diff --git a/net/minecraft/world/entity/ai/attributes/AttributeInstance.java b/net/minecraft/world/entity/ai/attributes/AttributeInstance.java -index 42ad600c6a5cb20e1d820f169f6a1a17ef3a5195..2ce6697a0bd2a094bbfdd0e2a9c7665e14837d40 100644 +index 42ad600c6a5cb20e1d820f169f6a1a17ef3a5195..c93b2c684d773551b14cc2ce024923536780ee17 100644 --- a/net/minecraft/world/entity/ai/attributes/AttributeInstance.java +++ b/net/minecraft/world/entity/ai/attributes/AttributeInstance.java -@@ -22,8 +22,11 @@ public class AttributeInstance { +@@ -22,8 +22,24 @@ public class AttributeInstance { private final Map> modifiersByOperation = Maps.newEnumMap( AttributeModifier.Operation.class ); - private final Map modifierById = new Object2ObjectArrayMap<>(); - private final Map permanentModifiers = new Object2ObjectArrayMap<>(); + // DivineMC start - Multithreaded tracker -+ private final boolean multiThreadedTrackingEnabled = org.bxteam.divinemc.config.DivineConfig.AsyncCategory.multithreadedEnabled; -+ private final Map modifierById = multiThreadedTrackingEnabled ? new java.util.concurrent.ConcurrentHashMap<>() : new Object2ObjectArrayMap<>(); -+ private final Map permanentModifiers = multiThreadedTrackingEnabled ? new java.util.concurrent.ConcurrentHashMap<>() : new Object2ObjectArrayMap<>(); ++ private final Map modifierById; ++ { ++ if (org.bxteam.divinemc.config.DivineConfig.AsyncCategory.multithreadedEnabled) { ++ modifierById = it.unimi.dsi.fastutil.objects.Object2ObjectMaps.synchronize(new Object2ObjectArrayMap<>(), this); ++ } else { ++ modifierById = new Object2ObjectArrayMap<>(); ++ } ++ } ++ private final Map permanentModifiers; ++ { ++ if (org.bxteam.divinemc.config.DivineConfig.AsyncCategory.multithreadedEnabled) { ++ permanentModifiers = it.unimi.dsi.fastutil.objects.Object2ObjectMaps.synchronize(new Object2ObjectArrayMap<>(), this); ++ } else { ++ permanentModifiers = new Object2ObjectArrayMap<>(); ++ } ++ } + // DivineMC end - Multithreaded tracker private double baseValue; private boolean dirty = true; private double cachedValue; +@@ -52,7 +68,13 @@ public class AttributeInstance { + + @VisibleForTesting + Map getModifiers(AttributeModifier.Operation operation) { +- return this.modifiersByOperation.computeIfAbsent(operation, operation1 -> new Object2ObjectOpenHashMap<>()); ++ // DivineMC start - Multithreaded tracker ++ return this.modifiersByOperation.computeIfAbsent(operation, operation1 -> { ++ if (org.bxteam.divinemc.config.DivineConfig.AsyncCategory.multithreadedEnabled) ++ return it.unimi.dsi.fastutil.objects.Object2ObjectMaps.synchronize(new Object2ObjectArrayMap<>(), this); ++ else return new Object2ObjectArrayMap<>(); ++ }); ++ // DivineMC end - Multithreaded tracker + } + + public Set getModifiers() { +@@ -140,8 +162,12 @@ public class AttributeInstance { + + public double getValue() { + if (this.dirty) { +- this.cachedValue = this.calculateValue(); ++ // DivineMC start - Multithreaded tracker ++ double value = this.calculateValue(); ++ this.cachedValue = value; + this.dirty = false; ++ return value; ++ // DivineMC end - Multithreaded tracker + } + + return this.cachedValue; +@@ -184,7 +210,15 @@ public class AttributeInstance { + } + + public AttributeInstance.Packed pack() { +- return new AttributeInstance.Packed(this.attribute, this.baseValue, List.copyOf(this.permanentModifiers.values())); ++ // DivineMC start - Multithreaded tracker ++ if (org.bxteam.divinemc.config.DivineConfig.AsyncCategory.multithreadedEnabled) { ++ synchronized (this) { ++ return new AttributeInstance.Packed(this.attribute, this.baseValue, List.copyOf(this.permanentModifiers.values())); ++ } ++ } else { ++ return new AttributeInstance.Packed(this.attribute, this.baseValue, List.copyOf(this.permanentModifiers.values())); ++ } ++ // DivineMC end - Multithreaded tracker + } + + public void apply(AttributeInstance.Packed instance) { diff --git a/net/minecraft/world/entity/ai/attributes/AttributeMap.java b/net/minecraft/world/entity/ai/attributes/AttributeMap.java -index 7dd8c1c8e27410854ce1ee90defc607c2710b5a2..de647aa4eee2d9af2d2c5d1cde3a8a836d4f7e48 100644 +index 7dd8c1c8e27410854ce1ee90defc607c2710b5a2..290a7fa565f695c7afe3cf0791f6cf1da6a39663 100644 --- a/net/minecraft/world/entity/ai/attributes/AttributeMap.java +++ b/net/minecraft/world/entity/ai/attributes/AttributeMap.java -@@ -14,9 +14,12 @@ import net.minecraft.core.Holder; +@@ -14,9 +14,11 @@ import net.minecraft.core.Holder; import net.minecraft.resources.ResourceLocation; public class AttributeMap { @@ -253,14 +532,111 @@ index 7dd8c1c8e27410854ce1ee90defc607c2710b5a2..de647aa4eee2d9af2d2c5d1cde3a8a83 - private final Set attributesToSync = new ObjectOpenHashSet<>(); - private final Set attributesToUpdate = new ObjectOpenHashSet<>(); + // DivineMC start - Multithreaded tracker -+ private final boolean multiThreadedTrackingEnabled = org.bxteam.divinemc.config.DivineConfig.AsyncCategory.multithreadedEnabled; -+ private final Map, AttributeInstance> attributes = multiThreadedTrackingEnabled ? new java.util.concurrent.ConcurrentHashMap<>() : new it.unimi.dsi.fastutil.objects.Reference2ReferenceOpenHashMap<>(0); -+ private final Set attributesToSync = multiThreadedTrackingEnabled ? com.google.common.collect.Sets.newConcurrentHashSet() : new it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet<>(0); -+ private final Set attributesToUpdate = multiThreadedTrackingEnabled ? com.google.common.collect.Sets.newConcurrentHashSet() : new it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet<>(0); ++ private final Map, AttributeInstance> attributes = new org.bxteam.divinemc.util.map.AttributeInstanceArrayMap(); ++ private final org.bxteam.divinemc.util.map.AttributeInstanceSet attributesToSync = new org.bxteam.divinemc.util.map.AttributeInstanceSet((org.bxteam.divinemc.util.map.AttributeInstanceArrayMap) attributes); ++ private final org.bxteam.divinemc.util.map.AttributeInstanceSet attributesToUpdate = new org.bxteam.divinemc.util.map.AttributeInstanceSet((org.bxteam.divinemc.util.map.AttributeInstanceArrayMap) attributes); + // DivineMC end - Multithreaded tracker private final AttributeSupplier supplier; private final net.minecraft.world.entity.LivingEntity entity; // Purpur - Ridables +@@ -30,28 +32,52 @@ public class AttributeMap { + this.supplier = defaultAttributes; + } + +- private void onAttributeModified(AttributeInstance instance) { ++ private synchronized void onAttributeModified(AttributeInstance instance) { // DivineMC - Multithreaded Tracker + this.attributesToUpdate.add(instance); + if (instance.getAttribute().value().isClientSyncable() && (entity == null || entity.shouldSendAttribute(instance.getAttribute().value()))) { // Purpur - Ridables + this.attributesToSync.add(instance); + } + } + +- public Set getAttributesToSync() { +- return this.attributesToSync; ++ // DivineMC start - Multithreaded Tracker ++ private static final AttributeInstance[] EMPTY_ATTRIBUTE_INSTANCE = new AttributeInstance[0]; ++ public synchronized Set getAttributesToSync() { ++ var clone = it.unimi.dsi.fastutil.objects.ReferenceArraySet.ofUnchecked(attributesToSync.toArray(EMPTY_ATTRIBUTE_INSTANCE)); ++ this.attributesToSync.clear(); ++ return clone; + } + +- public Set getAttributesToUpdate() { +- return this.attributesToUpdate; ++ public synchronized Set getAttributesToUpdate() { ++ var clone = it.unimi.dsi.fastutil.objects.ReferenceArraySet.ofUnchecked(attributesToUpdate.toArray(EMPTY_ATTRIBUTE_INSTANCE)); ++ this.attributesToUpdate.clear(); ++ return clone; + } + ++ public synchronized int[] getAttributesToUpdateIds() { ++ int[] clone = attributesToUpdate.inner.toIntArray(); ++ this.attributesToUpdate.clear(); ++ return clone; ++ } ++ // DivineMC end - Multithreaded Tracker ++ + public Collection getSyncableAttributes() { + return this.attributes.values().stream().filter(instance -> instance.getAttribute().value().isClientSyncable() && (entity == null || entity.shouldSendAttribute(instance.getAttribute().value()))).collect(Collectors.toList()); // Purpur - Ridables + } + + @Nullable + public AttributeInstance getInstance(Holder attribute) { +- return this.attributes.computeIfAbsent(attribute, holder -> this.supplier.createInstance(this::onAttributeModified, (Holder)holder)); ++ // DivineMC start - Multithreaded Tracker ++ AttributeInstance v; ++ if ((v = this.attributes.get(attribute)) == null) { ++ AttributeInstance newValue; ++ if ((newValue = this.supplier.createInstance(this::onAttributeModified, attribute)) != null) { ++ attributes.put(attribute, newValue); ++ return newValue; ++ } ++ } ++ ++ return v; ++ // DivineMC end - Multithreaded Tracker + } + + 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..dbcff8bdd6911843bc42f64d5dcf1bb854128075 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.bxteam.divinemc.util.map.AttributeInstanceArrayMap(instances); // DivineMC - Multithreaded Tracker + } + + public AttributeInstance getAttributeInstance(Holder attribute) { +diff --git a/net/minecraft/world/entity/vehicle/NewMinecartBehavior.java b/net/minecraft/world/entity/vehicle/NewMinecartBehavior.java +index 325ec57df2885f5e81b8a6b61e3a9fed9484b30f..1796f0a6f647c94b0943a6003a1307795294805e 100644 +--- a/net/minecraft/world/entity/vehicle/NewMinecartBehavior.java ++++ b/net/minecraft/world/entity/vehicle/NewMinecartBehavior.java +@@ -35,13 +35,20 @@ public class NewMinecartBehavior extends MinecartBehavior { + private int cachedLerpDelay; + private float cachedPartialTick; + private int lerpDelay = 0; +- public final List lerpSteps = new LinkedList<>(); ++ public final List lerpSteps; // DivineMC - Multithreaded Tracker + public final List currentLerpSteps = new LinkedList<>(); + public double currentLerpStepsTotalWeight = 0.0; + public NewMinecartBehavior.MinecartStep oldLerp = NewMinecartBehavior.MinecartStep.ZERO; + + public NewMinecartBehavior(AbstractMinecart minecart) { + super(minecart); ++ // DivineMC start - Multithreaded Tracker ++ if (org.bxteam.divinemc.config.DivineConfig.AsyncCategory.multithreadedEnabled) { ++ this.lerpSteps = it.unimi.dsi.fastutil.objects.ObjectLists.synchronize(new it.unimi.dsi.fastutil.objects.ObjectArrayList<>()); ++ } else { ++ this.lerpSteps = new it.unimi.dsi.fastutil.objects.ObjectArrayList<>(); ++ } ++ // DivineMC end - Multithreaded Tracker + } + + @Override diff --git a/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java b/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java index 7bbeed6c998c91e68376d3f17a510d68e3cd0b27..de7b3a8a7c841360310a88005da02a0733b46714 100644 --- a/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java diff --git a/divinemc-server/minecraft-patches/features/0008-Misc-Optimizations.patch b/divinemc-server/minecraft-patches/features/0008-Misc-Optimizations.patch index 37a6fc4..2bc1440 100644 --- a/divinemc-server/minecraft-patches/features/0008-Misc-Optimizations.patch +++ b/divinemc-server/minecraft-patches/features/0008-Misc-Optimizations.patch @@ -569,39 +569,6 @@ index c8354d46ed909090f7c15f396863bf7d73afcefa..382ef2dad5e995bc01f6492218b8c8f7 } } -diff --git a/net/minecraft/world/entity/Mob.java b/net/minecraft/world/entity/Mob.java -index e1f5a4814d051a43090bf6df2acbcd20fbbc1934..fd586acb15362a461c77256c6db9cc3a8002750d 100644 ---- a/net/minecraft/world/entity/Mob.java -+++ b/net/minecraft/world/entity/Mob.java -@@ -715,7 +715,7 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab - if (this.level().getDifficulty() == Difficulty.PEACEFUL && this.shouldDespawnInPeaceful()) { - this.discard(EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - add Bukkit remove cause - } else if (!this.isPersistenceRequired() && !this.requiresCustomPersistence()) { -- Entity nearestPlayer = this.level().findNearbyPlayer(this, -1.0, EntitySelector.PLAYER_AFFECTS_SPAWNING); // Paper - Affects Spawning API -+ Entity nearestPlayer = this.divinemc$findNearbyPlayer(this.level(), this, -1.0); // Paper - Affects Spawning API // DivineMC - faster player lookup - if (nearestPlayer != null) { - // Paper start - Configurable despawn distances - final io.papermc.paper.configuration.WorldConfiguration.Entities.Spawning.DespawnRangePair despawnRangePair = this.level().paperConfig().entities.spawning.despawnRanges.get(this.getType().getCategory()); -@@ -744,6 +744,19 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab - } - } - -+ // DivineMC start - faster player lookup -+ private Player divinemc$findNearbyPlayer(Level instance, Entity entity, double maxDistance) { -+ final Player closestPlayer = instance.getNearestPlayer(entity, this.getType().getCategory().getDespawnDistance()); -+ if (closestPlayer != null) { -+ return closestPlayer; -+ } else { -+ final List players = this.level().players(); -+ if (players.isEmpty()) return null; -+ return players.get(0); -+ } -+ } -+ // DivineMC end - faster player lookup -+ - @Override - protected final void serverAiStep() { - this.noActionTime++; diff --git a/net/minecraft/world/level/GameRules.java b/net/minecraft/world/level/GameRules.java index d510503a8ad272255aeba20a916642828023fd19..2d9bf302b779602d733187c6f86e52467f0dc540 100644 --- a/net/minecraft/world/level/GameRules.java diff --git a/divinemc-server/minecraft-patches/features/0010-Chunk-System-Optimizations.patch b/divinemc-server/minecraft-patches/features/0010-Chunk-System-Optimizations.patch index 6079f54..34342b8 100644 --- a/divinemc-server/minecraft-patches/features/0010-Chunk-System-Optimizations.patch +++ b/divinemc-server/minecraft-patches/features/0010-Chunk-System-Optimizations.patch @@ -5,22 +5,24 @@ Subject: [PATCH] Chunk System Optimizations diff --git a/ca/spottedleaf/moonrise/common/misc/NearbyPlayers.java b/ca/spottedleaf/moonrise/common/misc/NearbyPlayers.java -index 1b8193587814225c2ef2c5d9e667436eb50ff6c5..78e3c49a233dc6bed558458d555fe740a8cc839e 100644 +index 93272808d94e81d31af728ebe85df9a2bc7aedab..17c0f25206b12665518142f27c17d17f315aad5f 100644 --- a/ca/spottedleaf/moonrise/common/misc/NearbyPlayers.java +++ b/ca/spottedleaf/moonrise/common/misc/NearbyPlayers.java -@@ -59,12 +59,15 @@ public final class NearbyPlayers { +@@ -59,7 +59,7 @@ public final class NearbyPlayers { public static final int GENERAL_REALLY_SMALL_AREA_VIEW_DISTANCE_BLOCKS = (GENERAL_REALLY_SMALL_VIEW_DISTANCE << 4); private final ServerLevel world; - private final Reference2ReferenceOpenHashMap players = new Reference2ReferenceOpenHashMap<>(); -- private final Long2ReferenceOpenHashMap byChunk = new Long2ReferenceOpenHashMap<>(); ++ private final it.unimi.dsi.fastutil.objects.Reference2ReferenceMap players = it.unimi.dsi.fastutil.objects.Reference2ReferenceMaps.synchronize(new Reference2ReferenceOpenHashMap<>()); // DivineMC - Chunk System optimization + // DivineMC start - Multithreaded Tracker + private final it.unimi.dsi.fastutil.longs.Long2ReferenceMap byChunk; + { +@@ -70,10 +70,10 @@ public final class NearbyPlayers { + } + } + // DivineMC end - Multithreaded Tracker - private final Long2ReferenceOpenHashMap>[] directByChunk = new Long2ReferenceOpenHashMap[TOTAL_MAP_TYPES]; -+ // DivineMC start - Chunk System optimization -+ private final Object callbackLock = new Object(); -+ private final it.unimi.dsi.fastutil.objects.Reference2ReferenceMap players = it.unimi.dsi.fastutil.objects.Reference2ReferenceMaps.synchronize(new Reference2ReferenceOpenHashMap<>()); -+ private final it.unimi.dsi.fastutil.longs.Long2ReferenceMap byChunk = it.unimi.dsi.fastutil.longs.Long2ReferenceMaps.synchronize(new Long2ReferenceOpenHashMap<>()); -+ private final it.unimi.dsi.fastutil.longs.Long2ReferenceMap>[] directByChunk = new it.unimi.dsi.fastutil.longs.Long2ReferenceMap[TOTAL_MAP_TYPES]; -+ // DivineMC end - Chunk System optimization ++ private final it.unimi.dsi.fastutil.longs.Long2ReferenceMap>[] directByChunk = new it.unimi.dsi.fastutil.longs.Long2ReferenceMap[TOTAL_MAP_TYPES]; // DivineMC - Chunk System optimization { for (int i = 0; i < this.directByChunk.length; ++i) { - this.directByChunk[i] = new Long2ReferenceOpenHashMap<>(); @@ -103,6 +105,43 @@ index b2bcfb3557a0326fd7ec1059f95d6da4568dfd80..6bb36686ae7ca9f4bf763baa89408614 } } +diff --git a/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/EntityLookup.java b/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/EntityLookup.java +index 2d24d03bbdb5ee0d862cbfff2219f58afffafe12..950b284cb3b4488a794e6c6f936f55ea427ef7cc 100644 +--- a/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/EntityLookup.java ++++ b/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/EntityLookup.java +@@ -93,8 +93,14 @@ public abstract class EntityLookup implements LevelEntityGetter { + if (entity == null) { + return null; + } +- final Visibility visibility = EntityLookup.getEntityStatus(entity); +- return visibility.isAccessible() ? entity : null; ++ // DivineMC start - Chunk System Optimizations ++ final FullChunkStatus entityStatus = ((ChunkSystemEntity) entity).moonrise$getChunkStatus(); ++ return switch (entityStatus) { ++ case INACCESSIBLE -> null; ++ case FULL, BLOCK_TICKING, ENTITY_TICKING -> entity; ++ case null -> null; ++ }; ++ // DivineMC end - Chunk System Optimizations + } + + @Override +@@ -398,7 +404,14 @@ public abstract class EntityLookup implements LevelEntityGetter { + return Visibility.TICKING; + } + final FullChunkStatus entityStatus = ((ChunkSystemEntity)entity).moonrise$getChunkStatus(); +- return Visibility.fromFullChunkStatus(entityStatus == null ? FullChunkStatus.INACCESSIBLE : entityStatus); ++ // DivineMC start - Chunk System Optimizations ++ return switch (entityStatus) { ++ case INACCESSIBLE -> Visibility.HIDDEN; ++ case FULL, BLOCK_TICKING -> Visibility.TRACKED; ++ case ENTITY_TICKING -> Visibility.TICKING; ++ case null -> Visibility.HIDDEN; ++ }; ++ // DivineMC end - Chunk System Optimizations + } + + protected boolean addEntity(final Entity entity, final boolean fromDisk, final boolean event) { diff --git a/ca/spottedleaf/moonrise/patches/chunk_system/player/RegionizedPlayerChunkLoader.java b/ca/spottedleaf/moonrise/patches/chunk_system/player/RegionizedPlayerChunkLoader.java index dc2b3ccf7810731c0e2c90e5a476c1c8203a1fb7..5cb896334f9916b030ee523119946d3b40585fc3 100644 --- a/ca/spottedleaf/moonrise/patches/chunk_system/player/RegionizedPlayerChunkLoader.java @@ -921,7 +960,7 @@ index df6fbb35e5023b42de0b97434712e04a6b3e66a3..8c6b853c77e5b3af90913e4a878f344b commands.put(Set.of("fixlight"), new FixLightCommand()); // Paper - rewrite chunk system commands.put(Set.of("debug", "chunkinfo", "holderinfo"), new ChunkDebugCommand()); // Paper - rewrite chunk system diff --git a/net/minecraft/server/level/ChunkMap.java b/net/minecraft/server/level/ChunkMap.java -index 687af4f52dc3ae5564079b6782b63a4277c43439..ab3b1c7442b9f3dd72a9ce8c70dc708d2371471a 100644 +index 2c5104b9ae1d2ea902eeac5a1c9d49bc1af67c43..7ebaf03342e8802948b4f412c3f7120927f7b3b3 100644 --- a/net/minecraft/server/level/ChunkMap.java +++ b/net/minecraft/server/level/ChunkMap.java @@ -132,8 +132,8 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider @@ -935,20 +974,7 @@ index 687af4f52dc3ae5564079b6782b63a4277c43439..ab3b1c7442b9f3dd72a9ce8c70dc708d // Paper - rewrite chunk system public int serverViewDistance; public final WorldGenContext worldGenContext; // Paper - public -@@ -256,7 +256,11 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - - final ServerPlayer[] backingSet = inRange.getRawDataUnchecked(); - for (int i = 0, len = inRange.size(); i < len; i++) { -- ++(backingSet[i].mobCounts[index]); -+ // DivineMC start - Chunk System Optimizations -+ ServerPlayer player = backingSet[i]; -+ if (player == null) continue; -+ ++(player.mobCounts[index]); -+ // DivineMC end - Chunk System Optimizations - } - } - -@@ -273,7 +277,11 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider +@@ -283,7 +283,11 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider } final ServerPlayer[] backingSet = inRange.getRawDataUnchecked(); for (int i = 0, len = inRange.size(); i < len; i++) { @@ -961,7 +987,7 @@ index 687af4f52dc3ae5564079b6782b63a4277c43439..ab3b1c7442b9f3dd72a9ce8c70dc708d } } // Paper end - per player mob count backoff -@@ -774,27 +782,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider +@@ -784,27 +788,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider return false; } @@ -990,7 +1016,7 @@ index 687af4f52dc3ae5564079b6782b63a4277c43439..ab3b1c7442b9f3dd72a9ce8c70dc708d // Paper end - chunk tick iteration optimisation } -@@ -812,10 +800,10 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider +@@ -822,10 +806,10 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider final ServerPlayer[] raw = players.getRawDataUnchecked(); final int len = players.size(); @@ -1004,9 +1030,9 @@ index 687af4f52dc3ae5564079b6782b63a4277c43439..ab3b1c7442b9f3dd72a9ce8c70dc708d if (ret == null) { ret = new ArrayList<>(len - i); ret.add(player); -@@ -1208,6 +1196,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - } else { - for (int i = 0, len = players.size(); i < len; ++i) { +@@ -1272,6 +1256,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + Runnable updatePlayerTasks = () -> { + for (int i = 0; i < playersLen; ++i) { final ServerPlayer player = playersRaw[i]; + if (player == null) continue; // DivineMC - Chunk System Optimizations this.updatePlayer(player); @@ -1035,7 +1061,7 @@ index fd3d0f6cb53bc8b6186f0d86575f21007b2c20ed..7f3c41b59e288364d67534511fc038e6 } diff --git a/net/minecraft/server/level/ServerChunkCache.java b/net/minecraft/server/level/ServerChunkCache.java -index 75c8ce32e68f92e20201e9c243f46f2be716eac8..2873642844c683ae4388ae27a045e01441d15426 100644 +index 879d6eb8e72b63bc95d8028cbc2f6e93e516ab1d..6de832e7aec630914e70fb0f11223907ab28298c 100644 --- a/net/minecraft/server/level/ServerChunkCache.java +++ b/net/minecraft/server/level/ServerChunkCache.java @@ -444,8 +444,7 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon @@ -1049,7 +1075,7 @@ index 75c8ce32e68f92e20201e9c243f46f2be716eac8..2873642844c683ae4388ae27a045e014 } diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java -index d0d97e063acf8f11c32adf1fd2ec6bca59913c33..14c8df2f3b9c133b38e871e81f7ee2333322437c 100644 +index 740f6324eeb4021bc45d27d6145ff71282c761c2..0dbcf9460b27959f99f7a95824b711ee6a4e4c4d 100644 --- a/net/minecraft/server/level/ServerLevel.java +++ b/net/minecraft/server/level/ServerLevel.java @@ -179,6 +179,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe @@ -1156,7 +1182,7 @@ index d0d97e063acf8f11c32adf1fd2ec6bca59913c33..14c8df2f3b9c133b38e871e81f7ee233 } // Paper end - optimise random ticking -@@ -2559,16 +2561,13 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe +@@ -2558,16 +2560,13 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe public boolean isPositionTickingWithEntitiesLoaded(long chunkPos) { // Paper start - rewrite chunk system diff --git a/divinemc-server/minecraft-patches/features/0018-Block-Log4Shell-exploit.patch b/divinemc-server/minecraft-patches/features/0018-Block-Log4Shell-exploit.patch index fd8e236..afeaae4 100644 --- a/divinemc-server/minecraft-patches/features/0018-Block-Log4Shell-exploit.patch +++ b/divinemc-server/minecraft-patches/features/0018-Block-Log4Shell-exploit.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Block Log4Shell exploit diff --git a/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index 672995a6fb8c0f9e9155aa6f48edb1a52fd5cade..2858bd6c456e95adf80bb251044659e9e5c21700 100644 +index 8c94a5e7257c3728c6b23b9f943de999cf673a67..7e4a166169b1ef7dd2efedba918bc988c0dc82fd 100644 --- a/net/minecraft/server/network/ServerGamePacketListenerImpl.java +++ b/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -2516,6 +2516,7 @@ public class ServerGamePacketListenerImpl +@@ -2515,6 +2515,7 @@ public class ServerGamePacketListenerImpl } private void tryHandleChat(String message, Runnable handler, boolean sync) { // CraftBukkit @@ -16,7 +16,7 @@ index 672995a6fb8c0f9e9155aa6f48edb1a52fd5cade..2858bd6c456e95adf80bb251044659e9 if (isChatMessageIllegal(message)) { this.disconnectAsync(Component.translatable("multiplayer.disconnect.illegal_characters"), org.bukkit.event.player.PlayerKickEvent.Cause.ILLEGAL_CHARACTERS); // Paper - add proper async disconnect } else if (this.player.isRemoved() || this.player.getChatVisibility() == ChatVisiblity.HIDDEN) { // CraftBukkit - dead men tell no tales -@@ -2548,6 +2549,15 @@ public class ServerGamePacketListenerImpl +@@ -2547,6 +2548,15 @@ public class ServerGamePacketListenerImpl } } diff --git a/divinemc-server/minecraft-patches/features/0020-Optimize-canSee-checks.patch b/divinemc-server/minecraft-patches/features/0020-Optimize-canSee-checks.patch index 42dc5ae..b3b3c90 100644 --- a/divinemc-server/minecraft-patches/features/0020-Optimize-canSee-checks.patch +++ b/divinemc-server/minecraft-patches/features/0020-Optimize-canSee-checks.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Optimize canSee checks diff --git a/net/minecraft/server/level/ChunkMap.java b/net/minecraft/server/level/ChunkMap.java -index ab3b1c7442b9f3dd72a9ce8c70dc708d2371471a..1a9b7de55555484196de695f61982c940448b262 100644 +index 7ebaf03342e8802948b4f412c3f7120927f7b3b3..c47b38a9ef01c9f9d58d64b2662d1b0e51353c0d 100644 --- a/net/minecraft/server/level/ChunkMap.java +++ b/net/minecraft/server/level/ChunkMap.java -@@ -1328,7 +1328,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider +@@ -1417,7 +1417,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider flag = flag && this.entity.broadcastToPlayer(player) && ChunkMap.this.isChunkTracked(player, this.entity.chunkPosition().x, this.entity.chunkPosition().z); // Paper end - Configurable entity tracking range by Y // CraftBukkit start - respect vanish API diff --git a/divinemc-server/minecraft-patches/features/0026-Option-to-disable-disconnect.spam.patch b/divinemc-server/minecraft-patches/features/0026-Option-to-disable-disconnect.spam.patch index bb01254..4766c40 100644 --- a/divinemc-server/minecraft-patches/features/0026-Option-to-disable-disconnect.spam.patch +++ b/divinemc-server/minecraft-patches/features/0026-Option-to-disable-disconnect.spam.patch @@ -5,7 +5,7 @@ Subject: [PATCH] Option to disable disconnect.spam diff --git a/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index 121daa3509879dfec7cce629e1d5260fcbccb087..e4c7307d7efbcad86cd58a06bb53540faed4b574 100644 +index 4656b502b0d1025564147a917e99a58a5c31b007..f010c166a9744e1428f02aaf1a2218501435841e 100644 --- a/net/minecraft/server/network/ServerGamePacketListenerImpl.java +++ b/net/minecraft/server/network/ServerGamePacketListenerImpl.java @@ -839,7 +839,7 @@ public class ServerGamePacketListenerImpl @@ -34,7 +34,7 @@ index 121daa3509879dfec7cce629e1d5260fcbccb087..e4c7307d7efbcad86cd58a06bb53540f && parseResults.getExceptions().values().stream().anyMatch(e -> e instanceof io.papermc.paper.brigadier.TagParseCommandSyntaxException)) { this.disconnect(Component.translatable("disconnect.spam"), org.bukkit.event.player.PlayerKickEvent.Cause.SPAM); return; -@@ -2643,6 +2644,7 @@ public class ServerGamePacketListenerImpl +@@ -2642,6 +2643,7 @@ public class ServerGamePacketListenerImpl // this.chatSpamThrottler.increment(); if (!this.chatSpamThrottler.isIncrementAndUnderThreshold() // CraftBukkit end @@ -42,7 +42,7 @@ index 121daa3509879dfec7cce629e1d5260fcbccb087..e4c7307d7efbcad86cd58a06bb53540f && !this.server.getPlayerList().isOp(this.player.getGameProfile()) && !this.server.isSingleplayerOwner(this.player.getGameProfile())) { this.disconnectAsync(Component.translatable("disconnect.spam"), org.bukkit.event.player.PlayerKickEvent.Cause.SPAM); // Paper - kick event cause & add proper async disconnect -@@ -3400,7 +3402,7 @@ public class ServerGamePacketListenerImpl +@@ -3399,7 +3401,7 @@ public class ServerGamePacketListenerImpl public void handlePlaceRecipe(ServerboundPlaceRecipePacket packet) { // Paper start - auto recipe limit if (!org.bukkit.Bukkit.isPrimaryThread()) { diff --git a/divinemc-server/minecraft-patches/features/0037-Dynamic-Activation-of-Brain.patch b/divinemc-server/minecraft-patches/features/0037-Dynamic-Activation-of-Brain.patch index 5d7ea31..3a46378 100644 --- a/divinemc-server/minecraft-patches/features/0037-Dynamic-Activation-of-Brain.patch +++ b/divinemc-server/minecraft-patches/features/0037-Dynamic-Activation-of-Brain.patch @@ -31,7 +31,7 @@ index ca21597263cb430e2a5ae07e8cecfb0d53a270d2..226088405c019922085285ba5d04d7c1 } } diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java -index 05b8834b5566ac12655b563a1a58f123275fdedb..e05a0eed63098c535360b66276f154e4dc119d6a 100644 +index b4876d35ee6a4e0dab144b471520234157d7173b..00d9e6421185b1ba4f0fdff347a53cae5b830a0a 100644 --- a/net/minecraft/server/level/ServerLevel.java +++ b/net/minecraft/server/level/ServerLevel.java @@ -804,6 +804,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe @@ -43,7 +43,7 @@ index 05b8834b5566ac12655b563a1a58f123275fdedb..e05a0eed63098c535360b66276f154e4 if (!tickRateManager.isEntityFrozen(entity)) { entity.checkDespawn(); diff --git a/net/minecraft/world/entity/Entity.java b/net/minecraft/world/entity/Entity.java -index 55e82d490ed3489ff671ae8a49745571c4bfb993..e96eeae793d127b76a2fe6853cba49ce192b91f9 100644 +index 9971c034391c13237dc403cdc9f8806b3bfc69f4..1e201ff091bdf33c96e2cfa333030ae105ab7bc9 100644 --- a/net/minecraft/world/entity/Entity.java +++ b/net/minecraft/world/entity/Entity.java @@ -365,6 +365,8 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess @@ -56,7 +56,7 @@ index 55e82d490ed3489ff671ae8a49745571c4bfb993..e96eeae793d127b76a2fe6853cba49ce // Paper start - EAR 2 public final boolean defaultActivationState; diff --git a/net/minecraft/world/entity/EntityType.java b/net/minecraft/world/entity/EntityType.java -index c960166814cdb3043a38de40c59cb067ae936ba3..79d93bb4c7dd0f7c8c4cc8bafb893a0d93f70f2b 100644 +index 9950fccc0a708e701b81fcabc9e8f370e6d3a19d..0159627e2c9a540d062073faf9018f5215e10866 100644 --- a/net/minecraft/world/entity/EntityType.java +++ b/net/minecraft/world/entity/EntityType.java @@ -1085,6 +1085,7 @@ public class EntityType implements FeatureElement, EntityTypeT @@ -68,7 +68,7 @@ index c960166814cdb3043a38de40c59cb067ae936ba3..79d93bb4c7dd0f7c8c4cc8bafb893a0d @Nullable private Component description; diff --git a/net/minecraft/world/entity/Mob.java b/net/minecraft/world/entity/Mob.java -index fd586acb15362a461c77256c6db9cc3a8002750d..7f1b36ca38a15aa987afb8069fd4e34bed4c569a 100644 +index e1f5a4814d051a43090bf6df2acbcd20fbbc1934..c4292b95a0a90569aa8708cc3e54433d9757f1a5 100644 --- a/net/minecraft/world/entity/Mob.java +++ b/net/minecraft/world/entity/Mob.java @@ -209,10 +209,10 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab @@ -84,7 +84,7 @@ index fd586acb15362a461c77256c6db9cc3a8002750d..7f1b36ca38a15aa987afb8069fd4e34b this.targetSelector.tick(); } } -@@ -771,13 +771,19 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab +@@ -758,13 +758,19 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab // Paper end - Allow nerfed mobs to jump and float this.sensing.tick(); int i = this.tickCount + this.getId(); diff --git a/divinemc-server/minecraft-patches/features/0045-Parallel-world-ticking.patch b/divinemc-server/minecraft-patches/features/0045-Parallel-world-ticking.patch index 95d268e..281e868 100644 --- a/divinemc-server/minecraft-patches/features/0045-Parallel-world-ticking.patch +++ b/divinemc-server/minecraft-patches/features/0045-Parallel-world-ticking.patch @@ -3,9 +3,10 @@ From: NONPLAYT <76615486+NONPLAYT@users.noreply.github.com> Date: Wed, 29 Jan 2025 00:59:03 +0300 Subject: [PATCH] Parallel world ticking +Original project: https://github.com/SparklyPower/SparklyPaper diff --git a/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkHolderManager.java b/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkHolderManager.java -index dce2b0ae83e70ccaf2ac97441f80b25876ee9058..d10c42cb10f419b59b0b9176eda97d54075d6041 100644 +index dce2b0ae83e70ccaf2ac97441f80b25876ee9058..a25db7dd56e31f6a34e4b185b75c727e290e9af1 100644 --- a/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkHolderManager.java +++ b/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkHolderManager.java @@ -1155,7 +1155,7 @@ public final class ChunkHolderManager { @@ -13,7 +14,7 @@ index dce2b0ae83e70ccaf2ac97441f80b25876ee9058..d10c42cb10f419b59b0b9176eda97d54 return; } - if (!TickThread.isTickThread()) { -+ if (org.bxteam.divinemc.config.DivineConfig.AsyncCategory.enableParallelWorldTicking && !TickThread.isTickThreadFor(world)) { // DivineMC - Parallel world ticking ++ if (org.bxteam.divinemc.config.DivineConfig.AsyncCategory.enableParallelWorldTicking && !TickThread.isTickThreadFor(world) || !TickThread.isTickThread()) { // DivineMC - Parallel world ticking // These will be handled on the next ServerChunkCache$MainThreadExecutor#pollTask, as it runs the distance manager update // which will invoke processTicketUpdates this.getData().offThreadPendingFullLoadUpdate.addAll(changedFullStatus); // DivineMC - Chunk System optimization @@ -41,6 +42,28 @@ index dce2b0ae83e70ccaf2ac97441f80b25876ee9058..d10c42cb10f419b59b0b9176eda97d54 if (!PlatformHooks.get().allowAsyncTicketUpdates() && isTickThread) { TickThread.ensureTickThread("Cannot asynchronously process ticket updates"); +diff --git a/io/papermc/paper/redstone/RedstoneWireTurbo.java b/io/papermc/paper/redstone/RedstoneWireTurbo.java +index ff747a1ecdf3c888bca0d69de4f85dcd810b6139..544c05c94b535174d97675ea3c21706dfe305438 100644 +--- a/io/papermc/paper/redstone/RedstoneWireTurbo.java ++++ b/io/papermc/paper/redstone/RedstoneWireTurbo.java +@@ -829,14 +829,9 @@ public final class RedstoneWireTurbo { + j = getMaxCurrentStrength(upd, j); + int l = 0; + +- wire.shouldSignal = false; +- // Unfortunately, World.isBlockIndirectlyGettingPowered is complicated, +- // and I'm not ready to try to replicate even more functionality from +- // elsewhere in Minecraft into this accelerator. So sadly, we must +- // suffer the performance hit of this very expensive call. If there +- // is consistency to what this call returns, we may be able to cache it. +- final int k = worldIn.getBestNeighborSignal(upd.self); +- wire.shouldSignal = true; ++ // DivineMC start - Parallel world ticking ++ final int k = wire.getBlockSignal(worldIn, upd.self); ++ // DivineMC end - Parallel world ticking + + // The variable 'k' holds the maximum redstone power value of any adjacent blocks. + // If 'k' has the highest level of all neighbors, then the power level of this diff --git a/net/minecraft/core/dispenser/DispenseItemBehavior.java b/net/minecraft/core/dispenser/DispenseItemBehavior.java index ac27ff24f018d8798921c5152e679ceed1e88d8d..ec7d1353b19e55b00c558df8981323efb9b88bdf 100644 --- a/net/minecraft/core/dispenser/DispenseItemBehavior.java @@ -209,6 +232,106 @@ index c067f46935753794b49f29358262273fcd15d707..cbeb56539f00a3139f7c19d29cce92fa this.levels = Collections.unmodifiableMap(newLevels); } // CraftBukkit end +diff --git a/net/minecraft/server/PlayerAdvancements.java b/net/minecraft/server/PlayerAdvancements.java +index 10e5469df1800bcdfb3f8cb4045ee25a4bafc58c..8efed0ffdc906b6c1ba054831e481f53c11f102d 100644 +--- a/net/minecraft/server/PlayerAdvancements.java ++++ b/net/minecraft/server/PlayerAdvancements.java +@@ -54,6 +54,7 @@ public class PlayerAdvancements { + private final Map progress = new LinkedHashMap<>(); + private final Set visible = new HashSet<>(); + private final Set progressChanged = new HashSet<>(); ++ private final Set progressChangedConcurrent = java.util.concurrent.ConcurrentHashMap.newKeySet(); // DivineMC - Parallel world ticking + private final Set rootsToUpdate = new HashSet<>(); + private ServerPlayer player; + @Nullable +@@ -88,6 +89,7 @@ public class PlayerAdvancements { + this.visible.clear(); + this.rootsToUpdate.clear(); + this.progressChanged.clear(); ++ this.progressChangedConcurrent.clear(); // DivineMC - Parallel world ticking + this.isFirstPacket = true; + this.lastSelectedTab = null; + this.tree = manager.tree(); +@@ -178,10 +180,12 @@ public class PlayerAdvancements { + return false; + } + // Paper end - Add PlayerAdvancementCriterionGrantEvent +- this.unregisterListeners(advancement); +- this.progressChanged.add(advancement); +- flag = true; +- if (!isDone && orStartProgress.isDone()) { ++ // DivineMC start - Parallel world ticking ++ this.unregisterListeners(advancement); // Must unregister criteria listeners ++ (org.bxteam.divinemc.config.DivineConfig.AsyncCategory.enableParallelWorldTicking ? this.progressChangedConcurrent : this.progressChanged).add(advancement); ++ flag = true; // Mark progress changed ++ if (!isDone && orStartProgress.isDone()) { // If the advancement was just completed ++ // DivineMC end - Parallel world ticking + // Paper start - Add Adventure message to PlayerAdvancementDoneEvent + final net.kyori.adventure.text.Component message = advancement.value().display().flatMap(info -> { + return java.util.Optional.ofNullable( +@@ -215,8 +219,10 @@ public class PlayerAdvancements { + AdvancementProgress orStartProgress = this.getOrStartProgress(advancement); + boolean isDone = orStartProgress.isDone(); + if (orStartProgress.revokeProgress(criterionKey)) { ++ // DivineMC start - Parallel world ticking + this.registerListeners(advancement); +- this.progressChanged.add(advancement); ++ (org.bxteam.divinemc.config.DivineConfig.AsyncCategory.enableParallelWorldTicking ? this.progressChangedConcurrent : this.progressChanged).add(advancement); ++ // DivineMC end - Parallel world ticking + flag = true; + } + +@@ -266,7 +272,11 @@ public class PlayerAdvancements { + } + + public void flushDirty(ServerPlayer player, boolean showAdvancements) { +- if (this.isFirstPacket || !this.rootsToUpdate.isEmpty() || !this.progressChanged.isEmpty()) { ++ // DivineMC start - Parallel world ticking ++ final boolean useConcurrent = org.bxteam.divinemc.config.DivineConfig.AsyncCategory.enableParallelWorldTicking; ++ final Set relevantProgressSet = useConcurrent ? this.progressChangedConcurrent : this.progressChanged; ++ if (this.isFirstPacket || !this.rootsToUpdate.isEmpty() || !relevantProgressSet.isEmpty()) { ++ // DivineMC end - Parallel world ticking + Map map = new HashMap<>(); + Set set = new java.util.TreeSet<>(java.util.Comparator.comparing(adv -> adv.id().toString())); // Paper - Changed from HashSet to TreeSet ordered alphabetically. + Set set1 = new HashSet<>(); +@@ -277,13 +287,23 @@ public class PlayerAdvancements { + + this.rootsToUpdate.clear(); + +- for (AdvancementHolder advancementHolder : this.progressChanged) { +- if (this.visible.contains(advancementHolder)) { +- map.put(advancementHolder.id(), this.progress.get(advancementHolder)); ++ // DivineMC start - Parallel world ticking ++ if (!relevantProgressSet.isEmpty()) { ++ Set toProcess = useConcurrent ? new HashSet<>(relevantProgressSet) : relevantProgressSet; ++ ++ for (AdvancementHolder advancementHolder : toProcess) { ++ if (this.visible.contains(advancementHolder)) { ++ map.put(advancementHolder.id(), this.progress.get(advancementHolder)); ++ } ++ } ++ ++ if (useConcurrent) { ++ this.progressChangedConcurrent.removeAll(toProcess); ++ } else { ++ this.progressChanged.clear(); + } + } +- +- this.progressChanged.clear(); ++ // DivineMC end - Parallel world ticking + if (!map.isEmpty() || !set.isEmpty() || !set1.isEmpty()) { + player.connection.send(new ClientboundUpdateAdvancementsPacket(this.isFirstPacket, set, set1, map, showAdvancements)); + } +@@ -328,7 +348,7 @@ public class PlayerAdvancements { + if (this.visible.add(advancementHolder)) { + advancementOutput.add(advancementHolder); + if (this.progress.containsKey(advancementHolder)) { +- this.progressChanged.add(advancementHolder); ++ (org.bxteam.divinemc.config.DivineConfig.AsyncCategory.enableParallelWorldTicking ? this.progressChangedConcurrent : this.progressChanged).add(advancementHolder); // DivineMC - Parallel world ticking + } + } + } else if (this.visible.remove(advancementHolder)) { diff --git a/net/minecraft/server/dedicated/DedicatedServer.java b/net/minecraft/server/dedicated/DedicatedServer.java index 978934f81ba023d7565d2e66c51f6ca249510702..13d85eb366a070bfd6723088412f51af07892362 100644 --- a/net/minecraft/server/dedicated/DedicatedServer.java @@ -227,8 +350,20 @@ index 978934f81ba023d7565d2e66c51f6ca249510702..13d85eb366a070bfd6723088412f51af this.setPvpAllowed(properties.pvp); this.setFlightAllowed(properties.allowFlight); this.setMotd(properties.motd); +diff --git a/net/minecraft/server/level/ServerChunkCache.java b/net/minecraft/server/level/ServerChunkCache.java +index 7205fc8d3b17863c262d4c4c3cb956c852468c6f..3cf54630d36f821a232fa03f9094c4c1f70902a1 100644 +--- a/net/minecraft/server/level/ServerChunkCache.java ++++ b/net/minecraft/server/level/ServerChunkCache.java +@@ -175,7 +175,6 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon + + // call mid-tick tasks for chunk system + if ((i & 7) == 0) { +- ((ca.spottedleaf.moonrise.patches.chunk_system.server.ChunkSystemMinecraftServer)this.level.getServer()).moonrise$executeMidTickTasks(); + continue; + } + } diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java -index e05a0eed63098c535360b66276f154e4dc119d6a..0102d87411dcf0bba7f6873dc54385c957a7e2d7 100644 +index 00d9e6421185b1ba4f0fdff347a53cae5b830a0a..c3ed824b2c31902b50de74df37d9d8fd504454fc 100644 --- a/net/minecraft/server/level/ServerLevel.java +++ b/net/minecraft/server/level/ServerLevel.java @@ -181,7 +181,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe @@ -248,6 +383,15 @@ index e05a0eed63098c535360b66276f154e4dc119d6a..0102d87411dcf0bba7f6873dc54385c9 // CraftBukkit start public final LevelStorageSource.LevelStorageAccess levelStorageAccess; +@@ -681,7 +682,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + this.sleepStatus = new SleepStatus(); + this.gameEventDispatcher = new GameEventDispatcher(this); + this.randomSequences = Objects.requireNonNullElseGet(randomSequences, () -> this.getDataStorage().computeIfAbsent(RandomSequences.TYPE)); +- this.waypointManager = new ServerWaypointManager(); ++ this.waypointManager = new ServerWaypointManager(this); // DivineMC - Parallel world ticking + // Paper start - rewrite chunk system + this.moonrise$setEntityLookup(new ca.spottedleaf.moonrise.patches.chunk_system.level.entity.server.ServerEntityLookup((ServerLevel)(Object)this, ((ServerLevel)(Object)this).new EntityCallbacks())); + this.chunkTaskScheduler = new ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkTaskScheduler((ServerLevel)(Object)this); @@ -698,6 +699,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe this.chunkDataController = new ca.spottedleaf.moonrise.patches.chunk_system.io.datacontroller.ChunkDataController((ServerLevel)(Object)this, this.chunkTaskScheduler); // Paper end - rewrite chunk system @@ -316,7 +460,7 @@ index e05a0eed63098c535360b66276f154e4dc119d6a..0102d87411dcf0bba7f6873dc54385c9 // Paper start - extra debug info if (entity.valid) { diff --git a/net/minecraft/server/level/ServerPlayer.java b/net/minecraft/server/level/ServerPlayer.java -index c7d081aff58170251d075a58d5e1345fc1bda9f8..7f5ec82ad273226b34d0212e17dfd1fe905d68a0 100644 +index 95aabca0b1f2f3762df52e9a09afe19ed1939209..87fe8f80e76a63f7ab4f55668524f97be01e6929 100644 --- a/net/minecraft/server/level/ServerPlayer.java +++ b/net/minecraft/server/level/ServerPlayer.java @@ -433,6 +433,7 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc @@ -403,6 +547,91 @@ index 015e0d1400f1dfbe086c817f121d01f0726e82f2..199e0abdd3a00786a259e28db9d826df // CraftBukkit end serverPlayer.connection = player.connection; serverPlayer.restoreFrom(player, keepInventory); +diff --git a/net/minecraft/server/waypoints/ServerWaypointManager.java b/net/minecraft/server/waypoints/ServerWaypointManager.java +index f9e7532f86122a379692561a639a209a126e8bba..839f6b7696ef85314da185bedba7cfc5870c314a 100644 +--- a/net/minecraft/server/waypoints/ServerWaypointManager.java ++++ b/net/minecraft/server/waypoints/ServerWaypointManager.java +@@ -19,9 +19,17 @@ public class ServerWaypointManager implements WaypointManager waypoints = new HashSet<>(); + private final Set players = new HashSet<>(); + private final Table connections = HashBasedTable.create(); ++ // DivineMC start - Parallel world ticking ++ private final net.minecraft.server.level.ServerLevel level; ++ ++ public ServerWaypointManager(net.minecraft.server.level.ServerLevel level) { ++ this.level = level; ++ } ++ // DivineMC end - Parallel world ticking + + @Override + public void trackWaypoint(WaypointTransmitter waypoint) { ++ if (org.bxteam.divinemc.config.DivineConfig.AsyncCategory.enableParallelWorldTicking) ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.level, "Cannot track waypoints off-main"); // DivineMC - Parallel world ticking + this.waypoints.add(waypoint); + + for (ServerPlayer serverPlayer : this.players) { +@@ -31,6 +39,7 @@ public class ServerWaypointManager implements WaypointManager map = Tables.transpose(this.connections).row(waypoint); + SetView set = Sets.difference(this.players, map.keySet()); +@@ -47,12 +56,14 @@ public class ServerWaypointManager implements WaypointManager connection.disconnect()); + Tables.transpose(this.connections).row(waypoint).clear(); + this.waypoints.remove(waypoint); + } + + public void addPlayer(ServerPlayer player) { ++ if (org.bxteam.divinemc.config.DivineConfig.AsyncCategory.enableParallelWorldTicking) ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.level, "Cannot add player to waypoints off-main"); // DivineMC - Parallel world ticking + this.players.add(player); + + for (WaypointTransmitter waypointTransmitter : this.waypoints) { +@@ -65,6 +76,7 @@ public class ServerWaypointManager implements WaypointManager map = this.connections.row(player); + SetView set = Sets.difference(this.waypoints, map.keySet()); + +@@ -78,6 +90,7 @@ public class ServerWaypointManager implements WaypointManager { + connection.disconnect(); + return true; +@@ -87,6 +100,7 @@ public class ServerWaypointManager implements WaypointManager { +@@ -122,6 +137,7 @@ public class ServerWaypointManager implements WaypointManager { +@@ -44,15 +44,31 @@ public class GoToPotentialJobSite extends Behavior { + Optional memory = entity.getBrain().getMemory(MemoryModuleType.POTENTIAL_JOB_SITE); + memory.ifPresent(globalPos -> { BlockPos blockPos = globalPos.pos(); - ServerLevel level1 = level.getServer().getLevel(globalPos.dimension()); - if (level1 != null) { +- ServerLevel level1 = level.getServer().getLevel(globalPos.dimension()); +- if (level1 != null) { - PoiManager poiManager = level1.getPoiManager(); - if (poiManager.exists(blockPos, holder -> true)) { - poiManager.release(blockPos); - } -+ // DivineMC start - Parallel world ticking -+ Runnable releasePoiTask = () -> { -+ PoiManager poiManager = level1.getPoiManager(); ++ // DivineMC start - Parallel world ticking ++ ServerLevel entityLevel = level; ++ ServerLevel poiLevel = entityLevel.getServer().getLevel(globalPos.dimension()); ++ ++ if (poiLevel != null) { ++ Runnable poiOperationsTask = () -> { ++ PoiManager poiManager = poiLevel.getPoiManager(); + if (poiManager.exists(blockPos, holder -> true)) { + poiManager.release(blockPos); + } -+ -+ DebugPackets.sendPoiTicketCountPacket(level, blockPos); + }; - DebugPackets.sendPoiTicketCountPacket(level, blockPos); ++ Runnable debugPacketTask = () -> { ++ DebugPackets.sendPoiTicketCountPacket(entityLevel, blockPos); ++ }; ++ + if (org.bxteam.divinemc.config.DivineConfig.AsyncCategory.enableParallelWorldTicking) { -+ level.moonrise$getChunkTaskScheduler().scheduleChunkTask(0, 0, releasePoiTask, ca.spottedleaf.concurrentutil.util.Priority.BLOCKING); ++ poiLevel.moonrise$getChunkTaskScheduler().scheduleChunkTask(blockPos.getX() >> 4, blockPos.getZ() >> 4, poiOperationsTask, ca.spottedleaf.concurrentutil.util.Priority.BLOCKING); ++ entityLevel.moonrise$getChunkTaskScheduler().scheduleChunkTask(entity.chunkPosition().x, entity.chunkPosition().z, debugPacketTask, ca.spottedleaf.concurrentutil.util.Priority.BLOCKING); + } else { -+ releasePoiTask.run(); ++ poiOperationsTask.run(); ++ debugPacketTask.run(); + } -+ // DivineMC end - Parallel world ticking } ++ // DivineMC end - Parallel world ticking }); entity.getBrain().eraseMemory(MemoryModuleType.POTENTIAL_JOB_SITE); + } diff --git a/net/minecraft/world/entity/npc/Villager.java b/net/minecraft/world/entity/npc/Villager.java index 3e23bc54e0315b6324843851e072005ca4da3f8e..50130de436bb12334f5b7f63bf6bf30578b569b2 100644 --- a/net/minecraft/world/entity/npc/Villager.java @@ -826,10 +1066,18 @@ index d306f5f524dc64618df94c9783c2168dc561a5e3..6a0c4dc2ff5e3d82e811db63dc9da7b9 return true; } else { diff --git a/net/minecraft/world/level/block/RedStoneWireBlock.java b/net/minecraft/world/level/block/RedStoneWireBlock.java -index 1943a6aad888647953e2d9dbbeedb0bd81c6f9df..9585d3164eaa7522be950fe10d8487dcc88e0f0b 100644 +index 1943a6aad888647953e2d9dbbeedb0bd81c6f9df..48c5c13f993f5c30912833a99c82071942c83401 100644 --- a/net/minecraft/world/level/block/RedStoneWireBlock.java +++ b/net/minecraft/world/level/block/RedStoneWireBlock.java -@@ -283,7 +283,13 @@ public class RedStoneWireBlock extends Block { +@@ -66,6 +66,7 @@ public class RedStoneWireBlock extends Block { + private final BlockState crossState; + private final RedstoneWireEvaluator evaluator = new DefaultRedstoneWireEvaluator(this); + public boolean shouldSignal = true; ++ private final ThreadLocal shouldSignalTL = ThreadLocal.withInitial(() -> true); // DivineMC - Parallel world ticking + + @Override + public MapCodec codec() { +@@ -283,7 +284,13 @@ public class RedStoneWireBlock extends Block { if (orientation != null) { source = pos.relative(orientation.getFront().getOpposite()); } @@ -844,7 +1092,7 @@ index 1943a6aad888647953e2d9dbbeedb0bd81c6f9df..9585d3164eaa7522be950fe10d8487dc return; } updatePowerStrength(worldIn, pos, state, orientation, blockAdded); -@@ -311,7 +317,13 @@ public class RedStoneWireBlock extends Block { +@@ -311,7 +318,13 @@ public class RedStoneWireBlock extends Block { // [Space Walker] suppress shape updates and emit those manually to // bypass the new neighbor update stack. if (level.setBlock(pos, state, Block.UPDATE_KNOWN_SHAPE | Block.UPDATE_CLIENTS)) { @@ -859,6 +1107,71 @@ index 1943a6aad888647953e2d9dbbeedb0bd81c6f9df..9585d3164eaa7522be950fe10d8487dc } } } +@@ -328,10 +341,19 @@ public class RedStoneWireBlock extends Block { + } + + public int getBlockSignal(Level level, BlockPos pos) { +- this.shouldSignal = false; +- int bestNeighborSignal = level.getBestNeighborSignal(pos); +- this.shouldSignal = true; +- return bestNeighborSignal; ++ // DivineMC start - Parallel world ticking ++ if (org.bxteam.divinemc.config.DivineConfig.AsyncCategory.enableParallelWorldTicking) { ++ this.shouldSignalTL.set(false); ++ int bestNeighborSignal = level.getBestNeighborSignal(pos); ++ this.shouldSignalTL.set(true); ++ return bestNeighborSignal; ++ } else { ++ this.shouldSignal = false; ++ int bestNeighborSignal = level.getBestNeighborSignal(pos); ++ this.shouldSignal = true; ++ return bestNeighborSignal; ++ } ++ // DivineMC end - Parallel world ticking + } + + private void checkCornerChangeAt(Level level, BlockPos pos) { +@@ -422,12 +444,23 @@ public class RedStoneWireBlock extends Block { + + @Override + protected int getDirectSignal(BlockState blockState, BlockGetter blockAccess, BlockPos pos, Direction side) { +- return !this.shouldSignal ? 0 : blockState.getSignal(blockAccess, pos, side); ++ // DivineMC start - Parallel world ticking ++ boolean signal = org.bxteam.divinemc.config.DivineConfig.AsyncCategory.enableParallelWorldTicking ? this.shouldSignalTL.get() : this.shouldSignal; ++ return !signal ? 0 : blockState.getSignal(blockAccess, pos, side); ++ // DivineMC end - Parallel world ticking + } + + @Override + protected int getSignal(BlockState blockState, BlockGetter blockAccess, BlockPos pos, Direction side) { +- if (this.shouldSignal && side != Direction.DOWN) { ++ // DivineMC start - Parallel world ticking ++ boolean signal; ++ if (org.bxteam.divinemc.config.DivineConfig.AsyncCategory.enableParallelWorldTicking) { ++ signal = this.shouldSignalTL.get(); ++ } else { ++ signal = this.shouldSignal; ++ } ++ // DivineMC end - Parallel world ticking ++ if (signal && side != Direction.DOWN) { // DivineMC - Parallel world ticking + int powerValue = blockState.getValue(POWER); + if (powerValue == 0) { + return 0; +@@ -459,7 +492,13 @@ public class RedStoneWireBlock extends Block { + + @Override + protected boolean isSignalSource(BlockState state) { +- return this.shouldSignal; ++ // DivineMC start - Parallel world ticking ++ if (org.bxteam.divinemc.config.DivineConfig.AsyncCategory.enableParallelWorldTicking) { ++ return this.shouldSignalTL.get(); ++ } else { ++ return this.shouldSignal; ++ } ++ // DivineMC end - Parallel world ticking + } + + public static int getColorForPower(int power) { diff --git a/net/minecraft/world/level/block/SaplingBlock.java b/net/minecraft/world/level/block/SaplingBlock.java index a22cb810622e0ae97bc2a0d6390d026d9482b783..5856178e41523700ca7ed9a46c1c802c33903b53 100644 --- a/net/minecraft/world/level/block/SaplingBlock.java @@ -1042,17 +1355,22 @@ index 010c7204bfa772ff9a2187a5db76d7c65ea34f66..6de61b3e75edee87562e3aab01c7020c LevelChunkSection section = this.getSection(this.getSectionIndex(y)); boolean hasOnlyAir = section.hasOnlyAir(); diff --git a/net/minecraft/world/level/entity/EntityTickList.java b/net/minecraft/world/level/entity/EntityTickList.java -index 9e75320e51886e0f93c23683d8614128f44a613e..df8ede5c6d1f1af275f813d4b939136134214dff 100644 +index 9e75320e51886e0f93c23683d8614128f44a613e..d8b3e0901922016a22ca2801c2c4e0afd28e1c0a 100644 --- a/net/minecraft/world/level/entity/EntityTickList.java +++ b/net/minecraft/world/level/entity/EntityTickList.java -@@ -10,17 +10,26 @@ import net.minecraft.world.entity.Entity; +@@ -10,17 +10,31 @@ import net.minecraft.world.entity.Entity; public class EntityTickList { public final java.util.concurrent.ConcurrentLinkedQueue entities = new java.util.concurrent.ConcurrentLinkedQueue<>(); // Paper - rewrite chunk system // DivineMC - Async mob spawning + // DivineMC start - Parallel world ticking ++ @Nullable + private final net.minecraft.server.level.ServerLevel serverLevel; + -+ public EntityTickList(net.minecraft.server.level.ServerLevel serverLevel) { ++ public EntityTickList() { ++ this(null); ++ } ++ ++ public EntityTickList(@Nullable net.minecraft.server.level.ServerLevel serverLevel) { + this.serverLevel = serverLevel; + } + // DivineMC end - Parallel world ticking @@ -1072,7 +1390,7 @@ index 9e75320e51886e0f93c23683d8614128f44a613e..df8ede5c6d1f1af275f813d4b9391361 this.ensureActiveIsNotIterated(); this.entities.remove(entity); // Paper - rewrite chunk system } -@@ -30,6 +39,7 @@ public class EntityTickList { +@@ -30,6 +44,7 @@ public class EntityTickList { } public void forEach(Consumer entity) { @@ -1080,3 +1398,23 @@ index 9e75320e51886e0f93c23683d8614128f44a613e..df8ede5c6d1f1af275f813d4b9391361 // Paper start - rewrite chunk system // To ensure nothing weird happens with dimension travelling, do not iterate over new entries... // (by dfl iterator() is configured to not iterate over new entries) +diff --git a/net/minecraft/world/level/saveddata/maps/MapIndex.java b/net/minecraft/world/level/saveddata/maps/MapIndex.java +index 06025d79cc2297119b22224d777aca79f9d3d9c1..53e6a478eafec512be42422898e7fb334015946b 100644 +--- a/net/minecraft/world/level/saveddata/maps/MapIndex.java ++++ b/net/minecraft/world/level/saveddata/maps/MapIndex.java +@@ -23,8 +23,12 @@ public class MapIndex extends SavedData { + } + + public MapId getNextMapId() { +- MapId mapId = new MapId(++this.lastMapId); +- this.setDirty(); +- return mapId; ++ // DivineMC start - Parallel world ticking ++ synchronized (TYPE) { ++ MapId mapId = new MapId(++this.lastMapId); ++ this.setDirty(); ++ return mapId; ++ } ++ // DivineMC end - Parallel world ticking + } + } diff --git a/divinemc-server/minecraft-patches/features/0053-Regionized-Chunk-Ticking.patch b/divinemc-server/minecraft-patches/features/0053-Regionized-Chunk-Ticking.patch index 924f6b9..deccf8d 100644 --- a/divinemc-server/minecraft-patches/features/0053-Regionized-Chunk-Ticking.patch +++ b/divinemc-server/minecraft-patches/features/0053-Regionized-Chunk-Ticking.patch @@ -21,7 +21,7 @@ index 343ec870d6ea3e2792f369c4867a3afb4bcfa385..db909744c922f2e5b486a8f15dac79d6 if (var2 instanceof ClosedChannelException) { LOGGER.info("Connection closed during protocol change"); diff --git a/net/minecraft/server/level/ServerChunkCache.java b/net/minecraft/server/level/ServerChunkCache.java -index 7205fc8d3b17863c262d4c4c3cb956c852468c6f..9f6f8aace06702152117efc8252aefb7197b73b9 100644 +index 3cf54630d36f821a232fa03f9094c4c1f70902a1..5f62eee8a60d7fcadb1f9efb7473bc5c20c47263 100644 --- a/net/minecraft/server/level/ServerChunkCache.java +++ b/net/minecraft/server/level/ServerChunkCache.java @@ -57,6 +57,7 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon @@ -45,7 +45,7 @@ index 7205fc8d3b17863c262d4c4c3cb956c852468c6f..9f6f8aace06702152117efc8252aefb7 @Nullable @VisibleForDebug private NaturalSpawner.SpawnState lastSpawnState; -@@ -153,32 +156,241 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon +@@ -153,31 +156,240 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon return load ? this.syncLoad(chunkX, chunkZ, toStatus) : null; } // Paper end - rewrite chunk system @@ -268,11 +268,7 @@ index 7205fc8d3b17863c262d4c4c3cb956c852468c6f..9f6f8aace06702152117efc8252aefb7 + latch.await(); + return true; + } - -- // call mid-tick tasks for chunk system -- if ((i & 7) == 0) { -- ((ca.spottedleaf.moonrise.patches.chunk_system.server.ChunkSystemMinecraftServer)this.level.getServer()).moonrise$executeMidTickTasks(); -- continue; ++ + @Override + public boolean isReleasable() { + return latch.getCount() == 0; @@ -285,10 +281,12 @@ index 7205fc8d3b17863c262d4c4c3cb956c852468c6f..9f6f8aace06702152117efc8252aefb7 + java.util.Objects.checkFromToIndex(0, size, raw.length); + for (int i = 0; i < size; ++i) { + world.tickChunk(raw[i], randomTickSpeed); -+ + +- // call mid-tick tasks for chunk system +- if ((i & 7) == 0) { +- continue; + // call mid-tick tasks for chunk system + if ((i & 7) == 0) { -+ ((ca.spottedleaf.moonrise.patches.chunk_system.server.ChunkSystemMinecraftServer) this.level.getServer()).moonrise$executeMidTickTasks(); + continue; + } } @@ -297,7 +295,7 @@ index 7205fc8d3b17863c262d4c4c3cb956c852468c6f..9f6f8aace06702152117efc8252aefb7 } // Paper end - chunk tick iteration optimisations -@@ -502,14 +714,21 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon +@@ -501,14 +713,21 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon long gameTime = this.level.getGameTime(); long l = gameTime - this.lastInhabitedUpdate; this.lastInhabitedUpdate = gameTime; @@ -323,7 +321,7 @@ index 7205fc8d3b17863c262d4c4c3cb956c852468c6f..9f6f8aace06702152117efc8252aefb7 // DivineMC start - Async mob spawning if (org.bxteam.divinemc.config.DivineConfig.AsyncCategory.enableAsyncSpawning) { for (ServerPlayer player : this.level.players) { -@@ -538,14 +757,18 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon +@@ -537,14 +756,18 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon } private void broadcastChangedChunks() { @@ -348,7 +346,7 @@ index 7205fc8d3b17863c262d4c4c3cb956c852468c6f..9f6f8aace06702152117efc8252aefb7 } private void tickChunks(long timeInhabited) { -@@ -595,25 +818,28 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon +@@ -594,25 +817,28 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon filteredSpawningCategories = List.of(); } @@ -394,7 +392,7 @@ index 7205fc8d3b17863c262d4c4c3cb956c852468c6f..9f6f8aace06702152117efc8252aefb7 this.level.tickCustomSpawners(this.spawnEnemies, this.spawnFriendlies); } diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java -index 690b6d9f89f70afd8f37d907863b87dd1f4591af..7ae5ba48632528dfe5bbdedf252b5995f5452f1c 100644 +index 6edf0d8a4b8b82f85eac4d89cafd24e6730a61c6..83b46b31d1c0b565deeefe3dafa3b0287f14fb00 100644 --- a/net/minecraft/server/level/ServerLevel.java +++ b/net/minecraft/server/level/ServerLevel.java @@ -192,7 +192,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe diff --git a/divinemc-server/minecraft-patches/sources/net/minecraft/server/PlayerAdvancements.java.patch b/divinemc-server/minecraft-patches/sources/net/minecraft/server/PlayerAdvancements.java.patch deleted file mode 100644 index a7a439e..0000000 --- a/divinemc-server/minecraft-patches/sources/net/minecraft/server/PlayerAdvancements.java.patch +++ /dev/null @@ -1,26 +0,0 @@ ---- a/net/minecraft/server/PlayerAdvancements.java -+++ b/net/minecraft/server/PlayerAdvancements.java -@@ -51,16 +_,18 @@ - private final PlayerList playerList; - private final Path playerSavePath; - private AdvancementTree tree; -- private final Map progress = new LinkedHashMap<>(); -- private final Set visible = new HashSet<>(); -- private final Set progressChanged = new HashSet<>(); -- private final Set rootsToUpdate = new HashSet<>(); -+ // DivineMC start - Use synchronized map -+ private final Map progress = java.util.Collections.synchronizedMap(new LinkedHashMap<>()); -+ private final Set visible = com.google.common.collect.Sets.newConcurrentHashSet(); -+ private final Set progressChanged = com.google.common.collect.Sets.newConcurrentHashSet(); -+ private final Set rootsToUpdate = com.google.common.collect.Sets.newConcurrentHashSet(); -+ // DivineMC end - Use synchronized map - private ServerPlayer player; - @Nullable - private AdvancementHolder lastSelectedTab; - private boolean isFirstPacket = true; - private final Codec codec; -- public final Map, Set>> criterionData = new java.util.IdentityHashMap<>(); // Paper - fix advancement data player leakage -+ public final Map, Set>> criterionData = java.util.Collections.synchronizedMap(new java.util.IdentityHashMap<>()); // Paper - fix advancement data player leakage // DivineMC - Use synchronized map - - public PlayerAdvancements(DataFixer dataFixer, PlayerList playerList, ServerAdvancementManager manager, Path playerSavePath, ServerPlayer player) { - this.playerList = playerList; diff --git a/divinemc-server/paper-patches/features/0009-Multithreaded-Tracker.patch b/divinemc-server/paper-patches/features/0009-Multithreaded-Tracker.patch index 1d139b2..79a7765 100644 --- a/divinemc-server/paper-patches/features/0009-Multithreaded-Tracker.patch +++ b/divinemc-server/paper-patches/features/0009-Multithreaded-Tracker.patch @@ -21,6 +21,19 @@ index d7398b1ecf2660c29fb7d106b48fe02d3736603e..ab499a7eaccdc1578ec64f90f54f79b0 throw new IllegalStateException(event.getEventName() + " may only be triggered synchronously."); } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +index f81a6a2a9e1adaa3418c301984afeb19441f2039..efa472345ec74e21ca1cf05645b3ab39eda3caa4 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +@@ -2875,7 +2875,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + Iterator iterator = collection.iterator(); + while (iterator.hasNext()) { + AttributeInstance genericInstance = iterator.next(); +- if (genericInstance.getAttribute() == Attributes.MAX_HEALTH) { ++ if (genericInstance != null && genericInstance.getAttribute() == Attributes.MAX_HEALTH) { + iterator.remove(); + break; + } diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java index 98b766d115856dbf3ea11a983c1304591032f1b0..9b9b8b3439cfc0c36009fdb9e5d46b64c3aca393 100644 --- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/entity/tracking/MultithreadedTracker.java b/divinemc-server/src/main/java/org/bxteam/divinemc/entity/tracking/MultithreadedTracker.java index 669c6c5..6428810 100644 --- a/divinemc-server/src/main/java/org/bxteam/divinemc/entity/tracking/MultithreadedTracker.java +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/entity/tracking/MultithreadedTracker.java @@ -3,7 +3,6 @@ package org.bxteam.divinemc.entity.tracking; import ca.spottedleaf.moonrise.common.list.ReferenceList; import ca.spottedleaf.moonrise.common.misc.NearbyPlayers; import ca.spottedleaf.moonrise.common.util.TickThread; -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 net.minecraft.server.level.ChunkMap; @@ -19,7 +18,6 @@ import org.jetbrains.annotations.NotNull; import java.util.ArrayList; import java.util.List; import java.util.concurrent.BlockingQueue; -import java.util.concurrent.Executor; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.RejectedExecutionHandler; import java.util.concurrent.ThreadFactory; @@ -31,7 +29,7 @@ public class MultithreadedTracker { private static final Logger LOGGER = LogManager.getLogger(THREAD_PREFIX); private static long lastWarnMillis = System.currentTimeMillis(); - private static final ThreadPoolExecutor trackerExecutor = new ThreadPoolExecutor( + private static final ThreadPoolExecutor TRACKER_EXECUTOR = new ThreadPoolExecutor( getCorePoolSize(), getMaxPoolSize(), getKeepAliveTime(), TimeUnit.SECONDS, @@ -40,11 +38,7 @@ public class MultithreadedTracker { getRejectedPolicy() ); - public static Executor getTrackerExecutor() { - return trackerExecutor; - } - - public static void tick(ChunkSystemServerLevel level) { + public static void tick(ServerLevel level) { try { if (!DivineConfig.AsyncCategory.multithreadedCompatModeEnabled) { tickAsync(level); @@ -56,15 +50,14 @@ 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(); final ReferenceList trackerEntities = entityLookup.trackerEntities; final Entity[] trackerEntitiesRaw = trackerEntities.getRawDataUnchecked(); - // Move tracking to off-main - trackerExecutor.execute(() -> { + TRACKER_EXECUTOR.execute(() -> { for (final Entity entity : trackerEntitiesRaw) { if (entity == null) continue; @@ -72,19 +65,23 @@ public class MultithreadedTracker { if (tracker == null) continue; - tracker.moonrise$tick(nearbyPlayers.getChunk(entity.chunkPosition())); - tracker.serverEntity.sendChanges(); + synchronized (tracker) { + var trackedChunk = nearbyPlayers.getChunk(entity.chunkPosition()); + tracker.moonrise$tick(trackedChunk); + tracker.serverEntity.sendChanges(); + } } }); } - 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) { @@ -94,12 +91,19 @@ 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) { + tickTask[index] = tracker.tickCompact(nearbyPlayers.getChunk(entity.chunkPosition())); + sendChangesTasks[index] = () -> tracker.serverEntity.sendChanges(); + } + index++; } - // batch submit tasks - trackerExecutor.execute(() -> { + TRACKER_EXECUTOR.execute(() -> { + for (final Runnable tick : tickTask) { + if (tick == null) continue; + + tick.run(); + } for (final Runnable sendChanges : sendChangesTasks) { if (sendChanges == null) continue; diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/util/map/AttributeInstanceArrayMap.java b/divinemc-server/src/main/java/org/bxteam/divinemc/util/map/AttributeInstanceArrayMap.java new file mode 100644 index 0000000..9597aaa --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/util/map/AttributeInstanceArrayMap.java @@ -0,0 +1,326 @@ +package org.bxteam.divinemc.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; + +/** + * @author hayanesuru + */ +public final class AttributeInstanceArrayMap implements Map, AttributeInstance>, Cloneable { + private static final int VANILLA_ATTRIBUTE_SIZE = 35; // 1.21.6 + + private int size = 0; + private transient AttributeInstance[] a = new AttributeInstance[VANILLA_ATTRIBUTE_SIZE]; + private transient KeySet keys; + private transient Values values; + private transient EntrySet entries; + + public AttributeInstanceArrayMap() { + if (BuiltInRegistries.ATTRIBUTE.size() != VANILLA_ATTRIBUTE_SIZE) { + throw new IllegalStateException("Unexpected registry minecraft:attribute size"); + } + } + + public AttributeInstanceArrayMap(final @NotNull Map, AttributeInstance> m) { + this(); + putAll(m); + } + + 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 int size() { + return size; + } + + @Override + public boolean isEmpty() { + return size == 0; + } + + @Override + public 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 boolean containsValue(Object value) { + return value instanceof AttributeInstance val && Objects.equals(getInstance(val.getAttribute().value().uid), val); + } + + @Override + public AttributeInstance get(Object key) { + return key instanceof Holder holder && holder.value() instanceof Attribute attribute ? a[attribute.uid] : null; + } + + @Nullable + public AttributeInstance getInstance(int key) { + return a[key]; + } + + @Override + public AttributeInstance put(@NotNull Holder key, AttributeInstance value) { + int uid = key.value().uid; + AttributeInstance prev = a[uid]; + setByIndex(uid, value); + return prev; + } + + @Override + public 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 void putAll(@NotNull Map, ? extends AttributeInstance> m) { + for (AttributeInstance e : m.values()) { + if (e != null) { + setByIndex(e.getAttribute().value().uid, e); + } + } + } + + @Override + public void clear() { + Arrays.fill(a, null); + size = 0; + } + + @Override + public @NotNull Set> keySet() { + if (keys == null) { + keys = new KeySet(); + } + return keys; + } + + @Override + public @NotNull Collection values() { + if (values == null) { + values = new Values(); + } + return values; + } + + @Override + public @NotNull Set, AttributeInstance>> entrySet() { + if (entries == null) { + entries = new EntrySet(); + } + return entries; + } + + @Override + public boolean equals(Object o) { + if (o == this) return true; + if (!(o instanceof Map s)) return false; + if (s.size() != size()) return false; + if (o instanceof AttributeInstanceArrayMap that) { + return Arrays.equals(a, that.a); + } + for (Entry e : s.entrySet()) { + if (!Objects.equals(get(e.getKey()), e.getValue())) { + return false; + } + } + return true; + } + + @Override + public int hashCode() { + return Arrays.hashCode(a); + } + + @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.get(currentIndex).orElseThrow(); + } + + @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.get(nextIndex).orElseThrow(); + 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; + } +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/util/map/AttributeInstanceSet.java b/divinemc-server/src/main/java/org/bxteam/divinemc/util/map/AttributeInstanceSet.java new file mode 100644 index 0000000..705f69f --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/util/map/AttributeInstanceSet.java @@ -0,0 +1,116 @@ +package org.bxteam.divinemc.util.map; + +import it.unimi.dsi.fastutil.ints.IntArraySet; +import it.unimi.dsi.fastutil.ints.IntSet; +import net.minecraft.world.entity.ai.attributes.AttributeInstance; +import org.jetbrains.annotations.NotNull; + +import java.lang.reflect.Array; +import java.util.*; + +/** + * @author hayanesuru + */ +public final class AttributeInstanceSet extends AbstractCollection implements Set { + public final IntSet inner; + public final AttributeInstanceArrayMap map; + + public AttributeInstanceSet(AttributeInstanceArrayMap map) { + this.map = map; + inner = new IntArraySet(); + } + + @Override + public boolean add(AttributeInstance instance) { + return inner.add(instance.getAttribute().value().uid); + } + + @Override + public boolean remove(Object o) { + return o instanceof AttributeInstance instance && inner.remove(instance.getAttribute().value().uid); + } + + @Override + public @NotNull Iterator iterator() { + return new CloneIterator(inner.toIntArray(), map); + } + + @Override + public int size() { + return inner.size(); + } + + @Override + public boolean isEmpty() { + return inner.isEmpty(); + } + + @Override + public void clear() { + inner.clear(); + } + + @Override + public boolean contains(Object o) { + if (o instanceof AttributeInstance instance) { + return inner.contains(instance.getAttribute().value().uid); + } + return false; + } + + @Override + public AttributeInstance @NotNull [] toArray() { + int[] innerClone = inner.toIntArray(); + AttributeInstance[] arr = new AttributeInstance[innerClone.length]; + for (int i = 0; i < arr.length; i++) { + arr[i] = map.getInstance(innerClone[i]); + } + return arr; + } + + @SuppressWarnings({"unchecked"}) + @Override + public T @NotNull [] toArray(T[] a) { + if (a == null || (a.getClass() == AttributeInstance[].class && a.length == 0)) { + return (T[]) toArray(); + } + if (a.length < size()) { + a = (T[]) Array.newInstance(a.getClass().getComponentType(), size()); + } + System.arraycopy((T[]) toArray(), 0, a, 0, size()); + if (a.length > size()) { + a[size()] = null; + } + return a; + } + + static class CloneIterator implements Iterator { + private final int[] array; + private int index = 0; + private final AttributeInstanceArrayMap map; + + CloneIterator(int[] array, AttributeInstanceArrayMap map) { + this.array = array; + this.map = map; + } + + @Override + public boolean hasNext() { + return index < array.length; + } + + @Override + public AttributeInstance next() { + if (!hasNext()) throw new NoSuchElementException(); + return map.getInstance(array[index++]); + } + } + + @Override + public boolean equals(Object o) { + if (o == this) return true; + if (!(o instanceof Set s)) return false; + if (s.size() != size()) return false; + return containsAll(s); + } +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/util/map/ConcurrentLongHashSet.java b/divinemc-server/src/main/java/org/bxteam/divinemc/util/map/ConcurrentLongHashSet.java index d629fbd..3106d24 100644 --- a/divinemc-server/src/main/java/org/bxteam/divinemc/util/map/ConcurrentLongHashSet.java +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/util/map/ConcurrentLongHashSet.java @@ -6,102 +6,136 @@ import it.unimi.dsi.fastutil.longs.LongOpenHashSet; import it.unimi.dsi.fastutil.longs.LongSet; import org.jetbrains.annotations.NotNull; +import java.util.Arrays; import java.util.Collection; -import java.util.Iterator; import java.util.Objects; -import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.locks.ReentrantLock; /** - * A thread-safe implementation of {@link LongOpenHashSet} using ConcurrentHashMap.KeySetView as backing storage. - * This implementation provides concurrent access and high performance for concurrent operations. + * Optimized thread-safe implementation of {@link LongSet} that uses striped locking + * and primitive long arrays to minimize boxing/unboxing overhead. * * @author HaHaWTH at Leaf */ @SuppressWarnings({"unused", "deprecation"}) public final class ConcurrentLongHashSet extends LongOpenHashSet implements LongSet { - private final ConcurrentHashMap.KeySetView backing; + // Number of lock stripes - higher number means more concurrency but more memory + private static final int DEFAULT_CONCURRENCY_LEVEL = 16; + + // Load factor - when to resize the hash table + private static final float DEFAULT_LOAD_FACTOR = 0.75f; + + // Initial capacity per stripe + private static final int DEFAULT_INITIAL_CAPACITY = 16; + + // Array of segments (stripes) + private final Segment[] segments; + + // Total size, cached for faster size() operation + private final AtomicInteger size; /** - * Creates a new empty concurrent long set. + * Creates a new empty concurrent long set with default parameters. */ public ConcurrentLongHashSet() { - this.backing = ConcurrentHashMap.newKeySet(); + this(DEFAULT_CONCURRENCY_LEVEL * DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR, DEFAULT_CONCURRENCY_LEVEL); + } + + /** + * Creates a new concurrent long set with the specified parameters. + * + * @param initialCapacity the initial capacity + * @param loadFactor the load factor + * @param concurrencyLevel the concurrency level + */ + public ConcurrentLongHashSet(int initialCapacity, float loadFactor, int concurrencyLevel) { + // Need to call super() even though we don't use its state + super(); + + // Validate parameters + if (initialCapacity < 0) { + throw new IllegalArgumentException("Initial capacity must be positive"); + } + if (loadFactor <= 0 || Float.isNaN(loadFactor)) { + throw new IllegalArgumentException("Load factor must be positive"); + } + if (concurrencyLevel <= 0) { + throw new IllegalArgumentException("Concurrency level must be positive"); + } + + // Calculate segment count (power of 2) + int segmentCount = 1; + while (segmentCount < concurrencyLevel) { + segmentCount <<= 1; + } + + // Calculate capacity per segment + int segmentCapacity = Math.max(initialCapacity / segmentCount, DEFAULT_INITIAL_CAPACITY); + + // Create segments + this.segments = new Segment[segmentCount]; + for (int i = 0; i < segmentCount; i++) { + this.segments[i] = new Segment(segmentCapacity, loadFactor); + } + + this.size = new AtomicInteger(0); } @Override public int size() { - return backing.size(); + return size.get(); } @Override public boolean isEmpty() { - return backing.isEmpty(); - } - - @Override - public @NotNull LongIterator iterator() { - return new WrappingLongIterator(backing.iterator()); - } - - @NotNull - @Override - public Object @NotNull [] toArray() { - return backing.toArray(); - } - - @NotNull - @Override - public T @NotNull [] toArray(@NotNull T @NotNull [] array) { - Objects.requireNonNull(array, "Array cannot be null"); - return backing.toArray(array); - } - - @Override - public boolean containsAll(@NotNull Collection collection) { - Objects.requireNonNull(collection, "Collection cannot be null"); - return backing.containsAll(collection); - } - - @Override - public boolean addAll(@NotNull Collection collection) { - Objects.requireNonNull(collection, "Collection cannot be null"); - return backing.addAll(collection); - } - - @Override - public boolean removeAll(@NotNull Collection collection) { - Objects.requireNonNull(collection, "Collection cannot be null"); - return backing.removeAll(collection); - } - - @Override - public boolean retainAll(@NotNull Collection collection) { - Objects.requireNonNull(collection, "Collection cannot be null"); - return backing.retainAll(collection); - } - - @Override - public void clear() { - backing.clear(); + return size.get() == 0; } @Override public boolean add(long key) { - return backing.add(key); + Segment segment = segmentFor(key); + int delta = segment.add(key) ? 1 : 0; + if (delta > 0) { + size.addAndGet(delta); + } + return delta > 0; } @Override public boolean contains(long key) { - return backing.contains(key); + return segmentFor(key).contains(key); + } + + @Override + public boolean remove(long key) { + Segment segment = segmentFor(key); + int delta = segment.remove(key) ? -1 : 0; + if (delta < 0) { + size.addAndGet(delta); + } + return delta < 0; + } + + @Override + public void clear() { + for (Segment segment : segments) { + segment.clear(); + } + size.set(0); + } + + @Override + public @NotNull LongIterator iterator() { + return new ConcurrentLongIterator(); } @Override public long[] toLongArray() { - int size = backing.size(); - long[] result = new long[size]; - int i = 0; - for (Long value : backing) { - result[i++] = value; + long[] result = new long[size()]; + int index = 0; + for (Segment segment : segments) { + index = segment.toLongArray(result, index); } return result; } @@ -120,6 +154,104 @@ public final class ConcurrentLongHashSet extends LongOpenHashSet implements Long return array; } + @NotNull + @Override + public Object @NotNull [] toArray() { + Long[] result = new Long[size()]; + int index = 0; + for (Segment segment : segments) { + index = segment.toObjectArray(result, index); + } + return result; + } + + @NotNull + @Override + public T @NotNull [] toArray(@NotNull T @NotNull [] array) { + Objects.requireNonNull(array, "Array cannot be null"); + Long[] result = new Long[size()]; + int index = 0; + for (Segment segment : segments) { + index = segment.toObjectArray(result, index); + } + + if (array.length < result.length) { + return (T[]) result; + } + + System.arraycopy(result, 0, array, 0, result.length); + if (array.length > result.length) { + array[result.length] = null; + } + return array; + } + + @Override + public boolean containsAll(@NotNull Collection collection) { + Objects.requireNonNull(collection, "Collection cannot be null"); + for (Object o : collection) { + if (o instanceof Long) { + if (!contains(((Long) o).longValue())) { + return false; + } + } else { + return false; + } + } + return true; + } + + @Override + public boolean addAll(@NotNull Collection collection) { + Objects.requireNonNull(collection, "Collection cannot be null"); + boolean modified = false; + for (Long value : collection) { + modified |= add(value); + } + return modified; + } + + @Override + public boolean removeAll(@NotNull Collection collection) { + Objects.requireNonNull(collection, "Collection cannot be null"); + boolean modified = false; + for (Object o : collection) { + if (o instanceof Long) { + modified |= remove(((Long) o).longValue()); + } + } + return modified; + } + + @Override + public boolean retainAll(@NotNull Collection collection) { + Objects.requireNonNull(collection, "Collection cannot be null"); + + // Convert collection to a set of longs for faster lookups + LongOpenHashSet toRetain = new LongOpenHashSet(); + for (Object o : collection) { + if (o instanceof Long) { + toRetain.add(((Long) o).longValue()); + } + } + + boolean modified = false; + for (Segment segment : segments) { + modified |= segment.retainAll(toRetain); + } + + if (modified) { + // Recalculate size + int newSize = 0; + for (Segment segment : segments) { + newSize += segment.size(); + } + size.set(newSize); + } + + return modified; + } + @Override public boolean addAll(LongCollection c) { Objects.requireNonNull(c, "Collection cannot be null"); @@ -157,12 +289,23 @@ public final class ConcurrentLongHashSet extends LongOpenHashSet implements Long @Override public boolean retainAll(LongCollection c) { Objects.requireNonNull(c, "Collection cannot be null"); - return backing.retainAll(c); - } - @Override - public boolean remove(long k) { - return backing.remove(k); + // For LongCollection we can directly use it + boolean modified = false; + for (Segment segment : segments) { + modified |= segment.retainAll(c); + } + + if (modified) { + // Recalculate size + int newSize = 0; + for (Segment segment : segments) { + newSize += segment.size(); + } + size.set(newSize); + } + + return modified; } @Override @@ -175,39 +318,382 @@ public final class ConcurrentLongHashSet extends LongOpenHashSet implements Long @Override public int hashCode() { - return backing.hashCode(); + int hash = 0; + for (Segment segment : segments) { + hash += segment.hashCode(); + } + return hash; } @Override public String toString() { - return backing.toString(); + StringBuilder sb = new StringBuilder(); + sb.append('['); + + LongIterator it = iterator(); + boolean hasNext = it.hasNext(); + while (hasNext) { + sb.append(it.nextLong()); + hasNext = it.hasNext(); + if (hasNext) { + sb.append(", "); + } + } + + sb.append(']'); + return sb.toString(); } - static class WrappingLongIterator implements LongIterator { - private final Iterator backing; + /** + * Find the segment for a given key. + */ + private Segment segmentFor(long key) { + // Use high bits of hash to determine segment + // This helps spread keys more evenly across segments + return segments[(int) ((spread(key) >>> segmentShift()) & segmentMask())]; + } - WrappingLongIterator(Iterator backing) { - this.backing = Objects.requireNonNull(backing); + /** + * Spread bits to reduce clustering for keys with similar hash codes. + */ + private static long spread(long key) { + long h = key; + h ^= h >>> 32; + h ^= h >>> 16; + h ^= h >>> 8; + return h; + } + + private int segmentShift() { + return Integer.numberOfLeadingZeros(segments.length); + } + + private int segmentMask() { + return segments.length - 1; + } + + /** + * A segment is a striped portion of the hash set with its own lock. + */ + private static class Segment { + private final ReentrantLock lock = new ReentrantLock(); + private long[] keys; + private boolean[] used; + private int size; + private int threshold; + private final float loadFactor; + + Segment(int initialCapacity, float loadFactor) { + int capacity = MathUtil.nextPowerOfTwo(initialCapacity); + this.keys = new long[capacity]; + this.used = new boolean[capacity]; + this.size = 0; + this.loadFactor = loadFactor; + this.threshold = (int) (capacity * loadFactor); + } + + int size() { + lock.lock(); + try { + return size; + } finally { + lock.unlock(); + } + } + + boolean contains(long key) { + lock.lock(); + try { + int index = indexOf(key); + return used[index] && keys[index] == key; + } finally { + lock.unlock(); + } + } + + boolean add(long key) { + lock.lock(); + try { + int index = indexOf(key); + + // Key already exists + if (used[index] && keys[index] == key) { + return false; + } + + // Insert key + keys[index] = key; + if (!used[index]) { + used[index] = true; + size++; + + // Check if rehash is needed + if (size > threshold) { + rehash(); + } + } + + return true; + } finally { + lock.unlock(); + } + } + + boolean remove(long key) { + lock.lock(); + try { + int index = indexOf(key); + + // Key not found + if (!used[index] || keys[index] != key) { + return false; + } + + // Mark slot as unused + used[index] = false; + size--; + + // If the next slot is also used, we need to handle the removal properly + // to maintain the open addressing property + // This rehashing serves as a "cleanup" after removal + if (size > 0) { + rehashFromIndex(index); + } + + return true; + } finally { + lock.unlock(); + } + } + + void clear() { + lock.lock(); + try { + Arrays.fill(used, false); + size = 0; + } finally { + lock.unlock(); + } + } + + int toLongArray(long[] array, int offset) { + lock.lock(); + try { + for (int i = 0; i < keys.length; i++) { + if (used[i]) { + array[offset++] = keys[i]; + } + } + return offset; + } finally { + lock.unlock(); + } + } + + int toObjectArray(Long[] array, int offset) { + lock.lock(); + try { + for (int i = 0; i < keys.length; i++) { + if (used[i]) { + array[offset++] = keys[i]; + } + } + return offset; + } finally { + lock.unlock(); + } + } + + boolean retainAll(LongCollection toRetain) { + lock.lock(); + try { + boolean modified = false; + for (int i = 0; i < keys.length; i++) { + if (used[i] && !toRetain.contains(keys[i])) { + used[i] = false; + size--; + modified = true; + } + } + + // Rehash to clean up if needed + if (modified && size > 0) { + rehash(); + } + + return modified; + } finally { + lock.unlock(); + } + } + + /** + * Find the index where a key should be stored. + * Uses linear probing for collision resolution. + */ + private int indexOf(long key) { + int mask = keys.length - 1; + int index = (int) (spread(key) & mask); + + while (used[index] && keys[index] != key) { + index = (index + 1) & mask; + } + + return index; + } + + /** + * Rehash the segment with a larger capacity. + */ + private void rehash() { + int oldCapacity = keys.length; + int newCapacity = oldCapacity * 2; + + long[] oldKeys = keys; + boolean[] oldUsed = used; + + keys = new long[newCapacity]; + used = new boolean[newCapacity]; + size = 0; + threshold = (int) (newCapacity * loadFactor); + + // Re-add all keys + for (int i = 0; i < oldCapacity; i++) { + if (oldUsed[i]) { + add(oldKeys[i]); + } + } + } + + /** + * Rehash from a specific index after removal to maintain proper open addressing. + */ + private void rehashFromIndex(int startIndex) { + int mask = keys.length - 1; + int currentIndex = startIndex; + int nextIndex = (currentIndex + 1) & mask; + + // For each cluster of used slots following the removal point + while (used[nextIndex]) { + long key = keys[nextIndex]; + int targetIndex = (int) (spread(key) & mask); + + // If the key's ideal position is between the removal point and the current position, + // move it to the removal point + if ((targetIndex <= currentIndex && currentIndex < nextIndex) || + (nextIndex < targetIndex && targetIndex <= currentIndex) || + (currentIndex < nextIndex && nextIndex < targetIndex)) { + + keys[currentIndex] = keys[nextIndex]; + used[currentIndex] = true; + used[nextIndex] = false; + currentIndex = nextIndex; + } + + nextIndex = (nextIndex + 1) & mask; + } + } + + @Override + public int hashCode() { + lock.lock(); + try { + int hash = 0; + for (int i = 0; i < keys.length; i++) { + if (used[i]) { + hash += Long.hashCode(keys[i]); + } + } + return hash; + } finally { + lock.unlock(); + } + } + } + + /** + * Concurrent iterator for the set. + */ + private class ConcurrentLongIterator implements LongIterator { + private int segmentIndex; + private int keyIndex; + private long lastReturned; + private boolean lastReturnedValid; + + ConcurrentLongIterator() { + segmentIndex = 0; + keyIndex = 0; + lastReturnedValid = false; + advance(); } @Override public boolean hasNext() { - return backing.hasNext(); + return segmentIndex < segments.length; } @Override public long nextLong() { - return backing.next(); + if (!hasNext()) { + throw new java.util.NoSuchElementException(); + } + + lastReturned = segments[segmentIndex].keys[keyIndex]; + lastReturnedValid = true; + advance(); + return lastReturned; } @Override public Long next() { - return backing.next(); + return nextLong(); } @Override public void remove() { - backing.remove(); + if (!lastReturnedValid) { + throw new IllegalStateException(); + } + + ConcurrentLongHashSet.this.remove(lastReturned); + lastReturnedValid = false; + } + + private void advance() { + while (segmentIndex < segments.length) { + Segment segment = segments[segmentIndex]; + + // Lock the segment to get a consistent view + segment.lock.lock(); + try { + while (keyIndex < segment.keys.length) { + if (segment.used[keyIndex]) { + // Found next element + return; + } + keyIndex++; + } + } finally { + segment.lock.unlock(); + } + + // Move to next segment + segmentIndex++; + keyIndex = 0; + } + } + } + + /** + * Utility class for math operations. + */ + private static class MathUtil { + /** + * Returns the next power of two greater than or equal to the given value. + */ + static int nextPowerOfTwo(int value) { + int highestBit = Integer.highestOneBit(value); + return value > highestBit ? highestBit << 1 : value; } } }