9
0
mirror of https://github.com/Samsuik/Sakura.git synced 2025-12-21 07:49:29 +00:00
Files
SakuraMC/patches/server/0071-Async-Entity-Tracking.patch
2024-05-27 18:40:18 +01:00

419 lines
21 KiB
Diff

From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Samsuik <kfian294ma4@gmail.com>
Date: Mon, 27 May 2024 18:02:27 +0100
Subject: [PATCH] Async Entity Tracking
diff --git a/src/main/java/me/samsuik/sakura/player/tracking/AsyncEntityTracker.java b/src/main/java/me/samsuik/sakura/player/tracking/AsyncEntityTracker.java
new file mode 100644
index 0000000000000000000000000000000000000000..54422e6e4bf0860ea504414141821cca7c072b28
--- /dev/null
+++ b/src/main/java/me/samsuik/sakura/player/tracking/AsyncEntityTracker.java
@@ -0,0 +1,101 @@
+package me.samsuik.sakura.player.tracking;
+
+import com.google.common.collect.Iterators;
+import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet;
+import net.minecraft.server.level.ChunkMap;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ThreadFactory;
+
+public final class AsyncEntityTracker {
+ private static final int AVAILABLE_THREADS = Math.max(Runtime.getRuntime().availableProcessors() / 5 * 2, 1); // 0.4
+ private static final ThreadFactory THREAD_FACTORY = EntityTrackerThread::new;
+ private static ExecutorService TRACKER_EXECUTOR = null;
+
+ private final ChunkMap chunkMap;
+ private volatile boolean processingTick;
+
+ public AsyncEntityTracker(ChunkMap chunkMap) {
+ this.chunkMap = chunkMap;
+ }
+
+ private static void tryStartService() {
+ if (TRACKER_EXECUTOR == null) {
+ TRACKER_EXECUTOR = Executors.newFixedThreadPool(AVAILABLE_THREADS, THREAD_FACTORY);
+ }
+ }
+
+ public void cycle() {
+ if (this.processingTick) {
+ return; // uh oh.
+ }
+
+ tryStartService();
+ this.processingTick = true;
+
+ TrackedEntities trackedEntities = new TrackedEntities(this.chunkMap);
+ this.updatePlayersInParallel(trackedEntities);
+
+ TRACKER_EXECUTOR.execute(() -> {
+ try {
+ this.processEntities(trackedEntities);
+ } finally {
+ this.processingTick = false;
+ }
+ });
+ }
+
+ private void updatePlayersInParallel(TrackedEntities trackedEntities) {
+ trackedEntities.getEntities().parallelStream().forEach(tracker -> {
+ if (!tracker.isActive) return; // removed entity
+ synchronized (tracker.entity) {
+ if (tracker.shouldLookForPlayers()) {
+ tracker.updatePlayers(tracker.entity.getPlayersInTrackRange());
+ }
+ }
+ if (tracker.entity.isPassengersDirty.getAndSet(false)) {
+ trackedEntities.markEntityAsUpdated(tracker);
+ }
+ });
+ }
+
+ private void processEntities(TrackedEntities trackedEntities) {
+ for (ChunkMap.TrackedEntity tracker : trackedEntities) {
+ if (!tracker.isActive) continue; // removed entity
+ tracker.seenByLock.readLock().lock();
+ synchronized (tracker.entity) {
+ tracker.serverEntity.sendChanges();
+ }
+ tracker.seenByLock.readLock().unlock();
+ }
+ }
+
+ private static class TrackedEntities implements Iterable<ChunkMap.TrackedEntity> {
+ private final List<ChunkMap.TrackedEntity> entities;
+ private final Set<ChunkMap.TrackedEntity> alreadyUpdated;
+
+ public TrackedEntities(ChunkMap chunkMap) {
+ this.entities = new ArrayList<>(chunkMap.entityMap.values());
+ this.alreadyUpdated = new ReferenceOpenHashSet<>();
+ }
+
+ public void markEntityAsUpdated(ChunkMap.TrackedEntity trackedEntity) {
+ this.alreadyUpdated.add(trackedEntity);
+ }
+
+ public List<ChunkMap.TrackedEntity> getEntities() {
+ return this.entities;
+ }
+
+ @Override
+ public @NotNull Iterator<ChunkMap.TrackedEntity> iterator() {
+ return Iterators.filter(this.entities.iterator(), e -> !this.alreadyUpdated.contains(e));
+ }
+ }
+}
diff --git a/src/main/java/me/samsuik/sakura/player/tracking/EntityTrackerThread.java b/src/main/java/me/samsuik/sakura/player/tracking/EntityTrackerThread.java
new file mode 100644
index 0000000000000000000000000000000000000000..a4736257bee2f2c6c51363ca54269d477ae42464
--- /dev/null
+++ b/src/main/java/me/samsuik/sakura/player/tracking/EntityTrackerThread.java
@@ -0,0 +1,12 @@
+package me.samsuik.sakura.player.tracking;
+
+import java.util.concurrent.atomic.AtomicInteger;
+
+public final class EntityTrackerThread extends Thread {
+ private static final AtomicInteger THREAD_COUNTER = new AtomicInteger(0);
+
+ public EntityTrackerThread(Runnable runnable) {
+ super(runnable);
+ this.setName("Entity Tracker Thread " + THREAD_COUNTER.getAndIncrement());
+ }
+}
diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java
index 64bf4398a8a0b429e5a7483cf8a24a02c58b7fb3..c9c1f69f942d58304ec592e62d678cf595905bb8 100644
--- a/src/main/java/net/minecraft/server/level/ChunkMap.java
+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java
@@ -1163,8 +1163,14 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
}
}
// Paper end - optimised tracker
-
+ // Sakura start - async entity tracking
+ private final me.samsuik.sakura.player.tracking.AsyncEntityTracker asyncEntityTracker = new me.samsuik.sakura.player.tracking.AsyncEntityTracker(this);
protected void tick() {
+ if (this.level.sakuraConfig().players.entityTracker.asyncTracking) {
+ this.asyncEntityTracker.cycle();
+ return;
+ }
+ // Sakura end - async entity tracking
// Paper start - optimized tracker
if (true) {
this.processTrackQueue();
@@ -1308,12 +1314,14 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
public class TrackedEntity {
public final ServerEntity serverEntity;
- final Entity entity;
+ public final Entity entity; // Sakura - package-protected -> public
private final int range;
SectionPos lastSectionPos;
public final Set<ServerPlayerConnection> seenBy = new it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet<>(); // Paper - Perf: optimise map impl
private final int playerSearchInterval; // Sakura - reduce entity tracker player updates
private Vec3 entityPosition; // Sakura - reduce entity tracker player updates
+ public final java.util.concurrent.locks.ReentrantReadWriteLock seenByLock = new java.util.concurrent.locks.ReentrantReadWriteLock(); // Sakura - async entity tracking
+ public volatile boolean isActive = true; // Sakura - async entity tracking
public TrackedEntity(Entity entity, int i, int j, boolean flag) {
this.serverEntity = new ServerEntity(ChunkMap.this.level, entity, j, flag, this::broadcast, this.seenBy); // CraftBukkit
@@ -1343,7 +1351,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
// Paper start - use distance map to optimise tracker
com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<ServerPlayer> lastTrackerCandidates;
- final void updatePlayers(com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<ServerPlayer> newTrackerCandidates) {
+ public final void updatePlayers(com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<ServerPlayer> newTrackerCandidates) { // Sakura - async entity tracking
com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<ServerPlayer> oldTrackerCandidates = this.lastTrackerCandidates;
this.lastTrackerCandidates = newTrackerCandidates;
@@ -1355,7 +1363,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
continue;
}
ServerPlayer player = (ServerPlayer)raw;
- this.updatePlayer(player);
+ this.sakura_updatePlayer(player); // Sakura - async entity tracking
}
}
@@ -1370,7 +1378,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
for (ServerPlayerConnection conn : this.seenBy.toArray(new ServerPlayerConnection[0])) { // avoid CME
if (newTrackerCandidates == null || !newTrackerCandidates.contains(conn.getPlayer())) {
- this.updatePlayer(conn.getPlayer());
+ this.sakura_updatePlayer(conn.getPlayer()); // Sakura - async entity tracking
}
}
}
@@ -1412,18 +1420,26 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
this.serverEntity.removePairing(serverplayerconnection.getPlayer());
}
+ this.isActive = false; // Sakura - async entity tracking
}
public void removePlayer(ServerPlayer player) {
org.spigotmc.AsyncCatcher.catchOp("player tracker clear"); // Spigot
+ this.seenByLock.writeLock().lock(); // Sakura - async entity tracking
if (this.seenBy.remove(player.connection)) {
this.serverEntity.removePairing(player);
}
+ this.seenByLock.writeLock().unlock(); // Sakura - async entity tracking
}
public void updatePlayer(ServerPlayer player) {
org.spigotmc.AsyncCatcher.catchOp("player tracker update"); // Spigot
+ // Sakura start - async entity tracking
+ this.sakura_updatePlayer(player);
+ }
+ private void sakura_updatePlayer(ServerPlayer player) {
+ // Sakura end - async entity tracking
if (player != this.entity) {
// Paper start - remove allocation of Vec3D here
// Vec3 vec3d = player.position().subtract(this.entity.position());
@@ -1466,6 +1482,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
flag = false;
}
// CraftBukkit end
+ this.seenByLock.writeLock().lock(); // Sakura - async entity tracking
if (flag) {
if (this.seenBy.add(player.connection)) {
// Paper start - entity tracking events
@@ -1477,6 +1494,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
} else if (this.seenBy.remove(player.connection)) {
this.serverEntity.removePairing(player);
}
+ this.seenByLock.writeLock().unlock(); // Sakura - async entity tracking
}
}
diff --git a/src/main/java/net/minecraft/server/level/ServerEntity.java b/src/main/java/net/minecraft/server/level/ServerEntity.java
index b37f568fbd39be15f08e00f4ea5f28738a1d99fe..aab41f2bc51d2a3766170293e6262f9bb7d3072a 100644
--- a/src/main/java/net/minecraft/server/level/ServerEntity.java
+++ b/src/main/java/net/minecraft/server/level/ServerEntity.java
@@ -94,6 +94,11 @@ public class ServerEntity {
}
public void sendChanges() {
+ // Sakura start - async entity tracking
+ this.sendChanges(this.entity.isPassengersDirty.getAndSet(false));
+ }
+ private void updateDirtyPassengers() {
+ // Sakura end - async entity tracking
List<Entity> list = this.entity.getPassengers();
if (!list.equals(this.lastPassengers)) {
@@ -108,7 +113,13 @@ public class ServerEntity {
});
this.lastPassengers = list;
}
-
+ // Sakura start - async entity tracking
+ }
+ public final void sendChanges(boolean isPassengersDirty) {
+ if (isPassengersDirty) {
+ this.updateDirtyPassengers();
+ }
+ // Sakura end - async entity tracking
Entity entity = this.entity;
if (!this.trackedPlayers.isEmpty() && entity instanceof ItemFrame) { // Paper - Perf: Only tick item frames if players can see it
diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java
index 2a9a6a9f00343f614a0d2430095a17088861eb1f..8d27289aa81aa60ba6bdde57b74fc4a0a76df4e5 100644
--- a/src/main/java/net/minecraft/server/level/ServerLevel.java
+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
@@ -923,7 +923,11 @@ public class ServerLevel extends Level implements WorldGenLevel {
// Sakura end
gameprofilerfiller.push("tick");
+ // Sakura start - async entity tracking
+ synchronized (entity) {
this.guardEntityTick(this::tickNonPassenger, entity);
+ }
+ // Sakura end - async entity tracking
gameprofilerfiller.pop();
}
}
diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java
index 08d33295967f66051b9e846d854ffac6fe885281..520f158dba52c22a33166e0ad88cffab31c426b4 100644
--- a/src/main/java/net/minecraft/world/entity/Entity.java
+++ b/src/main/java/net/minecraft/world/entity/Entity.java
@@ -301,6 +301,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S
private int id;
public boolean blocksBuilding;
public ImmutableList<Entity> passengers;
+ public final java.util.concurrent.atomic.AtomicBoolean isPassengersDirty = new java.util.concurrent.atomic.AtomicBoolean(); // Sakura - aysnc entity tracking
protected int boardingCooldown;
@Nullable
private Entity vehicle;
@@ -3421,6 +3422,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S
this.passengers = ImmutableList.copyOf(list);
}
+ this.isPassengersDirty.set(true); // Sakura - async entity tracking
this.gameEvent(GameEvent.ENTITY_MOUNT, passenger);
}
}
@@ -3469,6 +3471,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S
}
entity.boardingCooldown = 60;
+ this.isPassengersDirty.set(true); // Sakura - async entity tracking
this.gameEvent(GameEvent.ENTITY_DISMOUNT, entity);
}
return true; // CraftBukkit
diff --git a/src/main/java/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java b/src/main/java/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java
index 45269115e63cfc3bd7dc740a5694e2cc7c35bcb1..fd782a72f934e2de943d664568ccc7293b980e69 100644
--- a/src/main/java/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java
+++ b/src/main/java/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java
@@ -63,7 +63,7 @@ public class MapItemSavedData extends SavedData {
public final List<MapItemSavedData.HoldingPlayer> carriedBy = Lists.newArrayList();
public final Map<Player, MapItemSavedData.HoldingPlayer> carriedByPlayers = Maps.newHashMap();
private final Map<String, MapBanner> bannerMarkers = Maps.newHashMap();
- public final Map<String, MapDecoration> decorations = Maps.newLinkedHashMap();
+ public final Map<String, MapDecoration> decorations = java.util.Collections.synchronizedMap(Maps.newLinkedHashMap()); // Sakura - async entity tracking
private final Map<String, MapFrame> frameMarkers = Maps.newHashMap();
private int trackedDecorationCount;
private org.bukkit.craftbukkit.map.RenderData vanillaRender = new org.bukkit.craftbukkit.map.RenderData(); // Paper
@@ -584,6 +584,7 @@ public class MapItemSavedData extends SavedData {
// Paper start
private void addSeenPlayers(java.util.Collection<MapDecoration> icons) {
org.bukkit.entity.Player player = (org.bukkit.entity.Player) this.player.getBukkitEntity();
+ synchronized (MapItemSavedData.this.decorations) { // Sakura - async entity tracking
MapItemSavedData.this.decorations.forEach((name, mapIcon) -> {
// If this cursor is for a player check visibility with vanish system
org.bukkit.entity.Player other = org.bukkit.Bukkit.getPlayerExact(name); // Spigot
@@ -591,6 +592,7 @@ public class MapItemSavedData extends SavedData {
icons.add(mapIcon);
}
});
+ } // Sakura - async entity tracking
}
private boolean shouldUseVanillaMap() {
return mapView.getRenderers().size() == 1 && mapView.getRenderers().get(0).getClass() == org.bukkit.craftbukkit.map.CraftMapRenderer.class;
diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java
index 4731d10dd5e493af9564d38d8bf1ff223390bd75..b1904549b1b1180ebae822e63871f6e80d9100cc 100644
--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java
+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java
@@ -715,9 +715,11 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity {
ChunkMap.TrackedEntity entityTracker = world.getChunkSource().chunkMap.entityMap.get(this.getEntityId());
if (entityTracker != null) {
+ entityTracker.seenByLock.readLock().lock(); // Sakura - async entity tracking
for (ServerPlayerConnection connection : entityTracker.seenBy) {
players.add(connection.getPlayer().getBukkitEntity());
}
+ entityTracker.seenByLock.readLock().unlock(); // Sakura - async entity tracking
}
return players.build();
@@ -1013,9 +1015,11 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity {
}
// Paper start, resend possibly desynced entity instead of add entity packet
+ entityTracker.seenByLock.readLock().lock(); // Sakura - async entity tracking
for (ServerPlayerConnection playerConnection : entityTracker.seenBy) {
this.getHandle().getEntityData().resendPossiblyDesyncedEntity(playerConnection.getPlayer());
}
+ entityTracker.seenByLock.readLock().unlock(); // Sakura - async entity tracking
// Paper end
}
@@ -1183,10 +1187,12 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity {
return java.util.Collections.emptySet();
}
+ this.entity.tracker.seenByLock.readLock().lock(); // Sakura - async entity tracking
Set<org.bukkit.entity.Player> set = new java.util.HashSet<>(this.entity.tracker.seenBy.size());
for (net.minecraft.server.network.ServerPlayerConnection connection : this.entity.tracker.seenBy) {
set.add(connection.getPlayer().getBukkitEntity().getPlayer());
}
+ this.entity.tracker.seenByLock.readLock().unlock(); // Sakura - async entity tracking
return set;
}
// Paper end - tracked players API
diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java
index 29f0c4c3fd9185bf8768572c135b50a9db34dbbe..cd462589ab90bf8dfd4a213f49c1257c8152e137 100644
--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java
+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java
@@ -2060,9 +2060,11 @@ public class CraftPlayer extends CraftHumanEntity implements Player {
}
ChunkMap.TrackedEntity entry = tracker.entityMap.get(other.getId());
+ entry.seenByLock.readLock().lock(); // Sakura - async entity tracking
if (entry != null && !entry.seenBy.contains(this.getHandle().connection)) {
entry.updatePlayer(this.getHandle());
}
+ entry.seenByLock.readLock().unlock(); // Sakura - async entity tracking
this.server.getPluginManager().callEvent(new PlayerShowEntityEvent(this, entity));
}
diff --git a/src/main/java/org/bukkit/craftbukkit/map/CraftMapRenderer.java b/src/main/java/org/bukkit/craftbukkit/map/CraftMapRenderer.java
index 15e9dd8844f893de5e8372b847c9e8295d6f69ca..8948474a27b70ed00a2eb0fc6db3beadd2a083ee 100644
--- a/src/main/java/org/bukkit/craftbukkit/map/CraftMapRenderer.java
+++ b/src/main/java/org/bukkit/craftbukkit/map/CraftMapRenderer.java
@@ -34,6 +34,7 @@ public class CraftMapRenderer extends MapRenderer {
cursors.removeCursor(cursors.getCursor(0));
}
+ synchronized (this.worldMap.decorations) { // Sakura - async entity tracking
for (String key : this.worldMap.decorations.keySet()) {
// If this cursor is for a player check visibility with vanish system
Player other = Bukkit.getPlayerExact((String) key);
@@ -44,6 +45,7 @@ public class CraftMapRenderer extends MapRenderer {
MapDecoration decoration = this.worldMap.decorations.get(key);
cursors.addCursor(decoration.x(), decoration.y(), (byte) (decoration.rot() & 15), decoration.type().getIcon(), true, decoration.name() == null ? null : io.papermc.paper.adventure.PaperAdventure.asAdventure(decoration.name())); // Paper
}
+ } // Sakura - async entity tracking
}
}