From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: peaches94 Date: Sat, 2 Jul 2022 00:35:56 -0500 Subject: [PATCH] Multithreaded Tracker Original license: GPL v3 Original project: https://github.com/Bloom-host/Petal Original license: GPL v3 Original project: https://github.com/TECHNOVE/Airplane-Experimental Co-authored-by: Paul Sauve Co-authored-by: Kevin Raneri Co-authored-by: HaHaWTH <102713261+HaHaWTH@users.noreply.github.com> This patch refactored from original multithreaded tracker (Petal version), and is derived from the Airplane fork by Paul Sauve, the tree is like: Airplane -> Pufferfish? -> Petal -> Leaf We made much of tracking logic asynchronously, and fixed visible issue for the case of some NPC plugins which using real entity type, e.g. Citizens. But it is still recommending to use those packet based, virtual entity based NPC plugins, e.g. ZNPC Plus, Adyeshach, Fancy NPC, etc. diff --git a/ca/spottedleaf/moonrise/common/misc/NearbyPlayers.java b/ca/spottedleaf/moonrise/common/misc/NearbyPlayers.java index 1b8193587814225c2ef2c5d9e667436eb50ff6c5..4200d22606c6a3dbdf282792a4007a51df66963b 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<>(); + // Leaf start - Multithreaded tracker + private final it.unimi.dsi.fastutil.longs.Long2ReferenceMap byChunk; + { + if (org.dreeam.leaf.config.modules.async.MultithreadedTracker.enabled) { + byChunk = it.unimi.dsi.fastutil.longs.Long2ReferenceMaps.synchronize(new Long2ReferenceOpenHashMap<>()); + } else { + byChunk = new Long2ReferenceOpenHashMap<>(); + } + } + // Leaf 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 dd2509996bfd08e8c3f9f2be042229eac6d7692d..a35e9fae8f8da0c42f0616c4f78dc396492673aa 100644 --- a/ca/spottedleaf/moonrise/patches/chunk_system/player/RegionizedPlayerChunkLoader.java +++ b/ca/spottedleaf/moonrise/patches/chunk_system/player/RegionizedPlayerChunkLoader.java @@ -342,7 +342,7 @@ public final class RegionizedPlayerChunkLoader { private boolean canGenerateChunks = true; private final ArrayDeque> delayedTicketOps = new ArrayDeque<>(); - private final LongOpenHashSet sentChunks = new LongOpenHashSet(); + private final LongOpenHashSet sentChunks = org.dreeam.leaf.config.modules.async.MultithreadedTracker.enabled && !org.dreeam.leaf.config.modules.async.MultithreadedTracker.compatModeEnabled ? new org.dreeam.leaf.util.map.ConcurrentLongHashSet() : new LongOpenHashSet(); // Leaf - Multithreaded tracker 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..2ccff968cb2065d34fad4d27573f9e3081edb2f2 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 + // Leaf start - Multithreaded tracker + public static final ServerPlayerConnection[] EMPTY_OBJECT_ARRAY = new ServerPlayerConnection[0]; + public final Set seenBy = org.dreeam.leaf.config.modules.async.MultithreadedTracker.enabled ? it.unimi.dsi.fastutil.objects.ReferenceSets.synchronize(new it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet<>()) : new it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet<>(); // Paper - Perf: optimise map impl + private volatile ServerPlayerConnection[] seenByArray = EMPTY_OBJECT_ARRAY; + public ServerPlayerConnection[] seenBy() { + return seenByArray; + } + public void seenByUpdated() { + this.seenByArray = this.seenBy.toArray(EMPTY_OBJECT_ARRAY); + } + // Leaf end - Multithreaded tracker // Paper start - optimise entity tracker private long lastChunkUpdate = -1L; @@ -1100,27 +1126,95 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider this.lastTrackedChunk = chunk; final ServerPlayer[] playersRaw = players.getRawDataUnchecked(); - - for (int i = 0, len = players.size(); i < len; ++i) { + final int playersLength = Math.min(playersRaw.length, players.size()); // Leaf - Multithreaded tracker + for (int i = 0; i < playersLength; ++i) { // Leaf - Multithreaded tracker final ServerPlayer player = playersRaw[i]; this.updatePlayer(player); } if (lastChunkUpdate != currChunkUpdate || lastTrackedChunk != chunk) { // need to purge any players possible not in the chunk list - for (final ServerPlayerConnection conn : new java.util.ArrayList<>(this.seenBy)) { + // Leaf start - 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); } } + if (removed) { + this.seenByUpdated(); + } } } + // Leaf end - Multithreaded tracker + + // Leaf start - Multithreaded tracker + public final @Nullable Runnable leafTickCompact(final ca.spottedleaf.moonrise.common.misc.NearbyPlayers.TrackedChunk chunk) { + if (chunk == null) { + this.moonrise$clearPlayers(); + return null; + } + + final ca.spottedleaf.moonrise.common.list.ReferenceList players = chunk.getPlayers(ca.spottedleaf.moonrise.common.misc.NearbyPlayers.NearbyMapType.VIEW_DISTANCE); + + if (players == null) { + this.moonrise$clearPlayers(); + return null; + } + + final long lastChunkUpdate = this.lastChunkUpdate; + final long currChunkUpdate = chunk.getUpdateCount(); + final ca.spottedleaf.moonrise.common.misc.NearbyPlayers.TrackedChunk lastTrackedChunk = this.lastTrackedChunk; + this.lastChunkUpdate = currChunkUpdate; + this.lastTrackedChunk = chunk; + + final ServerPlayer[] playersRaw = players.getRawDataUnchecked(); + final int playersLen = players.size(); // Ensure length won't change in the future tasks + + if (!org.dreeam.leaf.config.modules.async.MultithreadedTracker.enabled || !org.dreeam.leaf.config.modules.async.MultithreadedTracker.compatModeEnabled) { + throw new IllegalStateException(); + } + final boolean isServerPlayer = this.entity instanceof ServerPlayer; + final boolean isRealPlayer = isServerPlayer && ((ca.spottedleaf.moonrise.patches.chunk_system.player.ChunkSystemServerPlayer) this.entity).moonrise$isRealPlayer(); + Runnable updatePlayerTasks = () -> { + for (int i = 0; i < playersLen; ++i) { + final ServerPlayer player = playersRaw[i]; + this.updatePlayer(player); + } + + if (lastChunkUpdate != currChunkUpdate || lastTrackedChunk != chunk) { + // need to purge any players possible not in the chunk list + boolean removed = false; + for (final ServerPlayerConnection conn : this.seenBy()) { + final ServerPlayer player = conn.getPlayer(); + if (!players.contains(player)) { + removed |= this.removePlayerMulti(player); + } + } + if (removed) { + this.seenByUpdated(); + } + } + }; + + // Only update asynchronously for real player, and sync update for fake players + // This can fix compatibility issue with NPC plugins using real entity type, like Citizens + // To prevent visible issue with player type NPCs + // btw, still recommend to use packet based NPC plugins, like ZNPC Plus, Adyeshach, Fancy NPC, etc. + if (isRealPlayer || !isServerPlayer) { + return updatePlayerTasks; + } else { + updatePlayerTasks.run(); + return null; + } + } + // Leaf end - Multithreaded tracker @Override public final void moonrise$removeNonTickThreadPlayers() { boolean foundToRemove = false; - for (final ServerPlayerConnection conn : this.seenBy) { + for (final ServerPlayerConnection conn : this.seenBy()) { // Leaf - Multithreaded tracker if (!ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(conn.getPlayer())) { foundToRemove = true; break; @@ -1131,12 +1225,13 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider return; } - for (final ServerPlayerConnection conn : new java.util.ArrayList<>(this.seenBy)) { + for (final ServerPlayerConnection conn : this.seenBy()) { // Leaf - Multithreaded tracker ServerPlayer player = conn.getPlayer(); if (!ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(player)) { - this.removePlayer(player); + this.removePlayerMulti(player); // Leaf - Multithreaded tracker } } + this.seenByUpdated(); // Leaf - Multithreaded tracker } @Override @@ -1146,10 +1241,11 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider if (this.seenBy.isEmpty()) { return; } - for (final ServerPlayerConnection conn : new java.util.ArrayList<>(this.seenBy)) { + for (final ServerPlayerConnection conn : this.seenBy()) { // Leaf - Multithreaded tracker ServerPlayer player = conn.getPlayer(); - this.removePlayer(player); + this.removePlayerMulti(player); // Leaf - Multithreaded tracker } + this.seenByUpdated(); // Leaf - Multithreaded tracker } @Override @@ -1176,7 +1272,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider } public void broadcast(Packet packet) { - for (ServerPlayerConnection serverPlayerConnection : this.seenBy) { + for (ServerPlayerConnection serverPlayerConnection : this.seenBy()) { // Leaf - petal - Multithreaded tracker serverPlayerConnection.send(packet); } } @@ -1189,21 +1285,34 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider } public void broadcastRemoved() { - for (ServerPlayerConnection serverPlayerConnection : this.seenBy) { + for (ServerPlayerConnection serverPlayerConnection : this.seenBy()) { // Leaf - petal - Multithreaded tracker this.serverEntity.removePairing(serverPlayerConnection.getPlayer()); } } + // Leaf start - Multithreaded tracker + public boolean removePlayerMulti(ServerPlayer player) { + if (this.seenBy.remove(player.connection)) { + this.serverEntity.removePairing(player); + return true; + } else { + return false; + } + } + // Leaf end - Multithreaded tracker + public void removePlayer(ServerPlayer player) { - org.spigotmc.AsyncCatcher.catchOp("player tracker clear"); // Spigot + //org.spigotmc.AsyncCatcher.catchOp("player tracker clear"); // Spigot // Leaf - petal - Multithreaded tracker - We can remove async too if (this.seenBy.remove(player.connection)) { this.serverEntity.removePairing(player); } + this.seenByUpdated(); // Leaf - Multithreaded tracker } public void updatePlayer(ServerPlayer player) { - org.spigotmc.AsyncCatcher.catchOp("player tracker update"); // Spigot + //org.spigotmc.AsyncCatcher.catchOp("player tracker update"); // Spigot // Leaf - petal - Multithreaded tracker - We can update async if (player != this.entity) { + if (org.dreeam.leaf.config.modules.async.MultithreadedTracker.enabled && player == null) return; // Leaf - Multithreaded tracker // Paper start - remove allocation of Vec3D here // Vec3 vec3 = player.position().subtract(this.entity.position()); double vec3_dx = player.getX() - this.entity.getX(); @@ -1231,6 +1340,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider // CraftBukkit end if (flag) { if (this.seenBy.add(player.connection)) { + this.seenByUpdated(); // Leaf - 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); @@ -1239,6 +1349,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider this.serverEntity.onPlayerAdd(); // Paper - fix desync when a player is added to the tracker } } else if (this.seenBy.remove(player.connection)) { + this.seenByUpdated(); // Leaf - Multithreaded tracker this.serverEntity.removePairing(player); } } diff --git a/net/minecraft/server/level/ServerBossEvent.java b/net/minecraft/server/level/ServerBossEvent.java index f106373ef3ac4a8685c2939c9e8361688a285913..51ae390c68e7a3aa193329cc3bc47ca675930ff2 100644 --- a/net/minecraft/server/level/ServerBossEvent.java +++ b/net/minecraft/server/level/ServerBossEvent.java @@ -13,7 +13,7 @@ import net.minecraft.util.Mth; import net.minecraft.world.BossEvent; public class ServerBossEvent extends BossEvent { - private final Set players = Sets.newHashSet(); + private final Set players = org.dreeam.leaf.config.modules.async.MultithreadedTracker.enabled ? Sets.newConcurrentHashSet() : Sets.newHashSet(); // Leaf - petal - Multithreaded tracker - players can be removed in async tracking private final Set unmodifiablePlayers = Collections.unmodifiableSet(this.players); public boolean visible = true; diff --git a/net/minecraft/server/level/ServerEntity.java b/net/minecraft/server/level/ServerEntity.java index 1dee20436fc29537319ee456756a8e8f7b6fe66a..5351d373aa0f4450386a6b50f88052c031949f5b 100644 --- a/net/minecraft/server/level/ServerEntity.java +++ b/net/minecraft/server/level/ServerEntity.java @@ -110,8 +110,16 @@ public class ServerEntity { .forEach( removedPassenger -> { if (removedPassenger instanceof ServerPlayer serverPlayer1) { - serverPlayer1.connection - .teleport(serverPlayer1.getX(), serverPlayer1.getY(), serverPlayer1.getZ(), serverPlayer1.getYRot(), serverPlayer1.getXRot()); + // Leaf start - Multithreaded tracker - Ensure teleport is executed on server thread + if (org.dreeam.leaf.config.modules.async.MultithreadedTracker.enabled && Thread.currentThread() instanceof org.dreeam.leaf.async.tracker.MultithreadedTracker.MultithreadedTrackerThread) { + net.minecraft.server.MinecraftServer.getServer().scheduleOnMain(() -> serverPlayer1.connection + .teleport(serverPlayer1.getX(), serverPlayer1.getY(), serverPlayer1.getZ(), serverPlayer1.getYRot(), serverPlayer1.getXRot()) + ); + } else { + serverPlayer1.connection + .teleport(serverPlayer1.getX(), serverPlayer1.getY(), serverPlayer1.getZ(), serverPlayer1.getYRot(), serverPlayer1.getXRot()); + } + // Leaf end - Multithreaded tracker - Ensure teleport is executed on server thread } } ); @@ -124,7 +132,7 @@ public class ServerEntity { MapId mapId = itemFrame.cachedMapId; // Paper - Perf: Cache map ids on item frames MapItemSavedData savedData = MapItem.getSavedData(mapId, this.level); if (savedData != null) { - for (final net.minecraft.server.network.ServerPlayerConnection connection : this.trackedPlayers) { // Paper + for (final net.minecraft.server.network.ServerPlayerConnection connection : this.trackedPlayers.toArray(ChunkMap.TrackedEntity.EMPTY_OBJECT_ARRAY)) { // Paper // Leaf - Multithreaded tracker final ServerPlayer serverPlayer = connection.getPlayer(); // Paper savedData.tickCarriedBy(serverPlayer, item); Packet updatePacket = savedData.getUpdatePacket(mapId, serverPlayer); @@ -443,7 +451,7 @@ public class ServerEntity { this.broadcastAndSend(new ClientboundUpdateAttributesPacket(this.entity.getId(), attributesToSync)); } - attributesToSync.clear(); + // attributesToSync.clear(); // Leaf - Multithreaded tracker } } diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java index 275b640f4536366152f59acf071dd4eba15696c8..a669a59a42f814480879a52d2da5e04c636720de 100644 --- a/net/minecraft/server/level/ServerLevel.java +++ b/net/minecraft/server/level/ServerLevel.java @@ -2522,7 +2522,7 @@ 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"); // Spigot // Leaf - Multithreaded tracker return this.moonrise$getEntityLookup(); // Paper - rewrite chunk system } @@ -2754,7 +2754,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe } map.carriedByPlayers.remove(player); - if (map.carriedBy.removeIf(holdingPlayer -> holdingPlayer.player == player)) { + if (map.carriedBy.removeIf(holdingPlayer -> holdingPlayer != null && holdingPlayer.player == player)) { // Leaf - Multithreaded tracker map.decorations.remove(player.getName().getString()); } } diff --git a/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/net/minecraft/server/network/ServerGamePacketListenerImpl.java index 327b3bc89920c4ab02c1126dc63bca05ce3abefe..1415043bee5fbbfcf9dab9184a9418d52f531f62 100644 --- a/net/minecraft/server/network/ServerGamePacketListenerImpl.java +++ b/net/minecraft/server/network/ServerGamePacketListenerImpl.java @@ -1820,7 +1820,7 @@ public class ServerGamePacketListenerImpl } public void internalTeleport(PositionMoveRotation posMoveRotation, Set relatives) { - org.spigotmc.AsyncCatcher.catchOp("teleport"); // Paper + //org.spigotmc.AsyncCatcher.catchOp("teleport"); // Paper // Leaf - 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 d502325d693539842fd6f5485365e0e9b786b7aa..a99527409e9aae6c8b321a5ed100b2645151087e 100644 --- a/net/minecraft/world/entity/LivingEntity.java +++ b/net/minecraft/world/entity/LivingEntity.java @@ -1311,13 +1311,13 @@ public abstract class LivingEntity extends Entity implements Attackable { } private void refreshDirtyAttributes() { - Set attributesToUpdate = this.getAttributes().getAttributesToUpdate(); + // Leaf 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(); + // Leaf 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..406767c60ec1a324faaf5d3658b161647497f99b 100644 --- a/net/minecraft/world/entity/ai/attributes/Attribute.java +++ b/net/minecraft/world/entity/ai/attributes/Attribute.java @@ -16,10 +16,15 @@ public class Attribute { private boolean syncable; private final String descriptionId; private Attribute.Sentiment sentiment = Attribute.Sentiment.POSITIVE; + // Leaf start - Optimize AttributeMap + public final int uid; + private static final java.util.concurrent.atomic.AtomicInteger SIZE = new java.util.concurrent.atomic.AtomicInteger(); + // Leaf end - Optimize AttributeMap protected Attribute(String descriptionId, double defaultValue) { this.defaultValue = defaultValue; this.descriptionId = descriptionId; + this.uid = SIZE.getAndAdd(1); // Leaf - Optimize AttributeMap } public double getDefaultValue() { diff --git a/net/minecraft/world/entity/ai/attributes/AttributeInstance.java b/net/minecraft/world/entity/ai/attributes/AttributeInstance.java index 8013594bb4844e7a8abf28123958e7f632d39341..b502c4a0f3695cc5bee8954f937f64584df1584d 100644 --- a/net/minecraft/world/entity/ai/attributes/AttributeInstance.java +++ b/net/minecraft/world/entity/ai/attributes/AttributeInstance.java @@ -24,8 +24,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<>(); + // Leaf start - Multithreaded tracker + private final Map modifierById; + { + if (org.dreeam.leaf.config.modules.async.MultithreadedTracker.enabled) { + modifierById = it.unimi.dsi.fastutil.objects.Object2ObjectMaps.synchronize(new Object2ObjectArrayMap<>(), this); + } else { + modifierById = new Object2ObjectArrayMap<>(); + } + } + private final Map permanentModifiers; + { + if (org.dreeam.leaf.config.modules.async.MultithreadedTracker.enabled) { + permanentModifiers = it.unimi.dsi.fastutil.objects.Object2ObjectMaps.synchronize(new Object2ObjectArrayMap<>(), this); + } else { + permanentModifiers = new Object2ObjectArrayMap<>(); + } + } + // Leaf end - Multithreaded tracker private double baseValue; private boolean dirty = true; private double cachedValue; @@ -54,7 +70,13 @@ public class AttributeInstance { @VisibleForTesting Map getModifiers(AttributeModifier.Operation operation) { - return this.modifiersByOperation.computeIfAbsent(operation, operation1 -> new Object2ObjectOpenHashMap<>()); + // Leaf start - Multithreaded tracker + return this.modifiersByOperation.computeIfAbsent(operation, operation1 -> { + if (org.dreeam.leaf.config.modules.async.MultithreadedTracker.enabled) + return it.unimi.dsi.fastutil.objects.Object2ObjectMaps.synchronize(new Object2ObjectArrayMap<>(), this); + else return new Object2ObjectArrayMap<>(); + }); + // Leaf end - Multithreaded tracker } public Set getModifiers() { @@ -142,8 +164,12 @@ public class AttributeInstance { public double getValue() { if (this.dirty) { - this.cachedValue = this.calculateValue(); + // Leaf start - Multithreaded tracker + double value = this.calculateValue(); + this.cachedValue = value; this.dirty = false; + return value; + // Leaf end - Multithreaded tracker } return this.cachedValue; @@ -195,9 +221,19 @@ public class AttributeInstance { if (!this.permanentModifiers.isEmpty()) { ListTag listTag = new ListTag(); - for (AttributeModifier attributeModifier : this.permanentModifiers.values()) { - listTag.add(attributeModifier.save()); + // Leaf start - Multithreaded tracker + if (org.dreeam.leaf.config.modules.async.MultithreadedTracker.enabled) { + synchronized (this) { + for (AttributeModifier attributeModifier : this.permanentModifiers.values()) { + listTag.add(attributeModifier.save()); + } + } + } else { + for (AttributeModifier attributeModifier : this.permanentModifiers.values()) { + listTag.add(attributeModifier.save()); + } } + // Leaf end - Multithreaded tracker compoundTag.put("modifiers", listTag); } diff --git a/net/minecraft/world/entity/ai/attributes/AttributeMap.java b/net/minecraft/world/entity/ai/attributes/AttributeMap.java index 89f4c5b2d61e27acd48063f9f24ce9ea91898b8b..0bc846721b1af44904a705f5c4aef897a03824e0 100644 --- a/net/minecraft/world/entity/ai/attributes/AttributeMap.java +++ b/net/minecraft/world/entity/ai/attributes/AttributeMap.java @@ -20,12 +20,14 @@ import org.slf4j.Logger; public class AttributeMap { private static final Logger LOGGER = LogUtils.getLogger(); // Gale start - Lithium - replace AI attributes with optimized collections - private final Map, AttributeInstance> attributes = new it.unimi.dsi.fastutil.objects.Reference2ReferenceOpenHashMap<>(0); - private final Set attributesToSync = new it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet<>(0); - private final Set attributesToUpdate = new it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet<>(0); + // Leaf start - Multithreaded tracker + private final Map, AttributeInstance> attributes = new org.dreeam.leaf.util.map.AttributeInstanceArrayMap(); + private final org.dreeam.leaf.util.map.AttributeInstanceSet attributesToSync = new org.dreeam.leaf.util.map.AttributeInstanceSet((org.dreeam.leaf.util.map.AttributeInstanceArrayMap) attributes); + private final org.dreeam.leaf.util.map.AttributeInstanceSet attributesToUpdate = new org.dreeam.leaf.util.map.AttributeInstanceSet((org.dreeam.leaf.util.map.AttributeInstanceArrayMap) attributes); + // Leaf end - Multithreaded tracker // Gale end - Lithium - replace AI attributes with optimized collections private final AttributeSupplier supplier; - private final java.util.function.Function, AttributeInstance> createInstance; // Gale - Airplane - reduce entity allocations + //private final java.util.function.Function, AttributeInstance> createInstance; // Gale - Airplane - reduce entity allocations // Leaf - Optimize AttributeMap private final net.minecraft.world.entity.LivingEntity entity; // Purpur - Ridables public AttributeMap(AttributeSupplier supplier) { @@ -36,31 +38,54 @@ public class AttributeMap { this.entity = entity; // Purpur end - Ridables this.supplier = defaultAttributes; - this.createInstance = holder -> this.supplier.createInstance(this::onAttributeModified, holder); // Gale - Airplane - reduce entity allocations + //this.createInstance = holder -> this.supplier.createInstance(this::onAttributeModified, holder); // Gale - Airplane - reduce entity allocations // Leaf - Optimize AttributeMap } - private void onAttributeModified(AttributeInstance instance) { + // Leaf start - Multithreaded tracker + private synchronized void onAttributeModified(AttributeInstance instance) { 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; + 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; + } + // Leaf 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, this.createInstance); // Gale - Airplane - reduce entity allocations - cache lambda, as for some reason java allocates it anyways + // Leaf 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; + // Leaf 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..05de8a77b389691dd6986f36b4cb8cc0935e21e4 100644 --- a/net/minecraft/world/entity/ai/attributes/AttributeSupplier.java +++ b/net/minecraft/world/entity/ai/attributes/AttributeSupplier.java @@ -11,7 +11,7 @@ public class AttributeSupplier { private final Map, AttributeInstance> instances; AttributeSupplier(Map, AttributeInstance> instances) { - this.instances = instances; + this.instances = new org.dreeam.leaf.util.map.AttributeInstanceArrayMap(instances); // Leaf - Optimize AttributeMap } public AttributeInstance getAttributeInstance(Holder attribute) { @@ -41,7 +41,7 @@ public class AttributeSupplier { } @Nullable - public AttributeInstance createInstance(Consumer onDirty, Holder attribute) { + public AttributeInstance createInstance(Consumer onDirty, Holder attribute) { // Leaf - Multithreaded tracker AttributeInstance attributeInstance = this.instances.get(attribute); if (attributeInstance == null) { return null; diff --git a/net/minecraft/world/entity/item/PrimedTnt.java b/net/minecraft/world/entity/item/PrimedTnt.java index c96f458994818392857642282ec3d492124885da..d345afd14ef6fe2f0a584df5dfa080fd7ab3f47e 100644 --- a/net/minecraft/world/entity/item/PrimedTnt.java +++ b/net/minecraft/world/entity/item/PrimedTnt.java @@ -142,12 +142,14 @@ public class PrimedTnt extends Entity implements TraceableEntity { net.minecraft.network.protocol.game.ClientboundSetEntityMotionPacket velocityPacket = new net.minecraft.network.protocol.game.ClientboundSetEntityMotionPacket(this); net.minecraft.network.protocol.game.ClientboundTeleportEntityPacket positionPacket = net.minecraft.network.protocol.game.ClientboundTeleportEntityPacket.teleport(this.getId(), net.minecraft.world.entity.PositionMoveRotation.of(this), java.util.Set.of(), this.onGround); - ete.seenBy.stream() - .filter(viewer -> (viewer.getPlayer().getX() - this.getX()) * (viewer.getPlayer().getY() - this.getY()) * (viewer.getPlayer().getZ() - this.getZ()) < 16 * 16) - .forEach(viewer -> { + // Leaf start - Multithreaded tracker + for (var viewer : ete.seenBy()) { + if ((viewer.getPlayer().getX() - this.getX()) * (viewer.getPlayer().getY() - this.getY()) * (viewer.getPlayer().getZ() - this.getZ()) < 16 * 16) { viewer.send(velocityPacket); viewer.send(positionPacket); - }); + } + } + // Leaf end - Multithreaded tracker } } // Paper end - Option to prevent TNT from moving in water diff --git a/net/minecraft/world/entity/vehicle/NewMinecartBehavior.java b/net/minecraft/world/entity/vehicle/NewMinecartBehavior.java index 724466d14c925704671e510cea1919ee95a2ae02..4426b344677ab9f2753dd2d219921bcb7cf39980 100644 --- a/net/minecraft/world/entity/vehicle/NewMinecartBehavior.java +++ 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; // Leaf - 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); + // Leaf start - Multithreaded tracker + if (org.dreeam.leaf.config.modules.async.MultithreadedTracker.enabled) { + lerpSteps = it.unimi.dsi.fastutil.objects.ObjectLists.synchronize(new it.unimi.dsi.fastutil.objects.ObjectArrayList<>()); + } else { + lerpSteps = new it.unimi.dsi.fastutil.objects.ObjectArrayList<>(); + } + // Leaf 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 681dec447486138088fe5f705ef4fadab531139f..27f8a22d798a17dbd5949d1b6ff0526837fe91d5 100644 --- a/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java +++ b/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java @@ -279,6 +279,7 @@ public class MapItemSavedData extends SavedData { for (int i = 0; i < this.carriedBy.size(); i++) { MapItemSavedData.HoldingPlayer holdingPlayer1 = this.carriedBy.get(i); + if (holdingPlayer1 == null) continue; // Leaf - Multithreaded tracker Player player1 = holdingPlayer1.player; String string = player1.getName().getString(); if (!player1.isRemoved() && (player1.getInventory().contains(predicate) || mapStack.isFramed())) {