mirror of
https://github.com/Winds-Studio/Leaf.git
synced 2025-12-20 15:39:37 +00:00
611 lines
34 KiB
Diff
611 lines
34 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>
|
|
|
|
This patch refactored from original multithreaded tracker (Petal version),
|
|
and is derived from the Airplane fork by Paul Sauve, the tree is like:
|
|
Airplane -> Pufferfish? -> Petal -> Leaf
|
|
|
|
We made much of tracking logic asynchronously, and fixed visible issue
|
|
for the case of some NPC plugins which using real entity type, e.g. Citizens.
|
|
|
|
But it is still recommending to use those packet based, virtual entity
|
|
based NPC plugins, e.g. ZNPC Plus, Adyeshach, Fancy NPC, etc.
|
|
|
|
diff --git a/src/main/java/io/papermc/paper/plugin/manager/PaperEventManager.java b/src/main/java/io/papermc/paper/plugin/manager/PaperEventManager.java
|
|
index e42677bb004201efe1702779a78cc8d0ca05e80f..6676be8304e9415099ed423d3315180cafebd928 100644
|
|
--- a/src/main/java/io/papermc/paper/plugin/manager/PaperEventManager.java
|
|
+++ b/src/main/java/io/papermc/paper/plugin/manager/PaperEventManager.java
|
|
@@ -42,6 +42,12 @@ class PaperEventManager {
|
|
if (event.isAsynchronous() && this.server.isPrimaryThread()) {
|
|
throw new IllegalStateException(event.getEventName() + " may only be triggered asynchronously.");
|
|
} else if (!event.isAsynchronous() && !this.server.isPrimaryThread() && !this.server.isStopping()) {
|
|
+ // Leaf start - Multithreaded tracker
|
|
+ if (org.dreeam.leaf.config.modules.async.MultithreadedTracker.enabled) {
|
|
+ net.minecraft.server.MinecraftServer.getServer().scheduleOnMain(event::callEvent);
|
|
+ return;
|
|
+ }
|
|
+ // Leaf end - Multithreaded tracker
|
|
throw new IllegalStateException(event.getEventName() + " may only be triggered synchronously.");
|
|
}
|
|
// Leaves start - skip photographer
|
|
diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java
|
|
index 6985da233e41a62bea04277260f81b3ba200a415..58e79417e3722ce73cbbc1f9c74cbc73178f762d 100644
|
|
--- a/src/main/java/net/minecraft/server/level/ChunkMap.java
|
|
+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java
|
|
@@ -234,6 +234,15 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
|
|
return;
|
|
}
|
|
final ServerPlayer[] backingSet = inRange.getRawDataUnchecked();
|
|
+ // Leaf start - Multithreaded tracker
|
|
+ if (org.dreeam.leaf.config.modules.async.MultithreadedTracker.enabled)
|
|
+ for (int i = 0, len = inRange.size(); i < len; i++) {
|
|
+ final ServerPlayer player = backingSet[i];
|
|
+ if (player == null) continue;
|
|
+ ++(player.mobCounts[index]);
|
|
+ }
|
|
+ else
|
|
+ // Leaf end - Multithreaded tracker
|
|
for (int i = 0, len = inRange.size(); i < len; i++) {
|
|
++(backingSet[i].mobCounts[index]);
|
|
}
|
|
@@ -972,6 +981,21 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
|
|
((ca.spottedleaf.moonrise.patches.entity_tracker.EntityTrackerEntity)entity).moonrise$setTrackedEntity(null); // Paper - optimise entity tracker
|
|
}
|
|
|
|
+ // Leaf start - petal - Multithreaded tracker
|
|
+ private final java.util.concurrent.ConcurrentLinkedQueue<Runnable> trackerMainThreadTasks = new java.util.concurrent.ConcurrentLinkedQueue<>();
|
|
+ private boolean tracking = false;
|
|
+
|
|
+ public void runOnTrackerMainThread(final Runnable runnable) {
|
|
+ //final boolean isOnMain = ca.spottedleaf.moonrise.common.util.TickThread.isTickThread();
|
|
+ //System.out.println(isOnMain);
|
|
+ if (false && this.tracking) { // TODO: check here
|
|
+ this.trackerMainThreadTasks.add(runnable);
|
|
+ } else {
|
|
+ runnable.run();
|
|
+ }
|
|
+ }
|
|
+ // Leaf end - petal - Multithreaded tracker
|
|
+
|
|
// Paper start - optimise entity tracker
|
|
private void newTrackerTick() {
|
|
final ca.spottedleaf.moonrise.common.misc.NearbyPlayers nearbyPlayers = ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel)this.level).moonrise$getNearbyPlayers();
|
|
@@ -1005,6 +1029,13 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
|
|
// Paper end - optimise entity tracker
|
|
|
|
protected void tick() {
|
|
+ // Leaf start - Multithreaded tracker
|
|
+ if (org.dreeam.leaf.config.modules.async.MultithreadedTracker.enabled) {
|
|
+ final ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel level = this.level;
|
|
+ org.dreeam.leaf.async.tracker.MultithreadedTracker.tick(level);
|
|
+ return;
|
|
+ }
|
|
+ // Leaf end - Multithreaded tracker
|
|
// Paper start - optimise entity tracker
|
|
if (true) {
|
|
this.newTrackerTick();
|
|
@@ -1154,7 +1185,9 @@ 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
|
|
+ ? Sets.newConcurrentHashSet()
|
|
+ : new it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet<>(); // Paper - Perf: optimise map impl // Leaf - petal - Multithreaded tracker
|
|
|
|
// Paper start - optimise entity tracker
|
|
private long lastChunkUpdate = -1L;
|
|
@@ -1181,7 +1214,39 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
|
|
this.lastTrackedChunk = chunk;
|
|
|
|
final ServerPlayer[] playersRaw = players.getRawDataUnchecked();
|
|
+ final int playersLen = players.size(); // Ensure length won't change in the future tasks
|
|
+
|
|
+ // Leaf start - Multithreaded tracker
|
|
+ if (org.dreeam.leaf.config.modules.async.MultithreadedTracker.enabled && org.dreeam.leaf.config.modules.async.MultithreadedTracker.compatModeEnabled) {
|
|
+ final boolean isServerPlayer = this.entity instanceof ServerPlayer;
|
|
+ final boolean isRealPlayer = isServerPlayer && ((ca.spottedleaf.moonrise.patches.chunk_system.player.ChunkSystemServerPlayer) this.entity).moonrise$isRealPlayer();
|
|
+ Runnable updatePlayerTasks = () -> {
|
|
+ for (int i = 0; i < playersLen; ++i) {
|
|
+ final ServerPlayer player = playersRaw[i];
|
|
+ this.updatePlayer(player);
|
|
+ }
|
|
|
|
+ if (lastChunkUpdate != currChunkUpdate || lastTrackedChunk != chunk) {
|
|
+ // need to purge any players possible not in the chunk list
|
|
+ for (final ServerPlayerConnection conn : new java.util.ArrayList<>(this.seenBy)) {
|
|
+ final ServerPlayer player = conn.getPlayer();
|
|
+ if (!players.contains(player)) {
|
|
+ this.removePlayer(player);
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ };
|
|
+
|
|
+ // Only update asynchronously for real player, and sync update for fake players
|
|
+ // This can fix compatibility issue with NPC plugins using real entity type, like Citizens
|
|
+ // To prevent visible issue with player type NPCs
|
|
+ // btw, still recommend to use packet based NPC plugins, like ZNPC Plus, Adyeshach, Fancy NPC, etc.
|
|
+ if (isRealPlayer || !isServerPlayer) {
|
|
+ org.dreeam.leaf.async.tracker.MultithreadedTracker.getTrackerExecutor().execute(updatePlayerTasks);
|
|
+ } else {
|
|
+ updatePlayerTasks.run();
|
|
+ }
|
|
+ } else {
|
|
for (int i = 0, len = players.size(); i < len; ++i) {
|
|
final ServerPlayer player = playersRaw[i];
|
|
this.updatePlayer(player);
|
|
@@ -1196,6 +1261,8 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
|
|
}
|
|
}
|
|
}
|
|
+ }
|
|
+ // Leaf end - Multithreaded tracker
|
|
}
|
|
|
|
@Override
|
|
@@ -1250,14 +1317,11 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
|
|
}
|
|
|
|
public void broadcast(Packet<?> packet) {
|
|
- Iterator iterator = this.seenBy.iterator();
|
|
-
|
|
- while (iterator.hasNext()) {
|
|
- ServerPlayerConnection serverplayerconnection = (ServerPlayerConnection) iterator.next();
|
|
-
|
|
+ // Leaf start - petal - Multithreaded tracker
|
|
+ for (ServerPlayerConnection serverplayerconnection : this.seenBy.toArray(new ServerPlayerConnection[0])) {
|
|
serverplayerconnection.send(packet);
|
|
}
|
|
-
|
|
+ // Leaf end - petal - Multithreaded tracker
|
|
}
|
|
|
|
public void broadcastAndSend(Packet<?> packet) {
|
|
@@ -1269,18 +1333,15 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
|
|
}
|
|
|
|
public void broadcastRemoved() {
|
|
- Iterator iterator = this.seenBy.iterator();
|
|
-
|
|
- while (iterator.hasNext()) {
|
|
- ServerPlayerConnection serverplayerconnection = (ServerPlayerConnection) iterator.next();
|
|
-
|
|
+ // Leaf start - petal - Multithreaded tracker
|
|
+ for (ServerPlayerConnection serverplayerconnection : this.seenBy.toArray(new ServerPlayerConnection[0])) {
|
|
this.serverEntity.removePairing(serverplayerconnection.getPlayer());
|
|
}
|
|
-
|
|
+ // Leaf end - petal - 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);
|
|
}
|
|
@@ -1288,8 +1349,9 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
|
|
}
|
|
|
|
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 vec3d = player.position().subtract(this.entity.position());
|
|
double vec3d_dx = player.getX() - this.entity.getX();
|
|
diff --git a/src/main/java/net/minecraft/server/level/ServerBossEvent.java b/src/main/java/net/minecraft/server/level/ServerBossEvent.java
|
|
index 4f91107f9ae42f96c060c310596db9aa869a8dbc..f9889f593ed144ee8f1f5bd380e631c659b0c2b8 100644
|
|
--- a/src/main/java/net/minecraft/server/level/ServerBossEvent.java
|
|
+++ b/src/main/java/net/minecraft/server/level/ServerBossEvent.java
|
|
@@ -13,7 +13,9 @@ import net.minecraft.util.Mth;
|
|
import net.minecraft.world.BossEvent;
|
|
|
|
public class ServerBossEvent extends BossEvent {
|
|
- private final Set<ServerPlayer> players = Sets.newHashSet();
|
|
+ private final Set<ServerPlayer> players = org.dreeam.leaf.config.modules.async.MultithreadedTracker.enabled
|
|
+ ? Sets.newConcurrentHashSet()
|
|
+ : Sets.newHashSet(); // Leaf - petal - Multithreaded tracker - players can be removed in async tracking
|
|
private final Set<ServerPlayer> unmodifiablePlayers = Collections.unmodifiableSet(this.players);
|
|
public boolean visible = true;
|
|
|
|
diff --git a/src/main/java/net/minecraft/server/level/ServerEntity.java b/src/main/java/net/minecraft/server/level/ServerEntity.java
|
|
index 05125144ce0cb50fa6ac769fa025cda010c93f14..189bfe0e97943f3f560fa3c2674013e2e833bd5e 100644
|
|
--- a/src/main/java/net/minecraft/server/level/ServerEntity.java
|
|
+++ b/src/main/java/net/minecraft/server/level/ServerEntity.java
|
|
@@ -115,7 +115,13 @@ public class ServerEntity {
|
|
this.broadcastAndSend(new ClientboundSetPassengersPacket(this.entity)); // CraftBukkit
|
|
ServerEntity.removedPassengers(list, this.lastPassengers).forEach((entity) -> {
|
|
if (entity instanceof ServerPlayer entityplayer) {
|
|
- entityplayer.connection.teleport(entityplayer.getX(), entityplayer.getY(), entityplayer.getZ(), entityplayer.getYRot(), entityplayer.getXRot());
|
|
+ // Leaf start - Multithreaded tracker - Ensure teleport is executed on server thread
|
|
+ if (org.dreeam.leaf.config.modules.async.MultithreadedTracker.enabled && Thread.currentThread() instanceof org.dreeam.leaf.async.tracker.MultithreadedTracker.MultithreadedTrackerThread) {
|
|
+ net.minecraft.server.MinecraftServer.getServer().scheduleOnMain(() -> entityplayer.connection.teleport(entityplayer.getX(), entityplayer.getY(), entityplayer.getZ(), entityplayer.getYRot(), entityplayer.getXRot()));
|
|
+ } else {
|
|
+ entityplayer.connection.teleport(entityplayer.getX(), entityplayer.getY(), entityplayer.getZ(), entityplayer.getYRot(), entityplayer.getXRot());
|
|
+ }
|
|
+ // Leaf end - Multithreaded tracker - Ensure teleport is executed on server thread
|
|
}
|
|
|
|
});
|
|
@@ -336,7 +342,11 @@ public class ServerEntity {
|
|
|
|
public void removePairing(ServerPlayer player) {
|
|
this.entity.stopSeenByPlayer(player);
|
|
- player.connection.send(new ClientboundRemoveEntitiesPacket(new int[]{this.entity.getId()}));
|
|
+ // Leaf start - petal - Multithreaded tracker - send in main thread
|
|
+ ((ServerLevel) this.entity.level()).chunkSource.chunkMap.runOnTrackerMainThread(() ->
|
|
+ player.connection.send(new ClientboundRemoveEntitiesPacket(this.entity.getId()))
|
|
+ );
|
|
+ // Leaf end - petal - Multithreaded tracker - send in main thread
|
|
}
|
|
|
|
public void addPairing(ServerPlayer player) {
|
|
@@ -344,7 +354,11 @@ public class ServerEntity {
|
|
|
|
Objects.requireNonNull(list);
|
|
this.sendPairingData(player, list::add);
|
|
- player.connection.send(new ClientboundBundlePacket(list));
|
|
+ // Leaf start - petal - Multithreaded tracker - send in main thread
|
|
+ ((ServerLevel) this.entity.level()).chunkSource.chunkMap.runOnTrackerMainThread(() ->
|
|
+ player.connection.send(new ClientboundBundlePacket(list))
|
|
+ );
|
|
+ // Leaf end - petal - Multithreaded tracker - send in main thread
|
|
this.entity.startSeenByPlayer(player);
|
|
}
|
|
|
|
@@ -464,19 +478,28 @@ public class ServerEntity {
|
|
|
|
if (list != null) {
|
|
this.trackedDataValues = datawatcher.getNonDefaultValues();
|
|
- this.broadcastAndSend(new ClientboundSetEntityDataPacket(this.entity.getId(), list));
|
|
+ // Leaf start - petal - Multithreaded tracker - send in main thread
|
|
+ ((ServerLevel) this.entity.level()).chunkSource.chunkMap.runOnTrackerMainThread(() ->
|
|
+ this.broadcastAndSend(new ClientboundSetEntityDataPacket(this.entity.getId(), list))
|
|
+ );
|
|
+ // Leaf end - petal - Multithreaded tracker - send in main thread
|
|
}
|
|
|
|
if (this.entity instanceof LivingEntity) {
|
|
Set<AttributeInstance> set = ((LivingEntity) this.entity).getAttributes().getAttributesToSync();
|
|
|
|
if (!set.isEmpty()) {
|
|
+ // Leaf end - petal - Multithreaded tracker - send in main thread
|
|
+ final Set<AttributeInstance> copy = new it.unimi.dsi.fastutil.objects.ObjectOpenHashSet<>(set);
|
|
+ ((ServerLevel) this.entity.level()).chunkSource.chunkMap.runOnTrackerMainThread(() -> {
|
|
// CraftBukkit start - Send scaled max health
|
|
if (this.entity instanceof ServerPlayer) {
|
|
- ((ServerPlayer) this.entity).getBukkitEntity().injectScaledMaxHealth(set, false);
|
|
+ ((ServerPlayer) this.entity).getBukkitEntity().injectScaledMaxHealth(copy, false);
|
|
}
|
|
// CraftBukkit end
|
|
- this.broadcastAndSend(new ClientboundUpdateAttributesPacket(this.entity.getId(), set));
|
|
+ this.broadcastAndSend(new ClientboundUpdateAttributesPacket(this.entity.getId(), copy));
|
|
+ });
|
|
+ // Leaf end - petal - Multithreaded tracker - send in main thread
|
|
}
|
|
|
|
set.clear();
|
|
diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java
|
|
index 0874d1da44c82c87a8061233f1ed089ee0e0179d..6d8fb4fe9733bd1e83af7f8c148bdb54fa26a14b 100644
|
|
--- a/src/main/java/net/minecraft/server/level/ServerLevel.java
|
|
+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
|
|
@@ -2398,7 +2398,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ca.spottedleaf.
|
|
|
|
@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
|
|
}
|
|
|
|
diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
|
|
index 82b885a6037afa35da76997d9eab6fe3390df5c2..e0962547709d6951cc98da94f028bd4d5b7b25dd 100644
|
|
--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
|
|
+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
|
|
@@ -1811,7 +1811,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl
|
|
}
|
|
|
|
public void internalTeleport(double d0, double d1, double d2, float f, float f1, Set<RelativeMovement> set) { // Paper
|
|
- org.spigotmc.AsyncCatcher.catchOp("teleport"); // Paper
|
|
+ //org.spigotmc.AsyncCatcher.catchOp("teleport"); // Paper // Leaf - Multithreaded tracker
|
|
// Paper start - Prevent teleporting dead entities
|
|
if (player.isRemoved()) {
|
|
LOGGER.info("Attempt to teleport removed player {} restricted", player.getScoreboardName());
|
|
diff --git a/src/main/java/net/minecraft/world/entity/ai/attributes/AttributeInstance.java b/src/main/java/net/minecraft/world/entity/ai/attributes/AttributeInstance.java
|
|
index d28f9e077a50122e86848cfa9db83f6b0e8eef6c..3b717cc52c68994e26a389579ec02640ae526f0d 100644
|
|
--- a/src/main/java/net/minecraft/world/entity/ai/attributes/AttributeInstance.java
|
|
+++ b/src/main/java/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 ? new java.util.concurrent.ConcurrentHashMap<>() : new Object2ObjectArrayMap<>();
|
|
+ private final Map<ResourceLocation, AttributeModifier> permanentModifiers = multiThreadedTrackingEnabled ? new java.util.concurrent.ConcurrentHashMap<>() : new Object2ObjectArrayMap<>();
|
|
+ // Leaf end - Multithreaded tracker
|
|
private double baseValue;
|
|
private boolean dirty = true;
|
|
private double cachedValue;
|
|
diff --git a/src/main/java/net/minecraft/world/entity/ai/attributes/AttributeMap.java b/src/main/java/net/minecraft/world/entity/ai/attributes/AttributeMap.java
|
|
index 14ceb3308474e76220bd64b0254df3f2925d4206..6cd45791b19df76e367d2693bce349c66def65d8 100644
|
|
--- a/src/main/java/net/minecraft/world/entity/ai/attributes/AttributeMap.java
|
|
+++ b/src/main/java/net/minecraft/world/entity/ai/attributes/AttributeMap.java
|
|
@@ -19,11 +19,14 @@ import org.slf4j.Logger;
|
|
|
|
public class AttributeMap {
|
|
private static final Logger LOGGER = LogUtils.getLogger();
|
|
+ // Leaf start - Multithreaded tracker
|
|
+ private final boolean multiThreadedTrackingEnabled = org.dreeam.leaf.config.modules.async.MultithreadedTracker.enabled;
|
|
// 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);
|
|
+ private final Map<Holder<Attribute>, AttributeInstance> attributes = multiThreadedTrackingEnabled ? new java.util.concurrent.ConcurrentHashMap<>() : new it.unimi.dsi.fastutil.objects.Reference2ReferenceOpenHashMap<>(0);
|
|
+ private final Set<AttributeInstance> attributesToSync = multiThreadedTrackingEnabled ? com.google.common.collect.Sets.newConcurrentHashSet() : new it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet<>(0);
|
|
+ private final Set<AttributeInstance> attributesToUpdate = multiThreadedTrackingEnabled ? com.google.common.collect.Sets.newConcurrentHashSet() : new it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet<>(0);
|
|
// Gale end - Lithium - replace AI attributes with optimized collections
|
|
+ // 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
|
|
diff --git a/src/main/java/org/dreeam/leaf/async/tracker/MultithreadedTracker.java b/src/main/java/org/dreeam/leaf/async/tracker/MultithreadedTracker.java
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..4f214a8f058434cb7f4930df75fc3d0f310878ae
|
|
--- /dev/null
|
|
+++ b/src/main/java/org/dreeam/leaf/async/tracker/MultithreadedTracker.java
|
|
@@ -0,0 +1,182 @@
|
|
+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 ca.spottedleaf.moonrise.patches.entity_tracker.EntityTrackerTrackedEntity;
|
|
+import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
|
+import net.minecraft.server.level.ChunkMap;
|
|
+import net.minecraft.server.level.ServerLevel;
|
|
+import net.minecraft.world.entity.Entity;
|
|
+import org.apache.logging.log4j.LogManager;
|
|
+import org.apache.logging.log4j.Logger;
|
|
+
|
|
+import java.util.Arrays;
|
|
+import java.util.concurrent.Executor;
|
|
+import java.util.concurrent.LinkedBlockingQueue;
|
|
+import java.util.concurrent.ThreadPoolExecutor;
|
|
+import java.util.concurrent.TimeUnit;
|
|
+
|
|
+public class MultithreadedTracker {
|
|
+
|
|
+ private static final Logger LOGGER = LogManager.getLogger("MultithreadedTracker");
|
|
+ public static class MultithreadedTrackerThread extends Thread {
|
|
+ @Override
|
|
+ public void run() {
|
|
+ super.run();
|
|
+ }
|
|
+ }
|
|
+ private static final Executor trackerExecutor = new ThreadPoolExecutor(
|
|
+ 1,
|
|
+ org.dreeam.leaf.config.modules.async.MultithreadedTracker.asyncEntityTrackerMaxThreads,
|
|
+ org.dreeam.leaf.config.modules.async.MultithreadedTracker.asyncEntityTrackerKeepalive, TimeUnit.SECONDS,
|
|
+ new LinkedBlockingQueue<>(),
|
|
+ new ThreadFactoryBuilder()
|
|
+ .setThreadFactory(
|
|
+ r -> new MultithreadedTrackerThread() {
|
|
+ @Override
|
|
+ public void run() {
|
|
+ r.run();
|
|
+ }
|
|
+ }
|
|
+ )
|
|
+ .setNameFormat("Leaf Async Tracker Thread - %d")
|
|
+ .setPriority(Thread.NORM_PRIORITY - 2)
|
|
+ .build());
|
|
+
|
|
+ private MultithreadedTracker() {
|
|
+ }
|
|
+
|
|
+ public static Executor getTrackerExecutor() {
|
|
+ return trackerExecutor;
|
|
+ }
|
|
+
|
|
+ public static void tick(ChunkSystemServerLevel level) {
|
|
+ try {
|
|
+ if (!org.dreeam.leaf.config.modules.async.MultithreadedTracker.compatModeEnabled) {
|
|
+ tickAsync(level);
|
|
+ } else {
|
|
+ tickAsyncWithCompatMode(level);
|
|
+ }
|
|
+ } catch (Exception e) {
|
|
+ LOGGER.error("Error occurred while executing async task.", e);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ private static void tickAsync(ChunkSystemServerLevel 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();
|
|
+
|
|
+ // Move tracking to off-main
|
|
+ trackerExecutor.execute(() -> {
|
|
+ for (final Entity entity : trackerEntitiesRaw) {
|
|
+ if (entity == null) continue;
|
|
+
|
|
+ final ChunkMap.TrackedEntity tracker = ((EntityTrackerEntity) entity).moonrise$getTrackedEntity();
|
|
+
|
|
+ if (tracker == null) continue;
|
|
+
|
|
+ ((EntityTrackerTrackedEntity) tracker).moonrise$tick(nearbyPlayers.getChunk(entity.chunkPosition()));
|
|
+ tracker.serverEntity.sendChanges();
|
|
+ }
|
|
+ });
|
|
+
|
|
+ // process unloads
|
|
+ final ReferenceList<Entity> unloadedEntities = entityLookup.trackerUnloadedEntities;
|
|
+ final Entity[] unloadedEntitiesRaw = Arrays.copyOf(unloadedEntities.getRawDataUnchecked(), unloadedEntities.size());
|
|
+ unloadedEntities.clear();
|
|
+
|
|
+ // Move player unload to off-main
|
|
+ trackerExecutor.execute(() -> {
|
|
+ for (final Entity entity : unloadedEntitiesRaw) {
|
|
+ final ChunkMap.TrackedEntity tracker = ((EntityTrackerEntity) entity).moonrise$getTrackedEntity();
|
|
+
|
|
+ if (tracker == null) continue;
|
|
+
|
|
+ ((EntityTrackerTrackedEntity) tracker).moonrise$clearPlayers();
|
|
+ }
|
|
+ });
|
|
+ }
|
|
+
|
|
+ private static void tickAsyncWithCompatMode(ChunkSystemServerLevel 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];
|
|
+ int index = 0;
|
|
+
|
|
+ for (final Entity entity : trackerEntitiesRaw) {
|
|
+ if (entity == null) continue;
|
|
+
|
|
+ final ChunkMap.TrackedEntity tracker = ((EntityTrackerEntity) entity).moonrise$getTrackedEntity();
|
|
+
|
|
+ if (tracker == null) continue;
|
|
+
|
|
+ ((EntityTrackerTrackedEntity) tracker).moonrise$tick(nearbyPlayers.getChunk(entity.chunkPosition()));
|
|
+ sendChangesTasks[index++] = () -> tracker.serverEntity.sendChanges(); // Collect send changes to task array
|
|
+ }
|
|
+
|
|
+ // batch submit tasks
|
|
+ trackerExecutor.execute(() -> {
|
|
+ for (final Runnable sendChanges : sendChangesTasks) {
|
|
+ if (sendChanges == null) continue;
|
|
+
|
|
+ sendChanges.run();
|
|
+ }
|
|
+ });
|
|
+
|
|
+ // process unloads
|
|
+ final ReferenceList<Entity> unloadedEntities = entityLookup.trackerUnloadedEntities;
|
|
+ final Entity[] unloadedEntitiesRaw = Arrays.copyOf(unloadedEntities.getRawDataUnchecked(), unloadedEntities.size());
|
|
+ unloadedEntities.clear();
|
|
+
|
|
+ trackerExecutor.execute(() -> {
|
|
+ for (final Entity entity : unloadedEntitiesRaw) {
|
|
+ final ChunkMap.TrackedEntity tracker = ((EntityTrackerEntity) entity).moonrise$getTrackedEntity();
|
|
+
|
|
+ if (tracker == null) continue;
|
|
+
|
|
+ ((EntityTrackerTrackedEntity) tracker).moonrise$clearPlayers();
|
|
+ }
|
|
+ });
|
|
+ }
|
|
+
|
|
+ // Original ChunkMap#newTrackerTick of Paper
|
|
+ // Just for diff usage for future update
|
|
+ private static void tickOriginal(ServerLevel level) {
|
|
+ final ca.spottedleaf.moonrise.common.misc.NearbyPlayers nearbyPlayers = ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel) level).moonrise$getNearbyPlayers();
|
|
+ final ca.spottedleaf.moonrise.patches.chunk_system.level.entity.server.ServerEntityLookup entityLookup = (ca.spottedleaf.moonrise.patches.chunk_system.level.entity.server.ServerEntityLookup) ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel) level).moonrise$getEntityLookup();
|
|
+
|
|
+ final ca.spottedleaf.moonrise.common.list.ReferenceList<net.minecraft.world.entity.Entity> trackerEntities = entityLookup.trackerEntities;
|
|
+ final Entity[] trackerEntitiesRaw = trackerEntities.getRawDataUnchecked();
|
|
+ for (int i = 0, len = trackerEntities.size(); i < len; ++i) {
|
|
+ final Entity entity = trackerEntitiesRaw[i];
|
|
+ final ChunkMap.TrackedEntity tracker = ((ca.spottedleaf.moonrise.patches.entity_tracker.EntityTrackerEntity) entity).moonrise$getTrackedEntity();
|
|
+ if (tracker == null) {
|
|
+ continue;
|
|
+ }
|
|
+ ((ca.spottedleaf.moonrise.patches.entity_tracker.EntityTrackerTrackedEntity) tracker).moonrise$tick(nearbyPlayers.getChunk(entity.chunkPosition()));
|
|
+ tracker.serverEntity.sendChanges();
|
|
+ }
|
|
+
|
|
+ // process unloads
|
|
+ final ca.spottedleaf.moonrise.common.list.ReferenceList<net.minecraft.world.entity.Entity> unloadedEntities = entityLookup.trackerUnloadedEntities;
|
|
+ final Entity[] unloadedEntitiesRaw = java.util.Arrays.copyOf(unloadedEntities.getRawDataUnchecked(), unloadedEntities.size());
|
|
+ unloadedEntities.clear();
|
|
+
|
|
+ for (final Entity entity : unloadedEntitiesRaw) {
|
|
+ final ChunkMap.TrackedEntity tracker = ((ca.spottedleaf.moonrise.patches.entity_tracker.EntityTrackerEntity) entity).moonrise$getTrackedEntity();
|
|
+ if (tracker == null) {
|
|
+ continue;
|
|
+ }
|
|
+ ((ca.spottedleaf.moonrise.patches.entity_tracker.EntityTrackerTrackedEntity) tracker).moonrise$clearPlayers();
|
|
+ }
|
|
+ }
|
|
+}
|
|
diff --git a/src/main/java/org/dreeam/leaf/config/modules/async/MultithreadedTracker.java b/src/main/java/org/dreeam/leaf/config/modules/async/MultithreadedTracker.java
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..bf253dbdcff603a4ebce181bf75131dd8aa52721
|
|
--- /dev/null
|
|
+++ b/src/main/java/org/dreeam/leaf/config/modules/async/MultithreadedTracker.java
|
|
@@ -0,0 +1,48 @@
|
|
+package org.dreeam.leaf.config.modules.async;
|
|
+
|
|
+import org.dreeam.leaf.config.ConfigModules;
|
|
+import org.dreeam.leaf.config.EnumConfigCategory;
|
|
+import org.dreeam.leaf.config.LeafConfig;
|
|
+
|
|
+public class MultithreadedTracker extends ConfigModules {
|
|
+
|
|
+ public String getBasePath() {
|
|
+ return EnumConfigCategory.ASYNC.getBaseKeyName() + ".async-entity-tracker";
|
|
+ }
|
|
+
|
|
+ public static boolean enabled = false;
|
|
+ public static boolean compatModeEnabled = false;
|
|
+ public static int asyncEntityTrackerMaxThreads = 0;
|
|
+ public static int asyncEntityTrackerKeepalive = 60;
|
|
+
|
|
+ @Override
|
|
+ public void onLoaded() {
|
|
+ config.addCommentRegionBased(getBasePath(), """
|
|
+ Make entity tracking saving asynchronously, can improve performance significantly,
|
|
+ especially in some massive entities in small area situations.""",
|
|
+ """
|
|
+ 异步实体跟踪,
|
|
+ 在实体数量多且密集的情况下效果明显.""");
|
|
+
|
|
+ enabled = config().getBoolean(getBasePath() + ".enabled", enabled);
|
|
+ compatModeEnabled = config.getBoolean(getBasePath() + ".compat-mode", compatModeEnabled, config.pickStringRegionBased("""
|
|
+ Enable compat mode ONLY if Citizens or NPC plugins using real entity has installed,
|
|
+ Compat mode fixed visible issue with player type NPCs of Citizens,
|
|
+ But still recommend to use packet based / virtual entity NPC plugin, e.g. ZNPC Plus, Adyeshach, Fancy NPC or else.""",
|
|
+ """
|
|
+ 是否启用兼容模式,
|
|
+ 如果你的服务器安装了 Citizens 或其他类似非发包 NPC 插件, 请开启此项."""));
|
|
+ asyncEntityTrackerMaxThreads = config.getInt(getBasePath() + ".max-threads", asyncEntityTrackerMaxThreads);
|
|
+ asyncEntityTrackerKeepalive = config.getInt(getBasePath() + ".keepalive", asyncEntityTrackerKeepalive);
|
|
+
|
|
+ if (asyncEntityTrackerMaxThreads < 0)
|
|
+ asyncEntityTrackerMaxThreads = Math.max(Runtime.getRuntime().availableProcessors() + asyncEntityTrackerMaxThreads, 1);
|
|
+ else if (asyncEntityTrackerMaxThreads == 0)
|
|
+ asyncEntityTrackerMaxThreads = Math.max(Runtime.getRuntime().availableProcessors() / 4, 1);
|
|
+
|
|
+ if (!enabled)
|
|
+ asyncEntityTrackerMaxThreads = 0;
|
|
+ else
|
|
+ LeafConfig.LOGGER.info("Using {} threads for Async Entity Tracker", asyncEntityTrackerMaxThreads);
|
|
+ }
|
|
+}
|