diff --git a/divinemc-server/minecraft-patches/features/0012-Multithreaded-Tracker.patch b/divinemc-server/minecraft-patches/features/0012-Multithreaded-Tracker.patch index e093a59..35eb17f 100644 --- a/divinemc-server/minecraft-patches/features/0012-Multithreaded-Tracker.patch +++ b/divinemc-server/minecraft-patches/features/0012-Multithreaded-Tracker.patch @@ -5,7 +5,7 @@ Subject: [PATCH] Multithreaded Tracker diff --git a/net/minecraft/server/level/ChunkMap.java b/net/minecraft/server/level/ChunkMap.java -index 718487155b923bfffa215b70a12d42681a8ae141..19e98a8d905541f72becd0f61a86cc5c08e09daa 100644 +index 94da5ce57b27047443859503f6dcc01d0493021a..15a5e2d95260bfb33f286853c04e94bad96a2d5f 100644 --- a/net/minecraft/server/level/ChunkMap.java +++ b/net/minecraft/server/level/ChunkMap.java @@ -250,9 +250,19 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider @@ -283,10 +283,10 @@ index 8127a71fd7d45541525be75e6699c2a5bae95f5f..e6a55f6fa9f4b827d14ae29c82cb7e30 } diff --git a/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index b2f2682c44eeed9ed4f5421113cc7cc704454ba5..33730967df54467b2f046f66a1590c7069e44c5d 100644 +index c20d9cf66641a70a724c93599d668eb7b16bbb42..f4f47bb6ad9a5ae981b0de3fbc1405ea1e8a3947 100644 --- a/net/minecraft/server/network/ServerGamePacketListenerImpl.java +++ b/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -1806,7 +1806,7 @@ public class ServerGamePacketListenerImpl +@@ -1813,7 +1813,7 @@ public class ServerGamePacketListenerImpl } public void internalTeleport(PositionMoveRotation posMoveRotation, Set relatives) { diff --git a/divinemc-server/minecraft-patches/features/0013-Implement-Linear-region-format.patch b/divinemc-server/minecraft-patches/features/0013-Implement-Linear-region-format.patch index 6863ab1..f5cb2ca 100644 --- a/divinemc-server/minecraft-patches/features/0013-Implement-Linear-region-format.patch +++ b/divinemc-server/minecraft-patches/features/0013-Implement-Linear-region-format.patch @@ -56,10 +56,10 @@ index 51c126735ace8fdde89ad97b5cab62f244212db0..cf497c3919a1d5114a18474f04ccce18 + public void moonrise$write(final space.bxteam.divinemc.region.AbstractRegionFile regionFile) throws IOException; // DivineMC - linear region format } diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java -index d04c06fafd133f773f311e7c2708fa8b049da67c..b3f2e7bb4519cc078d3cede11bce232f1255c6a0 100644 +index 989be9b9ea224efecb1bb5998fdf6ceeed2850ae..836224638eca453cd7c83f393c08f3fc86c3da2b 100644 --- a/net/minecraft/server/MinecraftServer.java +++ b/net/minecraft/server/MinecraftServer.java -@@ -950,10 +950,10 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop +Date: Sun, 26 Jan 2025 16:18:37 +0300 +Subject: [PATCH] MSPT Tracking for each world + + +diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java +index e7491104c5510dcd2d9732ac3809d80b53c3a9e8..0c4deecde0fb1f35dc39cf66449eda9f60f9421a 100644 +--- a/net/minecraft/server/MinecraftServer.java ++++ b/net/minecraft/server/MinecraftServer.java +@@ -1771,7 +1771,15 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop entitiesWithScheduledTasks = java.util.concurrent.ConcurrentHashMap.newKeySet(); // DivineMC - Skip EntityScheduler's executeTick checks if there isn't any tasks to be run + + public static S spin(Function threadFunction) { + ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry.init(); // Paper - rewrite data converter system +@@ -1705,17 +_,18 @@ + this.server.getScheduler().mainThreadHeartbeat(); // CraftBukkit + // Paper start - Folia scheduler API + ((io.papermc.paper.threadedregions.scheduler.FoliaGlobalRegionScheduler) org.bukkit.Bukkit.getGlobalRegionScheduler()).tick(); +- getAllLevels().forEach(level -> { +- for (final net.minecraft.world.entity.Entity entity : level.getEntities().getAll()) { +- if (entity.isRemoved()) { +- continue; +- } +- final org.bukkit.craftbukkit.entity.CraftEntity bukkit = entity.getBukkitEntityRaw(); +- if (bukkit != null) { +- bukkit.taskScheduler.executeTick(); +- } +- } +- }); ++ // DivineMC start - Skip EntityScheduler's executeTick checks if there isn't any tasks to be run ++ for (final net.minecraft.world.entity.Entity entity : entitiesWithScheduledTasks) { ++ if (entity.isRemoved()) { ++ continue; ++ } ++ ++ final org.bukkit.craftbukkit.entity.CraftEntity bukkit = entity.getBukkitEntityRaw(); ++ if (bukkit != null) { ++ bukkit.taskScheduler.executeTick(); ++ } ++ } ++ // DivineMC end - Skip EntityScheduler's executeTick checks if there isn't any tasks to be run + // Paper end - Folia scheduler API + io.papermc.paper.adventure.providers.ClickCallbackProviderImpl.CALLBACK_MANAGER.handleQueue(this.tickCount); // Paper + profilerFiller.push("commandFunctions"); diff --git a/divinemc-server/minecraft-patches/sources/net/minecraft/server/level/ChunkMap.java.patch b/divinemc-server/minecraft-patches/sources/net/minecraft/server/level/ChunkMap.java.patch index 9c3b8e0..84d9934 100644 --- a/divinemc-server/minecraft-patches/sources/net/minecraft/server/level/ChunkMap.java.patch +++ b/divinemc-server/minecraft-patches/sources/net/minecraft/server/level/ChunkMap.java.patch @@ -9,3 +9,12 @@ private final Long2ByteMap chunkTypeCache = new Long2ByteOpenHashMap(); // Paper - rewrite chunk system public int serverViewDistance; +@@ -1232,7 +_,7 @@ + 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 +- if (flag && !player.getBukkitEntity().canSee(this.entity.getBukkitEntity())) { // Paper - only consider hits ++ if (flag && !player.getBukkitEntity().canSeeChunkMapUpdatePlayer(this.entity.getBukkitEntity())) { // Paper - only consider hits // DivineMC - optimize canSee checks + flag = false; + } + // CraftBukkit end diff --git a/divinemc-server/minecraft-patches/sources/net/minecraft/server/network/ServerGamePacketListenerImpl.java.patch b/divinemc-server/minecraft-patches/sources/net/minecraft/server/network/ServerGamePacketListenerImpl.java.patch index a3499c5..13459cb 100644 --- a/divinemc-server/minecraft-patches/sources/net/minecraft/server/network/ServerGamePacketListenerImpl.java.patch +++ b/divinemc-server/minecraft-patches/sources/net/minecraft/server/network/ServerGamePacketListenerImpl.java.patch @@ -18,7 +18,7 @@ flag2 = true; // Paper - diff on change, this should be moved wrongly LOGGER.warn("{} (vehicle of {}) moved wrongly! {}", rootVehicle.getName().getString(), this.player.getName().getString(), Math.sqrt(d7)); } -@@ -2397,6 +_,7 @@ +@@ -2404,6 +_,7 @@ } private void tryHandleChat(String message, Runnable handler, boolean sync) { // CraftBukkit @@ -26,7 +26,7 @@ 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 -@@ -2424,6 +_,15 @@ +@@ -2431,6 +_,15 @@ return optional; } } diff --git a/divinemc-server/minecraft-patches/sources/net/minecraft/world/level/Level.java.patch b/divinemc-server/minecraft-patches/sources/net/minecraft/world/level/Level.java.patch index 3d38bc6..b327f87 100644 --- a/divinemc-server/minecraft-patches/sources/net/minecraft/world/level/Level.java.patch +++ b/divinemc-server/minecraft-patches/sources/net/minecraft/world/level/Level.java.patch @@ -1,17 +1,14 @@ --- a/net/minecraft/world/level/Level.java +++ b/net/minecraft/world/level/Level.java -@@ -115,9 +_,9 @@ +@@ -115,7 +_,7 @@ public static final int TICKS_PER_DAY = 24000; public static final int MAX_ENTITY_SPAWN_Y = 20000000; public static final int MIN_ENTITY_SPAWN_Y = -20000000; - public final List blockEntityTickers = Lists.newArrayList(); // Paper - public -+ public final List blockEntityTickers = new space.bxteam.divinemc.util.lithium.HashedReferenceList<>(Lists.newArrayList()); // Paper - public // DivineMC - lithium: hashed_list ++ public final space.bxteam.divinemc.util.BlockEntityTickersList blockEntityTickers = new space.bxteam.divinemc.util.BlockEntityTickersList(); // DivineMC - optimize block entity removal protected final NeighborUpdater neighborUpdater; -- private final List pendingBlockEntityTickers = Lists.newArrayList(); -+ private final List pendingBlockEntityTickers = new space.bxteam.divinemc.util.lithium.HashedReferenceList<>(Lists.newArrayList()); // DivineMC - lithium: hashed_list + private final List pendingBlockEntityTickers = Lists.newArrayList(); private boolean tickingBlockEntities; - public final Thread thread; - private final boolean isDebug; @@ -172,8 +_,6 @@ public final io.papermc.paper.antixray.ChunkPacketBlockController chunkPacketBlockController; // Paper - Anti-Xray public final org.purpurmc.purpur.PurpurWorldConfig purpurConfig; // Purpur - Purpur config files @@ -30,3 +27,21 @@ this.chunkPacketBlockController = this.paperConfig().anticheat.antiXray.enabled ? new io.papermc.paper.antixray.ChunkPacketBlockControllerAntiXray(this, executor) : io.papermc.paper.antixray.ChunkPacketBlockController.NO_OPERATION_INSTANCE; // Paper - Anti-Xray this.entityLookup = new ca.spottedleaf.moonrise.patches.chunk_system.level.entity.dfl.DefaultEntityLookup(this); // Paper - rewrite chunk system } +@@ -1523,7 +_,8 @@ + TickingBlockEntity tickingBlockEntity = this.blockEntityTickers.get(this.tileTickPosition); + // Spigot end + if (tickingBlockEntity.isRemoved()) { +- toRemove.add(tickingBlockEntity); // Paper - Fix MC-117075; use removeAll ++ //toRemove.add(tickingBlockEntity); // Paper - Fix MC-117075; use removeAll ++ this.blockEntityTickers.markAsRemoved(this.tileTickPosition); // Paper - Fix MC-117075 // DivineMC - optimize block entity removal + } else if (runsNormally && this.shouldTickBlocksAt(tickingBlockEntity.getPos())) { + tickingBlockEntity.tick(); + // Paper start - rewrite chunk system +@@ -1535,6 +_,7 @@ + } + this.blockEntityTickers.removeAll(toRemove); // Paper - Fix MC-117075 + ++ this.blockEntityTickers.removeMarkedEntries(); // DivineMC - optimize block entity removal + this.tickingBlockEntities = false; + profilerFiller.pop(); + this.spigotConfig.currentPrimedTnt = 0; // Spigot diff --git a/divinemc-server/paper-patches/features/0006-Skip-EntityScheduler-s-executeTick-checks-if-there-i.patch b/divinemc-server/paper-patches/features/0006-Skip-EntityScheduler-s-executeTick-checks-if-there-i.patch new file mode 100644 index 0000000..4e02765 --- /dev/null +++ b/divinemc-server/paper-patches/features/0006-Skip-EntityScheduler-s-executeTick-checks-if-there-i.patch @@ -0,0 +1,90 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: NONPLAYT <76615486+NONPLAYT@users.noreply.github.com> +Date: Sun, 26 Jan 2025 16:13:42 +0300 +Subject: [PATCH] Skip EntityScheduler's executeTick checks if there isn't any + tasks to be run + +Original project: https://github.com/SparklyPower/SparklyPaper + +diff --git a/src/main/java/io/papermc/paper/threadedregions/EntityScheduler.java b/src/main/java/io/papermc/paper/threadedregions/EntityScheduler.java +index c03608fec96b51e1867f43d8f42e5aefb1520e46..eda35b81c36ca8ebe4f9487cb41e2b0c4cbfc686 100644 +--- a/src/main/java/io/papermc/paper/threadedregions/EntityScheduler.java ++++ b/src/main/java/io/papermc/paper/threadedregions/EntityScheduler.java +@@ -36,6 +36,7 @@ public final class EntityScheduler { + * The Entity. Note that it is the CraftEntity, since only that class properly tracks world transfers. + */ + public final CraftEntity entity; ++ public final net.minecraft.server.MinecraftServer server; // DivineMC - Skip EntityScheduler's executeTick checks if there isn't any tasks to be run + + private static final record ScheduledTask(Consumer run, Consumer retired) {} + +@@ -46,7 +47,8 @@ public final class EntityScheduler { + + private final ArrayDeque currentlyExecuting = new ArrayDeque<>(); + +- public EntityScheduler(final CraftEntity entity) { ++ public EntityScheduler(final net.minecraft.server.MinecraftServer server, final CraftEntity entity) { // DivineMC - Skip EntityScheduler's executeTick checks if there isn't any tasks to be run ++ this.server = Validate.notNull(server); + this.entity = Validate.notNull(entity); + } + +@@ -61,15 +63,15 @@ public final class EntityScheduler { + * @throws IllegalStateException If the scheduler is already retired. + */ + public void retire() { ++ final Entity thisEntity = this.entity.getHandleRaw(); // DivineMC - Skip EntityScheduler's executeTick checks if there isn't any tasks to be run + synchronized (this.stateLock) { + if (this.tickCount == RETIRED_TICK_COUNT) { + throw new IllegalStateException("Already retired"); + } + this.tickCount = RETIRED_TICK_COUNT; ++ this.server.entitiesWithScheduledTasks.remove(thisEntity); // DivineMC - Skip EntityScheduler's executeTick checks if there isn't any tasks to be run + } + +- final Entity thisEntity = this.entity.getHandleRaw(); +- + // correctly handle and order retiring while running executeTick + for (int i = 0, len = this.currentlyExecuting.size(); i < len; ++i) { + final ScheduledTask task = this.currentlyExecuting.pollFirst(); +@@ -124,6 +126,7 @@ public final class EntityScheduler { + if (this.tickCount == RETIRED_TICK_COUNT) { + return false; + } ++ this.server.entitiesWithScheduledTasks.add(this.entity.getHandleRaw()); // DivineMC - Skip EntityScheduler's executeTick checks if there isn't any tasks to be run + this.oneTimeDelayed.computeIfAbsent(this.tickCount + Math.max(1L, delay), (final long keyInMap) -> { + return new ArrayList<>(); + }).add(task); +@@ -143,6 +146,12 @@ public final class EntityScheduler { + TickThread.ensureTickThread(thisEntity, "May not tick entity scheduler asynchronously"); + final List toRun; + synchronized (this.stateLock) { ++ // DivineMC start - Skip EntityScheduler's executeTick checks if there isn't any tasks to be run ++ if (this.currentlyExecuting.isEmpty() && this.oneTimeDelayed.isEmpty()) { ++ this.server.entitiesWithScheduledTasks.remove(thisEntity); ++ return; ++ } ++ // DivineMC end - Skip EntityScheduler's executeTick checks if there isn't any tasks to be run + if (this.tickCount == RETIRED_TICK_COUNT) { + throw new IllegalStateException("Ticking retired scheduler"); + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +index dca2761fe4765c6e95b5db0d0cb5c818eb8697b4..62efcf81ca48f2f1125f64b489ca9521c6e0f287 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +@@ -75,7 +75,7 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { + private final CraftPersistentDataContainer persistentDataContainer = new CraftPersistentDataContainer(CraftEntity.DATA_TYPE_REGISTRY); + protected net.kyori.adventure.pointer.Pointers adventure$pointers; // Paper - implement pointers + // Paper start - Folia shedulers +- public final io.papermc.paper.threadedregions.EntityScheduler taskScheduler = new io.papermc.paper.threadedregions.EntityScheduler(this); ++ public final io.papermc.paper.threadedregions.EntityScheduler taskScheduler; // DivineMC - Skip EntityScheduler's executeTick checks if there isn't any tasks to be run + private final io.papermc.paper.threadedregions.scheduler.FoliaEntityScheduler apiScheduler = new io.papermc.paper.threadedregions.scheduler.FoliaEntityScheduler(this); + + @Override +@@ -88,6 +88,7 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { + this.server = server; + this.entity = entity; + this.entityType = CraftEntityType.minecraftToBukkit(entity.getType()); ++ this.taskScheduler = new io.papermc.paper.threadedregions.EntityScheduler(this.entity.getServer(), this); // DivineMC - Skip EntityScheduler's executeTick checks if there isn't any tasks to be run + } + + // Purpur start - Fire Immunity API diff --git a/divinemc-server/paper-patches/files/src/main/java/io/papermc/paper/command/MSPTCommand.java.patch b/divinemc-server/paper-patches/files/src/main/java/io/papermc/paper/command/MSPTCommand.java.patch new file mode 100644 index 0000000..9b55c68 --- /dev/null +++ b/divinemc-server/paper-patches/files/src/main/java/io/papermc/paper/command/MSPTCommand.java.patch @@ -0,0 +1,50 @@ +--- a/src/main/java/io/papermc/paper/command/MSPTCommand.java ++++ b/src/main/java/io/papermc/paper/command/MSPTCommand.java +@@ -78,6 +_,47 @@ + ) + ) + ); ++ ++ // DivineMC start - MSPT Tracking for each world ++ sender.sendMessage(text()); ++ sender.sendMessage(text().content("World tick times ").color(GOLD) ++ .append(text().color(YELLOW) ++ .append( ++ text("("), ++ text("avg", GRAY), ++ text("/"), ++ text("min", GRAY), ++ text("/"), ++ text("max", GRAY), ++ text(")") ++ ) ++ ).append( ++ text(" from last 5s"), ++ text(",", GRAY), ++ text(" 10s"), ++ text(",", GRAY), ++ text(" 1m"), ++ text(":", YELLOW) ++ ) ++ ); ++ for (net.minecraft.server.level.ServerLevel serverLevel : server.getAllLevels()) { ++ List worldTimes = new ArrayList<>(); ++ worldTimes.addAll(eval(serverLevel.tickTimes5s.getTimes())); ++ worldTimes.addAll(eval(serverLevel.tickTimes10s.getTimes())); ++ worldTimes.addAll(eval(serverLevel.tickTimes60s.getTimes())); ++ ++ sender.sendMessage(text().content("◴ " + serverLevel.getWorld().getName() + ": ").color(GOLD) ++ .append(text().color(GRAY) ++ .append( ++ worldTimes.get(0), SLASH, worldTimes.get(1), SLASH, worldTimes.get(2), text(", ", YELLOW), ++ worldTimes.get(3), SLASH, worldTimes.get(4), SLASH, worldTimes.get(5), text(", ", YELLOW), ++ worldTimes.get(6), SLASH, worldTimes.get(7), SLASH, worldTimes.get(8) ++ ) ++ ) ++ ); ++ } ++ // DivineMC end - MSPT Tracking for each world ++ + return true; + } + diff --git a/divinemc-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java.patch b/divinemc-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java.patch index b20188e..66b2f60 100644 --- a/divinemc-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java.patch +++ b/divinemc-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java.patch @@ -1,6 +1,32 @@ --- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -@@ -3643,4 +_,19 @@ +@@ -208,7 +_,7 @@ + private boolean hasPlayedBefore = false; + private final ConversationTracker conversationTracker = new ConversationTracker(); + private final Set channels = new HashSet(); +- private final Map>> invertedVisibilityEntities = new HashMap<>(); ++ private final Map>> invertedVisibilityEntities = new it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap<>(); // DivineMC - optimize canSee checks + private final Set unlistedEntities = new HashSet<>(); // Paper - Add Listing API for Player + private static final WeakHashMap> pluginWeakReferences = new WeakHashMap<>(); + private int hash = 0; +@@ -2264,8 +_,14 @@ + + @Override + public boolean canSee(org.bukkit.entity.Entity entity) { +- return this.equals(entity) || entity.isVisibleByDefault() ^ this.invertedVisibilityEntities.containsKey(entity.getUniqueId()); // SPIGOT-7312: Can always see self +- } ++ return this.equals(entity) || entity.isVisibleByDefault() ^ (!invertedVisibilityEntities.isEmpty() && this.invertedVisibilityEntities.containsKey(entity.getUniqueId())); // SPIGOT-7312: Can always see self // DivineMC - optimize canSee checks ++ } ++ ++ // DivineMC start - optimize canSee checks ++ public boolean canSeeChunkMapUpdatePlayer(org.bukkit.entity.Entity entity) { ++ return entity.isVisibleByDefault() ^ (!invertedVisibilityEntities.isEmpty() && this.invertedVisibilityEntities.containsKey(entity.getUniqueId())); ++ } ++ // DivineMC end - optimize canSee checks + + public boolean canSeePlayer(UUID uuid) { + org.bukkit.entity.Entity entity = this.getServer().getPlayer(uuid); +@@ -3644,4 +_,19 @@ this.getHandle().connection.send(new net.minecraft.network.protocol.game.ClientboundPlayerCombatKillPacket(getEntityId(), io.papermc.paper.adventure.PaperAdventure.asVanilla(message))); } // Purpur end - Death screen API diff --git a/divinemc-server/src/main/java/space/bxteam/divinemc/util/BlockEntityTickersList.java b/divinemc-server/src/main/java/space/bxteam/divinemc/util/BlockEntityTickersList.java new file mode 100644 index 0000000..f1c9cde --- /dev/null +++ b/divinemc-server/src/main/java/space/bxteam/divinemc/util/BlockEntityTickersList.java @@ -0,0 +1,90 @@ +package space.bxteam.divinemc.util; + +import it.unimi.dsi.fastutil.ints.IntOpenHashSet; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import net.minecraft.world.level.block.entity.TickingBlockEntity; + +import java.util.Arrays; +import java.util.Collection; + +/** + * A list for ServerLevel's blockEntityTickers + *

+ * This list is behaving identically to ObjectArrayList, but it has an additional method, `removeAllByIndex`, that allows a list of integers to be passed indicating what + * indexes should be deleted from the list + *

+ * This is faster than using removeAll, since we don't need to compare the identity of each block entity, and faster than looping through each index manually and deleting with remove, + * since we don't need to resize the array every single remove. + */ +public final class BlockEntityTickersList extends ObjectArrayList { + private final IntOpenHashSet toRemove = new IntOpenHashSet(); + private int startSearchFromIndex = -1; + + /** Creates a new array list with {@link #DEFAULT_INITIAL_CAPACITY} capacity. */ + public BlockEntityTickersList() { + super(); + } + + /** + * Creates a new array list and fills it with a given collection. + * + * @param c a collection that will be used to fill the array list. + */ + public BlockEntityTickersList(final Collection c) { + super(c); + } + + /** + * Marks an entry as removed + * + * @param index the index of the item on the list to be marked as removed + */ + public void markAsRemoved(final int index) { + // The block entities list always loop starting from 0, so we only need to check if the startSearchFromIndex is -1 and that's it + if (this.startSearchFromIndex == -1) + this.startSearchFromIndex = index; + this.toRemove.add(index); + } + + /** + * Removes elements that have been marked as removed. + */ + public void removeMarkedEntries() { + if (this.startSearchFromIndex == -1) // No entries in the list, skip + return; + + removeAllByIndex(startSearchFromIndex, toRemove); + toRemove.clear(); + this.startSearchFromIndex = -1; // Reset the start search index + } + + /** + * Removes elements by their index. + */ + private void removeAllByIndex(final int startSearchFromIndex, final IntOpenHashSet c) { // can't use Set because we want to avoid autoboxing when using contains + final int requiredMatches = c.size(); + if (requiredMatches == 0) + return; // exit early, we don't need to do anything + + final Object[] a = this.a; + int j = startSearchFromIndex; + int matches = 0; + for (int i = startSearchFromIndex; i < size; i++) { // If the user knows the first index to be removed, we can skip a lot of unnecessary comparsions + if (!c.contains(i)) { + a[j++] = a[i]; + } else { + matches++; + } + + if (matches == requiredMatches) { // Exit the loop if we already removed everything, we don't need to check anything else + if (i != (size - 1)) { // If it isn't the last index... + System.arraycopy(a, i + 1, a, j, size - i - 1); + } + j = size - requiredMatches; + break; + } + } + Arrays.fill(a, j, size, null); + size = j; + } +} diff --git a/divinemc-server/src/main/java/space/bxteam/divinemc/util/lithium/HashedReferenceList.java b/divinemc-server/src/main/java/space/bxteam/divinemc/util/lithium/HashedReferenceList.java deleted file mode 100644 index aafa3e5..0000000 --- a/divinemc-server/src/main/java/space/bxteam/divinemc/util/lithium/HashedReferenceList.java +++ /dev/null @@ -1,281 +0,0 @@ -package space.bxteam.divinemc.util.lithium; - -import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap; -import it.unimi.dsi.fastutil.objects.ReferenceArrayList; -import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet; -import org.jetbrains.annotations.NotNull; - -import java.util.Collection; -import java.util.Iterator; -import java.util.List; -import java.util.ListIterator; -import java.util.NoSuchElementException; - -/** - * Wraps a {@link List} with a hash table which provides O(1) lookups for {@link Collection#contains(Object)}. The type - * contained by this list must use reference-equality semantics. - */ -@SuppressWarnings("SuspiciousMethodCalls") -public class HashedReferenceList implements List { - private final ReferenceArrayList list; - private final Reference2IntOpenHashMap counter; - - public HashedReferenceList(List list) { - this.list = new ReferenceArrayList<>(); - this.list.addAll(list); - - this.counter = new Reference2IntOpenHashMap<>(); - this.counter.defaultReturnValue(0); - - for (T obj : this.list) { - this.counter.addTo(obj, 1); - } - } - - @Override - public int size() { - return this.list.size(); - } - - @Override - public boolean isEmpty() { - return this.list.isEmpty(); - } - - @Override - public boolean contains(Object o) { - return this.counter.containsKey(o); - } - - @Override - public Iterator iterator() { - return this.listIterator(); - } - - @Override - public Object[] toArray() { - return this.list.toArray(); - } - - @Override - public T1[] toArray(T1 @NotNull [] a) { - return this.list.toArray(a); - } - - @Override - public boolean add(T t) { - this.trackReferenceAdded(t); - - return this.list.add(t); - } - - @Override - public boolean remove(Object o) { - this.trackReferenceRemoved(o); - - return this.list.remove(o); - } - - @Override - public boolean containsAll(Collection c) { - for (Object obj : c) { - if (!this.counter.containsKey(obj)) { - return false; - } - } - - return true; - } - - @Override - public boolean addAll(Collection c) { - for (T obj : c) { - this.trackReferenceAdded(obj); - } - - return this.list.addAll(c); - } - - @Override - public boolean addAll(int index, Collection c) { - for (T obj : c) { - this.trackReferenceAdded(obj); - } - - return this.list.addAll(index, c); - } - - @Override - public boolean removeAll(@NotNull Collection c) { - if (this.size() >= 2 && c.size() > 4 && c instanceof List) { - //HashReferenceList uses reference equality, so using ReferenceOpenHashSet is fine - c = new ReferenceOpenHashSet<>(c); - } - this.counter.keySet().removeAll(c); - return this.list.removeAll(c); - } - - @Override - public boolean retainAll(@NotNull Collection c) { - this.counter.keySet().retainAll(c); - return this.list.retainAll(c); - } - - @Override - public void clear() { - this.counter.clear(); - this.list.clear(); - } - - @Override - public T get(int index) { - return this.list.get(index); - } - - @Override - public T set(int index, T element) { - T prev = this.list.set(index, element); - - if (prev != element) { - if (prev != null) { - this.trackReferenceRemoved(prev); - } - - this.trackReferenceAdded(element); - } - - return prev; - } - - @Override - public void add(int index, T element) { - this.trackReferenceAdded(element); - - this.list.add(index, element); - } - - @Override - public T remove(int index) { - T prev = this.list.remove(index); - - if (prev != null) { - this.trackReferenceRemoved(prev); - } - - return prev; - } - - @Override - public int indexOf(Object o) { - return this.list.indexOf(o); - } - - @Override - public int lastIndexOf(Object o) { - return this.list.lastIndexOf(o); - } - - @Override - public ListIterator listIterator() { - return this.listIterator(0); - } - - @Override - public ListIterator listIterator(int index) { - return new ListIterator<>() { - private final ListIterator inner = HashedReferenceList.this.list.listIterator(index); - - @Override - public boolean hasNext() { - return this.inner.hasNext(); - } - - @Override - public T next() { - return this.inner.next(); - } - - @Override - public boolean hasPrevious() { - return this.inner.hasPrevious(); - } - - @Override - public T previous() { - return this.inner.previous(); - } - - @Override - public int nextIndex() { - return this.inner.nextIndex(); - } - - @Override - public int previousIndex() { - return this.inner.previousIndex(); - } - - @Override - public void remove() { - int last = this.previousIndex(); - - if (last == -1) { - throw new NoSuchElementException(); - } - - T prev = HashedReferenceList.this.get(last); - - if (prev != null) { - HashedReferenceList.this.trackReferenceRemoved(prev); - } - - this.inner.remove(); - } - - @Override - public void set(T t) { - int last = this.previousIndex(); - - if (last == -1) { - throw new NoSuchElementException(); - } - - T prev = HashedReferenceList.this.get(last); - - if (prev != t) { - if (prev != null) { - HashedReferenceList.this.trackReferenceRemoved(prev); - } - - HashedReferenceList.this.trackReferenceAdded(t); - } - - this.inner.remove(); - } - - @Override - public void add(T t) { - HashedReferenceList.this.trackReferenceAdded(t); - - this.inner.add(t); - } - }; - } - - @Override - public List subList(int fromIndex, int toIndex) { - return this.list.subList(fromIndex, toIndex); - } - - private void trackReferenceAdded(T t) { - this.counter.addTo(t, 1); - } - - @SuppressWarnings("unchecked") - private void trackReferenceRemoved(Object o) { - if (this.counter.addTo((T) o, -1) <= 1) { - this.counter.removeInt(o); - } - } - -} diff --git a/gradle.properties b/gradle.properties index c43d031..c4a5a7e 100644 --- a/gradle.properties +++ b/gradle.properties @@ -2,7 +2,7 @@ group = space.bxteam.divinemc mcVersion=1.21.4 version=1.21.4-R0.1-SNAPSHOT -purpurRef=5e5857dc91b7015ac72df1f40bd715c84ef61231 +purpurRef=5583a3f19b75a9d07367e2ca80adbbafa9af6593 experimental=false org.gradle.configuration-cache=true