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/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/server/level/ChunkMap.java b/net/minecraft/server/level/ChunkMap.java index 5d9d233e3a568aa6297ed9c703fa450f98158602..8986c059e7aadb58ae8d9ab7b848de10f9faa6b2 100644 --- a/net/minecraft/server/level/ChunkMap.java +++ b/net/minecraft/server/level/ChunkMap.java @@ -248,6 +248,15 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider } final ServerPlayer[] backingSet = inRange.getRawDataUnchecked(); + // Leaf start - Multithreaded tracker + if (org.dreeam.leaf.config.modules.async.MultithreadedTracker.enabled) + for (int i = 0, len = inRange.size(); i < len; i++) { + final ServerPlayer player = backingSet[i]; + if (player == null) continue; + ++(player.mobCounts[index]); + } + else + // Leaf end - Multithreaded tracker for (int i = 0, len = inRange.size(); i < len; i++) { ++(backingSet[i].mobCounts[index]); } @@ -951,6 +960,13 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider // Paper end - optimise entity tracker protected void tick() { + // Leaf start - Multithreaded tracker + if (org.dreeam.leaf.config.modules.async.MultithreadedTracker.enabled) { + final ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel level = this.level; + org.dreeam.leaf.async.tracker.MultithreadedTracker.tick(level); + return; + } + // Leaf end - Multithreaded tracker // Paper start - optimise entity tracker if (true) { this.newTrackerTick(); @@ -1073,7 +1089,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider final Entity entity; private final int range; SectionPos lastSectionPos; - public final Set seenBy = new it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet<>(); // Paper - Perf: optimise map impl + public final Set seenBy = org.dreeam.leaf.config.modules.async.MultithreadedTracker.enabled ? com.google.common.collect.Sets.newConcurrentHashSet() : new it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet<>(); // Paper - Perf: optimise map impl // Leaf - petal - Multithreaded tracker // Paper start - optimise entity tracker private long lastChunkUpdate = -1L; @@ -1100,7 +1116,39 @@ 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 + + // Leaf start - Multithreaded tracker + if (org.dreeam.leaf.config.modules.async.MultithreadedTracker.enabled && org.dreeam.leaf.config.modules.async.MultithreadedTracker.compatModeEnabled) { + final boolean isServerPlayer = this.entity instanceof ServerPlayer; + final boolean isRealPlayer = isServerPlayer && ((ca.spottedleaf.moonrise.patches.chunk_system.player.ChunkSystemServerPlayer) this.entity).moonrise$isRealPlayer(); + Runnable updatePlayerTasks = () -> { + for (int i = 0; i < playersLen; ++i) { + final ServerPlayer player = playersRaw[i]; + this.updatePlayer(player); + } + if (lastChunkUpdate != currChunkUpdate || lastTrackedChunk != chunk) { + // need to purge any players possible not in the chunk list + for (final ServerPlayerConnection conn : new java.util.ArrayList<>(this.seenBy)) { + final ServerPlayer player = conn.getPlayer(); + if (!players.contains(player)) { + this.removePlayer(player); + } + } + } + }; + + // Only update asynchronously for real player, and sync update for fake players + // This can fix compatibility issue with NPC plugins using real entity type, like Citizens + // To prevent visible issue with player type NPCs + // btw, still recommend to use packet based NPC plugins, like ZNPC Plus, Adyeshach, Fancy NPC, etc. + if (isRealPlayer || !isServerPlayer) { + org.dreeam.leaf.async.tracker.MultithreadedTracker.getTrackerExecutor().execute(updatePlayerTasks); + } else { + updatePlayerTasks.run(); + } + } else { for (int i = 0, len = players.size(); i < len; ++i) { final ServerPlayer player = playersRaw[i]; this.updatePlayer(player); @@ -1115,6 +1163,8 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider } } } + } + // Leaf end - Multithreaded tracker } @Override @@ -1176,7 +1226,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider } public void broadcast(Packet packet) { - for (ServerPlayerConnection serverPlayerConnection : this.seenBy) { + for (ServerPlayerConnection serverPlayerConnection : this.seenBy.toArray(new ServerPlayerConnection[0])) {// Leaf - petal - Multithreaded tracker serverPlayerConnection.send(packet); } } @@ -1189,21 +1239,22 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider } public void broadcastRemoved() { - for (ServerPlayerConnection serverPlayerConnection : this.seenBy) { + for (ServerPlayerConnection serverPlayerConnection : this.seenBy.toArray(new ServerPlayerConnection[0])) {// Leaf - petal - Multithreaded tracker this.serverEntity.removePairing(serverPlayerConnection.getPlayer()); } } 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); } } 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(); 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 d8298c7925e3bcea07ead4d438478cc51abcfa16..78798a84c6b49708f2650b52b40e397e4f26b532 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 } } ); @@ -443,7 +451,7 @@ public class ServerEntity { this.broadcastAndSend(new ClientboundUpdateAttributesPacket(this.entity.getId(), attributesToSync)); } - attributesToSync.clear(); + ((LivingEntity)this.entity).getAttributes().getAttributesIdToSync().clear(); // Leaf - Multithreaded tracker } } diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java index d6ebc25dc5f04194edde5ad3a1166113e5542a1d..49cbdf014d0626b36eb4c451b6de09508822b7fd 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 04bf8bba0d8c0d5459605253dcc3f135bf43fd95..abe79d07196de0a10a382d4c37161c7eb4a604ae 100644 --- a/net/minecraft/server/network/ServerGamePacketListenerImpl.java +++ b/net/minecraft/server/network/ServerGamePacketListenerImpl.java @@ -1819,7 +1819,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..6a0b1d4f17e9c3c3152ea1696ecb4a054abd56ce 100644 --- a/net/minecraft/world/entity/LivingEntity.java +++ b/net/minecraft/world/entity/LivingEntity.java @@ -1317,7 +1317,7 @@ public abstract class LivingEntity extends Entity implements Attackable { this.onAttributeUpdated(attributeInstance.getAttribute()); } - attributesToUpdate.clear(); + this.getAttributes().getAttributesIdToUpdate().clear(); // Leaf - 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..8b18ec1644eb743483ed6ed79e9c98d287fd7e20 100644 --- a/net/minecraft/world/entity/ai/attributes/Attribute.java +++ b/net/minecraft/world/entity/ai/attributes/Attribute.java @@ -16,6 +16,7 @@ public class Attribute { private boolean syncable; private final String descriptionId; private Attribute.Sentiment sentiment = Attribute.Sentiment.POSITIVE; + public int uniqueId; // Leaf protected Attribute(String descriptionId, double defaultValue) { this.defaultValue = defaultValue; diff --git a/net/minecraft/world/entity/ai/attributes/AttributeInstance.java b/net/minecraft/world/entity/ai/attributes/AttributeInstance.java index 8013594bb4844e7a8abf28123958e7f632d39341..80c703fe85ec21e4d218823504f4bbf7826b2fa6 100644 --- a/net/minecraft/world/entity/ai/attributes/AttributeInstance.java +++ b/net/minecraft/world/entity/ai/attributes/AttributeInstance.java @@ -24,8 +24,11 @@ public class AttributeInstance { private final Map> modifiersByOperation = Maps.newEnumMap( AttributeModifier.Operation.class ); - private final Map modifierById = new Object2ObjectArrayMap<>(); - private final Map permanentModifiers = new Object2ObjectArrayMap<>(); + // Leaf start - Multithreaded tracker + private final boolean multiThreadedTrackingEnabled = org.dreeam.leaf.config.modules.async.MultithreadedTracker.enabled; + private final Map modifierById = multiThreadedTrackingEnabled ? it.unimi.dsi.fastutil.objects.Object2ObjectMaps.synchronize(new Object2ObjectArrayMap<>(), this) : new Object2ObjectArrayMap<>(); + private final Map permanentModifiers = multiThreadedTrackingEnabled ? it.unimi.dsi.fastutil.objects.Object2ObjectMaps.synchronize(new Object2ObjectArrayMap<>(), this) : new Object2ObjectArrayMap<>(); + // Leaf end - Multithreaded tracker private double baseValue; private boolean dirty = true; private double cachedValue; @@ -114,7 +117,15 @@ public class AttributeInstance { } protected void setDirty() { - this.dirty = true; + // Leaf start - Multithreaded tracker + if (multiThreadedTrackingEnabled) { + synchronized (this) { + this.dirty = true; + } + } else { + this.dirty = true; + } + // Leaf end - Multithreaded tracker this.onDirty.accept(this); } @@ -141,6 +152,17 @@ public class AttributeInstance { } public double getValue() { + // Leaf start - Multithreaded tracker + if (multiThreadedTrackingEnabled) { + synchronized (this) { + if (this.dirty) { + this.cachedValue = this.calculateValue(); + this.dirty = false; + } + return this.cachedValue; + } + } + // Leaf end - Multithreaded tracker if (this.dirty) { this.cachedValue = this.calculateValue(); this.dirty = false; diff --git a/net/minecraft/world/entity/ai/attributes/AttributeMap.java b/net/minecraft/world/entity/ai/attributes/AttributeMap.java index 89f4c5b2d61e27acd48063f9f24ce9ea91898b8b..9e26059285e56b9337478f5f21aa48741b828a76 100644 --- a/net/minecraft/world/entity/ai/attributes/AttributeMap.java +++ b/net/minecraft/world/entity/ai/attributes/AttributeMap.java @@ -19,13 +19,13 @@ import org.slf4j.Logger; public class AttributeMap { private static final Logger LOGGER = LogUtils.getLogger(); - // Gale start - Lithium - replace AI attributes with optimized collections - private final Map, AttributeInstance> attributes = new it.unimi.dsi.fastutil.objects.Reference2ReferenceOpenHashMap<>(0); - private final Set attributesToSync = new it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet<>(0); - private final Set attributesToUpdate = new it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet<>(0); - // Gale end - Lithium - replace AI attributes with optimized collections + // Leaf start - Multithreaded tracker + private final java.util.concurrent.atomic.AtomicReferenceArray attributes; + private final it.unimi.dsi.fastutil.ints.IntSet attributesToSync; + private final it.unimi.dsi.fastutil.ints.IntSet attributesToUpdate; + private final java.util.function.Consumer onDirty; + // Leaf end - Multithreaded tracker private final AttributeSupplier supplier; - private final java.util.function.Function, AttributeInstance> createInstance; // Gale - Airplane - reduce entity allocations private final net.minecraft.world.entity.LivingEntity entity; // Purpur - Ridables public AttributeMap(AttributeSupplier supplier) { @@ -36,54 +36,115 @@ 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 + // Leaf start - Multithreaded tracker + this.attributes = new java.util.concurrent.atomic.AtomicReferenceArray<>(BuiltInRegistries.ATTRIBUTE.size()); + if (org.dreeam.leaf.config.modules.async.MultithreadedTracker.enabled) { + this.attributesToSync = it.unimi.dsi.fastutil.ints.IntSets.synchronize(new it.unimi.dsi.fastutil.ints.IntArraySet()); + this.attributesToUpdate = it.unimi.dsi.fastutil.ints.IntSets.synchronize(new it.unimi.dsi.fastutil.ints.IntArraySet()); + } else { + this.attributesToSync = new it.unimi.dsi.fastutil.ints.IntArraySet(); + this.attributesToUpdate = new it.unimi.dsi.fastutil.ints.IntArraySet(); + } + this.onDirty = this::onAttributeModified; + // Leaf end - Multithreaded tracker } private void onAttributeModified(AttributeInstance instance) { - this.attributesToUpdate.add(instance); + this.attributesToUpdate.add(instance.getAttribute().value().uniqueId); if (instance.getAttribute().value().isClientSyncable() && (entity == null || entity.shouldSendAttribute(instance.getAttribute().value()))) { // Purpur - Ridables - this.attributesToSync.add(instance); + this.attributesToSync.add(instance.getAttribute().value().uniqueId); } } - public Set getAttributesToSync() { + // Leaf start - Multithreaded tracker + public it.unimi.dsi.fastutil.ints.IntSet getAttributesIdToSync() { return this.attributesToSync; } - public Set getAttributesToUpdate() { + public it.unimi.dsi.fastutil.ints.IntSet getAttributesIdToUpdate() { return this.attributesToUpdate; } + @Deprecated + public Set getAttributesToSync() { + var set = new it.unimi.dsi.fastutil.objects.ObjectArraySet(); + for (int i : this.attributesToSync.toIntArray()) { + var attribute = this.attributes.get(i); + if (attribute == null) { + throw new NullPointerException("attribute cannot be null"); + } + set.add(attribute); + } + return set; + } + + @Deprecated + public Set getAttributesToUpdate() { + var set = new it.unimi.dsi.fastutil.objects.ObjectArraySet(); + for (int i : this.attributesToUpdate.toIntArray()) { + var attribute = this.attributes.get(i); + if (attribute == null) { + throw new NullPointerException("attribute cannot be null"); + } + set.add(attribute); + } + return set; + } + // 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 + // Leaf start + var attrs = this.attributes; + var result = new java.util.ArrayList(); + int len = attrs.length(); + for (int i = 0; i < len; i++) { + AttributeInstance instance = attrs.get(i); + if (instance != null + && instance.getAttribute().value().isClientSyncable() + && (entity == null || entity.shouldSendAttribute(instance.getAttribute().value())) + ) { + result.add(instance); + } + } + return result; + // Leaf end } @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 + int id = attribute.value().uniqueId; + var attr = this.attributes.getAcquire(id); + if (attr != null) { + return attr; + } + var newAttr = this.supplier.createInstance(this.onDirty, attribute); + attributes.compareAndExchangeRelease(id, null, newAttr); + return attributes.getAcquire(id); + // Leaf end } public boolean hasAttribute(Holder attribute) { - return this.attributes.get(attribute) != null || this.supplier.hasAttribute(attribute); + return this.attributes.get(attribute.value().uniqueId) != null || this.supplier.hasAttribute(attribute); // Leaf } public boolean hasModifier(Holder attribute, ResourceLocation id) { - AttributeInstance attributeInstance = this.attributes.get(attribute); + AttributeInstance attributeInstance = this.attributes.get(attribute.value().uniqueId); // Leaf return attributeInstance != null ? attributeInstance.getModifier(id) != null : this.supplier.hasModifier(attribute, id); } public double getValue(Holder attribute) { - AttributeInstance attributeInstance = this.attributes.get(attribute); + AttributeInstance attributeInstance = this.attributes.get(attribute.value().uniqueId); // Leaf return attributeInstance != null ? attributeInstance.getValue() : this.supplier.getValue(attribute); } public double getBaseValue(Holder attribute) { - AttributeInstance attributeInstance = this.attributes.get(attribute); + AttributeInstance attributeInstance = this.attributes.get(attribute.value().uniqueId); // Leaf return attributeInstance != null ? attributeInstance.getBaseValue() : this.supplier.getBaseValue(attribute); } public double getModifierValue(Holder attribute, ResourceLocation id) { - AttributeInstance attributeInstance = this.attributes.get(attribute); + AttributeInstance attributeInstance = this.attributes.get(attribute.value().uniqueId); // Leaf return attributeInstance != null ? attributeInstance.getModifier(id).amount() : this.supplier.getModifierValue(attribute, id); } @@ -99,7 +160,7 @@ public class AttributeMap { public void removeAttributeModifiers(Multimap, AttributeModifier> modifiers) { modifiers.asMap().forEach((holder, collection) -> { - AttributeInstance attributeInstance = this.attributes.get(holder); + AttributeInstance attributeInstance = this.attributes.get(holder.value().uniqueId); // Leaf if (attributeInstance != null) { collection.forEach(attributeModifier -> attributeInstance.removeModifier(attributeModifier.id())); } @@ -107,37 +168,58 @@ public class AttributeMap { } public void assignAllValues(AttributeMap map) { - map.attributes.values().forEach(attribute -> { - AttributeInstance instance = this.getInstance(attribute.getAttribute()); + // Leaf start + var attrs = map.attributes; + int len = attrs.length(); + for (int i = 0; i < len; i++) { + AttributeInstance instance = attrs.get(i); if (instance != null) { - instance.replaceFrom(attribute); + AttributeInstance self = this.getInstance(instance.getAttribute()); + if (self != null) { + self.replaceFrom(instance); + } } - }); + } + // Leaf end } public void assignBaseValues(AttributeMap map) { - map.attributes.values().forEach(attribute -> { - AttributeInstance instance = this.getInstance(attribute.getAttribute()); + // Leaf start + var attrs = map.attributes; + int len = attrs.length(); + for (int i = 0; i < len; i++) { + AttributeInstance instance = attrs.get(i); if (instance != null) { - instance.setBaseValue(attribute.getBaseValue()); + AttributeInstance self = this.getInstance(instance.getAttribute()); + if (self != null) { + self.setBaseValue(instance.getBaseValue()); + } } - }); + } + // Leaf end } public void assignPermanentModifiers(AttributeMap map) { - map.attributes.values().forEach(attribute -> { - AttributeInstance instance = this.getInstance(attribute.getAttribute()); + // Leaf start + var attrs = map.attributes; + int len = attrs.length(); + for (int i = 0; i < len; i++) { + AttributeInstance instance = attrs.get(i); if (instance != null) { - instance.addPermanentModifiers(attribute.getPermanentModifiers()); + AttributeInstance self = this.getInstance(instance.getAttribute()); + if (self != null) { + self.addPermanentModifiers(instance.getPermanentModifiers()); + } } - }); + } + // Leaf end } public boolean resetBaseValue(Holder attribute) { if (!this.supplier.hasAttribute(attribute)) { return false; } else { - AttributeInstance attributeInstance = this.attributes.get(attribute); + AttributeInstance attributeInstance = this.attributes.get(attribute.value().uniqueId); // Leaf if (attributeInstance != null) { attributeInstance.setBaseValue(this.supplier.getBaseValue(attribute)); } @@ -149,9 +231,16 @@ public class AttributeMap { public ListTag save() { ListTag listTag = new ListTag(); - for (AttributeInstance attributeInstance : this.attributes.values()) { - listTag.add(attributeInstance.save()); + // Leaf start + var attrs = this.attributes; + int len = attrs.length(); + for (int i = 0; i < len; i++) { + AttributeInstance instance = attrs.get(i); + if (instance != null) { + listTag.add(instance.save()); + } } + // Leaf end return listTag; } @@ -177,7 +266,7 @@ public class AttributeMap { // Paper - start - living entity allow attribute registration public void registerAttribute(Holder attributeBase) { AttributeInstance attributeModifiable = new AttributeInstance(attributeBase, AttributeInstance::getAttribute); - attributes.put(attributeBase, attributeModifiable); + attributes.setRelease(attributeBase.value().uniqueId, attributeModifiable); // Leaf } // Paper - end - living entity allow attribute registration diff --git a/net/minecraft/world/entity/ai/attributes/Attributes.java b/net/minecraft/world/entity/ai/attributes/Attributes.java index 26e4f766640f28068b6c07174285768e7d251b4f..59dca559ff875d710ecc5bafda23b7125b86b8ac 100644 --- a/net/minecraft/world/entity/ai/attributes/Attributes.java +++ b/net/minecraft/world/entity/ai/attributes/Attributes.java @@ -6,6 +6,8 @@ import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.resources.ResourceLocation; public class Attributes { + private static final java.util.concurrent.atomic.AtomicInteger LENGTH = new java.util.concurrent.atomic.AtomicInteger(0); // Leaf + public static final Holder ARMOR = register("armor", new RangedAttribute("attribute.name.armor", 0.0, 0.0, 30.0).setSyncable(true)); public static final Holder ARMOR_TOUGHNESS = register( "armor_toughness", new RangedAttribute("attribute.name.armor_toughness", 0.0, 0.0, 20.0).setSyncable(true) @@ -93,6 +95,7 @@ public class Attributes { ); private static Holder register(String name, Attribute attribute) { + attribute.uniqueId = LENGTH.getAndIncrement(); // Leaf return Registry.registerForHolder(BuiltInRegistries.ATTRIBUTE, ResourceLocation.withDefaultNamespace(name), attribute); } 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())) {