Files
Purpur/patches/server/0003-Multithreaded-entity-tracking.patch
BillyGalbreath 7f7f024f02 Updated Upstream (Paper, Tuinity, & Airplane)
Upstream has released updates that appear to apply and compile correctly

Paper Changes:
8a29f5894 [Auto] Updated Upstream (Bukkit/CraftBukkit)
8756d232c Expose server protocol version (#5416)
4492bc4cc remove l4j class no longer in existence from preload list
be1370517 Updated Upstream (CraftBukkit) (#5484)
d560151ec Bump mysql-connector-java to 8.0.23 (Fixes #5473) (#5474)
61f400f11 Update log4j to 2.11.2 for JDK 9+ compat (#5400)
a98196585 Updated Upstream (Bukkit/CraftBukkit)
de138fac4 [Auto] Updated Upstream (Bukkit)
304a216ba [CI-SKIP] Ignore gitignore when adding files in automation
d8e384a16 [CI-SKIP] Drop `Allow PlayerEditBookEvent to fire for off hand` (#5471)

Tuinity Changes:
d5261ad29 Do not load chunks for getCubes by default
da9cf9828 Don't read neighbor chunk data off disk when converting chunks
a0aa5ab07 Do not load 1 radius neighbors for lighting
5ccfa52a2 Fix terrible patch times
af53d703a Stop large move vectors in player packet handling from killing the server
6e56ee735 Fix OBFHELPER for flushHeaderin RegionFile
995d05c1c Do not update TE's in generating chunks

Airplane Changes:
8de8e82a2 Update upstream (Tuinity)
2021-04-13 10:56:32 -05:00

464 lines
24 KiB
Diff

From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Paul Sauve <paul@technove.co>
Date: Fri, 12 Feb 2021 16:29:41 -0600
Subject: [PATCH] Multithreaded entity tracking
Adds a configuration option to run the tracker on multiple threads.
This will generally be faster than the single threaded approach,
unless you don't have a lot of entities/players.
Airplane
Copyright (C) 2020 Technove LLC
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
diff --git a/src/main/java/com/tuinity/tuinity/util/maplist/IteratorSafeOrderedReferenceSet.java b/src/main/java/com/tuinity/tuinity/util/maplist/IteratorSafeOrderedReferenceSet.java
index be408aebbccbda46e8aa82ef337574137cfa0096..739839314fd8a88b5fca8b9678e1df07a166c35d 100644
--- a/src/main/java/com/tuinity/tuinity/util/maplist/IteratorSafeOrderedReferenceSet.java
+++ b/src/main/java/com/tuinity/tuinity/util/maplist/IteratorSafeOrderedReferenceSet.java
@@ -16,11 +16,11 @@ public final class IteratorSafeOrderedReferenceSet<E> {
/* list impl */
protected E[] listElements;
- protected int listSize;
+ protected int listSize; public int getListSize() { return this.listSize; } // Airplane - getter
protected final double maxFragFactor;
- protected int iteratorCount;
+ public int iteratorCount; // Airplane - public for debug
private final boolean threadRestricted;
diff --git a/src/main/java/gg/airplane/AirplaneConfig.java b/src/main/java/gg/airplane/AirplaneConfig.java
index a77e628518920e84b03a8a00e1308a9a53a00896..5a20ad857b7c550281000e8d94dac78d72591f31 100644
--- a/src/main/java/gg/airplane/AirplaneConfig.java
+++ b/src/main/java/gg/airplane/AirplaneConfig.java
@@ -96,4 +96,17 @@ public class AirplaneConfig {
}
+ public static boolean multithreadedEntityTracker = false;
+ public static boolean entityTrackerAsyncPackets = false;
+
+ private static void entityTracker() {
+ config.setComment("tracker", "Options to improve the performance of the entity tracker");
+
+ multithreadedEntityTracker = config.getBoolean("tracker.multithreaded", multithreadedEntityTracker,
+ "This enables the multithreading of the tracker.");
+ entityTrackerAsyncPackets = config.getBoolean("tracker.unsafe-async-packets", entityTrackerAsyncPackets,
+ "This option can break plugins that assume packets from the",
+ "entity tracker will be sent sync.");
+ }
+
}
diff --git a/src/main/java/gg/airplane/commands/AirplaneCommands.java b/src/main/java/gg/airplane/commands/AirplaneCommands.java
index 66b20250a26d005427601b1cdee43bdd9eba70cc..f84e26b2d8ab9a9b2d9e92e18002483127121135 100644
--- a/src/main/java/gg/airplane/commands/AirplaneCommands.java
+++ b/src/main/java/gg/airplane/commands/AirplaneCommands.java
@@ -2,11 +2,13 @@ package gg.airplane.commands;
import gg.airplane.AirplaneCommand;
import gg.airplane.flare.FlareCommand;
+import gg.airplane.structs.TrackQueue;
import net.minecraft.server.MinecraftServer;
public class AirplaneCommands {
public static void init() {
MinecraftServer.getServer().server.getCommandMap().register("airplane", "Airplane", new AirplaneCommand());
MinecraftServer.getServer().server.getCommandMap().register("flare", "Airplane", new FlareCommand());
+ TrackQueue.TrackQueueDebugCommand.register();
}
}
diff --git a/src/main/java/gg/airplane/structs/TrackQueue.java b/src/main/java/gg/airplane/structs/TrackQueue.java
new file mode 100644
index 0000000000000000000000000000000000000000..4419fbe94041f4b8a0ea848880798289d063b8e2
--- /dev/null
+++ b/src/main/java/gg/airplane/structs/TrackQueue.java
@@ -0,0 +1,109 @@
+package gg.airplane.structs;
+
+import com.tuinity.tuinity.util.maplist.IteratorSafeOrderedReferenceSet;
+import net.minecraft.server.level.WorldServer;
+import net.minecraft.world.level.chunk.Chunk;
+import net.minecraft.server.MinecraftServer;
+import org.apache.logging.log4j.Level;
+import org.bukkit.command.Command;
+import org.bukkit.command.CommandSender;
+
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.ForkJoinPool;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * Helper class to handle processing a track queue.
+ */
+public class TrackQueue {
+
+ public static class TrackQueueDebugCommand extends Command {
+ protected TrackQueueDebugCommand() {
+ super("trackqueuedebug");
+ }
+
+ public static void register() {
+ MinecraftServer.getServer().server.getCommandMap().register("trackqueuedebug", "Airplane", new TrackQueueDebugCommand());
+ }
+
+ @Override
+ public boolean execute(CommandSender sender, String commandLabel, String[] args) {
+ if (!sender.isOp()) {
+ return false;
+ }
+ for (WorldServer world : MinecraftServer.getServer().getWorlds()) {
+ IteratorSafeOrderedReferenceSet<Chunk> chunks = world.getChunkProvider().entityTickingChunks;
+ sender.sendMessage(world.getWorld().getName() + ": " + chunks.size() + " / " + chunks.getListSize() + " (iterators: " + chunks.iteratorCount + ")");
+ }
+ return true;
+ }
+ }
+
+ private final IteratorSafeOrderedReferenceSet<Chunk> chunks;
+ private final ForkJoinPool pool = new ForkJoinPool(Math.max(4, Runtime.getRuntime().availableProcessors() >> 2));
+ private final AtomicInteger taskIndex = new AtomicInteger();
+
+ private final ConcurrentLinkedQueue<Runnable> mainThreadTasks;
+
+ public TrackQueue(IteratorSafeOrderedReferenceSet<Chunk> chunks, ConcurrentLinkedQueue<Runnable> mainThreadTasks) {
+ this.chunks = chunks;
+ this.mainThreadTasks = mainThreadTasks;
+ }
+
+ public void start() {
+ int iterator = this.chunks.createRawIterator();
+ if (iterator == -1) {
+ return;
+ }
+ try {
+ this.taskIndex.set(iterator);
+
+ for (int i = 0; i < this.pool.getParallelism(); i++) {
+ this.pool.execute(this::run);
+ }
+
+ while (this.taskIndex.get() < this.chunks.getListSize()) {
+ this.runMainThreadTasks();
+ this.handleTask(); // assist
+ }
+ this.runMainThreadTasks(); // finish tasks
+ } finally {
+ this.chunks.finishRawIterator();
+ }
+ }
+
+ private void runMainThreadTasks() {
+ Runnable task;
+ while ((task = this.mainThreadTasks.poll()) != null) {
+ try {
+ task.run();
+ } catch (Throwable t) {
+ MinecraftServer.LOGGER.log(Level.WARN, "Tasks failed while ticking track queue", t);
+ }
+ }
+
+ }
+
+ private void run() {
+ while (handleTask());
+ }
+
+ private boolean handleTask() {
+ int index;
+ while ((index = this.taskIndex.getAndIncrement()) < this.chunks.getListSize()) {
+ Chunk chunk = this.chunks.rawGet(index);
+ if (chunk != null) {
+ try {
+ chunk.entityTracker.run();
+ } catch (Throwable t) {
+ MinecraftServer.LOGGER.log(Level.WARN, "Ticking tracker failed", t);
+ }
+
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+}
diff --git a/src/main/java/net/minecraft/server/level/ChunkProviderServer.java b/src/main/java/net/minecraft/server/level/ChunkProviderServer.java
index 87c87b9767003652814c3726eece64470dbb69a8..dc1b7db4d39d4dfa65d60e5a059d3b94def6cf62 100644
--- a/src/main/java/net/minecraft/server/level/ChunkProviderServer.java
+++ b/src/main/java/net/minecraft/server/level/ChunkProviderServer.java
@@ -388,7 +388,7 @@ public class ChunkProviderServer extends IChunkProvider {
}
final com.tuinity.tuinity.util.maplist.IteratorSafeOrderedReferenceSet<Chunk> tickingChunks = new com.tuinity.tuinity.util.maplist.IteratorSafeOrderedReferenceSet<>(4096, 0.75f, 4096, 0.15, true);
- final com.tuinity.tuinity.util.maplist.IteratorSafeOrderedReferenceSet<Chunk> entityTickingChunks = new com.tuinity.tuinity.util.maplist.IteratorSafeOrderedReferenceSet<>(4096, 0.75f, 4096, 0.15, true);
+ public final com.tuinity.tuinity.util.maplist.IteratorSafeOrderedReferenceSet<Chunk> entityTickingChunks = new com.tuinity.tuinity.util.maplist.IteratorSafeOrderedReferenceSet<>(4096, 0.75f, 4096, 0.15, true); // Airplane - public for debug
// Tuinity end
public ChunkProviderServer(WorldServer worldserver, Convertable.ConversionSession convertable_conversionsession, DataFixer datafixer, DefinedStructureManager definedstructuremanager, Executor executor, ChunkGenerator chunkgenerator, int i, boolean flag, WorldLoadListener worldloadlistener, Supplier<WorldPersistentData> supplier) {
diff --git a/src/main/java/net/minecraft/server/level/EntityPlayer.java b/src/main/java/net/minecraft/server/level/EntityPlayer.java
index 62b95dcba8606330fbb3239e74c5eaf8baa3c51d..fd6dbbf619b828c4ef3d00f8dc30d3893007db7b 100644
--- a/src/main/java/net/minecraft/server/level/EntityPlayer.java
+++ b/src/main/java/net/minecraft/server/level/EntityPlayer.java
@@ -182,7 +182,7 @@ public class EntityPlayer extends EntityHuman implements ICrafting {
public NetworkManager networkManager; // Paper
public final MinecraftServer server;
public final PlayerInteractManager playerInteractManager;
- public final Deque<Integer> removeQueue = new ArrayDeque<>(); // Paper
+ public final Deque<Integer> removeQueue = new java.util.concurrent.ConcurrentLinkedDeque<>(); // Paper // Airplane concurrent deque
private final AdvancementDataPlayer advancementDataPlayer;
private final ServerStatisticManager serverStatisticManager;
private float lastHealthScored = Float.MIN_VALUE;
diff --git a/src/main/java/net/minecraft/server/level/EntityTrackerEntry.java b/src/main/java/net/minecraft/server/level/EntityTrackerEntry.java
index 67ca28463f5add7c18f7f16b918c3f36f8feeeda..37e64e24ca3a90370cdf12e5ff9cd1fceede796b 100644
--- a/src/main/java/net/minecraft/server/level/EntityTrackerEntry.java
+++ b/src/main/java/net/minecraft/server/level/EntityTrackerEntry.java
@@ -75,6 +75,10 @@ public class EntityTrackerEntry {
* Requested in https://github.com/PaperMC/Paper/issues/1537 to allow intercepting packets
*/
public void sendPlayerPacket(EntityPlayer player, Packet packet) {
+ if (!gg.airplane.AirplaneConfig.entityTrackerAsyncPackets && !org.bukkit.Bukkit.isPrimaryThread()) {
+ this.b.chunkProvider.playerChunkMap.trackerEnsureMain(() -> sendPlayerPacket(player, packet));
+ return;
+ }
player.playerConnection.sendPacket(packet);
}
@@ -103,7 +107,7 @@ public class EntityTrackerEntry {
public final void tick() { this.a(); } // Paper - OBFHELPER
public void a() {
- com.tuinity.tuinity.util.TickThread.softEnsureTickThread("Tracker update"); // Tuinity
+ //com.tuinity.tuinity.util.TickThread.softEnsureTickThread("Tracker update"); // Tuinity // Airplane - allow multithreaded
List<Entity> list = this.tracker.passengers; // Paper - do not copy list
if (!list.equals(this.p)) {
@@ -116,6 +120,8 @@ public class EntityTrackerEntry {
ItemStack itemstack = entityitemframe.getItem();
if (this.tickCounter % 10 == 0 && itemstack.getItem() instanceof ItemWorldMap) { // CraftBukkit - Moved this.tickCounter % 10 logic here so item frames do not enter the other blocks
+ // Airplane start - process maps on main
+ this.b.chunkProvider.playerChunkMap.trackerEnsureMain(() -> {
WorldMap worldmap = ItemWorldMap.getSavedMap(itemstack, this.b);
Iterator iterator = this.trackedPlayers.iterator(); // CraftBukkit
@@ -129,6 +135,8 @@ public class EntityTrackerEntry {
entityplayer.playerConnection.sendPacket(packet);
}
}
+ });
+ // Airplane end
}
this.c();
@@ -263,18 +271,25 @@ public class EntityTrackerEntry {
// CraftBukkit start - Create PlayerVelocity event
boolean cancelled = false;
- if (this.tracker instanceof EntityPlayer) {
+ if (this.tracker instanceof EntityPlayer && PlayerVelocityEvent.getHandlerList().getRegisteredListeners().length > 0) { // Airplane - ensure there's listeners
+ // Airplane start - run on main thread
+ this.b.chunkProvider.playerChunkMap.trackerEnsureMain(() -> {
Player player = (Player) this.tracker.getBukkitEntity();
org.bukkit.util.Vector velocity = player.getVelocity();
PlayerVelocityEvent event = new PlayerVelocityEvent(player, velocity.clone());
this.tracker.world.getServer().getPluginManager().callEvent(event);
- if (event.isCancelled()) {
- cancelled = true;
- } else if (!velocity.equals(event.getVelocity())) {
+ if (!event.isCancelled() && !velocity.equals(event.getVelocity())) {
player.setVelocity(event.getVelocity());
}
+ if (!event.isCancelled()) {
+ this.broadcastIncludingSelf(new PacketPlayOutEntityVelocity(this.tracker)); // duplicate from !cancelled below
+ }
+
+ });
+ cancelled = true; // don't broadcast until the event has finished
+ // Airplane end
}
if (!cancelled) {
@@ -358,7 +373,9 @@ public class EntityTrackerEntry {
if (!list.isEmpty()) {
consumer.accept(new PacketPlayOutEntityEquipment(this.tracker.getId(), list));
}
+ this.b.chunkProvider.playerChunkMap.trackerEnsureMain(() -> { // Airplane
((EntityLiving) this.tracker).updateEquipment(); // CraftBukkit - SPIGOT-3789: sync again immediately after sending
+ }); // Airplane
}
// CraftBukkit start - Fix for nonsensical head yaw
@@ -436,6 +453,10 @@ public class EntityTrackerEntry {
// Paper end
private void broadcastIncludingSelf(Packet<?> packet) {
+ if (!gg.airplane.AirplaneConfig.entityTrackerAsyncPackets && !org.bukkit.Bukkit.isPrimaryThread()) {
+ this.b.chunkProvider.playerChunkMap.trackerEnsureMain(() -> broadcastIncludingSelf(packet));
+ return;
+ }
this.f.accept(packet);
if (this.tracker instanceof EntityPlayer) {
((EntityPlayer) this.tracker).playerConnection.sendPacket(packet);
diff --git a/src/main/java/net/minecraft/server/level/PlayerChunkMap.java b/src/main/java/net/minecraft/server/level/PlayerChunkMap.java
index bc18b9c3aac4c5feeb1603554e0ac009af9b457e..c9d6ddd8874195c07b3573c6b1f61ffdcff2dddd 100644
--- a/src/main/java/net/minecraft/server/level/PlayerChunkMap.java
+++ b/src/main/java/net/minecraft/server/level/PlayerChunkMap.java
@@ -791,6 +791,11 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
return (PlayerChunk) (this.hasPendingVisibleUpdate ? this.pendingVisibleChunks.get(i) : ((ProtectedVisibleChunksMap)this.visibleChunks).safeGet(i));
// Paper end
}
+ // Airplane start - since neither map can be updated during tracker tick, it's safe to allow direct retrieval here
+ private PlayerChunk trackerGetVisibleChunk(long i) {
+ return this.hasPendingVisibleUpdate ? this.pendingVisibleChunks.get(i) : ((ProtectedVisibleChunksMap) this.visibleChunks).safeGet(i);
+ }
+ // Airplane end
protected final IntSupplier getPrioritySupplier(long i) { return c(i); } // Paper - OBFHELPER
protected IntSupplier c(long i) {
@@ -2194,10 +2199,30 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
entity.tracker = null; // Paper - We're no longer tracked
}
+ // Airplane start - tools to ensure main thread
+ private final java.util.concurrent.ConcurrentLinkedQueue<Runnable> trackerMainQueue = new java.util.concurrent.ConcurrentLinkedQueue<>();
+ private gg.airplane.structs.TrackQueue trackQueue;
+
+ void trackerEnsureMain(Runnable runnable) {
+ if (this.world.serverThread == Thread.currentThread()) {
+ runnable.run();
+ } else {
+ this.trackerMainQueue.add(runnable);
+ }
+ }
+ // Airplane end
+
// Paper start - optimised tracker
private final void processTrackQueue() {
this.world.timings.tracker1.startTiming();
try {
+ // Airplane start - multithreaded tracker
+ if (this.trackQueue == null) this.trackQueue = new gg.airplane.structs.TrackQueue(this.world.getChunkProvider().entityTickingChunks, trackerMainQueue);
+ if (gg.airplane.AirplaneConfig.multithreadedEntityTracker) {
+ this.trackQueue.start();
+ return;
+ }
+ // Airplane end
com.tuinity.tuinity.util.maplist.IteratorSafeOrderedReferenceSet.Iterator<Chunk> iterator = this.world.getChunkProvider().entityTickingChunks.iterator();
try {
while (iterator.hasNext()) {
@@ -2463,7 +2488,7 @@ Sections go from 0..16. Now whenever a section is not empty, it can potentially
public class EntityTracker {
final EntityTrackerEntry trackerEntry; // Paper - private -> package private
- private final Entity tracker;
+ public final Entity tracker; // Airplane - public for chunk
private final int trackingDistance;
private SectionPosition e;
// Paper start
@@ -2482,7 +2507,9 @@ Sections go from 0..16. Now whenever a section is not empty, it can potentially
// Paper start - use distance map to optimise tracker
com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<EntityPlayer> lastTrackerCandidates;
- final void updatePlayers(com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<EntityPlayer> newTrackerCandidates) {
+ public synchronized final void tickEntry() { this.trackerEntry.tick(); } // Airplane - move entry tick into sync block
+
+ public synchronized final void updatePlayers(com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<EntityPlayer> newTrackerCandidates) { // Airplane
com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<EntityPlayer> oldTrackerCandidates = this.lastTrackerCandidates;
this.lastTrackerCandidates = newTrackerCandidates;
@@ -2523,7 +2550,13 @@ Sections go from 0..16. Now whenever a section is not empty, it can potentially
return this.tracker.getId();
}
- public void broadcast(Packet<?> packet) {
+ public synchronized void broadcast(Packet<?> packet) { // Airplane - synchronized for tracked player
+ // Airplane start
+ if (!gg.airplane.AirplaneConfig.entityTrackerAsyncPackets && !org.bukkit.Bukkit.isPrimaryThread()) {
+ trackerEnsureMain(() -> broadcast(packet));
+ return;
+ }
+ // Airplane end
Iterator iterator = this.trackedPlayers.iterator();
while (iterator.hasNext()) {
@@ -2535,6 +2568,12 @@ Sections go from 0..16. Now whenever a section is not empty, it can potentially
}
public void broadcastIncludingSelf(Packet<?> packet) {
+ // Airplane start
+ if (!gg.airplane.AirplaneConfig.entityTrackerAsyncPackets && !org.bukkit.Bukkit.isPrimaryThread()) {
+ trackerEnsureMain(() -> broadcastIncludingSelf(packet));
+ return;
+ }
+ // Airplane end
this.broadcast(packet);
if (this.tracker instanceof EntityPlayer) {
((EntityPlayer) this.tracker).playerConnection.sendPacket(packet);
@@ -2561,8 +2600,8 @@ Sections go from 0..16. Now whenever a section is not empty, it can potentially
}
- public void updatePlayer(EntityPlayer entityplayer) {
- org.spigotmc.AsyncCatcher.catchOp("player tracker update"); // Spigot
+ public synchronized void updatePlayer(EntityPlayer entityplayer) { // Airplane - sync for access to map
+ //org.spigotmc.AsyncCatcher.catchOp("player tracker update"); // Spigot // Airplane - allow sync for tracker
if (entityplayer != this.tracker) {
// Paper start - remove allocation of Vec3D here
//Vec3D vec3d = entityplayer.getPositionVector().d(this.tracker.getPositionVector()); // MC-155077, SPIGOT-5113
@@ -2583,7 +2622,7 @@ Sections go from 0..16. Now whenever a section is not empty, it can potentially
*/
int x = this.tracker.chunkX, z = this.tracker.chunkZ;
long chunkcoordintpair = ChunkCoordIntPair.pair(x, z);
- PlayerChunk playerchunk = PlayerChunkMap.this.getVisibleChunk(chunkcoordintpair);
+ PlayerChunk playerchunk = PlayerChunkMap.this.trackerGetVisibleChunk(chunkcoordintpair); // Airplane
if (playerchunk != null && playerchunk.getSendingChunk() != null && PlayerChunkMap.this.playerChunkManager.isChunkSent(entityplayer, MathHelper.floor(this.tracker.locX()) >> 4, MathHelper.floor(this.tracker.locZ()) >> 4)) { // Paper - no-tick view distance // Tuinity - don't broadcast in chunks the player hasn't received
flag1 = PlayerChunkMap.someDistanceCalculation(x, z, entityplayer, false) <= PlayerChunkMap.this.viewDistance;
diff --git a/src/main/java/net/minecraft/world/level/chunk/Chunk.java b/src/main/java/net/minecraft/world/level/chunk/Chunk.java
index 7474c070598bc093e06f02f19d49f3a6fa6b3d4e..c07fb5ca761c0f2067bd103026ded618a8620947 100644
--- a/src/main/java/net/minecraft/world/level/chunk/Chunk.java
+++ b/src/main/java/net/minecraft/world/level/chunk/Chunk.java
@@ -111,6 +111,26 @@ public class Chunk implements IChunkAccess {
}
// Airplane end
+ // Airplane start - entity tracker runnable
+ // prevents needing to allocate new lambda in processTrackQueue
+ public final Runnable entityTracker = new Runnable() {
+ @Override
+ public void run() {
+ Entity[] entities = Chunk.this.entities.getRawData();
+ for (int i = 0, len = Chunk.this.entities.size(); i < len; ++i) {
+ Entity entity = entities[i];
+ if (entity != null) {
+ PlayerChunkMap.EntityTracker tracker = ((WorldServer) Chunk.this.getWorld()).getChunkProvider().playerChunkMap.trackedEntities.get(entity.getId());
+ if (tracker != null) {
+ tracker.updatePlayers(tracker.tracker.getPlayersInTrackRange());
+ tracker.tickEntry();
+ }
+ }
+ }
+ }
+ };
+ // Airplane end
+
public Chunk(World world, ChunkCoordIntPair chunkcoordintpair, BiomeStorage biomestorage) {
this(world, chunkcoordintpair, biomestorage, ChunkConverter.a, TickListEmpty.b(), TickListEmpty.b(), 0L, (ChunkSection[]) null, (Consumer) null);
}