mirror of
https://github.com/BX-Team/DivineMC.git
synced 2025-12-19 14:59:25 +00:00
add back multithreaded tracker
This commit is contained in:
@@ -1,544 +0,0 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: NONPLAYT <76615486+NONPLAYT@users.noreply.github.com>
|
||||
Date: Mon, 16 Dec 2024 20:43:11 +0300
|
||||
Subject: [PATCH] Multithreaded Tracker
|
||||
|
||||
|
||||
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 2219edf3e56a2eade1049f448360247f48e3c52a..6f50be80a88504d0948ce9635eadfee9a208ae82 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()) {
|
||||
+ // DivineMC start - Multithreaded tracker
|
||||
+ if (space.bxteam.divinemc.configuration.DivineConfig.multithreadedEnabled) {
|
||||
+ net.minecraft.server.MinecraftServer.getServer().scheduleOnMain(event::callEvent);
|
||||
+ return;
|
||||
+ }
|
||||
+ // DivineMC end
|
||||
throw new IllegalStateException(event.getEventName() + " may only be triggered synchronously.");
|
||||
}
|
||||
|
||||
diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java
|
||||
index 7ef101bdb4e9b37bd2e73247ef916769789de2ea..9d344cc8b50a70efdd325a7b7fd8768f73285f58 100644
|
||||
--- a/src/main/java/net/minecraft/server/level/ChunkMap.java
|
||||
+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java
|
||||
@@ -243,9 +243,19 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
|
||||
return;
|
||||
}
|
||||
final ServerPlayer[] backingSet = inRange.getRawDataUnchecked();
|
||||
- for (int i = 0, len = inRange.size(); i < len; i++) {
|
||||
- ++(backingSet[i].mobCounts[index]);
|
||||
+ // DivineMC start - Multithreaded tracker
|
||||
+ if (space.bxteam.divinemc.configuration.DivineConfig.multithreadedEnabled) {
|
||||
+ for (int i = 0, len = inRange.size(); i < len; i++) {
|
||||
+ final ServerPlayer player = backingSet[i];
|
||||
+ if (player == null) continue;
|
||||
+ ++(player.mobCounts[index]);
|
||||
+ }
|
||||
+ } else {
|
||||
+ for (int i = 0, len = inRange.size(); i < len; i++) {
|
||||
+ ++(backingSet[i].mobCounts[index]);
|
||||
+ }
|
||||
}
|
||||
+ // DivineMC end
|
||||
}
|
||||
// Paper start - per player mob count backoff
|
||||
public void updateFailurePlayerMobTypeMap(int chunkX, int chunkZ, net.minecraft.world.entity.MobCategory mobCategory) {
|
||||
@@ -956,6 +966,21 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
|
||||
((ca.spottedleaf.moonrise.patches.entity_tracker.EntityTrackerEntity)entity).moonrise$setTrackedEntity(null); // Paper - optimise entity tracker
|
||||
}
|
||||
|
||||
+ // DivineMC start - 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();
|
||||
+ }
|
||||
+ }
|
||||
+ // DivineMC end
|
||||
+
|
||||
// Paper start - optimise entity tracker
|
||||
private void newTrackerTick() {
|
||||
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)this.level).moonrise$getEntityLookup();;
|
||||
@@ -978,6 +1003,13 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
|
||||
// Paper end - optimise entity tracker
|
||||
|
||||
protected void tick() {
|
||||
+ // DivineMC start - Multithreaded tracker
|
||||
+ if (space.bxteam.divinemc.configuration.DivineConfig.multithreadedEnabled) {
|
||||
+ final ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel level = this.level;
|
||||
+ space.bxteam.divinemc.tracker.MultithreadedTracker.tick(level);
|
||||
+ return;
|
||||
+ }
|
||||
+ // DivineMC end
|
||||
// Paper start - optimise entity tracker
|
||||
if (true) {
|
||||
this.newTrackerTick();
|
||||
@@ -1127,7 +1159,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 = space.bxteam.divinemc.configuration.DivineConfig.multithreadedEnabled
|
||||
+ ? Sets.newConcurrentHashSet()
|
||||
+ : new it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet<>(); // Paper - Perf: optimise map impl // DivineMC - Multithreaded tracker
|
||||
|
||||
// Paper start - optimise entity tracker
|
||||
private long lastChunkUpdate = -1L;
|
||||
@@ -1154,21 +1188,55 @@ 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
|
||||
+
|
||||
+ // DivineMC start - Multithreaded tracker
|
||||
+ if (space.bxteam.divinemc.configuration.DivineConfig.multithreadedEnabled && space.bxteam.divinemc.configuration.DivineConfig.multithreadedCompatModeEnabled) {
|
||||
+ final boolean isServerPlayer = this.entity instanceof ServerPlayer;
|
||||
+ final boolean isRealPlayer = isServerPlayer && ((ca.spottedleaf.moonrise.patches.chunk_system.player.ChunkSystemServerPlayer) this.entity).moonrise$isRealPlayer();
|
||||
+ Runnable updatePlayerTasks = () -> {
|
||||
+ for (int i = 0; i < playersLen; ++i) {
|
||||
+ final ServerPlayer player = playersRaw[i];
|
||||
+ this.updatePlayer(player);
|
||||
+ }
|
||||
|
||||
- for (int i = 0, len = players.size(); i < len; ++i) {
|
||||
- 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) {
|
||||
+ space.bxteam.divinemc.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);
|
||||
+ }
|
||||
|
||||
- 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);
|
||||
+ 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);
|
||||
+ }
|
||||
}
|
||||
}
|
||||
}
|
||||
+ // DivineMC end
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1228,14 +1296,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();
|
||||
-
|
||||
+ // DivineMC start - Multithreaded tracker
|
||||
+ for (ServerPlayerConnection serverplayerconnection : this.seenBy.toArray(new ServerPlayerConnection[0])) {
|
||||
serverplayerconnection.send(packet);
|
||||
}
|
||||
-
|
||||
+ // DivineMC end
|
||||
}
|
||||
|
||||
public void broadcastAndSend(Packet<?> packet) {
|
||||
@@ -1247,18 +1312,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();
|
||||
-
|
||||
+ // DivineMC start - Multithreaded tracker
|
||||
+ for (ServerPlayerConnection serverplayerconnection : this.seenBy.toArray(new ServerPlayerConnection[0])) {
|
||||
this.serverEntity.removePairing(serverplayerconnection.getPlayer());
|
||||
}
|
||||
-
|
||||
+ // DivineMC end
|
||||
}
|
||||
|
||||
public void removePlayer(ServerPlayer player) {
|
||||
- org.spigotmc.AsyncCatcher.catchOp("player tracker clear"); // Spigot
|
||||
+ //org.spigotmc.AsyncCatcher.catchOp("player tracker clear"); // Spigot // DivineMC - Multithreaded tracker - we don't need this
|
||||
if (this.seenBy.remove(player.connection)) {
|
||||
this.serverEntity.removePairing(player);
|
||||
}
|
||||
@@ -1266,8 +1328,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 // DivineMC - Multithreaded tracker - we don't need this
|
||||
if (player != this.entity) {
|
||||
+ if (space.bxteam.divinemc.configuration.DivineConfig.multithreadedEnabled && player == null) return; // DivineMC - 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..39dada67dd2ed13f7f191e42c366f4a96767f275 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 = space.bxteam.divinemc.configuration.DivineConfig.multithreadedEnabled
|
||||
+ ? Sets.newConcurrentHashSet()
|
||||
+ : Sets.newHashSet(); // DivineMC - 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 944de5ff109b15b76b69326e69b6b1c2afb63dd1..415fe9e2bf9a110021edbdc39e4498ccc8366636 100644
|
||||
--- a/src/main/java/net/minecraft/server/level/ServerEntity.java
|
||||
+++ b/src/main/java/net/minecraft/server/level/ServerEntity.java
|
||||
@@ -119,7 +119,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());
|
||||
+ // DivineMC start - Multithreaded tracker - Ensure teleport is executed on server thread
|
||||
+ if (space.bxteam.divinemc.configuration.DivineConfig.multithreadedEnabled && Thread.currentThread() instanceof space.bxteam.divinemc.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());
|
||||
+ }
|
||||
+ // DivineMC end
|
||||
}
|
||||
|
||||
});
|
||||
@@ -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()}));
|
||||
+ // DivineMC start - Multithreaded tracker - send in main thread
|
||||
+ ((ServerLevel) this.entity.level()).chunkSource.chunkMap.runOnTrackerMainThread(() ->
|
||||
+ player.connection.send(new ClientboundRemoveEntitiesPacket(this.entity.getId()))
|
||||
+ );
|
||||
+ // DivineMC end
|
||||
}
|
||||
|
||||
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));
|
||||
+ // DivineMC start - Multithreaded tracker - send in main thread
|
||||
+ ((ServerLevel) this.entity.level()).chunkSource.chunkMap.runOnTrackerMainThread(() ->
|
||||
+ player.connection.send(new ClientboundBundlePacket(list))
|
||||
+ );
|
||||
+ // DivineMC end
|
||||
this.entity.startSeenByPlayer(player);
|
||||
}
|
||||
|
||||
@@ -451,7 +465,10 @@ public class ServerEntity {
|
||||
|
||||
if (list != null) {
|
||||
this.trackedDataValues = datawatcher.getNonDefaultValues();
|
||||
- this.broadcastAndSend(new ClientboundSetEntityDataPacket(this.entity.getId(), list));
|
||||
+ // DivineMC start - Multithreaded tracker - send in main thread
|
||||
+ ((ServerLevel) this.entity.level()).chunkSource.chunkMap.runOnTrackerMainThread(() ->
|
||||
+ this.broadcastAndSend(new ClientboundSetEntityDataPacket(this.entity.getId(), list))
|
||||
+ );
|
||||
}
|
||||
|
||||
if (this.entity instanceof LivingEntity) {
|
||||
@@ -461,12 +478,16 @@ public class ServerEntity {
|
||||
// DivineMC end
|
||||
|
||||
if (!set.isEmpty()) {
|
||||
+ 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));
|
||||
+ });
|
||||
+ // DivineMC end
|
||||
}
|
||||
|
||||
attributes.clear(); // DivineMC
|
||||
diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java
|
||||
index b0095c0848ca0162944961a24c7b807fb5846b06..8b812d1ec336fa30cd779d5ebb02b5811c97d369 100644
|
||||
--- a/src/main/java/net/minecraft/server/level/ServerLevel.java
|
||||
+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
|
||||
@@ -2604,7 +2604,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 // DivineMC - 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 ac1d8c5eb616d11fc1bda929a8607daf2d616b46..40e2da69d6907bf664ef37fe8dbe0951b03d3aec 100644
|
||||
--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
|
||||
+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
|
||||
@@ -1888,7 +1888,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl
|
||||
}
|
||||
|
||||
public void internalTeleport(PositionMoveRotation positionmoverotation, Set<Relative> set) {
|
||||
- org.spigotmc.AsyncCatcher.catchOp("teleport"); // Paper
|
||||
+ //org.spigotmc.AsyncCatcher.catchOp("teleport"); // Paper // DivineMC - 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 27a7852a5d3f8c8960f098646ff5587c50556aa5..ac9534203a256b2a5c1d94e4b3996990e8861aca 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<>();
|
||||
+ // DivineMC start - Multithreaded tracker
|
||||
+ private final boolean multiThreadedTrackingEnabled = space.bxteam.divinemc.configuration.DivineConfig.multithreadedEnabled;
|
||||
+ 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<>();
|
||||
+ // DivineMC end
|
||||
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 98fa43c8a34650795a0ae1ebc28ce17ec1ba5271..3b27e866185f6dcd1c7baaa3fdef591f1a0bdac3 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,9 +19,12 @@ import org.slf4j.Logger;
|
||||
|
||||
public class AttributeMap {
|
||||
private static final Logger LOGGER = LogUtils.getLogger();
|
||||
- private final Map<Holder<Attribute>, AttributeInstance> attributes = new Object2ObjectOpenHashMap<>();
|
||||
- private final Set<AttributeInstance> attributesToSync = new ObjectOpenHashSet<>();
|
||||
- private final Set<AttributeInstance> attributesToUpdate = new ObjectOpenHashSet<>();
|
||||
+ // DivineMC start - Multithreaded tracker
|
||||
+ private final boolean multiThreadedTrackingEnabled = space.bxteam.divinemc.configuration.DivineConfig.multithreadedEnabled;
|
||||
+ 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);
|
||||
+ // DivineMC end
|
||||
private final AttributeSupplier supplier;
|
||||
private final java.util.function.Function<Holder<Attribute>, AttributeInstance> createInstance; // Pufferfish
|
||||
private final net.minecraft.world.entity.LivingEntity entity; // Purpur - Ridables
|
||||
diff --git a/src/main/java/space/bxteam/divinemc/configuration/DivineConfig.java b/src/main/java/space/bxteam/divinemc/configuration/DivineConfig.java
|
||||
index ed44eaad59afa76d8ca8ac74d0cee4fb681dfe83..fe9bff9d542d731aede4903e807acd876b0286d0 100644
|
||||
--- a/src/main/java/space/bxteam/divinemc/configuration/DivineConfig.java
|
||||
+++ b/src/main/java/space/bxteam/divinemc/configuration/DivineConfig.java
|
||||
@@ -196,4 +196,25 @@ public class DivineConfig {
|
||||
else
|
||||
Bukkit.getLogger().log(Level.INFO, "Using " + asyncPathfindingMaxThreads + " threads for Async Pathfinding");
|
||||
}
|
||||
+
|
||||
+ public static boolean multithreadedEnabled = false;
|
||||
+ public static boolean multithreadedCompatModeEnabled = false;
|
||||
+ public static int asyncEntityTrackerMaxThreads = 0;
|
||||
+ public static int asyncEntityTrackerKeepalive = 60;
|
||||
+ private static void multithreadedTracker() {
|
||||
+ multithreadedEnabled = getBoolean("settings.multithreaded-tracker.enable", multithreadedEnabled);
|
||||
+ multithreadedCompatModeEnabled = getBoolean("settings.multithreaded-tracker.compat-mode", multithreadedCompatModeEnabled);
|
||||
+ asyncEntityTrackerMaxThreads = getInt("settings.multithreaded-tracker.max-threads", asyncEntityTrackerMaxThreads);
|
||||
+ asyncEntityTrackerKeepalive = getInt("settings.multithreaded-tracker.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 (!multithreadedEnabled)
|
||||
+ asyncEntityTrackerMaxThreads = 0;
|
||||
+ else
|
||||
+ Bukkit.getLogger().log(Level.INFO, "Using " + asyncEntityTrackerMaxThreads + " threads for Async Entity Tracker");
|
||||
+ }
|
||||
}
|
||||
diff --git a/src/main/java/space/bxteam/divinemc/tracker/MultithreadedTracker.java b/src/main/java/space/bxteam/divinemc/tracker/MultithreadedTracker.java
|
||||
new file mode 100644
|
||||
index 0000000000000000000000000000000000000000..3621d8641eb657d84b886589bc957bdf8798785a
|
||||
--- /dev/null
|
||||
+++ b/src/main/java/space/bxteam/divinemc/tracker/MultithreadedTracker.java
|
||||
@@ -0,0 +1,139 @@
|
||||
+package space.bxteam.divinemc.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.FullChunkStatus;
|
||||
+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.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,
|
||||
+ space.bxteam.divinemc.configuration.DivineConfig.asyncEntityTrackerMaxThreads,
|
||||
+ space.bxteam.divinemc.configuration.DivineConfig.asyncEntityTrackerKeepalive, TimeUnit.SECONDS,
|
||||
+ new LinkedBlockingQueue<>(),
|
||||
+ new ThreadFactoryBuilder()
|
||||
+ .setThreadFactory(
|
||||
+ r -> new MultithreadedTrackerThread() {
|
||||
+ @Override
|
||||
+ public void run() {
|
||||
+ r.run();
|
||||
+ }
|
||||
+ }
|
||||
+ )
|
||||
+ .setNameFormat("DivineMC 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 (!space.bxteam.divinemc.configuration.DivineConfig.multithreadedCompatModeEnabled) {
|
||||
+ 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();
|
||||
+ }
|
||||
+ });
|
||||
+ }
|
||||
+
|
||||
+ 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();
|
||||
+ }
|
||||
+ });
|
||||
+ }
|
||||
+
|
||||
+ // Original ChunkMap#newTrackerTick of Paper
|
||||
+ // Just for diff usage for future update
|
||||
+ private static void tickOriginal(ServerLevel level) {
|
||||
+ 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(((ca.spottedleaf.moonrise.patches.chunk_system.entity.ChunkSystemEntity) entity).moonrise$getChunkData().nearbyPlayers);
|
||||
+ if (((ca.spottedleaf.moonrise.patches.entity_tracker.EntityTrackerTrackedEntity) tracker).moonrise$hasPlayers()
|
||||
+ || ((ca.spottedleaf.moonrise.patches.chunk_system.entity.ChunkSystemEntity) entity).moonrise$getChunkStatus().isOrAfter(FullChunkStatus.ENTITY_TICKING)) {
|
||||
+ tracker.serverEntity.sendChanges();
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+}
|
||||
@@ -0,0 +1,335 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: NONPLAYT <76615486+NONPLAYT@users.noreply.github.com>
|
||||
Date: Wed, 15 Jan 2025 19:48:24 +0300
|
||||
Subject: [PATCH] Multithreaded Tracker
|
||||
|
||||
|
||||
diff --git a/net/minecraft/server/level/ChunkMap.java b/net/minecraft/server/level/ChunkMap.java
|
||||
index 718487155b923bfffa215b70a12d42681a8ae141..8c67ee3d5eaa7ab02f8b22c2e41af0b1d59bcdcd 100644
|
||||
--- a/net/minecraft/server/level/ChunkMap.java
|
||||
+++ b/net/minecraft/server/level/ChunkMap.java
|
||||
@@ -250,9 +250,19 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
|
||||
}
|
||||
|
||||
final ServerPlayer[] backingSet = inRange.getRawDataUnchecked();
|
||||
- for (int i = 0, len = inRange.size(); i < len; i++) {
|
||||
- ++(backingSet[i].mobCounts[index]);
|
||||
+ // DivineMC start - Multithreaded tracker
|
||||
+ if (space.bxteam.divinemc.configuration.DivineGlobalConfiguration.get().multithreadTracker.multithreadedEnabled) {
|
||||
+ for (int i = 0, len = inRange.size(); i < len; i++) {
|
||||
+ final ServerPlayer player = backingSet[i];
|
||||
+ if (player == null) continue;
|
||||
+ ++(player.mobCounts[index]);
|
||||
+ }
|
||||
+ } else {
|
||||
+ for (int i = 0, len = inRange.size(); i < len; i++) {
|
||||
+ ++(backingSet[i].mobCounts[index]);
|
||||
+ }
|
||||
}
|
||||
+ // DivineMC end - Multithreaded tracker
|
||||
}
|
||||
|
||||
// Paper start - per player mob count backoff
|
||||
@@ -936,6 +946,21 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
|
||||
((ca.spottedleaf.moonrise.patches.entity_tracker.EntityTrackerEntity)entity).moonrise$setTrackedEntity(null); // Paper - optimise entity tracker
|
||||
}
|
||||
|
||||
+ // DivineMC start - 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();
|
||||
+ }
|
||||
+ }
|
||||
+ // DivineMC end - Multithreaded tracker
|
||||
+
|
||||
// Paper start - optimise entity tracker
|
||||
private void newTrackerTick() {
|
||||
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)this.level).moonrise$getEntityLookup();;
|
||||
@@ -958,6 +983,13 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
|
||||
// Paper end - optimise entity tracker
|
||||
|
||||
protected void tick() {
|
||||
+ // DivineMC start - Multithreaded tracker
|
||||
+ if (space.bxteam.divinemc.configuration.DivineGlobalConfiguration.get().multithreadTracker.multithreadedEnabled) {
|
||||
+ final ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel level = this.level;
|
||||
+ space.bxteam.divinemc.tracker.MultithreadedTracker.tick(level);
|
||||
+ return;
|
||||
+ }
|
||||
+ // DivineMC end - Multithreaded tracker
|
||||
// Paper start - optimise entity tracker
|
||||
if (true) {
|
||||
this.newTrackerTick();
|
||||
@@ -1080,7 +1112,11 @@ 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
|
||||
+ // DivineMC start - Multithreaded tracker
|
||||
+ public final Set<ServerPlayerConnection> seenBy = space.bxteam.divinemc.configuration.DivineGlobalConfiguration.get().multithreadTracker.multithreadedEnabled
|
||||
+ ? com.google.common.collect.Sets.newConcurrentHashSet()
|
||||
+ : new it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet<>(); // Paper - Perf: optimise map impl
|
||||
+ // DivineMC end - Multithreaded tracker
|
||||
|
||||
// Paper start - optimise entity tracker
|
||||
private long lastChunkUpdate = -1L;
|
||||
@@ -1107,21 +1143,55 @@ 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
|
||||
+
|
||||
+ // DivineMC start - Multithreaded tracker
|
||||
+ if (space.bxteam.divinemc.configuration.DivineGlobalConfiguration.get().multithreadTracker.multithreadedEnabled && space.bxteam.divinemc.configuration.DivineGlobalConfiguration.get().multithreadTracker.multithreadedCompatModeEnabled) {
|
||||
+ final boolean isServerPlayer = this.entity instanceof ServerPlayer;
|
||||
+ final boolean isRealPlayer = isServerPlayer && ((ca.spottedleaf.moonrise.patches.chunk_system.player.ChunkSystemServerPlayer) this.entity).moonrise$isRealPlayer();
|
||||
+ Runnable updatePlayerTasks = () -> {
|
||||
+ for (int i = 0; i < playersLen; ++i) {
|
||||
+ final ServerPlayer player = playersRaw[i];
|
||||
+ this.updatePlayer(player);
|
||||
+ }
|
||||
|
||||
- for (int i = 0, len = players.size(); i < len; ++i) {
|
||||
- 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) {
|
||||
+ space.bxteam.divinemc.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);
|
||||
+ }
|
||||
|
||||
- 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);
|
||||
+ 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);
|
||||
+ }
|
||||
}
|
||||
}
|
||||
}
|
||||
+ // DivineMC end - Multithreaded tracker
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1183,9 +1253,11 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
|
||||
}
|
||||
|
||||
public void broadcast(Packet<?> packet) {
|
||||
- for (ServerPlayerConnection serverPlayerConnection : this.seenBy) {
|
||||
+ // DivineMC start - Multithreaded tracker
|
||||
+ for (ServerPlayerConnection serverPlayerConnection : this.seenBy.toArray(new ServerPlayerConnection[0])) {
|
||||
serverPlayerConnection.send(packet);
|
||||
}
|
||||
+ // DivineMC end - Multithreaded tracker
|
||||
}
|
||||
|
||||
public void broadcastAndSend(Packet<?> packet) {
|
||||
@@ -1196,9 +1268,11 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
|
||||
}
|
||||
|
||||
public void broadcastRemoved() {
|
||||
- for (ServerPlayerConnection serverPlayerConnection : this.seenBy) {
|
||||
+ // DivineMC start - Multithreaded tracker
|
||||
+ for (ServerPlayerConnection serverPlayerConnection : this.seenBy.toArray(new ServerPlayerConnection[0])) {
|
||||
this.serverEntity.removePairing(serverPlayerConnection.getPlayer());
|
||||
}
|
||||
+ // DivineMC end - Multithreaded tracker
|
||||
}
|
||||
|
||||
public void removePlayer(ServerPlayer player) {
|
||||
@@ -1209,8 +1283,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"); // DivineMC - Multithreaded tracker - we don't need this
|
||||
if (player != this.entity) {
|
||||
+ if (space.bxteam.divinemc.configuration.DivineGlobalConfiguration.get().multithreadTracker.multithreadedEnabled && player == null) return; // DivineMC - 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();
|
||||
diff --git a/net/minecraft/server/level/ServerBossEvent.java b/net/minecraft/server/level/ServerBossEvent.java
|
||||
index f106373ef3ac4a8685c2939c9e8361688a285913..4a5c4eb52b069baf87b28dffc1598cfb42e83b89 100644
|
||||
--- a/net/minecraft/server/level/ServerBossEvent.java
|
||||
+++ b/net/minecraft/server/level/ServerBossEvent.java
|
||||
@@ -13,7 +13,11 @@ import net.minecraft.util.Mth;
|
||||
import net.minecraft.world.BossEvent;
|
||||
|
||||
public class ServerBossEvent extends BossEvent {
|
||||
- private final Set<ServerPlayer> players = Sets.newHashSet();
|
||||
+ // DivineMC start - Multithreaded tracker - players can be removed in async tracking
|
||||
+ private final Set<ServerPlayer> players = space.bxteam.divinemc.configuration.DivineGlobalConfiguration.get().multithreadTracker.multithreadedEnabled
|
||||
+ ? Sets.newConcurrentHashSet()
|
||||
+ : Sets.newHashSet();
|
||||
+ // DivineMC end - Multithreaded tracker
|
||||
private final Set<ServerPlayer> unmodifiablePlayers = Collections.unmodifiableSet(this.players);
|
||||
public boolean visible = true;
|
||||
|
||||
diff --git a/net/minecraft/server/level/ServerEntity.java b/net/minecraft/server/level/ServerEntity.java
|
||||
index 12a5c87d6e10ff8c1a19a041606a0016f7379901..7bac96ab1fde3dfbc9a72d832a7e48013736e1d7 100644
|
||||
--- a/net/minecraft/server/level/ServerEntity.java
|
||||
+++ b/net/minecraft/server/level/ServerEntity.java
|
||||
@@ -110,8 +110,13 @@ public class ServerEntity {
|
||||
.forEach(
|
||||
removedPassenger -> {
|
||||
if (removedPassenger instanceof ServerPlayer serverPlayer1) {
|
||||
- serverPlayer1.connection
|
||||
- .teleport(serverPlayer1.getX(), serverPlayer1.getY(), serverPlayer1.getZ(), serverPlayer1.getYRot(), serverPlayer1.getXRot());
|
||||
+ // DivineMC start - Multithreaded tracker
|
||||
+ if (space.bxteam.divinemc.configuration.DivineGlobalConfiguration.get().multithreadTracker.multithreadedEnabled && Thread.currentThread() instanceof space.bxteam.divinemc.tracker.MultithreadedTracker.MultithreadedTrackerThread) {
|
||||
+ net.minecraft.server.MinecraftServer.getServer().scheduleOnMain(() -> serverPlayer1.connection.teleport(serverPlayer1.getX(), serverPlayer1.getY(), serverPlayer1.getZ(), serverPlayer1.getYRot(), serverPlayer1.getXRot()));
|
||||
+ } else {
|
||||
+ serverPlayer1.connection.teleport(serverPlayer1.getX(), serverPlayer1.getY(), serverPlayer1.getZ(), serverPlayer1.getYRot(), serverPlayer1.getXRot());
|
||||
+ }
|
||||
+ // DivineMC end - Multithreaded tracker
|
||||
}
|
||||
}
|
||||
);
|
||||
@@ -304,7 +309,11 @@ public class ServerEntity {
|
||||
|
||||
public void removePairing(ServerPlayer player) {
|
||||
this.entity.stopSeenByPlayer(player);
|
||||
- player.connection.send(new ClientboundRemoveEntitiesPacket(this.entity.getId()));
|
||||
+ // DivineMC start - Multithreaded tracker - send in main thread
|
||||
+ ((ServerLevel) this.entity.level()).chunkSource.chunkMap.runOnTrackerMainThread(() ->
|
||||
+ player.connection.send(new ClientboundRemoveEntitiesPacket(this.entity.getId()))
|
||||
+ );
|
||||
+ // DivineMC end - Multithreaded tracker
|
||||
}
|
||||
|
||||
public void addPairing(ServerPlayer player) {
|
||||
@@ -404,7 +413,11 @@ public class ServerEntity {
|
||||
List<SynchedEntityData.DataValue<?>> list = entityData.packDirty();
|
||||
if (list != null) {
|
||||
this.trackedDataValues = entityData.getNonDefaultValues();
|
||||
- this.broadcastAndSend(new ClientboundSetEntityDataPacket(this.entity.getId(), list));
|
||||
+ // DivineMC start - Multithreaded tracker - send in main thread
|
||||
+ ((ServerLevel) this.entity.level()).chunkSource.chunkMap.runOnTrackerMainThread(() ->
|
||||
+ this.broadcastAndSend(new ClientboundSetEntityDataPacket(this.entity.getId(), list))
|
||||
+ );
|
||||
+ // DivineMC end - Multithreaded tracker
|
||||
}
|
||||
|
||||
if (this.entity instanceof LivingEntity) {
|
||||
@@ -413,12 +426,17 @@ public class ServerEntity {
|
||||
final Set<AttributeInstance> attributesToSync = this.level.divineConfig().optimizations.suppressErrorsFromDirtyAttributes ? Collections.synchronizedSet(attributes) : attributes;
|
||||
// DivineMC end - Suppress errors from dirty attributes
|
||||
if (!attributesToSync.isEmpty()) {
|
||||
- // CraftBukkit start - Send scaled max health
|
||||
- if (this.entity instanceof ServerPlayer serverPlayer) {
|
||||
- serverPlayer.getBukkitEntity().injectScaledMaxHealth(attributesToSync, false);
|
||||
- }
|
||||
- // CraftBukkit end
|
||||
- this.broadcastAndSend(new ClientboundUpdateAttributesPacket(this.entity.getId(), attributesToSync));
|
||||
+ // DivineMC start - Multithreaded tracker
|
||||
+ final Set<AttributeInstance> copy = new it.unimi.dsi.fastutil.objects.ObjectOpenHashSet<>(attributesToSync);
|
||||
+ ((ServerLevel) this.entity.level()).chunkSource.chunkMap.runOnTrackerMainThread(() -> {
|
||||
+ // CraftBukkit start - Send scaled max health
|
||||
+ if (this.entity instanceof ServerPlayer serverPlayer) {
|
||||
+ serverPlayer.getBukkitEntity().injectScaledMaxHealth(copy, false);
|
||||
+ }
|
||||
+ // CraftBukkit end
|
||||
+ this.broadcastAndSend(new ClientboundUpdateAttributesPacket(this.entity.getId(), copy));
|
||||
+ });
|
||||
+ // DivineMC end - Multithreaded tracker
|
||||
}
|
||||
|
||||
attributes.clear();
|
||||
diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java
|
||||
index c186cfc4f821cb6449e0eb060cd87c267bcdc069..43391e40cc72af39415fb989fc3369d1b46d4623 100644
|
||||
--- a/net/minecraft/server/level/ServerLevel.java
|
||||
+++ b/net/minecraft/server/level/ServerLevel.java
|
||||
@@ -2512,7 +2512,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"); // DivineMC - Multithreaded tracker
|
||||
return this.moonrise$getEntityLookup(); // Paper - rewrite chunk system
|
||||
}
|
||||
|
||||
diff --git a/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/net/minecraft/server/network/ServerGamePacketListenerImpl.java
|
||||
index 0be1ee79461fcae88e6cb61adb16c132567d0001..19d30bdcc5ad497d5fe4fc879703d22bcd35b860 100644
|
||||
--- a/net/minecraft/server/network/ServerGamePacketListenerImpl.java
|
||||
+++ b/net/minecraft/server/network/ServerGamePacketListenerImpl.java
|
||||
@@ -1806,7 +1806,7 @@ public class ServerGamePacketListenerImpl
|
||||
}
|
||||
|
||||
public void internalTeleport(PositionMoveRotation posMoveRotation, Set<Relative> relatives) {
|
||||
- org.spigotmc.AsyncCatcher.catchOp("teleport"); // Paper
|
||||
+ //org.spigotmc.AsyncCatcher.catchOp("teleport"); // DivineMC - 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/ai/attributes/AttributeInstance.java b/net/minecraft/world/entity/ai/attributes/AttributeInstance.java
|
||||
index 8013594bb4844e7a8abf28123958e7f632d39341..cfd14b1388638b167c2081909320bca6a2139d19 100644
|
||||
--- a/net/minecraft/world/entity/ai/attributes/AttributeInstance.java
|
||||
+++ b/net/minecraft/world/entity/ai/attributes/AttributeInstance.java
|
||||
@@ -24,8 +24,11 @@ public class AttributeInstance {
|
||||
private final Map<AttributeModifier.Operation, Map<ResourceLocation, AttributeModifier>> modifiersByOperation = Maps.newEnumMap(
|
||||
AttributeModifier.Operation.class
|
||||
);
|
||||
- private final Map<ResourceLocation, AttributeModifier> modifierById = new Object2ObjectArrayMap<>();
|
||||
- private final Map<ResourceLocation, AttributeModifier> permanentModifiers = new Object2ObjectArrayMap<>();
|
||||
+ // DivineMC start - Multithreaded tracker
|
||||
+ private final boolean multiThreadedTrackingEnabled = space.bxteam.divinemc.configuration.DivineGlobalConfiguration.get().multithreadTracker.multithreadedEnabled;
|
||||
+ 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<>();
|
||||
+ // DivineMC end - Multithreaded tracker
|
||||
private double baseValue;
|
||||
private boolean dirty = true;
|
||||
private double cachedValue;
|
||||
diff --git a/net/minecraft/world/entity/ai/attributes/AttributeMap.java b/net/minecraft/world/entity/ai/attributes/AttributeMap.java
|
||||
index a25d74592e89e3d6339479c6dc2b6f45d1932cfc..57fee93f53464fb249aedf9f7319fcab14dadffe 100644
|
||||
--- a/net/minecraft/world/entity/ai/attributes/AttributeMap.java
|
||||
+++ b/net/minecraft/world/entity/ai/attributes/AttributeMap.java
|
||||
@@ -19,9 +19,12 @@ import org.slf4j.Logger;
|
||||
|
||||
public class AttributeMap {
|
||||
private static final Logger LOGGER = LogUtils.getLogger();
|
||||
- private final Map<Holder<Attribute>, AttributeInstance> attributes = new Object2ObjectOpenHashMap<>();
|
||||
- private final Set<AttributeInstance> attributesToSync = new ObjectOpenHashSet<>();
|
||||
- private final Set<AttributeInstance> attributesToUpdate = new ObjectOpenHashSet<>();
|
||||
+ // DivineMC start - Multithreaded tracker
|
||||
+ private final boolean multiThreadedTrackingEnabled = space.bxteam.divinemc.configuration.DivineGlobalConfiguration.get().multithreadTracker.multithreadedEnabled;
|
||||
+ 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);
|
||||
+ // DivineMC end - Multithreaded tracker
|
||||
private final AttributeSupplier supplier;
|
||||
private final net.minecraft.world.entity.LivingEntity entity; // Purpur - Ridables
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
import com.destroystokyo.paper.event.server.ServerExceptionEvent;
|
||||
import com.destroystokyo.paper.exception.ServerEventException;
|
||||
import com.google.common.collect.Sets;
|
||||
@@ -36,15 +_,16 @@
|
||||
@@ -36,15 +_,22 @@
|
||||
|
||||
// SimplePluginManager
|
||||
public void callEvent(@NotNull Event event) {
|
||||
@@ -18,6 +18,12 @@
|
||||
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()) {
|
||||
+ // DivineMC start - Multithreaded tracker
|
||||
+ if (space.bxteam.divinemc.configuration.DivineGlobalConfiguration.get().multithreadTracker.multithreadedEnabled) {
|
||||
+ net.minecraft.server.MinecraftServer.getServer().scheduleOnMain(event::callEvent);
|
||||
+ return;
|
||||
+ }
|
||||
+ // DivineMC end - Multithreaded tracker
|
||||
throw new IllegalStateException(event.getEventName() + " may only be triggered synchronously.");
|
||||
}
|
||||
|
||||
|
||||
@@ -48,6 +48,30 @@ public class DivineGlobalConfiguration extends ConfigurationPart {
|
||||
}
|
||||
}
|
||||
|
||||
public MultithreadTracker multithreadTracker;
|
||||
|
||||
public class MultithreadTracker extends ConfigurationPart {
|
||||
public boolean multithreadedEnabled = false;
|
||||
public boolean multithreadedCompatModeEnabled = false;
|
||||
public int asyncEntityTrackerMaxThreads = 0;
|
||||
public int asyncEntityTrackerKeepalive = 60;
|
||||
|
||||
@PostProcess
|
||||
public void init() {
|
||||
if (asyncEntityTrackerMaxThreads < 0) {
|
||||
asyncEntityTrackerMaxThreads = Math.max(Runtime.getRuntime().availableProcessors() + asyncEntityTrackerMaxThreads, 1);
|
||||
} else if (asyncEntityTrackerMaxThreads == 0) {
|
||||
asyncEntityTrackerMaxThreads = Math.max(Runtime.getRuntime().availableProcessors() / 4, 1);
|
||||
}
|
||||
|
||||
if (!multithreadedEnabled) {
|
||||
asyncEntityTrackerMaxThreads = 0;
|
||||
} else {
|
||||
Bukkit.getLogger().log(Level.INFO, "Using " + asyncEntityTrackerMaxThreads + " threads for Async Entity Tracker");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Chat chat;
|
||||
|
||||
public class Chat extends ConfigurationPart {
|
||||
|
||||
@@ -0,0 +1,141 @@
|
||||
package space.bxteam.divinemc.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.FullChunkStatus;
|
||||
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.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,
|
||||
space.bxteam.divinemc.configuration.DivineGlobalConfiguration.get().multithreadTracker.asyncEntityTrackerMaxThreads,
|
||||
space.bxteam.divinemc.configuration.DivineGlobalConfiguration.get().multithreadTracker.asyncEntityTrackerKeepalive, TimeUnit.SECONDS,
|
||||
new LinkedBlockingQueue<>(),
|
||||
new ThreadFactoryBuilder()
|
||||
.setThreadFactory(
|
||||
r -> new MultithreadedTrackerThread() {
|
||||
@Override
|
||||
public void run() {
|
||||
r.run();
|
||||
}
|
||||
}
|
||||
)
|
||||
.setNameFormat("DivineMC 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 (!space.bxteam.divinemc.configuration.DivineGlobalConfiguration.get().multithreadTracker.multithreadedCompatModeEnabled) {
|
||||
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();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Original ChunkMap#newTrackerTick of Paper
|
||||
// Just for diff usage for future update
|
||||
@SuppressWarnings("DuplicatedCode")
|
||||
private static void tickOriginal(ServerLevel level) {
|
||||
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(((ca.spottedleaf.moonrise.patches.chunk_system.entity.ChunkSystemEntity) entity).moonrise$getChunkData().nearbyPlayers);
|
||||
if (((ca.spottedleaf.moonrise.patches.entity_tracker.EntityTrackerTrackedEntity) tracker).moonrise$hasPlayers()
|
||||
|| ((ca.spottedleaf.moonrise.patches.chunk_system.entity.ChunkSystemEntity) entity).moonrise$getChunkStatus().isOrAfter(FullChunkStatus.ENTITY_TICKING)) {
|
||||
tracker.serverEntity.sendChanges();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user