mirror of
https://github.com/Samsuik/Sakura.git
synced 2025-12-21 07:49:29 +00:00
419 lines
21 KiB
Diff
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
|
|
}
|
|
|
|
}
|