From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: NONPLAYT <76615486+NONPLAYT@users.noreply.github.com> Date: Tue, 28 Jan 2025 01:18:49 +0300 Subject: [PATCH] Petal: Multithreaded Tracker Original project: https://github.com/Bloom-host/Petal Original license: GPL v3 Patch description: 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..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 ea3d63856ed487c4d23b0448c97169c230932832..646bd06468bdd7a7ebc5ecc7e876617ad0fc6c05 100644 --- a/ca/spottedleaf/moonrise/patches/chunk_system/player/RegionizedPlayerChunkLoader.java +++ b/ca/spottedleaf/moonrise/patches/chunk_system/player/RegionizedPlayerChunkLoader.java @@ -345,7 +345,11 @@ public final class RegionizedPlayerChunkLoader { private boolean canGenerateChunks = true; private final ArrayDeque> delayedTicketOps = new ArrayDeque<>(); - private final LongOpenHashSet sentChunks = new LongOpenHashSet(); + // DivineMC start - Multithreaded tracker + private final LongOpenHashSet sentChunks = org.bxteam.divinemc.config.DivineConfig.AsyncCategory.multithreadedEnabled && !org.bxteam.divinemc.config.DivineConfig.AsyncCategory.multithreadedCompatModeEnabled + ? new org.bxteam.divinemc.util.map.ConcurrentLongHashSet() + : new LongOpenHashSet(); + // DivineMC end - 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..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 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; @@ -1199,23 +1253,93 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider this.lastTrackedChunk = chunk; final ServerPlayer[] playersRaw = players.getRawDataUnchecked(); + final int playersLength = Math.min(playersRaw.length, players.size()); // DivineMC - Multithreaded tracker - for (int i = 0, len = players.size(); i < len; ++i) { + 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); } } + + 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 + 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; + } + } + // DivineMC end - Multithreaded tracker + @Override public final void moonrise$removeNonTickThreadPlayers() { boolean foundToRemove = false; @@ -1230,12 +1354,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 @@ -1245,10 +1370,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 @@ -1276,7 +1402,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider @Override public void sendToTrackingPlayers(Packet packet) { - for (ServerPlayerConnection serverPlayerConnection : this.seenBy) { + for (ServerPlayerConnection serverPlayerConnection : this.seenBy()) { // DivineMC - Multithreaded tracker // TODO: verify serverPlayerConnection.send(packet); } } @@ -1291,7 +1417,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider @Override public void sendToTrackingPlayersFiltered(Packet packet, Predicate filter) { - for (ServerPlayerConnection serverPlayerConnection : this.seenBy) { + for (ServerPlayerConnection serverPlayerConnection : this.seenBy()) { // DivineMC - Multithreaded tracker // TODO: verify if (filter.test(serverPlayerConnection.getPlayer())) { serverPlayerConnection.send(packet); } @@ -1299,24 +1425,35 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider } public void broadcastRemoved() { - for (ServerPlayerConnection serverPlayerConnection : this.seenBy) { + 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); if (this.seenBy.isEmpty()) { ChunkMap.this.level.debugSynchronizers().dropEntity(this.entity); } } + this.seenByUpdated(); // DivineMC - Multithreaded tracker } public void updatePlayer(ServerPlayer player) { - org.spigotmc.AsyncCatcher.catchOp("player tracker update"); // Spigot if (player != this.entity) { + if (org.bxteam.divinemc.config.DivineConfig.AsyncCategory.multithreadedEnabled && player == null) return; // DivineMC - 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(); @@ -1344,6 +1481,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); @@ -1357,6 +1495,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider this.serverEntity.onPlayerAdd(); // Paper - fix desync when a player is added to the tracker } } else { + this.seenByUpdated(); // DivineMC - Multithreaded tracker // TODO: check this this.removePlayer(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 +++ b/net/minecraft/server/level/ServerBossEvent.java @@ -13,7 +13,11 @@ import net.minecraft.util.Mth; import net.minecraft.world.BossEvent; public class ServerBossEvent extends BossEvent { - private final Set players = Sets.newHashSet(); + // DivineMC start - Multithreaded tracker - players can be removed in async tracking + private final Set players = org.bxteam.divinemc.config.DivineConfig.AsyncCategory.multithreadedEnabled + ? Sets.newConcurrentHashSet() + : Sets.newHashSet(); + // DivineMC end - Multithreaded tracker 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 737a6ff0bfec9b555fa425619d97b80ef95cb3e6..cfa2c3aa357a0dbb7edf7f0c8cebea5ed2f31cbc 100644 --- a/net/minecraft/server/level/ServerEntity.java +++ b/net/minecraft/server/level/ServerEntity.java @@ -122,7 +122,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); @@ -409,8 +409,6 @@ public class ServerEntity { // CraftBukkit end this.synchronizer.sendToTrackingPlayersAndSelf(new ClientboundUpdateAttributesPacket(this.entity.getId(), attributesToSync)); } - - attributesToSync.clear(); } } diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java index b240c7f2579e25d520fbc0ab08e801028bc15192..01ad6566c236bac2141f75fa9cf37844e3d97637 100644 --- a/net/minecraft/server/level/ServerLevel.java +++ b/net/minecraft/server/level/ServerLevel.java @@ -2579,7 +2579,6 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe @Override public LevelEntityGetter getEntities() { - org.spigotmc.AsyncCatcher.catchOp("Chunk getEntities call"); // Spigot return this.moonrise$getEntityLookup(); // Paper - rewrite chunk system } @@ -2858,7 +2857,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)) { // DivineMC - 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 2638343fcccbaf81d19dcd4fd09534b2e7bee796..e5ece51effaddeb49b689f897da5172958360d5f 100644 --- a/net/minecraft/server/network/ServerGamePacketListenerImpl.java +++ b/net/minecraft/server/network/ServerGamePacketListenerImpl.java @@ -1942,7 +1942,6 @@ public class ServerGamePacketListenerImpl } public void internalTeleport(PositionMoveRotation posMoveRotation, Set relatives) { - org.spigotmc.AsyncCatcher.catchOp("teleport"); // Paper // 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 49f83d3dedc16d977f7904971af13cc17ed32882..86370c9f6e83e5815922080c10336d394075b4e9 100644 --- a/net/minecraft/world/entity/LivingEntity.java +++ b/net/minecraft/world/entity/LivingEntity.java @@ -1359,13 +1359,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 18563961e9dba1a11265c6ea708881d4e46846ff..355e552f77e7639ddf1c8b4da9868775017563c3 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; public 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); // DivineMC - Multithreaded Tracker } 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..c93b2c684d773551b14cc2ce024923536780ee17 100644 --- a/net/minecraft/world/entity/ai/attributes/AttributeInstance.java +++ b/net/minecraft/world/entity/ai/attributes/AttributeInstance.java @@ -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 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..290a7fa565f695c7afe3cf0791f6cf1da6a39663 100644 --- a/net/minecraft/world/entity/ai/attributes/AttributeMap.java +++ b/net/minecraft/world/entity/ai/attributes/AttributeMap.java @@ -14,9 +14,11 @@ import net.minecraft.core.Holder; import net.minecraft.resources.ResourceLocation; public class AttributeMap { - private final Map, AttributeInstance> attributes = new Object2ObjectOpenHashMap<>(); - private final Set attributesToSync = new ObjectOpenHashSet<>(); - private final Set attributesToUpdate = new ObjectOpenHashSet<>(); + // DivineMC start - Multithreaded tracker + 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 00c7fe0419fa44736b971b684adfe1b963e24bd5..581486ad49945d1658cb070d9f418f7a2fc9196d 100644 --- a/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java +++ b/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java @@ -212,6 +212,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; // DivineMC - Multithreaded tracker Player player1 = holdingPlayer1.player; String plainTextName = player1.getPlainTextName(); if (!player1.isRemoved() && (player1.getInventory().contains(predicate) || mapStack.isFramed())) {