even more work
This commit is contained in:
		
					parent
					
						
							
								5352f23252
							
						
					
				
			
			
				commit
				
					
						713d787f98
					
				
			
		
					 16 changed files with 10 additions and 3741 deletions
				
			
		
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							| 
						 | 
					@ -1,127 +0,0 @@
 | 
				
			||||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
 | 
					 | 
				
			||||||
From: CullanP <cullanpage@gmail.com>
 | 
					 | 
				
			||||||
Date: Thu, 3 Mar 2016 02:13:38 -0600
 | 
					 | 
				
			||||||
Subject: [PATCH] Avoid hopper searches if there are no items
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Hoppers searching for items and minecarts is the most expensive part of hopper ticking.
 | 
					 | 
				
			||||||
We keep track of the number of minecarts and items in a chunk.
 | 
					 | 
				
			||||||
If there are no items in the chunk, we skip searching for items.
 | 
					 | 
				
			||||||
If there are no minecarts in the chunk, we skip searching for them.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Usually hoppers aren't near items, so we can skip most item searches.
 | 
					 | 
				
			||||||
And since minecart hoppers are used _very_ rarely near we can avoid alot of searching there.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Combined, this adds up a lot.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
diff --git a/src/main/java/net/minecraft/world/entity/EntitySelector.java b/src/main/java/net/minecraft/world/entity/EntitySelector.java
 | 
					 | 
				
			||||||
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
 | 
					 | 
				
			||||||
--- a/src/main/java/net/minecraft/world/entity/EntitySelector.java
 | 
					 | 
				
			||||||
+++ b/src/main/java/net/minecraft/world/entity/EntitySelector.java
 | 
					 | 
				
			||||||
@@ -0,0 +0,0 @@ public final class EntitySelector {
 | 
					 | 
				
			||||||
     public static final Predicate<Entity> ENTITY_NOT_BEING_RIDDEN = (entity) -> {
 | 
					 | 
				
			||||||
         return entity.isAlive() && !entity.isVehicle() && !entity.isPassenger();
 | 
					 | 
				
			||||||
     };
 | 
					 | 
				
			||||||
+    public static final Predicate<Entity> isInventory() { return CONTAINER_ENTITY_SELECTOR; } // Paper - OBFHELPER
 | 
					 | 
				
			||||||
     public static final Predicate<Entity> CONTAINER_ENTITY_SELECTOR = (entity) -> {
 | 
					 | 
				
			||||||
         return entity instanceof Container && entity.isAlive();
 | 
					 | 
				
			||||||
     };
 | 
					 | 
				
			||||||
diff --git a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java
 | 
					 | 
				
			||||||
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
 | 
					 | 
				
			||||||
--- a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java
 | 
					 | 
				
			||||||
+++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java
 | 
					 | 
				
			||||||
@@ -0,0 +0,0 @@ import net.minecraft.server.level.ChunkHolder;
 | 
					 | 
				
			||||||
 import net.minecraft.server.level.ServerChunkCache;
 | 
					 | 
				
			||||||
 import net.minecraft.server.level.ServerLevel;
 | 
					 | 
				
			||||||
 import net.minecraft.util.Mth;
 | 
					 | 
				
			||||||
+import net.minecraft.world.Container;
 | 
					 | 
				
			||||||
 import net.minecraft.world.entity.Entity;
 | 
					 | 
				
			||||||
+import net.minecraft.world.entity.EntitySelector;
 | 
					 | 
				
			||||||
 import net.minecraft.world.entity.EntityType;
 | 
					 | 
				
			||||||
 import net.minecraft.world.entity.boss.EnderDragonPart;
 | 
					 | 
				
			||||||
 import net.minecraft.world.entity.boss.enderdragon.EnderDragon;
 | 
					 | 
				
			||||||
+import net.minecraft.world.entity.item.ItemEntity;
 | 
					 | 
				
			||||||
 import net.minecraft.world.level.ChunkPos;
 | 
					 | 
				
			||||||
 import net.minecraft.world.level.ChunkTickList;
 | 
					 | 
				
			||||||
 import net.minecraft.world.level.EmptyTickList;
 | 
					 | 
				
			||||||
@@ -0,0 +0,0 @@ public class LevelChunk implements ChunkAccess {
 | 
					 | 
				
			||||||
             return removed;
 | 
					 | 
				
			||||||
         }
 | 
					 | 
				
			||||||
     }
 | 
					 | 
				
			||||||
+    // Track the number of minecarts and items
 | 
					 | 
				
			||||||
+    // Keep this synced with entitySlices.add() and entitySlices.remove()
 | 
					 | 
				
			||||||
+    private final int[] itemCounts = new int[16];
 | 
					 | 
				
			||||||
+    private final int[] inventoryEntityCounts = new int[16];
 | 
					 | 
				
			||||||
     // Paper end
 | 
					 | 
				
			||||||
 
 | 
					 | 
				
			||||||
     public LevelChunk(Level world, ChunkPos pos, ChunkBiomeContainer biomes, UpgradeData upgradeData, TickList<Block> blockTickScheduler, TickList<Fluid> fluidTickScheduler, long inhabitedTime, @Nullable LevelChunkSection[] sections, @Nullable Consumer<LevelChunk> loadToWorldConsumer) {
 | 
					 | 
				
			||||||
@@ -0,0 +0,0 @@ public class LevelChunk implements ChunkAccess {
 | 
					 | 
				
			||||||
         entity.zChunk = this.chunkPos.z;
 | 
					 | 
				
			||||||
         this.entities.add(entity); // Paper - per chunk entity list
 | 
					 | 
				
			||||||
         this.entitySlices[k].add(entity);
 | 
					 | 
				
			||||||
+        // Paper start
 | 
					 | 
				
			||||||
+        if (entity instanceof ItemEntity) {
 | 
					 | 
				
			||||||
+            itemCounts[k]++;
 | 
					 | 
				
			||||||
+        } else if (entity instanceof Container) {
 | 
					 | 
				
			||||||
+            inventoryEntityCounts[k]++;
 | 
					 | 
				
			||||||
+        }
 | 
					 | 
				
			||||||
+        // Paper end
 | 
					 | 
				
			||||||
         entity.entitySlice = this.entitySlices[k]; // Paper
 | 
					 | 
				
			||||||
         this.markUnsaved(); // Paper
 | 
					 | 
				
			||||||
     }
 | 
					 | 
				
			||||||
@@ -0,0 +0,0 @@ public class LevelChunk implements ChunkAccess {
 | 
					 | 
				
			||||||
         if (!this.entitySlices[section].remove(entity)) {
 | 
					 | 
				
			||||||
             return;
 | 
					 | 
				
			||||||
         }
 | 
					 | 
				
			||||||
+        if (entity instanceof ItemEntity) {
 | 
					 | 
				
			||||||
+            itemCounts[section]--;
 | 
					 | 
				
			||||||
+        } else if (entity instanceof Container) {
 | 
					 | 
				
			||||||
+            inventoryEntityCounts[section]--;
 | 
					 | 
				
			||||||
+        }
 | 
					 | 
				
			||||||
         entityCounts.decrement(entity.getMinecraftKeyString());
 | 
					 | 
				
			||||||
         this.markUnsaved(); // Paper
 | 
					 | 
				
			||||||
         // Paper end
 | 
					 | 
				
			||||||
@@ -0,0 +0,0 @@ public class LevelChunk implements ChunkAccess {
 | 
					 | 
				
			||||||
         for (int k = i; k <= j; ++k) {
 | 
					 | 
				
			||||||
             Iterator iterator = this.entitySlices[k].iterator(); // Spigot
 | 
					 | 
				
			||||||
 
 | 
					 | 
				
			||||||
+            // Paper start - Don't search for inventories if we have none, and that is all we want
 | 
					 | 
				
			||||||
+            /*
 | 
					 | 
				
			||||||
+             * We check if they want inventories by seeing if it is the static `IEntitySelector.d`
 | 
					 | 
				
			||||||
+             *
 | 
					 | 
				
			||||||
+             * Make sure the inventory selector stays in sync.
 | 
					 | 
				
			||||||
+             * It should be the one that checks `var1 instanceof IInventory && var1.isAlive()`
 | 
					 | 
				
			||||||
+             */
 | 
					 | 
				
			||||||
+            if (predicate == EntitySelector.isInventory() && inventoryEntityCounts[k] <= 0) continue;
 | 
					 | 
				
			||||||
             while (iterator.hasNext()) {
 | 
					 | 
				
			||||||
                 T entity = (T) iterator.next(); // CraftBukkit - decompile error
 | 
					 | 
				
			||||||
                 if (entity.shouldBeRemoved) continue; // Paper
 | 
					 | 
				
			||||||
@@ -0,0 +0,0 @@ public class LevelChunk implements ChunkAccess {
 | 
					 | 
				
			||||||
         i = Mth.clamp(i, 0, this.entitySlices.length - 1);
 | 
					 | 
				
			||||||
         j = Mth.clamp(j, 0, this.entitySlices.length - 1);
 | 
					 | 
				
			||||||
 
 | 
					 | 
				
			||||||
+        // Paper start
 | 
					 | 
				
			||||||
+        int[] counts;
 | 
					 | 
				
			||||||
+        if (ItemEntity.class.isAssignableFrom(entityClass)) {
 | 
					 | 
				
			||||||
+            counts = itemCounts;
 | 
					 | 
				
			||||||
+        } else if (Container.class.isAssignableFrom(entityClass)) {
 | 
					 | 
				
			||||||
+            counts = inventoryEntityCounts;
 | 
					 | 
				
			||||||
+        } else {
 | 
					 | 
				
			||||||
+            counts = null;
 | 
					 | 
				
			||||||
+        }
 | 
					 | 
				
			||||||
+        // Paper end
 | 
					 | 
				
			||||||
         for (int k = i; k <= j; ++k) {
 | 
					 | 
				
			||||||
+            if (counts != null && counts[k] <= 0) continue; // Paper - Don't check a chunk if it doesn't have the type we are looking for
 | 
					 | 
				
			||||||
             Iterator iterator = this.entitySlices[k].iterator(); // Spigot
 | 
					 | 
				
			||||||
 
 | 
					 | 
				
			||||||
+            // Paper start - Don't search for inventories if we have none, and that is all we want
 | 
					 | 
				
			||||||
+            /*
 | 
					 | 
				
			||||||
+             * We check if they want inventories by seeing if it is the static `IEntitySelector.d`
 | 
					 | 
				
			||||||
+             *
 | 
					 | 
				
			||||||
+             * Make sure the inventory selector stays in sync.
 | 
					 | 
				
			||||||
+             * It should be the one that checks `var1 instanceof IInventory && var1.isAlive()`
 | 
					 | 
				
			||||||
+             */
 | 
					 | 
				
			||||||
+            if (predicate == EntitySelector.isInventory() && inventoryEntityCounts[k] <= 0) continue;
 | 
					 | 
				
			||||||
+            // Paper end
 | 
					 | 
				
			||||||
             while (iterator.hasNext()) {
 | 
					 | 
				
			||||||
                 T t0 = (T) iterator.next(); // CraftBukkit - decompile error
 | 
					 | 
				
			||||||
                 if (t0.shouldBeRemoved) continue; // Paper
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,127 +0,0 @@
 | 
				
			||||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
 | 
					 | 
				
			||||||
From: kickash32 <kickash32@gmail.com>
 | 
					 | 
				
			||||||
Date: Mon, 3 Jun 2019 02:02:39 -0400
 | 
					 | 
				
			||||||
Subject: [PATCH] Implement alternative item-despawn-rate
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
 | 
					 | 
				
			||||||
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
 | 
					 | 
				
			||||||
--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
 | 
					 | 
				
			||||||
+++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
 | 
					 | 
				
			||||||
@@ -0,0 +0,0 @@
 | 
					 | 
				
			||||||
 package com.destroystokyo.paper;
 | 
					 | 
				
			||||||
 
 | 
					 | 
				
			||||||
 import java.util.Arrays;
 | 
					 | 
				
			||||||
+import java.util.EnumMap;
 | 
					 | 
				
			||||||
+import java.util.HashMap;
 | 
					 | 
				
			||||||
 import java.util.List;
 | 
					 | 
				
			||||||
+import java.util.Map;
 | 
					 | 
				
			||||||
 
 | 
					 | 
				
			||||||
 import com.destroystokyo.paper.antixray.ChunkPacketBlockControllerAntiXray.EngineMode;
 | 
					 | 
				
			||||||
 import org.bukkit.Bukkit;
 | 
					 | 
				
			||||||
+import org.bukkit.Material;
 | 
					 | 
				
			||||||
+import org.bukkit.configuration.ConfigurationSection;
 | 
					 | 
				
			||||||
 import org.bukkit.configuration.file.YamlConfiguration;
 | 
					 | 
				
			||||||
 import org.spigotmc.SpigotWorldConfig;
 | 
					 | 
				
			||||||
 
 | 
					 | 
				
			||||||
@@ -0,0 +0,0 @@ public class PaperWorldConfig {
 | 
					 | 
				
			||||||
     private void disableRelativeProjectileVelocity() {
 | 
					 | 
				
			||||||
         disableRelativeProjectileVelocity = getBoolean("game-mechanics.disable-relative-projectile-velocity", false);
 | 
					 | 
				
			||||||
     }
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+    public boolean altItemDespawnRateEnabled;
 | 
					 | 
				
			||||||
+    public Map<Material, Integer> altItemDespawnRateMap;
 | 
					 | 
				
			||||||
+    private void altItemDespawnRate() {
 | 
					 | 
				
			||||||
+        String path = "alt-item-despawn-rate";
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+        altItemDespawnRateEnabled = getBoolean(path + ".enabled", false);
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+        Map<Material, Integer> altItemDespawnRateMapDefault = new EnumMap<>(Material.class);
 | 
					 | 
				
			||||||
+        altItemDespawnRateMapDefault.put(Material.COBBLESTONE, 300);
 | 
					 | 
				
			||||||
+        for (Material key : altItemDespawnRateMapDefault.keySet()) {
 | 
					 | 
				
			||||||
+            config.addDefault("world-settings.default." + path + ".items." + key, altItemDespawnRateMapDefault.get(key));
 | 
					 | 
				
			||||||
+        }
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+        Map<String, Integer> rawMap = new HashMap<>();
 | 
					 | 
				
			||||||
+        try {
 | 
					 | 
				
			||||||
+            ConfigurationSection mapSection = config.getConfigurationSection("world-settings." + worldName + "." + path + ".items");
 | 
					 | 
				
			||||||
+            if (mapSection == null) {
 | 
					 | 
				
			||||||
+                mapSection = config.getConfigurationSection("world-settings.default." + path + ".items");
 | 
					 | 
				
			||||||
+            }
 | 
					 | 
				
			||||||
+            for (String key : mapSection.getKeys(false)) {
 | 
					 | 
				
			||||||
+                int val = mapSection.getInt(key);
 | 
					 | 
				
			||||||
+                rawMap.put(key, val);
 | 
					 | 
				
			||||||
+            }
 | 
					 | 
				
			||||||
+        }
 | 
					 | 
				
			||||||
+        catch (Exception e) {
 | 
					 | 
				
			||||||
+            logError("alt-item-despawn-rate was malformatted");
 | 
					 | 
				
			||||||
+            altItemDespawnRateEnabled = false;
 | 
					 | 
				
			||||||
+        }
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+        altItemDespawnRateMap = new EnumMap<>(Material.class);
 | 
					 | 
				
			||||||
+        if (!altItemDespawnRateEnabled) {
 | 
					 | 
				
			||||||
+            return;
 | 
					 | 
				
			||||||
+        }
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+        for(String key : rawMap.keySet()) {
 | 
					 | 
				
			||||||
+            try {
 | 
					 | 
				
			||||||
+                altItemDespawnRateMap.put(Material.valueOf(key), rawMap.get(key));
 | 
					 | 
				
			||||||
+            } catch (Exception e) {
 | 
					 | 
				
			||||||
+                logError("Could not add item " + key + " to altItemDespawnRateMap: " + e.getMessage());
 | 
					 | 
				
			||||||
+            }
 | 
					 | 
				
			||||||
+        }
 | 
					 | 
				
			||||||
+        if(altItemDespawnRateEnabled) {
 | 
					 | 
				
			||||||
+            for(Material key : altItemDespawnRateMap.keySet()) {
 | 
					 | 
				
			||||||
+                log("Alternative item despawn rate of " + key + ": " + altItemDespawnRateMap.get(key));
 | 
					 | 
				
			||||||
+            }
 | 
					 | 
				
			||||||
+        }
 | 
					 | 
				
			||||||
+    }
 | 
					 | 
				
			||||||
 }
 | 
					 | 
				
			||||||
diff --git a/src/main/java/net/minecraft/world/entity/item/ItemEntity.java b/src/main/java/net/minecraft/world/entity/item/ItemEntity.java
 | 
					 | 
				
			||||||
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
 | 
					 | 
				
			||||||
--- a/src/main/java/net/minecraft/world/entity/item/ItemEntity.java
 | 
					 | 
				
			||||||
+++ b/src/main/java/net/minecraft/world/entity/item/ItemEntity.java
 | 
					 | 
				
			||||||
@@ -0,0 +0,0 @@ import net.minecraft.server.MinecraftServer;
 | 
					 | 
				
			||||||
 import net.minecraft.server.level.ServerLevel;
 | 
					 | 
				
			||||||
 import net.minecraft.sounds.SoundEvents;
 | 
					 | 
				
			||||||
 import net.minecraft.stats.Stats;
 | 
					 | 
				
			||||||
+import org.bukkit.Material; // Paper
 | 
					 | 
				
			||||||
 import org.bukkit.event.entity.EntityPickupItemEvent;
 | 
					 | 
				
			||||||
 import org.bukkit.event.player.PlayerPickupItemEvent;
 | 
					 | 
				
			||||||
 // CraftBukkit end
 | 
					 | 
				
			||||||
@@ -0,0 +0,0 @@ public class ItemEntity extends Entity {
 | 
					 | 
				
			||||||
                 }
 | 
					 | 
				
			||||||
             }
 | 
					 | 
				
			||||||
 
 | 
					 | 
				
			||||||
-            if (!this.level.isClientSide && this.age >= level.spigotConfig.itemDespawnRate) { // Spigot
 | 
					 | 
				
			||||||
+            if (!this.level.isClientSide && this.age >= this.getDespawnRate()) { // Spigot // Paper
 | 
					 | 
				
			||||||
                 // CraftBukkit start - fire ItemDespawnEvent
 | 
					 | 
				
			||||||
                 if (org.bukkit.craftbukkit.event.CraftEventFactory.callItemDespawnEvent(this).isCancelled()) {
 | 
					 | 
				
			||||||
                     this.age = 0;
 | 
					 | 
				
			||||||
@@ -0,0 +0,0 @@ public class ItemEntity extends Entity {
 | 
					 | 
				
			||||||
         this.lastTick = MinecraftServer.currentTick;
 | 
					 | 
				
			||||||
         // CraftBukkit end
 | 
					 | 
				
			||||||
 
 | 
					 | 
				
			||||||
-        if (!this.level.isClientSide && this.age >= level.spigotConfig.itemDespawnRate) { // Spigot
 | 
					 | 
				
			||||||
+        if (!this.level.isClientSide && this.age >= this.getDespawnRate()) { // Spigot // Paper
 | 
					 | 
				
			||||||
             // CraftBukkit start - fire ItemDespawnEvent
 | 
					 | 
				
			||||||
             if (org.bukkit.craftbukkit.event.CraftEventFactory.callItemDespawnEvent(this).isCancelled()) {
 | 
					 | 
				
			||||||
                 this.age = 0;
 | 
					 | 
				
			||||||
@@ -0,0 +0,0 @@ public class ItemEntity extends Entity {
 | 
					 | 
				
			||||||
 
 | 
					 | 
				
			||||||
     public void makeFakeItem() {
 | 
					 | 
				
			||||||
         this.setNeverPickUp();
 | 
					 | 
				
			||||||
-        this.age = level.spigotConfig.itemDespawnRate - 1; // Spigot
 | 
					 | 
				
			||||||
+        this.age = this.getDespawnRate() - 1; // Spigot // Paper
 | 
					 | 
				
			||||||
     }
 | 
					 | 
				
			||||||
 
 | 
					 | 
				
			||||||
+    // Paper start
 | 
					 | 
				
			||||||
+    public int getDespawnRate(){
 | 
					 | 
				
			||||||
+        Material material = this.getItem().getBukkitStack().getType();
 | 
					 | 
				
			||||||
+        return level.paperConfig.altItemDespawnRateMap.getOrDefault(material, level.spigotConfig.itemDespawnRate);
 | 
					 | 
				
			||||||
+    }
 | 
					 | 
				
			||||||
+    // Paper end
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
     @Override
 | 
					 | 
				
			||||||
     public Packet<?> getAddEntityPacket() {
 | 
					 | 
				
			||||||
         return new ClientboundAddEntityPacket(this);
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,26 +0,0 @@
 | 
				
			||||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
 | 
					 | 
				
			||||||
From: Shane Freeder <theboyetronic@gmail.com>
 | 
					 | 
				
			||||||
Date: Sun, 28 Jul 2019 00:51:11 +0100
 | 
					 | 
				
			||||||
Subject: [PATCH] Mark entities as being ticked when notifying navigation
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java
 | 
					 | 
				
			||||||
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
 | 
					 | 
				
			||||||
--- a/src/main/java/net/minecraft/server/level/ServerLevel.java
 | 
					 | 
				
			||||||
+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
 | 
					 | 
				
			||||||
@@ -0,0 +0,0 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl
 | 
					 | 
				
			||||||
         VoxelShape voxelshape1 = newState.getCollisionShape(this, pos);
 | 
					 | 
				
			||||||
 
 | 
					 | 
				
			||||||
         if (Shapes.joinIsNotEmpty(voxelshape, voxelshape1, BooleanOp.NOT_SAME)) {
 | 
					 | 
				
			||||||
+            boolean wasTicking = this.tickingEntities; this.tickingEntities = true; // Paper
 | 
					 | 
				
			||||||
             Iterator iterator = this.navigations.iterator();
 | 
					 | 
				
			||||||
 
 | 
					 | 
				
			||||||
             while (iterator.hasNext()) {
 | 
					 | 
				
			||||||
@@ -0,0 +0,0 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl
 | 
					 | 
				
			||||||
                 }
 | 
					 | 
				
			||||||
             }
 | 
					 | 
				
			||||||
 
 | 
					 | 
				
			||||||
+            this.tickingEntities = wasTicking; // Paper
 | 
					 | 
				
			||||||
         }
 | 
					 | 
				
			||||||
     }
 | 
					 | 
				
			||||||
 
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,350 +0,0 @@
 | 
				
			||||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
 | 
					 | 
				
			||||||
From: Spottedleaf <Spottedleaf@users.noreply.github.com>
 | 
					 | 
				
			||||||
Date: Fri, 19 Jul 2019 03:29:14 -0700
 | 
					 | 
				
			||||||
Subject: [PATCH] Reduce sync loads
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
This reduces calls to getChunkAt which would load chunks.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
This patch also adds a tool to find calls which are doing this, however
 | 
					 | 
				
			||||||
it must be enabled by setting the startup flag -Dpaper.debug-sync-loads=true
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
To get a debug log for sync loads, the command is /paper syncloadinfo
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
diff --git a/src/main/java/com/destroystokyo/paper/PaperCommand.java b/src/main/java/com/destroystokyo/paper/PaperCommand.java
 | 
					 | 
				
			||||||
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
 | 
					 | 
				
			||||||
--- a/src/main/java/com/destroystokyo/paper/PaperCommand.java
 | 
					 | 
				
			||||||
+++ b/src/main/java/com/destroystokyo/paper/PaperCommand.java
 | 
					 | 
				
			||||||
@@ -0,0 +0,0 @@
 | 
					 | 
				
			||||||
 package com.destroystokyo.paper;
 | 
					 | 
				
			||||||
 
 | 
					 | 
				
			||||||
 import com.destroystokyo.paper.io.chunk.ChunkTaskManager;
 | 
					 | 
				
			||||||
+import com.destroystokyo.paper.io.SyncLoadFinder;
 | 
					 | 
				
			||||||
 import com.google.common.base.Functions;
 | 
					 | 
				
			||||||
 import com.google.common.base.Joiner;
 | 
					 | 
				
			||||||
 import com.google.common.collect.ImmutableSet;
 | 
					 | 
				
			||||||
 import com.google.common.collect.Iterables;
 | 
					 | 
				
			||||||
 import com.google.common.collect.Lists;
 | 
					 | 
				
			||||||
 import com.google.common.collect.Maps;
 | 
					 | 
				
			||||||
+import com.google.gson.JsonObject;
 | 
					 | 
				
			||||||
+import com.google.gson.internal.Streams;
 | 
					 | 
				
			||||||
+import com.google.gson.stream.JsonWriter;
 | 
					 | 
				
			||||||
+import net.minecraft.resources.ResourceLocation;
 | 
					 | 
				
			||||||
+import net.minecraft.server.MCUtil;
 | 
					 | 
				
			||||||
 import net.minecraft.server.MinecraftServer;
 | 
					 | 
				
			||||||
 import net.minecraft.server.level.ChunkHolder;
 | 
					 | 
				
			||||||
 import net.minecraft.server.level.ServerChunkCache;
 | 
					 | 
				
			||||||
@@ -0,0 +0,0 @@ import net.minecraft.server.level.ServerLevel;
 | 
					 | 
				
			||||||
 import net.minecraft.world.entity.Entity;
 | 
					 | 
				
			||||||
 import net.minecraft.world.entity.EntityType;
 | 
					 | 
				
			||||||
 import net.minecraft.world.level.ChunkPos;
 | 
					 | 
				
			||||||
-import net.minecraft.resources.ResourceLocation;
 | 
					 | 
				
			||||||
 import net.minecraft.server.MCUtil;
 | 
					 | 
				
			||||||
 import org.apache.commons.lang3.tuple.MutablePair;
 | 
					 | 
				
			||||||
 import org.apache.commons.lang3.tuple.Pair;
 | 
					 | 
				
			||||||
@@ -0,0 +0,0 @@ import org.bukkit.craftbukkit.CraftWorld;
 | 
					 | 
				
			||||||
 import org.bukkit.entity.Player;
 | 
					 | 
				
			||||||
 
 | 
					 | 
				
			||||||
 import java.io.File;
 | 
					 | 
				
			||||||
+import java.io.FileOutputStream;
 | 
					 | 
				
			||||||
+import java.io.PrintStream;
 | 
					 | 
				
			||||||
+import java.io.StringWriter;
 | 
					 | 
				
			||||||
 import java.time.LocalDateTime;
 | 
					 | 
				
			||||||
 import java.time.format.DateTimeFormatter;
 | 
					 | 
				
			||||||
 import java.util.ArrayList;
 | 
					 | 
				
			||||||
@@ -0,0 +0,0 @@ import java.util.stream.Collectors;
 | 
					 | 
				
			||||||
 
 | 
					 | 
				
			||||||
 public class PaperCommand extends Command {
 | 
					 | 
				
			||||||
     private static final String BASE_PERM = "bukkit.command.paper.";
 | 
					 | 
				
			||||||
-    private static final ImmutableSet<String> SUBCOMMANDS = ImmutableSet.<String>builder().add("heap", "entity", "reload", "version", "debug", "chunkinfo", "dumpwaiting").build();
 | 
					 | 
				
			||||||
+    private static final ImmutableSet<String> SUBCOMMANDS = ImmutableSet.<String>builder().add("heap", "entity", "reload", "version", "debug", "chunkinfo", "dumpwaiting", "syncloadinfo").build();
 | 
					 | 
				
			||||||
 
 | 
					 | 
				
			||||||
     public PaperCommand(String name) {
 | 
					 | 
				
			||||||
         super(name);
 | 
					 | 
				
			||||||
@@ -0,0 +0,0 @@ public class PaperCommand extends Command {
 | 
					 | 
				
			||||||
             case "chunkinfo":
 | 
					 | 
				
			||||||
                 doChunkInfo(sender, args);
 | 
					 | 
				
			||||||
                 break;
 | 
					 | 
				
			||||||
+            case "syncloadinfo":
 | 
					 | 
				
			||||||
+                this.doSyncLoadInfo(sender, args);
 | 
					 | 
				
			||||||
+                break;
 | 
					 | 
				
			||||||
             case "ver":
 | 
					 | 
				
			||||||
                 if (!testPermission(sender, "version")) break; // "ver" needs a special check because it's an alias. All other commands are checked up before the switch statement (because they are present in the SUBCOMMANDS set)
 | 
					 | 
				
			||||||
             case "version":
 | 
					 | 
				
			||||||
@@ -0,0 +0,0 @@ public class PaperCommand extends Command {
 | 
					 | 
				
			||||||
         return true;
 | 
					 | 
				
			||||||
     }
 | 
					 | 
				
			||||||
 
 | 
					 | 
				
			||||||
+    private void doSyncLoadInfo(CommandSender sender, String[] args) {
 | 
					 | 
				
			||||||
+        if (!SyncLoadFinder.ENABLED) {
 | 
					 | 
				
			||||||
+            sender.sendMessage(ChatColor.RED + "This command requires the server startup flag '-Dpaper.debug-sync-loads=true' to be set.");
 | 
					 | 
				
			||||||
+            return;
 | 
					 | 
				
			||||||
+        }
 | 
					 | 
				
			||||||
+        File file = new File(new File(new File("."), "debug"),
 | 
					 | 
				
			||||||
+            "sync-load-info" + DateTimeFormatter.ofPattern("yyyy-MM-dd_HH.mm.ss").format(LocalDateTime.now()) + ".txt");
 | 
					 | 
				
			||||||
+        file.getParentFile().mkdirs();
 | 
					 | 
				
			||||||
+        sender.sendMessage(ChatColor.GREEN + "Writing sync load info to " + file.toString());
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+        try {
 | 
					 | 
				
			||||||
+            final JsonObject data = SyncLoadFinder.serialize();
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+            StringWriter stringWriter = new StringWriter();
 | 
					 | 
				
			||||||
+            JsonWriter jsonWriter = new JsonWriter(stringWriter);
 | 
					 | 
				
			||||||
+            jsonWriter.setIndent(" ");
 | 
					 | 
				
			||||||
+            jsonWriter.setLenient(false);
 | 
					 | 
				
			||||||
+            Streams.write(data, jsonWriter);
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+            String fileData = stringWriter.toString();
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+            try (
 | 
					 | 
				
			||||||
+                PrintStream out = new PrintStream(new FileOutputStream(file), false, "UTF-8")
 | 
					 | 
				
			||||||
+            ) {
 | 
					 | 
				
			||||||
+                out.print(fileData);
 | 
					 | 
				
			||||||
+            }
 | 
					 | 
				
			||||||
+            sender.sendMessage(ChatColor.GREEN + "Successfully written sync load information!");
 | 
					 | 
				
			||||||
+        } catch (Throwable thr) {
 | 
					 | 
				
			||||||
+            sender.sendMessage(ChatColor.RED + "Failed to write sync load information");
 | 
					 | 
				
			||||||
+            thr.printStackTrace();
 | 
					 | 
				
			||||||
+        }
 | 
					 | 
				
			||||||
+    }
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
     private void doChunkInfo(CommandSender sender, String[] args) {
 | 
					 | 
				
			||||||
         List<org.bukkit.World> worlds;
 | 
					 | 
				
			||||||
         if (args.length < 2 || args[1].equals("*")) {
 | 
					 | 
				
			||||||
diff --git a/src/main/java/com/destroystokyo/paper/io/SyncLoadFinder.java b/src/main/java/com/destroystokyo/paper/io/SyncLoadFinder.java
 | 
					 | 
				
			||||||
new file mode 100644
 | 
					 | 
				
			||||||
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
 | 
					 | 
				
			||||||
--- /dev/null
 | 
					 | 
				
			||||||
+++ b/src/main/java/com/destroystokyo/paper/io/SyncLoadFinder.java
 | 
					 | 
				
			||||||
@@ -0,0 +0,0 @@
 | 
					 | 
				
			||||||
+package com.destroystokyo.paper.io;
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+import com.google.gson.JsonArray;
 | 
					 | 
				
			||||||
+import com.google.gson.JsonObject;
 | 
					 | 
				
			||||||
+import com.mojang.datafixers.util.Pair;
 | 
					 | 
				
			||||||
+import it.unimi.dsi.fastutil.longs.Long2IntMap;
 | 
					 | 
				
			||||||
+import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap;
 | 
					 | 
				
			||||||
+import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+import java.util.ArrayList;
 | 
					 | 
				
			||||||
+import java.util.List;
 | 
					 | 
				
			||||||
+import java.util.Map;
 | 
					 | 
				
			||||||
+import java.util.WeakHashMap;
 | 
					 | 
				
			||||||
+import net.minecraft.world.level.Level;
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+public class SyncLoadFinder {
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+    public static final boolean ENABLED = Boolean.getBoolean("paper.debug-sync-loads");
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+    private static final WeakHashMap<Level, Object2ObjectOpenHashMap<ThrowableWithEquals, SyncLoadInformation>> SYNC_LOADS = new WeakHashMap<>();
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+    private static final class SyncLoadInformation {
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+        public int times;
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+        public final Long2IntOpenHashMap coordinateTimes = new Long2IntOpenHashMap();
 | 
					 | 
				
			||||||
+    }
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+    public static void logSyncLoad(final Level world, final int chunkX, final int chunkZ) {
 | 
					 | 
				
			||||||
+        if (!ENABLED) {
 | 
					 | 
				
			||||||
+            return;
 | 
					 | 
				
			||||||
+        }
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+        final ThrowableWithEquals stacktrace = new ThrowableWithEquals(Thread.currentThread().getStackTrace());
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+        SYNC_LOADS.compute(world, (final Level keyInMap, Object2ObjectOpenHashMap<ThrowableWithEquals, SyncLoadInformation> map) -> {
 | 
					 | 
				
			||||||
+            if (map == null) {
 | 
					 | 
				
			||||||
+                map = new Object2ObjectOpenHashMap<>();
 | 
					 | 
				
			||||||
+            }
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+            map.compute(stacktrace, (ThrowableWithEquals keyInMap0, SyncLoadInformation valueInMap) -> {
 | 
					 | 
				
			||||||
+                if (valueInMap == null) {
 | 
					 | 
				
			||||||
+                    valueInMap = new SyncLoadInformation();
 | 
					 | 
				
			||||||
+                }
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+                ++valueInMap.times;
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+                valueInMap.coordinateTimes.compute(IOUtil.getCoordinateKey(chunkX, chunkZ), (Long keyInMap1, Integer valueInMap1) -> {
 | 
					 | 
				
			||||||
+                    return valueInMap1 == null ? Integer.valueOf(1) : Integer.valueOf(valueInMap1.intValue() + 1);
 | 
					 | 
				
			||||||
+                });
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+                return valueInMap;
 | 
					 | 
				
			||||||
+            });
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+            return map;
 | 
					 | 
				
			||||||
+        });
 | 
					 | 
				
			||||||
+    }
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+    public static JsonObject serialize() {
 | 
					 | 
				
			||||||
+        final JsonObject ret = new JsonObject();
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+        final JsonArray worldsData = new JsonArray();
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+        for (final Map.Entry<Level, Object2ObjectOpenHashMap<ThrowableWithEquals, SyncLoadInformation>> entry : SYNC_LOADS.entrySet()) {
 | 
					 | 
				
			||||||
+            final Level world = entry.getKey();
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+            final JsonObject worldData = new JsonObject();
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+            worldData.addProperty("name", world.getWorld().getName());
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+            final List<Pair<ThrowableWithEquals, SyncLoadInformation>> data = new ArrayList<>();
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+            entry.getValue().forEach((ThrowableWithEquals stacktrace, SyncLoadInformation times) -> {
 | 
					 | 
				
			||||||
+                data.add(new Pair<>(stacktrace, times));
 | 
					 | 
				
			||||||
+            });
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+            data.sort((Pair<ThrowableWithEquals, SyncLoadInformation> pair1, Pair<ThrowableWithEquals, SyncLoadInformation> pair2) -> {
 | 
					 | 
				
			||||||
+                return Integer.compare(pair2.getSecond().times, pair1.getSecond().times); // reverse order
 | 
					 | 
				
			||||||
+            });
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+            final JsonArray stacktraces = new JsonArray();
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+            for (Pair<ThrowableWithEquals, SyncLoadInformation> pair : data) {
 | 
					 | 
				
			||||||
+                final JsonObject stacktrace = new JsonObject();
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+                stacktrace.addProperty("times", pair.getSecond().times);
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+                final JsonArray traces = new JsonArray();
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+                for (StackTraceElement element : pair.getFirst().stacktrace) {
 | 
					 | 
				
			||||||
+                    traces.add(String.valueOf(element));
 | 
					 | 
				
			||||||
+                }
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+                stacktrace.add("stacktrace", traces);
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+                final JsonArray coordinates = new JsonArray();
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+                for (Long2IntMap.Entry coordinate : pair.getSecond().coordinateTimes.long2IntEntrySet()) {
 | 
					 | 
				
			||||||
+                    final long key = coordinate.getLongKey();
 | 
					 | 
				
			||||||
+                    final int times = coordinate.getIntValue();
 | 
					 | 
				
			||||||
+                    coordinates.add("(" + IOUtil.getCoordinateX(key) + "," + IOUtil.getCoordinateZ(key) + "): " + times);
 | 
					 | 
				
			||||||
+                }
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+                stacktrace.add("coordinates", coordinates);
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+                stacktraces.add(stacktrace);
 | 
					 | 
				
			||||||
+            }
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+            worldData.add("stacktraces", stacktraces);
 | 
					 | 
				
			||||||
+            worldsData.add(worldData);
 | 
					 | 
				
			||||||
+        }
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+        ret.add("worlds", worldsData);
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+        return ret;
 | 
					 | 
				
			||||||
+    }
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+    static final class ThrowableWithEquals {
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+        private final StackTraceElement[] stacktrace;
 | 
					 | 
				
			||||||
+        private final int hash;
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+        public ThrowableWithEquals(final StackTraceElement[] stacktrace) {
 | 
					 | 
				
			||||||
+            this.stacktrace = stacktrace;
 | 
					 | 
				
			||||||
+            this.hash = ThrowableWithEquals.hash(stacktrace);
 | 
					 | 
				
			||||||
+        }
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+        public static int hash(final StackTraceElement[] stacktrace) {
 | 
					 | 
				
			||||||
+            int hash = 0;
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+            for (int i = 0; i < stacktrace.length; ++i) {
 | 
					 | 
				
			||||||
+                hash *= 31;
 | 
					 | 
				
			||||||
+                hash += stacktrace[i].hashCode();
 | 
					 | 
				
			||||||
+            }
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+            return hash;
 | 
					 | 
				
			||||||
+        }
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+        @Override
 | 
					 | 
				
			||||||
+        public int hashCode() {
 | 
					 | 
				
			||||||
+            return this.hash;
 | 
					 | 
				
			||||||
+        }
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+        @Override
 | 
					 | 
				
			||||||
+        public boolean equals(final Object obj) {
 | 
					 | 
				
			||||||
+            if (obj == null || obj.getClass() != this.getClass()) {
 | 
					 | 
				
			||||||
+                return false;
 | 
					 | 
				
			||||||
+            }
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+            final ThrowableWithEquals other = (ThrowableWithEquals)obj;
 | 
					 | 
				
			||||||
+            final StackTraceElement[] otherStackTrace = other.stacktrace;
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+            if (this.stacktrace.length != otherStackTrace.length || this.hash != other.hash) {
 | 
					 | 
				
			||||||
+                return false;
 | 
					 | 
				
			||||||
+            }
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+            if (this == obj) {
 | 
					 | 
				
			||||||
+                return true;
 | 
					 | 
				
			||||||
+            }
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+            for (int i = 0; i < this.stacktrace.length; ++i) {
 | 
					 | 
				
			||||||
+                if (!this.stacktrace[i].equals(otherStackTrace[i])) {
 | 
					 | 
				
			||||||
+                    return false;
 | 
					 | 
				
			||||||
+                }
 | 
					 | 
				
			||||||
+            }
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+            return true;
 | 
					 | 
				
			||||||
+        }
 | 
					 | 
				
			||||||
+    }
 | 
					 | 
				
			||||||
+}
 | 
					 | 
				
			||||||
diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java
 | 
					 | 
				
			||||||
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
 | 
					 | 
				
			||||||
--- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java
 | 
					 | 
				
			||||||
+++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java
 | 
					 | 
				
			||||||
@@ -0,0 +0,0 @@ public class ServerChunkCache extends ChunkSource {
 | 
					 | 
				
			||||||
                 this.level.asyncChunkTaskManager.raisePriority(x1, z1, com.destroystokyo.paper.io.PrioritizedTaskQueue.HIGHEST_PRIORITY);
 | 
					 | 
				
			||||||
                 com.destroystokyo.paper.io.chunk.ChunkTaskManager.pushChunkWait(this.level, x1, z1);
 | 
					 | 
				
			||||||
                 // Paper end
 | 
					 | 
				
			||||||
+                com.destroystokyo.paper.io.SyncLoadFinder.logSyncLoad(this.level, x1, z1); // Paper - sync load info
 | 
					 | 
				
			||||||
                 this.level.timings.syncChunkLoad.startTiming(); // Paper
 | 
					 | 
				
			||||||
             this.mainThreadProcessor.managedBlock(completablefuture::isDone);
 | 
					 | 
				
			||||||
                 com.destroystokyo.paper.io.chunk.ChunkTaskManager.popChunkWait(); // Paper - async chunk debug
 | 
					 | 
				
			||||||
diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java
 | 
					 | 
				
			||||||
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
 | 
					 | 
				
			||||||
--- a/src/main/java/net/minecraft/server/level/ServerLevel.java
 | 
					 | 
				
			||||||
+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
 | 
					 | 
				
			||||||
@@ -0,0 +0,0 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl
 | 
					 | 
				
			||||||
     };
 | 
					 | 
				
			||||||
     public final com.destroystokyo.paper.io.chunk.ChunkTaskManager asyncChunkTaskManager;
 | 
					 | 
				
			||||||
     // Paper end
 | 
					 | 
				
			||||||
+    // Paper start
 | 
					 | 
				
			||||||
+    @Override
 | 
					 | 
				
			||||||
+    public boolean hasChunk(int chunkX, int chunkZ) {
 | 
					 | 
				
			||||||
+        return this.getChunkSource().getChunkAtIfLoadedImmediately(chunkX, chunkZ) != null;
 | 
					 | 
				
			||||||
+    }
 | 
					 | 
				
			||||||
+    // Paper end
 | 
					 | 
				
			||||||
 
 | 
					 | 
				
			||||||
     // Add env and gen to constructor, WorldData -> WorldDataServer
 | 
					 | 
				
			||||||
     public ServerLevel(MinecraftServer minecraftserver, Executor executor, LevelStorageSource.LevelStorageAccess convertable_conversionsession, ServerLevelData iworlddataserver, ResourceKey<net.minecraft.world.level.Level> resourcekey, DimensionType dimensionmanager, ChunkProgressListener worldloadlistener, ChunkGenerator chunkgenerator, boolean flag, long i, List<CustomSpawner> list, boolean flag1, org.bukkit.World.Environment env, org.bukkit.generator.ChunkGenerator gen) {
 | 
					 | 
				
			||||||
diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java
 | 
					 | 
				
			||||||
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
 | 
					 | 
				
			||||||
--- a/src/main/java/net/minecraft/world/level/Level.java
 | 
					 | 
				
			||||||
+++ b/src/main/java/net/minecraft/world/level/Level.java
 | 
					 | 
				
			||||||
@@ -0,0 +0,0 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
 | 
					 | 
				
			||||||
 
 | 
					 | 
				
			||||||
         for (int i1 = i; i1 <= j; ++i1) {
 | 
					 | 
				
			||||||
             for (int j1 = k; j1 <= l; ++j1) {
 | 
					 | 
				
			||||||
-                LevelChunk chunk = ichunkprovider.getChunk(i1, j1, false);
 | 
					 | 
				
			||||||
+                LevelChunk chunk = (LevelChunk)this.getChunkIfLoadedImmediately(i1, j1); // Paper
 | 
					 | 
				
			||||||
 
 | 
					 | 
				
			||||||
                 if (chunk != null) {
 | 
					 | 
				
			||||||
                     chunk.getEntities(except, box, list, predicate);
 | 
					 | 
				
			||||||
@@ -0,0 +0,0 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
 | 
					 | 
				
			||||||
 
 | 
					 | 
				
			||||||
         for (int i1 = i; i1 < j; ++i1) {
 | 
					 | 
				
			||||||
             for (int j1 = k; j1 < l; ++j1) {
 | 
					 | 
				
			||||||
-                LevelChunk chunk = this.getChunkSource().getChunk(i1, j1, false);
 | 
					 | 
				
			||||||
+                LevelChunk chunk = (LevelChunk)this.getChunkIfLoadedImmediately(i1, j1); // Paper
 | 
					 | 
				
			||||||
 
 | 
					 | 
				
			||||||
                 if (chunk != null) {
 | 
					 | 
				
			||||||
                     chunk.getEntities(type, box, list, predicate);
 | 
					 | 
				
			||||||
@@ -0,0 +0,0 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
 | 
					 | 
				
			||||||
 
 | 
					 | 
				
			||||||
         for (int i1 = i; i1 < j; ++i1) {
 | 
					 | 
				
			||||||
             for (int j1 = k; j1 < l; ++j1) {
 | 
					 | 
				
			||||||
-                LevelChunk chunk = ichunkprovider.getChunk(i1, j1, false);
 | 
					 | 
				
			||||||
+                LevelChunk chunk = (LevelChunk)this.getChunkIfLoadedImmediately(i1, j1); // Paper
 | 
					 | 
				
			||||||
 
 | 
					 | 
				
			||||||
                 if (chunk != null) {
 | 
					 | 
				
			||||||
                     chunk.getEntitiesOfClass(entityClass, box, list, predicate);
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,117 +0,0 @@
 | 
				
			||||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
 | 
					 | 
				
			||||||
From: Aikar <aikar@aikar.co>
 | 
					 | 
				
			||||||
Date: Fri, 29 May 2020 20:29:02 -0400
 | 
					 | 
				
			||||||
Subject: [PATCH] Synchronize DataPaletteBlock instead of ReentrantLock
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Mojang has flaws in their logic about chunks being concurrently
 | 
					 | 
				
			||||||
wrote to. So we constantly see crashes around multiple threads writing.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Additionally, java has optimized synchronization so well that its
 | 
					 | 
				
			||||||
in many times faster than trying to manage read wrote locks for low
 | 
					 | 
				
			||||||
contention situations.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
And this is extremely a low contention situation.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
diff --git a/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java b/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java
 | 
					 | 
				
			||||||
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
 | 
					 | 
				
			||||||
--- a/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java
 | 
					 | 
				
			||||||
+++ b/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java
 | 
					 | 
				
			||||||
@@ -0,0 +0,0 @@ import java.util.function.Function;
 | 
					 | 
				
			||||||
 import java.util.function.Predicate;
 | 
					 | 
				
			||||||
 import java.util.stream.Collectors;
 | 
					 | 
				
			||||||
 import net.minecraft.CrashReport;
 | 
					 | 
				
			||||||
-import net.minecraft.CrashReportCategory;
 | 
					 | 
				
			||||||
 import net.minecraft.ReportedException;
 | 
					 | 
				
			||||||
 import net.minecraft.core.IdMapper;
 | 
					 | 
				
			||||||
 import net.minecraft.nbt.CompoundTag;
 | 
					 | 
				
			||||||
@@ -0,0 +0,0 @@ public class PalettedContainer<T> implements PaletteResize<T> {
 | 
					 | 
				
			||||||
     private int bits; private int getBitsPerObject() { return this.bits; } // Paper - OBFHELPER
 | 
					 | 
				
			||||||
     private final ReentrantLock lock = new ReentrantLock();
 | 
					 | 
				
			||||||
 
 | 
					 | 
				
			||||||
-    public void acquire() {
 | 
					 | 
				
			||||||
-        if (this.lock.isLocked() && !this.lock.isHeldByCurrentThread()) {
 | 
					 | 
				
			||||||
+    public void acquire() { /* // Paper start - disable this - use proper synchronization
 | 
					 | 
				
			||||||
+        if (this.j.isLocked() && !this.j.isHeldByCurrentThread()) {
 | 
					 | 
				
			||||||
             String s = (String) Thread.getAllStackTraces().keySet().stream().filter(Objects::nonNull).map((thread) -> {
 | 
					 | 
				
			||||||
                 return thread.getName() + ": \n\tat " + (String) Arrays.stream(thread.getStackTrace()).map(Object::toString).collect(Collectors.joining("\n\tat "));
 | 
					 | 
				
			||||||
             }).collect(Collectors.joining("\n"));
 | 
					 | 
				
			||||||
             CrashReport crashreport = new CrashReport("Writing into PalettedContainer from multiple threads", new IllegalStateException());
 | 
					 | 
				
			||||||
-            CrashReportCategory crashreportsystemdetails = crashreport.addCategory("Thread dumps");
 | 
					 | 
				
			||||||
+            CrashReportSystemDetails crashreportsystemdetails = crashreport.a("Thread dumps");
 | 
					 | 
				
			||||||
 
 | 
					 | 
				
			||||||
-            crashreportsystemdetails.setDetail("Thread dumps", (Object) s);
 | 
					 | 
				
			||||||
+            crashreportsystemdetails.a("Thread dumps", (Object) s);
 | 
					 | 
				
			||||||
             throw new ReportedException(crashreport);
 | 
					 | 
				
			||||||
         } else {
 | 
					 | 
				
			||||||
-            this.lock.lock();
 | 
					 | 
				
			||||||
-        }
 | 
					 | 
				
			||||||
+            this.j.lock();
 | 
					 | 
				
			||||||
+        } */ // Paper end
 | 
					 | 
				
			||||||
     }
 | 
					 | 
				
			||||||
 
 | 
					 | 
				
			||||||
     public void release() {
 | 
					 | 
				
			||||||
-        this.lock.unlock();
 | 
					 | 
				
			||||||
+        //this.j.unlock(); // Paper - disable this
 | 
					 | 
				
			||||||
     }
 | 
					 | 
				
			||||||
 
 | 
					 | 
				
			||||||
     public PalettedContainer(Palette<T> fallbackPalette, IdMapper<T> idList, Function<CompoundTag, T> elementDeserializer, Function<T, CompoundTag> elementSerializer, T defaultElement) {
 | 
					 | 
				
			||||||
@@ -0,0 +0,0 @@ public class PalettedContainer<T> implements PaletteResize<T> {
 | 
					 | 
				
			||||||
     }
 | 
					 | 
				
			||||||
 
 | 
					 | 
				
			||||||
     @Override
 | 
					 | 
				
			||||||
-    public int onResize(int newSize, T objectAdded) {
 | 
					 | 
				
			||||||
+    public synchronized int onResize(int newSize, T objectAdded) { // Paper - synchronize
 | 
					 | 
				
			||||||
         this.acquire();
 | 
					 | 
				
			||||||
         BitStorage databits = this.storage;
 | 
					 | 
				
			||||||
         Palette<T> datapalette = this.palette;
 | 
					 | 
				
			||||||
@@ -0,0 +0,0 @@ public class PalettedContainer<T> implements PaletteResize<T> {
 | 
					 | 
				
			||||||
     }
 | 
					 | 
				
			||||||
 
 | 
					 | 
				
			||||||
     public T getAndSet(int x, int y, int z, T value) {
 | 
					 | 
				
			||||||
-        this.acquire();
 | 
					 | 
				
			||||||
-        T t1 = this.getAndSet(getIndex(x, y, z), value);
 | 
					 | 
				
			||||||
+        //this.a(); // Paper - remove to reduce ops - synchronize handled below
 | 
					 | 
				
			||||||
+        return this.getAndSet(getIndex(x, y, z), value); // Paper
 | 
					 | 
				
			||||||
 
 | 
					 | 
				
			||||||
-        this.release();
 | 
					 | 
				
			||||||
-        return t1;
 | 
					 | 
				
			||||||
+        //this.b(); // Paper
 | 
					 | 
				
			||||||
+        //return t1; // PAper
 | 
					 | 
				
			||||||
     }
 | 
					 | 
				
			||||||
 
 | 
					 | 
				
			||||||
     public T getAndSetUnchecked(int x, int y, int z, T value) {
 | 
					 | 
				
			||||||
         return this.getAndSet(getIndex(x, y, z), value);
 | 
					 | 
				
			||||||
     }
 | 
					 | 
				
			||||||
 
 | 
					 | 
				
			||||||
-    protected T getAndSet(int index, T value) {
 | 
					 | 
				
			||||||
+    protected synchronized T getAndSet(int index, T value) { // Paper - synchronize - writes
 | 
					 | 
				
			||||||
         int j = this.palette.idFor(value);
 | 
					 | 
				
			||||||
         int k = this.storage.getAndSet(index, j);
 | 
					 | 
				
			||||||
         T t1 = this.palette.valueFor(k);
 | 
					 | 
				
			||||||
@@ -0,0 +0,0 @@ public class PalettedContainer<T> implements PaletteResize<T> {
 | 
					 | 
				
			||||||
     }
 | 
					 | 
				
			||||||
 
 | 
					 | 
				
			||||||
     public void writeDataPaletteBlock(FriendlyByteBuf packetDataSerializer) { this.write(packetDataSerializer); } // Paper - OBFHELPER
 | 
					 | 
				
			||||||
-    public void write(FriendlyByteBuf buf) {
 | 
					 | 
				
			||||||
+    public synchronized void write(FriendlyByteBuf buf) { // Paper - synchronize
 | 
					 | 
				
			||||||
         this.acquire();
 | 
					 | 
				
			||||||
         buf.writeByte(this.bits);
 | 
					 | 
				
			||||||
         this.palette.write(buf);
 | 
					 | 
				
			||||||
@@ -0,0 +0,0 @@ public class PalettedContainer<T> implements PaletteResize<T> {
 | 
					 | 
				
			||||||
         this.release();
 | 
					 | 
				
			||||||
     }
 | 
					 | 
				
			||||||
 
 | 
					 | 
				
			||||||
-    public void read(ListTag paletteTag, long[] data) {
 | 
					 | 
				
			||||||
+    public synchronized void read(ListTag paletteTag, long[] data) { // Paper - synchronize
 | 
					 | 
				
			||||||
         this.acquire();
 | 
					 | 
				
			||||||
         int i = Math.max(4, Mth.ceillog2(paletteTag.size()));
 | 
					 | 
				
			||||||
 
 | 
					 | 
				
			||||||
@@ -0,0 +0,0 @@ public class PalettedContainer<T> implements PaletteResize<T> {
 | 
					 | 
				
			||||||
         this.release();
 | 
					 | 
				
			||||||
     }
 | 
					 | 
				
			||||||
 
 | 
					 | 
				
			||||||
-    public void write(CompoundTag nbttagcompound, String s, String s1) {
 | 
					 | 
				
			||||||
+    public synchronized void write(CompoundTag nbttagcompound, String s, String s1) { // Paper - synchronize
 | 
					 | 
				
			||||||
         this.acquire();
 | 
					 | 
				
			||||||
         HashMapPalette<T> datapalettehash = new HashMapPalette<>(this.registry, this.bits, this.dummyPaletteResize, this.reader, this.writer);
 | 
					 | 
				
			||||||
         T t0 = this.defaultValue;
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,119 +0,0 @@
 | 
				
			||||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
 | 
					 | 
				
			||||||
From: Spottedleaf <Spottedleaf@users.noreply.github.com>
 | 
					 | 
				
			||||||
Date: Mon, 8 Jul 2019 00:13:36 -0700
 | 
					 | 
				
			||||||
Subject: [PATCH] Use getChunkIfLoadedImmediately in places
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
This prevents us from hitting chunk loads for chunks at or less-than
 | 
					 | 
				
			||||||
ticket level 33 (yes getChunkIfLoaded will actually perform a chunk
 | 
					 | 
				
			||||||
load in that case).
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java
 | 
					 | 
				
			||||||
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
 | 
					 | 
				
			||||||
--- a/src/main/java/net/minecraft/server/level/ServerLevel.java
 | 
					 | 
				
			||||||
+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
 | 
					 | 
				
			||||||
@@ -0,0 +0,0 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl
 | 
					 | 
				
			||||||
     }
 | 
					 | 
				
			||||||
 
 | 
					 | 
				
			||||||
     @Override public LevelChunk getChunkIfLoaded(int x, int z) { // Paper - this was added in world too but keeping here for NMS ABI
 | 
					 | 
				
			||||||
-        return this.chunkSource.getChunk(x, z, false);
 | 
					 | 
				
			||||||
+        return this.chunkSource.getChunkAtIfLoadedImmediately(x, z); // Paper
 | 
					 | 
				
			||||||
     }
 | 
					 | 
				
			||||||
 
 | 
					 | 
				
			||||||
     // Paper start - Asynchronous IO
 | 
					 | 
				
			||||||
diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
 | 
					 | 
				
			||||||
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
 | 
					 | 
				
			||||||
--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
 | 
					 | 
				
			||||||
+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
 | 
					 | 
				
			||||||
@@ -0,0 +0,0 @@ public class ServerGamePacketListenerImpl implements ServerGamePacketListener {
 | 
					 | 
				
			||||||
                                 speed = player.abilities.walkingSpeed * 10f;
 | 
					 | 
				
			||||||
                             }
 | 
					 | 
				
			||||||
                             // Paper start - Prevent moving into unloaded chunks
 | 
					 | 
				
			||||||
-                            if (player.level.paperConfig.preventMovingIntoUnloadedChunks && (this.player.getX() != toX || this.player.getZ() != toZ) && !worldserver.hasChunk((int) Math.floor(toX) >> 4, (int) Math.floor(toZ) >> 4)) {
 | 
					 | 
				
			||||||
+                            if (player.level.paperConfig.preventMovingIntoUnloadedChunks && (this.player.getX() != toX || this.player.getZ() != toZ) && worldserver.getChunkIfLoadedImmediately((int) Math.floor(toX) >> 4, (int) Math.floor(toZ) >> 4) == null) { // Paper - use getIfLoadedImmediately
 | 
					 | 
				
			||||||
                                 this.internalTeleport(this.player.getX(), this.player.getY(), this.player.getZ(), this.player.yRot, this.player.xRot, Collections.emptySet());
 | 
					 | 
				
			||||||
                                 return;
 | 
					 | 
				
			||||||
                             }
 | 
					 | 
				
			||||||
diff --git a/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiManager.java b/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiManager.java
 | 
					 | 
				
			||||||
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
 | 
					 | 
				
			||||||
--- a/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiManager.java
 | 
					 | 
				
			||||||
+++ b/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiManager.java
 | 
					 | 
				
			||||||
@@ -0,0 +0,0 @@ public class PoiManager extends SectionStorage<PoiSection> {
 | 
					 | 
				
			||||||
     // Paper start - Asynchronous chunk io
 | 
					 | 
				
			||||||
     @javax.annotation.Nullable
 | 
					 | 
				
			||||||
     @Override
 | 
					 | 
				
			||||||
-    public CompoundTag read(ChunkPos chunkcoordintpair) throws java.io.IOException {
 | 
					 | 
				
			||||||
+    public CompoundTag read(ChunkPos pos) throws java.io.IOException {
 | 
					 | 
				
			||||||
         if (this.world != null && Thread.currentThread() != com.destroystokyo.paper.io.PaperFileIOThread.Holder.INSTANCE) {
 | 
					 | 
				
			||||||
             CompoundTag ret = com.destroystokyo.paper.io.PaperFileIOThread.Holder.INSTANCE
 | 
					 | 
				
			||||||
-                .loadChunkDataAsyncFuture(this.world, chunkcoordintpair.x, chunkcoordintpair.z, com.destroystokyo.paper.io.IOUtil.getPriorityForCurrentThread(),
 | 
					 | 
				
			||||||
+                .loadChunkDataAsyncFuture(this.world, pos.x, pos.z, com.destroystokyo.paper.io.IOUtil.getPriorityForCurrentThread(),
 | 
					 | 
				
			||||||
                     true, false, true).join().poiData;
 | 
					 | 
				
			||||||
 
 | 
					 | 
				
			||||||
             if (ret == com.destroystokyo.paper.io.PaperFileIOThread.FAILURE_VALUE) {
 | 
					 | 
				
			||||||
@@ -0,0 +0,0 @@ public class PoiManager extends SectionStorage<PoiSection> {
 | 
					 | 
				
			||||||
             }
 | 
					 | 
				
			||||||
             return ret;
 | 
					 | 
				
			||||||
         }
 | 
					 | 
				
			||||||
-        return super.read(chunkcoordintpair);
 | 
					 | 
				
			||||||
+        return super.read(pos);
 | 
					 | 
				
			||||||
     }
 | 
					 | 
				
			||||||
 
 | 
					 | 
				
			||||||
     @Override
 | 
					 | 
				
			||||||
-    public void write(ChunkPos chunkcoordintpair, CompoundTag nbttagcompound) throws java.io.IOException {
 | 
					 | 
				
			||||||
+    public void write(ChunkPos pos, CompoundTag tag) throws java.io.IOException {
 | 
					 | 
				
			||||||
         if (this.world != null && Thread.currentThread() != com.destroystokyo.paper.io.PaperFileIOThread.Holder.INSTANCE) {
 | 
					 | 
				
			||||||
             com.destroystokyo.paper.io.PaperFileIOThread.Holder.INSTANCE.scheduleSave(
 | 
					 | 
				
			||||||
-                this.world, chunkcoordintpair.x, chunkcoordintpair.z, nbttagcompound, null,
 | 
					 | 
				
			||||||
+                this.world, pos.x, pos.z, tag, null,
 | 
					 | 
				
			||||||
                 com.destroystokyo.paper.io.IOUtil.getPriorityForCurrentThread());
 | 
					 | 
				
			||||||
             return;
 | 
					 | 
				
			||||||
         }
 | 
					 | 
				
			||||||
-        super.write(chunkcoordintpair, nbttagcompound);
 | 
					 | 
				
			||||||
+        super.write(pos, tag);
 | 
					 | 
				
			||||||
     }
 | 
					 | 
				
			||||||
     // Paper end
 | 
					 | 
				
			||||||
 
 | 
					 | 
				
			||||||
diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java
 | 
					 | 
				
			||||||
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
 | 
					 | 
				
			||||||
--- a/src/main/java/net/minecraft/world/level/Level.java
 | 
					 | 
				
			||||||
+++ b/src/main/java/net/minecraft/world/level/Level.java
 | 
					 | 
				
			||||||
@@ -0,0 +0,0 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
 | 
					 | 
				
			||||||
         return (CraftServer) Bukkit.getServer();
 | 
					 | 
				
			||||||
     }
 | 
					 | 
				
			||||||
 
 | 
					 | 
				
			||||||
+    // Paper start
 | 
					 | 
				
			||||||
+    @Override
 | 
					 | 
				
			||||||
+    public boolean hasChunk(int chunkX, int chunkZ) {
 | 
					 | 
				
			||||||
+        return ((ServerLevel)this).getChunkIfLoaded(chunkX, chunkZ) != null;
 | 
					 | 
				
			||||||
+    }
 | 
					 | 
				
			||||||
+    // Paper end
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
     public ResourceKey<DimensionType> getTypeKey() {
 | 
					 | 
				
			||||||
         return typeKey;
 | 
					 | 
				
			||||||
     }
 | 
					 | 
				
			||||||
@@ -0,0 +0,0 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
 | 
					 | 
				
			||||||
 
 | 
					 | 
				
			||||||
         for (int i1 = i; i1 < j; ++i1) {
 | 
					 | 
				
			||||||
             for (int j1 = k; j1 < l; ++j1) {
 | 
					 | 
				
			||||||
-                LevelChunk chunk = ichunkprovider.getChunkNow(i1, j1);
 | 
					 | 
				
			||||||
+                LevelChunk chunk = (LevelChunk)this.getChunkIfLoadedImmediately(i1, j1); // Paper
 | 
					 | 
				
			||||||
 
 | 
					 | 
				
			||||||
                 if (chunk != null) {
 | 
					 | 
				
			||||||
                     chunk.getEntitiesOfClass(entityClass, box, list, predicate);
 | 
					 | 
				
			||||||
diff --git a/src/main/java/org/spigotmc/ActivationRange.java b/src/main/java/org/spigotmc/ActivationRange.java
 | 
					 | 
				
			||||||
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
 | 
					 | 
				
			||||||
--- a/src/main/java/org/spigotmc/ActivationRange.java
 | 
					 | 
				
			||||||
+++ b/src/main/java/org/spigotmc/ActivationRange.java
 | 
					 | 
				
			||||||
@@ -0,0 +0,0 @@ public class ActivationRange
 | 
					 | 
				
			||||||
             {
 | 
					 | 
				
			||||||
                 for ( int j1 = k; j1 <= l; ++j1 )
 | 
					 | 
				
			||||||
                 {
 | 
					 | 
				
			||||||
-                    if ( world.getWorld().isChunkLoaded( i1, j1 ) )
 | 
					 | 
				
			||||||
+                    LevelChunk chunk = (LevelChunk) world.getChunkIfLoadedImmediately( i1, j1 );
 | 
					 | 
				
			||||||
+                    if ( chunk != null )
 | 
					 | 
				
			||||||
                     {
 | 
					 | 
				
			||||||
-                        activateChunkEntities( world.getChunk( i1, j1 ) );
 | 
					 | 
				
			||||||
+                        activateChunkEntities( chunk );
 | 
					 | 
				
			||||||
                     }
 | 
					 | 
				
			||||||
                 }
 | 
					 | 
				
			||||||
             }
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,924 +0,0 @@
 | 
				
			||||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
 | 
					 | 
				
			||||||
From: kickash32 <kickash32@gmail.com>
 | 
					 | 
				
			||||||
Date: Mon, 19 Aug 2019 01:27:58 +0500
 | 
					 | 
				
			||||||
Subject: [PATCH] implement optional per player mob spawns
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
diff --git a/src/main/java/co/aikar/timings/WorldTimingsHandler.java b/src/main/java/co/aikar/timings/WorldTimingsHandler.java
 | 
					 | 
				
			||||||
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
 | 
					 | 
				
			||||||
--- a/src/main/java/co/aikar/timings/WorldTimingsHandler.java
 | 
					 | 
				
			||||||
+++ b/src/main/java/co/aikar/timings/WorldTimingsHandler.java
 | 
					 | 
				
			||||||
@@ -0,0 +0,0 @@ public class WorldTimingsHandler {
 | 
					 | 
				
			||||||
 
 | 
					 | 
				
			||||||
 
 | 
					 | 
				
			||||||
     public final Timing miscMobSpawning;
 | 
					 | 
				
			||||||
+    public final Timing playerMobDistanceMapUpdate;
 | 
					 | 
				
			||||||
 
 | 
					 | 
				
			||||||
     public final Timing poiUnload;
 | 
					 | 
				
			||||||
     public final Timing chunkUnload;
 | 
					 | 
				
			||||||
@@ -0,0 +0,0 @@ public class WorldTimingsHandler {
 | 
					 | 
				
			||||||
 
 | 
					 | 
				
			||||||
 
 | 
					 | 
				
			||||||
         miscMobSpawning = Timings.ofSafe(name + "Mob spawning - Misc");
 | 
					 | 
				
			||||||
+        playerMobDistanceMapUpdate = Timings.ofSafe(name + "Per Player Mob Spawning - Distance Map Update");
 | 
					 | 
				
			||||||
 
 | 
					 | 
				
			||||||
         poiUnload = Timings.ofSafe(name + "Chunk unload - POI");
 | 
					 | 
				
			||||||
         chunkUnload = Timings.ofSafe(name + "Chunk unload - Chunk");
 | 
					 | 
				
			||||||
diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
 | 
					 | 
				
			||||||
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
 | 
					 | 
				
			||||||
--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
 | 
					 | 
				
			||||||
+++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
 | 
					 | 
				
			||||||
@@ -0,0 +0,0 @@ public class PaperWorldConfig {
 | 
					 | 
				
			||||||
             }
 | 
					 | 
				
			||||||
         }
 | 
					 | 
				
			||||||
     }
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+    public boolean perPlayerMobSpawns = false;
 | 
					 | 
				
			||||||
+    private void perPlayerMobSpawns() {
 | 
					 | 
				
			||||||
+        perPlayerMobSpawns = getBoolean("per-player-mob-spawns", false);
 | 
					 | 
				
			||||||
+    }
 | 
					 | 
				
			||||||
 }
 | 
					 | 
				
			||||||
diff --git a/src/main/java/com/destroystokyo/paper/util/PlayerMobDistanceMap.java b/src/main/java/com/destroystokyo/paper/util/PlayerMobDistanceMap.java
 | 
					 | 
				
			||||||
new file mode 100644
 | 
					 | 
				
			||||||
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
 | 
					 | 
				
			||||||
--- /dev/null
 | 
					 | 
				
			||||||
+++ b/src/main/java/com/destroystokyo/paper/util/PlayerMobDistanceMap.java
 | 
					 | 
				
			||||||
@@ -0,0 +0,0 @@
 | 
					 | 
				
			||||||
+package com.destroystokyo.paper.util;
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap;
 | 
					 | 
				
			||||||
+import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
 | 
					 | 
				
			||||||
+import it.unimi.dsi.fastutil.objects.ObjectLinkedOpenHashSet;
 | 
					 | 
				
			||||||
+import java.util.List;
 | 
					 | 
				
			||||||
+import java.util.Map;
 | 
					 | 
				
			||||||
+import net.minecraft.core.SectionPos;
 | 
					 | 
				
			||||||
+import net.minecraft.server.level.ServerPlayer;
 | 
					 | 
				
			||||||
+import net.minecraft.world.level.ChunkPos;
 | 
					 | 
				
			||||||
+import org.spigotmc.AsyncCatcher;
 | 
					 | 
				
			||||||
+import java.util.HashMap;
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+/** @author Spottedleaf */
 | 
					 | 
				
			||||||
+public final class PlayerMobDistanceMap {
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+    private static final PooledHashSets.PooledObjectLinkedOpenHashSet<ServerPlayer> EMPTY_SET = new PooledHashSets.PooledObjectLinkedOpenHashSet<>();
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+    private final Map<ServerPlayer, SectionPos> players = new HashMap<>();
 | 
					 | 
				
			||||||
+    // we use linked for better iteration.
 | 
					 | 
				
			||||||
+    private final Long2ObjectOpenHashMap<PooledHashSets.PooledObjectLinkedOpenHashSet<ServerPlayer>> playerMap = new Long2ObjectOpenHashMap<>(32, 0.5f);
 | 
					 | 
				
			||||||
+    private int viewDistance;
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+    private final PooledHashSets<ServerPlayer> pooledHashSets = new PooledHashSets<>();
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+    public PooledHashSets.PooledObjectLinkedOpenHashSet<ServerPlayer> getPlayersInRange(final ChunkPos chunkPos) {
 | 
					 | 
				
			||||||
+        return this.getPlayersInRange(chunkPos.x, chunkPos.z);
 | 
					 | 
				
			||||||
+    }
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+    public PooledHashSets.PooledObjectLinkedOpenHashSet<ServerPlayer> getPlayersInRange(final int chunkX, final int chunkZ) {
 | 
					 | 
				
			||||||
+        return this.playerMap.getOrDefault(ChunkPos.asLong(chunkX, chunkZ), EMPTY_SET);
 | 
					 | 
				
			||||||
+    }
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+    public void update(final List<ServerPlayer> currentPlayers, final int newViewDistance) {
 | 
					 | 
				
			||||||
+        AsyncCatcher.catchOp("Distance map update");
 | 
					 | 
				
			||||||
+        final ObjectLinkedOpenHashSet<ServerPlayer> gone = new ObjectLinkedOpenHashSet<>(this.players.keySet());
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+        final int oldViewDistance = this.viewDistance;
 | 
					 | 
				
			||||||
+        this.viewDistance = newViewDistance;
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+        for (final ServerPlayer player : currentPlayers) {
 | 
					 | 
				
			||||||
+            if (player.isSpectator() || !player.affectsSpawning) {
 | 
					 | 
				
			||||||
+                continue; // will be left in 'gone' (or not added at all)
 | 
					 | 
				
			||||||
+            }
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+            gone.remove(player);
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+            final SectionPos newPosition = player.getPlayerMapSection();
 | 
					 | 
				
			||||||
+            final SectionPos oldPosition = this.players.put(player, newPosition);
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+            if (oldPosition == null) {
 | 
					 | 
				
			||||||
+                this.addNewPlayer(player, newPosition, newViewDistance);
 | 
					 | 
				
			||||||
+            } else {
 | 
					 | 
				
			||||||
+                this.updatePlayer(player, oldPosition, newPosition, oldViewDistance, newViewDistance);
 | 
					 | 
				
			||||||
+            }
 | 
					 | 
				
			||||||
+            //this.validatePlayer(player, newViewDistance); // debug only
 | 
					 | 
				
			||||||
+        }
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+        for (final ServerPlayer player : gone) {
 | 
					 | 
				
			||||||
+            final SectionPos oldPosition = this.players.remove(player);
 | 
					 | 
				
			||||||
+            if (oldPosition != null) {
 | 
					 | 
				
			||||||
+                this.removePlayer(player, oldPosition, oldViewDistance);
 | 
					 | 
				
			||||||
+            }
 | 
					 | 
				
			||||||
+        }
 | 
					 | 
				
			||||||
+    }
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+    // expensive op, only for debug
 | 
					 | 
				
			||||||
+    private void validatePlayer(final ServerPlayer player, final int viewDistance) {
 | 
					 | 
				
			||||||
+        int entiesGot = 0;
 | 
					 | 
				
			||||||
+        int expectedEntries = (2 * viewDistance + 1);
 | 
					 | 
				
			||||||
+        expectedEntries *= expectedEntries;
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+        final SectionPos currPosition = player.getPlayerMapSection();
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+        final int centerX = currPosition.getX();
 | 
					 | 
				
			||||||
+        final int centerZ = currPosition.getZ();
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+        for (final Long2ObjectLinkedOpenHashMap.Entry<PooledHashSets.PooledObjectLinkedOpenHashSet<ServerPlayer>> entry : this.playerMap.long2ObjectEntrySet()) {
 | 
					 | 
				
			||||||
+            final long key = entry.getLongKey();
 | 
					 | 
				
			||||||
+            final PooledHashSets.PooledObjectLinkedOpenHashSet<ServerPlayer> map = entry.getValue();
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+            if (map.referenceCount == 0) {
 | 
					 | 
				
			||||||
+                throw new IllegalStateException("Invalid map");
 | 
					 | 
				
			||||||
+            }
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+            if (map.set.contains(player)) {
 | 
					 | 
				
			||||||
+                ++entiesGot;
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+                final int chunkX = ChunkPos.getX(key);
 | 
					 | 
				
			||||||
+                final int chunkZ = ChunkPos.getZ(key);
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+                final int dist = Math.max(Math.abs(chunkX - centerX), Math.abs(chunkZ - centerZ));
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+                if (dist > viewDistance) {
 | 
					 | 
				
			||||||
+                    throw new IllegalStateException("Expected view distance " + viewDistance + ", got " + dist);
 | 
					 | 
				
			||||||
+                }
 | 
					 | 
				
			||||||
+            }
 | 
					 | 
				
			||||||
+        }
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+        if (entiesGot != expectedEntries) {
 | 
					 | 
				
			||||||
+            throw new IllegalStateException("Expected " + expectedEntries + ", got " + entiesGot);
 | 
					 | 
				
			||||||
+        }
 | 
					 | 
				
			||||||
+    }
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+    private void addPlayerTo(final ServerPlayer player, final int chunkX, final int chunkZ) {
 | 
					 | 
				
			||||||
+       this.playerMap.compute(ChunkPos.asLong(chunkX, chunkZ), (final Long key, final PooledHashSets.PooledObjectLinkedOpenHashSet<ServerPlayer> players) -> {
 | 
					 | 
				
			||||||
+           if (players == null) {
 | 
					 | 
				
			||||||
+               return player.cachedSingleMobDistanceMap;
 | 
					 | 
				
			||||||
+           } else {
 | 
					 | 
				
			||||||
+               return PlayerMobDistanceMap.this.pooledHashSets.findMapWith(players, player);
 | 
					 | 
				
			||||||
+           }
 | 
					 | 
				
			||||||
+        });
 | 
					 | 
				
			||||||
+    }
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+    private void removePlayerFrom(final ServerPlayer player, final int chunkX, final int chunkZ) {
 | 
					 | 
				
			||||||
+        this.playerMap.compute(ChunkPos.asLong(chunkX, chunkZ), (final Long keyInMap, final PooledHashSets.PooledObjectLinkedOpenHashSet<ServerPlayer> players) -> {
 | 
					 | 
				
			||||||
+            return PlayerMobDistanceMap.this.pooledHashSets.findMapWithout(players, player); // rets null instead of an empty map
 | 
					 | 
				
			||||||
+        });
 | 
					 | 
				
			||||||
+    }
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+    private void updatePlayer(final ServerPlayer player, final SectionPos oldPosition, final SectionPos newPosition, final int oldViewDistance, final int newViewDistance) {
 | 
					 | 
				
			||||||
+        final int toX = newPosition.getX();
 | 
					 | 
				
			||||||
+        final int toZ = newPosition.getZ();
 | 
					 | 
				
			||||||
+        final int fromX = oldPosition.getX();
 | 
					 | 
				
			||||||
+        final int fromZ = oldPosition.getZ();
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+        final int dx = toX - fromX;
 | 
					 | 
				
			||||||
+        final int dz = toZ - fromZ;
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+        final int totalX = Math.abs(fromX - toX);
 | 
					 | 
				
			||||||
+        final int totalZ = Math.abs(fromZ - toZ);
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+        if (Math.max(totalX, totalZ) > (2 * oldViewDistance)) {
 | 
					 | 
				
			||||||
+            // teleported?
 | 
					 | 
				
			||||||
+            this.removePlayer(player, oldPosition, oldViewDistance);
 | 
					 | 
				
			||||||
+            this.addNewPlayer(player, newPosition, newViewDistance);
 | 
					 | 
				
			||||||
+            return;
 | 
					 | 
				
			||||||
+        }
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+        // x axis is width
 | 
					 | 
				
			||||||
+        // z axis is height
 | 
					 | 
				
			||||||
+        // right refers to the x axis of where we moved
 | 
					 | 
				
			||||||
+        // top refers to the z axis of where we moved
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+        if (oldViewDistance == newViewDistance) {
 | 
					 | 
				
			||||||
+            // same view distance
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+            // used for relative positioning
 | 
					 | 
				
			||||||
+            final int up = 1 | (dz >> (Integer.SIZE - 1)); // 1 if dz >= 0, -1 otherwise
 | 
					 | 
				
			||||||
+            final int right = 1 | (dx >> (Integer.SIZE - 1)); // 1 if dx >= 0, -1 otherwise
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+            // The area excluded by overlapping the two view distance squares creates four rectangles:
 | 
					 | 
				
			||||||
+            // Two on the left, and two on the right. The ones on the left we consider the "removed" section
 | 
					 | 
				
			||||||
+            // and on the right the "added" section.
 | 
					 | 
				
			||||||
+            // https://i.imgur.com/MrnOBgI.png is a reference image. Note that the outside border is not actually
 | 
					 | 
				
			||||||
+            // exclusive to the regions they surround.
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+            // 4 points of the rectangle
 | 
					 | 
				
			||||||
+            int maxX; // exclusive
 | 
					 | 
				
			||||||
+            int minX; // inclusive
 | 
					 | 
				
			||||||
+            int maxZ; // exclusive
 | 
					 | 
				
			||||||
+            int minZ; // inclusive
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+            if (dx != 0) {
 | 
					 | 
				
			||||||
+                // handle right addition
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+                maxX = toX + (oldViewDistance * right) + right; // exclusive
 | 
					 | 
				
			||||||
+                minX = fromX + (oldViewDistance * right) + right; // inclusive
 | 
					 | 
				
			||||||
+                maxZ = fromZ + (oldViewDistance * up) + up; // exclusive
 | 
					 | 
				
			||||||
+                minZ = toZ - (oldViewDistance * up); // inclusive
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+                for (int currX = minX; currX != maxX; currX += right) {
 | 
					 | 
				
			||||||
+                    for (int currZ = minZ; currZ != maxZ; currZ += up) {
 | 
					 | 
				
			||||||
+                        this.addPlayerTo(player, currX, currZ);
 | 
					 | 
				
			||||||
+                    }
 | 
					 | 
				
			||||||
+                }
 | 
					 | 
				
			||||||
+            }
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+            if (dz != 0) {
 | 
					 | 
				
			||||||
+                // handle up addition
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+                maxX = toX + (oldViewDistance * right) + right; // exclusive
 | 
					 | 
				
			||||||
+                minX = toX - (oldViewDistance * right); // inclusive
 | 
					 | 
				
			||||||
+                maxZ = toZ + (oldViewDistance * up) + up; // exclusive
 | 
					 | 
				
			||||||
+                minZ = fromZ + (oldViewDistance * up) + up; // inclusive
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+                for (int currX = minX; currX != maxX; currX += right) {
 | 
					 | 
				
			||||||
+                    for (int currZ = minZ; currZ != maxZ; currZ += up) {
 | 
					 | 
				
			||||||
+                        this.addPlayerTo(player, currX, currZ);
 | 
					 | 
				
			||||||
+                    }
 | 
					 | 
				
			||||||
+                }
 | 
					 | 
				
			||||||
+            }
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+            if (dx != 0) {
 | 
					 | 
				
			||||||
+                // handle left removal
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+                maxX = toX - (oldViewDistance * right); // exclusive
 | 
					 | 
				
			||||||
+                minX = fromX - (oldViewDistance * right); // inclusive
 | 
					 | 
				
			||||||
+                maxZ = fromZ + (oldViewDistance * up) + up; // exclusive
 | 
					 | 
				
			||||||
+                minZ = toZ - (oldViewDistance * up); // inclusive
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+                for (int currX = minX; currX != maxX; currX += right) {
 | 
					 | 
				
			||||||
+                    for (int currZ = minZ; currZ != maxZ; currZ += up) {
 | 
					 | 
				
			||||||
+                        this.removePlayerFrom(player, currX, currZ);
 | 
					 | 
				
			||||||
+                    }
 | 
					 | 
				
			||||||
+                }
 | 
					 | 
				
			||||||
+            }
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+            if (dz != 0) {
 | 
					 | 
				
			||||||
+                // handle down removal
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+                maxX = fromX + (oldViewDistance * right) + right; // exclusive
 | 
					 | 
				
			||||||
+                minX = fromX - (oldViewDistance * right); // inclusive
 | 
					 | 
				
			||||||
+                maxZ = toZ - (oldViewDistance * up); // exclusive
 | 
					 | 
				
			||||||
+                minZ = fromZ - (oldViewDistance * up); // inclusive
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+                for (int currX = minX; currX != maxX; currX += right) {
 | 
					 | 
				
			||||||
+                    for (int currZ = minZ; currZ != maxZ; currZ += up) {
 | 
					 | 
				
			||||||
+                        this.removePlayerFrom(player, currX, currZ);
 | 
					 | 
				
			||||||
+                    }
 | 
					 | 
				
			||||||
+                }
 | 
					 | 
				
			||||||
+            }
 | 
					 | 
				
			||||||
+        } else {
 | 
					 | 
				
			||||||
+            // different view distance
 | 
					 | 
				
			||||||
+            // for now :)
 | 
					 | 
				
			||||||
+            this.removePlayer(player, oldPosition, oldViewDistance);
 | 
					 | 
				
			||||||
+            this.addNewPlayer(player, newPosition, newViewDistance);
 | 
					 | 
				
			||||||
+        }
 | 
					 | 
				
			||||||
+    }
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+    private void removePlayer(final ServerPlayer player, final SectionPos position, final int viewDistance) {
 | 
					 | 
				
			||||||
+        final int x = position.getX();
 | 
					 | 
				
			||||||
+        final int z = position.getZ();
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+        for (int xoff = -viewDistance; xoff <= viewDistance; ++xoff) {
 | 
					 | 
				
			||||||
+            for (int zoff = -viewDistance; zoff <= viewDistance; ++zoff) {
 | 
					 | 
				
			||||||
+                this.removePlayerFrom(player, x + xoff, z + zoff);
 | 
					 | 
				
			||||||
+            }
 | 
					 | 
				
			||||||
+        }
 | 
					 | 
				
			||||||
+    }
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+    private void addNewPlayer(final ServerPlayer player, final SectionPos position, final int viewDistance) {
 | 
					 | 
				
			||||||
+        final int x = position.getX();
 | 
					 | 
				
			||||||
+        final int z = position.getZ();
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+        for (int xoff = -viewDistance; xoff <= viewDistance; ++xoff) {
 | 
					 | 
				
			||||||
+            for (int zoff = -viewDistance; zoff <= viewDistance; ++zoff) {
 | 
					 | 
				
			||||||
+                this.addPlayerTo(player, x + xoff, z + zoff);
 | 
					 | 
				
			||||||
+            }
 | 
					 | 
				
			||||||
+        }
 | 
					 | 
				
			||||||
+    }
 | 
					 | 
				
			||||||
+}
 | 
					 | 
				
			||||||
diff --git a/src/main/java/com/destroystokyo/paper/util/PooledHashSets.java b/src/main/java/com/destroystokyo/paper/util/PooledHashSets.java
 | 
					 | 
				
			||||||
new file mode 100644
 | 
					 | 
				
			||||||
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
 | 
					 | 
				
			||||||
--- /dev/null
 | 
					 | 
				
			||||||
+++ b/src/main/java/com/destroystokyo/paper/util/PooledHashSets.java
 | 
					 | 
				
			||||||
@@ -0,0 +0,0 @@
 | 
					 | 
				
			||||||
+package com.destroystokyo.paper.util;
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
 | 
					 | 
				
			||||||
+import it.unimi.dsi.fastutil.objects.ObjectLinkedOpenHashSet;
 | 
					 | 
				
			||||||
+import java.lang.ref.WeakReference;
 | 
					 | 
				
			||||||
+import java.util.Iterator;
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+/** @author Spottedleaf */
 | 
					 | 
				
			||||||
+public class PooledHashSets<E> {
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+    // we really want to avoid that equals() check as much as possible...
 | 
					 | 
				
			||||||
+    protected final Object2ObjectOpenHashMap<PooledObjectLinkedOpenHashSet<E>, PooledObjectLinkedOpenHashSet<E>> mapPool = new Object2ObjectOpenHashMap<>(64, 0.25f);
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+    protected void decrementReferenceCount(final PooledObjectLinkedOpenHashSet<E> current) {
 | 
					 | 
				
			||||||
+        if (current.referenceCount == 0) {
 | 
					 | 
				
			||||||
+            throw new IllegalStateException("Cannot decrement reference count for " + current);
 | 
					 | 
				
			||||||
+        }
 | 
					 | 
				
			||||||
+        if (current.referenceCount == -1 || --current.referenceCount > 0) {
 | 
					 | 
				
			||||||
+            return;
 | 
					 | 
				
			||||||
+        }
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+        this.mapPool.remove(current);
 | 
					 | 
				
			||||||
+        return;
 | 
					 | 
				
			||||||
+    }
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+    public PooledObjectLinkedOpenHashSet<E> findMapWith(final PooledObjectLinkedOpenHashSet<E> current, final E object) {
 | 
					 | 
				
			||||||
+        final PooledObjectLinkedOpenHashSet<E> cached = current.getAddCache(object);
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+        if (cached != null) {
 | 
					 | 
				
			||||||
+            if (cached.referenceCount != -1) {
 | 
					 | 
				
			||||||
+                ++cached.referenceCount;
 | 
					 | 
				
			||||||
+            }
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+            decrementReferenceCount(current);
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+            return cached;
 | 
					 | 
				
			||||||
+        }
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+        if (!current.add(object)) {
 | 
					 | 
				
			||||||
+            return current;
 | 
					 | 
				
			||||||
+        }
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+        // we use get/put since we use a different key on put
 | 
					 | 
				
			||||||
+        PooledObjectLinkedOpenHashSet<E> ret = this.mapPool.get(current);
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+        if (ret == null) {
 | 
					 | 
				
			||||||
+            ret = new PooledObjectLinkedOpenHashSet<>(current);
 | 
					 | 
				
			||||||
+            current.remove(object);
 | 
					 | 
				
			||||||
+            this.mapPool.put(ret, ret);
 | 
					 | 
				
			||||||
+            ret.referenceCount = 1;
 | 
					 | 
				
			||||||
+        } else {
 | 
					 | 
				
			||||||
+            if (ret.referenceCount != -1) {
 | 
					 | 
				
			||||||
+                ++ret.referenceCount;
 | 
					 | 
				
			||||||
+            }
 | 
					 | 
				
			||||||
+            current.remove(object);
 | 
					 | 
				
			||||||
+        }
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+        current.updateAddCache(object, ret);
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+        decrementReferenceCount(current);
 | 
					 | 
				
			||||||
+        return ret;
 | 
					 | 
				
			||||||
+    }
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+    // rets null if current.size() == 1
 | 
					 | 
				
			||||||
+    public PooledObjectLinkedOpenHashSet<E> findMapWithout(final PooledObjectLinkedOpenHashSet<E> current, final E object) {
 | 
					 | 
				
			||||||
+        if (current.set.size() == 1) {
 | 
					 | 
				
			||||||
+            decrementReferenceCount(current);
 | 
					 | 
				
			||||||
+            return null;
 | 
					 | 
				
			||||||
+        }
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+        final PooledObjectLinkedOpenHashSet<E> cached = current.getRemoveCache(object);
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+        if (cached != null) {
 | 
					 | 
				
			||||||
+            if (cached.referenceCount != -1) {
 | 
					 | 
				
			||||||
+                ++cached.referenceCount;
 | 
					 | 
				
			||||||
+            }
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+            decrementReferenceCount(current);
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+            return cached;
 | 
					 | 
				
			||||||
+        }
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+        if (!current.remove(object)) {
 | 
					 | 
				
			||||||
+            return current;
 | 
					 | 
				
			||||||
+        }
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+        // we use get/put since we use a different key on put
 | 
					 | 
				
			||||||
+        PooledObjectLinkedOpenHashSet<E> ret = this.mapPool.get(current);
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+        if (ret == null) {
 | 
					 | 
				
			||||||
+            ret = new PooledObjectLinkedOpenHashSet<>(current);
 | 
					 | 
				
			||||||
+            current.add(object);
 | 
					 | 
				
			||||||
+            this.mapPool.put(ret, ret);
 | 
					 | 
				
			||||||
+            ret.referenceCount = 1;
 | 
					 | 
				
			||||||
+        } else {
 | 
					 | 
				
			||||||
+            if (ret.referenceCount != -1) {
 | 
					 | 
				
			||||||
+                ++ret.referenceCount;
 | 
					 | 
				
			||||||
+            }
 | 
					 | 
				
			||||||
+            current.add(object);
 | 
					 | 
				
			||||||
+        }
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+        current.updateRemoveCache(object, ret);
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+        decrementReferenceCount(current);
 | 
					 | 
				
			||||||
+        return ret;
 | 
					 | 
				
			||||||
+    }
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+    public static final class PooledObjectLinkedOpenHashSet<E> implements Iterable<E> {
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+        private static final WeakReference NULL_REFERENCE = new WeakReference(null);
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+        final ObjectLinkedOpenHashSet<E> set;
 | 
					 | 
				
			||||||
+        int referenceCount; // -1 if special
 | 
					 | 
				
			||||||
+        int hash; // optimize hashcode
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+        // add cache
 | 
					 | 
				
			||||||
+        WeakReference<E> lastAddObject = NULL_REFERENCE;
 | 
					 | 
				
			||||||
+        WeakReference<PooledObjectLinkedOpenHashSet<E>> lastAddMap = NULL_REFERENCE;
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+        // remove cache
 | 
					 | 
				
			||||||
+        WeakReference<E> lastRemoveObject = NULL_REFERENCE;
 | 
					 | 
				
			||||||
+        WeakReference<PooledObjectLinkedOpenHashSet<E>> lastRemoveMap = NULL_REFERENCE;
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+        public PooledObjectLinkedOpenHashSet() {
 | 
					 | 
				
			||||||
+            this.set = new ObjectLinkedOpenHashSet<>(2, 0.6f);
 | 
					 | 
				
			||||||
+        }
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+        public PooledObjectLinkedOpenHashSet(final E single) {
 | 
					 | 
				
			||||||
+            this();
 | 
					 | 
				
			||||||
+            this.referenceCount = -1;
 | 
					 | 
				
			||||||
+            this.add(single);
 | 
					 | 
				
			||||||
+        }
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+        public PooledObjectLinkedOpenHashSet(final PooledObjectLinkedOpenHashSet<E> other) {
 | 
					 | 
				
			||||||
+            this.set = other.set.clone();
 | 
					 | 
				
			||||||
+            this.hash = other.hash;
 | 
					 | 
				
			||||||
+        }
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+        // from https://github.com/Spottedleaf/ConcurrentUtil/blob/master/src/main/java/ca/spottedleaf/concurrentutil/util/IntegerUtil.java
 | 
					 | 
				
			||||||
+        // generated by https://github.com/skeeto/hash-prospector
 | 
					 | 
				
			||||||
+        static int hash0(int x) {
 | 
					 | 
				
			||||||
+            x *= 0x36935555;
 | 
					 | 
				
			||||||
+            x ^= x >>> 16;
 | 
					 | 
				
			||||||
+            return x;
 | 
					 | 
				
			||||||
+        }
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+        public PooledObjectLinkedOpenHashSet<E> getAddCache(final E element) {
 | 
					 | 
				
			||||||
+            final E currentAdd = this.lastAddObject.get();
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+            if (currentAdd == null || !(currentAdd == element || currentAdd.equals(element))) {
 | 
					 | 
				
			||||||
+                return null;
 | 
					 | 
				
			||||||
+            }
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+            final PooledObjectLinkedOpenHashSet<E> map = this.lastAddMap.get();
 | 
					 | 
				
			||||||
+            if (map == null || map.referenceCount == 0) {
 | 
					 | 
				
			||||||
+                // we need to ret null if ref count is zero as calling code will assume the map is in use
 | 
					 | 
				
			||||||
+                return null;
 | 
					 | 
				
			||||||
+            }
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+            return map;
 | 
					 | 
				
			||||||
+        }
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+        public PooledObjectLinkedOpenHashSet<E> getRemoveCache(final E element) {
 | 
					 | 
				
			||||||
+            final E currentRemove = this.lastRemoveObject.get();
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+            if (currentRemove == null || !(currentRemove == element || currentRemove.equals(element))) {
 | 
					 | 
				
			||||||
+                return null;
 | 
					 | 
				
			||||||
+            }
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+            final PooledObjectLinkedOpenHashSet<E> map = this.lastRemoveMap.get();
 | 
					 | 
				
			||||||
+            if (map == null || map.referenceCount == 0) {
 | 
					 | 
				
			||||||
+                // we need to ret null if ref count is zero as calling code will assume the map is in use
 | 
					 | 
				
			||||||
+                return null;
 | 
					 | 
				
			||||||
+            }
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+            return map;
 | 
					 | 
				
			||||||
+        }
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+        public void updateAddCache(final E element, final PooledObjectLinkedOpenHashSet<E> map) {
 | 
					 | 
				
			||||||
+            this.lastAddObject = new WeakReference<>(element);
 | 
					 | 
				
			||||||
+            this.lastAddMap = new WeakReference<>(map);
 | 
					 | 
				
			||||||
+        }
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+        public void updateRemoveCache(final E element, final PooledObjectLinkedOpenHashSet<E> map) {
 | 
					 | 
				
			||||||
+            this.lastRemoveObject = new WeakReference<>(element);
 | 
					 | 
				
			||||||
+            this.lastRemoveMap = new WeakReference<>(map);
 | 
					 | 
				
			||||||
+        }
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+        boolean add(final E element) {
 | 
					 | 
				
			||||||
+            boolean added =  this.set.add(element);
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+            if (added) {
 | 
					 | 
				
			||||||
+                this.hash += hash0(element.hashCode());
 | 
					 | 
				
			||||||
+            }
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+            return added;
 | 
					 | 
				
			||||||
+        }
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+        boolean remove(Object element) {
 | 
					 | 
				
			||||||
+            boolean removed = this.set.remove(element);
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+            if (removed) {
 | 
					 | 
				
			||||||
+                this.hash -= hash0(element.hashCode());
 | 
					 | 
				
			||||||
+            }
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+            return removed;
 | 
					 | 
				
			||||||
+        }
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+        @Override
 | 
					 | 
				
			||||||
+        public Iterator<E> iterator() {
 | 
					 | 
				
			||||||
+            return this.set.iterator();
 | 
					 | 
				
			||||||
+        }
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+        @Override
 | 
					 | 
				
			||||||
+        public int hashCode() {
 | 
					 | 
				
			||||||
+            return this.hash;
 | 
					 | 
				
			||||||
+        }
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+        @Override
 | 
					 | 
				
			||||||
+        public boolean equals(final Object other) {
 | 
					 | 
				
			||||||
+            if (!(other instanceof PooledObjectLinkedOpenHashSet)) {
 | 
					 | 
				
			||||||
+                return false;
 | 
					 | 
				
			||||||
+            }
 | 
					 | 
				
			||||||
+            if (this.referenceCount == 0) {
 | 
					 | 
				
			||||||
+                return other == this;
 | 
					 | 
				
			||||||
+            } else {
 | 
					 | 
				
			||||||
+                if (other == this) {
 | 
					 | 
				
			||||||
+                    // Unfortunately we are never equal to our own instance while in use!
 | 
					 | 
				
			||||||
+                    return false;
 | 
					 | 
				
			||||||
+                }
 | 
					 | 
				
			||||||
+                return this.hash == ((PooledObjectLinkedOpenHashSet)other).hash && this.set.equals(((PooledObjectLinkedOpenHashSet)other).set);
 | 
					 | 
				
			||||||
+            }
 | 
					 | 
				
			||||||
+        }
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+        @Override
 | 
					 | 
				
			||||||
+        public String toString() {
 | 
					 | 
				
			||||||
+            return "PooledHashSet: size: " + this.set.size() + ", reference count: " + this.referenceCount + ", hash: " +
 | 
					 | 
				
			||||||
+                this.hashCode() + ", identity: " + System.identityHashCode(this) + " map: " + this.set.toString();
 | 
					 | 
				
			||||||
+        }
 | 
					 | 
				
			||||||
+    }
 | 
					 | 
				
			||||||
+}
 | 
					 | 
				
			||||||
diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java
 | 
					 | 
				
			||||||
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
 | 
					 | 
				
			||||||
--- a/src/main/java/net/minecraft/server/level/ChunkMap.java
 | 
					 | 
				
			||||||
+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java
 | 
					 | 
				
			||||||
@@ -0,0 +0,0 @@ import net.minecraft.util.thread.ProcessorMailbox;
 | 
					 | 
				
			||||||
 import net.minecraft.world.entity.Entity;
 | 
					 | 
				
			||||||
 import net.minecraft.world.entity.EntityType;
 | 
					 | 
				
			||||||
 import net.minecraft.world.entity.Mob;
 | 
					 | 
				
			||||||
+import net.minecraft.world.entity.MobCategory;
 | 
					 | 
				
			||||||
 import net.minecraft.world.entity.ai.village.poi.PoiManager;
 | 
					 | 
				
			||||||
 import net.minecraft.world.entity.boss.EnderDragonPart;
 | 
					 | 
				
			||||||
 import net.minecraft.world.level.ChunkPos;
 | 
					 | 
				
			||||||
@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
 | 
					 | 
				
			||||||
     public final Int2ObjectMap<ChunkMap.TrackedEntity> entityMap;
 | 
					 | 
				
			||||||
     private final Long2ByteMap chunkTypeCache;
 | 
					 | 
				
			||||||
     private final Queue<Runnable> unloadQueue; private final Queue<Runnable> getUnloadQueueTasks() { return this.unloadQueue; } // Paper - OBFHELPER
 | 
					 | 
				
			||||||
-    private int viewDistance;
 | 
					 | 
				
			||||||
+    int viewDistance; // Paper - private -> package private
 | 
					 | 
				
			||||||
+    public final com.destroystokyo.paper.util.PlayerMobDistanceMap playerMobDistanceMap; // Paper
 | 
					 | 
				
			||||||
 
 | 
					 | 
				
			||||||
     // CraftBukkit start - recursion-safe executor for Chunk loadCallback() and unloadCallback()
 | 
					 | 
				
			||||||
     public final CallbackExecutor callbackExecutor = new CallbackExecutor();
 | 
					 | 
				
			||||||
@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
 | 
					 | 
				
			||||||
         this.overworldDataStorage = supplier;
 | 
					 | 
				
			||||||
         this.poiManager = new PoiManager(new File(this.storageFolder, "poi"), dataFixer, flag, this.level); // Paper
 | 
					 | 
				
			||||||
         this.setViewDistance(i);
 | 
					 | 
				
			||||||
+        this.playerMobDistanceMap = this.level.paperConfig.perPlayerMobSpawns ? new com.destroystokyo.paper.util.PlayerMobDistanceMap() : null; // Paper
 | 
					 | 
				
			||||||
+    }
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+    public void updatePlayerMobTypeMap(Entity entity) {
 | 
					 | 
				
			||||||
+        if (!this.level.paperConfig.perPlayerMobSpawns) {
 | 
					 | 
				
			||||||
+            return;
 | 
					 | 
				
			||||||
+        }
 | 
					 | 
				
			||||||
+        int chunkX = (int)Math.floor(entity.getX()) >> 4;
 | 
					 | 
				
			||||||
+        int chunkZ = (int)Math.floor(entity.getZ()) >> 4;
 | 
					 | 
				
			||||||
+        int index = entity.getType().getEnumCreatureType().ordinal();
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+        for (ServerPlayer player : this.playerMobDistanceMap.getPlayersInRange(chunkX, chunkZ)) {
 | 
					 | 
				
			||||||
+            ++player.mobCounts[index];
 | 
					 | 
				
			||||||
+        }
 | 
					 | 
				
			||||||
+    }
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+    public int getMobCountNear(ServerPlayer entityPlayer, MobCategory enumCreatureType) {
 | 
					 | 
				
			||||||
+        return entityPlayer.mobCounts[enumCreatureType.ordinal()];
 | 
					 | 
				
			||||||
     }
 | 
					 | 
				
			||||||
 
 | 
					 | 
				
			||||||
     private static double euclideanDistanceSquared(ChunkPos pos, Entity entity) {
 | 
					 | 
				
			||||||
diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java
 | 
					 | 
				
			||||||
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
 | 
					 | 
				
			||||||
--- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java
 | 
					 | 
				
			||||||
+++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java
 | 
					 | 
				
			||||||
@@ -0,0 +0,0 @@ public class ServerChunkCache extends ChunkSource {
 | 
					 | 
				
			||||||
             this.level.getProfiler().push("naturalSpawnCount");
 | 
					 | 
				
			||||||
             this.level.timings.countNaturalMobs.startTiming(); // Paper - timings
 | 
					 | 
				
			||||||
             int l = this.distanceManager.getNaturalSpawnChunkCount();
 | 
					 | 
				
			||||||
-            NaturalSpawner.SpawnState spawnercreature_d = NaturalSpawner.createState(l, this.level.getAllEntities(), this::getFullChunk);
 | 
					 | 
				
			||||||
+            // Paper start - per player mob spawning
 | 
					 | 
				
			||||||
+            NaturalSpawner.SpawnState spawnercreature_d; // moved down
 | 
					 | 
				
			||||||
+            if (this.chunkMap.playerMobDistanceMap != null) {
 | 
					 | 
				
			||||||
+                // update distance map
 | 
					 | 
				
			||||||
+                this.level.timings.playerMobDistanceMapUpdate.startTiming();
 | 
					 | 
				
			||||||
+                this.chunkMap.playerMobDistanceMap.update(this.level.players, this.chunkMap.viewDistance);
 | 
					 | 
				
			||||||
+                this.level.timings.playerMobDistanceMapUpdate.stopTiming();
 | 
					 | 
				
			||||||
+                // re-set mob counts
 | 
					 | 
				
			||||||
+                for (ServerPlayer player : this.level.players) {
 | 
					 | 
				
			||||||
+                    Arrays.fill(player.mobCounts, 0);
 | 
					 | 
				
			||||||
+                }
 | 
					 | 
				
			||||||
+                spawnercreature_d = NaturalSpawner.countMobs(l, this.level.getAllEntities(), this::getFullChunk, true);
 | 
					 | 
				
			||||||
+            } else {
 | 
					 | 
				
			||||||
+                spawnercreature_d = NaturalSpawner.countMobs(l, this.level.getAllEntities(), this::getFullChunk, false);
 | 
					 | 
				
			||||||
+            }
 | 
					 | 
				
			||||||
+            // Paper end
 | 
					 | 
				
			||||||
             this.level.timings.countNaturalMobs.stopTiming(); // Paper - timings
 | 
					 | 
				
			||||||
 
 | 
					 | 
				
			||||||
             this.lastSpawnState = spawnercreature_d;
 | 
					 | 
				
			||||||
diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java
 | 
					 | 
				
			||||||
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
 | 
					 | 
				
			||||||
--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java
 | 
					 | 
				
			||||||
+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java
 | 
					 | 
				
			||||||
@@ -0,0 +0,0 @@ import net.minecraft.world.entity.Entity;
 | 
					 | 
				
			||||||
 import net.minecraft.world.entity.HumanoidArm;
 | 
					 | 
				
			||||||
 import net.minecraft.world.entity.LivingEntity;
 | 
					 | 
				
			||||||
 import net.minecraft.world.entity.Mob;
 | 
					 | 
				
			||||||
+import net.minecraft.world.entity.MobCategory;
 | 
					 | 
				
			||||||
 import net.minecraft.world.entity.NeutralMob;
 | 
					 | 
				
			||||||
 import net.minecraft.world.entity.animal.horse.AbstractHorse;
 | 
					 | 
				
			||||||
 import net.minecraft.world.entity.item.ItemEntity;
 | 
					 | 
				
			||||||
@@ -0,0 +0,0 @@ public class ServerPlayer extends Player implements ContainerListener {
 | 
					 | 
				
			||||||
     public boolean queueHealthUpdatePacket = false;
 | 
					 | 
				
			||||||
     public net.minecraft.network.protocol.game.ClientboundSetHealthPacket queuedHealthUpdatePacket;
 | 
					 | 
				
			||||||
     // Paper end
 | 
					 | 
				
			||||||
+    // Paper start - mob spawning rework
 | 
					 | 
				
			||||||
+    public static final int ENUMCREATURETYPE_TOTAL_ENUMS = MobCategory.values().length;
 | 
					 | 
				
			||||||
+    public final int[] mobCounts = new int[ENUMCREATURETYPE_TOTAL_ENUMS]; // Paper
 | 
					 | 
				
			||||||
+    public final com.destroystokyo.paper.util.PooledHashSets.PooledObjectLinkedOpenHashSet<ServerPlayer> cachedSingleMobDistanceMap;
 | 
					 | 
				
			||||||
+    // Paper end
 | 
					 | 
				
			||||||
 
 | 
					 | 
				
			||||||
     // CraftBukkit start
 | 
					 | 
				
			||||||
     public String displayName;
 | 
					 | 
				
			||||||
@@ -0,0 +0,0 @@ public class ServerPlayer extends Player implements ContainerListener {
 | 
					 | 
				
			||||||
         this.adventure$displayName = net.kyori.adventure.text.Component.text(this.getScoreboardName()); // Paper
 | 
					 | 
				
			||||||
         this.canPickUpLoot = true;
 | 
					 | 
				
			||||||
         this.maxHealthCache = this.getMaxHealth();
 | 
					 | 
				
			||||||
+        this.cachedSingleMobDistanceMap = new com.destroystokyo.paper.util.PooledHashSets.PooledObjectLinkedOpenHashSet<>(this); // Paper
 | 
					 | 
				
			||||||
     }
 | 
					 | 
				
			||||||
 
 | 
					 | 
				
			||||||
     // Yes, this doesn't match Vanilla, but it's the best we can do for now.
 | 
					 | 
				
			||||||
@@ -0,0 +0,0 @@ public class ServerPlayer extends Player implements ContainerListener {
 | 
					 | 
				
			||||||
 
 | 
					 | 
				
			||||||
     }
 | 
					 | 
				
			||||||
 
 | 
					 | 
				
			||||||
+    public final SectionPos getPlayerMapSection() { return this.getLastSectionPos(); } // Paper - OBFHELPER
 | 
					 | 
				
			||||||
     public SectionPos getLastSectionPos() {
 | 
					 | 
				
			||||||
         return this.lastSectionPos;
 | 
					 | 
				
			||||||
     }
 | 
					 | 
				
			||||||
diff --git a/src/main/java/net/minecraft/world/entity/EntityType.java b/src/main/java/net/minecraft/world/entity/EntityType.java
 | 
					 | 
				
			||||||
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
 | 
					 | 
				
			||||||
--- a/src/main/java/net/minecraft/world/entity/EntityType.java
 | 
					 | 
				
			||||||
+++ b/src/main/java/net/minecraft/world/entity/EntityType.java
 | 
					 | 
				
			||||||
@@ -0,0 +0,0 @@ public class EntityType<T extends Entity> {
 | 
					 | 
				
			||||||
         return this.canSpawnFarFromPlayer;
 | 
					 | 
				
			||||||
     }
 | 
					 | 
				
			||||||
 
 | 
					 | 
				
			||||||
+    public final MobCategory getEnumCreatureType() { return this.getCategory(); } // Paper - OBFHELPER
 | 
					 | 
				
			||||||
     public MobCategory getCategory() {
 | 
					 | 
				
			||||||
         return this.category;
 | 
					 | 
				
			||||||
     }
 | 
					 | 
				
			||||||
diff --git a/src/main/java/net/minecraft/world/level/NaturalSpawner.java b/src/main/java/net/minecraft/world/level/NaturalSpawner.java
 | 
					 | 
				
			||||||
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
 | 
					 | 
				
			||||||
--- a/src/main/java/net/minecraft/world/level/NaturalSpawner.java
 | 
					 | 
				
			||||||
+++ b/src/main/java/net/minecraft/world/level/NaturalSpawner.java
 | 
					 | 
				
			||||||
@@ -0,0 +0,0 @@ import net.minecraft.core.Registry;
 | 
					 | 
				
			||||||
 import net.minecraft.nbt.CompoundTag;
 | 
					 | 
				
			||||||
 import net.minecraft.server.MCUtil;
 | 
					 | 
				
			||||||
 import net.minecraft.server.level.ServerLevel;
 | 
					 | 
				
			||||||
+import net.minecraft.server.level.ServerPlayer;
 | 
					 | 
				
			||||||
 import net.minecraft.tags.BlockTags;
 | 
					 | 
				
			||||||
 import net.minecraft.tags.FluidTags;
 | 
					 | 
				
			||||||
 import net.minecraft.tags.Tag;
 | 
					 | 
				
			||||||
@@ -0,0 +0,0 @@ public final class NaturalSpawner {
 | 
					 | 
				
			||||||
     });
 | 
					 | 
				
			||||||
 
 | 
					 | 
				
			||||||
     public static NaturalSpawner.SpawnState createState(int spawningChunkCount, Iterable<Entity> entities, NaturalSpawner.ChunkGetter chunkSource) {
 | 
					 | 
				
			||||||
+        // Paper start - add countMobs parameter
 | 
					 | 
				
			||||||
+        return countMobs(spawningChunkCount, entities, chunkSource, false);
 | 
					 | 
				
			||||||
+    }
 | 
					 | 
				
			||||||
+    public static NaturalSpawner.SpawnState countMobs(int i, Iterable<Entity> iterable, NaturalSpawner.ChunkGetter spawnercreature_b, boolean countMobs) {
 | 
					 | 
				
			||||||
+        // Paper end - add countMobs parameter
 | 
					 | 
				
			||||||
         PotentialCalculator spawnercreatureprobabilities = new PotentialCalculator();
 | 
					 | 
				
			||||||
         Object2IntOpenHashMap<MobCategory> object2intopenhashmap = new Object2IntOpenHashMap();
 | 
					 | 
				
			||||||
-        Iterator iterator = entities.iterator();
 | 
					 | 
				
			||||||
+        Iterator iterator = iterable.iterator();
 | 
					 | 
				
			||||||
 
 | 
					 | 
				
			||||||
         while (iterator.hasNext()) {
 | 
					 | 
				
			||||||
             Entity entity = (Entity) iterator.next();
 | 
					 | 
				
			||||||
@@ -0,0 +0,0 @@ public final class NaturalSpawner {
 | 
					 | 
				
			||||||
                 BlockPos blockposition = entity.blockPosition();
 | 
					 | 
				
			||||||
                 long j = ChunkPos.asLong(blockposition.getX() >> 4, blockposition.getZ() >> 4);
 | 
					 | 
				
			||||||
 
 | 
					 | 
				
			||||||
-                chunkSource.query(j, (chunk) -> {
 | 
					 | 
				
			||||||
+                spawnercreature_b.query(j, (chunk) -> {
 | 
					 | 
				
			||||||
                     MobSpawnSettings.MobSpawnCost biomesettingsmobs_b = getRoughBiome(blockposition, chunk).getMobSettings().getMobSpawnCost(entity.getType());
 | 
					 | 
				
			||||||
 
 | 
					 | 
				
			||||||
                     if (biomesettingsmobs_b != null) {
 | 
					 | 
				
			||||||
@@ -0,0 +0,0 @@ public final class NaturalSpawner {
 | 
					 | 
				
			||||||
                     }
 | 
					 | 
				
			||||||
 
 | 
					 | 
				
			||||||
                     object2intopenhashmap.addTo(enumcreaturetype, 1);
 | 
					 | 
				
			||||||
+                    // Paper start
 | 
					 | 
				
			||||||
+                    if (countMobs) {
 | 
					 | 
				
			||||||
+                        ((ServerLevel)chunk.world).getChunkSource().chunkMap.updatePlayerMobTypeMap(entity);
 | 
					 | 
				
			||||||
+                    }
 | 
					 | 
				
			||||||
+                    // Paper end
 | 
					 | 
				
			||||||
                 });
 | 
					 | 
				
			||||||
             }
 | 
					 | 
				
			||||||
         }
 | 
					 | 
				
			||||||
 
 | 
					 | 
				
			||||||
-        return new NaturalSpawner.SpawnState(spawningChunkCount, object2intopenhashmap, spawnercreatureprobabilities);
 | 
					 | 
				
			||||||
+        return new NaturalSpawner.SpawnState(i, object2intopenhashmap, spawnercreatureprobabilities);
 | 
					 | 
				
			||||||
     }
 | 
					 | 
				
			||||||
 
 | 
					 | 
				
			||||||
     private static Biome getRoughBiome(BlockPos pos, ChunkAccess chunk) {
 | 
					 | 
				
			||||||
@@ -0,0 +0,0 @@ public final class NaturalSpawner {
 | 
					 | 
				
			||||||
                 continue;
 | 
					 | 
				
			||||||
             }
 | 
					 | 
				
			||||||
 
 | 
					 | 
				
			||||||
-            if ((spawnAnimals || !enumcreaturetype.isFriendly()) && (spawnMonsters || enumcreaturetype.isFriendly()) && (shouldSpawnAnimals || !enumcreaturetype.isPersistent()) && info.a(enumcreaturetype, limit)) {
 | 
					 | 
				
			||||||
+            // Paper start - only allow spawns upto the limit per chunk and update count afterwards
 | 
					 | 
				
			||||||
+            int currEntityCount = info.getEntityCountsByType().getInt(enumcreaturetype);
 | 
					 | 
				
			||||||
+            int k1 = limit * info.getSpawnerChunks() / NaturalSpawner.MAGIC_NUMBER;
 | 
					 | 
				
			||||||
+            int difference = k1 - currEntityCount;
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+            if (world.paperConfig.perPlayerMobSpawns) {
 | 
					 | 
				
			||||||
+                int minDiff = Integer.MAX_VALUE;
 | 
					 | 
				
			||||||
+                for (ServerPlayer entityplayer : world.getChunkSource().chunkMap.playerMobDistanceMap.getPlayersInRange(chunk.getPos())) {
 | 
					 | 
				
			||||||
+                    minDiff = Math.min(limit - world.getChunkSource().chunkMap.getMobCountNear(entityplayer, enumcreaturetype), minDiff);
 | 
					 | 
				
			||||||
+                }
 | 
					 | 
				
			||||||
+                difference = (minDiff == Integer.MAX_VALUE) ? 0 : minDiff;
 | 
					 | 
				
			||||||
+            }
 | 
					 | 
				
			||||||
+            // Paper end
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+            // Paper start - per player mob spawning
 | 
					 | 
				
			||||||
+            if ((spawnAnimals || !enumcreaturetype.isFriendly()) && (spawnMonsters || enumcreaturetype.isFriendly()) && (shouldSpawnAnimals || !enumcreaturetype.isPersistent()) && difference > 0) {
 | 
					 | 
				
			||||||
                 // CraftBukkit end
 | 
					 | 
				
			||||||
-                spawnCategoryForChunk(enumcreaturetype, world, chunk, (entitytypes, blockposition, ichunkaccess) -> {
 | 
					 | 
				
			||||||
+                int spawnCount = spawnMobs(enumcreaturetype, world, chunk, (entitytypes, blockposition, ichunkaccess) -> {
 | 
					 | 
				
			||||||
                     return info.canSpawn(entitytypes, blockposition, ichunkaccess);
 | 
					 | 
				
			||||||
                 }, (entityinsentient, ichunkaccess) -> {
 | 
					 | 
				
			||||||
                     info.afterSpawn(entityinsentient, ichunkaccess);
 | 
					 | 
				
			||||||
-                });
 | 
					 | 
				
			||||||
+                },
 | 
					 | 
				
			||||||
+                difference, world.paperConfig.perPlayerMobSpawns ? world.getChunkSource().chunkMap::updatePlayerMobTypeMap : null);
 | 
					 | 
				
			||||||
+                info.getEntityCountsByType().mergeInt(enumcreaturetype, spawnCount, Integer::sum);
 | 
					 | 
				
			||||||
+                // Paper end - per player mob spawning
 | 
					 | 
				
			||||||
             }
 | 
					 | 
				
			||||||
         }
 | 
					 | 
				
			||||||
 
 | 
					 | 
				
			||||||
@@ -0,0 +0,0 @@ public final class NaturalSpawner {
 | 
					 | 
				
			||||||
     }
 | 
					 | 
				
			||||||
 
 | 
					 | 
				
			||||||
     public static void spawnCategoryForChunk(MobCategory group, ServerLevel world, LevelChunk chunk, NaturalSpawner.SpawnPredicate checker, NaturalSpawner.AfterSpawnCallback runner) {
 | 
					 | 
				
			||||||
-        BlockPos blockposition = getRandomPosWithin(world, chunk);
 | 
					 | 
				
			||||||
+        // Paper start - add parameters and int ret type
 | 
					 | 
				
			||||||
+        spawnMobs(group, world, chunk, checker, runner, Integer.MAX_VALUE, null);
 | 
					 | 
				
			||||||
+    }
 | 
					 | 
				
			||||||
+    public static int spawnMobs(MobCategory enumcreaturetype, ServerLevel worldserver, LevelChunk chunk, NaturalSpawner.SpawnPredicate spawnercreature_c, NaturalSpawner.AfterSpawnCallback spawnercreature_a, int maxSpawns, Consumer<Entity> trackEntity) {
 | 
					 | 
				
			||||||
+        // Paper end - add parameters and int ret type
 | 
					 | 
				
			||||||
+        BlockPos blockposition = getRandomPosWithin(worldserver, chunk);
 | 
					 | 
				
			||||||
 
 | 
					 | 
				
			||||||
         if (blockposition.getY() >= 1) {
 | 
					 | 
				
			||||||
-            spawnCategoryForPosition(group, world, (ChunkAccess) chunk, blockposition, checker, runner);
 | 
					 | 
				
			||||||
+            return spawnMobsInternal(enumcreaturetype, worldserver, (ChunkAccess) chunk, blockposition, spawnercreature_c, spawnercreature_a, maxSpawns, trackEntity);
 | 
					 | 
				
			||||||
         }
 | 
					 | 
				
			||||||
+        return 0; // Paper
 | 
					 | 
				
			||||||
     }
 | 
					 | 
				
			||||||
 
 | 
					 | 
				
			||||||
     public static void spawnCategoryForPosition(MobCategory group, ServerLevel world, ChunkAccess chunk, BlockPos pos, NaturalSpawner.SpawnPredicate checker, NaturalSpawner.AfterSpawnCallback runner) {
 | 
					 | 
				
			||||||
-        StructureFeatureManager structuremanager = world.structureFeatureManager();
 | 
					 | 
				
			||||||
-        ChunkGenerator chunkgenerator = world.getChunkSource().getGenerator();
 | 
					 | 
				
			||||||
-        int i = pos.getY();
 | 
					 | 
				
			||||||
-        BlockState iblockdata = world.getTypeIfLoadedAndInBounds(pos); // Paper - don't load chunks for mob spawn
 | 
					 | 
				
			||||||
-
 | 
					 | 
				
			||||||
-        if (iblockdata != null && !iblockdata.isRedstoneConductor(chunk, pos)) { // Paper - don't load chunks for mob spawn
 | 
					 | 
				
			||||||
+        // Paper start - add maxSpawns parameter and return spawned mobs
 | 
					 | 
				
			||||||
+        spawnMobsInternal(group, world, chunk, pos, checker, runner, Integer.MAX_VALUE, null);
 | 
					 | 
				
			||||||
+    }
 | 
					 | 
				
			||||||
+    public static int spawnMobsInternal(MobCategory enumcreaturetype, ServerLevel worldserver, ChunkAccess ichunkaccess, BlockPos blockposition, NaturalSpawner.SpawnPredicate spawnercreature_c, NaturalSpawner.AfterSpawnCallback spawnercreature_a, int maxSpawns, Consumer<Entity> trackEntity) {
 | 
					 | 
				
			||||||
+        // Paper end - add maxSpawns parameter and return spawned mobs
 | 
					 | 
				
			||||||
+        StructureFeatureManager structuremanager = worldserver.structureFeatureManager();
 | 
					 | 
				
			||||||
+        ChunkGenerator chunkgenerator = worldserver.getChunkSource().getGenerator();
 | 
					 | 
				
			||||||
+        int i = blockposition.getY();
 | 
					 | 
				
			||||||
+        BlockState iblockdata = worldserver.getTypeIfLoadedAndInBounds(blockposition); // Paper - don't load chunks for mob spawn
 | 
					 | 
				
			||||||
+        int j = 0; // Paper - moved up
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+        if (iblockdata != null && !iblockdata.isRedstoneConductor(ichunkaccess, blockposition)) { // Paper - don't load chunks for mob spawn
 | 
					 | 
				
			||||||
             BlockPos.MutableBlockPos blockposition_mutableblockposition = new BlockPos.MutableBlockPos();
 | 
					 | 
				
			||||||
-            int j = 0;
 | 
					 | 
				
			||||||
+            // Paper - moved up
 | 
					 | 
				
			||||||
             int k = 0;
 | 
					 | 
				
			||||||
 
 | 
					 | 
				
			||||||
             while (k < 3) {
 | 
					 | 
				
			||||||
-                int l = pos.getX();
 | 
					 | 
				
			||||||
-                int i1 = pos.getZ();
 | 
					 | 
				
			||||||
+                int l = blockposition.getX();
 | 
					 | 
				
			||||||
+                int i1 = blockposition.getZ();
 | 
					 | 
				
			||||||
                 boolean flag = true;
 | 
					 | 
				
			||||||
                 MobSpawnSettings.SpawnerData biomesettingsmobs_c = null;
 | 
					 | 
				
			||||||
                 SpawnGroupData groupdataentity = null;
 | 
					 | 
				
			||||||
-                int j1 = Mth.ceil(world.random.nextFloat() * 4.0F);
 | 
					 | 
				
			||||||
+                int j1 = Mth.ceil(worldserver.random.nextFloat() * 4.0F);
 | 
					 | 
				
			||||||
                 int k1 = 0;
 | 
					 | 
				
			||||||
                 int l1 = 0;
 | 
					 | 
				
			||||||
 
 | 
					 | 
				
			||||||
@@ -0,0 +0,0 @@ public final class NaturalSpawner {
 | 
					 | 
				
			||||||
                     if (l1 < j1) {
 | 
					 | 
				
			||||||
                         label53:
 | 
					 | 
				
			||||||
                         {
 | 
					 | 
				
			||||||
-                            l += world.random.nextInt(6) - world.random.nextInt(6);
 | 
					 | 
				
			||||||
-                            i1 += world.random.nextInt(6) - world.random.nextInt(6);
 | 
					 | 
				
			||||||
+                            l += worldserver.random.nextInt(6) - worldserver.random.nextInt(6);
 | 
					 | 
				
			||||||
+                            i1 += worldserver.random.nextInt(6) - worldserver.random.nextInt(6);
 | 
					 | 
				
			||||||
                             blockposition_mutableblockposition.set(l, i, i1);
 | 
					 | 
				
			||||||
                             double d0 = (double) l + 0.5D;
 | 
					 | 
				
			||||||
                             double d1 = (double) i1 + 0.5D;
 | 
					 | 
				
			||||||
-                            Player entityhuman = world.getNearestPlayer(d0, (double) i, d1, -1.0D, false);
 | 
					 | 
				
			||||||
+                            Player entityhuman = worldserver.getNearestPlayer(d0, (double) i, d1, -1.0D, false);
 | 
					 | 
				
			||||||
 
 | 
					 | 
				
			||||||
                             if (entityhuman != null) {
 | 
					 | 
				
			||||||
                                 double d2 = entityhuman.distanceToSqr(d0, (double) i, d1);
 | 
					 | 
				
			||||||
 
 | 
					 | 
				
			||||||
-                                if (isRightDistanceToPlayerAndSpawnPoint(world, chunk, blockposition_mutableblockposition, d2) && world.isLoadedAndInBounds(blockposition_mutableblockposition)) { // Paper - don't load chunks for mob spawn
 | 
					 | 
				
			||||||
+                                if (isRightDistanceToPlayerAndSpawnPoint(worldserver, ichunkaccess, blockposition_mutableblockposition, d2) && worldserver.isLoadedAndInBounds(blockposition_mutableblockposition)) { // Paper - don't load chunks for mob spawn
 | 
					 | 
				
			||||||
                                     if (biomesettingsmobs_c == null) {
 | 
					 | 
				
			||||||
-                                        biomesettingsmobs_c = getRandomSpawnMobAt(world, structuremanager, chunkgenerator, group, world.random, (BlockPos) blockposition_mutableblockposition);
 | 
					 | 
				
			||||||
+                                        biomesettingsmobs_c = getRandomSpawnMobAt(worldserver, structuremanager, chunkgenerator, enumcreaturetype, worldserver.random, (BlockPos) blockposition_mutableblockposition);
 | 
					 | 
				
			||||||
                                         if (biomesettingsmobs_c == null) {
 | 
					 | 
				
			||||||
                                             break label53;
 | 
					 | 
				
			||||||
                                         }
 | 
					 | 
				
			||||||
 
 | 
					 | 
				
			||||||
-                                        j1 = biomesettingsmobs_c.minCount + world.random.nextInt(1 + biomesettingsmobs_c.maxCount - biomesettingsmobs_c.minCount);
 | 
					 | 
				
			||||||
+                                        j1 = biomesettingsmobs_c.minCount + worldserver.random.nextInt(1 + biomesettingsmobs_c.maxCount - biomesettingsmobs_c.minCount);
 | 
					 | 
				
			||||||
                                     }
 | 
					 | 
				
			||||||
 
 | 
					 | 
				
			||||||
                                     // Paper start
 | 
					 | 
				
			||||||
-                                    Boolean doSpawning = a(world, group, structuremanager, chunkgenerator, biomesettingsmobs_c, blockposition_mutableblockposition, d2);
 | 
					 | 
				
			||||||
+                                    Boolean doSpawning = a(worldserver, enumcreaturetype, structuremanager, chunkgenerator, biomesettingsmobs_c, blockposition_mutableblockposition, d2);
 | 
					 | 
				
			||||||
                                     if (doSpawning == null) {
 | 
					 | 
				
			||||||
-                                        return;
 | 
					 | 
				
			||||||
+                                        return j; // Paper
 | 
					 | 
				
			||||||
                                     }
 | 
					 | 
				
			||||||
-                                    if (doSpawning && checker.test(biomesettingsmobs_c.type, blockposition_mutableblockposition, chunk)) {
 | 
					 | 
				
			||||||
+                                    if (doSpawning && spawnercreature_c.test(biomesettingsmobs_c.type, blockposition_mutableblockposition, ichunkaccess)) {
 | 
					 | 
				
			||||||
                                         // Paper end
 | 
					 | 
				
			||||||
-                                        Mob entityinsentient = getMobForSpawn(world, biomesettingsmobs_c.type);
 | 
					 | 
				
			||||||
+                                        Mob entityinsentient = getMobForSpawn(worldserver, biomesettingsmobs_c.type);
 | 
					 | 
				
			||||||
 
 | 
					 | 
				
			||||||
 
 | 
					 | 
				
			||||||
                                         if (entityinsentient == null) {
 | 
					 | 
				
			||||||
-                                            return;
 | 
					 | 
				
			||||||
+                                            return j; // Paper
 | 
					 | 
				
			||||||
                                         }
 | 
					 | 
				
			||||||
 
 | 
					 | 
				
			||||||
-                                        entityinsentient.moveTo(d0, (double) i, d1, world.random.nextFloat() * 360.0F, 0.0F);
 | 
					 | 
				
			||||||
-                                        if (isValidPositionForMob(world, entityinsentient, d2)) {
 | 
					 | 
				
			||||||
-                                            groupdataentity = entityinsentient.finalizeSpawn(world, world.getCurrentDifficultyAt(entityinsentient.blockPosition()), MobSpawnType.NATURAL, groupdataentity, (CompoundTag) null);
 | 
					 | 
				
			||||||
+                                        entityinsentient.moveTo(d0, (double) i, d1, worldserver.random.nextFloat() * 360.0F, 0.0F);
 | 
					 | 
				
			||||||
+                                        if (isValidPositionForMob(worldserver, entityinsentient, d2)) {
 | 
					 | 
				
			||||||
+                                            groupdataentity = entityinsentient.finalizeSpawn(worldserver, worldserver.getCurrentDifficultyAt(entityinsentient.blockPosition()), MobSpawnType.NATURAL, groupdataentity, (CompoundTag) null);
 | 
					 | 
				
			||||||
                                             // CraftBukkit start
 | 
					 | 
				
			||||||
-                                            world.addAllEntities(entityinsentient, SpawnReason.NATURAL);
 | 
					 | 
				
			||||||
+                                            worldserver.addAllEntities(entityinsentient, SpawnReason.NATURAL);
 | 
					 | 
				
			||||||
                                             if (!entityinsentient.removed) {
 | 
					 | 
				
			||||||
-                                                ++j;
 | 
					 | 
				
			||||||
+                                                ++j; // Paper - force diff on name change - we expect this to be the total amount spawned
 | 
					 | 
				
			||||||
                                                 ++k1;
 | 
					 | 
				
			||||||
-                                                runner.run(entityinsentient, chunk);
 | 
					 | 
				
			||||||
+                                                spawnercreature_a.run(entityinsentient, ichunkaccess);
 | 
					 | 
				
			||||||
+                                                // Paper start
 | 
					 | 
				
			||||||
+                                                if (trackEntity != null) {
 | 
					 | 
				
			||||||
+                                                    trackEntity.accept(entityinsentient);
 | 
					 | 
				
			||||||
+                                                }
 | 
					 | 
				
			||||||
+                                                // Paper end
 | 
					 | 
				
			||||||
                                             }
 | 
					 | 
				
			||||||
                                             // CraftBukkit end
 | 
					 | 
				
			||||||
-                                            if (j >= entityinsentient.getMaxSpawnClusterSize()) {
 | 
					 | 
				
			||||||
-                                                return;
 | 
					 | 
				
			||||||
+                                            if (j >= entityinsentient.getMaxSpawnClusterSize() || j >= maxSpawns) { // Paper
 | 
					 | 
				
			||||||
+                                                return j; // Paper
 | 
					 | 
				
			||||||
                                             }
 | 
					 | 
				
			||||||
 
 | 
					 | 
				
			||||||
                                             if (entityinsentient.isMaxGroupSizeReached(k1)) {
 | 
					 | 
				
			||||||
@@ -0,0 +0,0 @@ public final class NaturalSpawner {
 | 
					 | 
				
			||||||
             }
 | 
					 | 
				
			||||||
 
 | 
					 | 
				
			||||||
         }
 | 
					 | 
				
			||||||
+        return j; // Paper
 | 
					 | 
				
			||||||
     }
 | 
					 | 
				
			||||||
 
 | 
					 | 
				
			||||||
     private static boolean isRightDistanceToPlayerAndSpawnPoint(ServerLevel world, ChunkAccess chunk, BlockPos.MutableBlockPos pos, double squaredDistance) {
 | 
					 | 
				
			||||||
@@ -0,0 +0,0 @@ public final class NaturalSpawner {
 | 
					 | 
				
			||||||
 
 | 
					 | 
				
			||||||
     public static class SpawnState {
 | 
					 | 
				
			||||||
 
 | 
					 | 
				
			||||||
-        private final int spawnableChunkCount;
 | 
					 | 
				
			||||||
-        private final Object2IntOpenHashMap<MobCategory> mobCategoryCounts;
 | 
					 | 
				
			||||||
+        private final int spawnableChunkCount; final int getSpawnerChunks() { return this.spawnableChunkCount; } // Paper - OBFHELPER
 | 
					 | 
				
			||||||
+        private final Object2IntOpenHashMap<MobCategory> mobCategoryCounts; final Object2IntMap<MobCategory> getEntityCountsByType() { return this.mobCategoryCounts; } // Paper - OBFHELPER
 | 
					 | 
				
			||||||
         private final PotentialCalculator spawnPotential;
 | 
					 | 
				
			||||||
         private final Object2IntMap<MobCategory> unmodifiableMobCategoryCounts;
 | 
					 | 
				
			||||||
         @Nullable
 | 
					 | 
				
			||||||
@@ -0,0 +0,0 @@ public final class NaturalSpawner {
 | 
					 | 
				
			||||||
 
 | 
					 | 
				
			||||||
         // CraftBukkit start
 | 
					 | 
				
			||||||
         private boolean a(MobCategory enumcreaturetype, int limit) {
 | 
					 | 
				
			||||||
-            int i = limit * this.spawnableChunkCount / NaturalSpawner.MAGIC_NUMBER;
 | 
					 | 
				
			||||||
+            int i = limit * this.spawnableChunkCount / NaturalSpawner.MAGIC_NUMBER; // Paper - diff on change, needed in the spawn method
 | 
					 | 
				
			||||||
             // CraftBukkit end
 | 
					 | 
				
			||||||
 
 | 
					 | 
				
			||||||
             return this.mobCategoryCounts.getInt(enumcreaturetype) < i;
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,317 +0,0 @@
 | 
				
			||||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
 | 
					 | 
				
			||||||
From: Shane Freeder <theboyetronic@gmail.com>
 | 
					 | 
				
			||||||
Date: Sun, 9 Jun 2019 03:53:22 +0100
 | 
					 | 
				
			||||||
Subject: [PATCH] incremental chunk saving
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
 | 
					 | 
				
			||||||
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
 | 
					 | 
				
			||||||
--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
 | 
					 | 
				
			||||||
+++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
 | 
					 | 
				
			||||||
@@ -0,0 +0,0 @@ public class PaperWorldConfig {
 | 
					 | 
				
			||||||
         keepLoadedRange = (short) (getInt("keep-spawn-loaded-range", Math.min(spigotConfig.viewDistance, 10)) * 16);
 | 
					 | 
				
			||||||
         log( "Keep Spawn Loaded Range: " + (keepLoadedRange/16));
 | 
					 | 
				
			||||||
     }
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+    public int autoSavePeriod = -1;
 | 
					 | 
				
			||||||
+    private void autoSavePeriod() {
 | 
					 | 
				
			||||||
+        autoSavePeriod = getInt("auto-save-interval", -1);
 | 
					 | 
				
			||||||
+        if (autoSavePeriod > 0) {
 | 
					 | 
				
			||||||
+            log("Auto Save Interval: " +autoSavePeriod + " (" + (autoSavePeriod / 20) + "s)");
 | 
					 | 
				
			||||||
+        } else if (autoSavePeriod < 0) {
 | 
					 | 
				
			||||||
+            autoSavePeriod = net.minecraft.server.MinecraftServer.getServer().autosavePeriod;
 | 
					 | 
				
			||||||
+        }
 | 
					 | 
				
			||||||
+    }
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+    public int maxAutoSaveChunksPerTick = 24;
 | 
					 | 
				
			||||||
+    private void maxAutoSaveChunksPerTick() {
 | 
					 | 
				
			||||||
+        maxAutoSaveChunksPerTick = getInt("max-auto-save-chunks-per-tick", 24);
 | 
					 | 
				
			||||||
+    }
 | 
					 | 
				
			||||||
 }
 | 
					 | 
				
			||||||
diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java
 | 
					 | 
				
			||||||
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
 | 
					 | 
				
			||||||
--- a/src/main/java/net/minecraft/server/MinecraftServer.java
 | 
					 | 
				
			||||||
+++ b/src/main/java/net/minecraft/server/MinecraftServer.java
 | 
					 | 
				
			||||||
@@ -0,0 +0,0 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
 | 
					 | 
				
			||||||
     public static int currentTick = 0; // Paper - Further improve tick loop
 | 
					 | 
				
			||||||
     public java.util.Queue<Runnable> processQueue = new java.util.concurrent.ConcurrentLinkedQueue<Runnable>();
 | 
					 | 
				
			||||||
     public int autosavePeriod;
 | 
					 | 
				
			||||||
+    public boolean serverAutoSave = false; // Paper
 | 
					 | 
				
			||||||
     public Commands vanillaCommandDispatcher;
 | 
					 | 
				
			||||||
     private boolean forceTicks;
 | 
					 | 
				
			||||||
     // CraftBukkit end
 | 
					 | 
				
			||||||
@@ -0,0 +0,0 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
 | 
					 | 
				
			||||||
             this.status.getPlayers().setSample(agameprofile);
 | 
					 | 
				
			||||||
         }
 | 
					 | 
				
			||||||
 
 | 
					 | 
				
			||||||
-        if (autosavePeriod > 0 && this.tickCount % autosavePeriod == 0) { // CraftBukkit
 | 
					 | 
				
			||||||
-            MinecraftServer.LOGGER.debug("Autosave started");
 | 
					 | 
				
			||||||
+        //if (autosavePeriod > 0 && this.ticks % autosavePeriod == 0) { // CraftBukkit // Paper - move down
 | 
					 | 
				
			||||||
+            //MinecraftServer.LOGGER.debug("Autosave started"); // Paper
 | 
					 | 
				
			||||||
+            serverAutoSave = (autosavePeriod > 0 && this.tickCount % autosavePeriod == 0); // Paper
 | 
					 | 
				
			||||||
             this.profiler.push("save");
 | 
					 | 
				
			||||||
+            if (autosavePeriod > 0 && this.tickCount % autosavePeriod == 0) { // Paper
 | 
					 | 
				
			||||||
             this.playerList.saveAll();
 | 
					 | 
				
			||||||
-            this.saveAllChunks(true, false, false);
 | 
					 | 
				
			||||||
+            }// Paper
 | 
					 | 
				
			||||||
+            // Paper start
 | 
					 | 
				
			||||||
+            for (ServerLevel world : getAllLevels()) {
 | 
					 | 
				
			||||||
+                if (world.paperConfig.autoSavePeriod > 0) {
 | 
					 | 
				
			||||||
+                    world.saveIncrementally(serverAutoSave);
 | 
					 | 
				
			||||||
+                }
 | 
					 | 
				
			||||||
+            }
 | 
					 | 
				
			||||||
+            // Paper end
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
             this.profiler.pop();
 | 
					 | 
				
			||||||
-            MinecraftServer.LOGGER.debug("Autosave finished");
 | 
					 | 
				
			||||||
-        }
 | 
					 | 
				
			||||||
+            //MinecraftServer.LOGGER.debug("Autosave finished"); // Paper
 | 
					 | 
				
			||||||
+        //} // Paper
 | 
					 | 
				
			||||||
 
 | 
					 | 
				
			||||||
         this.profiler.push("snooper");
 | 
					 | 
				
			||||||
         if (((DedicatedServer) this).getProperties().snooperEnabled && !this.snooper.isStarted() && this.tickCount > 100) { // Spigot
 | 
					 | 
				
			||||||
diff --git a/src/main/java/net/minecraft/server/level/ChunkHolder.java b/src/main/java/net/minecraft/server/level/ChunkHolder.java
 | 
					 | 
				
			||||||
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
 | 
					 | 
				
			||||||
--- a/src/main/java/net/minecraft/server/level/ChunkHolder.java
 | 
					 | 
				
			||||||
+++ b/src/main/java/net/minecraft/server/level/ChunkHolder.java
 | 
					 | 
				
			||||||
@@ -0,0 +0,0 @@ public class ChunkHolder {
 | 
					 | 
				
			||||||
 
 | 
					 | 
				
			||||||
     private final ChunkMap chunkMap; // Paper
 | 
					 | 
				
			||||||
 
 | 
					 | 
				
			||||||
+    long lastAutoSaveTime; // Paper - incremental autosave
 | 
					 | 
				
			||||||
+    long inactiveTimeStart; // Paper - incremental autosave
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
     public ChunkHolder(ChunkPos pos, int level, LevelLightEngine lightingProvider, ChunkHolder.LevelChangeListener levelUpdateListener, ChunkHolder.PlayerProvider playersWatchingChunkProvider) {
 | 
					 | 
				
			||||||
         this.futures = new AtomicReferenceArray(ChunkHolder.CHUNK_STATUSES.size());
 | 
					 | 
				
			||||||
         this.fullChunkFuture = ChunkHolder.UNLOADED_LEVEL_CHUNK_FUTURE;
 | 
					 | 
				
			||||||
@@ -0,0 +0,0 @@ public class ChunkHolder {
 | 
					 | 
				
			||||||
         boolean flag2 = playerchunk_state.isOrAfter(ChunkHolder.FullChunkStatus.BORDER);
 | 
					 | 
				
			||||||
         boolean flag3 = playerchunk_state1.isOrAfter(ChunkHolder.FullChunkStatus.BORDER);
 | 
					 | 
				
			||||||
 
 | 
					 | 
				
			||||||
+        boolean prevHasBeenLoaded = this.wasAccessibleSinceLastSave; // Paper
 | 
					 | 
				
			||||||
         this.wasAccessibleSinceLastSave |= flag3;
 | 
					 | 
				
			||||||
+        // Paper start - incremental autosave
 | 
					 | 
				
			||||||
+        if (this.wasAccessibleSinceLastSave & !prevHasBeenLoaded) {
 | 
					 | 
				
			||||||
+            long timeSinceAutoSave = this.inactiveTimeStart - this.lastAutoSaveTime;
 | 
					 | 
				
			||||||
+            if (timeSinceAutoSave < 0) {
 | 
					 | 
				
			||||||
+                // safest bet is to assume autosave is needed here
 | 
					 | 
				
			||||||
+                timeSinceAutoSave = this.chunkMap.level.paperConfig.autoSavePeriod;
 | 
					 | 
				
			||||||
+            }
 | 
					 | 
				
			||||||
+            this.lastAutoSaveTime = this.chunkMap.level.getGameTime() - timeSinceAutoSave;
 | 
					 | 
				
			||||||
+            this.chunkMap.autoSaveQueue.add(this);
 | 
					 | 
				
			||||||
+        }
 | 
					 | 
				
			||||||
+        // Paper end
 | 
					 | 
				
			||||||
         if (!flag2 && flag3) {
 | 
					 | 
				
			||||||
             // Paper start - cache ticking ready status
 | 
					 | 
				
			||||||
             int expectCreateCount = ++this.fullChunkCreateCount;
 | 
					 | 
				
			||||||
@@ -0,0 +0,0 @@ public class ChunkHolder {
 | 
					 | 
				
			||||||
     }
 | 
					 | 
				
			||||||
 
 | 
					 | 
				
			||||||
     public void refreshAccessibility() {
 | 
					 | 
				
			||||||
+        boolean prev = this.wasAccessibleSinceLastSave; // Paper
 | 
					 | 
				
			||||||
+        this.wasAccessibleSinceLastSave = getFullChunkStatus(this.ticketLevel).isOrAfter(ChunkHolder.FullChunkStatus.BORDER);
 | 
					 | 
				
			||||||
+        // Paper start - incremental autosave
 | 
					 | 
				
			||||||
+        if (prev != this.wasAccessibleSinceLastSave) {
 | 
					 | 
				
			||||||
+            if (this.wasAccessibleSinceLastSave) {
 | 
					 | 
				
			||||||
+                long timeSinceAutoSave = this.inactiveTimeStart - this.lastAutoSaveTime;
 | 
					 | 
				
			||||||
+                if (timeSinceAutoSave < 0) {
 | 
					 | 
				
			||||||
+                    // safest bet is to assume autosave is needed here
 | 
					 | 
				
			||||||
+                    timeSinceAutoSave = this.chunkMap.level.paperConfig.autoSavePeriod;
 | 
					 | 
				
			||||||
+                }
 | 
					 | 
				
			||||||
+                this.lastAutoSaveTime = this.chunkMap.level.getGameTime() - timeSinceAutoSave;
 | 
					 | 
				
			||||||
+                this.chunkMap.autoSaveQueue.add(this);
 | 
					 | 
				
			||||||
+            } else {
 | 
					 | 
				
			||||||
+                this.inactiveTimeStart = this.chunkMap.level.getGameTime();
 | 
					 | 
				
			||||||
+                this.chunkMap.autoSaveQueue.remove(this);
 | 
					 | 
				
			||||||
+            }
 | 
					 | 
				
			||||||
+        }
 | 
					 | 
				
			||||||
+        // Paper end
 | 
					 | 
				
			||||||
+    }
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+    // Paper start - incremental autosave
 | 
					 | 
				
			||||||
+    public boolean setHasBeenLoaded() {
 | 
					 | 
				
			||||||
         this.wasAccessibleSinceLastSave = getFullChunkStatus(this.ticketLevel).isOrAfter(ChunkHolder.FullChunkStatus.BORDER);
 | 
					 | 
				
			||||||
+        return this.wasAccessibleSinceLastSave;
 | 
					 | 
				
			||||||
     }
 | 
					 | 
				
			||||||
+    // Paper end
 | 
					 | 
				
			||||||
 
 | 
					 | 
				
			||||||
     public void replaceProtoChunk(ImposterProtoChunk protochunkextension) {
 | 
					 | 
				
			||||||
         for (int i = 0; i < this.futures.length(); ++i) {
 | 
					 | 
				
			||||||
diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java
 | 
					 | 
				
			||||||
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
 | 
					 | 
				
			||||||
--- a/src/main/java/net/minecraft/server/level/ChunkMap.java
 | 
					 | 
				
			||||||
+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java
 | 
					 | 
				
			||||||
@@ -0,0 +0,0 @@ import net.minecraft.world.level.levelgen.structure.templatesystem.StructureMana
 | 
					 | 
				
			||||||
 import net.minecraft.world.level.storage.DimensionDataStorage;
 | 
					 | 
				
			||||||
 import net.minecraft.world.level.storage.LevelStorageSource;
 | 
					 | 
				
			||||||
 import net.minecraft.world.phys.Vec3;
 | 
					 | 
				
			||||||
+import it.unimi.dsi.fastutil.objects.ObjectRBTreeSet; // Paper
 | 
					 | 
				
			||||||
 import org.apache.commons.lang3.mutable.MutableBoolean;
 | 
					 | 
				
			||||||
 import org.apache.logging.log4j.LogManager;
 | 
					 | 
				
			||||||
 import org.apache.logging.log4j.Logger;
 | 
					 | 
				
			||||||
@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
 | 
					 | 
				
			||||||
 
 | 
					 | 
				
			||||||
     }
 | 
					 | 
				
			||||||
 
 | 
					 | 
				
			||||||
+    // Paper start - incremental autosave
 | 
					 | 
				
			||||||
+    final ObjectRBTreeSet<ChunkHolder> autoSaveQueue = new ObjectRBTreeSet<>((playerchunk1, playerchunk2) -> {
 | 
					 | 
				
			||||||
+        int timeCompare =  Long.compare(playerchunk1.lastAutoSaveTime, playerchunk2.lastAutoSaveTime);
 | 
					 | 
				
			||||||
+        if (timeCompare != 0) {
 | 
					 | 
				
			||||||
+            return timeCompare;
 | 
					 | 
				
			||||||
+        }
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+        return Long.compare(MCUtil.getCoordinateKey(playerchunk1.pos), MCUtil.getCoordinateKey(playerchunk2.pos));
 | 
					 | 
				
			||||||
+    });
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+    protected void saveIncrementally() {
 | 
					 | 
				
			||||||
+        int savedThisTick = 0;
 | 
					 | 
				
			||||||
+        // optimized since we search far less chunks to hit ones that need to be saved
 | 
					 | 
				
			||||||
+        List<ChunkHolder> reschedule = new java.util.ArrayList<>(this.level.paperConfig.maxAutoSaveChunksPerTick);
 | 
					 | 
				
			||||||
+        long currentTick = this.level.getGameTime();
 | 
					 | 
				
			||||||
+        long maxSaveTime = currentTick - this.level.paperConfig.autoSavePeriod;
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+        for (Iterator<ChunkHolder> iterator = this.autoSaveQueue.iterator(); iterator.hasNext();) {
 | 
					 | 
				
			||||||
+            ChunkHolder playerchunk = iterator.next();
 | 
					 | 
				
			||||||
+            if (playerchunk.lastAutoSaveTime > maxSaveTime) {
 | 
					 | 
				
			||||||
+                break;
 | 
					 | 
				
			||||||
+            }
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+            iterator.remove();
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+            ChunkAccess ichunkaccess = playerchunk.getChunkToSave().getNow(null);
 | 
					 | 
				
			||||||
+            if (ichunkaccess instanceof LevelChunk) {
 | 
					 | 
				
			||||||
+                boolean shouldSave = ((LevelChunk)ichunkaccess).lastSaveTime <= maxSaveTime;
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+                if (shouldSave && this.save(ichunkaccess)) {
 | 
					 | 
				
			||||||
+                    ++savedThisTick;
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+                    if (!playerchunk.setHasBeenLoaded()) {
 | 
					 | 
				
			||||||
+                        // do not fall through to reschedule logic
 | 
					 | 
				
			||||||
+                        playerchunk.inactiveTimeStart = currentTick;
 | 
					 | 
				
			||||||
+                        if (savedThisTick >= this.level.paperConfig.maxAutoSaveChunksPerTick) {
 | 
					 | 
				
			||||||
+                            break;
 | 
					 | 
				
			||||||
+                        }
 | 
					 | 
				
			||||||
+                        continue;
 | 
					 | 
				
			||||||
+                    }
 | 
					 | 
				
			||||||
+                }
 | 
					 | 
				
			||||||
+            }
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+            reschedule.add(playerchunk);
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+            if (savedThisTick >= this.level.paperConfig.maxAutoSaveChunksPerTick) {
 | 
					 | 
				
			||||||
+                break;
 | 
					 | 
				
			||||||
+            }
 | 
					 | 
				
			||||||
+        }
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+        for (int i = 0, len = reschedule.size(); i < len; ++i) {
 | 
					 | 
				
			||||||
+            ChunkHolder playerchunk = reschedule.get(i);
 | 
					 | 
				
			||||||
+            playerchunk.lastAutoSaveTime = this.level.getGameTime();
 | 
					 | 
				
			||||||
+            this.autoSaveQueue.add(playerchunk);
 | 
					 | 
				
			||||||
+        }
 | 
					 | 
				
			||||||
+    }
 | 
					 | 
				
			||||||
+    // Paper end
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
     protected void saveAllChunks(boolean flush) {
 | 
					 | 
				
			||||||
         if (flush) {
 | 
					 | 
				
			||||||
             List<ChunkHolder> list = (List) this.visibleChunkMap.values().stream().filter(ChunkHolder::wasAccessibleSinceLastSave).peek(ChunkHolder::refreshAccessibility).collect(Collectors.toList());
 | 
					 | 
				
			||||||
@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
 | 
					 | 
				
			||||||
 
 | 
					 | 
				
			||||||
                         this.level.unload(chunk);
 | 
					 | 
				
			||||||
                     }
 | 
					 | 
				
			||||||
+                    this.autoSaveQueue.remove(playerchunk); // Paper
 | 
					 | 
				
			||||||
 
 | 
					 | 
				
			||||||
                     this.lightEngine.updateChunkStatus(ichunkaccess.getPos());
 | 
					 | 
				
			||||||
                     this.lightEngine.tryScheduleUpdate();
 | 
					 | 
				
			||||||
@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
 | 
					 | 
				
			||||||
                     playerchunk.replaceProtoChunk(new ImposterProtoChunk(chunk));
 | 
					 | 
				
			||||||
                 }
 | 
					 | 
				
			||||||
 
 | 
					 | 
				
			||||||
+                chunk.setLastSaveTime(this.level.getGameTime() - 1); // Paper - avoid autosaving newly generated/loaded chunks
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
                 chunk.setFullStatus(() -> {
 | 
					 | 
				
			||||||
                     return ChunkHolder.getFullChunkStatus(playerchunk.getTicketLevel());
 | 
					 | 
				
			||||||
                 });
 | 
					 | 
				
			||||||
diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java
 | 
					 | 
				
			||||||
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
 | 
					 | 
				
			||||||
--- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java
 | 
					 | 
				
			||||||
+++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java
 | 
					 | 
				
			||||||
@@ -0,0 +0,0 @@ public class ServerChunkCache extends ChunkSource {
 | 
					 | 
				
			||||||
         } // Paper - Timings
 | 
					 | 
				
			||||||
     }
 | 
					 | 
				
			||||||
 
 | 
					 | 
				
			||||||
+    // Paper start - duplicate save, but call incremental
 | 
					 | 
				
			||||||
+    public void saveIncrementally() {
 | 
					 | 
				
			||||||
+        this.runDistanceManagerUpdates();
 | 
					 | 
				
			||||||
+        try (co.aikar.timings.Timing timed = level.timings.chunkSaveData.startTiming()) { // Paper - Timings
 | 
					 | 
				
			||||||
+            this.chunkMap.saveIncrementally();
 | 
					 | 
				
			||||||
+        } // Paper - Timings
 | 
					 | 
				
			||||||
+    }
 | 
					 | 
				
			||||||
+    // Paper end
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
     @Override
 | 
					 | 
				
			||||||
     public void close() throws IOException {
 | 
					 | 
				
			||||||
         // CraftBukkit start
 | 
					 | 
				
			||||||
diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java
 | 
					 | 
				
			||||||
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
 | 
					 | 
				
			||||||
--- a/src/main/java/net/minecraft/server/level/ServerLevel.java
 | 
					 | 
				
			||||||
+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
 | 
					 | 
				
			||||||
@@ -0,0 +0,0 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl
 | 
					 | 
				
			||||||
         return !this.server.isUnderSpawnProtection(this, pos, player) && this.getWorldBorder().isWithinBounds(pos);
 | 
					 | 
				
			||||||
     }
 | 
					 | 
				
			||||||
 
 | 
					 | 
				
			||||||
+    // Paper start - derived from below
 | 
					 | 
				
			||||||
+    public void saveIncrementally(boolean doFull) {
 | 
					 | 
				
			||||||
+        ServerChunkCache chunkproviderserver = this.getChunkSource();
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+        if (doFull) {
 | 
					 | 
				
			||||||
+            org.bukkit.Bukkit.getPluginManager().callEvent(new org.bukkit.event.world.WorldSaveEvent(getWorld()));
 | 
					 | 
				
			||||||
+        }
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+        try (co.aikar.timings.Timing ignored = timings.worldSave.startTiming()) {
 | 
					 | 
				
			||||||
+            if (doFull) {
 | 
					 | 
				
			||||||
+                this.saveData();
 | 
					 | 
				
			||||||
+            }
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+            timings.worldSaveChunks.startTiming(); // Paper
 | 
					 | 
				
			||||||
+            if (!this.noSave()) chunkproviderserver.saveIncrementally();
 | 
					 | 
				
			||||||
+            timings.worldSaveChunks.stopTiming(); // Paper
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+            // Copied from save()
 | 
					 | 
				
			||||||
+            // CraftBukkit start - moved from MinecraftServer.saveChunks
 | 
					 | 
				
			||||||
+            if (doFull) { // Paper
 | 
					 | 
				
			||||||
+                ServerLevel worldserver1 = this;
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
+                worldDataServer.setWorldBorder(worldserver1.getWorldBorder().createSettings());
 | 
					 | 
				
			||||||
+                worldDataServer.setCustomBossEvents(this.server.getCustomBossEvents().save());
 | 
					 | 
				
			||||||
+                convertable.saveDataTag(this.server.registryHolder, this.worldDataServer, this.server.getPlayerList().getSingleplayerData());
 | 
					 | 
				
			||||||
+            }
 | 
					 | 
				
			||||||
+            // CraftBukkit end
 | 
					 | 
				
			||||||
+        }
 | 
					 | 
				
			||||||
+    }
 | 
					 | 
				
			||||||
+    // Paper end
 | 
					 | 
				
			||||||
+
 | 
					 | 
				
			||||||
     public void save(@Nullable ProgressListener progressListener, boolean flush, boolean flag1) {
 | 
					 | 
				
			||||||
         ServerChunkCache chunkproviderserver = this.getChunkSource();
 | 
					 | 
				
			||||||
 
 | 
					 | 
				
			||||||
@@ -0,0 +0,0 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl
 | 
					 | 
				
			||||||
         // CraftBukkit end
 | 
					 | 
				
			||||||
     }
 | 
					 | 
				
			||||||
 
 | 
					 | 
				
			||||||
+    private void saveData() { this.saveLevelData(); } // Paper - OBFHELPER
 | 
					 | 
				
			||||||
     private void saveLevelData() {
 | 
					 | 
				
			||||||
         if (this.dragonFight != null) {
 | 
					 | 
				
			||||||
             this.worldDataServer.setEndDragonFightData(this.dragonFight.saveData()); // CraftBukkit
 | 
					 | 
				
			||||||
diff --git a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java
 | 
					 | 
				
			||||||
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
 | 
					 | 
				
			||||||
--- a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java
 | 
					 | 
				
			||||||
+++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java
 | 
					 | 
				
			||||||
@@ -0,0 +0,0 @@ public class LevelChunk implements ChunkAccess {
 | 
					 | 
				
			||||||
     private TickList<Block> blockTicks;
 | 
					 | 
				
			||||||
     private TickList<Fluid> liquidTicks;
 | 
					 | 
				
			||||||
     private boolean lastSaveHadEntities;
 | 
					 | 
				
			||||||
-    private long lastSaveTime;
 | 
					 | 
				
			||||||
+    public long lastSaveTime; // Paper
 | 
					 | 
				
			||||||
     private volatile boolean unsaved;
 | 
					 | 
				
			||||||
     private long inhabitedTime;
 | 
					 | 
				
			||||||
     @Nullable
 | 
					 | 
				
			||||||
| 
						 | 
					@ -29,7 +29,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 | 
				
			||||||
--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
 | 
					--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
 | 
				
			||||||
+++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
 | 
					+++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
 | 
				
			||||||
@@ -0,0 +0,0 @@ public class PaperWorldConfig {
 | 
					@@ -0,0 +0,0 @@ public class PaperWorldConfig {
 | 
				
			||||||
             Bukkit.getLogger().warning("You have enabled permission-based Anti-Xray checking - depending on your permission plugin, this may cause performance issues");
 | 
					             log("Using improved mob spawn limits (Only Natural Spawns impact spawn limits for more natural spawns)");
 | 
				
			||||||
         }
 | 
					         }
 | 
				
			||||||
     }
 | 
					     }
 | 
				
			||||||
+
 | 
					+
 | 
				
			||||||
| 
						 | 
					@ -38,6 +38,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 | 
				
			||||||
+        disableRelativeProjectileVelocity = getBoolean("game-mechanics.disable-relative-projectile-velocity", false);
 | 
					+        disableRelativeProjectileVelocity = getBoolean("game-mechanics.disable-relative-projectile-velocity", false);
 | 
				
			||||||
+    }
 | 
					+    }
 | 
				
			||||||
 }
 | 
					 }
 | 
				
			||||||
 | 
					 
 | 
				
			||||||
diff --git a/src/main/java/net/minecraft/world/entity/projectile/Projectile.java b/src/main/java/net/minecraft/world/entity/projectile/Projectile.java
 | 
					diff --git a/src/main/java/net/minecraft/world/entity/projectile/Projectile.java b/src/main/java/net/minecraft/world/entity/projectile/Projectile.java
 | 
				
			||||||
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
 | 
					index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
 | 
				
			||||||
--- a/src/main/java/net/minecraft/world/entity/projectile/Projectile.java
 | 
					--- a/src/main/java/net/minecraft/world/entity/projectile/Projectile.java
 | 
				
			||||||
| 
						 | 
					@ -11,9 +11,9 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 | 
				
			||||||
--- a/src/main/java/net/minecraft/server/MinecraftServer.java
 | 
					--- a/src/main/java/net/minecraft/server/MinecraftServer.java
 | 
				
			||||||
+++ b/src/main/java/net/minecraft/server/MinecraftServer.java
 | 
					+++ b/src/main/java/net/minecraft/server/MinecraftServer.java
 | 
				
			||||||
@@ -0,0 +0,0 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
 | 
					@@ -0,0 +0,0 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
 | 
				
			||||||
         } else if (debugWorld) {
 | 
					             worldProperties.setSpawn(BlockPos.ZERO.above(80), 0.0F);
 | 
				
			||||||
             iworlddataserver.setSpawn(BlockPos.ZERO.above(), 0.0F);
 | 
					 | 
				
			||||||
         } else {
 | 
					         } else {
 | 
				
			||||||
 | 
					             ChunkGenerator chunkgenerator = world.getChunkSource().getGenerator();
 | 
				
			||||||
-            BiomeSource worldchunkmanager = chunkgenerator.getBiomeSource();
 | 
					-            BiomeSource worldchunkmanager = chunkgenerator.getBiomeSource();
 | 
				
			||||||
-            Random random = new Random(world.getSeed());
 | 
					-            Random random = new Random(world.getSeed());
 | 
				
			||||||
-            BlockPos blockposition = worldchunkmanager.findBiomeHorizontal(0, world.getSeaLevel(), 0, 256, (biomebase) -> {
 | 
					-            BlockPos blockposition = worldchunkmanager.findBiomeHorizontal(0, world.getSeaLevel(), 0, 256, (biomebase) -> {
 | 
				
			||||||
| 
						 | 
					@ -14,7 +14,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 | 
				
			||||||
         Player player = entity.getBukkitEntity();
 | 
					         Player player = entity.getBukkitEntity();
 | 
				
			||||||
         PlayerLoginEvent event = new PlayerLoginEvent(player, hostname, ((java.net.InetSocketAddress) socketaddress).getAddress(), ((java.net.InetSocketAddress) loginlistener.connection.getRawAddress()).getAddress());
 | 
					         PlayerLoginEvent event = new PlayerLoginEvent(player, hostname, ((java.net.InetSocketAddress) socketaddress).getAddress(), ((java.net.InetSocketAddress) loginlistener.connection.getRawAddress()).getAddress());
 | 
				
			||||||
 
 | 
					 
 | 
				
			||||||
-        if (getBans().isBanned(gameprofile) && !getBans().get(gameprofile).hasExpired()) {
 | 
					-        if (this.getBans().isBanned(gameprofile) && !this.getBans().get(gameprofile).hasExpired()) {
 | 
				
			||||||
-            UserBanListEntry gameprofilebanentry = (UserBanListEntry) this.bans.get(gameprofile);
 | 
					-            UserBanListEntry gameprofilebanentry = (UserBanListEntry) this.bans.get(gameprofile);
 | 
				
			||||||
+        // Paper start - Fix MC-158900
 | 
					+        // Paper start - Fix MC-158900
 | 
				
			||||||
+        UserBanListEntry gameprofilebanentry;
 | 
					+        UserBanListEntry gameprofilebanentry;
 | 
				
			||||||
| 
						 | 
					@ -21,9 +21,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 | 
				
			||||||
--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
 | 
					--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
 | 
				
			||||||
+++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
 | 
					+++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
 | 
				
			||||||
@@ -0,0 +0,0 @@ public class PaperWorldConfig {
 | 
					@@ -0,0 +0,0 @@ public class PaperWorldConfig {
 | 
				
			||||||
         maxAutoSaveChunksPerTick = getInt("max-auto-save-chunks-per-tick", 24);
 | 
					     private void preventMovingIntoUnloadedChunks() {
 | 
				
			||||||
 | 
					         preventMovingIntoUnloadedChunks = getBoolean("prevent-moving-into-unloaded-chunks", false);
 | 
				
			||||||
     }
 | 
					     }
 | 
				
			||||||
 
 | 
					+
 | 
				
			||||||
+    public boolean countAllMobsForSpawning = false;
 | 
					+    public boolean countAllMobsForSpawning = false;
 | 
				
			||||||
+    private void countAllMobsForSpawning() {
 | 
					+    private void countAllMobsForSpawning() {
 | 
				
			||||||
+        countAllMobsForSpawning = getBoolean("count-all-mobs-for-spawning", false);
 | 
					+        countAllMobsForSpawning = getBoolean("count-all-mobs-for-spawning", false);
 | 
				
			||||||
| 
						 | 
					@ -33,10 +34,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 | 
				
			||||||
+            log("Using improved mob spawn limits (Only Natural Spawns impact spawn limits for more natural spawns)");
 | 
					+            log("Using improved mob spawn limits (Only Natural Spawns impact spawn limits for more natural spawns)");
 | 
				
			||||||
+        }
 | 
					+        }
 | 
				
			||||||
+    }
 | 
					+    }
 | 
				
			||||||
 | 
					 }
 | 
				
			||||||
+
 | 
					+
 | 
				
			||||||
     public boolean antiXray;
 | 
					 | 
				
			||||||
     public EngineMode engineMode;
 | 
					 | 
				
			||||||
     public int maxChunkSectionIndex;
 | 
					 | 
				
			||||||
diff --git a/src/main/java/net/minecraft/world/level/NaturalSpawner.java b/src/main/java/net/minecraft/world/level/NaturalSpawner.java
 | 
					diff --git a/src/main/java/net/minecraft/world/level/NaturalSpawner.java b/src/main/java/net/minecraft/world/level/NaturalSpawner.java
 | 
				
			||||||
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
 | 
					index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
 | 
				
			||||||
--- a/src/main/java/net/minecraft/world/level/NaturalSpawner.java
 | 
					--- a/src/main/java/net/minecraft/world/level/NaturalSpawner.java
 | 
				
			||||||
| 
						 | 
					@ -53,5 +52,5 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 | 
				
			||||||
+                }
 | 
					+                }
 | 
				
			||||||
+                // Paper end
 | 
					+                // Paper end
 | 
				
			||||||
                 BlockPos blockposition = entity.blockPosition();
 | 
					                 BlockPos blockposition = entity.blockPosition();
 | 
				
			||||||
                 long j = ChunkPos.asLong(blockposition.getX() >> 4, blockposition.getZ() >> 4);
 | 
					                 long j = ChunkPos.asLong(SectionPos.blockToSectionCoord(blockposition.getX()), SectionPos.blockToSectionCoord(blockposition.getZ()));
 | 
				
			||||||
 
 | 
					 
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue