9
0
mirror of https://github.com/Winds-Studio/Leaf.git synced 2025-12-19 15:09:25 +00:00
Files
Leaf/leaf-server/minecraft-patches/features/0166-Multithreaded-Tracker.patch
hayanesuru 6300dc3cfe Revert AI goal selector to vanilla behavior (#458)
* Revert AI goal selector to vanilla behavior

* remove config

* Remove config & Update patch comments

* rename

* re apply
2025-08-15 02:50:55 +08:00

1478 lines
80 KiB
Diff

From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: peaches94 <peachescu94@gmail.com>
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 <paul@technove.co>
Co-authored-by: Kevin Raneri <kevin.raneri@gmail.com>
Co-authored-by: HaHaWTH <102713261+HaHaWTH@users.noreply.github.com>
Co-authored-by: hayanesuru <hayanesuru@outlook.jp>
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
The core logic has beed reworked compared to the old one, can handle larger
scale situation better now.
Current impl includes many improvements and fixes we made, such as
plugin compat issues with some NPC plugins using real entity type,
e.g. Citizens.
However we still recommend to use those packet 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..2a2626e90836ae52a8a686b2040843c6644b6914 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<ServerPlayer, TrackedPlayer[]> players = new Reference2ReferenceOpenHashMap<>();
- private final Long2ReferenceOpenHashMap<TrackedChunk> byChunk = new Long2ReferenceOpenHashMap<>();
+ // Leaf start - Multithreaded tracker
+ private final it.unimi.dsi.fastutil.longs.Long2ReferenceMap<TrackedChunk> byChunk;
+ {
+ if (org.dreeam.leaf.config.modules.async.MultithreadedTracker.enabled) {
+ byChunk = it.unimi.dsi.fastutil.longs.Long2ReferenceMaps.synchronize(new Long2ReferenceOpenHashMap<>());
+ } else {
+ byChunk = new Long2ReferenceOpenHashMap<>();
+ }
+ }
+ // Leaf end - Multithreaded tracker
private final Long2ReferenceOpenHashMap<ReferenceList<ServerPlayer>>[] directByChunk = new Long2ReferenceOpenHashMap[TOTAL_MAP_TYPES];
{
for (int i = 0; i < this.directByChunk.length; ++i) {
@@ -164,6 +173,8 @@ public final class NearbyPlayers {
private int nonEmptyLists;
private long updateCount;
+ public final it.unimi.dsi.fastutil.objects.ReferenceSet<ServerPlayer> playersTracking = org.dreeam.leaf.config.modules.async.MultithreadedTracker.enabled ? it.unimi.dsi.fastutil.objects.ReferenceSets.synchronize(new it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet<>(1)) : null; // Leaf - Multithreaded tracker
+ public final java.util.concurrent.atomic.AtomicLong trackingUpdateCountAtomic = org.dreeam.leaf.config.modules.async.MultithreadedTracker.enabled ? new java.util.concurrent.atomic.AtomicLong(0) : null; // Leaf - Multithreaded tracker
public TrackedChunk(final long chunkKey, final NearbyPlayers nearbyPlayers) {
this.chunkKey = chunkKey;
this.nearbyPlayers = nearbyPlayers;
@@ -177,6 +188,12 @@ public final class NearbyPlayers {
return this.updateCount;
}
+ // Leaf start - Multithreaded tracker
+ public long getAtomicUpdateCount() {
+ return this.trackingUpdateCountAtomic.get() | 0x1000000000000000L;
+ }
+ // Leaf end - Multithreaded tracker
+
public ReferenceList<ServerPlayer> getPlayers(final NearbyMapType type) {
return this.players[type.ordinal()];
}
@@ -185,6 +202,12 @@ public final class NearbyPlayers {
++this.updateCount;
final int idx = type.ordinal();
+ // Leaf start - Multithreaded tracker
+ if (org.dreeam.leaf.config.modules.async.MultithreadedTracker.enabled && idx == NearbyMapType.VIEW_DISTANCE.ordinal()) {
+ this.trackingUpdateCountAtomic.getAndIncrement();
+ this.playersTracking.add(player);
+ }
+ // Leaf end - Multithreaded tracker
final ReferenceList<ServerPlayer> list = this.players[idx];
if (list == null) {
++this.nonEmptyLists;
@@ -203,6 +226,12 @@ public final class NearbyPlayers {
++this.updateCount;
final int idx = type.ordinal();
+ // Leaf start - Multithreaded tracker
+ if (org.dreeam.leaf.config.modules.async.MultithreadedTracker.enabled && type == NearbyMapType.VIEW_DISTANCE) {
+ this.trackingUpdateCountAtomic.getAndIncrement();
+ this.playersTracking.remove(player);
+ }
+ // Leaf end - Multithreaded tracker
final ReferenceList<ServerPlayer> list = this.players[idx];
if (list == null) {
throw new IllegalStateException("Does not contain player " + player);
diff --git a/ca/spottedleaf/moonrise/patches/chunk_system/player/RegionizedPlayerChunkLoader.java b/ca/spottedleaf/moonrise/patches/chunk_system/player/RegionizedPlayerChunkLoader.java
index bdc1200ef5317fdaf58973bf580b0a672aee800f..20b1186f53c267f69ed7852f5cf3dd2460f8200d 100644
--- a/ca/spottedleaf/moonrise/patches/chunk_system/player/RegionizedPlayerChunkLoader.java
+++ b/ca/spottedleaf/moonrise/patches/chunk_system/player/RegionizedPlayerChunkLoader.java
@@ -344,7 +344,7 @@ public final class RegionizedPlayerChunkLoader {
private boolean canGenerateChunks = true;
private final ArrayDeque<ChunkHolderManager.TicketOperation<?, ?>> delayedTicketOps = new ArrayDeque<>();
- private final LongOpenHashSet sentChunks = new LongOpenHashSet();
+ private final LongOpenHashSet sentChunks = org.dreeam.leaf.config.modules.async.MultithreadedTracker.enabled ? 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;
@@ -422,6 +422,14 @@ public final class RegionizedPlayerChunkLoader {
PlatformHooks.get().onChunkWatch(this.world, chunk, this.player);
PlayerChunkSender.sendChunk(this.player.connection, this.world, chunk);
+ // Leaf start - Multithreaded tracker
+ if (org.dreeam.leaf.config.modules.async.MultithreadedTracker.enabled) {
+ ca.spottedleaf.moonrise.common.misc.NearbyPlayers.TrackedChunk trackedChunk = this.world.moonrise$getNearbyPlayers().getChunk(chunkX, chunkZ);
+ if (trackedChunk != null) {
+ trackedChunk.trackingUpdateCountAtomic.getAndIncrement();
+ }
+ }
+ // Leaf end - Multithreaded tracker
return;
}
throw new IllegalStateException();
diff --git a/net/minecraft/network/protocol/game/ClientboundUpdateAttributesPacket.java b/net/minecraft/network/protocol/game/ClientboundUpdateAttributesPacket.java
index 9c0c99b936b4a82ebfe924866e53ec71f7bbe9ad..2ccff968cb2065d34fad4d27573f9e3081edb2f2 100644
--- a/net/minecraft/network/protocol/game/ClientboundUpdateAttributesPacket.java
+++ b/net/minecraft/network/protocol/game/ClientboundUpdateAttributesPacket.java
@@ -32,6 +32,7 @@ public class ClientboundUpdateAttributesPacket implements Packet<ClientGamePacke
this.attributes = Lists.newArrayList();
for (AttributeInstance attributeInstance : attributes) {
+ if (attributeInstance == null) continue; // Leaf - Multithreaded tracker
this.attributes
.add(
new ClientboundUpdateAttributesPacket.AttributeSnapshot(
diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java
index efb281f71ef5404fa40b7d04104b1c15d14e2c05..90409e9ef5cac2abe6748b723207c33224821194 100644
--- a/net/minecraft/server/MinecraftServer.java
+++ b/net/minecraft/server/MinecraftServer.java
@@ -1732,6 +1732,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
}
this.isIteratingOverLevels = false; // Paper - Throw exception on world create while being ticked
+ if (org.dreeam.leaf.config.modules.async.MultithreadedTracker.enabled) { org.dreeam.leaf.async.tracker.AsyncTracker.onTickEnd(this); } // Leaf - Multithreaded tracker
this.tickConnection();
this.playerList.tick();
if (this.tickRateManager.runsNormally()) {
@@ -1809,6 +1810,11 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
Map<ResourceKey<Level>, ServerLevel> oldLevels = this.levels;
Map<ResourceKey<Level>, ServerLevel> newLevels = Maps.newLinkedHashMap(oldLevels);
newLevels.remove(level.dimension());
+ // Leaf start - Multithreaded tracker
+ if (org.dreeam.leaf.config.modules.async.MultithreadedTracker.enabled) {
+ level.trackerTask = null;
+ }
+ // Leaf end - Multithreaded tracker
this.levels = Collections.unmodifiableMap(newLevels);
}
// CraftBukkit end
diff --git a/net/minecraft/server/level/ChunkMap.java b/net/minecraft/server/level/ChunkMap.java
index 74d11e8983f12f6f33fe2eb3016730507e1031d4..42555d9fc1b37696b8da4c85f9a809819bcc6dde 100644
--- a/net/minecraft/server/level/ChunkMap.java
+++ b/net/minecraft/server/level/ChunkMap.java
@@ -1013,6 +1013,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 ServerLevel level = this.level;
+ org.dreeam.leaf.async.tracker.AsyncTracker.tick(level);
+ return;
+ }
+ // Leaf end - Multithreaded tracker
// Paper start - optimise entity tracker
if (true) {
this.newTrackerTick();
@@ -1135,12 +1142,60 @@ 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
+ // Leaf start - Multithreaded tracker
+ public static final ServerPlayerConnection[] EMPTY_OBJECT_ARRAY = new ServerPlayerConnection[0];
+
+ private class SeenBySet extends it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet<ServerPlayerConnection> {
+ @Override
+ public boolean add(ServerPlayerConnection serverPlayerConnection) {
+ if (super.add(serverPlayerConnection)) {
+ // for plugin compatibility
+ TrackedEntity.this.seenByUpdated = true;
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public boolean remove(Object k) {
+ if (super.remove(k)) {
+ // for plugin compatibility
+ TrackedEntity.this.seenByUpdated = true;
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public void clear() {
+ TrackedEntity.this.seenByUpdated = true;
+ super.clear();
+ }
+ }
+
+ public final Set<ServerPlayerConnection> seenBy = org.dreeam.leaf.config.modules.async.MultithreadedTracker.enabled ? it.unimi.dsi.fastutil.objects.ReferenceSets.synchronize(new SeenBySet()) : new SeenBySet(); // Paper - Perf: optimise map impl
+ private volatile boolean seenByUpdated = false;
+ 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;
+ }
+ // Leaf end - Multithreaded tracker
// Paper start - optimise entity tracker
private long lastChunkUpdate = -1L;
private ca.spottedleaf.moonrise.common.misc.NearbyPlayers.TrackedChunk lastTrackedChunk;
+ // Leaf - Multithreaded tracker - diff on change
@Override
public final void moonrise$tick(final ca.spottedleaf.moonrise.common.misc.NearbyPlayers.TrackedChunk chunk) {
if (chunk == null) {
@@ -1150,6 +1205,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
final ca.spottedleaf.moonrise.common.list.ReferenceList<ServerPlayer> players = chunk.getPlayers(ca.spottedleaf.moonrise.common.misc.NearbyPlayers.NearbyMapType.VIEW_DISTANCE);
+ // Leaf - Multithreaded tracker - diff on change
if (players == null) {
this.moonrise$clearPlayers();
return;
@@ -1162,27 +1218,120 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
this.lastTrackedChunk = chunk;
final ServerPlayer[] playersRaw = players.getRawDataUnchecked();
-
- for (int i = 0, len = players.size(); i < len; ++i) {
+ // Leaf start - Multithreaded tracker
+ final int playersLength = Math.min(playersRaw.length, players.size());
+ boolean updated = false;
+ for (int i = 0; i < playersLength; ++i) {
final ServerPlayer player = playersRaw[i];
- this.updatePlayer(player);
+ updated |= this.updatePlayerMulti(player);
}
+ if (updated) seenByUpdated();
+ // Leaf end - Multithreaded tracker
if (lastChunkUpdate != currChunkUpdate || lastTrackedChunk != chunk) {
// need to purge any players possible not in the chunk list
- for (final ServerPlayerConnection conn : new java.util.ArrayList<>(this.seenBy)) {
+ // Leaf start - Multithreaded tracker
+ boolean removed = false;
+ for (final ServerPlayerConnection conn : this.seenBy()) {
final ServerPlayer player = conn.getPlayer();
if (!players.contains(player)) {
- this.removePlayer(player);
+ removed |= this.removePlayerMulti(player);
}
}
+ if (removed) this.seenByUpdated();
+ // Leaf end - Multithreaded tracker
}
}
+ // Leaf start - Multithreaded tracker
+ public final void leafTick(final org.dreeam.leaf.async.tracker.TrackerCtx ctx, final ca.spottedleaf.moonrise.common.misc.NearbyPlayers.TrackedChunk chunk) {
+ if (chunk == null || chunk.playersTracking.isEmpty()) {
+ this.lastChunkUpdate = -1L;
+ this.lastTrackedChunk = null;
+ if (!this.seenBy.isEmpty()) {
+ for (final ServerPlayerConnection conn : this.seenBy()) {
+ if (this.seenBy.remove(conn)) {
+ ctx.stopSeenByPlayer(conn, this.entity);
+ ctx.send(conn, new net.minecraft.network.protocol.game.ClientboundRemoveEntitiesPacket(this.entity.getId()));
+ }
+ }
+ this.seenByUpdated();
+ }
+ return;
+ }
+
+ final it.unimi.dsi.fastutil.objects.ReferenceSet<ServerPlayer> players = chunk.playersTracking;
+ final long currChunkUpdate = chunk.getAtomicUpdateCount();
+ final boolean chunkStateChanged = this.lastChunkUpdate != currChunkUpdate || this.lastTrackedChunk != chunk;
+ this.lastChunkUpdate = currChunkUpdate;
+ this.lastTrackedChunk = chunk;
+
+ if (!(chunkStateChanged || ((entity.tickCount + entity.getId()) & 15) == 15)) {
+ return;
+ }
+
+ boolean updated = false;
+ final double ex = this.entity.getX();
+ final double ey = this.entity.getY();
+ final double ez = this.entity.getZ();
+ final int eChunkX = this.entity.chunkPosition().x;
+ final int eChunkZ = this.entity.chunkPosition().z;
+ final double effectiveRange = this.getEffectiveRange();
+ final double rangeSqr = effectiveRange * effectiveRange;
+ final double rangeY = level.paperConfig().entities.trackingRangeY.enabled ? level.paperConfig().entities.trackingRangeY.get(this.entity, -1) : -1;
+ final double rangeYSqr = (rangeY > 0.0) ? (rangeY * rangeY) : 0.0;
+ synchronized (players) {
+ for (ServerPlayer player : players) {
+ if (player == this.entity) {
+ continue;
+ }
+ final double dx = player.getX() - ex;
+ final double dz = player.getZ() - ez;
+ final double dy = player.getY() - ey;
+ final double playerViewDistance = ChunkMap.this.getPlayerViewDistance(player);
+ final boolean flag = ((dx * dx + dz * dz) <= Math.min(rangeSqr, playerViewDistance * playerViewDistance * 256.0))
+ && ((rangeYSqr == 0.0) || ((dy * dy) <= rangeYSqr))
+ && this.entity.broadcastToPlayer(player)
+ && ChunkMap.this.isChunkTracked(player, eChunkX, eChunkZ)
+ && player.getBukkitEntity().canSeeChunkMapUpdatePlayer(this.entity.getBukkitEntity());
+ if (flag) {
+ if (this.seenBy.add(player.connection)) {
+ this.serverEntity.leafAddPairing(ctx, player);
+ this.serverEntity.onPlayerAdd();
+ updated = true;
+ }
+ } else if (this.seenBy.remove(player.connection)) {
+ ctx.stopSeenByPlayer(player.connection, this.entity);
+ ctx.send(player.connection, new net.minecraft.network.protocol.game.ClientboundRemoveEntitiesPacket(this.entity.getId()));
+ updated = true;
+ }
+ }
+ }
+ if (updated) {
+ this.seenByUpdated();
+ }
+ if (!chunkStateChanged) {
+ return;
+ }
+ updated = false;
+ for (final ServerPlayerConnection conn : this.seenBy()) {
+ final ServerPlayer player = conn.getPlayer();
+ if (!players.contains(player) && this.seenBy.remove(conn)) {
+ ctx.stopSeenByPlayer(conn, this.entity);
+ ctx.send(conn, new net.minecraft.network.protocol.game.ClientboundRemoveEntitiesPacket(this.entity.getId()));
+ updated = true;
+ }
+ }
+ if (updated) {
+ this.seenByUpdated();
+ }
+ }
+ // Leaf end - Multithreaded tracker
+
@Override
public final void moonrise$removeNonTickThreadPlayers() {
boolean foundToRemove = false;
- for (final ServerPlayerConnection conn : this.seenBy) {
+ for (final ServerPlayerConnection conn : this.seenBy()) { // Leaf - Multithreaded tracker
if (!ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(conn.getPlayer())) {
foundToRemove = true;
break;
@@ -1193,12 +1342,13 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
return;
}
- for (final ServerPlayerConnection conn : new java.util.ArrayList<>(this.seenBy)) {
+ for (final ServerPlayerConnection conn : this.seenBy()) { // Leaf - Multithreaded tracker
ServerPlayer player = conn.getPlayer();
if (!ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(player)) {
- this.removePlayer(player);
+ this.removePlayerMulti(player); // Leaf - Multithreaded tracker
}
}
+ this.seenByUpdated(); // Leaf - Multithreaded tracker
}
@Override
@@ -1208,10 +1358,11 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
if (this.seenBy.isEmpty()) {
return;
}
- for (final ServerPlayerConnection conn : new java.util.ArrayList<>(this.seenBy)) {
+ for (final ServerPlayerConnection conn : this.seenBy()) { // Leaf - Multithreaded tracker
ServerPlayer player = conn.getPlayer();
- this.removePlayer(player);
+ this.removePlayerMulti(player); // Leaf - Multithreaded tracker
}
+ this.seenByUpdated(); // Leaf - Multithreaded tracker
}
@Override
@@ -1238,20 +1389,46 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
}
public void broadcast(Packet<?> packet) {
- for (ServerPlayerConnection serverPlayerConnection : this.seenBy) {
+ // Leaf - Multithreaded tracker - diff on change
+ for (ServerPlayerConnection serverPlayerConnection : this.seenBy()) { // Leaf - petal - Multithreaded tracker
serverPlayerConnection.send(packet);
}
}
public void broadcastIgnorePlayers(Packet<?> packet, List<UUID> ignoredPlayers) {
- for (ServerPlayerConnection serverPlayerConnection : this.seenBy) {
+ // Leaf - Multithreaded tracker - diff on change
+ for (ServerPlayerConnection serverPlayerConnection : this.seenBy()) {
if (!ignoredPlayers.contains(serverPlayerConnection.getPlayer().getUUID())) {
serverPlayerConnection.send(packet);
}
}
}
+ // Leaf start - Multithreaded tracker
+ public void leafBroadcast(org.dreeam.leaf.async.tracker.TrackerCtx ctx, Packet<? super net.minecraft.network.protocol.game.ClientGamePacketListener> packet) {
+ for (ServerPlayerConnection serverPlayerConnection : this.seenBy()) {
+ ctx.send(serverPlayerConnection, packet);
+ }
+ }
+
+ public void leafBroadcastIgnorePlayers(org.dreeam.leaf.async.tracker.TrackerCtx ctx, Packet<? super net.minecraft.network.protocol.game.ClientGamePacketListener> packet, List<UUID> ignoredPlayers) {
+ for (ServerPlayerConnection serverPlayerConnection : this.seenBy()) {
+ if (!ignoredPlayers.contains(serverPlayerConnection.getPlayer().getUUID())) {
+ ctx.send(serverPlayerConnection, packet);
+ }
+ }
+ }
+
+ public void leafBroadcastAndSend(org.dreeam.leaf.async.tracker.TrackerCtx ctx, Packet<? super net.minecraft.network.protocol.game.ClientGamePacketListener> packet) {
+ leafBroadcast(ctx, packet);
+ if (this.entity instanceof ServerPlayer serverPlayer) {
+ ctx.send(serverPlayer.connection, packet);
+ }
+ }
+ // Leaf end - Multithreaded tracker
+
public void broadcastAndSend(Packet<?> packet) {
+ // Leaf - Multithreaded tracker - diff on change
this.broadcast(packet);
if (this.entity instanceof ServerPlayer) {
((ServerPlayer)this.entity).connection.send(packet);
@@ -1259,21 +1436,34 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
}
public void broadcastRemoved() {
- for (ServerPlayerConnection serverPlayerConnection : this.seenBy) {
+ for (ServerPlayerConnection serverPlayerConnection : this.seenBy()) { // Leaf - petal - Multithreaded tracker
this.serverEntity.removePairing(serverPlayerConnection.getPlayer());
}
}
+ // Leaf start - Multithreaded tracker
+ public boolean removePlayerMulti(ServerPlayer player) {
+ if (this.seenBy.remove(player.connection)) {
+ this.serverEntity.removePairing(player);
+ return true;
+ } else {
+ return false;
+ }
+ }
+ // Leaf end - Multithreaded tracker
+
public void removePlayer(ServerPlayer player) {
- org.spigotmc.AsyncCatcher.catchOp("player tracker clear"); // Spigot
+ //org.spigotmc.AsyncCatcher.catchOp("player tracker clear"); // Spigot // Leaf - petal - Multithreaded tracker - We can remove async too
if (this.seenBy.remove(player.connection)) {
this.serverEntity.removePairing(player);
+ this.seenByUpdated(); // Leaf - Multithreaded tracker
}
}
public void updatePlayer(ServerPlayer player) {
- org.spigotmc.AsyncCatcher.catchOp("player tracker update"); // Spigot
+ //org.spigotmc.AsyncCatcher.catchOp("player tracker update"); // Spigot // Leaf - petal - Multithreaded tracker - We can update async
if (player != this.entity) {
+ if (org.dreeam.leaf.config.modules.async.MultithreadedTracker.enabled && player == null) return; // Leaf - Multithreaded tracker
// Paper start - remove allocation of Vec3D here
// Vec3 vec3 = player.position().subtract(this.entity.position());
double vec3_dx = player.getX() - this.entity.getX();
@@ -1301,6 +1491,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
// CraftBukkit end
if (flag) {
if (this.seenBy.add(player.connection)) {
+ this.seenByUpdated(); // Leaf - Multithreaded tracker
// Paper start - entity tracking events
if (io.papermc.paper.event.player.PlayerTrackEntityEvent.getHandlerList().getRegisteredListeners().length == 0 || new io.papermc.paper.event.player.PlayerTrackEntityEvent(player.getBukkitEntity(), this.entity.getBukkitEntity()).callEvent()) {
this.serverEntity.addPairing(player);
@@ -1309,11 +1500,60 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
this.serverEntity.onPlayerAdd(); // Paper - fix desync when a player is added to the tracker
}
} else if (this.seenBy.remove(player.connection)) {
+ this.seenByUpdated(); // Leaf - Multithreaded tracker
this.serverEntity.removePairing(player);
}
}
}
+ // Leaf start - Multithreaded tracker
+ private boolean updatePlayerMulti(ServerPlayer player) {
+ //org.spigotmc.AsyncCatcher.catchOp("player tracker update"); // Spigot // Leaf - petal - Multithreaded tracker - We can update async
+ if (player != this.entity) {
+ // Paper start - remove allocation of Vec3D here
+ // Vec3 vec3 = player.position().subtract(this.entity.position());
+ double vec3_dx = player.getX() - this.entity.getX();
+ double vec3_dz = player.getZ() - this.entity.getZ();
+ // Paper end - remove allocation of Vec3D here
+ int playerViewDistance = ChunkMap.this.getPlayerViewDistance(player);
+ double d = Math.min(this.getEffectiveRange(), playerViewDistance * 16);
+ double d1 = vec3_dx * vec3_dx + vec3_dz * vec3_dz; // Paper
+ double d2 = d * d;
+ // Paper start - Configurable entity tracking range by Y
+ boolean flag = d1 <= d2;
+ if (flag && level.paperConfig().entities.trackingRangeY.enabled) {
+ double rangeY = level.paperConfig().entities.trackingRangeY.get(this.entity, -1);
+ if (rangeY != -1) {
+ double vec3_dy = player.getY() - this.entity.getY();
+ flag = vec3_dy * vec3_dy <= rangeY * rangeY;
+ }
+ }
+ flag = flag && this.entity.broadcastToPlayer(player) && ChunkMap.this.isChunkTracked(player, this.entity.chunkPosition().x, this.entity.chunkPosition().z);
+ // Paper end - Configurable entity tracking range by Y
+ // CraftBukkit start - respect vanish API
+ if (flag && !player.getBukkitEntity().canSeeChunkMapUpdatePlayer(this.entity.getBukkitEntity())) { // Paper - only consider hits // SparklyPaper - optimize canSee checks
+ flag = false;
+ }
+ // CraftBukkit end
+ if (flag) {
+ if (this.seenBy.add(player.connection)) {
+ // 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);
+ }
+ // Paper end - entity tracking events
+ this.serverEntity.onPlayerAdd(); // Paper - fix desync when a player is added to the tracker
+ return true;
+ }
+ } else if (this.seenBy.remove(player.connection)) {
+ this.serverEntity.removePairing(player);
+ return true;
+ }
+ }
+ return false;
+ }
+ // Leaf end - Multithreaded tracker
+
private int scaledRange(int trackingDistance) {
return ChunkMap.this.level.getServer().getScaledTrackingDistance(trackingDistance);
}
@@ -1339,10 +1579,14 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
// Paper end - optimise entity tracker
}
+ // Leaf start - Multithreaded tracker
public void updatePlayers(List<ServerPlayer> playersList) {
+ boolean updated = false;
for (ServerPlayer serverPlayer : playersList) {
- this.updatePlayer(serverPlayer);
+ updated |= this.updatePlayerMulti(serverPlayer);
}
+ if (updated) seenByUpdated();
}
+ // Leaf end - Multithreaded tracker
}
}
diff --git a/net/minecraft/server/level/ServerBossEvent.java b/net/minecraft/server/level/ServerBossEvent.java
index f106373ef3ac4a8685c2939c9e8361688a285913..7df467924d029d6e42b1d0b039cb9272bb068c4d 100644
--- a/net/minecraft/server/level/ServerBossEvent.java
+++ b/net/minecraft/server/level/ServerBossEvent.java
@@ -105,6 +105,20 @@ public class ServerBossEvent extends BossEvent {
}
}
+ // Leaf start - Multithreaded tracker
+ public void leafAddPlayer(org.dreeam.leaf.async.tracker.TrackerCtx ctx, ServerPlayer player) {
+ if (this.players.add(player) && this.visible) {
+ ctx.send(player.connection, ClientboundBossEventPacket.createAddPacket(this));
+ }
+ }
+
+ public void leafRemovePlayer(org.dreeam.leaf.async.tracker.TrackerCtx ctx, ServerPlayer player) {
+ if (this.players.remove(player) && this.visible) {
+ ctx.send(player.connection, ClientboundBossEventPacket.createRemovePacket(this.getId()));
+ }
+ }
+ // Leaf end - Multithreaded tracker
+
public void removeAllPlayers() {
if (!this.players.isEmpty()) {
for (ServerPlayer serverPlayer : Lists.newArrayList(this.players)) {
diff --git a/net/minecraft/server/level/ServerEntity.java b/net/minecraft/server/level/ServerEntity.java
index 0dee18df07c979da6125a4e7a955343e44d67ac2..dae07c6c31949c75c810c9260232cccc8160b606 100644
--- a/net/minecraft/server/level/ServerEntity.java
+++ b/net/minecraft/server/level/ServerEntity.java
@@ -57,11 +57,13 @@ public class ServerEntity {
public static final int FORCED_POS_UPDATE_PERIOD = 60;
private static final int FORCED_TELEPORT_PERIOD = 400;
private final ServerLevel level;
+ // Leaf - Multithreaded tracker - diff on change
private final Entity entity;
private final int updateInterval;
private final boolean trackDelta;
private final Consumer<Packet<?>> broadcast;
private final BiConsumer<Packet<?>, List<UUID>> broadcastWithIgnore;
+ // Leaf - Multithreaded tracker - diff on change
private final VecDeltaCodec positionCodec = new VecDeltaCodec();
private byte lastSentYRot;
private byte lastSentXRot;
@@ -94,6 +96,7 @@ public class ServerEntity {
this.updateInterval = updateInterval;
this.trackDelta = trackDelta;
this.broadcastWithIgnore = broadcastWithIgnore;
+ // Leaf - Multithreaded tracker - diff on change
this.positionCodec.setBase(entity.trackingPosition());
this.lastSentMovement = entity.getDeltaMovement();
this.lastSentYRot = Mth.packDegrees(entity.getYRot());
@@ -105,204 +108,415 @@ public class ServerEntity {
// Paper start - fix desync when a player is added to the tracker
private boolean forceStateResync;
+ // Leaf - Multithreaded tracker - diff on change
public void onPlayerAdd() {
this.forceStateResync = true;
}
// Paper end - fix desync when a player is added to the tracker
+ // Leaf - Multithreaded tracker - diff on change
public void sendChanges() {
// Paper start - optimise collisions
if (((ca.spottedleaf.moonrise.patches.chunk_system.entity.ChunkSystemEntity)this.entity).moonrise$isHardColliding()) {
this.teleportDelay = 9999;
}
+ // Leaf - Multithreaded tracker - diff on change
// Paper end - optimise collisions
List<Entity> passengers = this.entity.getPassengers();
+ // Leaf - Multithreaded tracker - diff on change
if (!passengers.equals(this.lastPassengers)) {
// Leaf start - Remove stream in entity mountedOrDismounted changes update
List<UUID> list = new ArrayList<>();
for (Entity entity : this.lastPassengers) {
+ // Leaf - Multithreaded tracker - diff on change
if (!passengers.contains(entity)) {
list.add(entity.getUUID());
}
}
+ // Leaf - Multithreaded tracker - diff on change
for (Entity entity : passengers) {
if (!this.lastPassengers.contains(entity)) {
list.add(entity.getUUID());
}
+ // Leaf - Multithreaded tracker - diff on change
}
// Leaf end - Remove stream in entity mountedOrDismounted changes update
this.broadcastWithIgnore.accept(new ClientboundSetPassengersPacket(this.entity), list);
// Paper start - Allow riding players
+ // Leaf - Multithreaded tracker - diff on change
if (this.entity instanceof ServerPlayer player) {
player.connection.send(new ClientboundSetPassengersPacket(this.entity));
}
// Paper end - Allow riding players
this.lastPassengers = passengers;
+ // Leaf - Multithreaded tracker - diff on change
}
+ // Leaf - Multithreaded tracker - diff on change
if (!this.trackedPlayers.isEmpty() && this.entity instanceof ItemFrame itemFrame /*&& this.tickCount % 10 == 0*/) { // CraftBukkit - moved tickCount below // Paper - Perf: Only tick item frames if players can see it
ItemStack item = itemFrame.getItem();
if (this.level.paperConfig().maps.itemFrameCursorUpdateInterval > 0 && this.tickCount % this.level.paperConfig().maps.itemFrameCursorUpdateInterval == 0 && item.getItem() instanceof MapItem) { // CraftBukkit - Moved this.tickCounter % 10 logic here so item frames do not enter the other blocks // Paper - Make item frame map cursor update interval configurable
MapId mapId = itemFrame.cachedMapId; // Paper - Perf: Cache map ids on item frames
MapItemSavedData savedData = MapItem.getSavedData(mapId, this.level);
+ // Leaf - Multithreaded tracker - diff on change
if (savedData != null) {
for (final net.minecraft.server.network.ServerPlayerConnection connection : this.trackedPlayers) { // Paper
final ServerPlayer serverPlayer = connection.getPlayer(); // Paper
savedData.tickCarriedBy(serverPlayer, item);
Packet<?> updatePacket = savedData.getUpdatePacket(mapId, serverPlayer);
+ // Leaf - Multithreaded tracker - diff on change
if (updatePacket != null) {
serverPlayer.connection.send(updatePacket);
}
}
}
+ // Leaf - Multithreaded tracker - diff on change
}
+ // Leaf - Multithreaded tracker - diff on change
this.sendDirtyEntityData();
}
+ // Leaf - Multithreaded tracker - diff on change
if (this.forceStateResync || this.tickCount % this.updateInterval == 0 || this.entity.hasImpulse || this.entity.getEntityData().isDirty()) { // Paper - fix desync when a player is added to the tracker
byte b = Mth.packDegrees(this.entity.getYRot());
byte b1 = Mth.packDegrees(this.entity.getXRot());
boolean flag = Math.abs(b - this.lastSentYRot) >= 1 || Math.abs(b1 - this.lastSentXRot) >= 1;
+ // Leaf - Multithreaded tracker - diff on change
if (this.entity.isPassenger()) {
if (flag) {
+ // Leaf - Multithreaded tracker - diff on change
this.broadcast.accept(new ClientboundMoveEntityPacket.Rot(this.entity.getId(), b, b1, this.entity.onGround()));
this.lastSentYRot = b;
this.lastSentXRot = b1;
}
+ // Leaf - Multithreaded tracker - diff on change
+ // Leaf - Multithreaded tracker - diff on change
this.positionCodec.setBase(this.entity.trackingPosition());
this.sendDirtyEntityData();
this.wasRiding = true;
} else if (this.entity instanceof AbstractMinecart abstractMinecart
&& abstractMinecart.getBehavior() instanceof NewMinecartBehavior newMinecartBehavior) {
+ // Leaf - Multithreaded tracker - diff on change
this.handleMinecartPosRot(newMinecartBehavior, b, b1, flag);
} else {
this.teleportDelay++;
Vec3 vec3 = this.entity.trackingPosition();
+ // Leaf - Multithreaded tracker - diff on change
// Paper start - reduce allocation of Vec3D here
Vec3 base = this.positionCodec.base;
double vec3_dx = vec3.x - base.x;
double vec3_dy = vec3.y - base.y;
double vec3_dz = vec3.z - base.z;
+ // Leaf - Multithreaded tracker - diff on change
boolean flag1 = (vec3_dx * vec3_dx + vec3_dy * vec3_dy + vec3_dz * vec3_dz) >= 7.62939453125E-6D;
// Paper end - reduce allocation of Vec3D here
Packet<?> packet = null;
+ // Leaf - Multithreaded tracker - diff on change
boolean flag2 = flag1 || this.tickCount % 60 == 0;
boolean flag3 = false;
boolean flag4 = false;
long l = this.positionCodec.encodeX(vec3);
long l1 = this.positionCodec.encodeY(vec3);
long l2 = this.positionCodec.encodeZ(vec3);
+ // Leaf - Multithreaded tracker - diff on change
boolean flag5 = l < -32768L || l > 32767L || l1 < -32768L || l1 > 32767L || l2 < -32768L || l2 > 32767L;
if (this.forceStateResync || this.entity.getRequiresPrecisePosition() // Paper - fix desync when a player is added to the tracker
|| flag5
|| this.teleportDelay > 400
|| this.wasRiding
|| this.wasOnGround != this.entity.onGround()) {
+ // Leaf - Multithreaded tracker - diff on change
this.wasOnGround = this.entity.onGround();
this.teleportDelay = 0;
packet = ClientboundEntityPositionSyncPacket.of(this.entity);
flag3 = true;
flag4 = true;
+ // Leaf - Multithreaded tracker - diff on change
// Gale start - Airplane - better checking for useless move packets
} else {
+ // Leaf - Multithreaded tracker - diff on change
if (flag2 || flag || this.entity instanceof AbstractArrow) {
if ((!flag2 || !flag) && !(this.entity instanceof AbstractArrow)) {
+ // Leaf - Multithreaded tracker - diff on change
if (flag2) {
packet = new ClientboundMoveEntityPacket.Pos(this.entity.getId(), (short) l, (short) l1, (short) l2, this.entity.onGround());
flag3 = true;
} else if (flag) {
+ // Leaf - Multithreaded tracker - diff on change
packet = new ClientboundMoveEntityPacket.Rot(this.entity.getId(), b, b1, this.entity.onGround());
flag4 = true;
}
} else {
+ // Leaf - Multithreaded tracker - diff on change
packet = new ClientboundMoveEntityPacket.PosRot(this.entity.getId(), (short) l, (short) l1, (short) l2, b, b1, this.entity.onGround());
flag3 = true;
flag4 = true;
}
}
}
+ // Leaf - Multithreaded tracker - diff on change
// Gale end - Airplane - better checking for useless move packets
if (org.dreeam.leaf.config.modules.opt.ReduceUselessPackets.reduceUselessEntityMovePackets && isUselessMoveEntityPacket(packet)) packet = null; // Purpur - Dont send useless entity packets
+ // Leaf - Multithreaded tracker - diff on change
if (this.entity.hasImpulse || this.trackDelta || this.entity instanceof LivingEntity && ((LivingEntity)this.entity).isFallFlying()) {
Vec3 deltaMovement = this.entity.getDeltaMovement();
if (deltaMovement != this.lastSentMovement) { // SparklyPaper start - skip distanceToSqr call in ServerEntity#sendChanges if the delta movement hasn't changed
double d = deltaMovement.distanceToSqr(this.lastSentMovement);
if (d > 1.0E-7 || d > 0.0 && deltaMovement.lengthSqr() == 0.0) {
this.lastSentMovement = deltaMovement;
+ // Leaf - Multithreaded tracker - diff on change
if (this.entity instanceof AbstractHurtingProjectile abstractHurtingProjectile) {
+ // Leaf - Multithreaded tracker - diff on change
this.broadcast
.accept(
new ClientboundBundlePacket(
List.of(
+ // Leaf - Multithreaded tracker - diff on change
new ClientboundSetEntityMotionPacket(this.entity.getId(), this.lastSentMovement),
new ClientboundProjectilePowerPacket(abstractHurtingProjectile.getId(), abstractHurtingProjectile.accelerationPower)
)
)
);
+ // Leaf - Multithreaded tracker - diff on change
} else {
+ // Leaf - Multithreaded tracker - diff on change
this.broadcast.accept(new ClientboundSetEntityMotionPacket(this.entity.getId(), this.lastSentMovement));
}
}
} // SparklyPaper end
}
+ // Leaf - Multithreaded tracker - diff on change
if (packet != null) {
this.broadcast.accept(packet);
}
this.sendDirtyEntityData();
+ // Leaf - Multithreaded tracker - diff on change
if (flag3) {
this.positionCodec.setBase(vec3);
}
if (flag4) {
+ // Leaf - Multithreaded tracker - diff on change
this.lastSentYRot = b;
this.lastSentXRot = b1;
}
+ // Leaf - Multithreaded tracker - diff on change
this.wasRiding = false;
}
+ // Leaf - Multithreaded tracker - diff on change
byte b2 = Mth.packDegrees(this.entity.getYHeadRot());
if (Math.abs(b2 - this.lastSentYHeadRot) >= 1) {
this.broadcast.accept(new ClientboundRotateHeadPacket(this.entity, b2));
this.lastSentYHeadRot = b2;
}
+ // Leaf - Multithreaded tracker - diff on change
this.entity.hasImpulse = false;
this.forceStateResync = false; // Paper - fix desync when a player is added to the tracker
}
+ // Leaf - Multithreaded tracker - diff on change
this.tickCount++;
if (this.entity.hurtMarked) {
// CraftBukkit start - Create PlayerVelocity event
boolean cancelled = false;
+ // Leaf - Multithreaded tracker - diff on change
if (this.entity instanceof ServerPlayer) {
org.bukkit.entity.Player player = (org.bukkit.entity.Player) this.entity.getBukkitEntity();
org.bukkit.util.Vector velocity = player.getVelocity();
+ // Leaf - Multithreaded tracker - diff on change
org.bukkit.event.player.PlayerVelocityEvent event = new org.bukkit.event.player.PlayerVelocityEvent(player, velocity.clone());
if (!event.callEvent()) {
cancelled = true;
+ // Leaf - Multithreaded tracker - diff on change
} else if (!velocity.equals(event.getVelocity())) {
player.setVelocity(event.getVelocity());
}
}
+ // Leaf - Multithreaded tracker - diff on change
if (cancelled) {
return;
}
+ // Leaf - Multithreaded tracker - diff on change
// CraftBukkit end
this.entity.hurtMarked = false;
this.broadcastAndSend(new ClientboundSetEntityMotionPacket(this.entity));
+ // Leaf - Multithreaded tracker - diff on change
}
}
+ // Leaf start - Multithreaded tracker
+ public void leafSendChanges(org.dreeam.leaf.async.tracker.TrackerCtx ctx, ChunkMap.TrackedEntity trackedEntity) {
+ // Paper start - optimise collisions
+ if (this.entity.moonrise$isHardColliding()) {
+ this.teleportDelay = 9999;
+ }
+ // Paper end - optimise collisions
+ List<Entity> passengers = this.entity.getPassengers();
+ if (!passengers.equals(this.lastPassengers)) {
+ // Leaf start - Remove stream in entity mountedOrDismounted changes update
+ List<UUID> list = new ArrayList<>();
+ for (Entity entity : this.lastPassengers) {
+ if (!passengers.contains(entity)) {
+ list.add(entity.getUUID());
+ }
+ }
+ for (Entity entity : passengers) {
+ if (!this.lastPassengers.contains(entity)) {
+ list.add(entity.getUUID());
+ }
+ }
+ // Leaf end - Remove stream in entity mountedOrDismounted changes update
+ trackedEntity.leafBroadcastIgnorePlayers(ctx, new ClientboundSetPassengersPacket(this.entity), list);
+ // Paper start - Allow riding players
+ if (this.entity instanceof ServerPlayer player) {
+ ctx.send(player.connection, new ClientboundSetPassengersPacket(this.entity));
+ }
+ // Paper end - Allow riding players
+ this.lastPassengers = passengers;
+ }
+ if (!trackedEntity.seenBy.isEmpty() && this.entity instanceof ItemFrame itemFrame /*&& this.tickCount % 10 == 0*/) { // CraftBukkit - moved tickCount below // Paper - Perf: Only tick item frames if players can see it
+ if (this.level.paperConfig().maps.itemFrameCursorUpdateInterval > 0 && this.tickCount % this.level.paperConfig().maps.itemFrameCursorUpdateInterval == 0 && itemFrame.getItem().getItem() instanceof MapItem) { // CraftBukkit - Moved this.tickCounter % 10 logic here so item frames do not enter the other blocks // Paper - Make item frame map cursor update interval configurable
+ ctx.updateItemFrame(itemFrame);
+ }
+ this.leafSendDirtyEntityData(ctx, trackedEntity);
+ }
+
+ if (this.forceStateResync || this.tickCount % this.updateInterval == 0 || this.entity.hasImpulse || this.entity.getEntityData().isDirty()) { // Paper - fix desync when a player is added to the tracker
+ byte b = Mth.packDegrees(this.entity.getYRot());
+ byte b1 = Mth.packDegrees(this.entity.getXRot());
+ boolean flag = Math.abs(b - this.lastSentYRot) >= 1 || Math.abs(b1 - this.lastSentXRot) >= 1;
+ if (this.entity.isPassenger()) {
+ if (flag) {
+ trackedEntity.leafBroadcast(ctx, new ClientboundMoveEntityPacket.Rot(this.entity.getId(), b, b1, this.entity.onGround()));
+ this.lastSentYRot = b;
+ this.lastSentXRot = b1;
+ }
+
+ this.positionCodec.setBase(this.entity.trackingPosition());
+ this.leafSendDirtyEntityData(ctx, trackedEntity);
+ this.wasRiding = true;
+ } else if (this.entity instanceof AbstractMinecart abstractMinecart
+ && abstractMinecart.getBehavior() instanceof NewMinecartBehavior newMinecartBehavior) {
+ this.leafHandleMinecartPosRot(ctx, trackedEntity, newMinecartBehavior, b, b1, flag);
+ } else {
+ this.teleportDelay++;
+ Vec3 vec3 = this.entity.trackingPosition();
+ // Paper start - reduce allocation of Vec3D here
+ Vec3 base = this.positionCodec.base;
+ double vec3_dx = vec3.x - base.x;
+ double vec3_dy = vec3.y - base.y;
+ double vec3_dz = vec3.z - base.z;
+ boolean flag1 = (vec3_dx * vec3_dx + vec3_dy * vec3_dy + vec3_dz * vec3_dz) >= 7.62939453125E-6D;
+ // Paper end - reduce allocation of Vec3D here
+ Packet<? super ClientGamePacketListener> packet = null;
+ boolean flag2 = flag1 || this.tickCount % 60 == 0;
+ boolean flag3 = false;
+ boolean flag4 = false;
+ long l = this.positionCodec.encodeX(vec3);
+ long l1 = this.positionCodec.encodeY(vec3);
+ long l2 = this.positionCodec.encodeZ(vec3);
+ boolean flag5 = l < -32768L || l > 32767L || l1 < -32768L || l1 > 32767L || l2 < -32768L || l2 > 32767L;
+ if (this.forceStateResync || this.entity.getRequiresPrecisePosition() // Paper - fix desync when a player is added to the tracker
+ || flag5
+ || this.teleportDelay > 400
+ || this.wasRiding
+ || this.wasOnGround != this.entity.onGround()) {
+ this.wasOnGround = this.entity.onGround();
+ this.teleportDelay = 0;
+ packet = ClientboundEntityPositionSyncPacket.of(this.entity);
+ flag3 = true;
+ flag4 = true;
+ // Gale start - Airplane - better checking for useless move packets
+ } else {
+ if (flag2 || flag || this.entity instanceof AbstractArrow) {
+ if ((!flag2 || !flag) && !(this.entity instanceof AbstractArrow)) {
+ if (flag2) {
+ packet = new ClientboundMoveEntityPacket.Pos(this.entity.getId(), (short) l, (short) l1, (short) l2, this.entity.onGround());
+ flag3 = true;
+ } else if (flag) {
+ packet = new ClientboundMoveEntityPacket.Rot(this.entity.getId(), b, b1, this.entity.onGround());
+ flag4 = true;
+ }
+ } else {
+ packet = new ClientboundMoveEntityPacket.PosRot(this.entity.getId(), (short) l, (short) l1, (short) l2, b, b1, this.entity.onGround());
+ flag3 = true;
+ flag4 = true;
+ }
+ }
+ }
+ // Gale end - Airplane - better checking for useless move packets
+
+ if (org.dreeam.leaf.config.modules.opt.ReduceUselessPackets.reduceUselessEntityMovePackets && isUselessMoveEntityPacket(packet)) packet = null; // Purpur - Dont send useless entity packets
+
+ if (this.entity.hasImpulse || this.trackDelta || this.entity instanceof LivingEntity && ((LivingEntity)this.entity).isFallFlying()) {
+ Vec3 deltaMovement = this.entity.getDeltaMovement();
+ if (deltaMovement != this.lastSentMovement) { // SparklyPaper start - skip distanceToSqr call in ServerEntity#sendChanges if the delta movement hasn't changed
+ double d = deltaMovement.distanceToSqr(this.lastSentMovement);
+ if (d > 1.0E-7 || d > 0.0 && deltaMovement.lengthSqr() == 0.0) {
+ this.lastSentMovement = deltaMovement;
+ if (this.entity instanceof AbstractHurtingProjectile abstractHurtingProjectile) {
+ trackedEntity.leafBroadcast(ctx, new ClientboundBundlePacket(List.of(
+ new ClientboundSetEntityMotionPacket(this.entity.getId(), this.lastSentMovement),
+ new ClientboundProjectilePowerPacket(abstractHurtingProjectile.getId(), abstractHurtingProjectile.accelerationPower)
+ )));
+ } else {
+ trackedEntity.leafBroadcast(ctx, new ClientboundSetEntityMotionPacket(this.entity.getId(), this.lastSentMovement));
+ }
+ }
+ } // SparklyPaper end
+ }
+
+ if (packet != null) {
+ trackedEntity.leafBroadcast(ctx, packet);
+ }
+
+ this.leafSendDirtyEntityData(ctx, trackedEntity);
+ if (flag3) {
+ this.positionCodec.setBase(vec3);
+ }
+
+ if (flag4) {
+ this.lastSentYRot = b;
+ this.lastSentXRot = b1;
+ }
+
+ this.wasRiding = false;
+ }
+
+ byte b2 = Mth.packDegrees(this.entity.getYHeadRot());
+ if (Math.abs(b2 - this.lastSentYHeadRot) >= 1) {
+ trackedEntity.leafBroadcast(ctx, new ClientboundRotateHeadPacket(this.entity, b2));
+ this.lastSentYHeadRot = b2;
+ }
+
+ this.entity.hasImpulse = false;
+ this.forceStateResync = false; // Paper - fix desync when a player is added to the tracker
+ }
+
+ this.tickCount++;
+ if (this.entity.hurtMarked) {
+ if (this.entity instanceof ServerPlayer serverPlayer) {
+ ctx.playerVelocity(serverPlayer);
+ } else {
+ this.entity.hurtMarked = false;
+ trackedEntity.leafBroadcastAndSend(ctx, new ClientboundSetEntityMotionPacket(this.entity));
+ }
+ }
+ }
+ // Leaf end - Multithreaded tracker
+
private Stream<Entity> mountedOrDismounted(List<Entity> entities) {
return Streams.concat(
this.lastPassengers.stream().filter(entity -> !entities.contains(entity)),
@@ -356,6 +570,39 @@ public class ServerEntity {
this.positionCodec.setBase(this.entity.position());
}
+ // Leaf start - Multithreaded tracker
+ private void leafHandleMinecartPosRot(org.dreeam.leaf.async.tracker.TrackerCtx ctx, ChunkMap.TrackedEntity trackedEntity, NewMinecartBehavior behavior, byte yRot, byte xRot, boolean dirty) {
+ this.leafSendDirtyEntityData(ctx, trackedEntity);
+ if (behavior.lerpSteps.isEmpty()) {
+ Vec3 deltaMovement = this.entity.getDeltaMovement();
+ double d = deltaMovement.distanceToSqr(this.lastSentMovement);
+ Vec3 vec3 = this.entity.trackingPosition();
+ boolean flag = this.positionCodec.delta(vec3).lengthSqr() >= 7.6293945E-6F;
+ boolean flag1 = flag || this.tickCount % 60 == 0;
+ if (flag1 || dirty || d > 1.0E-7) {
+ trackedEntity
+ .leafBroadcast(ctx,
+ new ClientboundMoveMinecartPacket(
+ this.entity.getId(),
+ List.of(
+ new NewMinecartBehavior.MinecartStep(
+ this.entity.position(), this.entity.getDeltaMovement(), this.entity.getYRot(), this.entity.getXRot(), 1.0F
+ )
+ )
+ )
+ );
+ }
+ } else {
+ trackedEntity.leafBroadcast(ctx, new ClientboundMoveMinecartPacket(this.entity.getId(), List.copyOf(behavior.lerpSteps)));
+ behavior.lerpSteps.clear();
+ }
+
+ this.lastSentYRot = yRot;
+ this.lastSentXRot = xRot;
+ this.positionCodec.setBase(this.entity.position());
+ }
+ // Leaf end - Multithreaded tracker
+
public void removePairing(ServerPlayer player) {
this.entity.stopSeenByPlayer(player);
player.connection.send(new ClientboundRemoveEntitiesPacket(this.entity.getId()));
@@ -368,8 +615,23 @@ public class ServerEntity {
this.entity.startSeenByPlayer(player);
}
+ // Leaf start - Multithreaded tracker
+ public void leafAddPairing(org.dreeam.leaf.async.tracker.TrackerCtx ctx, ServerPlayer player) {
+ final net.minecraft.server.network.ServerGamePacketListenerImpl connection = player.connection;
+ List<Packet<? super ClientGamePacketListener>> list = new ArrayList<>();
+ this.sendPairingData0(player, list::add, true);
+ ctx.send(connection, new ClientboundBundlePacket(list));
+ ctx.startSeenByPlayer(connection, this.entity);
+ }
+
public void sendPairingData(ServerPlayer player, Consumer<Packet<ClientGamePacketListener>> consumer) {
+ sendPairingData0(player, consumer, false);
+ }
+
+ private void sendPairingData0(ServerPlayer player, Consumer<Packet<ClientGamePacketListener>> consumer, boolean async) {
if (this.entity.isRemoved()) {
+ // Leaf end - Multithreaded tracker
+
// CraftBukkit start - Remove useless error spam, just return
// LOGGER.warn("Fetching packet for removed entity {}", this.entity);
return;
@@ -407,7 +669,7 @@ public class ServerEntity {
if (!list.isEmpty()) {
consumer.accept(new ClientboundSetEquipmentPacket(this.entity.getId(), list, true)); // Paper - data sanitization
}
- ((LivingEntity) this.entity).detectEquipmentUpdates(); // CraftBukkit - SPIGOT-3789: sync again immediately after sending
+ if (!async) { ((LivingEntity) this.entity).detectEquipmentUpdates(); } // CraftBukkit - SPIGOT-3789: sync again immediately after sending // Leaf - Multithreaded tracker
}
if (!this.entity.getPassengers().isEmpty()) {
@@ -443,6 +705,7 @@ public class ServerEntity {
return Mth.unpackDegrees(this.lastSentYHeadRot);
}
+ // Leaf - Multithreaded tracker - diff on change
public void sendDirtyEntityData() {
SynchedEntityData entityData = this.entity.getEntityData();
List<SynchedEntityData.DataValue<?>> list = entityData.packDirty();
@@ -450,10 +713,12 @@ public class ServerEntity {
this.trackedDataValues = entityData.getNonDefaultValues();
this.broadcastAndSend(new ClientboundSetEntityDataPacket(this.entity.getId(), list));
}
+ // Leaf - Multithreaded tracker - diff on change
if (this.entity instanceof LivingEntity) {
Set<AttributeInstance> attributesToSync = ((LivingEntity)this.entity).getAttributes().getAttributesToSync();
if (!attributesToSync.isEmpty()) {
+ // Leaf - Multithreaded tracker - diff on change
// CraftBukkit start - Send scaled max health
if (this.entity instanceof ServerPlayer serverPlayer) {
serverPlayer.getBukkitEntity().injectScaledMaxHealth(attributesToSync, false);
@@ -462,11 +727,38 @@ public class ServerEntity {
this.broadcastAndSend(new ClientboundUpdateAttributesPacket(this.entity.getId(), attributesToSync));
}
- attributesToSync.clear();
+ // attributesToSync.clear(); // Leaf - Multithreaded tracker
+ }
+ }
+
+ // Leaf start - Multithreaded tracker
+ public void leafSendDirtyEntityData(org.dreeam.leaf.async.tracker.TrackerCtx ctx, ChunkMap.TrackedEntity trackedEntity) {
+ SynchedEntityData entityData = this.entity.getEntityData();
+ List<SynchedEntityData.DataValue<?>> list = entityData.packDirty();
+ if (list != null) {
+ this.trackedDataValues = entityData.getNonDefaultValues();
+ ClientboundSetEntityDataPacket packet = new ClientboundSetEntityDataPacket(this.entity.getId(), list);
+ trackedEntity.leafBroadcastAndSend(ctx, packet);
+ }
+
+ if (this.entity instanceof LivingEntity) {
+ Set<AttributeInstance> attributesToSync = ((LivingEntity)this.entity).getAttributes().getAttributesToSync();
+ if (!attributesToSync.isEmpty()) {
+ // CraftBukkit start - Send scaled max health
+ if (this.entity instanceof ServerPlayer serverPlayer) {
+ serverPlayer.getBukkitEntity().injectScaledMaxHealth(attributesToSync, false);
+ }
+ // CraftBukkit end
+ trackedEntity.leafBroadcastAndSend(ctx, new ClientboundUpdateAttributesPacket(this.entity.getId(), attributesToSync));
+ }
+
+ // attributesToSync.clear(); // Leaf - Multithreaded tracker
}
}
+ // Leaf end - Multithreaded tracker
private void broadcastAndSend(Packet<?> packet) {
+ // Leaf - Multithreaded tracker - diff on change
this.broadcast.accept(packet);
if (this.entity instanceof ServerPlayer) {
((ServerPlayer)this.entity).connection.send(packet);
diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java
index 08d12a1acc3a672a77daa15f82392cd603c30283..b0ac6de9e0f15d234781fc43dd6fd1d5cd6c5ddf 100644
--- a/net/minecraft/server/level/ServerLevel.java
+++ b/net/minecraft/server/level/ServerLevel.java
@@ -216,6 +216,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
private final alternate.current.wire.WireHandler wireHandler = new alternate.current.wire.WireHandler(this); // Paper - optimize redstone (Alternate Current)
public boolean hasRidableMoveEvent = false; // Purpur - Ridables
final List<ServerPlayer> realPlayers; // Leaves - skip
+ public volatile @Nullable java.util.concurrent.Future<org.dreeam.leaf.async.tracker.TrackerCtx>[] trackerTask; // Leaf - Multithreaded tracker
@Override
public @Nullable LevelChunk getChunkIfLoaded(int x, int z) {
@@ -834,6 +835,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
this.tickBlockEntities();
}
// Paper - rewrite chunk system
+ if (org.dreeam.leaf.config.modules.async.MultithreadedTracker.enabled) { org.dreeam.leaf.async.tracker.AsyncTracker.onEntitiesTickEnd(this); } // Leaf - Multithreaded tracker
}
@Override
@@ -2527,7 +2529,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
@Override
public LevelEntityGetter<Entity> 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
}
@@ -2799,7 +2801,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 1556b2d4a758e3a15b6c4468bf994ea3781a4958..581791bf2892715543f003c36e301e690cc393f1 100644
--- a/net/minecraft/server/network/ServerGamePacketListenerImpl.java
+++ b/net/minecraft/server/network/ServerGamePacketListenerImpl.java
@@ -1928,7 +1928,7 @@ public class ServerGamePacketListenerImpl
}
public void internalTeleport(PositionMoveRotation posMoveRotation, Set<Relative> 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 4d4efd979c4478c1571e7044f639afe08a7118d6..de4d13e7c6f57d6cc3a78b1b9ca914157820186c 100644
--- a/net/minecraft/world/entity/LivingEntity.java
+++ b/net/minecraft/world/entity/LivingEntity.java
@@ -1342,13 +1342,13 @@ public abstract class LivingEntity extends Entity implements Attackable, Waypoin
}
private void refreshDirtyAttributes() {
- Set<AttributeInstance> attributesToUpdate = this.getAttributes().getAttributesToUpdate();
+ // Leaf start - Multithreaded tracker
+ int[] attributesToUpdate = this.getAttributes().getAttributesToUpdateIds();
- for (AttributeInstance attributeInstance : attributesToUpdate) {
- this.onAttributeUpdated(attributeInstance.getAttribute());
+ for (int attribute : attributesToUpdate) {
+ this.onAttributeUpdated(net.minecraft.core.registries.BuiltInRegistries.ATTRIBUTE.get(attribute).orElseThrow());
}
-
- attributesToUpdate.clear();
+ // Leaf end - Multithreaded tracker
}
protected void onAttributeUpdated(Holder<Attribute> attribute) {
diff --git a/net/minecraft/world/entity/ai/attributes/Attribute.java b/net/minecraft/world/entity/ai/attributes/Attribute.java
index f8419dde44ebc7324e783f8bee42132d5ec973c3..d7eef96e12c738cd06a8e16efe48b7673f2a8608 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 int SIZE = 0;
+ // Leaf end - Optimize AttributeMap
protected Attribute(String descriptionId, double defaultValue) {
this.defaultValue = defaultValue;
this.descriptionId = descriptionId;
+ this.uid = SIZE++; // Leaf - Optimize AttributeMap
}
public double getDefaultValue() {
diff --git a/net/minecraft/world/entity/ai/attributes/AttributeInstance.java b/net/minecraft/world/entity/ai/attributes/AttributeInstance.java
index 42ad600c6a5cb20e1d820f169f6a1a17ef3a5195..7f8eb388308806008805970d4d8ed329440380ee 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<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 Map<ResourceLocation, AttributeModifier> modifierById;
+ {
+ if (org.dreeam.leaf.config.modules.async.MultithreadedTracker.enabled) {
+ modifierById = it.unimi.dsi.fastutil.objects.Object2ObjectMaps.synchronize(new Object2ObjectArrayMap<>(), this);
+ } else {
+ modifierById = new Object2ObjectArrayMap<>();
+ }
+ }
+ private final Map<ResourceLocation, AttributeModifier> permanentModifiers;
+ {
+ if (org.dreeam.leaf.config.modules.async.MultithreadedTracker.enabled) {
+ permanentModifiers = it.unimi.dsi.fastutil.objects.Object2ObjectMaps.synchronize(new Object2ObjectArrayMap<>(), this);
+ } else {
+ permanentModifiers = new Object2ObjectArrayMap<>();
+ }
+ }
+ // Leaf end - Multithreaded tracker
private double baseValue;
private boolean dirty = true;
private double cachedValue;
@@ -52,7 +68,13 @@ public class AttributeInstance {
@VisibleForTesting
Map<ResourceLocation, AttributeModifier> getModifiers(AttributeModifier.Operation operation) {
- return this.modifiersByOperation.computeIfAbsent(operation, operation1 -> new Object2ObjectOpenHashMap<>());
+ // Leaf start - Multithreaded tracker
+ return this.modifiersByOperation.computeIfAbsent(operation, operation1 -> {
+ if (org.dreeam.leaf.config.modules.async.MultithreadedTracker.enabled)
+ return it.unimi.dsi.fastutil.objects.Object2ObjectMaps.synchronize(new Object2ObjectArrayMap<>(), this);
+ else return new Object2ObjectArrayMap<>();
+ });
+ // Leaf end - Multithreaded tracker
}
public Set<AttributeModifier> getModifiers() {
@@ -140,8 +162,12 @@ public class AttributeInstance {
public double getValue() {
if (this.dirty) {
- this.cachedValue = this.calculateValue();
+ // Leaf start - Multithreaded tracker
+ double value = this.calculateValue();
+ this.cachedValue = value;
this.dirty = false;
+ return value;
+ // Leaf end - Multithreaded tracker
}
return this.cachedValue;
@@ -184,7 +210,15 @@ public class AttributeInstance {
}
public AttributeInstance.Packed pack() {
- return new AttributeInstance.Packed(this.attribute, this.baseValue, List.copyOf(this.permanentModifiers.values()));
+ // Leaf start - Multithreaded tracker
+ if (org.dreeam.leaf.config.modules.async.MultithreadedTracker.enabled) {
+ 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()));
+ }
+ // Leaf 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 0ac398b8b10aae5e67a797b2991c66874003f282..d62c2644847173de8e11296508f111e786a95091 100644
--- a/net/minecraft/world/entity/ai/attributes/AttributeMap.java
+++ b/net/minecraft/world/entity/ai/attributes/AttributeMap.java
@@ -15,9 +15,11 @@ import net.minecraft.resources.ResourceLocation;
public class AttributeMap {
// 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);
+ // Leaf start - Multithreaded tracker
+ private final Map<Holder<Attribute>, AttributeInstance> attributes = new org.dreeam.leaf.util.map.AttributeInstanceArrayMap();
+ private final org.dreeam.leaf.util.map.AttributeInstanceSet attributesToSync = new org.dreeam.leaf.util.map.AttributeInstanceSet((org.dreeam.leaf.util.map.AttributeInstanceArrayMap) attributes);
+ private final org.dreeam.leaf.util.map.AttributeInstanceSet attributesToUpdate = new org.dreeam.leaf.util.map.AttributeInstanceSet((org.dreeam.leaf.util.map.AttributeInstanceArrayMap) attributes);
+ // Leaf end - Multithreaded tracker
// Gale end - Lithium - replace AI attributes with optimized collections
private final AttributeSupplier supplier;
private final net.minecraft.world.entity.LivingEntity entity; // Purpur - Ridables
@@ -32,28 +34,51 @@ public class AttributeMap {
this.supplier = defaultAttributes;
}
- private void onAttributeModified(AttributeInstance instance) {
+ // Leaf start - Multithreaded tracker
+ private synchronized void onAttributeModified(AttributeInstance instance) {
this.attributesToUpdate.add(instance);
if (instance.getAttribute().value().isClientSyncable() && (entity == null || entity.shouldSendAttribute(instance.getAttribute().value()))) { // Purpur - Ridables
this.attributesToSync.add(instance);
}
}
- public Set<AttributeInstance> getAttributesToSync() {
- return this.attributesToSync;
+ private static final AttributeInstance[] EMPTY_ATTRIBUTE_INSTANCE = new AttributeInstance[0];
+ public synchronized Set<AttributeInstance> getAttributesToSync() {
+ var clone = it.unimi.dsi.fastutil.objects.ReferenceArraySet.ofUnchecked(attributesToSync.toArray(EMPTY_ATTRIBUTE_INSTANCE));
+ this.attributesToSync.clear();
+ return clone;
}
- public Set<AttributeInstance> getAttributesToUpdate() {
- return this.attributesToUpdate;
+ public synchronized Set<AttributeInstance> getAttributesToUpdate() {
+ var clone = it.unimi.dsi.fastutil.objects.ReferenceArraySet.ofUnchecked(attributesToUpdate.toArray(EMPTY_ATTRIBUTE_INSTANCE));
+ this.attributesToUpdate.clear();
+ return clone;
}
+ public synchronized int[] getAttributesToUpdateIds() {
+ int[] clone = attributesToUpdate.inner.toIntArray();
+ this.attributesToUpdate.clear();
+ return clone;
+ }
+ // Leaf end - Multithreaded tracker
+
public Collection<AttributeInstance> 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> attribute) {
- return this.attributes.computeIfAbsent(attribute, holder -> this.supplier.createInstance(this::onAttributeModified, (Holder<Attribute>)holder));
+ // Leaf start - Multithreaded tracker
+ AttributeInstance v;
+ if ((v = this.attributes.get(attribute)) == null) {
+ AttributeInstance newValue;
+ if ((newValue = this.supplier.createInstance(this::onAttributeModified, attribute)) != null) {
+ attributes.put(attribute, newValue);
+ return newValue;
+ }
+ }
+ return v;
+ // Leaf end - Multithreaded tracker
}
public boolean hasAttribute(Holder<Attribute> attribute) {
diff --git a/net/minecraft/world/entity/ai/attributes/AttributeSupplier.java b/net/minecraft/world/entity/ai/attributes/AttributeSupplier.java
index 24710041ccbc70e5506d8d89ae34f0141977f209..05de8a77b389691dd6986f36b4cb8cc0935e21e4 100644
--- a/net/minecraft/world/entity/ai/attributes/AttributeSupplier.java
+++ b/net/minecraft/world/entity/ai/attributes/AttributeSupplier.java
@@ -11,7 +11,7 @@ public class AttributeSupplier {
private final Map<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) {
@@ -41,7 +41,7 @@ public class AttributeSupplier {
}
@Nullable
- public AttributeInstance createInstance(Consumer<AttributeInstance> onDirty, Holder<Attribute> attribute) {
+ public AttributeInstance createInstance(Consumer<AttributeInstance> onDirty, Holder<Attribute> attribute) { // Leaf - Multithreaded tracker
AttributeInstance attributeInstance = this.instances.get(attribute);
if (attributeInstance == null) {
return null;
diff --git a/net/minecraft/world/entity/vehicle/NewMinecartBehavior.java b/net/minecraft/world/entity/vehicle/NewMinecartBehavior.java
index 325ec57df2885f5e81b8a6b61e3a9fed9484b30f..abc5c097861d0decf49d0d3970ab48f1cf8b1cf1 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<NewMinecartBehavior.MinecartStep> lerpSteps = new LinkedList<>();
+ public final List<NewMinecartBehavior.MinecartStep> lerpSteps; // Leaf - Multithreaded tracker
public final List<NewMinecartBehavior.MinecartStep> currentLerpSteps = new LinkedList<>();
public double currentLerpStepsTotalWeight = 0.0;
public NewMinecartBehavior.MinecartStep oldLerp = NewMinecartBehavior.MinecartStep.ZERO;
public NewMinecartBehavior(AbstractMinecart minecart) {
super(minecart);
+ // Leaf start - Multithreaded tracker
+ if (org.dreeam.leaf.config.modules.async.MultithreadedTracker.enabled) {
+ lerpSteps = it.unimi.dsi.fastutil.objects.ObjectLists.synchronize(new it.unimi.dsi.fastutil.objects.ObjectArrayList<>());
+ } else {
+ lerpSteps = new it.unimi.dsi.fastutil.objects.ObjectArrayList<>();
+ }
+ // Leaf end - Multithreaded tracker
}
@Override