9
0
mirror of https://github.com/Winds-Studio/Leaf.git synced 2025-12-19 15:09:25 +00:00

optimize tracker (#323)

* optimize tracker

* optimize scaledRange

* cleanup

* fix loop

* fix loop

* optimize AttributeMap

* optimize TrackedEntity#seenBy

* revert packDirty

* cleanup
This commit is contained in:
hayanesuru
2025-05-17 21:10:27 +09:00
committed by GitHub
parent 42fe940854
commit ff9e4f506b
10 changed files with 750 additions and 192 deletions

View File

@@ -37,7 +37,7 @@ index dd2509996bfd08e8c3f9f2be042229eac6d7692d..a35e9fae8f8da0c42f0616c4f78dc396
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..4984e57d3b0806164111e79acfc82f6d9b27bfbf 100644
index 5d9d233e3a568aa6297ed9c703fa450f98158602..fa11526bf799910da9193ae6e3366542d50e7a98 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
@@ -62,7 +62,7 @@ index 5d9d233e3a568aa6297ed9c703fa450f98158602..4984e57d3b0806164111e79acfc82f6d
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;
+ final ServerLevel level = this.level;
+ org.dreeam.leaf.async.tracker.MultithreadedTracker.tick(level);
+ return;
+ }
@@ -70,97 +70,200 @@ index 5d9d233e3a568aa6297ed9c703fa450f98158602..4984e57d3b0806164111e79acfc82f6d
// Paper start - optimise entity tracker
if (true) {
this.newTrackerTick();
@@ -1073,11 +1089,12 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
@@ -1073,12 +1089,24 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
final Entity entity;
private final int range;
SectionPos lastSectionPos;
- public final Set<ServerPlayerConnection> seenBy = new it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet<>(); // Paper - Perf: optimise map impl
+ public final Set<ServerPlayerConnection> 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
+ public final Set<ServerPlayerConnection> 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 // Leaf - petal - Multithreaded tracker
+ public volatile ServerPlayerConnection[] seenByArray = EMPTY_OBJECT_ARRAY; // Leaf
// Paper start - optimise entity tracker
private long lastChunkUpdate = -1L;
private ca.spottedleaf.moonrise.common.misc.NearbyPlayers.TrackedChunk lastTrackedChunk;
+ public final Object sync = new Object(); // Leaf - Multithreaded tracker
+ public final Object sync = new Object(); // Leaf - Multithreaded tracker
+ // Leaf start - Multithreaded tracker
+ public static final ServerPlayerConnection[] EMPTY_OBJECT_ARRAY = new ServerPlayerConnection[0];
+ public ServerPlayerConnection[] seenBy() {
+ return seenByArray;
+ }
+ public void seenByUpdated() {
+ this.seenByArray = this.seenBy.toArray(EMPTY_OBJECT_ARRAY);
+ }
+ // Leaf end - Multithreaded tracker
+
@Override
public final void moonrise$tick(final ca.spottedleaf.moonrise.common.misc.NearbyPlayers.TrackedChunk chunk) {
@@ -1100,8 +1117,41 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
if (chunk == null) {
@@ -1100,27 +1128,95 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
this.lastTrackedChunk = chunk;
final ServerPlayer[] playersRaw = players.getRawDataUnchecked();
+ // Leaf start - Multithreaded tracker
+ 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) {
+ 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);
+ }
-
- for (int i = 0, len = players.size(); i < len; ++i) {
+ 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 {
+ final int playersLength = Math.min(playersRaw.length, players.size());
+ for (int i = 0; i < playersLength; ++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);
}
@@ -1115,6 +1165,8 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
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
+ 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
+ }
+ }
+
+ // 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<ServerPlayer> 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
}
+ // 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
if (!ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(conn.getPlayer())) {
foundToRemove = true;
break;
@@ -1131,12 +1227,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
ServerPlayer player = conn.getPlayer();
if (!ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(player)) {
- this.removePlayer(player);
+ this.removePlayerMulti(player); // Leaf
}
}
+ this.seenByUpdated(); // Leaf
}
@Override
@@ -1176,7 +1228,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
@@ -1146,10 +1243,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
ServerPlayer player = conn.getPlayer();
- this.removePlayer(player);
+ this.removePlayerMulti(player); // Leaf
}
+ this.seenByUpdated(); // Leaf
}
@Override
@@ -1176,7 +1274,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
+ for (ServerPlayerConnection serverPlayerConnection : this.seenBy()) { // Leaf - petal - Multithreaded tracker
serverPlayerConnection.send(packet);
}
}
@@ -1189,21 +1241,22 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
@@ -1189,21 +1287,34 @@ 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
+ for (ServerPlayerConnection serverPlayerConnection : this.seenBy()) { // Leaf - petal - Multithreaded tracker
this.serverEntity.removePairing(serverPlayerConnection.getPlayer());
}
}
+ // Leaf start
+ public boolean removePlayerMulti(ServerPlayer player) {
+ if (this.seenBy.remove(player.connection)) {
+ this.serverEntity.removePairing(player);
+ return true;
+ } else {
+ return false;
+ }
+ }
+ // Leaf end
+
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
}
public void updatePlayer(ServerPlayer player) {
@@ -171,6 +274,22 @@ index 5d9d233e3a568aa6297ed9c703fa450f98158602..4984e57d3b0806164111e79acfc82f6d
// 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 +1342,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
// CraftBukkit end
if (flag) {
if (this.seenBy.add(player.connection)) {
+ this.seenByUpdated(); // Leaf
// 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 +1351,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
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
@@ -185,10 +304,18 @@ index f106373ef3ac4a8685c2939c9e8361688a285913..51ae390c68e7a3aa193329cc3bc47ca6
public boolean visible = true;
diff --git a/net/minecraft/server/level/ServerEntity.java b/net/minecraft/server/level/ServerEntity.java
index d8298c7925e3bcea07ead4d438478cc51abcfa16..be3057119bcbce4a4f72284fa7ba8f60ba43f397 100644
index d8298c7925e3bcea07ead4d438478cc51abcfa16..e67d87b3043b381d27f75f37e3b7f922e18dcc2d 100644
--- a/net/minecraft/server/level/ServerEntity.java
+++ b/net/minecraft/server/level/ServerEntity.java
@@ -110,8 +110,16 @@ public class ServerEntity {
@@ -70,6 +70,7 @@ public class ServerEntity {
private boolean wasOnGround;
@Nullable
private List<SynchedEntityData.DataValue<?>> trackedDataValues;
+ public boolean wantSendDirtyEntityData = false; // Leaf - Multithreaded tracker
// CraftBukkit start
private final Set<net.minecraft.server.network.ServerPlayerConnection> trackedPlayers;
@@ -110,8 +111,16 @@ public class ServerEntity {
.forEach(
removedPassenger -> {
if (removedPassenger instanceof ServerPlayer serverPlayer1) {
@@ -207,41 +334,30 @@ index d8298c7925e3bcea07ead4d438478cc51abcfa16..be3057119bcbce4a4f72284fa7ba8f60
}
}
);
@@ -434,16 +442,33 @@ public class ServerEntity {
if (this.entity instanceof LivingEntity) {
Set<AttributeInstance> attributesToSync = ((LivingEntity)this.entity).getAttributes().getAttributesToSync();
+ // Leaf start - Multithreaded tracker
+ if (org.dreeam.leaf.config.modules.async.MultithreadedTracker.enabled) {
+ synchronized (attributesToSync) {
+ if (!attributesToSync.isEmpty()) {
+ // CraftBukkit start - Send scaled max health
+ if (this.entity instanceof ServerPlayer serverPlayer) {
+ serverPlayer.getBukkitEntity().injectScaledMaxHealth(attributesToSync, false);
+ }
+ // CraftBukkit end
+ this.broadcastAndSend(new ClientboundUpdateAttributesPacket(this.entity.getId(), attributesToSync));
+ }
+
+ attributesToSync.clear();
+ }
+ } else {
if (!attributesToSync.isEmpty()) {
// CraftBukkit start - Send scaled max health
if (this.entity instanceof ServerPlayer serverPlayer) {
- serverPlayer.getBukkitEntity().injectScaledMaxHealth(attributesToSync, false);
+ serverPlayer.getBukkitEntity().injectScaledMaxHealth(attributesToSync, false); // Leaf - Multithreaded tracker - diff on change
}
// CraftBukkit end
this.broadcastAndSend(new ClientboundUpdateAttributesPacket(this.entity.getId(), attributesToSync));
}
attributesToSync.clear();
+ }
+ // Leaf end - Multithreaded tracker
}
@@ -124,7 +133,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
final ServerPlayer serverPlayer = connection.getPlayer(); // Paper
savedData.tickCarriedBy(serverPlayer, item);
Packet<?> updatePacket = savedData.getUpdatePacket(mapId, serverPlayer);
@@ -424,7 +433,13 @@ public class ServerEntity {
return Mth.unpackDegrees(this.lastSentYHeadRot);
}
- private void sendDirtyEntityData() {
+ public void sendDirtyEntityData() { // Leaf - public
+ // Leaf start - Multithreaded tracker
+ if (Thread.currentThread() instanceof org.dreeam.leaf.async.tracker.MultithreadedTracker.MultithreadedTrackerThread) {
+ wantSendDirtyEntityData = true;
+ return;
+ }
+ // Leaf end - Multithreaded tracker
SynchedEntityData entityData = this.entity.getEntityData();
List<SynchedEntityData.DataValue<?>> list = entityData.packDirty();
if (list != null) {
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
@@ -277,98 +393,29 @@ index 04bf8bba0d8c0d5459605253dcc3f135bf43fd95..abe79d07196de0a10a382d4c37161c7e
// 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/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<AttributeModifier.Operation, Map<ResourceLocation, AttributeModifier>> modifiersByOperation = Maps.newEnumMap(
AttributeModifier.Operation.class
);
- private final Map<ResourceLocation, AttributeModifier> modifierById = new Object2ObjectArrayMap<>();
- private final Map<ResourceLocation, AttributeModifier> permanentModifiers = new Object2ObjectArrayMap<>();
+ // Leaf start - Multithreaded tracker
+ private final boolean multiThreadedTrackingEnabled = org.dreeam.leaf.config.modules.async.MultithreadedTracker.enabled;
+ private final Map<ResourceLocation, AttributeModifier> modifierById = multiThreadedTrackingEnabled ? it.unimi.dsi.fastutil.objects.Object2ObjectMaps.synchronize(new Object2ObjectArrayMap<>(), this) : new Object2ObjectArrayMap<>();
+ private final Map<ResourceLocation, AttributeModifier> 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 {
}
diff --git a/net/minecraft/world/entity/item/PrimedTnt.java b/net/minecraft/world/entity/item/PrimedTnt.java
index c96f458994818392857642282ec3d492124885da..1caf6846c5057f2c9bcd56ba9f217cc2863cd41b 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);
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;
- 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
+ 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);
- });
+ }
+ }
+ 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..d6e7685cb0e9beaa017bcc665f0f1c7c29c2ca5f 100644
--- a/net/minecraft/world/entity/ai/attributes/AttributeMap.java
+++ b/net/minecraft/world/entity/ai/attributes/AttributeMap.java
@@ -19,11 +19,11 @@ 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<Holder<Attribute>, AttributeInstance> attributes = new it.unimi.dsi.fastutil.objects.Reference2ReferenceOpenHashMap<>(0);
- private final Set<AttributeInstance> attributesToSync = new it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet<>(0);
- private final Set<AttributeInstance> attributesToUpdate = new it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet<>(0);
- // Gale end - Lithium - replace AI attributes with optimized collections
+ // Leaf start - Multithreaded tracker
+ private final Map<Holder<Attribute>, AttributeInstance> attributes;
+ private final Set<AttributeInstance> attributesToSync;
+ private final Set<AttributeInstance> attributesToUpdate;
+ // Leaf end - Multithreaded tracker
private final AttributeSupplier supplier;
private final java.util.function.Function<Holder<Attribute>, AttributeInstance> createInstance; // Gale - Airplane - reduce entity allocations
private final net.minecraft.world.entity.LivingEntity entity; // Purpur - Ridables
@@ -37,6 +37,17 @@ public class AttributeMap {
// Purpur end - Ridables
this.supplier = defaultAttributes;
this.createInstance = holder -> this.supplier.createInstance(this::onAttributeModified, holder); // Gale - Airplane - reduce entity allocations
+ // Leaf start - Multithreaded tracker
+ if (org.dreeam.leaf.config.modules.async.MultithreadedTracker.enabled) {
+ this.attributes = it.unimi.dsi.fastutil.objects.Reference2ReferenceMaps.synchronize(new it.unimi.dsi.fastutil.objects.Reference2ReferenceOpenHashMap<>(0));
+ this.attributesToSync = it.unimi.dsi.fastutil.objects.ReferenceSets.synchronize(new it.unimi.dsi.fastutil.objects.ReferenceArraySet<>(0));
+ this.attributesToUpdate = it.unimi.dsi.fastutil.objects.ReferenceSets.synchronize(new it.unimi.dsi.fastutil.objects.ReferenceArraySet<>(0));
+ } else {
+ this.attributes = new it.unimi.dsi.fastutil.objects.Reference2ReferenceOpenHashMap<>(0);
+ this.attributesToSync = new it.unimi.dsi.fastutil.objects.ReferenceArraySet<>(0);
+ this.attributesToUpdate = new it.unimi.dsi.fastutil.objects.ReferenceArraySet<>(0);
+ }
+ // Leaf end - Multithreaded tracker
}
private void onAttributeModified(AttributeInstance instance) {
+ // Leaf end
}
}
// 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

View File

@@ -5,10 +5,10 @@ Subject: [PATCH] Optimize addOrUpdateTransientModifier
diff --git a/net/minecraft/world/entity/ai/attributes/AttributeInstance.java b/net/minecraft/world/entity/ai/attributes/AttributeInstance.java
index ceff383d565267edd13a6d9006030b8e1f8053e3..7dae9cc18cd6eede8f1b2196b55103428f35382e 100644
index 8013594bb4844e7a8abf28123958e7f632d39341..7505485c8965e5492a9d68288596178cfe0971ee 100644
--- a/net/minecraft/world/entity/ai/attributes/AttributeInstance.java
+++ b/net/minecraft/world/entity/ai/attributes/AttributeInstance.java
@@ -88,8 +88,13 @@ public class AttributeInstance {
@@ -85,8 +85,13 @@ public class AttributeInstance {
}
public void addOrUpdateTransientModifier(AttributeModifier modifier) {

View File

@@ -34,10 +34,10 @@ index 98af1ad020a003db66d7319f33d43deec315aec5..9669036e6b7f1830888e48c99acb01d4
for (int i = 0; i < this.tickables.size(); i++) {
this.tickables.get(i).run();
diff --git a/net/minecraft/server/level/ServerEntity.java b/net/minecraft/server/level/ServerEntity.java
index be3057119bcbce4a4f72284fa7ba8f60ba43f397..65caf86832b3f33ad87208eb37b44ef2d8a2ec4e 100644
index e67d87b3043b381d27f75f37e3b7f922e18dcc2d..d64434d808d164ab1201f244058e2964860a590c 100644
--- a/net/minecraft/server/level/ServerEntity.java
+++ b/net/minecraft/server/level/ServerEntity.java
@@ -283,6 +283,7 @@ public class ServerEntity {
@@ -284,6 +284,7 @@ public class ServerEntity {
this.entity.hurtMarked = false;
this.broadcastAndSend(new ClientboundSetEntityMotionPacket(this.entity));
}

View File

@@ -0,0 +1,86 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: hayanesuru <hayanesuru@outlook.jp>
Date: Thu, 15 May 2025 21:11:18 +0900
Subject: [PATCH] optimize AttributeMap
diff --git a/net/minecraft/world/entity/ai/attributes/Attribute.java b/net/minecraft/world/entity/ai/attributes/Attribute.java
index f8419dde44ebc7324e783f8bee42132d5ec973c3..1c52a118bad6b4b8abdf2527347ef66888b71f54 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/AttributeMap.java b/net/minecraft/world/entity/ai/attributes/AttributeMap.java
index 89f4c5b2d61e27acd48063f9f24ce9ea91898b8b..19f7119256ac50ba8db961f179d3fabb9263944e 100644
--- a/net/minecraft/world/entity/ai/attributes/AttributeMap.java
+++ b/net/minecraft/world/entity/ai/attributes/AttributeMap.java
@@ -20,12 +20,12 @@ 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<Holder<Attribute>, AttributeInstance> attributes = new it.unimi.dsi.fastutil.objects.Reference2ReferenceOpenHashMap<>(0);
+ private final Map<Holder<Attribute>, AttributeInstance> attributes = new org.dreeam.leaf.util.map.AttributeInstanceArrayMap(); // Leaf - optimize AttributeMap
private final Set<AttributeInstance> attributesToSync = new it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet<>(0);
private final Set<AttributeInstance> attributesToUpdate = new it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet<>(0);
// Gale end - Lithium - replace AI attributes with optimized collections
private final AttributeSupplier supplier;
- private final java.util.function.Function<Holder<Attribute>, AttributeInstance> createInstance; // Gale - Airplane - reduce entity allocations
+ // private final java.util.function.Function<Holder<Attribute>, AttributeInstance> createInstance; // Gale - Airplane - reduce entity allocations
private final net.minecraft.world.entity.LivingEntity entity; // Purpur - Ridables
public AttributeMap(AttributeSupplier supplier) {
@@ -36,7 +36,7 @@ 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
}
private void onAttributeModified(AttributeInstance instance) {
@@ -60,7 +60,17 @@ public class AttributeMap {
@Nullable
public AttributeInstance getInstance(Holder<Attribute> 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 - optimize AttributeMap
+ AttributeInstance v;
+ if ((v = this.attributes.get(attribute)) == null) {
+ AttributeInstance newValue;
+ if ((newValue = this.supplier.createInstance(this::onAttributeModified, attribute)) != null) {
+ this.attributes.put(attribute, newValue);
+ return newValue;
+ }
+ }
+ return v;
+ // Leaf end - optimize AttributeMap
}
public boolean hasAttribute(Holder<Attribute> attribute) {
diff --git a/net/minecraft/world/entity/ai/attributes/AttributeSupplier.java b/net/minecraft/world/entity/ai/attributes/AttributeSupplier.java
index 24710041ccbc70e5506d8d89ae34f0141977f209..608edf272735d356215cf0147e65dcef391e1638 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<Holder<Attribute>, AttributeInstance> instances;
AttributeSupplier(Map<Holder<Attribute>, AttributeInstance> instances) {
- this.instances = instances;
+ this.instances = new org.dreeam.leaf.util.map.AttributeInstanceArrayMap(instances); // Leaf - optimize AttributeMap
}
public AttributeInstance getAttributeInstance(Holder<Attribute> attribute) {

View File

@@ -0,0 +1,25 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: hayanesuru <hayanesuru@outlook.jp>
Date: Sat, 17 May 2025 19:01:50 +0900
Subject: [PATCH] optimize getScaledTrackingDistance
diff --git a/net/minecraft/server/dedicated/DedicatedServer.java b/net/minecraft/server/dedicated/DedicatedServer.java
index eb0589b203bcf72cd24bb37f2c448c23cb8d6f2b..a1ec070b8620ba34bae9103f19307f42e6ed2a7c 100644
--- a/net/minecraft/server/dedicated/DedicatedServer.java
+++ b/net/minecraft/server/dedicated/DedicatedServer.java
@@ -838,7 +838,13 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface
@Override
public int getScaledTrackingDistance(int trackingDistance) {
- return this.getProperties().entityBroadcastRangePercentage * trackingDistance / 100;
+ // Leaf start
+ int p = this.getProperties().entityBroadcastRangePercentage;
+ if (p == 100) {
+ return trackingDistance;
+ }
+ return p * trackingDistance / 100;
+ // Leaf end
}
@Override

View File

@@ -0,0 +1,27 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: hayanesuru <hayanesuru@outlook.jp>
Date: Sat, 17 May 2025 19:03:31 +0900
Subject: [PATCH] optimize SynchedEntityData#packDirty
diff --git a/net/minecraft/network/syncher/SynchedEntityData.java b/net/minecraft/network/syncher/SynchedEntityData.java
index 3d90f9f1ac1bd281edf6bb0f93ea821657d5bd2f..546c36d0bc14b8db49245ff162be3dbc4d680da6 100644
--- a/net/minecraft/network/syncher/SynchedEntityData.java
+++ b/net/minecraft/network/syncher/SynchedEntityData.java
@@ -84,7 +84,15 @@ public class SynchedEntityData {
return null;
} else {
this.isDirty = false;
- List<SynchedEntityData.DataValue<?>> list = new ArrayList<>();
+ // Leaf start
+ int cap = 0;
+ for (SynchedEntityData.DataItem<?> dataItem : this.itemsById) {
+ if (dataItem.isDirty()) {
+ cap += 1;
+ }
+ }
+ ArrayList<SynchedEntityData.DataValue<?>> list = new ArrayList<>(cap);
+ // Leaf end
for (SynchedEntityData.DataItem<?> dataItem : this.itemsById) {
if (dataItem.isDirty()) {

View File

@@ -40,6 +40,37 @@ index 379c2dc1853e45a96dda9b13bf28b7e08f65658a..361f4de9cdf0f7505628a2fed2a3f536
throw new IllegalStateException(event.getEventName() + " may only be triggered synchronously.");
}
// Leaves start - skip photographer
diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java
index edcd209798740f31cb302f36d7864a0d8ea1d561..e2444cc9e28dd432bf3351066b1408102decfa0a 100644
--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java
+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java
@@ -749,7 +749,7 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity {
ChunkMap.TrackedEntity entityTracker = world.getChunkSource().chunkMap.entityMap.get(this.getEntityId());
if (entityTracker != null) {
- for (ServerPlayerConnection connection : entityTracker.seenBy) {
+ for (ServerPlayerConnection connection : entityTracker.seenBy()) { // Leaf
players.add(connection.getPlayer().getBukkitEntity());
}
}
@@ -1057,7 +1057,7 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity {
}
// Paper start - resend possibly desynced entity instead of add entity packet
- for (final ServerPlayerConnection connection : entityTracker.seenBy) {
+ for (final ServerPlayerConnection connection : entityTracker.seenBy()) { // Leaf
this.getHandle().resendPossiblyDesyncedEntityData(connection.getPlayer());
}
// Paper end - resend possibly desynced entity instead of add entity packet
@@ -1245,7 +1245,7 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity {
}
Set<org.bukkit.entity.Player> set = new java.util.HashSet<>(tracker.seenBy.size());
- for (net.minecraft.server.network.ServerPlayerConnection connection : tracker.seenBy) {
+ for (net.minecraft.server.network.ServerPlayerConnection connection : tracker.seenBy()) { // Leaf
set.add(connection.getPlayer().getBukkitEntity().getPlayer());
}
return set;
diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java
index e52479f3c888268fd1febeb78e9965af834a8ae9..c2552c3706831f7012b5b449fa43c7d5990056a4 100644
--- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java

View File

@@ -2,12 +2,14 @@ package org.dreeam.leaf.async.tracker;
import ca.spottedleaf.moonrise.common.list.ReferenceList;
import ca.spottedleaf.moonrise.common.misc.NearbyPlayers;
import ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel;
import ca.spottedleaf.moonrise.patches.chunk_system.level.entity.server.ServerEntityLookup;
import ca.spottedleaf.moonrise.patches.entity_tracker.EntityTrackerEntity;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import it.unimi.dsi.fastutil.objects.ReferenceArrayList;
import net.minecraft.Util;
import net.minecraft.server.level.ChunkMap;
import net.minecraft.server.level.FullChunkStatus;
import net.minecraft.server.level.ServerEntity;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.entity.Entity;
import org.apache.logging.log4j.LogManager;
@@ -49,11 +51,7 @@ public class MultithreadedTracker {
}
}
public static Executor getTrackerExecutor() {
return TRACKER_EXECUTOR;
}
public static void tick(ChunkSystemServerLevel level) {
public static void tick(ServerLevel level) {
try {
if (!org.dreeam.leaf.config.modules.async.MultithreadedTracker.compatModeEnabled) {
tickAsync(level);
@@ -65,7 +63,7 @@ public class MultithreadedTracker {
}
}
private static void tickAsync(ChunkSystemServerLevel level) {
private static void tickAsync(ServerLevel level) {
final NearbyPlayers nearbyPlayers = level.moonrise$getNearbyPlayers();
final ServerEntityLookup entityLookup = (ServerEntityLookup) level.moonrise$getEntityLookup();
@@ -74,6 +72,7 @@ public class MultithreadedTracker {
// Move tracking to off-main
TRACKER_EXECUTOR.execute(() -> {
ReferenceArrayList<ServerEntity> sendDirty = new ReferenceArrayList<>();
for (final Entity entity : trackerEntitiesRaw) {
if (entity == null) continue;
@@ -85,18 +84,26 @@ public class MultithreadedTracker {
synchronized (tracker.sync) {
tracker.moonrise$tick(nearbyPlayers.getChunk(entity.chunkPosition()));
tracker.serverEntity.sendChanges();
if (tracker.serverEntity.wantSendDirtyEntityData) {
tracker.serverEntity.wantSendDirtyEntityData = false;
sendDirty.add(tracker.serverEntity);
}
}
}
if (!sendDirty.isEmpty()) {
level.getServer().execute(() -> sendDirty.forEach(ServerEntity::sendDirtyEntityData));
}
});
}
private static void tickAsyncWithCompatMode(ChunkSystemServerLevel level) {
private static void tickAsyncWithCompatMode(ServerLevel level) {
final NearbyPlayers nearbyPlayers = level.moonrise$getNearbyPlayers();
final ServerEntityLookup entityLookup = (ServerEntityLookup) level.moonrise$getEntityLookup();
final ReferenceList<Entity> trackerEntities = entityLookup.trackerEntities;
final Entity[] trackerEntitiesRaw = trackerEntities.getRawDataUnchecked();
final Runnable[] sendChangesTasks = new Runnable[trackerEntitiesRaw.length];
final Runnable[] tickTask = new Runnable[trackerEntitiesRaw.length];
int index = 0;
for (final Entity entity : trackerEntitiesRaw) {
@@ -106,17 +113,40 @@ public class MultithreadedTracker {
if (tracker == null) continue;
tracker.moonrise$tick(nearbyPlayers.getChunk(entity.chunkPosition()));
sendChangesTasks[index++] = () -> tracker.serverEntity.sendChanges(); // Collect send changes to task array
synchronized (tracker.sync) {
tickTask[index] = tracker.leafTickCompact(nearbyPlayers.getChunk(entity.chunkPosition()));
sendChangesTasks[index] = () -> tracker.serverEntity.sendChanges(); // Collect send changes to task array
}
index++;
}
// batch submit tasks
TRACKER_EXECUTOR.execute(() -> {
for (final Runnable tick : tickTask) {
if (tick == null) continue;
tick.run();
}
for (final Runnable sendChanges : sendChangesTasks) {
if (sendChanges == null) continue;
sendChanges.run();
}
ReferenceArrayList<ServerEntity> sendDirty = new ReferenceArrayList<>();
for (final Entity entity : trackerEntitiesRaw) {
if (entity == null) continue;
final ChunkMap.TrackedEntity tracker = ((EntityTrackerEntity) entity).moonrise$getTrackedEntity();
if (tracker == null) continue;
if (tracker.serverEntity.wantSendDirtyEntityData) {
tracker.serverEntity.wantSendDirtyEntityData = false;
sendDirty.add(tracker.serverEntity);
}
}
if (!sendDirty.isEmpty()) {
level.getServer().execute(() -> sendDirty.forEach(ServerEntity::sendDirtyEntityData));
}
});
}
@@ -161,10 +191,11 @@ public class MultithreadedTracker {
private static @NotNull ThreadFactory getThreadFactory() {
return new ThreadFactoryBuilder()
.setThreadFactory(MultithreadedTrackerThread::new)
.setNameFormat(THREAD_PREFIX + " Thread - %d")
.setPriority(Thread.NORM_PRIORITY - 2)
.build();
.setThreadFactory(MultithreadedTrackerThread::new)
.setNameFormat(THREAD_PREFIX + " Thread - %d")
.setPriority(Thread.NORM_PRIORITY - 2)
.setUncaughtExceptionHandler(Util::onThreadException)
.build();
}
private static @NotNull RejectedExecutionHandler getRejectedPolicy() {

View File

@@ -151,7 +151,7 @@ public class DoABarrelRollProtocol implements Protocol {
}
var payload = new RollSyncS2CPacket(player.getId(), isRolling, roll);
var packet = Protocols.createPacket(payload);
for (ServerPlayerConnection seenBy : player.moonrise$getTrackedEntity().seenBy.toArray(new ServerPlayerConnection[0])) {
for (ServerPlayerConnection seenBy : player.moonrise$getTrackedEntity().seenBy()) {
if (seenBy instanceof ServerGamePacketListenerImpl conn
&& getHandshakeState(conn).state == HandshakeState.ACCEPTED) {
seenBy.send(packet);

View File

@@ -0,0 +1,311 @@
package org.dreeam.leaf.util.map;
import net.minecraft.core.Holder;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.world.entity.ai.attributes.Attribute;
import net.minecraft.world.entity.ai.attributes.AttributeInstance;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
import java.util.AbstractMap.SimpleEntry;
/// fast array backend map with O(1) get & put & remove
public class AttributeInstanceArrayMap implements Map<Holder<Attribute>, AttributeInstance>, Cloneable {
private int size = 0;
private transient AttributeInstance[] a = new AttributeInstance[32];
private transient KeySet keys;
private transient Values values;
private transient EntrySet entries;
public AttributeInstanceArrayMap() {
if (BuiltInRegistries.ATTRIBUTE.size() != 32) {
throw new IllegalStateException("Registered custom attribute");
}
}
public AttributeInstanceArrayMap(final @NotNull Map<Holder<Attribute>, AttributeInstance> m) {
this();
for (AttributeInstance e : m.values()) {
setByIndex(e.getAttribute().value().uid, e);
}
}
private void setByIndex(int index, @Nullable AttributeInstance instance) {
boolean empty = a[index] == null;
if (instance == null) {
if (!empty) {
size--;
a[index] = null;
}
} else {
if (empty) {
size++;
}
a[index] = instance;
}
}
@Override
public final int size() {
return size;
}
@Override
public final boolean isEmpty() {
return size == 0;
}
@Override
public final boolean containsKey(Object key) {
if (key instanceof Holder<?> holder && holder.value() instanceof Attribute attribute) {
int uid = attribute.uid;
return uid >= 0 && uid < a.length && a[uid] != null;
}
return false;
}
@Override
public final boolean containsValue(Object value) {
for (final AttributeInstance instance : a) {
if (Objects.equals(value, instance)) {
return true;
}
}
return false;
}
@Override
public final AttributeInstance get(Object key) {
return key instanceof Holder<?> holder && holder.value() instanceof Attribute attribute ? a[attribute.uid] : null;
}
@Override
public final AttributeInstance put(@NotNull Holder<Attribute> key, AttributeInstance value) {
int uid = key.value().uid;
AttributeInstance prev = a[uid];
setByIndex(uid, value);
return prev;
}
@Override
public final AttributeInstance remove(Object key) {
if (!(key instanceof Holder<?> holder) || !(holder.value() instanceof Attribute attribute)) return null;
int uid = attribute.uid;
AttributeInstance prev = a[uid];
setByIndex(uid, null);
return prev;
}
@Override
public final void putAll(@NotNull Map<? extends Holder<Attribute>, ? extends AttributeInstance> m) {
m.forEach(this::put);
}
@Override
public final void clear() {
Arrays.fill(a, null);
size = 0;
}
@Override
public final @NotNull Set<Holder<Attribute>> keySet() {
if (keys == null) {
keys = new KeySet();
}
return keys;
}
@Override
public final @NotNull Collection<AttributeInstance> values() {
if (values == null) {
values = new Values();
}
return values;
}
@Override
public final @NotNull Set<Entry<Holder<Attribute>, AttributeInstance>> entrySet() {
if (entries == null) {
entries = new EntrySet();
}
return entries;
}
@Override
public final boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Map<?, ?> other)) return false;
return entrySet().equals(other.entrySet());
}
@Override
public final int hashCode() {
return entrySet().hashCode();
}
@Override
public AttributeInstanceArrayMap clone() {
AttributeInstanceArrayMap c;
try {
c = (AttributeInstanceArrayMap) super.clone();
} catch (CloneNotSupportedException cantHappen) {
throw new InternalError();
}
c.a = a.clone();
c.entries = null;
c.keys = null;
c.values = null;
return c;
}
private final class KeySet extends AbstractSet<Holder<Attribute>> {
@Override
public @NotNull Iterator<Holder<Attribute>> iterator() {
return new KeyIterator();
}
@Override
public int size() {
return size;
}
@Override
public boolean contains(Object o) {
return AttributeInstanceArrayMap.this.containsKey(o);
}
}
private final class KeyIterator implements Iterator<Holder<Attribute>> {
private int currentIndex = -1;
private int nextIndex = findNextOccupied(0);
@Override
public boolean hasNext() {
return nextIndex != -1;
}
@Override
public Holder<Attribute> next() {
if (!hasNext()) throw new NoSuchElementException();
currentIndex = nextIndex;
nextIndex = findNextOccupied(nextIndex + 1);
return BuiltInRegistries.ATTRIBUTE.asHolderIdMap().byIdOrThrow(currentIndex);
}
@Override
public void remove() {
if (currentIndex == -1) throw new IllegalStateException();
setByIndex(currentIndex, null);
currentIndex = -1;
}
}
private final class Values extends AbstractCollection<AttributeInstance> {
@Override
public @NotNull Iterator<AttributeInstance> iterator() {
return new ValueIterator();
}
@Override
public int size() {
return size;
}
@Override
public boolean contains(Object o) {
return containsValue(o);
}
}
private final class ValueIterator implements Iterator<AttributeInstance> {
private int currentIndex = -1;
private int nextIndex = findNextOccupied(0);
@Override
public boolean hasNext() {
return nextIndex != -1;
}
@Override
public AttributeInstance next() {
if (!hasNext()) throw new NoSuchElementException();
currentIndex = nextIndex;
AttributeInstance value = a[nextIndex];
nextIndex = findNextOccupied(nextIndex + 1);
return value;
}
@Override
public void remove() {
if (currentIndex == -1) throw new IllegalStateException();
setByIndex(currentIndex, null);
currentIndex = -1;
}
}
private final class EntrySet extends AbstractSet<Entry<Holder<Attribute>, AttributeInstance>> {
@Override
public @NotNull Iterator<Entry<Holder<Attribute>, AttributeInstance>> iterator() {
return new EntryIterator();
}
@Override
public int size() {
return size;
}
@Override
public boolean contains(Object o) {
if (!(o instanceof Entry<?, ?> e)) {
return false;
}
return Objects.equals(get(e.getKey()), e.getValue());
}
}
private final class EntryIterator implements Iterator<Entry<Holder<Attribute>, AttributeInstance>> {
private int currentIndex = -1;
private int nextIndex = findNextOccupied(0);
@Override
public boolean hasNext() {
return nextIndex != -1;
}
@Override
public Entry<Holder<Attribute>, AttributeInstance> next() {
if (!hasNext()) throw new NoSuchElementException();
currentIndex = nextIndex;
Holder<Attribute> key = BuiltInRegistries.ATTRIBUTE.asHolderIdMap().byIdOrThrow(nextIndex);
AttributeInstance value = a[nextIndex];
nextIndex = findNextOccupied(nextIndex + 1);
return new SimpleEntry<>(key, value) {
@Override
public AttributeInstance setValue(AttributeInstance newValue) {
AttributeInstance old = put(key, newValue);
super.setValue(newValue);
return old;
}
};
}
@Override
public void remove() {
if (currentIndex == -1) {
throw new IllegalStateException();
}
setByIndex(currentIndex, null);
currentIndex = -1;
}
}
private int findNextOccupied(int start) {
for (int i = start; i < a.length; i++) {
if (a[i] != null) {
return i;
}
}
return -1;
}
}