mirror of
https://github.com/PaperMC/Paper.git
synced 2024-12-29 12:27:59 +01:00
even more work
This commit is contained in:
parent
5352f23252
commit
713d787f98
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
|
||||
+++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
|
||||
@@ -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);
|
||||
+ }
|
||||
}
|
||||
|
||||
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
|
||||
--- 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
|
||||
+++ b/src/main/java/net/minecraft/server/MinecraftServer.java
|
||||
@@ -0,0 +0,0 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
||||
} else if (debugWorld) {
|
||||
iworlddataserver.setSpawn(BlockPos.ZERO.above(), 0.0F);
|
||||
worldProperties.setSpawn(BlockPos.ZERO.above(80), 0.0F);
|
||||
} else {
|
||||
ChunkGenerator chunkgenerator = world.getChunkSource().getGenerator();
|
||||
- BiomeSource worldchunkmanager = chunkgenerator.getBiomeSource();
|
||||
- Random random = new Random(world.getSeed());
|
||||
- BlockPos blockposition = worldchunkmanager.findBiomeHorizontal(0, world.getSeaLevel(), 0, 256, (biomebase) -> {
|
@ -14,7 +14,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
|
||||
Player player = entity.getBukkitEntity();
|
||||
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);
|
||||
+ // Paper start - Fix MC-158900
|
||||
+ UserBanListEntry gameprofilebanentry;
|
@ -21,9 +21,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
|
||||
--- 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 {
|
||||
maxAutoSaveChunksPerTick = getInt("max-auto-save-chunks-per-tick", 24);
|
||||
private void preventMovingIntoUnloadedChunks() {
|
||||
preventMovingIntoUnloadedChunks = getBoolean("prevent-moving-into-unloaded-chunks", false);
|
||||
}
|
||||
|
||||
+
|
||||
+ public boolean countAllMobsForSpawning = false;
|
||||
+ private void countAllMobsForSpawning() {
|
||||
+ 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)");
|
||||
+ }
|
||||
+ }
|
||||
}
|
||||
+
|
||||
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
|
||||
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
|
||||
--- a/src/main/java/net/minecraft/world/level/NaturalSpawner.java
|
||||
@ -53,5 +52,5 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
|
||||
+ }
|
||||
+ // Paper end
|
||||
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…
Reference in New Issue
Block a user