Merge tuinity (#6413)

This PR contains all of Tuinity's patches. Very notable ones are:

- Highly optimised collisions
- Optimised entity lookups by bounding box (Mojang made regressions in 1.17, this brings it back to 1.16)
- Starlight https://github.com/PaperMC/Starlight
- Rewritten dataconverter system https://github.com/PaperMC/DataConverter
- Random block ticking optimisation (wrongly dropped from Paper 1.17)
- Chunk ticking optimisations
- Anything else I've forgotten in the 60 or so patches

If you are a previous Tuinity user, your config will not migrate. You must do it yourself. The config options have simply been moved into paper.yml, so it will be an easy migration. However, please note that the chunk loading options in tuinity.yml are NOT compatible with the options in paper.yml. 

* Port tuinity, initial patchset

* Update gradle to 7.2

jmp said it fixes rebuildpatches not working for me. it fucking better

* Completely clean apply

* Remove tuinity config, add per player api patch

* Remove paper reobf mappings patch

* Properly update gradlew

* Force clean rebuild

* Mark fixups

Comments and ATs still need to be done

* grep -r "Tuinity"

* Fixup

* Ensure gameprofile lastaccess is written only under the state lock

* update URL for dataconverter

* Only clean rebuild tuinity patches

might fix merge conflicts

* Use UTF-8 for gradlew

* Clean rb patches again

* Convert block ids used as item ids

Neither the converters of pre 1.13 nor DFU handled these cases,
as by the time they were written the game at the time didn't
consider these ids valid - they would be air. Because of this,
some worlds have logspam since only DataConverter (not DFU or
legacy converters) will warn when an invalid id has been
seen.

While quite a few do need to now be considered as air, quite a lot
do not. So it makes sense to add conversion for these items, instead
of simply suppressing or ignoring the logs. I've now added id -> string conversion
for all block ids that could be used as items that existed in the game
before 1.7.10 (I have no interest in tracking down the
exact version block ids stopped working) that were on
https://minecraft-ids.grahamedgecombe.com/

Items that did not directly convert to new items will
be instead converted to air: stems, wheat crops, piston head,
tripwire wire block

* Fix LightPopulated parsing in V1466

The DFU code was checking if the number existed, not if it
didn't exist. I misread the original code.

* Always parse protochunk light sources unless it is marked as non-lit

Chunks not marked as lit will always go through the light engine,
so they should always have their block sources parsed.

* Update custom names to JSON for players

Missed this fix from CB, as it was inside
the DataFixers class.

I decided to double check all of the CB changes again:

DataFixers.java was the only area I missed, as I had inspected all
datafixer diffs and implemented them all into DataConverter. I also
checked Bootstrap.java again, and re-evaluated their changes. I had
previously done this, but determined that they were all bad.

The changes to make standing_sign block map to oak_sign block in
V1450 is bad, because that's not the item id V1450 accepts. Only
in 1.14 did oak_sign even exist, and as expected there is a converter
to rename all existing sign items/blocks.

The fix to register the portal block under id 1440 is useless, as
the flattenning logic will default to the lowest registered id - which
is the exact blockstate that CB registers into 1440. So it just
doesn't do anything.

The extra item ids in the id -> string converter are already added,
but I found this from EMC originally.

The change for the spawn egg id 23 -> Arrow is just wrong,
that id DOES correspond to TippedArrow, NOT Arrow. As
expected, the spawn egg already has a dedicated mapping for
Arrow, which is id 10 - which was Arrow's entity id.

I also ported a fix for the cooked_fished id update. This doesn't
really matter since there is already a dataconverter to fix this,
but the game didn't accept cooked_fished at the time. So I see
no harm.

* Review all converters and walkers

- Refactor V99 to have helper methods for defining entity/tile
  entity types
- Automatically namespace all ids that should be namespaced.
  While vanilla never saved non-namespaced data for things that
  are namespaced, plugins/users might have.
- Synchronised the identity ensure map in HelperBlockFlatteningV1450
- Code style consistency
- Add missing log warning in V102 for ITEM_NAME type conversion
- Use getBoolean instead of getByte
- Use ConverterAbstractEntityRename for V143 TippedArrow -> Arrow
  rename, as it will affect ENTITY_NAME type
- Always set isVillager to false in V502 for Zombie
- Register V808's converter under subversion 1 like DFU
- Register a breakpoint for V1.17.1. In the future, all final
  versions of major releases will have a breakpoint so that
  the work required to determine if a converter needs a breakpoint
  is minimal
- Validate that a dataconverter is only registered for a version
  that is registered
- ConverterFlattenTileEntity is actually ConverterFlattenEntity
  It even registered the converters under TILE_ENTITY, instead of
  ENTITY.
- Fix id comparison in V1492 STRUCTURE_FEATURE renamer
- Use ConverterAbstractStatsRename for V1510 stats renamer
  At the time I had written that class, the abstract renamer didn't
  exist.
- Ensure OwnerUUID is at least set to empty string in
  V1904 if the ocelot is converted to a cat (this is
  likely so that it retains a collar)
- Use generic read/write for Records in V1946
  Records is actually a list, not a map. So reading map was
  invalid.

* Always set light to zero when propagating decrease

This fixes an almost infinite loop where light values
would be spam queued on a very small subset on blocks.

This also likely fixes the memory issues people were
seeing.

* re-organize patches

* Apply and fix conflicts

* Revert some patches

getChunkAt retains chunks so that plugins don't spam loads
revert mc-4 fix will remain unless issues pop up

* Shuffle iterated chunks if per player is not enabled

Can help with some mob spawning stacking up at locations

* Make per player default, migrate all configs

* Adjust comments in fixups

* Rework config for player chunk loader

Old config is not compatible. Move all configs to be
under `settings` in paper.yml

The player chunk loader has been modified to
less aggressively load chunks, but to send
chunks at higher rates compared to tuinity. There are
new config entries to tune this behavior.

* Add back old constructor to CompressionEncoder/Decoder (fixes
 Tuinity #358)

* Raise chunk loading default limits

* Reduce worldgen thread workers for lower core count cpus

* Raise limits for chunk loading config

Also place it under `chunk-loading`

* Disable max chunk send rate by default

* Fix conflicts and rebuild patches

* Drop default send rate again

Appears to be still causing problems for no known reason

* Raise chunk send limits to 100 per player

While a low limit fixes ping issues for some people, most people
do not suffer from this issue and thus should not suffer from
an extremely slow load-in rate.

* Rebase part 1

Autosquash the fixups

* Move not implemented up

* Fixup mc-dev fixes

Missed this one

* Rebase per player viewdistance api into the original api patch

* Remove old light engine patch part 1

The prioritisation must be kept from it, so that part
has been rebased into the priority patch.
Part 2 will deal with rebasing all of the patches _after_

* Rebase remaining patches for old light patch removal

* Remove other mid tick patch

* Remove Optimize-PlayerChunkMap-memory-use-for-visibleChunks.patch

Replaced by `Do not copy visible chunks`

* Revert AT for Vec3i setX/Y/Z

The class is immutable. set should not be exposed

* Remove old IntegerUtil class

* Replace old CraftChunk#getEntities patch

* Remove import for SWMRNibbleArray in ChunkAccess

* Finished merge checklist

* Remove ensureTickThread impl in urgency patch

Co-authored-by: Spottedleaf <Spottedleaf@users.noreply.github.com>
Co-authored-by: Jason Penilla <11360596+jpenilla@users.noreply.github.com>
This commit is contained in:
Spottedleaf 2021-08-31 04:02:11 -07:00
parent 4a11f85ac5
commit bc2890b955
79 changed files with 39253 additions and 2384 deletions

View File

@ -19,9 +19,6 @@ public org.spigotmc.SpigotWorldConfig getList(Ljava/lang/String;Ljava/lang/Objec
public org.spigotmc.SpigotWorldConfig getString(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String; public org.spigotmc.SpigotWorldConfig getString(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
# MC Utils # MC Utils
public net.minecraft.core.Vec3i setX(I)Lnet/minecraft/core/Vec3i;
public net.minecraft.core.Vec3i setY(I)Lnet/minecraft/core/Vec3i;
public net.minecraft.core.Vec3i setZ(I)Lnet/minecraft/core/Vec3i;
public net.minecraft.server.level.ServerChunkCache mainThread public net.minecraft.server.level.ServerChunkCache mainThread
public net.minecraft.server.level.ServerLevel chunkSource public net.minecraft.server.level.ServerLevel chunkSource
public org.bukkit.craftbukkit.inventory.CraftItemStack handle public org.bukkit.craftbukkit.inventory.CraftItemStack handle

View File

@ -1,39 +0,0 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Byteflux <byte@byteflux.net>
Date: Mon, 29 Feb 2016 18:05:37 -0600
Subject: [PATCH] Add player view distance API
diff --git a/src/main/java/org/bukkit/entity/Player.java b/src/main/java/org/bukkit/entity/Player.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/org/bukkit/entity/Player.java
+++ b/src/main/java/org/bukkit/entity/Player.java
@@ -0,0 +0,0 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM
* @param affects Whether the player can affect mob spawning
*/
public void setAffectsSpawning(boolean affects);
+
+ /**
+ * Gets the view distance for this player
+ *
+ * @return the player's view distance
+ * @deprecated This is unimplemented and <i>will</i> throw an exception at runtime. The {@link org.bukkit.World World}-based methods still work.
+ * @see org.bukkit.World#getViewDistance()
+ * @see org.bukkit.World#getNoTickViewDistance()
+ */
+ @Deprecated
+ public int getViewDistance();
+
+ /**
+ * Sets the view distance for this player
+ *
+ * @param viewDistance the player's view distance
+ * @deprecated This is unimplemented and <i>will</i> throw an exception at runtime. The {@link org.bukkit.World World}-based methods still work.
+ * @see org.bukkit.World#setViewDistance(int)
+ * @see org.bukkit.World#setNoTickViewDistance(int)
+ */
+ @Deprecated
+ public void setViewDistance(int viewDistance);
// Paper end
/**

View File

@ -0,0 +1,132 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Byteflux <byte@byteflux.net>
Date: Mon, 29 Feb 2016 18:05:37 -0600
Subject: [PATCH] Add view distance API
Add per player no-tick, tick, and send view distances.
Also add send/no-tick view distance to World.
diff --git a/src/main/java/org/bukkit/World.java b/src/main/java/org/bukkit/World.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/org/bukkit/World.java
+++ b/src/main/java/org/bukkit/World.java
@@ -0,0 +0,0 @@ public interface World extends RegionAccessor, WorldInfo, PluginMessageRecipient
int getViewDistance();
// Spigot end
+ // Paper start - view distance api
+ /**
+ * Sets the view distance for this world.
+ * @param viewDistance view distance in [2, 32]
+ */
+ void setViewDistance(int viewDistance);
+
+ /**
+ * Returns the no-tick view distance for this world.
+ * <p>
+ * No-tick view distance is the view distance where chunks will load, however the chunks and their entities will not
+ * be set to tick.
+ * </p>
+ * @return The no-tick view distance for this world.
+ */
+ int getNoTickViewDistance();
+
+ /**
+ * Sets the no-tick view distance for this world.
+ * <p>
+ * No-tick view distance is the view distance where chunks will load, however the chunks and their entities will not
+ * be set to tick.
+ * </p>
+ * @param viewDistance view distance in [2, 32]
+ */
+ void setNoTickViewDistance(int viewDistance);
+
+ /**
+ * Gets the sending view distance for this world.
+ * <p>
+ * Sending view distance is the view distance where chunks will load in for players in this world.
+ * </p>
+ * @return The sending view distance for this world.
+ */
+ public int getSendViewDistance();
+
+ /**
+ * Sets the sending view distance for this world.
+ * <p>
+ * Sending view distance is the view distance where chunks will load in for players in this world.
+ * </p>
+ * @param viewDistance view distance in [2, 32] or -1
+ */
+ public void setSendViewDistance(int viewDistance);
+ // Paper end - view distance api
// Spigot start
public class Spigot {
diff --git a/src/main/java/org/bukkit/entity/Player.java b/src/main/java/org/bukkit/entity/Player.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/org/bukkit/entity/Player.java
+++ b/src/main/java/org/bukkit/entity/Player.java
@@ -0,0 +0,0 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM
* @param affects Whether the player can affect mob spawning
*/
public void setAffectsSpawning(boolean affects);
+
+ /**
+ * Gets the view distance for this player
+ *
+ * @return the player's view distance
+ * @see org.bukkit.World#getViewDistance()
+ * @see org.bukkit.World#getNoTickViewDistance()
+ */
+ public int getViewDistance();
+
+ /**
+ * Sets the view distance for this player
+ *
+ * @param viewDistance the player's view distance
+ * @see org.bukkit.World#setViewDistance(int)
+ * @see org.bukkit.World#setNoTickViewDistance(int)
+ */
+ public void setViewDistance(int viewDistance);
+
+ /**
+ * Gets the no-ticking view distance for this player.
+ * <p>
+ * No-tick view distance is the view distance where chunks will load, however the chunks and their entities will not
+ * be set to tick.
+ * </p>
+ * @return The no-tick view distance for this player.
+ */
+ public int getNoTickViewDistance();
+
+ /**
+ * Sets the no-ticking view distance for this player.
+ * <p>
+ * No-tick view distance is the view distance where chunks will load, however the chunks and their entities will not
+ * be set to tick.
+ * </p>
+ * @param viewDistance view distance in [2, 32] or -1
+ */
+ public void setNoTickViewDistance(int viewDistance);
+
+ /**
+ * Gets the sending view distance for this player.
+ * <p>
+ * Sending view distance is the view distance where chunks will load in for players.
+ * </p>
+ * @return The sending view distance for this player.
+ */
+ public int getSendViewDistance();
+
+ /**
+ * Sets the sending view distance for this player.
+ * <p>
+ * Sending view distance is the view distance where chunks will load in for players.
+ * </p>
+ * @param viewDistance view distance in [2, 32] or -1
+ */
+ public void setSendViewDistance(int viewDistance);
// Paper end
/**

View File

@ -1,45 +0,0 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Spottedleaf <Spottedleaf@users.noreply.github.com>
Date: Tue, 5 May 2020 21:28:01 -0700
Subject: [PATCH] World view distance api
diff --git a/src/main/java/org/bukkit/World.java b/src/main/java/org/bukkit/World.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/org/bukkit/World.java
+++ b/src/main/java/org/bukkit/World.java
@@ -0,0 +0,0 @@ public interface World extends RegionAccessor, WorldInfo, PluginMessageRecipient
int getViewDistance();
// Spigot end
+ // Paper start - view distance api
+ /**
+ * Sets the view distance for this world.
+ * @param viewDistance view distance in [2, 32]
+ */
+ void setViewDistance(int viewDistance);
+
+ /**
+ * Returns the no-tick view distance for this world.
+ * <p>
+ * No-tick view distance is the view distance where chunks will load, however the chunks and their entities will not
+ * be set to tick.
+ * </p>
+ * @return The no-tick view distance for this world.
+ */
+ int getNoTickViewDistance();
+
+ /**
+ * Sets the no-tick view distance for this world.
+ * <p>
+ * No-tick view distance is the view distance where chunks will load, however the chunks and their entities will not
+ * be set to tick.
+ * </p>
+ * @param viewDistance view distance in [2, 32]
+ */
+ void setNoTickViewDistance(int viewDistance);
+ // Paper end - view distance api
+
// Spigot start
public class Spigot {

View File

@ -29,8 +29,6 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ if(event.getResult() != null) return new BlockPos(event.getResult().getBlockX(), event.getResult().getBlockY(), event.getResult().getBlockZ()); + if(event.getResult() != null) return new BlockPos(event.getResult().getBlockX(), event.getResult().getBlockY(), event.getResult().getBlockZ());
+ // Get origin location (re)defined by event call. + // Get origin location (re)defined by event call.
+ center = new BlockPos(event.getOrigin().getBlockX(), event.getOrigin().getBlockY(), event.getOrigin().getBlockZ()); + center = new BlockPos(event.getOrigin().getBlockX(), event.getOrigin().getBlockY(), event.getOrigin().getBlockZ());
+ // Get world (re)defined by event call.
+ world = ((org.bukkit.craftbukkit.CraftWorld) event.getOrigin().getWorld()).getHandle();
+ // Get radius and whether to find unexplored structures (re)defined by event call. + // Get radius and whether to find unexplored structures (re)defined by event call.
+ radius = event.getRadius(); + radius = event.getRadius();
+ skipExistingChunks = event.shouldFindUnexplored(); + skipExistingChunks = event.shouldFindUnexplored();

View File

@ -0,0 +1,44 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Spottedleaf <Spottedleaf@users.noreply.github.com>
Date: Thu, 15 Jul 2021 01:41:53 -0700
Subject: [PATCH] Add more async catchers
diff --git a/src/main/java/net/minecraft/world/level/entity/EntityTickList.java b/src/main/java/net/minecraft/world/level/entity/EntityTickList.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/world/level/entity/EntityTickList.java
+++ b/src/main/java/net/minecraft/world/level/entity/EntityTickList.java
@@ -0,0 +0,0 @@ public class EntityTickList {
}
public void add(Entity entity) {
+ io.papermc.paper.util.TickThread.ensureTickThread("Asynchronous entity ticklist addition"); // Paper
this.ensureActiveIsNotIterated();
this.active.put(entity.getId(), entity);
}
public void remove(Entity entity) {
+ io.papermc.paper.util.TickThread.ensureTickThread("Asynchronous entity ticklist removal"); // Paper
this.ensureActiveIsNotIterated();
this.active.remove(entity.getId());
}
@@ -0,0 +0,0 @@ public class EntityTickList {
}
public void forEach(Consumer<Entity> action) {
+ io.papermc.paper.util.TickThread.ensureTickThread("Asynchronous entity ticklist iteration"); // Paper
if (this.iterated != null) {
throw new UnsupportedOperationException("Only one concurrent iteration supported");
} else {
diff --git a/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java b/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java
+++ b/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java
@@ -0,0 +0,0 @@ public class PersistentEntitySectionManager<T extends EntityAccess> implements A
}
public void updateChunkStatus(ChunkPos chunkPos, ChunkHolder.FullChunkStatus levelType) {
+ io.papermc.paper.util.TickThread.ensureTickThread("Asynchronous chunk ticking status update"); // Paper
Visibility visibility = Visibility.fromFullChunkStatus(levelType);
this.updateChunkStatus(chunkPos, visibility);

View File

@ -9,14 +9,14 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
--- a/src/main/java/com/destroystokyo/paper/PaperConfig.java --- a/src/main/java/com/destroystokyo/paper/PaperConfig.java
+++ b/src/main/java/com/destroystokyo/paper/PaperConfig.java +++ b/src/main/java/com/destroystokyo/paper/PaperConfig.java
@@ -0,0 +0,0 @@ public class PaperConfig { @@ -0,0 +0,0 @@ public class PaperConfig {
allowBlockPermanentBreakingExploits = getBoolean("settings.unsupported-settings.allow-permanent-block-break-exploits", allowBlockPermanentBreakingExploits);
} }
+
+ public static boolean consoleHasAllPermissions = false; + public static boolean consoleHasAllPermissions = false;
+ private static void consoleHasAllPermissions() { + private static void consoleHasAllPermissions() {
+ consoleHasAllPermissions = getBoolean("settings.console-has-all-permissions", consoleHasAllPermissions); + consoleHasAllPermissions = getBoolean("settings.console-has-all-permissions", consoleHasAllPermissions);
+ } + }
+
} }
diff --git a/src/main/java/org/bukkit/craftbukkit/command/CraftConsoleCommandSender.java b/src/main/java/org/bukkit/craftbukkit/command/CraftConsoleCommandSender.java diff --git a/src/main/java/org/bukkit/craftbukkit/command/CraftConsoleCommandSender.java b/src/main/java/org/bukkit/craftbukkit/command/CraftConsoleCommandSender.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644

View File

@ -0,0 +1,205 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Spottedleaf <spottedleaf@spottedleaf.dev>
Date: Fri, 30 Oct 2020 22:37:16 -0700
Subject: [PATCH] Add packet limiter config
Example config:
packet-limiter:
kick-message: '&cSent too many packets'
limits:
all:
interval: 7.0
max-packet-rate: 500.0
PacketPlayInAutoRecipe:
interval: 4.0
max-packet-rate: 5.0
action: DROP
all section refers to all incoming packets, the action for all is
hard coded to KICK.
For specific limits, the section name is the class's name,
and an action can be defined: DROP or KICK
If interval or rate are less-than 0, the limit is ignored
diff --git a/src/main/java/com/destroystokyo/paper/PaperConfig.java b/src/main/java/com/destroystokyo/paper/PaperConfig.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/com/destroystokyo/paper/PaperConfig.java
+++ b/src/main/java/com/destroystokyo/paper/PaperConfig.java
@@ -0,0 +0,0 @@ public class PaperConfig {
playerMaxConcurrentChunkLoads = getDouble("settings.chunk-loading.player-max-concurrent-loads", 4.0);
globalMaxConcurrentChunkLoads = getDouble("settings.chunk-loading.global-max-concurrent-loads", 500.0);
}
+
+ public static final class PacketLimit {
+ public final double packetLimitInterval;
+ public final double maxPacketRate;
+ public final ViolateAction violateAction;
+
+ public PacketLimit(final double packetLimitInterval, final double maxPacketRate, final ViolateAction violateAction) {
+ this.packetLimitInterval = packetLimitInterval;
+ this.maxPacketRate = maxPacketRate;
+ this.violateAction = violateAction;
+ }
+
+ public static enum ViolateAction {
+ KICK, DROP;
+ }
+ }
+
+ public static String kickMessage;
+ public static PacketLimit allPacketsLimit;
+ public static java.util.Map<Class<? extends net.minecraft.network.protocol.Packet<?>>, PacketLimit> packetSpecificLimits = new java.util.HashMap<>();
+
+ private static void packetLimiter() {
+ packetSpecificLimits.clear();
+ kickMessage = org.bukkit.ChatColor.translateAlternateColorCodes('&', getString("settings.packet-limiter.kick-message", "&cSent too many packets"));
+ allPacketsLimit = new PacketLimit(
+ getDouble("settings.packet-limiter.limits.all.interval", 7.0),
+ getDouble("settings.packet-limiter.limits.all.max-packet-rate", 500.0),
+ PacketLimit.ViolateAction.KICK
+ );
+ if (allPacketsLimit.maxPacketRate <= 0.0 || allPacketsLimit.packetLimitInterval <= 0.0) {
+ allPacketsLimit = null;
+ }
+ final ConfigurationSection section = config.getConfigurationSection("settings.packet-limiter.limits");
+
+ // add default packets
+
+ // auto recipe limiting
+ getDouble("settings.packet-limiter.limits." +
+ "PacketPlayInAutoRecipe" + ".interval", 4.0);
+ getDouble("settings.packet-limiter.limits." +
+ "PacketPlayInAutoRecipe" + ".max-packet-rate", 5.0);
+ getString("settings.packet-limiter.limits." +
+ "PacketPlayInAutoRecipe" + ".action", PacketLimit.ViolateAction.DROP.name());
+
+ final Map<String, String> mojangToSpigot = new HashMap<>();
+ final Map<String, io.papermc.paper.util.ObfHelper.ClassMapping> maps = io.papermc.paper.util.ObfHelper.INSTANCE.mappingsByObfName();
+ if (maps != null) {
+ maps.forEach((spigotName, classMapping) ->
+ mojangToSpigot.put(classMapping.mojangName(), classMapping.obfName()));
+ }
+
+ for (final String packetClassName : section.getKeys(false)) {
+ if (packetClassName.equals("all")) {
+ continue;
+ }
+ Class<?> packetClazz = null;
+
+ for (final String subpackage : List.of("game", "handshake", "login", "status")) {
+ final String fullName = "net.minecraft.network.protocol." + subpackage + "." + packetClassName;
+ try {
+ packetClazz = Class.forName(fullName);
+ break;
+ } catch (final ClassNotFoundException ex) {
+ try {
+ final String spigot = mojangToSpigot.get(fullName);
+ if (spigot != null) {
+ packetClazz = Class.forName(spigot);
+ }
+ } catch (final ClassNotFoundException ignore) {}
+ }
+ }
+
+ if (packetClazz == null || !net.minecraft.network.protocol.Packet.class.isAssignableFrom(packetClazz)) {
+ MinecraftServer.LOGGER.warn("Packet '" + packetClassName + "' does not exist, cannot limit it! Please update paper.yml");
+ continue;
+ }
+
+ if (!(section.get(packetClassName.concat(".interval")) instanceof Number) || !(section.get(packetClassName.concat(".max-packet-rate")) instanceof Number)) {
+ throw new RuntimeException("Packet limit setting " + packetClassName + " is missing interval or max-packet-rate!");
+ }
+
+ final String actionString = section.getString(packetClassName.concat(".action"), "KICK");
+ PacketLimit.ViolateAction action = PacketLimit.ViolateAction.KICK;
+ for (PacketLimit.ViolateAction test : PacketLimit.ViolateAction.values()) {
+ if (actionString.equalsIgnoreCase(test.name())) {
+ action = test;
+ break;
+ }
+ }
+
+ final double interval = section.getDouble(packetClassName.concat(".interval"));
+ final double rate = section.getDouble(packetClassName.concat(".max-packet-rate"));
+
+ if (interval > 0.0 && rate > 0.0) {
+ packetSpecificLimits.put((Class)packetClazz, new PacketLimit(interval, rate, action));
+ }
+ }
+ }
}
diff --git a/src/main/java/net/minecraft/network/Connection.java b/src/main/java/net/minecraft/network/Connection.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/network/Connection.java
+++ b/src/main/java/net/minecraft/network/Connection.java
@@ -0,0 +0,0 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
}
}
// Paper end - allow controlled flushing
+ // Paper start - packet limiter
+ protected final Object PACKET_LIMIT_LOCK = new Object();
+ protected final io.papermc.paper.util.IntervalledCounter allPacketCounts = com.destroystokyo.paper.PaperConfig.allPacketsLimit != null ? new io.papermc.paper.util.IntervalledCounter(
+ (long)(com.destroystokyo.paper.PaperConfig.allPacketsLimit.packetLimitInterval * 1.0e9)
+ ) : null;
+ protected final java.util.Map<Class<? extends net.minecraft.network.protocol.Packet<?>>, io.papermc.paper.util.IntervalledCounter> packetSpecificLimits = new java.util.HashMap<>();
+
+ private boolean stopReadingPackets;
+ private void killForPacketSpam() {
+ this.sendPacket(new ClientboundDisconnectPacket(org.bukkit.craftbukkit.util.CraftChatMessage.fromString(com.destroystokyo.paper.PaperConfig.kickMessage, true)[0]), (future) -> {
+ this.disconnect(org.bukkit.craftbukkit.util.CraftChatMessage.fromString(com.destroystokyo.paper.PaperConfig.kickMessage, true)[0]);
+ });
+ this.setReadOnly();
+ this.stopReadingPackets = true;
+ }
+ // Paper end - packet limiter
public Connection(PacketFlow side) {
this.receiving = side;
@@ -0,0 +0,0 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
protected void channelRead0(ChannelHandlerContext channelhandlercontext, Packet<?> packet) {
if (this.channel.isOpen()) {
+ // Paper start - packet limiter
+ if (this.stopReadingPackets) {
+ return;
+ }
+ if (this.allPacketCounts != null ||
+ com.destroystokyo.paper.PaperConfig.packetSpecificLimits.containsKey(packet.getClass())) {
+ long time = System.nanoTime();
+ synchronized (PACKET_LIMIT_LOCK) {
+ if (this.allPacketCounts != null) {
+ this.allPacketCounts.updateAndAdd(1, time);
+ if (this.allPacketCounts.getRate() >= com.destroystokyo.paper.PaperConfig.allPacketsLimit.maxPacketRate) {
+ this.killForPacketSpam();
+ return;
+ }
+ }
+
+ for (Class<?> check = packet.getClass(); check != Object.class; check = check.getSuperclass()) {
+ com.destroystokyo.paper.PaperConfig.PacketLimit packetSpecificLimit =
+ com.destroystokyo.paper.PaperConfig.packetSpecificLimits.get(check);
+ if (packetSpecificLimit == null) {
+ continue;
+ }
+ io.papermc.paper.util.IntervalledCounter counter = this.packetSpecificLimits.computeIfAbsent((Class)check, (clazz) -> {
+ return new io.papermc.paper.util.IntervalledCounter((long)(packetSpecificLimit.packetLimitInterval * 1.0e9));
+ });
+ counter.updateAndAdd(1, time);
+ if (counter.getRate() >= packetSpecificLimit.maxPacketRate) {
+ switch (packetSpecificLimit.violateAction) {
+ case DROP:
+ return;
+ case KICK:
+ this.killForPacketSpam();
+ return;
+ }
+ }
+ }
+ }
+ }
+ // Paper end - packet limiter
try {
Connection.genericsFtw(packet, this.packetListener);
} catch (RunningOnDifferentThreadException cancelledpackethandleexception) {

View File

@ -84,8 +84,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
commands.put("paper", new PaperCommand("paper")); commands.put("paper", new PaperCommand("paper"));
+ commands.put("mspt", new MSPTCommand("mspt")); + commands.put("mspt", new MSPTCommand("mspt"));
version = getInt("config-version", 21); version = getInt("config-version", 22);
set("config-version", 21); set("config-version", 22);
diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/server/MinecraftServer.java --- a/src/main/java/net/minecraft/server/MinecraftServer.java

View File

@ -9,8 +9,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java --- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
+++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
@@ -0,0 +0,0 @@ public class PaperWorldConfig { @@ -0,0 +0,0 @@ public class PaperWorldConfig {
private void perPlayerMobSpawns() { }
perPlayerMobSpawns = getBoolean("per-player-mob-spawns", false); perPlayerMobSpawns = getBoolean("per-player-mob-spawns", true);
} }
+ +
+ public boolean enderDragonsDeathAlwaysPlacesDragonEgg = false; + public boolean enderDragonsDeathAlwaysPlacesDragonEgg = false;

View File

@ -0,0 +1,147 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Spottedleaf <spottedleaf@spottedleaf.dev>
Date: Sat, 4 Apr 2020 15:27:44 -0700
Subject: [PATCH] Allow controlled flushing for network manager
Only make one flush call when emptying the packet queue too
This patch will be used to optimise out flush calls in later
patches.
diff --git a/src/main/java/net/minecraft/network/Connection.java b/src/main/java/net/minecraft/network/Connection.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/network/Connection.java
+++ b/src/main/java/net/minecraft/network/Connection.java
@@ -0,0 +0,0 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
public ConnectionProtocol protocol;
// Paper end
+ // Paper start - allow controlled flushing
+ volatile boolean canFlush = true;
+ private final java.util.concurrent.atomic.AtomicInteger packetWrites = new java.util.concurrent.atomic.AtomicInteger();
+ private int flushPacketsStart;
+ private final Object flushLock = new Object();
+
+ public void disableAutomaticFlush() {
+ synchronized (this.flushLock) {
+ this.flushPacketsStart = this.packetWrites.get(); // must be volatile and before canFlush = false
+ this.canFlush = false;
+ }
+ }
+
+ public void enableAutomaticFlush() {
+ synchronized (this.flushLock) {
+ this.canFlush = true;
+ if (this.packetWrites.get() != this.flushPacketsStart) { // must be after canFlush = true
+ this.flush(); // only make the flush call if we need to
+ }
+ }
+ }
+
+ private final void flush() {
+ if (this.channel.eventLoop().inEventLoop()) {
+ this.channel.flush();
+ } else {
+ this.channel.eventLoop().execute(() -> {
+ this.channel.flush();
+ });
+ }
+ }
+ // Paper end - allow controlled flushing
+
public Connection(PacketFlow side) {
this.receiving = side;
}
@@ -0,0 +0,0 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
net.minecraft.server.MCUtil.isMainThread() && packet.isReady() && this.queue.isEmpty() &&
(packet.getExtraPackets() == null || packet.getExtraPackets().isEmpty())
))) {
- this.sendPacket(packet, callback);
+ this.writePacket(packet, callback, null); // Paper
return;
}
// write the packets to the queue, then flush - antixray hooks there already
@@ -0,0 +0,0 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
}
private void sendPacket(Packet<?> packet, @Nullable GenericFutureListener<? extends Future<? super Void>> callback) {
+ // Paper start - add flush parameter
+ this.writePacket(packet, callback, Boolean.TRUE);
+ }
+ private void writePacket(Packet<?> packet, @Nullable GenericFutureListener<? extends Future<? super Void>> callback, Boolean flushConditional) {
+ this.packetWrites.getAndIncrement(); // must be befeore using canFlush
+ boolean effectiveFlush = flushConditional == null ? this.canFlush : flushConditional.booleanValue();
+ final boolean flush = effectiveFlush || packet instanceof net.minecraft.network.protocol.game.ClientboundKeepAlivePacket || packet instanceof ClientboundDisconnectPacket; // no delay for certain packets
+ // Paper end - add flush parameter
ConnectionProtocol enumprotocol = ConnectionProtocol.getProtocolForPacket(packet);
ConnectionProtocol enumprotocol1 = this.getCurrentProtocol();
@@ -0,0 +0,0 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
}
if (this.channel.eventLoop().inEventLoop()) {
- this.a(packet, callback, enumprotocol, enumprotocol1);
+ this.a(packet, callback, enumprotocol, enumprotocol1, flush); // Paper - add flush parameter
} else {
this.channel.eventLoop().execute(() -> {
- this.a(packet, callback, enumprotocol, enumprotocol1);
+ this.a(packet, callback, enumprotocol, enumprotocol1, flush); // Paper - add flush parameter
});
}
}
private void a(Packet<?> packet, @Nullable GenericFutureListener<? extends Future<? super Void>> genericfuturelistener, ConnectionProtocol enumprotocol, ConnectionProtocol enumprotocol1) {
+ // Paper start - add flush parameter
+ this.a(packet, genericfuturelistener, enumprotocol, enumprotocol1, true);
+ }
+ private void a(Packet<?> packet, @Nullable GenericFutureListener<? extends Future<? super Void>> genericfuturelistener, ConnectionProtocol enumprotocol, ConnectionProtocol enumprotocol1, boolean flush) {
+ // Paper end - add flush parameter
if (enumprotocol != enumprotocol1) {
this.setProtocol(enumprotocol);
}
@@ -0,0 +0,0 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
try {
// Paper end
- ChannelFuture channelfuture = this.channel.writeAndFlush(packet);
+ ChannelFuture channelfuture = flush ? this.channel.writeAndFlush(packet) : this.channel.write(packet); // Paper - add flush parameter
if (genericfuturelistener != null) {
channelfuture.addListener(genericfuturelistener);
@@ -0,0 +0,0 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
}
private boolean processQueue() {
if (this.queue.isEmpty()) return true;
+ // Paper start - make only one flush call per sendPacketQueue() call
+ final boolean needsFlush = this.canFlush;
+ boolean hasWrotePacket = false;
+ // Paper end - make only one flush call per sendPacketQueue() call
// If we are on main, we are safe here in that nothing else should be processing queue off main anymore
// But if we are not on main due to login/status, the parent is synchronized on packetQueue
java.util.Iterator<PacketHolder> iterator = this.queue.iterator();
@@ -0,0 +0,0 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
PacketHolder queued = iterator.next(); // poll -> peek
// Fix NPE (Spigot bug caused by handleDisconnection())
- if (queued == null) {
+ if (false && queued == null) { // Paper - diff on change, this logic is redundant: iterator guarantees ret of an element - on change, hook the flush logic here
return true;
}
Packet<?> packet = queued.packet;
if (!packet.isReady()) {
+ // Paper start - make only one flush call per sendPacketQueue() call
+ if (hasWrotePacket && (needsFlush || this.canFlush)) {
+ this.flush();
+ }
+ // Paper end - make only one flush call per sendPacketQueue() call
return false;
} else {
iterator.remove();
- this.sendPacket(packet, queued.listener);
+ this.writePacket(packet, queued.listener, (!iterator.hasNext() && (needsFlush || this.canFlush)) ? Boolean.TRUE : Boolean.FALSE); // Paper - make only one flush call per sendPacketQueue() call
+ hasWrotePacket = true; // Paper - make only one flush call per sendPacketQueue() call
}
}
return true;

View File

@ -0,0 +1,89 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Spottedleaf <Spottedleaf@users.noreply.github.com>
Date: Sat, 19 Jun 2021 22:47:17 -0700
Subject: [PATCH] Allow removal/addition of entities to entity ticklist during
tick
It really doesn't make any sense that we would iterate over removed
entities during tick. Sure - tick entity checks removed, but
does it check if the entity is in an entity ticking chunk?
No it doesn't. So, allowing removal while iteration
ENSURES only entities MARKED TO TICK are ticked.
diff --git a/src/main/java/net/minecraft/world/level/entity/EntityTickList.java b/src/main/java/net/minecraft/world/level/entity/EntityTickList.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/world/level/entity/EntityTickList.java
+++ b/src/main/java/net/minecraft/world/level/entity/EntityTickList.java
@@ -0,0 +0,0 @@ import javax.annotation.Nullable;
import net.minecraft.world.entity.Entity;
public class EntityTickList {
- private Int2ObjectMap<Entity> active = new Int2ObjectLinkedOpenHashMap<>();
- private Int2ObjectMap<Entity> passive = new Int2ObjectLinkedOpenHashMap<>();
- @Nullable
- private Int2ObjectMap<Entity> iterated;
+ private final io.papermc.paper.util.maplist.IteratorSafeOrderedReferenceSet<Entity> entities = new io.papermc.paper.util.maplist.IteratorSafeOrderedReferenceSet<>(true); // Paper - rewrite this, always keep this updated - why would we EVER tick an entity that's not ticking?
private void ensureActiveIsNotIterated() {
- if (this.iterated == this.active) {
- this.passive.clear();
-
- for(Entry<Entity> entry : Int2ObjectMaps.fastIterable(this.active)) {
- this.passive.put(entry.getIntKey(), entry.getValue());
- }
-
- Int2ObjectMap<Entity> int2ObjectMap = this.active;
- this.active = this.passive;
- this.passive = int2ObjectMap;
- }
+ // Paper - replace with better logic, do not delay removals
}
public void add(Entity entity) {
io.papermc.paper.util.TickThread.ensureTickThread("Asynchronous entity ticklist addition"); // Paper
this.ensureActiveIsNotIterated();
- this.active.put(entity.getId(), entity);
+ this.entities.add(entity); // Paper - replace with better logic, do not delay removals/additions
}
public void remove(Entity entity) {
io.papermc.paper.util.TickThread.ensureTickThread("Asynchronous entity ticklist removal"); // Paper
this.ensureActiveIsNotIterated();
- this.active.remove(entity.getId());
+ this.entities.remove(entity); // Paper - replace with better logic, do not delay removals/additions
}
public boolean contains(Entity entity) {
- return this.active.containsKey(entity.getId());
+ return this.entities.contains(entity); // Paper - replace with better logic, do not delay removals/additions
}
public void forEach(Consumer<Entity> action) {
io.papermc.paper.util.TickThread.ensureTickThread("Asynchronous entity ticklist iteration"); // Paper
- if (this.iterated != null) {
- throw new UnsupportedOperationException("Only one concurrent iteration supported");
- } else {
- this.iterated = this.active;
-
- try {
- for(Entity entity : this.active.values()) {
- action.accept(entity);
- }
- } finally {
- this.iterated = null;
+ // Paper start - replace with better logic, do not delay removals/additions
+ // To ensure nothing weird happens with dimension travelling, do not iterate over new entries...
+ // (by dfl iterator() is configured to not iterate over new entries)
+ io.papermc.paper.util.maplist.IteratorSafeOrderedReferenceSet.Iterator<Entity> iterator = this.entities.iterator();
+ try {
+ while (iterator.hasNext()) {
+ action.accept(iterator.next());
}
-
+ } finally {
+ iterator.finishedIterating();
}
+ // Paper end - replace with better logic, do not delay removals/additions
}
}

View File

@ -0,0 +1,53 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Spottedleaf <Spottedleaf@users.noreply.github.com>
Date: Mon, 23 Aug 2021 04:50:05 -0700
Subject: [PATCH] Always parse protochunk light sources unless it is marked as
non-lit
Chunks not marked as lit will always go through the light engine,
so they should always have their block sources parsed.
diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java
+++ b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java
@@ -0,0 +0,0 @@ public class ChunkSerializer {
protochunk.setLightEngine(lightengine);
}
- if (!flag && protochunk.getStatus().isOrAfter(ChunkStatus.LIGHT)) {
- Iterator iterator = BlockPos.betweenClosed(pos.getMinBlockX(), world.getMinBuildHeight(), pos.getMinBlockZ(), pos.getMaxBlockX(), world.getMaxBuildHeight() - 1, pos.getMaxBlockZ()).iterator();
+ if (!flag) { // Paper - fix incorrect parsing of blocks that emit light - it should always parse it, unless the chunk is marked as lit
+ // Paper start - let's make sure the implementation isn't as slow as possible
+ int offX = pos.x << 4;
+ int offZ = pos.z << 4;
+
+ int minChunkSection = io.papermc.paper.util.WorldUtil.getMinSection(world);
+ int maxChunkSection = io.papermc.paper.util.WorldUtil.getMaxSection(world);
+
+ LevelChunkSection[] sections = achunksection;
+ for (int sectionY = minChunkSection; sectionY <= maxChunkSection; ++sectionY) {
+ LevelChunkSection section = sections[sectionY - minChunkSection];
+ if (section == null || section.isEmpty()) {
+ // no sources in empty sections
+ continue;
+ }
+ int offY = sectionY << 4;
- while (iterator.hasNext()) {
- BlockPos blockposition = (BlockPos) iterator.next();
+ for (int index = 0; index < (16 * 16 * 16); ++index) {
+ if (section.states.get(index).getLightEmission() <= 0) {
+ continue;
+ }
- if (((ChunkAccess) object).getBlockState(blockposition).getLightEmission() != 0) {
- protochunk.addLight(blockposition);
+ // index = x | (z << 4) | (y << 8)
+ protochunk.addLight(new BlockPos(offX | (index & 15), offY | (index >>> 8), offZ | ((index >>> 4) & 15)));
}
}
+ // Paper end
}
}

View File

@ -198,9 +198,13 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ section.set("thread-per-world-generation", null); + section.set("thread-per-world-generation", null);
+ +
+ int threads = getInt("settings.async-chunks.threads", -1); + int threads = getInt("settings.async-chunks.threads", -1);
+ int cpus = Runtime.getRuntime().availableProcessors(); + int cpus = Runtime.getRuntime().availableProcessors() / 2;
+ if (threads <= 0) { + if (threads <= 0) {
+ threads = (int) Math.min(Integer.getInteger("paper.maxChunkThreads", 8), Math.max(1, cpus - 1)); + if (cpus <= 4) {
+ threads = cpus <= 2 ? 1 : 2;
+ } else {
+ threads = (int) Math.min(Integer.getInteger("paper.maxChunkThreads", 4), cpus / 2);
+ }
+ } + }
+ if (cpus == 1 && !Boolean.getBoolean("Paper.allowAsyncChunksSingleCore")) { + if (cpus == 1 && !Boolean.getBoolean("Paper.allowAsyncChunksSingleCore")) {
+ asyncChunks = false; + asyncChunks = false;
@ -2484,17 +2488,17 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ if (ioThrowable != null) { + if (ioThrowable != null) {
+ com.destroystokyo.paper.util.SneakyThrow.sneaky(ioThrowable); + com.destroystokyo.paper.util.SneakyThrow.sneaky(ioThrowable);
+ } + }
+
- if (nbttagcompound != null) {try (Timing ignored2 = this.level.timings.chunkLoadLevelTimer.startTimingIfSync()) { // Paper start - timings
- boolean flag = nbttagcompound.contains("Level", 10) && nbttagcompound.getCompound("Level").contains("Status", 8);
+ this.getVillagePlace().loadInData(pos, chunkHolder.poiData); + this.getVillagePlace().loadInData(pos, chunkHolder.poiData);
+ chunkHolder.tasks.forEach(Runnable::run); + chunkHolder.tasks.forEach(Runnable::run);
+ // Paper end + // Paper end
- if (nbttagcompound != null) {try (Timing ignored2 = this.level.timings.chunkLoadLevelTimer.startTimingIfSync()) { // Paper start - timings
- boolean flag = nbttagcompound.contains("Level", 10) && nbttagcompound.getCompound("Level").contains("Status", 8);
+ if (chunkHolder.protoChunk != null) {try (Timing ignored2 = this.level.timings.chunkLoadLevelTimer.startTimingIfSync()) { // Paper start - timings // Paper - chunk is created async
- if (flag) { - if (flag) {
- ProtoChunk protochunk = ChunkSerializer.read(this.level, this.structureManager, this.poiManager, pos, nbttagcompound); - ProtoChunk protochunk = ChunkSerializer.read(this.level, this.structureManager, this.poiManager, pos, nbttagcompound);
+ if (chunkHolder.protoChunk != null) {try (Timing ignored2 = this.level.timings.chunkLoadLevelTimer.startTimingIfSync()) { // Paper start - timings // Paper - chunk is created async
+
+ if (true) { + if (true) {
+ ProtoChunk protochunk = chunkHolder.protoChunk; + ProtoChunk protochunk = chunkHolder.protoChunk;
@ -2560,9 +2564,9 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ try (co.aikar.timings.Timing ignored1 = this.level.timings.chunkSaveDataSerialization.startTiming()) { // Paper + try (co.aikar.timings.Timing ignored1 = this.level.timings.chunkSaveDataSerialization.startTiming()) { // Paper
+ nbttagcompound = ChunkSerializer.write(this.level, chunk); + nbttagcompound = ChunkSerializer.write(this.level, chunk);
+ } // Paper + } // Paper
+
- this.write(chunkcoordintpair, nbttagcompound); - this.write(chunkcoordintpair, nbttagcompound);
+
+ // Paper start - async chunk io + // Paper start - async chunk io
+ com.destroystokyo.paper.io.PaperFileIOThread.Holder.INSTANCE.scheduleSave(this.level, chunkcoordintpair.x, chunkcoordintpair.z, + com.destroystokyo.paper.io.PaperFileIOThread.Holder.INSTANCE.scheduleSave(this.level, chunkcoordintpair.x, chunkcoordintpair.z,
+ null, nbttagcompound, com.destroystokyo.paper.io.PrioritizedTaskQueue.NORMAL_PRIORITY); + null, nbttagcompound, com.destroystokyo.paper.io.PrioritizedTaskQueue.NORMAL_PRIORITY);
@ -2806,9 +2810,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
--- a/src/main/java/net/minecraft/server/level/ServerLevel.java --- a/src/main/java/net/minecraft/server/level/ServerLevel.java
+++ b/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 Level implements WorldGenLevel { @@ -0,0 +0,0 @@ public class ServerLevel extends Level implements WorldGenLevel {
return this.chunkSource.getChunk(x, z, false);
} }
}
}
+
+ // Paper start - Asynchronous IO + // Paper start - Asynchronous IO
+ public final com.destroystokyo.paper.io.PaperFileIOThread.ChunkDataController poiDataController = new com.destroystokyo.paper.io.PaperFileIOThread.ChunkDataController() { + public final com.destroystokyo.paper.io.PaperFileIOThread.ChunkDataController poiDataController = new com.destroystokyo.paper.io.PaperFileIOThread.ChunkDataController() {
+ @Override + @Override
@ -2880,11 +2885,9 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ } + }
+ }; + };
+ public final com.destroystokyo.paper.io.chunk.ChunkTaskManager asyncChunkTaskManager; + public final com.destroystokyo.paper.io.chunk.ChunkTaskManager asyncChunkTaskManager;
+ // Paper end // Paper end
+
// Add env and gen to constructor, WorldData -> WorldDataServer // Add env and gen to constructor, WorldData -> WorldDataServer
public ServerLevel(MinecraftServer minecraftserver, Executor executor, LevelStorageSource.LevelStorageAccess convertable_conversionsession, ServerLevelData iworlddataserver, ResourceKey<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, org.bukkit.generator.BiomeProvider biomeProvider) {
// Objects.requireNonNull(minecraftserver); // CraftBukkit - decompile error
@@ -0,0 +0,0 @@ public class ServerLevel extends Level implements WorldGenLevel { @@ -0,0 +0,0 @@ public class ServerLevel extends Level implements WorldGenLevel {
this.sleepStatus = new SleepStatus(); this.sleepStatus = new SleepStatus();
@ -3673,25 +3676,34 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
this.entity.setYHeadRot(yaw); this.entity.setYHeadRot(yaw);
} }
+ @Override// Paper start + // Paper start
+ public java.util.concurrent.CompletableFuture<Boolean> teleportAsync(Location loc, @javax.annotation.Nonnull org.bukkit.event.player.PlayerTeleportEvent.TeleportCause cause) { + @Override
+ net.minecraft.server.level.ChunkMap playerChunkMap = ((CraftWorld) loc.getWorld()).getHandle().getChunkSource().chunkMap; + public java.util.concurrent.CompletableFuture<Boolean> teleportAsync(Location location, TeleportCause cause) {
+ java.util.concurrent.CompletableFuture<Boolean> future = new java.util.concurrent.CompletableFuture<>(); + Preconditions.checkArgument(location != null, "location");
+ location.checkFinite();
+ Location locationClone = location.clone(); // clone so we don't need to worry about mutations after this call.
+ +
+ loc.getWorld().getChunkAtAsyncUrgently(loc).thenCompose(chunk -> { + net.minecraft.server.level.ServerLevel world = ((CraftWorld)locationClone.getWorld()).getHandle();
+ net.minecraft.world.level.ChunkPos pair = new net.minecraft.world.level.ChunkPos(chunk.getX(), chunk.getZ()); + java.util.concurrent.CompletableFuture<Boolean> ret = new java.util.concurrent.CompletableFuture<>();
+ ((CraftWorld) loc.getWorld()).getHandle().getChunkSource().addTicketAtLevel(TicketType.POST_TELEPORT, pair, 31, 0); +
+ net.minecraft.server.level.ChunkHolder updatingChunk = playerChunkMap.getUpdatingChunkIfPresent(pair.toLong()); + world.loadChunksForMoveAsync(getHandle().getBoundingBoxAt(locationClone.getX(), locationClone.getY(), locationClone.getZ()), location.getX(), location.getZ(), (list) -> {
+ if (updatingChunk != null) { + net.minecraft.server.level.ServerChunkCache chunkProviderServer = world.getChunkSource();
+ return updatingChunk.getEntityTickingChunkFuture(); + for (net.minecraft.world.level.chunk.ChunkAccess chunk : list) {
+ } else { + chunkProviderServer.addTicketAtLevel(net.minecraft.server.level.TicketType.POST_TELEPORT, chunk.getPos(), 33, CraftEntity.this.getEntityId());
+ return java.util.concurrent.CompletableFuture.completedFuture(com.mojang.datafixers.util.Either.left(((org.bukkit.craftbukkit.CraftChunk)chunk).getHandle())); + }
+ net.minecraft.server.MinecraftServer.getServer().scheduleOnMain(() -> {
+ try {
+ ret.complete(CraftEntity.this.teleport(locationClone, cause) ? Boolean.TRUE : Boolean.FALSE);
+ } catch (Throwable throwable) {
+ if (throwable instanceof ThreadDeath) {
+ throw (ThreadDeath)throwable;
+ }
+ ret.completeExceptionally(throwable);
+ } + }
+ }).thenAccept((chunk) -> future.complete(teleport(loc, cause))).exceptionally(ex -> {
+ future.completeExceptionally(ex);
+ return null;
+ }); + });
+ return future; + });
+
+ return ret;
+ } + }
+ // Paper end + // Paper end
+ +

View File

@ -0,0 +1,759 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Spottedleaf <Spottedleaf@users.noreply.github.com>
Date: Sun, 2 Feb 2020 02:25:10 -0800
Subject: [PATCH] Attempt to recalculate regionfile header if it is corrupt
Instead of trying to relocate the chunk, which is seems to never
be the correct choice, so we end up duplicating or swapping chunks,
we instead drop the current regionfile header and recalculate -
hoping that at least then we don't swap chunks, and maybe recover
them all.
diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java
+++ b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java
@@ -0,0 +0,0 @@ public class ChunkSerializer {
private static final String SKYLIGHT_STATE_TAG = "starlight.skylight_state";
private static final String STARLIGHT_VERSION_TAG = "starlight.light_version";
// Paper end - replace light engine impl
+ // Paper start
+ // TODO: Check on update
+ public static long getLastWorldSaveTime(CompoundTag chunkData) {
+ CompoundTag levelData = chunkData.getCompound("Level");
+ return levelData.getLong("LastUpdate");
+ }
+ // Paper end
private static final Logger LOGGER = LogManager.getLogger();
public static final String TAG_UPGRADE_DATA = "UpgradeData";
@@ -0,0 +0,0 @@ public class ChunkSerializer {
}
// Paper end
BiomeSource worldchunkmanager = chunkgenerator.getBiomeSource();
- CompoundTag nbttagcompound1 = nbt.getCompound("Level"); // Paper - diff on change, see ChunkSerializer#getChunkCoordinate
+ CompoundTag nbttagcompound1 = nbt.getCompound("Level"); // Paper - diff on change, see ChunkSerializer#getChunkCoordinate // Paper - diff on change
ChunkPos chunkcoordintpair1 = new ChunkPos(nbttagcompound1.getInt("xPos"), nbttagcompound1.getInt("zPos")); // Paper - diff on change, see ChunkSerializer#getChunkCoordinate
if (!Objects.equals(pos, chunkcoordintpair1)) {
@@ -0,0 +0,0 @@ public class ChunkSerializer {
nbttagcompound.put("Level", nbttagcompound1);
nbttagcompound1.putInt("xPos", chunkcoordintpair.x);
nbttagcompound1.putInt("zPos", chunkcoordintpair.z);
- nbttagcompound1.putLong("LastUpdate", asyncsavedata != null ? asyncsavedata.worldTime : world.getGameTime()); // Paper - async chunk unloading
+ nbttagcompound1.putLong("LastUpdate", asyncsavedata != null ? asyncsavedata.worldTime : world.getGameTime()); // Paper - async chunk unloading // Paper - diff on change
nbttagcompound1.putLong("InhabitedTime", chunk.getInhabitedTime());
nbttagcompound1.putString("Status", chunk.getStatus().getName());
UpgradeData chunkconverter = chunk.getUpgradeData();
diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java
+++ b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java
@@ -0,0 +0,0 @@ public class ChunkStorage implements AutoCloseable {
this.fixerUpper = dataFixer;
// Paper start - async chunk io
// remove IO worker
- this.regionFileCache = new RegionFileStorage(directory, dsync); // Paper - nuke IOWorker
+ this.regionFileCache = new RegionFileStorage(directory, dsync, true); // Paper - nuke IOWorker // Paper
// Paper end - async chunk io
}
diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/RegionBitmap.java b/src/main/java/net/minecraft/world/level/chunk/storage/RegionBitmap.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/world/level/chunk/storage/RegionBitmap.java
+++ b/src/main/java/net/minecraft/world/level/chunk/storage/RegionBitmap.java
@@ -0,0 +0,0 @@ import java.util.BitSet;
public class RegionBitmap {
private final BitSet used = new BitSet();
+ // Paper start
+ public final void copyFrom(RegionBitmap other) {
+ BitSet thisBitset = this.used;
+ BitSet otherBitset = other.used;
+
+ for (int i = 0; i < Math.max(thisBitset.size(), otherBitset.size()); ++i) {
+ thisBitset.set(i, otherBitset.get(i));
+ }
+ }
+
+ public final boolean tryAllocate(int from, int length) {
+ BitSet bitset = this.used;
+ int firstSet = bitset.nextSetBit(from);
+ if (firstSet > 0 && firstSet < (from + length)) {
+ return false;
+ }
+ bitset.set(from, from + length);
+ return true;
+ }
+ // Paper end
+
public void force(int start, int size) {
this.used.set(start, start + size);
}
diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFile.java b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFile.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFile.java
+++ b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFile.java
@@ -0,0 +0,0 @@ public class RegionFile implements AutoCloseable {
public final java.util.concurrent.locks.ReentrantLock fileLock = new java.util.concurrent.locks.ReentrantLock(true); // Paper
public final File regionFile; // Paper
+ // Paper start - try to recover from RegionFile header corruption
+ private static long roundToSectors(long bytes) {
+ long sectors = bytes >>> 12; // 4096 = 2^12
+ long remainingBytes = bytes & 4095;
+ long sign = -remainingBytes; // sign is 1 if nonzero
+ return sectors + (sign >>> 63);
+ }
+
+ private static final CompoundTag OVERSIZED_COMPOUND = new CompoundTag();
+
+ private CompoundTag attemptRead(long sector, int chunkDataLength, long fileLength) throws IOException {
+ try {
+ if (chunkDataLength < 0) {
+ return null;
+ }
+
+ long offset = sector * 4096L + 4L; // offset for chunk data
+
+ if ((offset + chunkDataLength) > fileLength) {
+ return null;
+ }
+
+ ByteBuffer chunkData = ByteBuffer.allocate(chunkDataLength);
+ if (chunkDataLength != this.file.read(chunkData, offset)) {
+ return null;
+ }
+
+ ((java.nio.Buffer)chunkData).flip();
+
+ byte compressionType = chunkData.get();
+ if (compressionType < 0) { // compressionType & 128 != 0
+ // oversized chunk
+ return OVERSIZED_COMPOUND;
+ }
+
+ RegionFileVersion compression = RegionFileVersion.fromId(compressionType);
+ if (compression == null) {
+ return null;
+ }
+
+ InputStream input = compression.wrap(new ByteArrayInputStream(chunkData.array(), chunkData.position(), chunkDataLength - chunkData.position()));
+
+ return NbtIo.read((java.io.DataInput)new DataInputStream(new BufferedInputStream(input)));
+ } catch (Exception ex) {
+ return null;
+ }
+ }
+
+ private int getLength(long sector) throws IOException {
+ ByteBuffer length = ByteBuffer.allocate(4);
+ if (4 != this.file.read(length, sector * 4096L)) {
+ return -1;
+ }
+
+ return length.getInt(0);
+ }
+
+ private void backupRegionFile() {
+ File backup = new File(this.regionFile.getParent(), this.regionFile.getName() + "." + new java.util.Random().nextLong() + ".backup");
+ this.backupRegionFile(backup);
+ }
+
+ private void backupRegionFile(File to) {
+ try {
+ this.file.force(true);
+ LOGGER.warn("Backing up regionfile \"" + this.regionFile.getAbsolutePath() + "\" to " + to.getAbsolutePath());
+ java.nio.file.Files.copy(this.regionFile.toPath(), to.toPath());
+ LOGGER.warn("Backed up the regionfile to " + to.getAbsolutePath());
+ } catch (IOException ex) {
+ LOGGER.error("Failed to backup to " + to.getAbsolutePath(), ex);
+ }
+ }
+
+ // note: only call for CHUNK regionfiles
+ void recalculateHeader() throws IOException {
+ if (!this.canRecalcHeader) {
+ return;
+ }
+ synchronized (this) {
+ LOGGER.warn("Corrupt regionfile header detected! Attempting to re-calculate header offsets for regionfile " + this.regionFile.getAbsolutePath(), new Throwable());
+
+ // try to backup file so maybe it could be sent to us for further investigation
+
+ this.backupRegionFile();
+ CompoundTag[] compounds = new CompoundTag[32 * 32]; // only in the regionfile (i.e exclude mojang/aikar oversized data)
+ int[] rawLengths = new int[32 * 32]; // length of chunk data including 4 byte length field, bytes
+ int[] sectorOffsets = new int[32 * 32]; // in sectors
+ boolean[] hasAikarOversized = new boolean[32 * 32];
+
+ long fileLength = this.file.size();
+ long totalSectors = roundToSectors(fileLength);
+
+ // search the regionfile from start to finish for the most up-to-date chunk data
+
+ for (long i = 2, maxSector = Math.min((long)(Integer.MAX_VALUE >>> 8), totalSectors); i < maxSector; ++i) { // first two sectors are header, skip
+ int chunkDataLength = this.getLength(i);
+ CompoundTag compound = this.attemptRead(i, chunkDataLength, fileLength);
+ if (compound == null || compound == OVERSIZED_COMPOUND) {
+ continue;
+ }
+
+ ChunkPos chunkPos = ChunkSerializer.getChunkCoordinate(compound);
+ int location = (chunkPos.x & 31) | ((chunkPos.z & 31) << 5);
+
+ CompoundTag otherCompound = compounds[location];
+
+ if (otherCompound != null && ChunkSerializer.getLastWorldSaveTime(otherCompound) > ChunkSerializer.getLastWorldSaveTime(compound)) {
+ continue; // don't overwrite newer data.
+ }
+
+ // aikar oversized?
+ File aikarOversizedFile = this.getOversizedFile(chunkPos.x, chunkPos.z);
+ boolean isAikarOversized = false;
+ if (aikarOversizedFile.exists()) {
+ try {
+ CompoundTag aikarOversizedCompound = this.getOversizedData(chunkPos.x, chunkPos.z);
+ if (ChunkSerializer.getLastWorldSaveTime(compound) == ChunkSerializer.getLastWorldSaveTime(aikarOversizedCompound)) {
+ // best we got for an id. hope it's good enough
+ isAikarOversized = true;
+ }
+ } catch (Exception ex) {
+ LOGGER.error("Failed to read aikar oversized data for absolute chunk (" + chunkPos.x + "," + chunkPos.z + ") in regionfile " + this.regionFile.getAbsolutePath() + ", oversized data for this chunk will be lost", ex);
+ // fall through, if we can't read aikar oversized we can't risk corrupting chunk data
+ }
+ }
+
+ hasAikarOversized[location] = isAikarOversized;
+ compounds[location] = compound;
+ rawLengths[location] = chunkDataLength + 4;
+ sectorOffsets[location] = (int)i;
+
+ int chunkSectorLength = (int)roundToSectors(rawLengths[location]);
+ i += chunkSectorLength;
+ --i; // gets incremented next iteration
+ }
+
+ // forge style oversized data is already handled by the local search, and aikar data we just hope
+ // we get it right as aikar data has no identifiers we could use to try and find its corresponding
+ // local data compound
+
+ java.nio.file.Path containingFolder = this.externalFileDir;
+ File[] regionFiles = containingFolder.toFile().listFiles();
+ boolean[] oversized = new boolean[32 * 32];
+ RegionFileVersion[] oversizedCompressionTypes = new RegionFileVersion[32 * 32];
+
+ if (regionFiles != null) {
+ ChunkPos ourLowerLeftPosition = RegionFileStorage.getRegionFileCoordinates(this.regionFile);
+
+ if (ourLowerLeftPosition == null) {
+ LOGGER.fatal("Unable to get chunk location of regionfile " + this.regionFile.getAbsolutePath() + ", cannot recover oversized chunks");
+ } else {
+ int lowerXBound = ourLowerLeftPosition.x; // inclusive
+ int lowerZBound = ourLowerLeftPosition.z; // inclusive
+ int upperXBound = lowerXBound + 32 - 1; // inclusive
+ int upperZBound = lowerZBound + 32 - 1; // inclusive
+
+ // read mojang oversized data
+ for (File regionFile : regionFiles) {
+ ChunkPos oversizedCoords = getOversizedChunkPair(regionFile);
+ if (oversizedCoords == null) {
+ continue;
+ }
+
+ if ((oversizedCoords.x < lowerXBound || oversizedCoords.x > upperXBound) || (oversizedCoords.z < lowerZBound || oversizedCoords.z > upperZBound)) {
+ continue; // not in our regionfile
+ }
+
+ // ensure oversized data is valid & is newer than data in the regionfile
+
+ int location = (oversizedCoords.x & 31) | ((oversizedCoords.z & 31) << 5);
+
+ byte[] chunkData;
+ try {
+ chunkData = Files.readAllBytes(regionFile.toPath());
+ } catch (Exception ex) {
+ LOGGER.error("Failed to read oversized chunk data in file " + regionFile.getAbsolutePath() + ", data will be lost", ex);
+ continue;
+ }
+
+ CompoundTag compound = null;
+
+ // We do not know the compression type, as it's stored in the regionfile. So we need to try all of them
+ RegionFileVersion compression = null;
+ for (RegionFileVersion compressionType : RegionFileVersion.VERSIONS.values()) {
+ try {
+ DataInputStream in = new DataInputStream(new BufferedInputStream(compressionType.wrap(new ByteArrayInputStream(chunkData)))); // typical java
+ compound = NbtIo.read((java.io.DataInput)in);
+ compression = compressionType;
+ break; // reaches here iff readNBT does not throw
+ } catch (Exception ex) {
+ continue;
+ }
+ }
+
+ if (compound == null) {
+ LOGGER.error("Failed to read oversized chunk data in file " + regionFile.getAbsolutePath() + ", it's corrupt. Its data will be lost");
+ continue;
+ }
+
+ if (compounds[location] == null || ChunkSerializer.getLastWorldSaveTime(compound) > ChunkSerializer.getLastWorldSaveTime(compounds[location])) {
+ oversized[location] = true;
+ oversizedCompressionTypes[location] = compression;
+ }
+ }
+ }
+ }
+
+ // now we need to calculate a new offset header
+
+ int[] calculatedOffsets = new int[32 * 32];
+ RegionBitmap newSectorAllocations = new RegionBitmap();
+ newSectorAllocations.force(0, 2); // make space for header
+
+ // allocate sectors for normal chunks
+
+ for (int chunkX = 0; chunkX < 32; ++chunkX) {
+ for (int chunkZ = 0; chunkZ < 32; ++chunkZ) {
+ int location = chunkX | (chunkZ << 5);
+
+ if (oversized[location]) {
+ continue;
+ }
+
+ int rawLength = rawLengths[location]; // bytes
+ int sectorOffset = sectorOffsets[location]; // sectors
+ int sectorLength = (int)roundToSectors(rawLength);
+
+ if (newSectorAllocations.tryAllocate(sectorOffset, sectorLength)) {
+ calculatedOffsets[location] = sectorOffset << 8 | (sectorLength > 255 ? 255 : sectorLength); // support forge style oversized
+ } else {
+ LOGGER.error("Failed to allocate space for local chunk (overlapping data??) at (" + chunkX + "," + chunkZ + ") in regionfile " + this.regionFile.getAbsolutePath() + ", chunk will be regenerated");
+ }
+ }
+ }
+
+ // allocate sectors for oversized chunks
+
+ for (int chunkX = 0; chunkX < 32; ++chunkX) {
+ for (int chunkZ = 0; chunkZ < 32; ++chunkZ) {
+ int location = chunkX | (chunkZ << 5);
+
+ if (!oversized[location]) {
+ continue;
+ }
+
+ int sectorOffset = newSectorAllocations.allocate(1);
+ int sectorLength = 1;
+
+ try {
+ this.file.write(this.createExternalStub(oversizedCompressionTypes[location]), sectorOffset * 4096);
+ // only allocate in the new offsets if the write succeeds
+ calculatedOffsets[location] = sectorOffset << 8 | (sectorLength > 255 ? 255 : sectorLength); // support forge style oversized
+ } catch (IOException ex) {
+ newSectorAllocations.free(sectorOffset, sectorLength);
+ LOGGER.error("Failed to write new oversized chunk data holder, local chunk at (" + chunkX + "," + chunkZ + ") in regionfile " + this.regionFile.getAbsolutePath() + " will be regenerated");
+ }
+ }
+ }
+
+ // rewrite aikar oversized data
+
+ this.oversizedCount = 0;
+ for (int chunkX = 0; chunkX < 32; ++chunkX) {
+ for (int chunkZ = 0; chunkZ < 32; ++chunkZ) {
+ int location = chunkX | (chunkZ << 5);
+ int isAikarOversized = hasAikarOversized[location] ? 1 : 0;
+
+ this.oversizedCount += isAikarOversized;
+ this.oversized[location] = (byte)isAikarOversized;
+ }
+ }
+
+ if (this.oversizedCount > 0) {
+ try {
+ this.writeOversizedMeta();
+ } catch (Exception ex) {
+ LOGGER.error("Failed to write aikar oversized chunk meta, all aikar style oversized chunk data will be lost for regionfile " + this.regionFile.getAbsolutePath(), ex);
+ this.getOversizedMetaFile().delete();
+ }
+ } else {
+ this.getOversizedMetaFile().delete();
+ }
+
+ this.usedSectors.copyFrom(newSectorAllocations);
+
+ // before we overwrite the old sectors, print a summary of the chunks that got changed.
+
+ LOGGER.info("Starting summary of changes for regionfile " + this.regionFile.getAbsolutePath());
+
+ for (int chunkX = 0; chunkX < 32; ++chunkX) {
+ for (int chunkZ = 0; chunkZ < 32; ++chunkZ) {
+ int location = chunkX | (chunkZ << 5);
+
+ int oldOffset = this.offsets.get(location);
+ int newOffset = calculatedOffsets[location];
+
+ if (oldOffset == newOffset) {
+ continue;
+ }
+
+ this.offsets.put(location, newOffset); // overwrite incorrect offset
+
+ if (oldOffset == 0) {
+ // found lost data
+ LOGGER.info("Found missing data for local chunk (" + chunkX + "," + chunkZ + ") in regionfile " + this.regionFile.getAbsolutePath());
+ } else if (newOffset == 0) {
+ LOGGER.warn("Data for local chunk (" + chunkX + "," + chunkZ + ") could not be recovered in regionfile " + this.regionFile.getAbsolutePath() + ", it will be regenerated");
+ } else {
+ LOGGER.info("Local chunk (" + chunkX + "," + chunkZ + ") changed to point to newer data or correct chunk in regionfile " + this.regionFile.getAbsolutePath());
+ }
+ }
+ }
+
+ LOGGER.info("End of change summary for regionfile " + this.regionFile.getAbsolutePath());
+
+ // simply destroy the timestamp header, it's not used
+
+ for (int i = 0; i < 32 * 32; ++i) {
+ this.timestamps.put(i, calculatedOffsets[i] != 0 ? (int)System.currentTimeMillis() : 0); // write a valid timestamp for valid chunks, I do not want to find out whatever dumb program actually checks this
+ }
+
+ // write new header
+ try {
+ this.flush();
+ this.file.force(true); // try to ensure it goes through...
+ LOGGER.info("Successfully wrote new header to disk for regionfile " + this.regionFile.getAbsolutePath());
+ } catch (IOException ex) {
+ LOGGER.fatal("Failed to write new header to disk for regionfile " + this.regionFile.getAbsolutePath(), ex);
+ }
+ }
+ }
+
+ final boolean canRecalcHeader; // final forces compile fail on new constructor
+ // Paper end
+
// Paper start - Cache chunk status
private final ChunkStatus[] statuses = new ChunkStatus[32 * 32];
@@ -0,0 +0,0 @@ public class RegionFile implements AutoCloseable {
public RegionFile(File file, File directory, boolean dsync) throws IOException {
this(file.toPath(), directory.toPath(), RegionFileVersion.VERSION_DEFLATE, dsync);
}
+ // Paper start - add can recalc flag
+ public RegionFile(File file, File directory, boolean dsync, boolean canRecalcHeader) throws IOException {
+ this(file.toPath(), directory.toPath(), RegionFileVersion.VERSION_DEFLATE, dsync, canRecalcHeader);
+ }
+ // Paper end - add can recalc flag
public RegionFile(Path file, Path directory, RegionFileVersion outputChunkStreamVersion, boolean dsync) throws IOException {
+ // Paper start - add can recalc flag
+ this(file, directory, outputChunkStreamVersion, dsync, false);
+ }
+ public RegionFile(Path file, Path directory, RegionFileVersion outputChunkStreamVersion, boolean dsync, boolean canRecalcHeader) throws IOException {
+ this.canRecalcHeader = canRecalcHeader;
+ // Paper end - add can recalc flag
this.header = ByteBuffer.allocateDirect(8192);
this.regionFile = file.toFile(); // Paper
initOversizedState(); // Paper
@@ -0,0 +0,0 @@ public class RegionFile implements AutoCloseable {
RegionFile.LOGGER.warn("Region file {} has truncated header: {}", file, i);
}
- long j = Files.size(file);
+ final long j = Files.size(file); final long regionFileSize = j; // Paper - recalculate header on header corruption
+ boolean needsHeaderRecalc = false; // Paper - recalculate header on header corruption
+ boolean hasBackedUp = false; // Paper - recalculate header on header corruption
for (int k = 0; k < 1024; ++k) {
- int l = this.offsets.get(k);
+ final int l = this.offsets.get(k); final int headerLocation = l; // Paper - we expect this to be the header location
if (l != 0) {
- int i1 = RegionFile.getSectorNumber(l);
- int j1 = RegionFile.getNumSectors(l);
+ final int i1 = RegionFile.getSectorNumber(l); final int offset = i1; // Paper - we expect this to be offset in file in sectors
+ int j1 = RegionFile.getNumSectors(l); final int sectorLength; // Paper - diff on change, we expect this to be sector length of region - watch out for reassignments
// Spigot start
if (j1 == 255) {
// We're maxed out, so we need to read the proper length from the section
@@ -0,0 +0,0 @@ public class RegionFile implements AutoCloseable {
j1 = (realLen.getInt(0) + 4) / 4096 + 1;
}
// Spigot end
+ sectorLength = j1; // Paper - diff on change, we expect this to be sector length of region
if (i1 < 2) {
RegionFile.LOGGER.warn("Region file {} has invalid sector at index: {}; sector {} overlaps with header", file, k, i1);
- this.offsets.put(k, 0);
+ //this.offsets.put(k, 0); // Paper - we catch this, but need it in the header for the summary change
} else if (j1 == 0) {
RegionFile.LOGGER.warn("Region file {} has an invalid sector at index: {}; size has to be > 0", file, k);
- this.offsets.put(k, 0);
+ //this.offsets.put(k, 0); // Paper - we catch this, but need it in the header for the summary change
} else if ((long) i1 * 4096L > j) {
RegionFile.LOGGER.warn("Region file {} has an invalid sector at index: {}; sector {} is out of bounds", file, k, i1);
- this.offsets.put(k, 0);
+ //this.offsets.put(k, 0); // Paper - we catch this, but need it in the header for the summary change
} else {
- this.usedSectors.force(i1, j1);
+ //this.usedSectors.force(i1, j1); // Paper - move this down so we can check if it fails to allocate
+ }
+ // Paper start - recalculate header on header corruption
+ if (offset < 2 || sectorLength <= 0 || ((long)offset * 4096L) > regionFileSize) {
+ if (canRecalcHeader) {
+ LOGGER.error("Detected invalid header for regionfile " + this.regionFile.getAbsolutePath() + "! Recalculating header...");
+ needsHeaderRecalc = true;
+ break;
+ } else {
+ // location = chunkX | (chunkZ << 5);
+ LOGGER.fatal("Detected invalid header for regionfile " + this.regionFile.getAbsolutePath() +
+ "! Cannot recalculate, removing local chunk (" + (headerLocation & 31) + "," + (headerLocation >>> 5) + ") from header");
+ if (!hasBackedUp) {
+ hasBackedUp = true;
+ this.backupRegionFile();
+ }
+ this.timestamps.put(headerLocation, 0); // be consistent, delete the timestamp too
+ this.offsets.put(headerLocation, 0); // delete the entry from header
+ continue;
+ }
+ }
+ boolean failedToAllocate = !this.usedSectors.tryAllocate(offset, sectorLength);
+ if (failedToAllocate) {
+ LOGGER.error("Overlapping allocation by local chunk (" + (headerLocation & 31) + "," + (headerLocation >>> 5) + ") in regionfile " + this.regionFile.getAbsolutePath());
}
+ if (failedToAllocate & !canRecalcHeader) {
+ // location = chunkX | (chunkZ << 5);
+ LOGGER.fatal("Detected invalid header for regionfile " + this.regionFile.getAbsolutePath() +
+ "! Cannot recalculate, removing local chunk (" + (headerLocation & 31) + "," + (headerLocation >>> 5) + ") from header");
+ if (!hasBackedUp) {
+ hasBackedUp = true;
+ this.backupRegionFile();
+ }
+ this.timestamps.put(headerLocation, 0); // be consistent, delete the timestamp too
+ this.offsets.put(headerLocation, 0); // delete the entry from header
+ continue;
+ }
+ needsHeaderRecalc |= failedToAllocate;
+ // Paper end - recalculate header on header corruption
}
}
+ // Paper start - recalculate header on header corruption
+ // we move the recalc here so comparison to old header is correct when logging to console
+ if (needsHeaderRecalc) { // true if header gave us overlapping allocations or had other issues
+ LOGGER.error("Recalculating regionfile " + this.regionFile.getAbsolutePath() + ", header gave erroneous offsets & locations");
+ this.recalculateHeader();
+ }
+ // Paper end
}
}
}
private Path getExternalChunkPath(ChunkPos chunkPos) {
- String s = "c." + chunkPos.x + "." + chunkPos.z + ".mcc";
+ String s = "c." + chunkPos.x + "." + chunkPos.z + ".mcc"; // Paper - diff on change
return this.externalFileDir.resolve(s);
}
+ // Paper start
+ private static ChunkPos getOversizedChunkPair(File file) {
+ String fileName = file.getName();
+
+ if (!fileName.startsWith("c.") || !fileName.endsWith(".mcc")) {
+ return null;
+ }
+
+ String[] split = fileName.split("\\.");
+
+ if (split.length != 4) {
+ return null;
+ }
+
+ try {
+ int x = Integer.parseInt(split[1]);
+ int z = Integer.parseInt(split[2]);
+
+ return new ChunkPos(x, z);
+ } catch (NumberFormatException ex) {
+ return null;
+ }
+ }
+ // Paper end
+
@Nullable
public synchronized DataInputStream getChunkDataInputStream(ChunkPos pos) throws IOException {
int i = this.getOffset(pos);
@@ -0,0 +0,0 @@ public class RegionFile implements AutoCloseable {
((java.nio.Buffer) bytebuffer).flip(); // CraftBukkit - decompile error
if (bytebuffer.remaining() < 5) {
RegionFile.LOGGER.error("Chunk {} header is truncated: expected {} but read {}", pos, l, bytebuffer.remaining());
+ // Paper start - recalculate header on regionfile corruption
+ if (this.canRecalcHeader) {
+ this.recalculateHeader();
+ return this.getChunkDataInputStream(pos);
+ }
+ // Paper end - recalculate header on regionfile corruption
return null;
} else {
int i1 = bytebuffer.getInt();
@@ -0,0 +0,0 @@ public class RegionFile implements AutoCloseable {
if (i1 == 0) {
RegionFile.LOGGER.warn("Chunk {} is allocated, but stream is missing", pos);
+ // Paper start - recalculate header on regionfile corruption
+ if (this.canRecalcHeader) {
+ this.recalculateHeader();
+ return this.getChunkDataInputStream(pos);
+ }
+ // Paper end - recalculate header on regionfile corruption
return null;
} else {
int j1 = i1 - 1;
@@ -0,0 +0,0 @@ public class RegionFile implements AutoCloseable {
if (RegionFile.isExternalStreamChunk(b0)) {
if (j1 != 0) {
RegionFile.LOGGER.warn("Chunk has both internal and external streams");
+ // Paper start - recalculate header on regionfile corruption
+ if (this.canRecalcHeader) {
+ this.recalculateHeader();
+ return this.getChunkDataInputStream(pos);
+ }
+ // Paper end - recalculate header on regionfile corruption
}
- return this.createExternalChunkInputStream(pos, RegionFile.getExternalChunkVersion(b0));
+ // Paper start - recalculate header on regionfile corruption
+ final DataInputStream ret = this.createExternalChunkInputStream(pos, RegionFile.getExternalChunkVersion(b0));
+ if (ret == null && this.canRecalcHeader) {
+ this.recalculateHeader();
+ return this.getChunkDataInputStream(pos);
+ }
+ return ret;
+ // Paper end - recalculate header on regionfile corruption
} else if (j1 > bytebuffer.remaining()) {
RegionFile.LOGGER.error("Chunk {} stream is truncated: expected {} but read {}", pos, j1, bytebuffer.remaining());
+ // Paper start - recalculate header on regionfile corruption
+ if (this.canRecalcHeader) {
+ this.recalculateHeader();
+ return this.getChunkDataInputStream(pos);
+ }
+ // Paper end - recalculate header on regionfile corruption
return null;
} else if (j1 < 0) {
RegionFile.LOGGER.error("Declared size {} of chunk {} is negative", i1, pos);
+ // Paper start - recalculate header on regionfile corruption
+ if (this.canRecalcHeader) {
+ this.recalculateHeader();
+ return this.getChunkDataInputStream(pos);
+ }
+ // Paper end - recalculate header on regionfile corruption
return null;
} else {
- return this.createChunkInputStream(pos, b0, RegionFile.createStream(bytebuffer, j1));
+ // Paper start - recalculate header on regionfile corruption
+ final DataInputStream ret = this.createChunkInputStream(pos, b0, RegionFile.createStream(bytebuffer, j1));
+ if (ret == null && this.canRecalcHeader) {
+ this.recalculateHeader();
+ return this.getChunkDataInputStream(pos);
+ }
+ return ret;
+ // Paper end - recalculate header on regionfile corruption
}
}
}
@@ -0,0 +0,0 @@ public class RegionFile implements AutoCloseable {
}
private ByteBuffer createExternalStub() {
+ // Paper start - add version param
+ return this.createExternalStub(this.version);
+ }
+ private ByteBuffer createExternalStub(RegionFileVersion version) {
+ // Paper end - add version param
ByteBuffer bytebuffer = ByteBuffer.allocate(5);
bytebuffer.putInt(1);
- bytebuffer.put((byte) (this.version.getId() | 128));
+ bytebuffer.put((byte) (version.getId() | 128)); // Paper - replace with version param
((java.nio.Buffer) bytebuffer).flip(); // CraftBukkit - decompile error
return bytebuffer;
}
diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java
+++ b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java
@@ -0,0 +0,0 @@ public class RegionFileStorage implements AutoCloseable {
private final File folder;
private final boolean sync;
+ private final boolean isChunkData; // Paper
+
RegionFileStorage(File directory, boolean dsync) {
+ // Paper start - add isChunkData param
+ this(directory, dsync, false);
+ }
+ RegionFileStorage(File directory, boolean dsync, boolean isChunkData) {
+ this.isChunkData = isChunkData;
+ // Paper end - add isChunkData param
this.folder = directory;
this.sync = dsync;
}
@@ -0,0 +0,0 @@ public class RegionFileStorage implements AutoCloseable {
File file = this.folder;
int j = chunkcoordintpair.getRegionX();
- File file1 = new File(file, "r." + j + "." + chunkcoordintpair.getRegionZ() + ".mca");
+ File file1 = new File(file, "r." + j + "." + chunkcoordintpair.getRegionZ() + ".mca"); // Paper - diff on change
if (existingOnly && !file1.exists()) return null; // CraftBukkit
- RegionFile regionfile1 = new RegionFile(file1, this.folder, this.sync);
+ RegionFile regionfile1 = new RegionFile(file1, this.folder, this.sync, this.isChunkData); // Paper - allow for chunk regionfiles to regen header
this.regionCache.putAndMoveToFirst(i, regionfile1);
// Paper start
@@ -0,0 +0,0 @@ public class RegionFileStorage implements AutoCloseable {
if (regionfile == null) {
return null;
}
+ // Paper start - Add regionfile parameter
+ return this.read(pos, regionfile);
+ }
+ public CompoundTag read(ChunkPos pos, RegionFile regionfile) throws IOException {
+ // We add the regionfile parameter to avoid the potential deadlock (on fileLock) if we went back to obtain a regionfile
+ // if we decide to re-read
+ // Paper end
// CraftBukkit end
try { // Paper
DataInputStream datainputstream = regionfile.getChunkDataInputStream(pos);
@@ -0,0 +0,0 @@ public class RegionFileStorage implements AutoCloseable {
try {
if (datainputstream != null) {
nbttagcompound = NbtIo.read((DataInput) datainputstream);
+ // Paper start - recover from corrupt regionfile header
+ if (this.isChunkData) {
+ ChunkPos chunkPos = ChunkSerializer.getChunkCoordinate(nbttagcompound);
+ if (!chunkPos.equals(pos)) {
+ MinecraftServer.LOGGER.error("Attempting to read chunk data at " + pos.toString() + " but got chunk data for " + chunkPos.toString() + " instead! Attempting regionfile recalculation for regionfile " + regionfile.regionFile.getAbsolutePath());
+ regionfile.recalculateHeader();
+ regionfile.fileLock.lock(); // otherwise we will unlock twice and only lock once.
+ return this.read(pos, regionfile);
+ }
+ }
+ // Paper end - recover from corrupt regionfile header
break label43;
}
diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileVersion.java b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileVersion.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileVersion.java
+++ b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileVersion.java
@@ -0,0 +0,0 @@ import java.util.zip.InflaterInputStream;
import javax.annotation.Nullable;
public class RegionFileVersion {
- private static final Int2ObjectMap<RegionFileVersion> VERSIONS = new Int2ObjectOpenHashMap<>();
+ public static final Int2ObjectMap<RegionFileVersion> VERSIONS = new Int2ObjectOpenHashMap<>(); // Paper - public
public static final RegionFileVersion VERSION_GZIP = register(new RegionFileVersion(1, GZIPInputStream::new, GZIPOutputStream::new));
public static final RegionFileVersion VERSION_DEFLATE = register(new RegionFileVersion(2, InflaterInputStream::new, DeflaterOutputStream::new));
public static final RegionFileVersion VERSION_NONE = register(new RegionFileVersion(3, (inputStream) -> {

View File

@ -0,0 +1,52 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Spottedleaf <spottedleaf@spottedleaf.dev>
Date: Sat, 4 Apr 2020 17:00:20 -0700
Subject: [PATCH] Consolidate flush calls for entity tracker packets
Most server packets seem to be sent from here, so try to avoid
expensive flush calls from them.
This change was motivated due to local testing:
- My server spawn has 130 cows in it (for testing a prev. patch)
- Try to let 200 players join spawn
Without this change, I could only get 20 players on before they
all started timing out due to the load put on the Netty I/O threads.
With this change I could get all 200 on at 0ms ping.
(one of the primary issues is that my CPU is kinda trash, and having
4 extra threads at 100% is just too much for it).
So in general this patch should reduce Netty I/O thread load.
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().pop();
}
+ // Paper start - controlled flush for entity tracker packets
+ List<net.minecraft.network.Connection> disabledFlushes = new java.util.ArrayList<>(this.level.players.size());
+ for (ServerPlayer player : this.level.players) {
+ net.minecraft.server.network.ServerGamePacketListenerImpl connection = player.connection;
+ if (connection != null) {
+ connection.connection.disableAutomaticFlush();
+ disabledFlushes.add(connection.connection);
+ }
+ }
+ try { // Paper end - controlled flush for entity tracker packets
this.chunkMap.tick();
+ // Paper start - controlled flush for entity tracker packets
+ } finally {
+ for (net.minecraft.network.Connection networkManager : disabledFlushes) {
+ networkManager.enableAutomaticFlush();
+ }
+ }
+ // Paper end - controlled flush for entity tracker packets
}
private void getFullChunk(long pos, Consumer<LevelChunk> chunkConsumer) {

View File

@ -17,7 +17,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ protected long updateCount; // Paper - correctly handle recursion + protected long updateCount; // Paper - correctly handle recursion
protected void updateFutures(ChunkMap chunkStorage, Executor executor) { protected void updateFutures(ChunkMap chunkStorage, Executor executor) {
ensureTickThread("Async ticket level update"); // Paper io.papermc.paper.util.TickThread.ensureTickThread("Async ticket level update"); // Paper
+ long updateCount = ++this.updateCount; // Paper - correctly handle recursion + long updateCount = ++this.updateCount; // Paper - correctly handle recursion
ChunkStatus chunkstatus = ChunkHolder.getStatus(this.oldTicketLevel); ChunkStatus chunkstatus = ChunkHolder.getStatus(this.oldTicketLevel);
ChunkStatus chunkstatus1 = ChunkHolder.getStatus(this.ticketLevel); ChunkStatus chunkstatus1 = ChunkHolder.getStatus(this.ticketLevel);

View File

@ -0,0 +1,343 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Spottedleaf <Spottedleaf@users.noreply.github.com>
Date: Thu, 11 Mar 2021 20:05:44 -0800
Subject: [PATCH] Custom table implementation for blockstate state lookups
Testing some redstone intensive machines showed to bring about a 10%
improvement.
diff --git a/src/main/java/io/papermc/paper/util/table/ZeroCollidingReferenceStateTable.java b/src/main/java/io/papermc/paper/util/table/ZeroCollidingReferenceStateTable.java
new file mode 100644
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
--- /dev/null
+++ b/src/main/java/io/papermc/paper/util/table/ZeroCollidingReferenceStateTable.java
@@ -0,0 +0,0 @@
+package io.papermc.paper.util.table;
+
+import com.google.common.collect.Table;
+import net.minecraft.world.level.block.state.StateHolder;
+import net.minecraft.world.level.block.state.properties.Property;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+public final class ZeroCollidingReferenceStateTable {
+
+ // upper 32 bits: starting index
+ // lower 32 bits: bitset for contained ids
+ protected final long[] this_index_table;
+ protected final Comparable<?>[] this_table;
+ protected final StateHolder<?, ?> this_state;
+
+ protected long[] index_table;
+ protected StateHolder<?, ?>[][] value_table;
+
+ public ZeroCollidingReferenceStateTable(final StateHolder<?, ?> state, final Map<Property<?>, Comparable<?>> this_map) {
+ this.this_state = state;
+ this.this_index_table = this.create_table(this_map.keySet());
+
+ int max_id = -1;
+ for (final Property<?> property : this_map.keySet()) {
+ final int id = lookup_vindex(property, this.this_index_table);
+ if (id > max_id) {
+ max_id = id;
+ }
+ }
+
+ this.this_table = new Comparable[max_id + 1];
+ for (final Map.Entry<Property<?>, Comparable<?>> entry : this_map.entrySet()) {
+ this.this_table[lookup_vindex(entry.getKey(), this.this_index_table)] = entry.getValue();
+ }
+ }
+
+ public void loadInTable(final Table<Property<?>, Comparable<?>, StateHolder<?, ?>> table,
+ final Map<Property<?>, Comparable<?>> this_map) {
+ final Set<Property<?>> combined = new HashSet<>(table.rowKeySet());
+ combined.addAll(this_map.keySet());
+
+ this.index_table = this.create_table(combined);
+
+ int max_id = -1;
+ for (final Property<?> property : combined) {
+ final int id = lookup_vindex(property, this.index_table);
+ if (id > max_id) {
+ max_id = id;
+ }
+ }
+
+ this.value_table = new StateHolder[max_id + 1][];
+
+ final Map<Property<?>, Map<Comparable<?>, StateHolder<?, ?>>> map = table.rowMap();
+ for (final Property<?> property : map.keySet()) {
+ final Map<Comparable<?>, StateHolder<?, ?>> propertyMap = map.get(property);
+
+ final int id = lookup_vindex(property, this.index_table);
+ final StateHolder<?, ?>[] states = this.value_table[id] = new StateHolder[property.getPossibleValues().size()];
+
+ for (final Map.Entry<Comparable<?>, StateHolder<?, ?>> entry : propertyMap.entrySet()) {
+ if (entry.getValue() == null) {
+ // TODO what
+ continue;
+ }
+
+ states[((Property)property).getIdFor(entry.getKey())] = entry.getValue();
+ }
+ }
+
+
+ for (final Map.Entry<Property<?>, Comparable<?>> entry : this_map.entrySet()) {
+ final Property<?> property = entry.getKey();
+ final int index = lookup_vindex(property, this.index_table);
+
+ if (this.value_table[index] == null) {
+ this.value_table[index] = new StateHolder[property.getPossibleValues().size()];
+ }
+
+ this.value_table[index][((Property)property).getIdFor(entry.getValue())] = this.this_state;
+ }
+ }
+
+
+ protected long[] create_table(final Collection<Property<?>> collection) {
+ int max_id = -1;
+ for (final Property<?> property : collection) {
+ final int id = property.getId();
+ if (id > max_id) {
+ max_id = id;
+ }
+ }
+
+ final long[] ret = new long[((max_id + 1) + 31) >>> 5]; // ceil((max_id + 1) / 32)
+
+ for (final Property<?> property : collection) {
+ final int id = property.getId();
+
+ ret[id >>> 5] |= (1L << (id & 31));
+ }
+
+ int total = 0;
+ for (int i = 1, len = ret.length; i < len; ++i) {
+ ret[i] |= (long)(total += Long.bitCount(ret[i - 1] & 0xFFFFFFFFL)) << 32;
+ }
+
+ return ret;
+ }
+
+ public Comparable<?> get(final Property<?> state) {
+ final Comparable<?>[] table = this.this_table;
+ final int index = lookup_vindex(state, this.this_index_table);
+
+ if (index < 0 || index >= table.length) {
+ return null;
+ }
+ return table[index];
+ }
+
+ public StateHolder<?, ?> get(final Property<?> property, final Comparable<?> with) {
+ final int withId = ((Property)property).getIdFor(with);
+ if (withId < 0) {
+ return null;
+ }
+
+ final int index = lookup_vindex(property, this.index_table);
+ final StateHolder<?, ?>[][] table = this.value_table;
+ if (index < 0 || index >= table.length) {
+ return null;
+ }
+
+ final StateHolder<?, ?>[] values = table[index];
+
+ if (withId >= values.length) {
+ return null;
+ }
+
+ return values[withId];
+ }
+
+ protected static int lookup_vindex(final Property<?> property, final long[] index_table) {
+ final int id = property.getId();
+ final long bitset_mask = (1L << (id & 31));
+ final long lower_mask = bitset_mask - 1;
+ final int index = id >>> 5;
+ if (index >= index_table.length) {
+ return -1;
+ }
+ final long index_value = index_table[index];
+ final long contains_check = ((index_value & bitset_mask) - 1) >> (Long.SIZE - 1); // -1L if doesn't contain
+
+ // index = total bits set in lower table values (upper 32 bits of index_value) plus total bits set in lower indices below id
+ // contains_check is 0 if the bitset had id set, else it's -1: so index is unaffected if contains_check == 0,
+ // otherwise it comes out as -1.
+ return (int)(((index_value >>> 32) + Long.bitCount(index_value & lower_mask)) | contains_check);
+ }
+}
diff --git a/src/main/java/net/minecraft/world/level/block/state/StateHolder.java b/src/main/java/net/minecraft/world/level/block/state/StateHolder.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/world/level/block/state/StateHolder.java
+++ b/src/main/java/net/minecraft/world/level/block/state/StateHolder.java
@@ -0,0 +0,0 @@ public abstract class StateHolder<O, S> {
private final ImmutableMap<Property<?>, Comparable<?>> values;
private Table<Property<?>, Comparable<?>, S> neighbours;
protected final MapCodec<S> propertiesCodec;
+ protected final io.papermc.paper.util.table.ZeroCollidingReferenceStateTable optimisedTable; // Paper - optimise state lookup
protected StateHolder(O owner, ImmutableMap<Property<?>, Comparable<?>> entries, MapCodec<S> codec) {
this.owner = owner;
this.values = entries;
this.propertiesCodec = codec;
+ this.optimisedTable = new io.papermc.paper.util.table.ZeroCollidingReferenceStateTable(this, entries); // Paper - optimise state lookup
}
public <T extends Comparable<T>> S cycle(Property<T> property) {
@@ -0,0 +0,0 @@ public abstract class StateHolder<O, S> {
}
public <T extends Comparable<T>> boolean hasProperty(Property<T> property) {
- return this.values.containsKey(property);
+ return this.optimisedTable.get(property) != null; // Paper - optimise state lookup
}
public <T extends Comparable<T>> T getValue(Property<T> property) {
- Comparable<?> comparable = this.values.get(property);
+ Comparable<?> comparable = this.optimisedTable.get(property); // Paper - optimise state lookup
if (comparable == null) {
throw new IllegalArgumentException("Cannot get property " + property + " as it does not exist in " + this.owner);
} else {
@@ -0,0 +0,0 @@ public abstract class StateHolder<O, S> {
}
public <T extends Comparable<T>> Optional<T> getOptionalValue(Property<T> property) {
- Comparable<?> comparable = this.values.get(property);
+ Comparable<?> comparable = this.optimisedTable.get(property); // Paper - optimise state lookup
return comparable == null ? Optional.empty() : Optional.of(property.getValueClass().cast(comparable));
}
public <T extends Comparable<T>, V extends T> S setValue(Property<T> property, V value) {
- Comparable<?> comparable = this.values.get(property);
- if (comparable == null) {
- throw new IllegalArgumentException("Cannot set property " + property + " as it does not exist in " + this.owner);
- } else if (comparable == value) {
- return (S)this;
- } else {
- S object = this.neighbours.get(property, value);
- if (object == null) {
- throw new IllegalArgumentException("Cannot set property " + property + " to " + value + " on " + this.owner + ", it is not an allowed value");
- } else {
- return object;
- }
+ // Paper start - optimise state lookup
+ final S ret = (S)this.optimisedTable.get(property, value);
+ if (ret == null) {
+ throw new IllegalArgumentException("Cannot set property " + property + " to " + value + " on " + this.owner + ", it is not an allowed value");
}
+ return ret;
+ // Paper end - optimise state lookup
}
public void populateNeighbours(Map<Map<Property<?>, Comparable<?>>, S> states) {
@@ -0,0 +0,0 @@ public abstract class StateHolder<O, S> {
}
}
- this.neighbours = (Table<Property<?>, Comparable<?>, S>)(table.isEmpty() ? table : ArrayTable.create(table));
+ this.neighbours = (Table<Property<?>, Comparable<?>, S>)(table.isEmpty() ? table : ArrayTable.create(table)); this.optimisedTable.loadInTable((Table)this.neighbours, this.values); // Paper - optimise state lookup
}
}
diff --git a/src/main/java/net/minecraft/world/level/block/state/properties/BooleanProperty.java b/src/main/java/net/minecraft/world/level/block/state/properties/BooleanProperty.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/world/level/block/state/properties/BooleanProperty.java
+++ b/src/main/java/net/minecraft/world/level/block/state/properties/BooleanProperty.java
@@ -0,0 +0,0 @@ import java.util.Optional;
public class BooleanProperty extends Property<Boolean> {
private final ImmutableSet<Boolean> values = ImmutableSet.of(true, false);
+ // Paper start - optimise iblockdata state lookup
+ @Override
+ public final int getIdFor(final Boolean value) {
+ return value.booleanValue() ? 1 : 0;
+ }
+ // Paper end - optimise iblockdata state lookup
+
protected BooleanProperty(String name) {
super(name, Boolean.class);
}
diff --git a/src/main/java/net/minecraft/world/level/block/state/properties/EnumProperty.java b/src/main/java/net/minecraft/world/level/block/state/properties/EnumProperty.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/world/level/block/state/properties/EnumProperty.java
+++ b/src/main/java/net/minecraft/world/level/block/state/properties/EnumProperty.java
@@ -0,0 +0,0 @@ public class EnumProperty<T extends Enum<T> & StringRepresentable> extends Prope
private final ImmutableSet<T> values;
private final Map<String, T> names = Maps.newHashMap();
+ // Paper start - optimise iblockdata state lookup
+ private int[] idLookupTable;
+
+ @Override
+ public final int getIdFor(final T value) {
+ return this.idLookupTable[value.ordinal()];
+ }
+ // Paper end - optimise iblockdata state lookup
+
protected EnumProperty(String name, Class<T> type, Collection<T> values) {
super(name, type);
this.values = ImmutableSet.copyOf(values);
@@ -0,0 +0,0 @@ public class EnumProperty<T extends Enum<T> & StringRepresentable> extends Prope
this.names.put(string, enum_);
}
+ // Paper start - optimise iblockdata state lookup
+ int id = 0;
+ this.idLookupTable = new int[type.getEnumConstants().length];
+ java.util.Arrays.fill(this.idLookupTable, -1);
+ for (final T value : this.getPossibleValues()) {
+ this.idLookupTable[value.ordinal()] = id++;
+ }
+ // Paper end - optimise iblockdata state lookup
}
@Override
diff --git a/src/main/java/net/minecraft/world/level/block/state/properties/IntegerProperty.java b/src/main/java/net/minecraft/world/level/block/state/properties/IntegerProperty.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/world/level/block/state/properties/IntegerProperty.java
+++ b/src/main/java/net/minecraft/world/level/block/state/properties/IntegerProperty.java
@@ -0,0 +0,0 @@ public class IntegerProperty extends Property<Integer> {
public final int min;
public final int max;
+ // Paper start - optimise iblockdata state lookup
+ @Override
+ public final int getIdFor(final Integer value) {
+ final int val = value.intValue();
+ final int ret = val - this.min;
+
+ return ret | ((this.max - ret) >> 31);
+ }
+ // Paper end - optimise iblockdata state lookup
+
protected IntegerProperty(String name, int min, int max) {
super(name, Integer.class);
this.min = min;
diff --git a/src/main/java/net/minecraft/world/level/block/state/properties/Property.java b/src/main/java/net/minecraft/world/level/block/state/properties/Property.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/world/level/block/state/properties/Property.java
+++ b/src/main/java/net/minecraft/world/level/block/state/properties/Property.java
@@ -0,0 +0,0 @@ public abstract class Property<T extends Comparable<T>> {
}, this::getName);
private final Codec<Property.Value<T>> valueCodec = this.codec.xmap(this::value, Property.Value::value);
+ // Paper start - optimise iblockdata state lookup
+ private static final java.util.concurrent.atomic.AtomicInteger ID_GENERATOR = new java.util.concurrent.atomic.AtomicInteger();
+ private final int id = ID_GENERATOR.getAndIncrement();
+
+ public final int getId() {
+ return this.id;
+ }
+
+ public abstract int getIdFor(final T value);
+ // Paper end - optimise state lookup
+
protected Property(String name, Class<T> type) {
this.clazz = type;
this.name = name;

View File

@ -0,0 +1,296 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Spottedleaf <spottedleaf@spottedleaf.dev>
Date: Thu, 26 Mar 2020 21:59:32 -0700
Subject: [PATCH] Detail more information in watchdog dumps
- Dump position, world, velocity, and uuid for currently ticking entities
- Dump player name, player uuid, position, and world for packet handling
diff --git a/src/main/java/net/minecraft/network/Connection.java b/src/main/java/net/minecraft/network/Connection.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/network/Connection.java
+++ b/src/main/java/net/minecraft/network/Connection.java
@@ -0,0 +0,0 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
}
if (this.packetListener instanceof ServerGamePacketListenerImpl) {
+ // Paper start - detailed watchdog information
+ net.minecraft.network.protocol.PacketUtils.packetProcessing.push(this.packetListener);
+ try {
+ // Paper end - detailed watchdog information
((ServerGamePacketListenerImpl) this.packetListener).tick();
+ } finally { // Paper start - detailed watchdog information
+ net.minecraft.network.protocol.PacketUtils.packetProcessing.pop();
+ } // Paper start - detailed watchdog information
}
if (!this.isConnected() && !this.disconnectionHandled) {
diff --git a/src/main/java/net/minecraft/network/protocol/PacketUtils.java b/src/main/java/net/minecraft/network/protocol/PacketUtils.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/network/protocol/PacketUtils.java
+++ b/src/main/java/net/minecraft/network/protocol/PacketUtils.java
@@ -0,0 +0,0 @@ public class PacketUtils {
private static final Logger LOGGER = LogManager.getLogger();
+ // Paper start - detailed watchdog information
+ public static final java.util.concurrent.ConcurrentLinkedDeque<PacketListener> packetProcessing = new java.util.concurrent.ConcurrentLinkedDeque<>();
+ static final java.util.concurrent.atomic.AtomicLong totalMainThreadPacketsProcessed = new java.util.concurrent.atomic.AtomicLong();
+
+ public static long getTotalProcessedPackets() {
+ return totalMainThreadPacketsProcessed.get();
+ }
+
+ public static java.util.List<PacketListener> getCurrentPacketProcessors() {
+ java.util.List<PacketListener> ret = new java.util.ArrayList<>(4);
+ for (PacketListener listener : packetProcessing) {
+ ret.add(listener);
+ }
+
+ return ret;
+ }
+ // Paper end - detailed watchdog information
+
public PacketUtils() {}
public static <T extends PacketListener> void ensureRunningOnSameThread(Packet<T> packet, T listener, ServerLevel world) throws RunningOnDifferentThreadException {
@@ -0,0 +0,0 @@ public class PacketUtils {
if (!engine.isSameThread()) {
Timing timing = MinecraftTimings.getPacketTiming(packet); // Paper - timings
engine.execute(() -> {
+ packetProcessing.push(listener); // Paper - detailed watchdog information
+ try { // Paper - detailed watchdog information
if (MinecraftServer.getServer().hasStopped() || (listener instanceof ServerGamePacketListenerImpl && ((ServerGamePacketListenerImpl) listener).processedDisconnect)) return; // CraftBukkit, MC-142590
if (listener.getConnection().isConnected()) {
try (Timing ignored = timing.startTiming()) { // Paper - timings
@@ -0,0 +0,0 @@ public class PacketUtils {
} else {
PacketUtils.LOGGER.debug("Ignoring packet due to disconnection: {}", packet);
}
+ // Paper start - detailed watchdog information
+ } finally {
+ totalMainThreadPacketsProcessed.getAndIncrement();
+ packetProcessing.pop();
+ }
+ // Paper end - detailed watchdog information
});
throw RunningOnDifferentThreadException.RUNNING_ON_DIFFERENT_THREAD;
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 Level implements WorldGenLevel {
}
+ // Paper start - log detailed entity tick information
+ // TODO replace with varhandle
+ static final java.util.concurrent.atomic.AtomicReference<Entity> currentlyTickingEntity = new java.util.concurrent.atomic.AtomicReference<>();
+
+ public static List<Entity> getCurrentlyTickingEntities() {
+ Entity ticking = currentlyTickingEntity.get();
+ List<Entity> ret = java.util.Arrays.asList(ticking == null ? new Entity[0] : new Entity[] { ticking });
+
+ return ret;
+ }
+ // Paper end - log detailed entity tick information
+
public void tickNonPassenger(Entity entity) {
+ // Paper start - log detailed entity tick information
+ io.papermc.paper.util.TickThread.ensureTickThread("Cannot tick an entity off-main");
+ try {
+ if (currentlyTickingEntity.get() == null) {
+ currentlyTickingEntity.lazySet(entity);
+ }
+ // Paper end - log detailed entity tick information
++TimingHistory.entityTicks; // Paper - timings
// Spigot start
co.aikar.timings.Timing timer; // Paper
@@ -0,0 +0,0 @@ public class ServerLevel extends Level implements WorldGenLevel {
this.tickPassenger(entity, entity1);
}
// } finally { timer.stopTiming(); } // Paper - timings - move up
-
+ // Paper start - log detailed entity tick information
+ } finally {
+ if (currentlyTickingEntity.get() == entity) {
+ currentlyTickingEntity.lazySet(null);
+ }
+ }
+ // Paper end - log detailed entity tick information
}
private void tickPassenger(Entity vehicle, Entity passenger) {
diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/world/entity/Entity.java
+++ b/src/main/java/net/minecraft/world/entity/Entity.java
@@ -0,0 +0,0 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, n
return this.onGround;
}
+ // Paper start - detailed watchdog information
+ public final Object posLock = new Object(); // Paper - log detailed entity tick information
+
+ private Vec3 moveVector;
+ private double moveStartX;
+ private double moveStartY;
+ private double moveStartZ;
+
+ public final Vec3 getMoveVector() {
+ return this.moveVector;
+ }
+
+ public final double getMoveStartX() {
+ return this.moveStartX;
+ }
+
+ public final double getMoveStartY() {
+ return this.moveStartY;
+ }
+
+ public final double getMoveStartZ() {
+ return this.moveStartZ;
+ }
+ // Paper end - detailed watchdog information
+
public void move(MoverType movementType, Vec3 movement) {
+ // Paper start - detailed watchdog information
+ io.papermc.paper.util.TickThread.ensureTickThread("Cannot move an entity off-main");
+ synchronized (this.posLock) {
+ this.moveStartX = this.getX();
+ this.moveStartY = this.getY();
+ this.moveStartZ = this.getZ();
+ this.moveVector = movement;
+ }
+ try {
+ // Paper end - detailed watchdog information
if (this.noPhysics) {
this.setPos(this.getX() + movement.x, this.getY() + movement.y, this.getZ() + movement.z);
} else {
@@ -0,0 +0,0 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, n
this.level.getProfiler().pop();
}
}
+ // Paper start - detailed watchdog information
+ } finally {
+ synchronized (this.posLock) { // Paper
+ this.moveVector = null;
+ } // Paper
+ }
+ // Paper end - detailed watchdog information
}
protected void tryCheckInsideBlocks() {
@@ -0,0 +0,0 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, n
}
public void setDeltaMovement(Vec3 velocity) {
+ synchronized (this.posLock) { // Paper
this.deltaMovement = velocity;
+ } // Paper
}
public void setDeltaMovement(double x, double y, double z) {
@@ -0,0 +0,0 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, n
}
// Paper end - fix MC-4
if (this.position.x != x || this.position.y != y || this.position.z != z) {
+ synchronized (this.posLock) { // Paper
this.position = new Vec3(x, y, z);
+ } // Paper
int i = Mth.floor(x);
int j = Mth.floor(y);
int k = Mth.floor(z);
diff --git a/src/main/java/org/spigotmc/WatchdogThread.java b/src/main/java/org/spigotmc/WatchdogThread.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/org/spigotmc/WatchdogThread.java
+++ b/src/main/java/org/spigotmc/WatchdogThread.java
@@ -0,0 +0,0 @@ public class WatchdogThread extends Thread
private volatile long lastTick;
private volatile boolean stopping;
+ // Paper start - log detailed tick information
+ private void dumpEntity(net.minecraft.world.entity.Entity entity) {
+ Logger log = Bukkit.getServer().getLogger();
+ double posX, posY, posZ;
+ net.minecraft.world.phys.Vec3 mot;
+ double moveStartX, moveStartY, moveStartZ;
+ net.minecraft.world.phys.Vec3 moveVec;
+ synchronized (entity.posLock) {
+ posX = entity.getX();
+ posY = entity.getY();
+ posZ = entity.getZ();
+ mot = entity.getDeltaMovement();
+ moveStartX = entity.getMoveStartX();
+ moveStartY = entity.getMoveStartY();
+ moveStartZ = entity.getMoveStartZ();
+ moveVec = entity.getMoveVector();
+ }
+
+ String entityType = entity.getMinecraftKey().toString();
+ java.util.UUID entityUUID = entity.getUUID();
+ net.minecraft.world.level.Level world = entity.level;
+
+ log.log(Level.SEVERE, "Ticking entity: " + entityType + ", entity class: " + entity.getClass().getName());
+ log.log(Level.SEVERE, "Entity status: removed: " + entity.isRemoved() + ", valid: " + entity.valid + ", alive: " + entity.isAlive() + ", is passenger: " + entity.isPassenger());
+ log.log(Level.SEVERE, "Entity UUID: " + entityUUID);
+ log.log(Level.SEVERE, "Position: world: '" + (world == null ? "unknown world?" : world.getWorld().getName()) + "' at location (" + posX + ", " + posY + ", " + posZ + ")");
+ log.log(Level.SEVERE, "Velocity: " + (mot == null ? "unknown velocity" : mot.toString()) + " (in blocks per tick)");
+ log.log(Level.SEVERE, "Entity AABB: " + entity.getBoundingBox());
+ if (moveVec != null) {
+ log.log(Level.SEVERE, "Move call information: ");
+ log.log(Level.SEVERE, "Start position: (" + moveStartX + ", " + moveStartY + ", " + moveStartZ + ")");
+ log.log(Level.SEVERE, "Move vector: " + moveVec.toString());
+ }
+ }
+
+ private void dumpTickingInfo() {
+ Logger log = Bukkit.getServer().getLogger();
+
+ // ticking entities
+ for (net.minecraft.world.entity.Entity entity : net.minecraft.server.level.ServerLevel.getCurrentlyTickingEntities()) {
+ this.dumpEntity(entity);
+ net.minecraft.world.entity.Entity vehicle = entity.getVehicle();
+ if (vehicle != null) {
+ log.log(Level.SEVERE, "Detailing vehicle for above entity:");
+ this.dumpEntity(vehicle);
+ }
+ }
+
+ // packet processors
+ for (net.minecraft.network.PacketListener packetListener : net.minecraft.network.protocol.PacketUtils.getCurrentPacketProcessors()) {
+ if (packetListener instanceof net.minecraft.server.network.ServerGamePacketListenerImpl) {
+ net.minecraft.server.level.ServerPlayer player = ((net.minecraft.server.network.ServerGamePacketListenerImpl)packetListener).player;
+ long totalPackets = net.minecraft.network.protocol.PacketUtils.getTotalProcessedPackets();
+ if (player == null) {
+ log.log(Level.SEVERE, "Handling packet for player connection or ticking player connection (null player): " + packetListener);
+ log.log(Level.SEVERE, "Total packets processed on the main thread for all players: " + totalPackets);
+ } else {
+ this.dumpEntity(player);
+ net.minecraft.world.entity.Entity vehicle = player.getVehicle();
+ if (vehicle != null) {
+ log.log(Level.SEVERE, "Detailing vehicle for above entity:");
+ this.dumpEntity(vehicle);
+ }
+ log.log(Level.SEVERE, "Total packets processed on the main thread for all players: " + totalPackets);
+ }
+ } else {
+ log.log(Level.SEVERE, "Handling packet for connection: " + packetListener);
+ }
+ }
+ }
+ // Paper end - log detailed tick information
+
private WatchdogThread(long timeoutTime, boolean restart)
{
super( "Paper Watchdog Thread" );
@@ -0,0 +0,0 @@ public class WatchdogThread extends Thread
log.log( Level.SEVERE, "------------------------------" );
log.log( Level.SEVERE, "Server thread dump (Look for plugins here before reporting to Paper!):" ); // Paper
com.destroystokyo.paper.io.chunk.ChunkTaskManager.dumpAllChunkLoadInfo(); // Paper
+ this.dumpTickingInfo(); // Paper - log detailed tick information
WatchdogThread.dumpThread( ManagementFactory.getThreadMXBean().getThreadInfo( server.serverThread.getId(), Integer.MAX_VALUE ), log );
log.log( Level.SEVERE, "------------------------------" );
//

View File

@ -0,0 +1,40 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Spottedleaf <spottedleaf@spottedleaf.dev>
Date: Sat, 18 Jul 2020 16:03:57 -0700
Subject: [PATCH] Distance manager tick timings
Recently this has been taking up more time, so add a timings to
really figure out how much.
diff --git a/src/main/java/co/aikar/timings/MinecraftTimings.java b/src/main/java/co/aikar/timings/MinecraftTimings.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/co/aikar/timings/MinecraftTimings.java
+++ b/src/main/java/co/aikar/timings/MinecraftTimings.java
@@ -0,0 +0,0 @@ public final class MinecraftTimings {
public static final Timing antiXrayUpdateTimer = Timings.ofSafe("anti-xray - update");
public static final Timing antiXrayObfuscateTimer = Timings.ofSafe("anti-xray - obfuscate");
+ public static final Timing distanceManagerTick = Timings.ofSafe("Distance Manager Tick"); // Paper - add timings for distance manager
public static final Timing midTickChunkTasks = Timings.ofSafe("Mid Tick Chunk Tasks");
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 {
public boolean runDistanceManagerUpdates() {
if (distanceManager.delayDistanceManagerTick) return false; // Paper - Chunk priority
if (this.chunkMap.unloadingPlayerChunk) { net.minecraft.server.MinecraftServer.LOGGER.fatal("Cannot tick distance manager while unloading playerchunks", new Throwable()); throw new IllegalStateException("Cannot tick distance manager while unloading playerchunks"); } // Paper
+ co.aikar.timings.MinecraftTimings.distanceManagerTick.startTiming(); try { // Paper - add timings for distance manager
boolean flag = this.distanceManager.runAllUpdates(this.chunkMap);
boolean flag1 = this.chunkMap.promoteChunkMap();
@@ -0,0 +0,0 @@ public class ServerChunkCache extends ChunkSource {
this.clearCache();
return true;
}
+ } finally { co.aikar.timings.MinecraftTimings.distanceManagerTick.stopTiming(); } // Paper - add timings for distance manager
}
// Paper start - helper

View File

@ -13,12 +13,12 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java +++ b/src/main/java/net/minecraft/server/level/ChunkMap.java
@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider @@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
} }
// Paper end // Tuiniy end
+ boolean unloadingPlayerChunk = false; // Paper - do not allow ticket level changes while unloading chunks + boolean unloadingPlayerChunk = false; // Paper - do not allow ticket level changes while unloading chunks
private final java.util.concurrent.ExecutorService lightThread; // Paper
public ChunkMap(ServerLevel world, LevelStorageSource.LevelStorageAccess session, DataFixer dataFixer, StructureManager structureManager, Executor executor, BlockableEventLoop<Runnable> mainThreadExecutor, LightChunkGetter chunkProvider, ChunkGenerator chunkGenerator, ChunkProgressListener worldGenerationProgressListener, ChunkStatusUpdateListener chunkStatusChangeListener, Supplier<DimensionDataStorage> persistentStateManagerFactory, int viewDistance, boolean dsync) { public ChunkMap(ServerLevel world, LevelStorageSource.LevelStorageAccess session, DataFixer dataFixer, StructureManager structureManager, Executor executor, BlockableEventLoop<Runnable> mainThreadExecutor, LightChunkGetter chunkProvider, ChunkGenerator chunkGenerator, ChunkProgressListener worldGenerationProgressListener, ChunkStatusUpdateListener chunkStatusChangeListener, Supplier<DimensionDataStorage> persistentStateManagerFactory, int viewDistance, boolean dsync) {
super(new File(session.getDimensionPath(world.dimension()), "region"), dataFixer, dsync); super(new File(session.getDimensionPath(world.dimension()), "region"), dataFixer, dsync);
this.visibleChunkMap = this.updatingChunkMap.clone();
@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider @@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
@Nullable @Nullable
@ -37,13 +37,13 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ this.unloadingPlayerChunk = true; + this.unloadingPlayerChunk = true;
+ try { + try {
+ // Paper end - do not allow ticket level changes while unloading chunks + // Paper end - do not allow ticket level changes while unloading chunks
if (this.pendingUnloads.remove(pos, holder) && ichunkaccess != null) { // Paper start
if (ichunkaccess instanceof LevelChunk) { boolean removed;
((LevelChunk) ichunkaccess).setLoaded(false); if ((removed = this.pendingUnloads.remove(pos, holder)) && ichunkaccess != null) {
@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider @@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
this.lightEngine.tryScheduleUpdate(); this.regionManagers.get(index).removeChunk(holder.pos.x, holder.pos.z);
this.progressListener.onStatusChange(ichunkaccess.getPos(), (ChunkStatus) null);
} }
} // Paper end
+ } finally { this.unloadingPlayerChunk = unloadingBefore; } // Paper - do not allow ticket level changes while unloading chunks + } finally { this.unloadingPlayerChunk = unloadingBefore; } // Paper - do not allow ticket level changes while unloading chunks
} }

View File

@ -0,0 +1,140 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Spottedleaf <Spottedleaf@users.noreply.github.com>
Date: Sun, 21 Mar 2021 11:22:10 -0700
Subject: [PATCH] Do not copy visible chunks
For servers with a lot of chunk holders, copying for each
tickDistanceManager call can take up quite a bit in
the function. I saw approximately 1/3rd of the function
on the copy.
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 @@ public class PaperCommand extends Command {
int ticking = 0;
int entityTicking = 0;
- for (ChunkHolder chunk : world.getChunkSource().chunkMap.updatingChunkMap.values()) {
+ for (ChunkHolder chunk : world.getChunkSource().chunkMap.updatingChunks.getUpdatingMap().values()) { // Paper - change updating chunks map
if (chunk.getFullChunkUnchecked() == null) {
continue;
}
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 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
private static final int MIN_VIEW_DISTANCE = 3;
public static final int MAX_VIEW_DISTANCE = 33;
public static final int MAX_CHUNK_DISTANCE = 33 + ChunkStatus.maxDistance();
+ // Paper start - Don't copy
+ public final com.destroystokyo.paper.util.map.QueuedChangesMapLong2Object<ChunkHolder> updatingChunks = new com.destroystokyo.paper.util.map.QueuedChangesMapLong2Object<>();
+ // Paper end - Don't copy
public static final int FORCED_TICKET_LEVEL = 31;
public final Long2ObjectLinkedOpenHashMap<ChunkHolder> updatingChunkMap = new Long2ObjectLinkedOpenHashMap();
public volatile Long2ObjectLinkedOpenHashMap<ChunkHolder> visibleChunkMap;
@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
@Nullable
public ChunkHolder getUpdatingChunkIfPresent(long pos) {
- return (ChunkHolder) this.updatingChunkMap.get(pos);
+ return this.updatingChunks.getUpdating(pos); // Paper - Don't copy
}
@Nullable
public ChunkHolder getVisibleChunkIfPresent(long pos) {
- return (ChunkHolder) this.visibleChunkMap.get(pos);
+ // Paper start - Don't copy
+ if (Thread.currentThread() == this.level.thread) {
+ return this.updatingChunks.getVisible(pos);
+ }
+ return this.updatingChunks.getVisibleAsync(pos);
+ // Paper end - Don't copy
}
protected IntSupplier getChunkQueueLevel(long pos) {
@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
// Paper end
}
- this.updatingChunkMap.put(pos, holder);
+ this.updatingChunks.queueUpdate(pos, holder); // Paper - Don't copy
this.modified = true;
}
@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
while (longiterator.hasNext()) { // Spigot
long j = longiterator.nextLong();
longiterator.remove(); // Spigot
- ChunkHolder playerchunk = (ChunkHolder) this.updatingChunkMap.remove(j);
+ ChunkHolder playerchunk = this.updatingChunks.queueRemove(j); // Paper - Don't copy
if (playerchunk != null) {
this.pendingUnloads.put(j, playerchunk);
@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
if (!this.modified) {
return false;
} else {
- this.visibleChunkMap = this.updatingChunkMap.clone();
+ // Paper start - Don't copy
+ synchronized (this.updatingChunks) {
+ this.updatingChunks.performUpdates();
+ }
+ // Paper end - Don't copy
+
this.modified = false;
return true;
}
@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
}
public int size() {
- return this.visibleChunkMap.size();
+ return this.updatingChunks.getVisibleMap().size(); // Paper - Don't copy
}
protected DistanceManager getDistanceManager() {
diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
@@ -0,0 +0,0 @@ public class CraftWorld extends CraftRegionAccessor implements World {
@Override
public int getTileEntityCount() {
// We don't use the full world tile entity list, so we must iterate chunks
- Long2ObjectLinkedOpenHashMap<ChunkHolder> chunks = world.getChunkSource().chunkMap.visibleChunkMap;
+ Long2ObjectLinkedOpenHashMap<ChunkHolder> chunks = world.getChunkSource().chunkMap.updatingChunks.getVisibleMap(); // Paper - change updating chunks map
int size = 0;
for (ChunkHolder playerchunk : chunks.values()) {
net.minecraft.world.level.chunk.LevelChunk chunk = playerchunk.getTickingChunk();
@@ -0,0 +0,0 @@ public class CraftWorld extends CraftRegionAccessor implements World {
public int getChunkCount() {
int ret = 0;
- for (ChunkHolder chunkHolder : world.getChunkSource().chunkMap.visibleChunkMap.values()) {
+ for (ChunkHolder chunkHolder : world.getChunkSource().chunkMap.updatingChunks.getVisibleMap().values()) { // Paper - change updating chunks map
if (chunkHolder.getTickingChunk() != null) {
++ret;
}
@@ -0,0 +0,0 @@ public class CraftWorld extends CraftRegionAccessor implements World {
@Override
public Chunk[] getLoadedChunks() {
- Long2ObjectLinkedOpenHashMap<ChunkHolder> chunks = this.world.getChunkSource().chunkMap.visibleChunkMap;
+ // Paper start
+ if (Thread.currentThread() != world.getLevel().thread) {
+ // Paper start - change updating chunks map
+ Long2ObjectLinkedOpenHashMap<ChunkHolder> chunks;
+ synchronized (world.getChunkSource().chunkMap.updatingChunks) {
+ chunks = world.getChunkSource().chunkMap.updatingChunks.getVisibleMap().clone();
+ }
+ return chunks.values().stream().map(ChunkHolder::getFullChunk).filter(Objects::nonNull).map(net.minecraft.world.level.chunk.LevelChunk::getBukkitChunk).toArray(Chunk[]::new);
+ // Paper end - change updating chunks map
+ }
+ // Paper end
+ Long2ObjectLinkedOpenHashMap<ChunkHolder> chunks = world.getChunkSource().chunkMap.updatingChunks.getVisibleMap(); // Paper - change updating chunks map
return chunks.values().stream().map(ChunkHolder::getFullChunk).filter(Objects::nonNull).map(net.minecraft.world.level.chunk.LevelChunk::getBukkitChunk).toArray(Chunk[]::new);
}

View File

@ -0,0 +1,22 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Spottedleaf <Spottedleaf@users.noreply.github.com>
Date: Sun, 7 Mar 2021 13:15:04 -0800
Subject: [PATCH] Do not run raytrace logic for AIR
Saves approx. 5% for the raytrace call, as most (expensive)
raytracing tends to go through air and returning early is an
easy win. The remaining problems with this function
are mostly with the block getting itself.
diff --git a/src/main/java/net/minecraft/world/level/BlockGetter.java b/src/main/java/net/minecraft/world/level/BlockGetter.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/world/level/BlockGetter.java
+++ b/src/main/java/net/minecraft/world/level/BlockGetter.java
@@ -0,0 +0,0 @@ public interface BlockGetter extends LevelHeightAccessor {
return BlockHitResult.miss(raytrace1.getTo(), Direction.getNearest(vec3d.x, vec3d.y, vec3d.z), new BlockPos(raytrace1.getTo()));
}
// Paper end
+ if (iblockdata.isAir()) return null; // Paper - optimise air cases
FluidState fluid = iblockdata.getFluidState(); // Paper - don't need to go to world state again
Vec3 vec3d = raytrace1.getFrom();
Vec3 vec3d1 = raytrace1.getTo();

View File

@ -0,0 +1,20 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Spottedleaf <spottedleaf@spottedleaf.dev>
Date: Fri, 28 Aug 2020 12:33:47 -0700
Subject: [PATCH] Don't lookup fluid state when raytracing
Just use the iblockdata already retrieved, removes a getType call.
diff --git a/src/main/java/net/minecraft/world/level/BlockGetter.java b/src/main/java/net/minecraft/world/level/BlockGetter.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/world/level/BlockGetter.java
+++ b/src/main/java/net/minecraft/world/level/BlockGetter.java
@@ -0,0 +0,0 @@ public interface BlockGetter extends LevelHeightAccessor {
return BlockHitResult.miss(raytrace1.getTo(), Direction.getNearest(vec3d.x, vec3d.y, vec3d.z), new BlockPos(raytrace1.getTo()));
}
// Paper end
- FluidState fluid = this.getFluidState(blockposition);
+ FluidState fluid = iblockdata.getFluidState(); // Paper - don't need to go to world state again
Vec3 vec3d = raytrace1.getFrom();
Vec3 vec3d1 = raytrace1.getTo();
VoxelShape voxelshape = raytrace1.getBlockShape(iblockdata, this, blockposition);

View File

@ -0,0 +1,21 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Spottedleaf <Spottedleaf@users.noreply.github.com>
Date: Sun, 11 Apr 2021 02:58:48 -0700
Subject: [PATCH] Don't read neighbour chunk data off disk when converting
chunks
Lighting is purged on update anyways, so let's not add more
into the conversion process
diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java
+++ b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java
@@ -0,0 +0,0 @@ public class ChunkStorage implements AutoCloseable {
// CraftBukkit start
private boolean check(ServerChunkCache cps, int x, int z) throws IOException {
+ if (true) return true; // Paper - this isn't even needed anymore, light is purged updating to 1.14+, why are we holding up the conversion process reading chunk data off disk - return true, we need to set light populated to true so the converter recognizes the chunk as being "full"
ChunkPos pos = new ChunkPos(x, z);
if (cps != null) {
//com.google.common.base.Preconditions.checkState(org.bukkit.Bukkit.isPrimaryThread(), "primary thread"); // Paper - this function is now MT-Safe

View File

@ -0,0 +1,168 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Spottedleaf <spottedleaf@spottedleaf.dev>
Date: Mon, 6 Apr 2020 04:20:44 -0700
Subject: [PATCH] Execute chunk tasks mid-tick
This will help the server load chunks if tick times are high.
diff --git a/src/main/java/co/aikar/timings/MinecraftTimings.java b/src/main/java/co/aikar/timings/MinecraftTimings.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/co/aikar/timings/MinecraftTimings.java
+++ b/src/main/java/co/aikar/timings/MinecraftTimings.java
@@ -0,0 +0,0 @@ public final class MinecraftTimings {
public static final Timing antiXrayUpdateTimer = Timings.ofSafe("anti-xray - update");
public static final Timing antiXrayObfuscateTimer = Timings.ofSafe("anti-xray - obfuscate");
+ public static final Timing midTickChunkTasks = Timings.ofSafe("Mid Tick Chunk Tasks");
+
private static final Map<Class<?>, String> taskNameCache = new MapMaker().weakKeys().makeMap();
private MinecraftTimings() {}
diff --git a/src/main/java/com/destroystokyo/paper/server/ticklist/PaperTickList.java b/src/main/java/com/destroystokyo/paper/server/ticklist/PaperTickList.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/com/destroystokyo/paper/server/ticklist/PaperTickList.java
+++ b/src/main/java/com/destroystokyo/paper/server/ticklist/PaperTickList.java
@@ -0,0 +0,0 @@ public final class PaperTickList<T> extends ServerTickList<T> { // extend to avo
toTick.tickState = STATE_SCHEDULED;
this.addToNotTickingReady(toTick);
}
+ MinecraftServer.getServer().executeMidTickTasks(); // Paper - exec chunk tasks during world tick
} catch (final Throwable thr) {
// start copy from TickListServer // TODO check on update
CrashReport crashreport = CrashReport.forThrowable(thr, "Exception while ticking");
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
return s0;
}
+ // Paper start - execute chunk tasks mid tick
+ static final long CHUNK_TASK_QUEUE_BACKOFF_MIN_TIME = 25L * 1000L; // 25us
+ static final long MAX_CHUNK_EXEC_TIME = 1000L; // 1us
+
+ static final long TASK_EXECUTION_FAILURE_BACKOFF = 5L * 1000L; // 5us
+
+ private static long lastMidTickExecute;
+ private static long lastMidTickExecuteFailure;
+
+ private boolean tickMidTickTasks() {
+ // give all worlds a fair chance at by targetting them all.
+ // if we execute too many tasks, that's fine - we have logic to correctly handle overuse of allocated time.
+ boolean executed = false;
+ for (ServerLevel world : this.getAllLevels()) {
+ long currTime = System.nanoTime();
+ if (currTime - world.lastMidTickExecuteFailure <= TASK_EXECUTION_FAILURE_BACKOFF) {
+ continue;
+ }
+ if (!world.getChunkSource().pollTask()) {
+ // we need to back off if this fails
+ world.lastMidTickExecuteFailure = currTime;
+ } else {
+ executed = true;
+ }
+ }
+
+ return executed;
+ }
+
+ public final void executeMidTickTasks() {
+ org.spigotmc.AsyncCatcher.catchOp("mid tick chunk task execution");
+ long startTime = System.nanoTime();
+ if ((startTime - lastMidTickExecute) <= CHUNK_TASK_QUEUE_BACKOFF_MIN_TIME || (startTime - lastMidTickExecuteFailure) <= TASK_EXECUTION_FAILURE_BACKOFF) {
+ // it's shown to be bad to constantly hit the queue (chunk loads slow to a crawl), even if no tasks are executed.
+ // so, backoff to prevent this
+ return;
+ }
+
+ co.aikar.timings.MinecraftTimings.midTickChunkTasks.startTiming();
+ try {
+ for (;;) {
+ boolean moreTasks = this.tickMidTickTasks();
+ long currTime = System.nanoTime();
+ long diff = currTime - startTime;
+
+ if (!moreTasks || diff >= MAX_CHUNK_EXEC_TIME) {
+ if (!moreTasks) {
+ lastMidTickExecuteFailure = currTime;
+ }
+
+ // note: negative values reduce the time
+ long overuse = diff - MAX_CHUNK_EXEC_TIME;
+ if (overuse >= (10L * 1000L * 1000L)) { // 10ms
+ // make sure something like a GC or dumb plugin doesn't screw us over...
+ overuse = 10L * 1000L * 1000L; // 10ms
+ }
+
+ double overuseCount = (double)overuse/(double)MAX_CHUNK_EXEC_TIME;
+ long extraSleep = (long)Math.round(overuseCount*CHUNK_TASK_QUEUE_BACKOFF_MIN_TIME);
+
+ lastMidTickExecute = currTime + extraSleep;
+ return;
+ }
+ }
+ } finally {
+ co.aikar.timings.MinecraftTimings.midTickChunkTasks.stopTiming();
+ }
+ }
+ // Paper end - execute chunk tasks mid tick
+
public MinecraftServer(OptionSet options, DataPackConfig datapackconfiguration, Thread thread, RegistryAccess.RegistryHolder iregistrycustom_dimension, LevelStorageSource.LevelStorageAccess convertable_conversionsession, WorldData savedata, PackRepository resourcepackrepository, Proxy proxy, DataFixer datafixer, ServerResources datapackresources, @Nullable MinecraftSessionService minecraftsessionservice, @Nullable GameProfileRepository gameprofilerepository, @Nullable GameProfileCache usercache, ChunkProgressListenerFactory worldloadlistenerfactory) {
super("Server");
SERVER = this; // Paper - better singleton
@@ -0,0 +0,0 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
private boolean pollTaskInternal() {
if (super.pollTask()) {
+ this.executeMidTickTasks(); // Paper - execute chunk tasks mid tick
return true;
} else {
if (this.haveTime()) {
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 {
Collections.shuffle(shuffled);
iterator = shuffled.iterator();
}
+ int chunksTicked = 0; // Paper
try { while (iterator.hasNext()) {
LevelChunk chunk = iterator.next();
ChunkHolder playerchunk = chunk.playerChunk;
@@ -0,0 +0,0 @@ public class ServerChunkCache extends ChunkSource {
this.level.tickChunk(chunk, k);
// this.level.timings.doTickTiles.stopTiming(); // Spigot // Paper
}
+ if ((chunksTicked++ & 1) == 0) net.minecraft.server.MinecraftServer.getServer().executeMidTickTasks(); // Paper
}
} // Paper start - optimise chunk tick iteration
} finally {
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 Level implements WorldGenLevel {
final Int2ObjectMap<EnderDragonPart> dragonParts;
private final StructureFeatureManager structureFeatureManager;
private final boolean tickTime;
-
+ // Paper start - execute chunk tasks mid tick
+ public long lastMidTickExecuteFailure;
+ // Paper end - execute chunk tasks mid tick
// CraftBukkit start
private int tickPosition;
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 {
public <T extends Entity> void guardEntityTick(Consumer<T> tickConsumer, T entity) {
try {
tickConsumer.accept(entity);
+ MinecraftServer.getServer().executeMidTickTasks(); // Paper - execute chunk tasks mid tick
} catch (Throwable throwable) {
if (throwable instanceof ThreadDeath) throw throwable; // Paper
// Paper start - Prevent tile entity and entity crashes

View File

@ -0,0 +1,197 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Spottedleaf <Spottedleaf@users.noreply.github.com>
Date: Sat, 19 Jun 2021 10:54:52 -0700
Subject: [PATCH] Fix Codec log spam
Mojang did NOT add dataconverters for world gen configurations
that they CHANGED. So, the codec fails to parse old data.
This fixes two instances:
- IntProvider is new and Mojang did not account for old data.
Thankfully, only ColumnPlace needed to be special cased.
- TreeConfiguration had changes. Thankfully, they were
only renames for one value and thankfully defaults could
be provided for two new values (WITHOUT changing behavior).
diff --git a/src/main/java/net/minecraft/server/MCUtil.java b/src/main/java/net/minecraft/server/MCUtil.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/server/MCUtil.java
+++ b/src/main/java/net/minecraft/server/MCUtil.java
@@ -0,0 +0,0 @@ public final class MCUtil {
public static int getTicketLevelFor(net.minecraft.world.level.chunk.ChunkStatus status) {
return net.minecraft.server.level.ChunkMap.MAX_VIEW_DISTANCE + net.minecraft.world.level.chunk.ChunkStatus.getDistance(status);
}
+
+ public static <A> com.mojang.serialization.MapCodec<A> fieldWithFallbacks(com.mojang.serialization.Codec<A> codec, String name, String ...fallback) {
+ return com.mojang.serialization.MapCodec.of(
+ new com.mojang.serialization.codecs.FieldEncoder<>(name, codec),
+ new FieldFallbackDecoder<>(name, java.util.Arrays.asList(fallback), codec),
+ () -> "FieldFallback[" + name + ": " + codec.toString() + "]"
+ );
+ }
+
+ // This is likely a common occurrence, sadly
+ public static final class FieldFallbackDecoder<A> extends com.mojang.serialization.MapDecoder.Implementation<A> {
+ protected final String name;
+ protected final List<String> fallback;
+ private final com.mojang.serialization.Decoder<A> elementCodec;
+
+ public FieldFallbackDecoder(final String name, final List<String> fallback, final com.mojang.serialization.Decoder<A> elementCodec) {
+ this.name = name;
+ this.fallback = fallback;
+ this.elementCodec = elementCodec;
+ }
+
+ @Override
+ public <T> com.mojang.serialization.DataResult<A> decode(final com.mojang.serialization.DynamicOps<T> ops, final com.mojang.serialization.MapLike<T> input) {
+ T value = input.get(name);
+ if (value == null) {
+ for (String fall : fallback) {
+ value = input.get(fall);
+ if (value != null) {
+ break;
+ }
+ }
+ if (value == null) {
+ return com.mojang.serialization.DataResult.error("No key " + name + " in " + input);
+ }
+ }
+ return elementCodec.parse(ops, value);
+ }
+
+ @Override
+ public <T> java.util.stream.Stream<T> keys(final com.mojang.serialization.DynamicOps<T> ops) {
+ return java.util.stream.Stream.of(ops.createString(name));
+ }
+
+ @Override
+ public boolean equals(final Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ final FieldFallbackDecoder<?> that = (FieldFallbackDecoder<?>)o;
+ return java.util.Objects.equals(name, that.name) && java.util.Objects.equals(elementCodec, that.elementCodec)
+ && java.util.Objects.equals(fallback, that.fallback);
+ }
+
+ @Override
+ public int hashCode() {
+ return java.util.Objects.hash(name, fallback, elementCodec);
+ }
+
+ @Override
+ public String toString() {
+ return "FieldDecoder[" + name + ": " + elementCodec + ']';
+ }
+ }
}
diff --git a/src/main/java/net/minecraft/util/valueproviders/IntProvider.java b/src/main/java/net/minecraft/util/valueproviders/IntProvider.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/util/valueproviders/IntProvider.java
+++ b/src/main/java/net/minecraft/util/valueproviders/IntProvider.java
@@ -0,0 +0,0 @@ import net.minecraft.core.Registry;
public abstract class IntProvider {
private static final Codec<Either<Integer, IntProvider>> CONSTANT_OR_DISPATCH_CODEC = Codec.either(Codec.INT, Registry.INT_PROVIDER_TYPES.dispatch(IntProvider::getType, IntProviderType::codec));
- public static final Codec<IntProvider> CODEC = CONSTANT_OR_DISPATCH_CODEC.xmap((either) -> {
+ public static final Codec<IntProvider> CODEC_REAL = CONSTANT_OR_DISPATCH_CODEC.xmap((either) -> { // Paper - used by CODEC below
return either.map(ConstantInt::of, (intProvider) -> {
return intProvider;
});
}, (intProvider) -> {
return intProvider.getType() == IntProviderType.CONSTANT ? Either.left(((ConstantInt)intProvider).getValue()) : Either.right(intProvider);
});
+ // Paper start
+ public static final Codec<IntProvider> CODEC = new Codec<>() {
+ @Override
+ public <T> DataResult<com.mojang.datafixers.util.Pair<IntProvider, T>> decode(com.mojang.serialization.DynamicOps<T> ops, T input) {
+ /*
+ UniformInt:
+ count -> { (old format)
+ base, spread
+ } -> {UniformInt} { (new format & type)
+ base, base + spread
+ } */
+
+
+ if (ops.get(input, "base").result().isPresent() && ops.get(input, "spread").result().isPresent()) {
+ // detected old format
+ int base = ops.getNumberValue(ops.get(input, "base").result().get()).result().get().intValue();
+ int spread = ops.getNumberValue(ops.get(input, "spread").result().get()).result().get().intValue();
+ return DataResult.success(new com.mojang.datafixers.util.Pair<>(UniformInt.of(base, base + spread), input));
+ }
+
+ // not old format, forward to real codec
+ return CODEC_REAL.decode(ops, input);
+ }
+
+ @Override
+ public <T> DataResult<T> encode(IntProvider input, com.mojang.serialization.DynamicOps<T> ops, T prefix) {
+ // forward to real codec
+ return CODEC_REAL.encode(input, ops, prefix);
+ }
+ };
+ // Paper end
public static final Codec<IntProvider> NON_NEGATIVE_CODEC = codec(0, Integer.MAX_VALUE);
public static final Codec<IntProvider> POSITIVE_CODEC = codec(1, Integer.MAX_VALUE);
diff --git a/src/main/java/net/minecraft/world/level/levelgen/feature/blockplacers/ColumnPlacer.java b/src/main/java/net/minecraft/world/level/levelgen/feature/blockplacers/ColumnPlacer.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/world/level/levelgen/feature/blockplacers/ColumnPlacer.java
+++ b/src/main/java/net/minecraft/world/level/levelgen/feature/blockplacers/ColumnPlacer.java
@@ -0,0 +0,0 @@ import net.minecraft.world.level.LevelAccessor;
import net.minecraft.world.level.block.state.BlockState;
public class ColumnPlacer extends BlockPlacer {
+ // Paper start
public static final Codec<ColumnPlacer> CODEC = RecordCodecBuilder.create((instance) -> {
- return instance.group(IntProvider.NON_NEGATIVE_CODEC.fieldOf("size").forGetter((columnPlacer) -> {
- return columnPlacer.size;
- })).apply(instance, ColumnPlacer::new);
+ return instance.group(
+ IntProvider.NON_NEGATIVE_CODEC.optionalFieldOf("size").forGetter((columnPlacer) -> {
+ return java.util.Optional.of(columnPlacer.size);
+ }),
+ Codec.INT.optionalFieldOf("min_size").forGetter((columnPlacer) -> {
+ return java.util.Optional.empty();
+ }),
+ Codec.INT.optionalFieldOf("extra_size").forGetter((columnPlacer) -> {
+ return java.util.Optional.empty();
+ })
+ ).apply(instance, ColumnPlacer::new);
});
+ public ColumnPlacer(java.util.Optional<IntProvider> size, java.util.Optional<Integer> minSize, java.util.Optional<Integer> extraSize) {
+ if (size.isPresent()) {
+ this.size = size.get();
+ } else {
+ this.size = net.minecraft.util.valueproviders.BiasedToBottomInt.of(minSize.get().intValue(), minSize.get().intValue() + extraSize.get().intValue());
+ }
+ }
+ // Paper end
private final IntProvider size;
public ColumnPlacer(IntProvider size) {
diff --git a/src/main/java/net/minecraft/world/level/levelgen/feature/configurations/TreeConfiguration.java b/src/main/java/net/minecraft/world/level/levelgen/feature/configurations/TreeConfiguration.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/world/level/levelgen/feature/configurations/TreeConfiguration.java
+++ b/src/main/java/net/minecraft/world/level/levelgen/feature/configurations/TreeConfiguration.java
@@ -0,0 +0,0 @@ public class TreeConfiguration implements FeatureConfiguration {
return treeConfiguration.trunkProvider;
}), TrunkPlacer.CODEC.fieldOf("trunk_placer").forGetter((treeConfiguration) -> {
return treeConfiguration.trunkPlacer;
- }), BlockStateProvider.CODEC.fieldOf("foliage_provider").forGetter((treeConfiguration) -> {
+ }), net.minecraft.server.MCUtil.fieldWithFallbacks(BlockStateProvider.CODEC, "foliage_provider", "leaves_provider").forGetter((treeConfiguration) -> { // Paper - provide fallback for rename
return treeConfiguration.foliageProvider;
- }), BlockStateProvider.CODEC.fieldOf("sapling_provider").forGetter((treeConfiguration) -> {
+ }), BlockStateProvider.CODEC.optionalFieldOf("sapling_provider", new SimpleStateProvider(Blocks.OAK_SAPLING.defaultBlockState())).forGetter((treeConfiguration) -> { // Paper - provide default - it looks like for now this is OK because it's just used to check canSurvive. Same check happens in 1.16.5 for the default we provide - so it should retain behavior...
return treeConfiguration.saplingProvider;
}), FoliagePlacer.CODEC.fieldOf("foliage_placer").forGetter((treeConfiguration) -> {
return treeConfiguration.foliagePlacer;
- }), BlockStateProvider.CODEC.fieldOf("dirt_provider").forGetter((treeConfiguration) -> {
+ }), BlockStateProvider.CODEC.optionalFieldOf("dirt_provider", new SimpleStateProvider(Blocks.DIRT.defaultBlockState())).forGetter((treeConfiguration) -> { // Paper - provide defaults, old data DOES NOT have this key (thankfully ALL OLD DATA used DIRT)
return treeConfiguration.dirtProvider;
}), FeatureSize.CODEC.fieldOf("minimum_size").forGetter((treeConfiguration) -> {
return treeConfiguration.minimumSize;

View File

@ -36,15 +36,17 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
--- a/src/main/java/com/destroystokyo/paper/PaperConfig.java --- a/src/main/java/com/destroystokyo/paper/PaperConfig.java
+++ b/src/main/java/com/destroystokyo/paper/PaperConfig.java +++ b/src/main/java/com/destroystokyo/paper/PaperConfig.java
@@ -0,0 +0,0 @@ public class PaperConfig { @@ -0,0 +0,0 @@ public class PaperConfig {
private static void consoleHasAllPermissions() {
consoleHasAllPermissions = getBoolean("settings.console-has-all-permissions", consoleHasAllPermissions); consoleHasAllPermissions = getBoolean("settings.console-has-all-permissions", consoleHasAllPermissions);
} }
+
+ public static boolean allowPistonDuplication; + public static boolean allowPistonDuplication;
+ private static void allowPistonDuplication() { + private static void allowPistonDuplication() {
+ config.set("settings.unsupported-settings.allow-piston-duplication-readme", "This setting controls if player should be able to use TNT duplication, but this also allows duplicating carpet, rails and potentially other items"); + config.set("settings.unsupported-settings.allow-piston-duplication-readme", "This setting controls if player should be able to use TNT duplication, but this also allows duplicating carpet, rails and potentially other items");
+ allowPistonDuplication = getBoolean("settings.unsupported-settings.allow-piston-duplication", config.getBoolean("settings.unsupported-settings.allow-tnt-duplication", false)); + allowPistonDuplication = getBoolean("settings.unsupported-settings.allow-piston-duplication", config.getBoolean("settings.unsupported-settings.allow-tnt-duplication", false));
+ set("settings.unsupported-settings.allow-tnt-duplication", null); + set("settings.unsupported-settings.allow-tnt-duplication", null);
+ } + }
+
} }
diff --git a/src/main/java/net/minecraft/world/level/block/piston/PistonBaseBlock.java b/src/main/java/net/minecraft/world/level/block/piston/PistonBaseBlock.java diff --git a/src/main/java/net/minecraft/world/level/block/piston/PistonBaseBlock.java b/src/main/java/net/minecraft/world/level/block/piston/PistonBaseBlock.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644

View File

@ -112,6 +112,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
this.lastOverloadWarning = this.nextTickTime; this.lastOverloadWarning = this.nextTickTime;
} }
++MinecraftServer.currentTickLong; // Paper
- if ( tickCount++ % MinecraftServer.SAMPLE_INTERVAL == 0 ) - if ( tickCount++ % MinecraftServer.SAMPLE_INTERVAL == 0 )
+ if ( ++MinecraftServer.currentTick % MinecraftServer.SAMPLE_INTERVAL == 0 ) + if ( ++MinecraftServer.currentTick % MinecraftServer.SAMPLE_INTERVAL == 0 )
{ {

File diff suppressed because it is too large Load Diff

View File

@ -244,13 +244,6 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ // Paper start + // Paper start
+ private boolean loadCallbackScheduled = false; + private boolean loadCallbackScheduled = false;
+ private boolean unloadCallbackScheduled = false; + private boolean unloadCallbackScheduled = false;
+ static void ensureTickThread(final String reason) {
+ // TODO REMOVE
+ if (!org.bukkit.Bukkit.isPrimaryThread()) {
+ MinecraftServer.LOGGER.fatal("Thread " + Thread.currentThread().getName() + " failed main thread check: " + reason, new Throwable());
+ throw new IllegalStateException(reason);
+ }
+ }
+ // Paper end + // Paper end
+ +
private void demoteFullChunk(ChunkMap playerchunkmap, ChunkHolder.FullChunkStatus playerchunk_state) { private void demoteFullChunk(ChunkMap playerchunkmap, ChunkHolder.FullChunkStatus playerchunk_state) {
@ -259,7 +252,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
} }
protected void updateFutures(ChunkMap chunkStorage, Executor executor) { protected void updateFutures(ChunkMap chunkStorage, Executor executor) {
+ ensureTickThread("Async ticket level update"); // Paper + io.papermc.paper.util.TickThread.ensureTickThread("Async ticket level update"); // Paper
ChunkStatus chunkstatus = ChunkHolder.getStatus(this.oldTicketLevel); ChunkStatus chunkstatus = ChunkHolder.getStatus(this.oldTicketLevel);
ChunkStatus chunkstatus1 = ChunkHolder.getStatus(this.ticketLevel); ChunkStatus chunkstatus1 = ChunkHolder.getStatus(this.ticketLevel);
boolean flag = this.oldTicketLevel <= ChunkMap.MAX_CHUNK_DISTANCE; boolean flag = this.oldTicketLevel <= ChunkMap.MAX_CHUNK_DISTANCE;
@ -267,7 +260,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
// ChunkUnloadEvent: Called before the chunk is unloaded: isChunkLoaded is still true and chunk can still be modified by plugins. // ChunkUnloadEvent: Called before the chunk is unloaded: isChunkLoaded is still true and chunk can still be modified by plugins.
if (playerchunk_state.isOrAfter(ChunkHolder.FullChunkStatus.BORDER) && !playerchunk_state1.isOrAfter(ChunkHolder.FullChunkStatus.BORDER)) { if (playerchunk_state.isOrAfter(ChunkHolder.FullChunkStatus.BORDER) && !playerchunk_state1.isOrAfter(ChunkHolder.FullChunkStatus.BORDER)) {
this.getFutureIfPresentUnchecked(ChunkStatus.FULL).thenAccept((either) -> { this.getFutureIfPresentUnchecked(ChunkStatus.FULL).thenAccept((either) -> {
+ ensureTickThread("Async full status chunk future completion"); // Paper + io.papermc.paper.util.TickThread.ensureTickThread("Async full status chunk future completion"); // Paper
LevelChunk chunk = (LevelChunk)either.left().orElse(null); LevelChunk chunk = (LevelChunk)either.left().orElse(null);
- if (chunk != null) { - if (chunk != null) {
+ if (chunk != null && chunk.wasLoadCallbackInvoked() && ChunkHolder.this.ticketLevel > 33) { // Paper - only invoke unload if load was called + if (chunk != null && chunk.wasLoadCallbackInvoked() && ChunkHolder.this.ticketLevel > 33) { // Paper - only invoke unload if load was called
@ -291,7 +284,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
this.scheduleFullChunkPromotion(chunkStorage, this.fullChunkFuture, executor, ChunkHolder.FullChunkStatus.BORDER); this.scheduleFullChunkPromotion(chunkStorage, this.fullChunkFuture, executor, ChunkHolder.FullChunkStatus.BORDER);
// Paper start - cache ticking ready status // Paper start - cache ticking ready status
this.fullChunkFuture.thenAccept(either -> { this.fullChunkFuture.thenAccept(either -> {
+ ensureTickThread("Async full chunk future completion"); // Paper + io.papermc.paper.util.TickThread.ensureTickThread("Async full chunk future completion"); // Paper
final Optional<LevelChunk> left = either.left(); final Optional<LevelChunk> left = either.left();
if (left.isPresent() && ChunkHolder.this.fullChunkCreateCount == expectCreateCount) { if (left.isPresent() && ChunkHolder.this.fullChunkCreateCount == expectCreateCount) {
// note: Here is a very good place to add callbacks to logic waiting on this. // note: Here is a very good place to add callbacks to logic waiting on this.
@ -306,7 +299,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
this.scheduleFullChunkPromotion(chunkStorage, this.tickingChunkFuture, executor, ChunkHolder.FullChunkStatus.TICKING); this.scheduleFullChunkPromotion(chunkStorage, this.tickingChunkFuture, executor, ChunkHolder.FullChunkStatus.TICKING);
// Paper start - cache ticking ready status // Paper start - cache ticking ready status
this.tickingChunkFuture.thenAccept(either -> { this.tickingChunkFuture.thenAccept(either -> {
+ ensureTickThread("Async full chunk future completion"); // Paper + io.papermc.paper.util.TickThread.ensureTickThread("Async full chunk future completion"); // Paper
either.ifLeft(chunk -> { either.ifLeft(chunk -> {
// note: Here is a very good place to add callbacks to logic waiting on this. // note: Here is a very good place to add callbacks to logic waiting on this.
ChunkHolder.this.isTickingReady = true; ChunkHolder.this.isTickingReady = true;
@ -314,10 +307,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
this.scheduleFullChunkPromotion(chunkStorage, this.entityTickingChunkFuture, executor, ChunkHolder.FullChunkStatus.ENTITY_TICKING); this.scheduleFullChunkPromotion(chunkStorage, this.entityTickingChunkFuture, executor, ChunkHolder.FullChunkStatus.ENTITY_TICKING);
// Paper start - cache ticking ready status // Paper start - cache ticking ready status
this.entityTickingChunkFuture.thenAccept(either -> { this.entityTickingChunkFuture.thenAccept(either -> {
+ ensureTickThread("Async full chunk future completion"); // Paper + io.papermc.paper.util.TickThread.ensureTickThread("Async full chunk future completion"); // Paper
either.ifLeft(chunk -> { either.ifLeft(chunk -> {
ChunkHolder.this.isEntityTickingReady = true; ChunkHolder.this.isEntityTickingReady = true;
}); // Paper start - entity ticking chunk set
@@ -0,0 +0,0 @@ public class ChunkHolder { @@ -0,0 +0,0 @@ public class ChunkHolder {
this.demoteFullChunk(chunkStorage, playerchunk_state1); this.demoteFullChunk(chunkStorage, playerchunk_state1);
} }
@ -337,6 +330,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ ioPriority = com.destroystokyo.paper.io.PrioritizedTaskQueue.HIGH_PRIORITY; + ioPriority = com.destroystokyo.paper.io.PrioritizedTaskQueue.HIGH_PRIORITY;
+ } + }
+ chunkMap.level.asyncChunkTaskManager.raisePriority(pos.x, pos.z, ioPriority); + chunkMap.level.asyncChunkTaskManager.raisePriority(pos.x, pos.z, ioPriority);
+ chunkMap.level.getChunkSource().getLightEngine().queue.changePriority(pos.toLong(), this.queueLevel, priority); // Paper // Restore this in chunk priority later?
+ } + }
+ if (currRequestedPriority != newRequestedPriority) { + if (currRequestedPriority != newRequestedPriority) {
+ this.onLevelChange.onLevelChange(this.pos, () -> this.queueLevel, priority, p -> this.queueLevel = p); // use preferred priority + this.onLevelChange.onLevelChange(this.pos, () -> this.queueLevel, priority, p -> this.queueLevel = p); // use preferred priority
@ -349,7 +343,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
// ChunkLoadEvent: Called after the chunk is loaded: isChunkLoaded returns true and chunk is ready to be modified by plugins. // ChunkLoadEvent: Called after the chunk is loaded: isChunkLoaded returns true and chunk is ready to be modified by plugins.
if (!playerchunk_state.isOrAfter(ChunkHolder.FullChunkStatus.BORDER) && playerchunk_state1.isOrAfter(ChunkHolder.FullChunkStatus.BORDER)) { if (!playerchunk_state.isOrAfter(ChunkHolder.FullChunkStatus.BORDER) && playerchunk_state1.isOrAfter(ChunkHolder.FullChunkStatus.BORDER)) {
this.getFutureIfPresentUnchecked(ChunkStatus.FULL).thenAccept((either) -> { this.getFutureIfPresentUnchecked(ChunkStatus.FULL).thenAccept((either) -> {
+ ensureTickThread("Async full status chunk future completion"); // Paper + io.papermc.paper.util.TickThread.ensureTickThread("Async full status chunk future completion"); // Paper
LevelChunk chunk = (LevelChunk)either.left().orElse(null); LevelChunk chunk = (LevelChunk)either.left().orElse(null);
- if (chunk != null) { - if (chunk != null) {
+ if (chunk != null && ChunkHolder.this.oldTicketLevel <= 33 && !chunk.wasLoadCallbackInvoked()) { // Paper - ensure ticket level is set to loaded before calling, as now this can complete with ticket level > 33 + if (chunk != null && ChunkHolder.this.oldTicketLevel <= 33 && !chunk.wasLoadCallbackInvoked()) { // Paper - ensure ticket level is set to loaded before calling, as now this can complete with ticket level > 33
@ -395,31 +389,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
Objects.requireNonNull(mainThreadExecutor); Objects.requireNonNull(mainThreadExecutor);
@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider @@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
this.playerViewDistanceTickMap = new com.destroystokyo.paper.util.misc.PlayerAreaMap(this.pooledLinkedPlayerHashSets, // Paper end - optimise PlayerChunkMap#isOutsideRange
(ServerPlayer player, int rangeX, int rangeZ, int currPosX, int currPosZ, int prevPosX, int prevPosZ,
com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<ServerPlayer> newState) -> {
+ checkHighPriorityChunks(player);
if (newState.size() != 1) {
return;
}
@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
}
ChunkPos chunkPos = new ChunkPos(rangeX, rangeZ);
ChunkMap.this.level.getChunkSource().removeTicketAtLevel(TicketType.PLAYER, chunkPos, 31, chunkPos); // entity ticking level, TODO check on update
- });
+ // Paper start
+ ChunkMap.this.level.getChunkSource().clearPriorityTickets(chunkPos);
+ },
+ (player, prevPos, newPos) -> {
+ player.lastHighPriorityChecked = -1; // reset and recheck
+ checkHighPriorityChunks(player);
+ });
+ // Paper end
this.playerViewDistanceNoTickMap = new com.destroystokyo.paper.util.misc.PlayerAreaMap(this.pooledLinkedPlayerHashSets);
this.playerViewDistanceBroadcastMap = new com.destroystokyo.paper.util.misc.PlayerAreaMap(this.pooledLinkedPlayerHashSets,
(ServerPlayer player, int rangeX, int rangeZ, int currPosX, int currPosZ, int prevPosX, int prevPosZ,
@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
// Paper end - no-tick view distance
} }
+ // Paper start - Chunk Prioritization + // Paper start - Chunk Prioritization
@ -1155,6 +1125,279 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
for (int i = 0; i < this.getInventory().getContainerSize(); ++i) { for (int i = 0; i < this.getInventory().getContainerSize(); ++i) {
ItemStack itemstack = this.getInventory().getItem(i); ItemStack itemstack = this.getInventory().getItem(i);
diff --git a/src/main/java/net/minecraft/server/level/ThreadedLevelLightEngine.java b/src/main/java/net/minecraft/server/level/ThreadedLevelLightEngine.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/server/level/ThreadedLevelLightEngine.java
+++ b/src/main/java/net/minecraft/server/level/ThreadedLevelLightEngine.java
@@ -0,0 +0,0 @@
package net.minecraft.server.level;
import com.mojang.datafixers.util.Pair;
+import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap; // Paper
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import it.unimi.dsi.fastutil.objects.ObjectList;
import it.unimi.dsi.fastutil.objects.ObjectListIterator;
@@ -0,0 +0,0 @@ import net.minecraft.util.thread.ProcessorMailbox;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.LightLayer;
import net.minecraft.world.level.chunk.ChunkAccess;
+import net.minecraft.world.level.chunk.ChunkStatus;
import net.minecraft.world.level.chunk.DataLayer;
import net.minecraft.world.level.chunk.LevelChunkSection;
import net.minecraft.world.level.chunk.LightChunkGetter;
@@ -0,0 +0,0 @@ import org.apache.logging.log4j.Logger;
public class ThreadedLevelLightEngine extends LevelLightEngine implements AutoCloseable {
private static final Logger LOGGER = LogManager.getLogger();
private final ProcessorMailbox<Runnable> taskMailbox;
- private final ObjectList<Pair<ThreadedLevelLightEngine.TaskType, Runnable>> lightTasks = new ObjectArrayList<>();
- private final ChunkMap chunkMap;
+ // Paper start
+ private static final int MAX_PRIORITIES = ChunkMap.MAX_CHUNK_DISTANCE + 2;
+
+ static class ChunkLightQueue {
+ public boolean shouldFastUpdate;
+ java.util.ArrayDeque<Runnable> pre = new java.util.ArrayDeque<Runnable>();
+ java.util.ArrayDeque<Runnable> post = new java.util.ArrayDeque<Runnable>();
+
+ ChunkLightQueue(long chunk) {}
+ }
+
+ static class PendingLightTask {
+ long chunkId;
+ IntSupplier priority;
+ Runnable pre;
+ Runnable post;
+ boolean fastUpdate;
+
+ public PendingLightTask(long chunkId, IntSupplier priority, Runnable pre, Runnable post, boolean fastUpdate) {
+ this.chunkId = chunkId;
+ this.priority = priority;
+ this.pre = pre;
+ this.post = post;
+ this.fastUpdate = fastUpdate;
+ }
+ }
+
+
+ // Retain the chunks priority level for queued light tasks
+ class LightQueue {
+ private int size = 0;
+ private final Long2ObjectLinkedOpenHashMap<ChunkLightQueue>[] buckets = new Long2ObjectLinkedOpenHashMap[MAX_PRIORITIES];
+ private final java.util.concurrent.ConcurrentLinkedQueue<PendingLightTask> pendingTasks = new java.util.concurrent.ConcurrentLinkedQueue<>();
+ private final java.util.concurrent.ConcurrentLinkedQueue<Runnable> priorityChanges = new java.util.concurrent.ConcurrentLinkedQueue<>();
+
+ private LightQueue() {
+ for (int i = 0; i < buckets.length; i++) {
+ buckets[i] = new Long2ObjectLinkedOpenHashMap<>();
+ }
+ }
+
+ public void changePriority(long pair, int currentPriority, int priority) {
+ this.priorityChanges.add(() -> {
+ ChunkLightQueue remove = this.buckets[currentPriority].remove(pair);
+ if (remove != null) {
+ ChunkLightQueue existing = this.buckets[Math.max(1, priority)].put(pair, remove);
+ if (existing != null) {
+ remove.pre.addAll(existing.pre);
+ remove.post.addAll(existing.post);
+ }
+ }
+ });
+ }
+
+ public final void addChunk(long chunkId, IntSupplier priority, Runnable pre, Runnable post) {
+ pendingTasks.add(new PendingLightTask(chunkId, priority, pre, post, true));
+ tryScheduleUpdate();
+ }
+
+ public final void add(long chunkId, IntSupplier priority, ThreadedLevelLightEngine.TaskType type, Runnable run) {
+ pendingTasks.add(new PendingLightTask(chunkId, priority, type == TaskType.PRE_UPDATE ? run : null, type == TaskType.POST_UPDATE ? run : null, false));
+ }
+ public final void add(PendingLightTask update) {
+ int priority = update.priority.getAsInt();
+ ChunkLightQueue lightQueue = this.buckets[priority].computeIfAbsent(update.chunkId, ChunkLightQueue::new);
+
+ if (update.pre != null) {
+ this.size++;
+ lightQueue.pre.add(update.pre);
+ }
+ if (update.post != null) {
+ this.size++;
+ lightQueue.post.add(update.post);
+ }
+ if (update.fastUpdate) {
+ lightQueue.shouldFastUpdate = true;
+ }
+ }
+
+ public final boolean isEmpty() {
+ return this.size == 0 && this.pendingTasks.isEmpty();
+ }
+
+ public final int size() {
+ return this.size;
+ }
+
+ public boolean poll(java.util.List<Runnable> pre, java.util.List<Runnable> post) {
+ PendingLightTask pending;
+ while ((pending = pendingTasks.poll()) != null) {
+ add(pending);
+ }
+ Runnable run;
+ while ((run = priorityChanges.poll()) != null) {
+ run.run();
+ }
+ boolean hasWork = false;
+ Long2ObjectLinkedOpenHashMap<ChunkLightQueue>[] buckets = this.buckets;
+ int priority = 0;
+ while (priority < MAX_PRIORITIES && !isEmpty()) {
+ Long2ObjectLinkedOpenHashMap<ChunkLightQueue> bucket = buckets[priority];
+ if (bucket.isEmpty()) {
+ priority++;
+ if (hasWork) {
+ return true;
+ } else {
+ continue;
+ }
+ }
+ ChunkLightQueue queue = bucket.removeFirst();
+ this.size -= queue.pre.size() + queue.post.size();
+ pre.addAll(queue.pre);
+ post.addAll(queue.post);
+ queue.pre.clear();
+ queue.post.clear();
+ hasWork = true;
+ if (queue.shouldFastUpdate) {
+ return true;
+ }
+ }
+ return hasWork;
+ }
+ }
+
+ final LightQueue queue = new LightQueue();
+ // Paper end
+ private final ChunkMap chunkMap; private final ChunkMap playerChunkMap; // Paper
private final ProcessorHandle<ChunkTaskPriorityQueueSorter.Message<Runnable>> sorterMailbox;
private volatile int taskPerBatch = 5;
private final AtomicBoolean scheduled = new AtomicBoolean();
public ThreadedLevelLightEngine(LightChunkGetter chunkProvider, ChunkMap chunkStorage, boolean hasBlockLight, ProcessorMailbox<Runnable> processor, ProcessorHandle<ChunkTaskPriorityQueueSorter.Message<Runnable>> executor) {
super(chunkProvider, true, hasBlockLight);
- this.chunkMap = chunkStorage;
+ this.chunkMap = chunkStorage; this.playerChunkMap = chunkMap; // Paper
this.sorterMailbox = executor;
this.taskMailbox = processor;
}
@@ -0,0 +0,0 @@ public class ThreadedLevelLightEngine extends LevelLightEngine implements AutoCl
}
private void addTask(int x, int z, IntSupplier completedLevelSupplier, ThreadedLevelLightEngine.TaskType stage, Runnable task) {
- this.sorterMailbox.tell(ChunkTaskPriorityQueueSorter.message(() -> {
- this.lightTasks.add(Pair.of(stage, task));
- if (this.lightTasks.size() >= this.taskPerBatch) {
- this.runUpdate();
- }
-
- }, ChunkPos.asLong(x, z), completedLevelSupplier));
+ // Paper start - replace method
+ this.queue.add(ChunkPos.asLong(x, z), completedLevelSupplier, stage, task);
+ // Paper end
}
@Override
@@ -0,0 +0,0 @@ public class ThreadedLevelLightEngine extends LevelLightEngine implements AutoCl
public CompletableFuture<ChunkAccess> lightChunk(ChunkAccess chunk, boolean excludeBlocks) {
ChunkPos chunkPos = chunk.getPos();
- chunk.setLightCorrect(false);
- this.addTask(chunkPos.x, chunkPos.z, ThreadedLevelLightEngine.TaskType.PRE_UPDATE, Util.name(() -> {
+ // Paper start
+ //ichunkaccess.b(false); // Don't need to disable this
+ long pair = chunkPos.toLong();
+ CompletableFuture<ChunkAccess> future = new CompletableFuture<>();
+ IntSupplier prioritySupplier = playerChunkMap.getChunkQueueLevel(pair);
+ boolean[] skippedPre = {false};
+ this.queue.addChunk(pair, prioritySupplier, Util.name(() -> {
+ // Paper end
LevelChunkSection[] levelChunkSections = chunk.getSections();
for(int i = 0; i < chunk.getSectionsCount(); ++i) {
@@ -0,0 +0,0 @@ public class ThreadedLevelLightEngine extends LevelLightEngine implements AutoCl
}, () -> {
return "lightChunk " + chunkPos + " " + excludeBlocks;
- }));
- return CompletableFuture.supplyAsync(() -> {
+ // Paper start - merge the 2 together
+ }), () -> {
+ this.chunkMap.releaseLightTicket(chunkPos); // Paper - moved from below, we want to call this even when returning early
+ if (skippedPre[0]) return; // Paper - future's already complete
chunk.setLightCorrect(true);
super.retainData(chunkPos, false);
- this.chunkMap.releaseLightTicket(chunkPos);
- return chunk;
- }, (runnable) -> {
- this.addTask(chunkPos.x, chunkPos.z, ThreadedLevelLightEngine.TaskType.POST_UPDATE, runnable);
+ //this.chunkMap.releaseLightTicket(chunkPos); // Paper - moved up
+ future.complete(chunk);
});
+ return future;
+ // Paper end
}
public void tryScheduleUpdate() {
- if ((!this.lightTasks.isEmpty() || super.hasLightWork()) && this.scheduled.compareAndSet(false, true)) {
+ if ((!this.queue.isEmpty() || super.hasLightWork()) && this.scheduled.compareAndSet(false, true)) { // Paper
this.taskMailbox.tell(() -> {
this.runUpdate();
this.scheduled.set(false);
+ tryScheduleUpdate(); // Paper - if we still have work to do, do it!
});
}
}
+ // Paper start - replace impl
+ private final java.util.List<Runnable> pre = new java.util.ArrayList<>();
+ private final java.util.List<Runnable> post = new java.util.ArrayList<>();
private void runUpdate() {
- int i = Math.min(this.lightTasks.size(), this.taskPerBatch);
- ObjectListIterator<Pair<ThreadedLevelLightEngine.TaskType, Runnable>> objectListIterator = this.lightTasks.iterator();
-
- int j;
- for(j = 0; objectListIterator.hasNext() && j < i; ++j) {
- Pair<ThreadedLevelLightEngine.TaskType, Runnable> pair = objectListIterator.next();
- if (pair.getFirst() == ThreadedLevelLightEngine.TaskType.PRE_UPDATE) {
- pair.getSecond().run();
- }
+ if (queue.poll(pre, post)) {
+ pre.forEach(Runnable::run);
+ pre.clear();
+ super.runUpdates(Integer.MAX_VALUE, true, true);
+ post.forEach(Runnable::run);
+ post.clear();
+ } else {
+ // might have level updates to go still
+ super.runUpdates(Integer.MAX_VALUE, true, true);
}
-
- objectListIterator.back(j);
- super.runUpdates(Integer.MAX_VALUE, true, true);
-
- for(int var5 = 0; objectListIterator.hasNext() && var5 < i; ++var5) {
- Pair<ThreadedLevelLightEngine.TaskType, Runnable> pair2 = objectListIterator.next();
- if (pair2.getFirst() == ThreadedLevelLightEngine.TaskType.POST_UPDATE) {
- pair2.getSecond().run();
- }
-
- objectListIterator.remove();
- }
-
+ // Paper end
}
public void setTaskPerBatch(int taskBatchSize) {
diff --git a/src/main/java/net/minecraft/server/level/Ticket.java b/src/main/java/net/minecraft/server/level/Ticket.java diff --git a/src/main/java/net/minecraft/server/level/Ticket.java b/src/main/java/net/minecraft/server/level/Ticket.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/server/level/Ticket.java --- a/src/main/java/net/minecraft/server/level/Ticket.java

View File

@ -1,29 +0,0 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Jake Potrebic <jake.m.potrebic@gmail.com>
Date: Fri, 25 Jun 2021 12:06:35 -0700
Subject: [PATCH] Improve CraftChunk#getEntities
diff --git a/src/main/java/org/bukkit/craftbukkit/CraftChunk.java b/src/main/java/org/bukkit/craftbukkit/CraftChunk.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/org/bukkit/craftbukkit/CraftChunk.java
+++ b/src/main/java/org/bukkit/craftbukkit/CraftChunk.java
@@ -0,0 +0,0 @@ public class CraftChunk implements Chunk {
this.getWorld().getChunkAt(x, z); // Transient load for this tick
}
- Location location = new Location(null, 0, 0, 0);
- return this.getWorld().getEntities().stream().filter((entity) -> {
- entity.getLocation(location);
- return location.getBlockX() >> 4 == this.x && location.getBlockZ() >> 4 == this.z;
- }).toArray(Entity[]::new);
+ // Paper start - improve CraftChunk#getEntities
+ return this.worldServer.entityManager.sectionStorage.getExistingSectionsInChunk(ChunkPos.asLong(this.x, this.z))
+ .flatMap(net.minecraft.world.level.entity.EntitySection::getEntities)
+ .map(net.minecraft.world.entity.Entity::getBukkitEntity)
+ .filter(entity -> entity != null && entity.isValid())
+ .toArray(Entity[]::new);
+ // Paper end
}
@Override

View File

@ -63,7 +63,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
this.waterLevel = this.getY(1.0D); this.waterLevel = this.getY(1.0D);
- this.setPos(this.getX(), (double) (this.getWaterLevelAbove() - this.getBbHeight()) + 0.101D, this.getZ()); - this.setPos(this.getX(), (double) (this.getWaterLevelAbove() - this.getBbHeight()) + 0.101D, this.getZ());
- this.setDeltaMovement(this.getDeltaMovement().multiply(1.0D, 0.0D, 1.0D)); - this.setDeltaMovement(this.getDeltaMovement().multiply(1.0D, 0.0D, 1.0D));
+ this.setDeltaMovement(this.getDeltaMovement().multiply(1.0D, 0.0D, 1.0D).add(0.0, ((double) (this.getWaterLevelAbove() - this.getBbHeight()) + 0.101D) - this.getY(), 0.0)); // Paper + this.move(MoverType.SELF, new Vec3(0.0, ((double) (this.getWaterLevelAbove() - this.getBbHeight()) + 0.101D) - this.getY(), 0.0)); // Paper
+ this.setDeltaMovement(this.getDeltaMovement().multiply(1.0D, 0.0D, 1.0D)); // Paper
this.lastYd = 0.0D; this.lastYd = 0.0D;
this.status = Boat.Status.IN_WATER; this.status = Boat.Status.IN_WATER;
} else { } else {
@ -76,7 +77,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
entityboat.setType(this.type); entityboat.setType(this.type);
entityboat.setYRot(user.getYRot()); entityboat.setYRot(user.getYRot());
- if (!world.noCollision(entityboat, entityboat.getBoundingBox().inflate(-0.1D))) { - if (!world.noCollision(entityboat, entityboat.getBoundingBox().inflate(-0.1D))) {
+ if (!world.noCollision(entityboat, entityboat.getBoundingBox().inflate(-net.minecraft.Util.COLLISION_EPSILON))) { // Paper + if (!world.noCollision(entityboat, entityboat.getBoundingBox())) { // Paper
return InteractionResultHolder.fail(itemstack); return InteractionResultHolder.fail(itemstack);
} else { } else {
if (!world.isClientSide) { if (!world.isClientSide) {

View File

@ -9,14 +9,14 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
--- a/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java --- a/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java
+++ b/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java +++ b/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java
@@ -0,0 +0,0 @@ public abstract class BlockBehaviour { @@ -0,0 +0,0 @@ public abstract class BlockBehaviour {
return this.shapeExceedsCube;
} }
// Paper end // Paper end
+ // Paper start + // Paper start
+ protected boolean isTicking; + protected boolean isTicking;
+ protected FluidState fluid; + protected FluidState fluid;
+ // Paper end + // Paper end
+
public void initCache() { public void initCache() {
+ this.fluid = this.getBlock().getFluidState(this.asState()); // Paper - moved from getFluid() + this.fluid = this.getBlock().getFluidState(this.asState()); // Paper - moved from getFluid()
+ this.isTicking = this.getBlock().isRandomlyTicking(this.asState()); // Paper - moved from isTicking() + this.isTicking = this.getBlock().isRandomlyTicking(this.asState()); // Paper - moved from isTicking()
@ -24,12 +24,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
this.cache = new BlockBehaviour.BlockStateBase.Cache(this.asState()); this.cache = new BlockBehaviour.BlockStateBase.Cache(this.asState());
} }
@@ -0,0 +0,0 @@ public abstract class BlockBehaviour { @@ -0,0 +0,0 @@ public abstract class BlockBehaviour {
return this.getBlock().getOcclusionShape(this.asState(), world, pos); return this.shapeExceedsCube; // Paper - moved into shape cache init
}
- public boolean hasLargeCollisionShape() {
+ public final boolean hasLargeCollisionShape() { // Paper
return this.cache == null || this.cache.largeCollisionShape;
} }
- public boolean useShapeForLightOcclusion() { - public boolean useShapeForLightOcclusion() {

View File

@ -84,8 +84,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
// Spigot start // Spigot start
public static final int TPS = 20; public static final int TPS = 20;
@@ -0,0 +0,0 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa @@ -0,0 +0,0 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
public final SlackActivityAccountant slackActivityAccountant = new SlackActivityAccountant();
// Spigot end // Spigot end
public static long currentTickLong = 0L; // Paper
+ public volatile Thread shutdownThread; // Paper + public volatile Thread shutdownThread; // Paper
+ public volatile boolean abnormalExit = false; // Paper + public volatile boolean abnormalExit = false; // Paper

View File

@ -9,10 +9,9 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
--- a/src/main/java/com/destroystokyo/paper/PaperConfig.java --- a/src/main/java/com/destroystokyo/paper/PaperConfig.java
+++ b/src/main/java/com/destroystokyo/paper/PaperConfig.java +++ b/src/main/java/com/destroystokyo/paper/PaperConfig.java
@@ -0,0 +0,0 @@ public class PaperConfig { @@ -0,0 +0,0 @@ public class PaperConfig {
allowPistonDuplication = getBoolean("settings.unsupported-settings.allow-piston-duplication", config.getBoolean("settings.unsupported-settings.allow-tnt-duplication", false));
set("settings.unsupported-settings.allow-tnt-duplication", null); set("settings.unsupported-settings.allow-tnt-duplication", null);
} }
+
+ public static int playerAutoSaveRate = -1; + public static int playerAutoSaveRate = -1;
+ public static int maxPlayerAutoSavePerTick = 10; + public static int maxPlayerAutoSavePerTick = 10;
+ private static void playerAutoSaveRate() { + private static void playerAutoSaveRate() {

View File

@ -0,0 +1,155 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Spottedleaf <spottedleaf@spottedleaf.dev>
Date: Fri, 14 Feb 2020 22:16:34 -0800
Subject: [PATCH] Lag compensate block breaking
Use time instead of ticks if ticks fall behind
diff --git a/src/main/java/com/destroystokyo/paper/PaperConfig.java b/src/main/java/com/destroystokyo/paper/PaperConfig.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/com/destroystokyo/paper/PaperConfig.java
+++ b/src/main/java/com/destroystokyo/paper/PaperConfig.java
@@ -0,0 +0,0 @@ public class PaperConfig {
}
}
}
+
+ public static boolean lagCompensateBlockBreaking;
+
+ private static void lagCompensateBlockBreaking() {
+ lagCompensateBlockBreaking = getBoolean("settings.lag-compensate-block-breaking", true);
+ }
}
diff --git a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java
+++ b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java
@@ -0,0 +0,0 @@ public class ServerPlayerGameMode {
@Nullable
private GameType previousGameModeForPlayer;
private boolean isDestroyingBlock;
- private int destroyProgressStart;
+ private int destroyProgressStart; private long lastDigTime; // Paper - lag compensate block breaking
private BlockPos destroyPos;
private int gameTicks;
private boolean hasDelayedDestroy;
private BlockPos delayedDestroyPos;
- private int delayedTickStart;
+ private int delayedTickStart; private long hasDestroyedTooFastStartTime; // Paper - lag compensate block breaking
private int lastSentState;
+ // Paper start - lag compensate block breaking
+ private int getTimeDiggingLagCompensate() {
+ int lagCompensated = (int)((System.nanoTime() - this.lastDigTime) / (50L * 1000L * 1000L));
+ int tickDiff = this.gameTicks - this.destroyProgressStart;
+ return (com.destroystokyo.paper.PaperConfig.lagCompensateBlockBreaking && lagCompensated > (tickDiff + 1)) ? lagCompensated : tickDiff; // add one to ensure we don't lag compensate unless we need to
+ }
+
+ private int getTimeDiggingTooFastLagCompensate() {
+ int lagCompensated = (int)((System.nanoTime() - this.hasDestroyedTooFastStartTime) / (50L * 1000L * 1000L));
+ int tickDiff = this.gameTicks - this.delayedTickStart;
+ return (com.destroystokyo.paper.PaperConfig.lagCompensateBlockBreaking && lagCompensated > (tickDiff + 1)) ? lagCompensated : tickDiff; // add one to ensure we don't lag compensate unless we need to
+ }
+ // Paper end
+
public ServerPlayerGameMode(ServerPlayer player) {
this.gameModeForPlayer = GameType.DEFAULT_MODE;
this.destroyPos = BlockPos.ZERO;
@@ -0,0 +0,0 @@ public class ServerPlayerGameMode {
if (iblockdata == null || iblockdata.isAir()) { // Paper
this.hasDelayedDestroy = false;
} else {
- float f = this.incrementDestroyProgress(iblockdata, this.delayedDestroyPos, this.delayedTickStart);
+ float f = this.updateBlockBreakAnimation(iblockdata, this.delayedDestroyPos, this.getTimeDiggingTooFastLagCompensate()); // Paper - lag compensate destroying blocks
if (f >= 1.0F) {
this.hasDelayedDestroy = false;
@@ -0,0 +0,0 @@ public class ServerPlayerGameMode {
this.lastSentState = -1;
this.isDestroyingBlock = false;
} else {
- this.incrementDestroyProgress(iblockdata, this.destroyPos, this.destroyProgressStart);
+ this.updateBlockBreakAnimation(iblockdata, this.destroyPos, this.getTimeDiggingLagCompensate()); // Paper - lag compensate destroying
}
}
@@ -0,0 +0,0 @@ public class ServerPlayerGameMode {
private float incrementDestroyProgress(BlockState state, BlockPos pos, int i) {
int j = this.gameTicks - i;
+ // Paper start - change i (startTime) to totalTime
+ return this.updateBlockBreakAnimation(state, pos, j);
+ }
+ private float updateBlockBreakAnimation(BlockState state, BlockPos pos, int totalTime) {
+ int j = totalTime;
+ // Paper end
float f = state.getDestroyProgress(this.player, this.player.level, pos) * (float) (j + 1);
int k = (int) (f * 10.0F);
@@ -0,0 +0,0 @@ public class ServerPlayerGameMode {
return;
}
- this.destroyProgressStart = this.gameTicks;
+ this.destroyProgressStart = this.gameTicks; this.lastDigTime = System.nanoTime(); // Paper - lag compensate block breaking
float f = 1.0F;
iblockdata = this.level.getBlockState(pos);
@@ -0,0 +0,0 @@ public class ServerPlayerGameMode {
int j = (int) (f * 10.0F);
this.level.destroyBlockProgress(this.player.getId(), pos, j);
- this.player.connection.send(new ClientboundBlockBreakAckPacket(pos, this.level.getBlockState(pos), action, true, "actual start of destroying"));
+ if (!com.destroystokyo.paper.PaperConfig.lagCompensateBlockBreaking) this.player.connection.send(new ClientboundBlockBreakAckPacket(pos, this.level.getBlockState(pos), action, true, "actual start of destroying"));
this.lastSentState = j;
}
} else if (action == ServerboundPlayerActionPacket.Action.STOP_DESTROY_BLOCK) {
if (pos.equals(this.destroyPos)) {
- int k = this.gameTicks - this.destroyProgressStart;
+ int k = this.getTimeDiggingLagCompensate(); // Paper - lag compensate block breaking
iblockdata = this.level.getBlockState(pos);
if (!iblockdata.isAir()) {
@@ -0,0 +0,0 @@ public class ServerPlayerGameMode {
this.isDestroyingBlock = false;
this.hasDelayedDestroy = true;
this.delayedDestroyPos = pos;
- this.delayedTickStart = this.destroyProgressStart;
+ this.delayedTickStart = this.destroyProgressStart; this.hasDestroyedTooFastStartTime = this.lastDigTime; // Paper - lag compensate block breaking
}
}
}
+ // Paper start - this can cause clients on a lagging server to think they're not currently destroying a block
+ if (com.destroystokyo.paper.PaperConfig.lagCompensateBlockBreaking) {
+ this.player.connection.send(new ClientboundBlockUpdatePacket(this.level, pos));
+ } else {
this.player.connection.send(new ClientboundBlockBreakAckPacket(pos, this.level.getBlockState(pos), action, true, "stopped destroying"));
+ }
+ // Paper end - this can cause clients on a lagging server to think they're not currently destroying a block
} else if (action == ServerboundPlayerActionPacket.Action.ABORT_DESTROY_BLOCK) {
this.isDestroyingBlock = false;
if (!Objects.equals(this.destroyPos, pos) && !BlockPos.ZERO.equals(this.destroyPos)) {
@@ -0,0 +0,0 @@ public class ServerPlayerGameMode {
}
this.level.destroyBlockProgress(this.player.getId(), pos, -1);
- this.player.connection.send(new ClientboundBlockBreakAckPacket(pos, this.level.getBlockState(pos), action, true, "aborted destroying"));
+ if (!com.destroystokyo.paper.PaperConfig.lagCompensateBlockBreaking) this.player.connection.send(new ClientboundBlockBreakAckPacket(pos, this.level.getBlockState(pos), action, true, "aborted destroying")); // Paper - this can cause clients on a lagging server to think they stopped destroying a block they're currently destroying
}
}
@@ -0,0 +0,0 @@ public class ServerPlayerGameMode {
public void destroyAndAck(BlockPos pos, ServerboundPlayerActionPacket.Action action, String reason) {
if (this.destroyBlock(pos)) {
+ // Paper start - this can cause clients on a lagging server to think they're not currently destroying a block
+ if (com.destroystokyo.paper.PaperConfig.lagCompensateBlockBreaking) {
+ this.player.connection.send(new ClientboundBlockUpdatePacket(this.level, pos));
+ } else {
this.player.connection.send(new ClientboundBlockBreakAckPacket(pos, this.level.getBlockState(pos), action, true, reason));
+ }
+ // Paper end - this can cause clients on a lagging server to think they're not currently destroying a block
} else {
this.player.connection.send(new ClientboundBlockUpdatePacket(this.level, pos)); // CraftBukkit - SPIGOT-5196
}

View File

@ -12,7 +12,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+++ b/src/main/java/org/spigotmc/AsyncCatcher.java +++ b/src/main/java/org/spigotmc/AsyncCatcher.java
@@ -0,0 +0,0 @@ public class AsyncCatcher @@ -0,0 +0,0 @@ public class AsyncCatcher
{ {
if ( AsyncCatcher.enabled && Thread.currentThread() != MinecraftServer.getServer().serverThread ) if ( (AsyncCatcher.enabled || io.papermc.paper.util.TickThread.STRICT_THREAD_CHECKS) && Thread.currentThread() != MinecraftServer.getServer().serverThread ) // Paper
{ {
+ MinecraftServer.LOGGER.fatal("Thread " + Thread.currentThread().getName() + " failed main thread check: " + reason, new Throwable()); // Paper + MinecraftServer.LOGGER.fatal("Thread " + Thread.currentThread().getName() + " failed main thread check: " + reason, new Throwable()); // Paper
throw new IllegalStateException( "Asynchronous " + reason + "!" ); throw new IllegalStateException( "Asynchronous " + reason + "!" );

View File

@ -221,6 +221,19 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
return i == 0; return i == 0;
}); });
} finally { } finally {
diff --git a/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiSection.java b/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiSection.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiSection.java
+++ b/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiSection.java
@@ -0,0 +0,0 @@ public class PoiSection {
private boolean isValid;
public static Codec<PoiSection> codec(Runnable updateListener) {
- return RecordCodecBuilder.create((instance) -> {
+ return RecordCodecBuilder.<PoiSection>create((instance) -> { // Paper - decompile fix
return instance.group(RecordCodecBuilder.point(updateListener), Codec.BOOL.optionalFieldOf("Valid", Boolean.valueOf(false)).forGetter((poiSet) -> {
return poiSet.isValid;
}), PoiRecord.codec(updateListener).listOf().fieldOf("Records").forGetter((poiSet) -> {
diff --git a/src/main/java/net/minecraft/world/item/crafting/RecipeManager.java b/src/main/java/net/minecraft/world/item/crafting/RecipeManager.java diff --git a/src/main/java/net/minecraft/world/item/crafting/RecipeManager.java b/src/main/java/net/minecraft/world/item/crafting/RecipeManager.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/world/item/crafting/RecipeManager.java --- a/src/main/java/net/minecraft/world/item/crafting/RecipeManager.java

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,34 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Spottedleaf <spottedleaf@spottedleaf.dev>
Date: Sun, 20 Sep 2020 16:10:49 -0700
Subject: [PATCH] Make sure inlined getChunkAt has inlined logic for loaded
chunks
Tux did some profiling some time ago and showed that the
previous getChunkAt method which had inlined logic for loaded
chunks did get inlined, but the standard CPS.getChunkAt
method was not inlined.
Paper recently reverted this optimisation, so it's been reintroduced
here.
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 {
@Override
public final LevelChunk getChunk(int chunkX, int chunkZ) { // Paper - final to help inline
+ // Paper start - make sure loaded chunks get the inlined variant of this function
+ net.minecraft.server.level.ServerChunkCache cps = ((ServerLevel)this).getChunkSource();
+ if (cps.mainThread == Thread.currentThread()) {
+ LevelChunk ifLoaded = cps.getChunkAtIfLoadedMainThread(chunkX, chunkZ);
+ if (ifLoaded != null) {
+ return ifLoaded;
+ }
+ }
+ // Paper end - make sure loaded chunks get the inlined variant of this function
return (LevelChunk) this.getChunk(chunkX, chunkZ, ChunkStatus.FULL, true); // Paper - avoid a method jump
}

View File

@ -0,0 +1,63 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Spottedleaf <spottedleaf@spottedleaf.dev>
Date: Mon, 6 Jul 2020 22:48:48 -0700
Subject: [PATCH] Manually inline methods in BlockPosition
diff --git a/src/main/java/net/minecraft/core/BlockPos.java b/src/main/java/net/minecraft/core/BlockPos.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/core/BlockPos.java
+++ b/src/main/java/net/minecraft/core/BlockPos.java
@@ -0,0 +0,0 @@ public class BlockPos extends Vec3i {
}
public BlockPos.MutableBlockPos set(int x, int y, int z) {
- this.setX(x);
- this.setY(y);
- this.setZ(z);
+ this.x = x; // Paper - force inline
+ this.y = y; // Paper - force inline
+ this.z = z; // Paper - force inline
return this;
}
@@ -0,0 +0,0 @@ public class BlockPos extends Vec3i {
// Paper start - comment out useless overrides @Override - TODO figure out why this is suddenly important to keep
@Override
public BlockPos.MutableBlockPos setX(int i) {
- super.setX(i);
+ this.x = i; // Paper
return this;
}
@Override
public BlockPos.MutableBlockPos setY(int i) {
- super.setY(i);
+ this.y = i; // Paper
return this;
}
@Override
public BlockPos.MutableBlockPos setZ(int i) {
- super.setZ(i);
+ this.z = i; // Paper
return this;
}
// Paper end
diff --git a/src/main/java/net/minecraft/core/Vec3i.java b/src/main/java/net/minecraft/core/Vec3i.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/core/Vec3i.java
+++ b/src/main/java/net/minecraft/core/Vec3i.java
@@ -0,0 +0,0 @@ public class Vec3i implements Comparable<Vec3i> {
return IntStream.of(vec3i.getX(), vec3i.getY(), vec3i.getZ());
});
public static final Vec3i ZERO = new Vec3i(0, 0, 0);
- private int x;
- private int y;
- private int z;
+ protected int x; // Paper - protected
+ protected int y; // Paper - protected
+ protected int z; // Paper - protected
// Paper start
public boolean isValidLocation(net.minecraft.world.level.LevelHeightAccessor levelHeightAccessor) {

View File

@ -1,268 +0,0 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Aikar <aikar@aikar.co>
Date: Thu, 9 Apr 2020 00:09:26 -0400
Subject: [PATCH] Mid Tick Chunk Tasks - Speed up processing of chunk loads and
generation
Credit to Spotted for the idea
A lot of the new chunk system requires constant back and forth the main thread
to handle priority scheduling and ensuring conflicting tasks do not run at the
same time.
The issue is, these queues are only checked at either:
A) Sync Chunk Loads
B) End of Tick while sleeping
This results in generating chunks sitting waiting for a full tick to
complete before it will even start the next unit of work to do.
Additionally, this also delays loading of chunks until this same timing.
We will now periodically poll the chunk task queues throughout the tick,
looking for work to do.
We do this in a fair method that considers all worlds, not just the one being
ticked, so that each world can get 1 task procesed each before the next pass.
In a view distance of 15, chunk loading performance was visually faster on the client.
Flying at high speed in spectator mode was able to keep up with chunk loading (as long as they are already generated)
diff --git a/src/main/java/co/aikar/timings/MinecraftTimings.java b/src/main/java/co/aikar/timings/MinecraftTimings.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/co/aikar/timings/MinecraftTimings.java
+++ b/src/main/java/co/aikar/timings/MinecraftTimings.java
@@ -0,0 +0,0 @@ import java.util.Map;
public final class MinecraftTimings {
public static final Timing serverOversleep = Timings.ofSafe("Server Oversleep");
+ public static final Timing midTickChunkTasks = Timings.ofSafe("Mid Tick Chunk Tasks");
public static final Timing playerListTimer = Timings.ofSafe("Player List");
public static final Timing commandFunctionsTimer = Timings.ofSafe("Command Functions");
public static final Timing connectionTimer = Timings.ofSafe("Connection Handler");
diff --git a/src/main/java/com/destroystokyo/paper/PaperConfig.java b/src/main/java/com/destroystokyo/paper/PaperConfig.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/com/destroystokyo/paper/PaperConfig.java
+++ b/src/main/java/com/destroystokyo/paper/PaperConfig.java
@@ -0,0 +0,0 @@ public class PaperConfig {
log("Async Chunks: Enabled - Chunks will be loaded much faster, without lag.");
}
}
+
+ public static int midTickChunkTasks = 1000;
+ private static void midTickChunkTasks() {
+ midTickChunkTasks = getInt("settings.chunk-tasks-per-tick", midTickChunkTasks);
+ }
}
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
// Paper end
tickSection = curTime;
}
+ midTickChunksTasksRan = 0; // Paper
// Spigot end
if (this.debugCommandProfilerDelayStart) {
@@ -0,0 +0,0 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
}
- private boolean haveTime() {
+ public boolean haveTime() { // Paper
// CraftBukkit start
if (isOversleep) return canOversleep();// Paper - because of our changes, this logic is broken
return this.forceTicks || this.runningTask() || Util.getMillis() < (this.mayHaveDelayedTasks ? this.delayedTasksMaxNextTickTime : this.nextTickTime);
@@ -0,0 +0,0 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
});
}
+ // Paper start
+ public int midTickChunksTasksRan = 0;
+ private long midTickLastRan = 0;
+ public void midTickLoadChunks() {
+ if (!isSameThread() || System.nanoTime() - midTickLastRan < 1000000) {
+ // only check once per 0.25ms incase this code is called in a hot method
+ return;
+ }
+ try (co.aikar.timings.Timing ignored = co.aikar.timings.MinecraftTimings.midTickChunkTasks.startTiming()) {
+ for (ServerLevel value : this.getAllLevels()) {
+ value.getChunkSource().mainThreadProcessor.midTickLoadChunks();
+ }
+ midTickLastRan = System.nanoTime();
+ }
+ }
+ // Paper end
+
@Override
public TickTask wrapRunnable(Runnable runnable) {
return new TickTask(this.tickCount, runnable);
@@ -0,0 +0,0 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
// Paper start - move oversleep into full server tick
isOversleep = true;MinecraftTimings.serverOversleep.startTiming();
this.managedBlock(() -> {
+ midTickLoadChunks(); // will only do loads since we are still considered !canSleepForTick
return !this.canOversleep();
});
isOversleep = false;MinecraftTimings.serverOversleep.stopTiming();
@@ -0,0 +0,0 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
}
public void tickChildren(BooleanSupplier shouldKeepTicking) {
+ midTickLoadChunks(); // Paper
MinecraftTimings.bukkitSchedulerTimer.startTiming(); // Spigot // Paper
this.server.getScheduler().mainThreadHeartbeat(this.tickCount); // CraftBukkit
MinecraftTimings.bukkitSchedulerTimer.stopTiming(); // Spigot // Paper
+ midTickLoadChunks(); // Paper
this.profiler.push("commandFunctions");
MinecraftTimings.commandFunctionsTimer.startTiming(); // Spigot // Paper
this.getFunctions().tick();
MinecraftTimings.commandFunctionsTimer.stopTiming(); // Spigot // Paper
+ midTickLoadChunks(); // Paper
this.profiler.popPush("levels");
Iterator iterator = this.getAllLevels().iterator();
@@ -0,0 +0,0 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
this.processQueue.remove().run();
}
MinecraftTimings.processQueueTimer.stopTiming(); // Spigot
-
+ midTickLoadChunks(); // Paper
MinecraftTimings.timeUpdateTimer.startTiming(); // Spigot // Paper
// Send time updates to everyone, it will get the right time from the world the player is in.
// Paper start - optimize time updates
@@ -0,0 +0,0 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
this.profiler.push("tick");
try {
+ midTickLoadChunks(); // Paper
worldserver.timings.doTick.startTiming(); // Spigot
worldserver.tick(shouldKeepTicking);
worldserver.timings.doTick.stopTiming(); // Spigot
+ midTickLoadChunks(); // Paper
} catch (Throwable throwable) {
// Spigot Start
CrashReport crashreport;
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("purge");
this.level.timings.doChunkMap.startTiming(); // Spigot
this.distanceManager.purgeStaleTickets();
+ this.level.getServer().midTickLoadChunks(); // Paper
this.runDistanceManagerUpdates();
this.level.timings.doChunkMap.stopTiming(); // Spigot
this.level.getProfiler().popPush("chunks");
@@ -0,0 +0,0 @@ public class ServerChunkCache extends ChunkSource {
this.level.timings.doChunkUnload.startTiming(); // Spigot
this.level.getProfiler().popPush("unload");
this.chunkMap.tick(booleansupplier);
+ this.level.getServer().midTickLoadChunks(); // Paper
this.level.timings.doChunkUnload.stopTiming(); // Spigot
this.level.getProfiler().pop();
this.clearCache();
@@ -0,0 +0,0 @@ public class ServerChunkCache extends ChunkSource {
};
// Paper end
this.level.timings.chunkTicks.startTiming(); // Paper
+ final int[] chunksTicked = {0}; // Paper
list.forEach((playerchunk) -> {
Optional<LevelChunk> optional = ((Either) playerchunk.getTickingChunkFuture().getNow(ChunkHolder.UNLOADED_LEVEL_CHUNK)).left();
@@ -0,0 +0,0 @@ public class ServerChunkCache extends ChunkSource {
chunk.setInhabitedTime(chunk.getInhabitedTime() + j);
if (flag1 && (this.spawnEnemies || this.spawnFriendlies) && this.level.getWorldBorder().isWithinBounds(chunk.getPos()) && !this.chunkMap.isOutsideOfRange(chunkcoordintpair, true)) { // Spigot
NaturalSpawner.spawnForChunk(this.level, chunk, spawnercreature_d, this.spawnFriendlies, this.spawnEnemies, flag2);
+ if (chunksTicked[0]++ % 10 == 0) this.level.getServer().midTickLoadChunks(); // Paper
}
// this.level.timings.doTickTiles.startTiming(); // Spigot // Paper
@@ -0,0 +0,0 @@ public class ServerChunkCache extends ChunkSource {
}
this.level.getProfiler().popPush("broadcast");
- this.chunkMap.getChunks().forEach((playerchunk) -> { // Paper - no... just no...
+ this.chunkMap.forEachVisibleChunk((playerchunk) -> { // Paper - safe iterator incase chunk loads, also no wrapping
Optional<LevelChunk> optional = ((Either) playerchunk.getTickingChunkFuture().getNow(ChunkHolder.UNLOADED_LEVEL_CHUNK)).left(); // CraftBukkit - decompile error
Objects.requireNonNull(playerchunk);
@@ -0,0 +0,0 @@ public class ServerChunkCache extends ChunkSource {
super.doRunTask(task);
}
+ // Paper start
+ private long lastMidTickChunkTask = 0;
+ public boolean pollChunkLoadTasks() {
+ if (com.destroystokyo.paper.io.chunk.ChunkTaskManager.pollChunkWaitQueue() || ServerChunkCache.this.level.asyncChunkTaskManager.pollNextChunkTask()) {
+ try {
+ ServerChunkCache.this.runDistanceManagerUpdates();
+ } finally {
+ // from below: process pending Chunk loadCallback() and unloadCallback() after each run task
+ chunkMap.callbackExecutor.run();
+ }
+ return true;
+ }
+ return false;
+ }
+ public void midTickLoadChunks() {
+ net.minecraft.server.MinecraftServer server = ServerChunkCache.this.level.getServer();
+ // always try to load chunks, restrain generation/other updates only. don't count these towards tick count
+ //noinspection StatementWithEmptyBody
+ while (pollChunkLoadTasks()) {}
+
+ if (System.nanoTime() - lastMidTickChunkTask < 200000) {
+ return;
+ }
+
+ for (;server.midTickChunksTasksRan < com.destroystokyo.paper.PaperConfig.midTickChunkTasks && server.haveTime();) {
+ if (this.pollTask()) {
+ server.midTickChunksTasksRan++;
+ lastMidTickChunkTask = System.nanoTime();
+ } else {
+ break;
+ }
+ }
+ }
+ // Paper end
+
@Override
// CraftBukkit start - process pending Chunk loadCallback() and unloadCallback() after each run task
public boolean pollTask() {
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 Level implements WorldGenLevel {
}
timings.scheduledBlocks.stopTiming(); // Paper
+ this.getServer().midTickLoadChunks(); // Paper
gameprofilerfiller.popPush("raid");
this.timings.raids.startTiming(); // Paper - timings
this.raids.tick();
@@ -0,0 +0,0 @@ public class ServerLevel extends Level implements WorldGenLevel {
timings.doSounds.startTiming(); // Spigot
this.runBlockEvents();
timings.doSounds.stopTiming(); // Spigot
+ this.getServer().midTickLoadChunks(); // Paper
this.handlingTick = false;
gameprofilerfiller.pop();
boolean flag3 = true || !this.players.isEmpty() || !this.getForcedChunks().isEmpty(); // CraftBukkit - this prevents entity cleanup, other issues on servers with no players
@@ -0,0 +0,0 @@ public class ServerLevel extends Level implements WorldGenLevel {
timings.entityTick.stopTiming(); // Spigot
timings.tickEntities.stopTiming(); // Spigot
gameprofilerfiller.pop();
+ this.getServer().midTickLoadChunks(); // Paper
this.tickBlockEntities();
}
gameprofilerfiller.push("entityManagement");
+ this.getServer().midTickLoadChunks(); // Paper
this.entityManager.tick();
gameprofilerfiller.pop();
}

View File

@ -0,0 +1,33 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Spottedleaf <spottedleaf@spottedleaf.dev>
Date: Sun, 19 Jul 2020 15:17:01 -0700
Subject: [PATCH] Name craft scheduler threads according to the plugin using
them
Provides quick access to culprits running far more threads than
they should be
diff --git a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftAsyncTask.java b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftAsyncTask.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftAsyncTask.java
+++ b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftAsyncTask.java
@@ -0,0 +0,0 @@ class CraftAsyncTask extends CraftTask {
@Override
public void run() {
final Thread thread = Thread.currentThread();
- synchronized (this.workers) {
+ // Paper start - name threads according to running plugin
+ final String nameBefore = thread.getName();
+ thread.setName(nameBefore + " - " + this.getOwner().getName());
+ try { synchronized (this.workers) { // Paper end - name threads according to running plugin
if (getPeriod() == CraftTask.CANCEL) {
// Never continue running after cancelled.
// Checking this with the lock is important!
@@ -0,0 +0,0 @@ class CraftAsyncTask extends CraftTask {
}
}
}
+ } finally { thread.setName(nameBefore); } // Paper - name worker thread according
}
LinkedList<BukkitWorker> getWorkers() {

View File

@ -225,11 +225,11 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ // Paper end - no-tick view distance + // Paper end - no-tick view distance
} }
// Paper end // Paper end
// Paper start
@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider @@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
this.overworldDataStorage = persistentStateManagerFactory; this.dataRegionManager = new io.papermc.paper.chunk.SingleThreadChunkRegionManager(this.level, 2, (1.0 / 3.0), 1, 6, "Data", DataRegionData::new, DataRegionSectionData::new);
this.poiManager = new PoiManager(new File(file, "poi"), dataFixer, dsync, world); this.regionManagers.add(this.dataRegionManager);
this.setViewDistance(viewDistance); // Paper end
+ // Paper start - no-tick view distance + // Paper start - no-tick view distance
+ this.setNoTickViewDistance(this.level.paperConfig.noTickViewDistance); + this.setNoTickViewDistance(this.level.paperConfig.noTickViewDistance);
+ this.playerViewDistanceTickMap = new com.destroystokyo.paper.util.misc.PlayerAreaMap(this.pooledLinkedPlayerHashSets, + this.playerViewDistanceTickMap = new com.destroystokyo.paper.util.misc.PlayerAreaMap(this.pooledLinkedPlayerHashSets,

View File

@ -0,0 +1,67 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Spottedleaf <Spottedleaf@users.noreply.github.com>
Date: Sun, 3 Mar 2019 20:53:18 -0800
Subject: [PATCH] Not implemeneted
Currently a placeholder patch.
diff --git a/src/main/java/io/papermc/paper/util/TickThread.java b/src/main/java/io/papermc/paper/util/TickThread.java
new file mode 100644
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
--- /dev/null
+++ b/src/main/java/io/papermc/paper/util/TickThread.java
@@ -0,0 +0,0 @@
+package io.papermc.paper.util;
+
+import net.minecraft.server.MinecraftServer;
+import org.bukkit.Bukkit;
+
+public final class TickThread extends Thread {
+
+ public static final boolean STRICT_THREAD_CHECKS = Boolean.getBoolean("paper.strict-thread-checks");
+
+ static {
+ if (STRICT_THREAD_CHECKS) {
+ MinecraftServer.LOGGER.warn("Strict thread checks enabled - performance may suffer");
+ }
+ }
+
+ public static void softEnsureTickThread(final String reason) {
+ if (!STRICT_THREAD_CHECKS) {
+ return;
+ }
+ ensureTickThread(reason);
+ }
+
+
+ public static void ensureTickThread(final String reason) {
+ if (!Bukkit.isPrimaryThread()) {
+ MinecraftServer.LOGGER.fatal("Thread " + Thread.currentThread().getName() + " failed main thread check: " + reason, new Throwable());
+ throw new IllegalStateException(reason);
+ }
+ }
+
+ public final int id; /* We don't override getId as the spec requires that it be unique (with respect to all other threads) */
+
+ public TickThread(final Runnable run, final String name, final int id) {
+ super(run, name);
+ this.id = id;
+ }
+
+ public static TickThread getCurrentTickThread() {
+ return (TickThread)Thread.currentThread();
+ }
+}
diff --git a/src/main/java/org/spigotmc/AsyncCatcher.java b/src/main/java/org/spigotmc/AsyncCatcher.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/org/spigotmc/AsyncCatcher.java
+++ b/src/main/java/org/spigotmc/AsyncCatcher.java
@@ -0,0 +0,0 @@ public class AsyncCatcher
public static void catchOp(String reason)
{
- if ( AsyncCatcher.enabled && Thread.currentThread() != MinecraftServer.getServer().serverThread )
+ if ( (AsyncCatcher.enabled || io.papermc.paper.util.TickThread.STRICT_THREAD_CHECKS) && Thread.currentThread() != MinecraftServer.getServer().serverThread ) // Paper
{
throw new IllegalStateException( "Asynchronous " + reason + "!" );
}

View File

@ -0,0 +1,29 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Spottedleaf <Spottedleaf@users.noreply.github.com>
Date: Fri, 19 Feb 2021 22:51:52 -0800
Subject: [PATCH] Oprimise map impl for tracked players
Reference2BooleanOpenHashMap is going to have
better lookups than HashMap.
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 org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.bukkit.entity.Player; // CraftBukkit
+import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet; // Paper
public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider {
@@ -0,0 +0,0 @@ Sections go from 0..16. Now whenever a section is not empty, it can potentially
final Entity entity;
private final int range;
SectionPos lastSectionPos;
- public final Set<ServerPlayerConnection> seenBy = Sets.newIdentityHashSet();
+ public final Set<ServerPlayerConnection> seenBy = new ReferenceOpenHashSet<>(); // Paper - optimise map impl
public TrackedEntity(Entity entity, int i, int j, boolean flag) {
this.serverEntity = new ServerEntity(ChunkMap.this.level, entity, j, flag, this::broadcast, this.seenBy); // CraftBukkit

View File

@ -0,0 +1,52 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Spottedleaf <Spottedleaf@users.noreply.github.com>
Date: Thu, 10 Jun 2021 14:36:00 -0700
Subject: [PATCH] Optimise BlockSoil nearby water lookup
Apparently the abstract block iteration was taking about
75% of the method call.
diff --git a/src/main/java/net/minecraft/world/level/block/FarmBlock.java b/src/main/java/net/minecraft/world/level/block/FarmBlock.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/world/level/block/FarmBlock.java
+++ b/src/main/java/net/minecraft/world/level/block/FarmBlock.java
@@ -0,0 +0,0 @@ public class FarmBlock extends Block {
}
private static boolean isNearWater(LevelReader world, BlockPos pos) {
- Iterator iterator = BlockPos.betweenClosed(pos.offset(-4, 0, -4), pos.offset(4, 1, 4)).iterator();
-
- BlockPos blockposition1;
-
- do {
- if (!iterator.hasNext()) {
- return false;
+ // Paper start - remove abstract block iteration
+ int xOff = pos.getX();
+ int yOff = pos.getY();
+ int zOff = pos.getZ();
+
+ for (int dz = -4; dz <= 4; ++dz) {
+ int z = dz + zOff;
+ for (int dx = -4; dx <= 4; ++dx) {
+ int x = xOff + dx;
+ for (int dy = 0; dy <= 1; ++dy) {
+ int y = dy + yOff;
+ net.minecraft.world.level.chunk.LevelChunk chunk = (net.minecraft.world.level.chunk.LevelChunk)world.getChunk(x >> 4, z >> 4);
+ net.minecraft.world.level.material.FluidState fluid = chunk.getBlockData(x, y, z).getFluidState();
+ if (fluid.is(FluidTags.WATER)) {
+ return true;
+ }
+ }
}
+ }
- blockposition1 = (BlockPos) iterator.next();
- } while (!world.getFluidState(blockposition1).is((Tag) FluidTags.WATER));
-
- return true;
+ return false;
+ // Paper end - remove abstract block iteration
}
@Override

View File

@ -913,10 +913,9 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
public ChunkHolder(ChunkPos pos, int level, LevelHeightAccessor world, LevelLightEngine lightingProvider, ChunkHolder.LevelChangeListener levelUpdateListener, ChunkHolder.PlayerProvider playersWatchingChunkProvider) { public ChunkHolder(ChunkPos pos, int level, LevelHeightAccessor world, LevelLightEngine lightingProvider, ChunkHolder.LevelChangeListener levelUpdateListener, ChunkHolder.PlayerProvider playersWatchingChunkProvider) {
this.futures = new AtomicReferenceArray(ChunkHolder.CHUNK_STATUSES.size()); this.futures = new AtomicReferenceArray(ChunkHolder.CHUNK_STATUSES.size());
@@ -0,0 +0,0 @@ public class ChunkHolder { @@ -0,0 +0,0 @@ public class ChunkHolder {
either.ifLeft(chunk -> { // Paper start - ticking chunk set
// note: Here is a very good place to add callbacks to logic waiting on this. ChunkHolder.this.chunkMap.level.getChunkSource().tickingChunks.add(chunk);
ChunkHolder.this.isTickingReady = true; // Paper end - ticking chunk set
+
+ // Paper start - rewrite ticklistserver + // Paper start - rewrite ticklistserver
+ if (ChunkHolder.this.chunkMap.level.entityManager.areEntitiesLoaded(this.pos.longKey)) { + if (ChunkHolder.this.chunkMap.level.entityManager.areEntitiesLoaded(this.pos.longKey)) {
+ ChunkHolder.this.chunkMap.level.onChunkSetTicking(ChunkHolder.this.pos.x, ChunkHolder.this.pos.z); + ChunkHolder.this.chunkMap.level.onChunkSetTicking(ChunkHolder.this.pos.x, ChunkHolder.this.pos.z);
@ -946,8 +945,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ } + }
+ // Paper end - rewrite ticklistserver + // Paper end - rewrite ticklistserver
public ServerChunkCache(ServerLevel world, LevelStorageSource.LevelStorageAccess session, DataFixer dataFixer, StructureManager structureManager, Executor workerExecutor, ChunkGenerator chunkGenerator, int viewDistance, boolean flag, ChunkProgressListener worldGenerationProgressListener, ChunkStatusUpdateListener chunkstatusupdatelistener, Supplier<DimensionDataStorage> supplier) { // Paper start
this.level = world; // this will try to avoid chunk neighbours for lighting
diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java 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 index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/server/level/ServerLevel.java --- a/src/main/java/net/minecraft/server/level/ServerLevel.java

View File

@ -0,0 +1,325 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Spottedleaf <spottedleaf@spottedleaf.dev>
Date: Thu, 9 Jul 2020 13:34:59 -0700
Subject: [PATCH] Optimise WorldServer#notify
Iterating over all of the navigators in the world is pretty expensive.
Instead, only iterate over navigators in the current region that are
eligible for repathing.
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 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
public final io.papermc.paper.chunk.SingleThreadChunkRegionManager dataRegionManager;
public static final class DataRegionData implements io.papermc.paper.chunk.SingleThreadChunkRegionManager.RegionData {
+ // Paper start - optimise notify()
+ private io.papermc.paper.util.maplist.IteratorSafeOrderedReferenceSet<Mob> navigators;
+
+ public io.papermc.paper.util.maplist.IteratorSafeOrderedReferenceSet<Mob> getNavigators() {
+ return this.navigators;
+ }
+
+ public boolean addToNavigators(final Mob navigator) {
+ if (this.navigators == null) {
+ this.navigators = new io.papermc.paper.util.maplist.IteratorSafeOrderedReferenceSet<>();
+ }
+ return this.navigators.add(navigator);
+ }
+
+ public boolean removeFromNavigators(final Mob navigator) {
+ if (this.navigators == null) {
+ return false;
+ }
+ return this.navigators.remove(navigator);
+ }
+ // Paper end - optimise notify()
}
public static final class DataRegionSectionData implements io.papermc.paper.chunk.SingleThreadChunkRegionManager.RegionSectionData {
+ // Paper start - optimise notify()
+ private io.papermc.paper.util.maplist.IteratorSafeOrderedReferenceSet<Mob> navigators;
+
+ public io.papermc.paper.util.maplist.IteratorSafeOrderedReferenceSet<Mob> getNavigators() {
+ return this.navigators;
+ }
+
+ public boolean addToNavigators(final io.papermc.paper.chunk.SingleThreadChunkRegionManager.RegionSection section, final Mob navigator) {
+ if (this.navigators == null) {
+ this.navigators = new io.papermc.paper.util.maplist.IteratorSafeOrderedReferenceSet<>();
+ }
+ final boolean ret = this.navigators.add(navigator);
+ if (ret) {
+ final DataRegionData data = (DataRegionData)section.getRegion().regionData;
+ if (!data.addToNavigators(navigator)) {
+ throw new IllegalStateException();
+ }
+ }
+ return ret;
+ }
+
+ public boolean removeFromNavigators(final io.papermc.paper.chunk.SingleThreadChunkRegionManager.RegionSection section, final Mob navigator) {
+ if (this.navigators == null) {
+ return false;
+ }
+ final boolean ret = this.navigators.remove(navigator);
+ if (ret) {
+ final DataRegionData data = (DataRegionData)section.getRegion().regionData;
+ if (!data.removeFromNavigators(navigator)) {
+ throw new IllegalStateException();
+ }
+ }
+ return ret;
+ }
+ // Paper end - optimise notify()
+
@Override
public void removeFromRegion(final io.papermc.paper.chunk.SingleThreadChunkRegionManager.RegionSection section,
final io.papermc.paper.chunk.SingleThreadChunkRegionManager.Region from) {
final DataRegionSectionData sectionData = (DataRegionSectionData)section.sectionData;
final DataRegionData fromData = (DataRegionData)from.regionData;
+ // Paper start - optimise notify()
+ if (sectionData.navigators != null) {
+ for (final Iterator<Mob> iterator = sectionData.navigators.unsafeIterator(io.papermc.paper.util.maplist.IteratorSafeOrderedReferenceSet.ITERATOR_FLAG_SEE_ADDITIONS); iterator.hasNext();) {
+ if (!fromData.removeFromNavigators(iterator.next())) {
+ throw new IllegalStateException();
+ }
+ }
+ }
+ // Paper end - optimise notify()
}
@Override
@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
final DataRegionSectionData sectionData = (DataRegionSectionData)section.sectionData;
final DataRegionData oldRegionData = oldRegion == null ? null : (DataRegionData)oldRegion.regionData;
final DataRegionData newRegionData = (DataRegionData)newRegion.regionData;
+ // Paper start - optimise notify()
+ if (sectionData.navigators != null) {
+ for (final Iterator<Mob> iterator = sectionData.navigators.unsafeIterator(io.papermc.paper.util.maplist.IteratorSafeOrderedReferenceSet.ITERATOR_FLAG_SEE_ADDITIONS); iterator.hasNext();) {
+ if (!newRegionData.addToNavigators(iterator.next())) {
+ throw new IllegalStateException();
+ }
+ }
+ }
+ // Paper end - optimise notify()
}
}
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 Level implements WorldGenLevel {
public void tickNonPassenger(Entity entity) {
// Paper start - log detailed entity tick information
io.papermc.paper.util.TickThread.ensureTickThread("Cannot tick an entity off-main");
+ this.entityManager.updateNavigatorsInRegion(entity); // Paper - optimise notify
try {
if (currentlyTickingEntity.get() == null) {
currentlyTickingEntity.lazySet(entity);
@@ -0,0 +0,0 @@ public class ServerLevel extends Level implements WorldGenLevel {
VoxelShape voxelshape1 = newState.getCollisionShape(this, pos);
if (Shapes.joinIsNotEmpty(voxelshape, voxelshape1, BooleanOp.NOT_SAME)) {
- Iterator iterator = this.navigatingMobs.iterator();
+ // Paper start - optimise notify()
+ io.papermc.paper.chunk.SingleThreadChunkRegionManager.Region region = this.getChunkSource().chunkMap.dataRegionManager.getRegion(pos.getX() >> 4, pos.getZ() >> 4);
+ if (region == null) {
+ return;
+ }
+ io.papermc.paper.util.maplist.IteratorSafeOrderedReferenceSet<Mob> navigatorsFromRegion = ((ChunkMap.DataRegionData)region.regionData).getNavigators();
+ if (navigatorsFromRegion == null) {
+ return;
+ }
+ io.papermc.paper.util.maplist.IteratorSafeOrderedReferenceSet.Iterator<Mob> iterator = navigatorsFromRegion.iterator();
- while (iterator.hasNext()) {
+
+ try { while (iterator.hasNext()) { // Paper end - optimise notify()
// CraftBukkit start - fix SPIGOT-6362
Mob entityinsentient;
try {
@@ -0,0 +0,0 @@ public class ServerLevel extends Level implements WorldGenLevel {
navigationabstract.recomputePath(pos);
}
}
+ // Paper start - optimise notify()
+ } finally {
+ iterator.finishedIterating();
+ }
+ // Paper end - optimise notify()
}
} // Paper
@@ -0,0 +0,0 @@ public class ServerLevel extends Level implements WorldGenLevel {
public void onTickingStart(Entity entity) {
ServerLevel.this.entityTickList.add(entity);
+ ServerLevel.this.entityManager.addNavigatorsIfPathingToRegion(entity); // Paper - optimise notify
}
public void onTickingEnd(Entity entity) {
ServerLevel.this.entityTickList.remove(entity);
+ ServerLevel.this.entityManager.removeNavigatorsFromData(entity); // Paper - optimise notify
}
public void onTrackingStart(Entity entity) {
diff --git a/src/main/java/net/minecraft/world/entity/ai/navigation/PathNavigation.java b/src/main/java/net/minecraft/world/entity/ai/navigation/PathNavigation.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/world/entity/ai/navigation/PathNavigation.java
+++ b/src/main/java/net/minecraft/world/entity/ai/navigation/PathNavigation.java
@@ -0,0 +0,0 @@ import net.minecraft.world.phys.Vec3;
public abstract class PathNavigation {
private static final int MAX_TIME_RECOMPUTE = 20;
- protected final Mob mob;
+ protected final Mob mob; public final Mob getEntity() { return this.mob; } // Paper - public accessor
protected final Level level;
@Nullable
protected Path path;
@@ -0,0 +0,0 @@ public abstract class PathNavigation {
protected long lastTimeoutCheck;
protected double timeoutLimit;
protected float maxDistanceToWaypoint = 0.5F;
- protected boolean hasDelayedRecomputation;
+ protected boolean hasDelayedRecomputation; protected final boolean needsPathRecalculation() { return this.hasDelayedRecomputation; } // Paper - public accessor
protected long timeLastRecompute;
protected NodeEvaluator nodeEvaluator;
private BlockPos targetPos;
@@ -0,0 +0,0 @@ public abstract class PathNavigation {
public final PathFinder pathFinder;
private boolean isStuck;
+ // Paper start
+ public boolean isViableForPathRecalculationChecking() {
+ return !this.needsPathRecalculation() &&
+ (this.path != null && !this.path.isDone() && this.path.getNodeCount() != 0);
+ }
+ // Paper end
+
public PathNavigation(Mob mob, Level world) {
this.mob = mob;
this.level = world;
@@ -0,0 +0,0 @@ public abstract class PathNavigation {
}
public void recomputePath(BlockPos pos) {
- if (this.path != null && !this.path.isDone() && this.path.getNodeCount() != 0) {
+ if (this.path != null && !this.path.isDone() && this.path.getNodeCount() != 0) { // Paper - diff on change - needed for isViableForPathRecalculationChecking()
Node node = this.path.getEndNode();
Vec3 vec3 = new Vec3(((double)node.x + this.mob.getX()) / 2.0D, ((double)node.y + this.mob.getY()) / 2.0D, ((double)node.z + this.mob.getZ()) / 2.0D);
if (pos.closerThan(vec3, (double)(this.path.getNodeCount() - this.path.getNextNodeIndex()))) {
diff --git a/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java b/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java
+++ b/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java
@@ -0,0 +0,0 @@ public class PersistentEntitySectionManager<T extends EntityAccess> implements A
this.entityGetter = new LevelEntityGetterAdapter<>(this.visibleEntityStorage, this.sectionStorage);
}
+ // Paper start - optimise notify()
+ public final void removeNavigatorsFromData(Entity entity, final int chunkX, final int chunkZ) {
+ if (!(entity instanceof net.minecraft.world.entity.Mob)) {
+ return;
+ }
+ io.papermc.paper.chunk.SingleThreadChunkRegionManager.RegionSection section =
+ this.entitySliceManager.world.getChunkSource().chunkMap.dataRegionManager.getRegionSection(chunkX, chunkZ);
+ if (section != null) {
+ net.minecraft.server.level.ChunkMap.DataRegionSectionData sectionData = (net.minecraft.server.level.ChunkMap.DataRegionSectionData)section.sectionData;
+ sectionData.removeFromNavigators(section, ((net.minecraft.world.entity.Mob)entity));
+ }
+ }
+
+ public final void removeNavigatorsFromData(Entity entity) {
+ if (!(entity instanceof net.minecraft.world.entity.Mob)) {
+ return;
+ }
+ BlockPos entityPos = entity.blockPosition();
+ io.papermc.paper.chunk.SingleThreadChunkRegionManager.RegionSection section =
+ this.entitySliceManager.world.getChunkSource().chunkMap.dataRegionManager.getRegionSection(entityPos.getX() >> 4, entityPos.getZ() >> 4);
+ if (section != null) {
+ net.minecraft.server.level.ChunkMap.DataRegionSectionData sectionData = (net.minecraft.server.level.ChunkMap.DataRegionSectionData)section.sectionData;
+ sectionData.removeFromNavigators(section, ((net.minecraft.world.entity.Mob)entity));
+ }
+ }
+
+ public final void addNavigatorsIfPathingToRegion(Entity entity) {
+ if (!(entity instanceof net.minecraft.world.entity.Mob)) {
+ return;
+ }
+ BlockPos entityPos = entity.blockPosition();
+ io.papermc.paper.chunk.SingleThreadChunkRegionManager.RegionSection section =
+ this.entitySliceManager.world.getChunkSource().chunkMap.dataRegionManager.getRegionSection(entityPos.getX() >> 4, entityPos.getZ() >> 4);
+ if (section != null) {
+ net.minecraft.server.level.ChunkMap.DataRegionSectionData sectionData = (net.minecraft.server.level.ChunkMap.DataRegionSectionData)section.sectionData;
+ if (((net.minecraft.world.entity.Mob)entity).getNavigation().isViableForPathRecalculationChecking()) {
+ sectionData.addToNavigators(section, ((net.minecraft.world.entity.Mob)entity));
+ }
+ }
+ }
+
+ public final void updateNavigatorsInRegion(Entity entity) {
+ if (!(entity instanceof net.minecraft.world.entity.Mob)) {
+ return;
+ }
+ BlockPos entityPos = entity.blockPosition();
+ io.papermc.paper.chunk.SingleThreadChunkRegionManager.RegionSection section =
+ this.entitySliceManager.world.getChunkSource().chunkMap.dataRegionManager.getRegionSection(entityPos.getX() >> 4, entityPos.getZ() >> 4);
+ if (section != null) {
+ net.minecraft.server.level.ChunkMap.DataRegionSectionData sectionData = (net.minecraft.server.level.ChunkMap.DataRegionSectionData)section.sectionData;
+ if (((net.minecraft.world.entity.Mob)entity).getNavigation().isViableForPathRecalculationChecking()) {
+ sectionData.addToNavigators(section, ((net.minecraft.world.entity.Mob)entity));
+ } else {
+ sectionData.removeFromNavigators(section, ((net.minecraft.world.entity.Mob)entity));
+ }
+ }
+ }
+ // Paper end - optimise notify()
+
void removeSectionIfEmpty(long sectionPos, EntitySection<T> section) {
if (section.isEmpty()) {
this.sectionStorage.remove(sectionPos);
@@ -0,0 +0,0 @@ public class PersistentEntitySectionManager<T extends EntityAccess> implements A
@Override
public void onMove() {
BlockPos blockposition = this.entity.blockPosition();
- long i = SectionPos.asLong(blockposition);
+ long i = SectionPos.asLong(blockposition); final long newSectionPos = i; // Paper - diff on change, new position section
if (i != this.currentSectionKey) {
PersistentEntitySectionManager.this.entitySliceManager.moveEntity((Entity)this.entity); // Paper
- Visibility visibility = this.currentSection.getStatus();
+ Visibility visibility = this.currentSection.getStatus(); final Visibility oldVisibility = visibility; // Paper - diff on change - this should be OLD section visibility
+ // Paper start
+ int shift = PersistentEntitySectionManager.this.entitySliceManager.world.getChunkSource().chunkMap.dataRegionManager.regionChunkShift;
+ int oldChunkX = io.papermc.paper.util.CoordinateUtils.getChunkSectionX(this.currentSectionKey);
+ int oldChunkZ = io.papermc.paper.util.CoordinateUtils.getChunkSectionZ(this.currentSectionKey);
+ int oldRegionX = oldChunkX >> shift;
+ int oldRegionZ = oldChunkZ >> shift;
+
+ int newRegionX = io.papermc.paper.util.CoordinateUtils.getChunkSectionX(newSectionPos) >> shift;
+ int newRegionZ = io.papermc.paper.util.CoordinateUtils.getChunkSectionZ(newSectionPos) >> shift;
+
+ if (oldRegionX != newRegionX || oldRegionZ != newRegionZ) {
+ PersistentEntitySectionManager.this.removeNavigatorsFromData((Entity)this.entity, oldChunkX, oldChunkZ);
+ }
+ // Paper end
if (!this.currentSection.remove(this.entity)) {
PersistentEntitySectionManager.LOGGER.warn("Entity {} wasn't found in section {} (moving to {})", this.entity, SectionPos.of(this.currentSectionKey), i);
@@ -0,0 +0,0 @@ public class PersistentEntitySectionManager<T extends EntityAccess> implements A
entitysection.add(this.entity); // CraftBukkit - decompile error
this.currentSection = entitysection;
this.currentSectionKey = i;
+ // Paper start
+ if ((oldRegionX != newRegionX || oldRegionZ != newRegionZ) && oldVisibility.isTicking() && entitysection.getStatus().isTicking()) {
+ PersistentEntitySectionManager.this.addNavigatorsIfPathingToRegion((Entity)this.entity);
+ }
+ // Paper end
this.updateStatus(visibility, entitysection.getStatus());
}

View File

@ -0,0 +1,94 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Spottedleaf <spottedleaf@spottedleaf.dev>
Date: Thu, 7 May 2020 05:48:54 -0700
Subject: [PATCH] Optimise chunk tick iteration
Use a dedicated list of entity ticking chunks to reduce the cost
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.lastSpawnState = spawnercreature_d;
this.level.getProfiler().pop();
- List<ChunkHolder> list = Lists.newArrayList(this.chunkMap.getChunks());
-
- Collections.shuffle(list);
+ // Paper - moved down, enabled if per-player = false
// Paper - moved natural spawn event up
this.level.timings.chunkTicks.startTiming(); // Paper
- list.forEach((playerchunk) -> {
- Optional<LevelChunk> optional = ((Either) playerchunk.getTickingChunkFuture().getNow(ChunkHolder.UNLOADED_LEVEL_CHUNK)).left();
-
- if (optional.isPresent()) {
- LevelChunk chunk = (LevelChunk) optional.get();
+ // Paper start
+ java.util.Iterator<LevelChunk> iterator;
+ if (this.level.paperConfig.perPlayerMobSpawns) {
+ iterator = this.entityTickingChunks.iterator();
+ } else {
+ iterator = this.entityTickingChunks.unsafeIterator();
+ List<LevelChunk> shuffled = new java.util.ArrayList<>(this.entityTickingChunks.size());
+ while (iterator.hasNext()) {
+ shuffled.add(iterator.next());
+ }
+ Collections.shuffle(shuffled);
+ iterator = shuffled.iterator();
+ }
+ try { while (iterator.hasNext()) {
+ LevelChunk chunk = iterator.next();
+ ChunkHolder playerchunk = chunk.playerChunk;
+ if (playerchunk != null) {
+ this.level.getProfiler().push("broadcast");
+ this.level.timings.broadcastChunkUpdates.startTiming(); // Paper - timings
+ playerchunk.broadcastChanges(chunk);
+ this.level.timings.broadcastChunkUpdates.stopTiming(); // Paper - timings
+ this.level.getProfiler().pop();
+ // Paper end
ChunkPos chunkcoordintpair = chunk.getPos();
- if (this.level.isPositionEntityTicking(chunkcoordintpair) && !this.chunkMap.isOutsideOfRange(playerchunk, chunkcoordintpair, false)) { // Paper - optimise isOutsideOfRange
+ if ((true || this.level.isPositionEntityTicking(chunkcoordintpair)) && !this.chunkMap.isOutsideOfRange(playerchunk, chunkcoordintpair, false)) { // Paper - optimise isOutsideOfRange // Paper - we only iterate entity ticking chunks
chunk.setInhabitedTime(chunk.getInhabitedTime() + j);
if (flag1 && (this.spawnEnemies || this.spawnFriendlies) && this.level.getWorldBorder().isWithinBounds(chunk.getPos()) && !this.chunkMap.isOutsideOfRange(playerchunk, chunkcoordintpair, true)) { // Spigot // Paper - optimise isOutsideOfRange
NaturalSpawner.spawnForChunk(this.level, chunk, spawnercreature_d, this.spawnFriendlies, this.spawnEnemies, flag2);
@@ -0,0 +0,0 @@ public class ServerChunkCache extends ChunkSource {
// this.level.timings.doTickTiles.stopTiming(); // Spigot // Paper
}
}
- });
+ } // Paper start - optimise chunk tick iteration
+ } finally {
+ if (iterator instanceof io.papermc.paper.util.maplist.IteratorSafeOrderedReferenceSet.Iterator) {
+ ((io.papermc.paper.util.maplist.IteratorSafeOrderedReferenceSet.Iterator<LevelChunk>)iterator).finishedIterating();
+ }
+ }
+ // Paper end - optimise chunk tick iteration
this.level.timings.chunkTicks.stopTiming(); // Paper
this.level.getProfiler().push("customSpawners");
if (flag1) {
@@ -0,0 +0,0 @@ public class ServerChunkCache extends ChunkSource {
} // Paper - timings
}
- this.level.getProfiler().popPush("broadcast");
- this.chunkMap.getChunks().forEach((playerchunk) -> { // Paper - no... just no...
- Optional<LevelChunk> optional = ((Either) playerchunk.getTickingChunkFuture().getNow(ChunkHolder.UNLOADED_LEVEL_CHUNK)).left(); // CraftBukkit - decompile error
-
- Objects.requireNonNull(playerchunk);
-
- // Paper start - timings
- optional.ifPresent(chunk -> {
- this.level.timings.broadcastChunkUpdates.startTiming(); // Paper - timings
- playerchunk.broadcastChanges(chunk);
- this.level.timings.broadcastChunkUpdates.stopTiming(); // Paper - timings
- });
- // Paper end
- });
- this.level.getProfiler().pop();
+ // Paper - no, iterating just ONCE is expensive enough! Don't do it TWICE! Code moved up
this.level.getProfiler().pop();
}

View File

@ -0,0 +1,168 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Spottedleaf <spottedleaf@spottedleaf.dev>
Date: Thu, 2 Jul 2020 12:02:43 -0700
Subject: [PATCH] Optimise collision checking in player move packet handling
Move collision logic to just the hasNewCollision call instead of getCubes + hasNewCollision
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 ServerPlayerConnection, Ser
return;
}
- boolean flag = worldserver.noCollision(entity, entity.getBoundingBox().deflate(0.0625D));
+ AABB oldBox = entity.getBoundingBox(); // Paper - copy from player movement packet
d6 = d3 - this.vehicleLastGoodX; // Paper - diff on change, used for checking large move vectors above
d7 = d4 - this.vehicleLastGoodY - 1.0E-6D; // Paper - diff on change, used for checking large move vectors above
d8 = d5 - this.vehicleLastGoodZ; // Paper - diff on change, used for checking large move vectors above
entity.move(MoverType.PLAYER, new Vec3(d6, d7, d8));
+ boolean didCollide = toX != entity.getX() || toY != entity.getY() || toZ != entity.getZ(); // Paper - needed here as the difference in Y can be reset - also note: this is only a guess at whether collisions took place, floating point errors can make this true when it shouldn't be...
double d11 = d7;
d6 = d3 - entity.getX();
@@ -0,0 +0,0 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Ser
boolean flag1 = false;
if (d10 > org.spigotmc.SpigotConfig.movedWronglyThreshold) { // Spigot
- flag1 = true;
+ flag1 = true; // Paper - diff on change, this should be moved wrongly
ServerGamePacketListenerImpl.LOGGER.warn("{} (vehicle of {}) moved wrongly! {}", entity.getName().getString(), this.player.getName().getString(), Math.sqrt(d10));
}
Location curPos = this.getCraftPlayer().getLocation(); // Spigot
entity.absMoveTo(d3, d4, d5, f, f1);
this.player.absMoveTo(d3, d4, d5, this.player.getYRot(), this.player.getXRot()); // CraftBukkit
- boolean flag2 = worldserver.noCollision(entity, entity.getBoundingBox().deflate(0.0625D));
-
- if (flag && (flag1 || !flag2)) {
+ // Paper start - optimise out extra getCubes
+ boolean teleportBack = flag1; // violating this is always a fail
+ if (!teleportBack) {
+ // note: only call after setLocation, or else getBoundingBox is wrong
+ AABB newBox = entity.getBoundingBox();
+ if (didCollide || !oldBox.equals(newBox)) {
+ teleportBack = this.hasNewCollision(worldserver, entity, oldBox, newBox);
+ } // else: no collision at all detected, why do we care?
+ }
+ if (teleportBack) { // Paper end - optimise out extra getCubes
entity.absMoveTo(d0, d1, d2, f, f1);
this.player.absMoveTo(d0, d1, d2, this.player.getYRot(), this.player.getXRot()); // CraftBukkit
this.connection.send(new ClientboundMoveVehiclePacket(entity));
@@ -0,0 +0,0 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Ser
}
private boolean noBlocksAround(Entity entity) {
- return entity.level.getBlockStates(entity.getBoundingBox().inflate(0.0625D).expandTowards(0.0D, -0.55D, 0.0D)).allMatch(BlockBehaviour.BlockStateBase::isAir);
+ // Paper start - stop using streams, this is already a known fixed problem in Entity#move
+ AABB box = entity.getBoundingBox().inflate(0.0625D).expandTowards(0.0D, -0.55D, 0.0D);
+ int minX = Mth.floor(box.minX);
+ int minY = Mth.floor(box.minY);
+ int minZ = Mth.floor(box.minZ);
+ int maxX = Mth.floor(box.maxX);
+ int maxY = Mth.floor(box.maxY);
+ int maxZ = Mth.floor(box.maxZ);
+
+ Level world = entity.level;
+ BlockPos.MutableBlockPos pos = new BlockPos.MutableBlockPos();
+
+ for (int y = minY; y <= maxY; ++y) {
+ for (int z = minZ; z <= maxZ; ++z) {
+ for (int x = minX; x <= maxX; ++x) {
+ pos.set(x, y, z);
+ BlockState type = world.getTypeIfLoaded(pos);
+ if (type != null && !type.isAir()) {
+ return false;
+ }
+ }
+ }
+ }
+
+ return true;
+ // Paper end - stop using streams, this is already a known fixed problem in Entity#move
}
@Override
@@ -0,0 +0,0 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Ser
}
if (this.awaitingPositionFromClient != null) {
- if (this.tickCount - this.awaitingTeleportTime > 20) {
+ if (false && this.tickCount - this.awaitingTeleportTime > 20) { // Paper - this will greatly screw with clients with > 1000ms RTT
this.awaitingTeleportTime = this.tickCount;
this.teleport(this.awaitingPositionFromClient.x, this.awaitingPositionFromClient.y, this.awaitingPositionFromClient.z, this.player.getYRot(), this.player.getXRot());
}
@@ -0,0 +0,0 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Ser
}
}
- AABB axisalignedbb = this.player.getBoundingBox();
+ AABB axisalignedbb = this.player.getBoundingBox(); // Paper - diff on change, should be old AABB
d7 = d0 - this.lastGoodX; // Paper - diff on change, used for checking large move vectors above
d8 = d1 - this.lastGoodY; // Paper - diff on change, used for checking large move vectors above
@@ -0,0 +0,0 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Ser
}
this.player.move(MoverType.PLAYER, new Vec3(d7, d8, d9));
+ boolean didCollide = toX != this.player.getX() || toY != this.player.getY() || toZ != this.player.getZ(); // Paper - needed here as the difference in Y can be reset - also note: this is only a guess at whether collisions took place, floating point errors can make this true when it shouldn't be...
this.player.setOnGround(packet.isOnGround()); // CraftBukkit - SPIGOT-5810, SPIGOT-5835: reset by this.player.move
// Paper start - prevent position desync
if (this.awaitingPositionFromClient != null) {
@@ -0,0 +0,0 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Ser
boolean flag1 = false;
if (!this.player.isChangingDimension() && d11 > org.spigotmc.SpigotConfig.movedWronglyThreshold && !this.player.isSleeping() && !this.player.gameMode.isCreative() && this.player.gameMode.getGameModeForPlayer() != GameType.SPECTATOR) { // Spigot
- flag1 = true;
+ flag1 = true; // Paper - diff on change, this should be moved wrongly
ServerGamePacketListenerImpl.LOGGER.warn("{} moved wrongly!", this.player.getName().getString());
}
this.player.absMoveTo(d0, d1, d2, f, f1);
- if (!this.player.noPhysics && !this.player.isSleeping() && (flag1 && worldserver.noCollision(this.player, axisalignedbb) || this.isPlayerCollidingWithAnythingNew((LevelReader) worldserver, axisalignedbb))) {
+ // Paper start - optimise out extra getCubes
+ // Original for reference:
+ // boolean teleportBack = flag1 && worldserver.getCubes(this.player, axisalignedbb) || (didCollide && this.a((IWorldReader) worldserver, axisalignedbb));
+ boolean teleportBack = flag1; // violating this is always a fail
+ if (!this.player.noPhysics && !this.player.isSleeping() && !teleportBack) {
+ AABB newBox = this.player.getBoundingBox();
+ if (didCollide || !axisalignedbb.equals(newBox)) {
+ // note: only call after setLocation, or else getBoundingBox is wrong
+ teleportBack = this.hasNewCollision(worldserver, this.player, axisalignedbb, newBox);
+ } // else: no collision at all detected, why do we care?
+ }
+ if (!this.player.noPhysics && !this.player.isSleeping() && teleportBack) { // Paper end - optimise out extra getCubes
this.teleport(d3, d4, d5, f, f1);
} else {
// CraftBukkit start - fire PlayerMoveEvent
@@ -0,0 +0,0 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Ser
}
}
+ // Paper start - optimise out extra getCubes
+ private boolean hasNewCollision(final ServerLevel world, final Entity entity, final AABB oldBox, final AABB newBox) {
+ final List<AABB> collisions = io.papermc.paper.util.CachedLists.getTempCollisionList();
+ try {
+ io.papermc.paper.util.CollisionUtil.getCollisions(world, entity, newBox, collisions, false, true,
+ true, false, null, null);
+
+ for (int i = 0, len = collisions.size(); i < len; ++i) {
+ final AABB box = collisions.get(i);
+ if (!io.papermc.paper.util.CollisionUtil.voxelShapeIntersect(box, oldBox)) {
+ return true;
+ }
+ }
+
+ return false;
+ } finally {
+ io.papermc.paper.util.CachedLists.returnTempCollisionList(collisions);
+ }
+ }
+ // Paper end - optimise out extra getCubes
+
private boolean isPlayerCollidingWithAnythingNew(LevelReader world, AABB box) {
Stream<VoxelShape> stream = world.getCollisions(this.player, this.player.getBoundingBox().deflate(9.999999747378752E-6D), (entity) -> {
return true;

View File

@ -0,0 +1,992 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Spottedleaf <Spottedleaf@users.noreply.github.com>
Date: Sun, 31 Jan 2021 02:29:24 -0800
Subject: [PATCH] Optimise general POI access
There are a couple of problems with mojang's POI code.
Firstly, it's all streams. Unsurprisingly, stacking
streams on top of each other is horrible for performance
and ultimately took up half of a villager's tick!
Secondly, sometime's the search radius is large and there are
a significant number of poi entries per chunk section. Even
removing streams at this point doesn't help much. The only solution
is to start at the search point and iterate outwards. This
type of approach shows massive gains for portals, simply because
we can avoid sync loading a large area of chunks. I also tested
a massive farm I found in JellySquid's discord, which showed
to benefit significantly simply because the farm had so many
portal blocks that searching through them all was very slow.
Great care has been taken so that behavior remains identical to
vanilla, however I cannot account for oddball Stream API
implementations, if they even exist (streams can technically
be loose with iteration order in a sorted stream given its
source stream is not tagged with ordered, and mojang does not
tag the source stream as ordered). However in my testing on openjdk
there showed no difference, as expected.
This patch also specifically optimises other areas of code to
use PoiAccess. For example, some villager AI and portaling code
had to be specifically modified.
diff --git a/src/main/java/io/papermc/paper/util/PoiAccess.java b/src/main/java/io/papermc/paper/util/PoiAccess.java
new file mode 100644
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
--- /dev/null
+++ b/src/main/java/io/papermc/paper/util/PoiAccess.java
@@ -0,0 +0,0 @@
+package io.papermc.paper.util;
+
+import it.unimi.dsi.fastutil.doubles.Double2ObjectMap;
+import it.unimi.dsi.fastutil.doubles.Double2ObjectRBTreeMap;
+import it.unimi.dsi.fastutil.longs.LongArrayFIFOQueue;
+import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
+import net.minecraft.core.BlockPos;
+import net.minecraft.util.Mth;
+import net.minecraft.world.entity.ai.village.poi.PoiManager;
+import net.minecraft.world.entity.ai.village.poi.PoiRecord;
+import net.minecraft.world.entity.ai.village.poi.PoiSection;
+import net.minecraft.world.entity.ai.village.poi.PoiType;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.function.Predicate;
+
+/**
+ * Provides optimised access to POI data. All returned values will be identical to vanilla.
+ */
+public final class PoiAccess {
+
+ protected static double clamp(final double val, final double min, final double max) {
+ return (val < min ? min : (val > max ? max : val));
+ }
+
+ protected static double getSmallestDistanceSquared(final double boxMinX, final double boxMinY, final double boxMinZ,
+ final double boxMaxX, final double boxMaxY, final double boxMaxZ,
+
+ final double circleX, final double circleY, final double circleZ) {
+ // is the circle center inside the box?
+ if (circleX >= boxMinX && circleX <= boxMaxX && circleY >= boxMinY && circleY <= boxMaxY && circleZ >= boxMinZ && circleZ <= boxMaxZ) {
+ return 0.0;
+ }
+
+ final double boxWidthX = (boxMaxX - boxMinX) / 2.0;
+ final double boxWidthY = (boxMaxY - boxMinY) / 2.0;
+ final double boxWidthZ = (boxMaxZ - boxMinZ) / 2.0;
+
+ final double boxCenterX = (boxMinX + boxMaxX) / 2.0;
+ final double boxCenterY = (boxMinY + boxMaxY) / 2.0;
+ final double boxCenterZ = (boxMinZ + boxMaxZ) / 2.0;
+
+ double centerDiffX = circleX - boxCenterX;
+ double centerDiffY = circleY - boxCenterY;
+ double centerDiffZ = circleZ - boxCenterZ;
+
+ centerDiffX = circleX - (clamp(centerDiffX, -boxWidthX, boxWidthX) + boxCenterX);
+ centerDiffY = circleY - (clamp(centerDiffY, -boxWidthY, boxWidthY) + boxCenterY);
+ centerDiffZ = circleZ - (clamp(centerDiffZ, -boxWidthZ, boxWidthZ) + boxCenterZ);
+
+ return (centerDiffX * centerDiffX) + (centerDiffY * centerDiffY) + (centerDiffZ * centerDiffZ);
+ }
+
+
+ // key is:
+ // upper 32 bits:
+ // upper 16 bits: max y section
+ // lower 16 bits: min y section
+ // lower 32 bits:
+ // upper 16 bits: section
+ // lower 16 bits: radius
+ protected static long getKey(final int minSection, final int maxSection, final int section, final int radius) {
+ return (
+ (maxSection & 0xFFFFL) << (64 - 16)
+ | (minSection & 0xFFFFL) << (64 - 32)
+ | (section & 0xFFFFL) << (64 - 48)
+ | (radius & 0xFFFFL) << (64 - 64)
+ );
+ }
+
+ // only includes x/z axis
+ // finds the closest poi data by distance.
+ public static BlockPos findClosestPoiDataPosition(final PoiManager poiStorage,
+ final Predicate<PoiType> villagePlaceType,
+ // position predicate must not modify chunk POI
+ final Predicate<BlockPos> positionPredicate,
+ final BlockPos sourcePosition,
+ final int range, // distance on x y z axis
+ final double maxDistance,
+ final PoiManager.Occupancy occupancy,
+ final boolean load) {
+ final PoiRecord ret = findClosestPoiDataRecord(
+ poiStorage, villagePlaceType, positionPredicate, sourcePosition, range, maxDistance, occupancy, load
+ );
+
+ return ret == null ? null : ret.getPos();
+ }
+
+ // only includes x/z axis
+ // finds the closest poi data by distance. if multiple match the same distance, then they all are returned.
+ public static void findClosestPoiDataPositions(final PoiManager poiStorage,
+ final Predicate<PoiType> villagePlaceType,
+ // position predicate must not modify chunk POI
+ final Predicate<BlockPos> positionPredicate,
+ final BlockPos sourcePosition,
+ final int range, // distance on x y z axis
+ final double maxDistance,
+ final PoiManager.Occupancy occupancy,
+ final boolean load,
+ final Set<BlockPos> ret) {
+ final Set<BlockPos> positions = new HashSet<>();
+ // pos predicate is last thing that runs before adding to ret.
+ final Predicate<BlockPos> newPredicate = (final BlockPos pos) -> {
+ if (positionPredicate != null && !positionPredicate.test(pos)) {
+ return false;
+ }
+ return positions.add(pos.immutable());
+ };
+
+ final List<PoiRecord> toConvert = new ArrayList<>();
+ findClosestPoiDataRecords(
+ poiStorage, villagePlaceType, newPredicate, sourcePosition, range, maxDistance, occupancy, load, toConvert
+ );
+
+ for (final PoiRecord record : toConvert) {
+ ret.add(record.getPos());
+ }
+ }
+
+ // only includes x/z axis
+ // finds the closest poi data by distance.
+ public static PoiRecord findClosestPoiDataRecord(final PoiManager poiStorage,
+ final Predicate<PoiType> villagePlaceType,
+ // position predicate must not modify chunk POI
+ final Predicate<BlockPos> positionPredicate,
+ final BlockPos sourcePosition,
+ final int range, // distance on x y z axis
+ final double maxDistance,
+ final PoiManager.Occupancy occupancy,
+ final boolean load) {
+ final List<PoiRecord> ret = new ArrayList<>();
+ findClosestPoiDataRecords(
+ poiStorage, villagePlaceType, positionPredicate, sourcePosition, range, maxDistance, occupancy, load, ret
+ );
+ return ret.isEmpty() ? null : ret.get(0);
+ }
+
+ // only includes x/z axis
+ // finds the closest poi data by distance. if multiple match the same distance, then they all are returned.
+ public static void findClosestPoiDataRecords(final PoiManager poiStorage,
+ final Predicate<PoiType> villagePlaceType,
+ // position predicate must not modify chunk POI
+ final Predicate<BlockPos> positionPredicate,
+ final BlockPos sourcePosition,
+ final int range, // distance on x y z axis
+ final double maxDistance,
+ final PoiManager.Occupancy occupancy,
+ final boolean load,
+ final List<PoiRecord> ret) {
+ final Predicate<? super PoiRecord> occupancyFilter = occupancy.getTest();
+
+ final List<PoiRecord> closestRecords = new ArrayList<>();
+ double closestDistanceSquared = maxDistance * maxDistance;
+
+ final int lowerX = Mth.floor(sourcePosition.getX() - range) >> 4;
+ final int lowerY = WorldUtil.getMinSection(poiStorage.world);
+ final int lowerZ = Mth.floor(sourcePosition.getZ() - range) >> 4;
+ final int upperX = Mth.floor(sourcePosition.getX() + range) >> 4;
+ final int upperY = WorldUtil.getMaxSection(poiStorage.world);
+ final int upperZ = Mth.floor(sourcePosition.getZ() + range) >> 4;
+
+ final int centerX = sourcePosition.getX() >> 4;
+ final int centerY = Mth.clamp(sourcePosition.getY() >> 4, lowerY, upperY);
+ final int centerZ = sourcePosition.getZ() >> 4;
+
+ final LongArrayFIFOQueue queue = new LongArrayFIFOQueue();
+ queue.enqueue(CoordinateUtils.getChunkSectionKey(centerX, centerY, centerZ));
+ final LongOpenHashSet seen = new LongOpenHashSet();
+
+ while (!queue.isEmpty()) {
+ final long key = queue.dequeueLong();
+ final int sectionX = CoordinateUtils.getChunkSectionX(key);
+ final int sectionY = CoordinateUtils.getChunkSectionY(key);
+ final int sectionZ = CoordinateUtils.getChunkSectionZ(key);
+
+ if (sectionX < lowerX || sectionX > upperX || sectionY < lowerY || sectionY > upperY || sectionZ < lowerZ || sectionZ > upperZ) {
+ // out of bound chunk
+ continue;
+ }
+
+ final double sectionDistanceSquared = getSmallestDistanceSquared(
+ (sectionX << 4) + 0.5,
+ (sectionY << 4) + 0.5,
+ (sectionZ << 4) + 0.5,
+ (sectionX << 4) + 15.5,
+ (sectionY << 4) + 15.5,
+ (sectionZ << 4) + 15.5,
+ (double)sourcePosition.getX(), (double)sourcePosition.getY(), (double)sourcePosition.getZ()
+ );
+ if (sectionDistanceSquared > closestDistanceSquared) {
+ continue;
+ }
+
+ // queue all neighbours
+ for (int dz = -1; dz <= 1; ++dz) {
+ for (int dx = -1; dx <= 1; ++dx) {
+ for (int dy = -1; dy <= 1; ++dy) {
+ // -1 and 1 have the 1st bit set. so just add up the first bits, and it will tell us how many
+ // values are set. we only care about cardinal neighbours, so, we only care if one value is set
+ if ((dx & 1) + (dy & 1) + (dz & 1) != 1) {
+ continue;
+ }
+
+ final int neighbourX = sectionX + dx;
+ final int neighbourY = sectionY + dy;
+ final int neighbourZ = sectionZ + dz;
+
+ final long neighbourKey = CoordinateUtils.getChunkSectionKey(neighbourX, neighbourY, neighbourZ);
+ if (seen.add(neighbourKey)) {
+ queue.enqueue(neighbourKey);
+ }
+ }
+ }
+ }
+
+ final Optional<PoiSection> poiSectionOptional = load ? poiStorage.getOrLoad(key) : poiStorage.get(key);
+
+ if (poiSectionOptional == null || !poiSectionOptional.isPresent()) {
+ continue;
+ }
+
+ final PoiSection poiSection = poiSectionOptional.orElse(null);
+
+ final Map<PoiType, Set<PoiRecord>> sectionData = poiSection.getData();
+ if (sectionData.isEmpty()) {
+ continue;
+ }
+
+ // now we search the section data
+ for (final Map.Entry<PoiType, Set<PoiRecord>> entry : sectionData.entrySet()) {
+ if (!villagePlaceType.test(entry.getKey())) {
+ // filter out by poi type
+ continue;
+ }
+
+ // now we can look at the poi data
+ for (final PoiRecord poiData : entry.getValue()) {
+ if (!occupancyFilter.test(poiData)) {
+ // filter by occupancy
+ continue;
+ }
+
+ final BlockPos poiPosition = poiData.getPos();
+
+ if (Math.abs(poiPosition.getX() - sourcePosition.getX()) > range
+ || Math.abs(poiPosition.getZ() - sourcePosition.getZ()) > range) {
+ // out of range for square radius
+ continue;
+ }
+
+ // it's important that it's poiPosition.distSqr(source) : the value actually is different IF the values are swapped!
+ final double dataRange = poiPosition.distSqr(sourcePosition);
+
+ if (dataRange > closestDistanceSquared) {
+ // out of range for distance check
+ continue;
+ }
+
+ if (positionPredicate != null && !positionPredicate.test(poiPosition)) {
+ // filter by position
+ continue;
+ }
+
+ if (dataRange < closestDistanceSquared) {
+ closestRecords.clear();
+ closestDistanceSquared = dataRange;
+ }
+ closestRecords.add(poiData);
+ }
+ }
+ }
+
+ // uh oh! we might have multiple records that match the distance sorting!
+ // we need to re-order our results by the way vanilla would have iterated over them.
+ closestRecords.sort((record1, record2) -> {
+ // vanilla iterates the same way we do for data inside sections, so we know the ordering inside a section
+ // is fine and should be preserved (this sort is stable so we're good there)
+ // but they iterate sections by x then by z (like the following)
+ // for (int x = -dx; x <= dx; ++x)
+ // for (int z = -dz; z <= dz; ++z)
+ // ....
+ // so we need to reorder such that records with lower chunk z, then lower chunk x come first
+ final BlockPos pos1 = record1.getPos();
+ final BlockPos pos2 = record2.getPos();
+
+ final int cx1 = pos1.getX() >> 4;
+ final int cz1 = pos1.getZ() >> 4;
+
+ final int cx2 = pos2.getX() >> 4;
+ final int cz2 = pos2.getZ() >> 4;
+
+ if (cz2 != cz1) {
+ // want smaller z
+ return Integer.compare(cz1, cz2);
+ }
+
+ if (cx2 != cx1) {
+ // want smaller x
+ return Integer.compare(cx1, cx2);
+ }
+
+ // same chunk
+ // once vanilla has the chunk, it will iterate from all of the chunk sections starting from smaller y
+ // so now we just compare section y, wanting smaller y
+
+ return Integer.compare(pos1.getY() >> 4, pos2.getY() >> 4);
+ });
+
+ // now we match perfectly what vanilla would have outputted, without having to search the whole radius (hopefully).
+ ret.addAll(closestRecords);
+ }
+
+ // finds the closest poi entry pos.
+ public static BlockPos findNearestPoiPosition(final PoiManager poiStorage,
+ final Predicate<PoiType> villagePlaceType,
+ // position predicate must not modify chunk POI
+ final Predicate<BlockPos> positionPredicate,
+ final BlockPos sourcePosition,
+ final int range, // distance on x y z axis
+ final double maxDistance,
+ final PoiManager.Occupancy occupancy,
+ final boolean load) {
+ final PoiRecord ret = findNearestPoiRecord(
+ poiStorage, villagePlaceType, positionPredicate, sourcePosition, range, maxDistance, occupancy, load
+ );
+ return ret == null ? null : ret.getPos();
+ }
+
+ // finds the closest `max` poi entry positions.
+ public static void findNearestPoiPositions(final PoiManager poiStorage,
+ final Predicate<PoiType> villagePlaceType,
+ // position predicate must not modify chunk POI
+ final Predicate<BlockPos> positionPredicate,
+ final BlockPos sourcePosition,
+ final int range, // distance on x y z axis
+ final double maxDistance,
+ final PoiManager.Occupancy occupancy,
+ final boolean load,
+ final int max,
+ final List<BlockPos> ret) {
+ final Set<BlockPos> positions = new HashSet<>();
+ // pos predicate is last thing that runs before adding to ret.
+ final Predicate<BlockPos> newPredicate = (final BlockPos pos) -> {
+ if (positionPredicate != null && !positionPredicate.test(pos)) {
+ return false;
+ }
+ return positions.add(pos.immutable());
+ };
+
+ final List<PoiRecord> toConvert = new ArrayList<>();
+ findNearestPoiRecords(
+ poiStorage, villagePlaceType, newPredicate, sourcePosition, range, maxDistance, occupancy, load, max, toConvert
+ );
+
+ for (final PoiRecord record : toConvert) {
+ ret.add(record.getPos());
+ }
+ }
+
+ // finds the closest poi entry.
+ public static PoiRecord findNearestPoiRecord(final PoiManager poiStorage,
+ final Predicate<PoiType> villagePlaceType,
+ // position predicate must not modify chunk POI
+ final Predicate<BlockPos> positionPredicate,
+ final BlockPos sourcePosition,
+ final int range, // distance on x y z axis
+ final double maxDistance,
+ final PoiManager.Occupancy occupancy,
+ final boolean load) {
+ final List<PoiRecord> ret = new ArrayList<>();
+ findNearestPoiRecords(
+ poiStorage, villagePlaceType, positionPredicate, sourcePosition, range, maxDistance, occupancy, load,
+ 1, ret
+ );
+ return ret.isEmpty() ? null : ret.get(0);
+ }
+
+ // finds the closest `max` poi entries.
+ public static void findNearestPoiRecords(final PoiManager poiStorage,
+ final Predicate<PoiType> villagePlaceType,
+ // position predicate must not modify chunk POI
+ final Predicate<BlockPos> positionPredicate,
+ final BlockPos sourcePosition,
+ final int range, // distance on x y z axis
+ final double maxDistance,
+ final PoiManager.Occupancy occupancy,
+ final boolean load,
+ final int max,
+ final List<PoiRecord> ret) {
+ final Predicate<? super PoiRecord> occupancyFilter = occupancy.getTest();
+
+ final double maxDistanceSquared = maxDistance * maxDistance;
+ final Double2ObjectRBTreeMap<List<PoiRecord>> closestRecords = new Double2ObjectRBTreeMap<>();
+ int totalRecords = 0;
+ double furthestDistanceSquared = maxDistanceSquared;
+
+ final int lowerX = Mth.floor(sourcePosition.getX() - range) >> 4;
+ final int lowerY = WorldUtil.getMinSection(poiStorage.world);
+ final int lowerZ = Mth.floor(sourcePosition.getZ() - range) >> 4;
+ final int upperX = Mth.floor(sourcePosition.getX() + range) >> 4;
+ final int upperY = WorldUtil.getMaxSection(poiStorage.world);
+ final int upperZ = Mth.floor(sourcePosition.getZ() + range) >> 4;
+
+ final int centerX = sourcePosition.getX() >> 4;
+ final int centerY = Mth.clamp(sourcePosition.getY() >> 4, lowerY, upperY);
+ final int centerZ = sourcePosition.getZ() >> 4;
+
+ final LongArrayFIFOQueue queue = new LongArrayFIFOQueue();
+ queue.enqueue(CoordinateUtils.getChunkSectionKey(centerX, centerY, centerZ));
+ final LongOpenHashSet seen = new LongOpenHashSet();
+
+ while (!queue.isEmpty()) {
+ final long key = queue.dequeueLong();
+ final int sectionX = CoordinateUtils.getChunkSectionX(key);
+ final int sectionY = CoordinateUtils.getChunkSectionY(key);
+ final int sectionZ = CoordinateUtils.getChunkSectionZ(key);
+
+ if (sectionX < lowerX || sectionX > upperX || sectionY < lowerY || sectionY > upperY || sectionZ < lowerZ || sectionZ > upperZ) {
+ // out of bound chunk
+ continue;
+ }
+
+ final double sectionDistanceSquared = getSmallestDistanceSquared(
+ (sectionX << 4) + 0.5,
+ (sectionY << 4) + 0.5,
+ (sectionZ << 4) + 0.5,
+ (sectionX << 4) + 15.5,
+ (sectionY << 4) + 15.5,
+ (sectionZ << 4) + 15.5,
+ (double) sourcePosition.getX(), (double) sourcePosition.getY(), (double) sourcePosition.getZ()
+ );
+
+ if (sectionDistanceSquared > (totalRecords >= max ? furthestDistanceSquared : maxDistanceSquared)) {
+ continue;
+ }
+
+ // queue all neighbours
+ for (int dz = -1; dz <= 1; ++dz) {
+ for (int dx = -1; dx <= 1; ++dx) {
+ for (int dy = -1; dy <= 1; ++dy) {
+ // -1 and 1 have the 1st bit set. so just add up the first bits, and it will tell us how many
+ // values are set. we only care about cardinal neighbours, so, we only care if one value is set
+ if ((dx & 1) + (dy & 1) + (dz & 1) != 1) {
+ continue;
+ }
+
+ final int neighbourX = sectionX + dx;
+ final int neighbourY = sectionY + dy;
+ final int neighbourZ = sectionZ + dz;
+
+ final long neighbourKey = CoordinateUtils.getChunkSectionKey(neighbourX, neighbourY, neighbourZ);
+ if (seen.add(neighbourKey)) {
+ queue.enqueue(neighbourKey);
+ }
+ }
+ }
+ }
+
+ final Optional<PoiSection> poiSectionOptional = load ? poiStorage.getOrLoad(key) : poiStorage.get(key);
+
+ if (poiSectionOptional == null || !poiSectionOptional.isPresent()) {
+ continue;
+ }
+
+ final PoiSection poiSection = poiSectionOptional.orElse(null);
+
+ final Map<PoiType, Set<PoiRecord>> sectionData = poiSection.getData();
+ if (sectionData.isEmpty()) {
+ continue;
+ }
+
+ // now we search the section data
+ for (final Map.Entry<PoiType, Set<PoiRecord>> entry : sectionData.entrySet()) {
+ if (!villagePlaceType.test(entry.getKey())) {
+ // filter out by poi type
+ continue;
+ }
+
+ // now we can look at the poi data
+ for (final PoiRecord poiData : entry.getValue()) {
+ if (!occupancyFilter.test(poiData)) {
+ // filter by occupancy
+ continue;
+ }
+
+ final BlockPos poiPosition = poiData.getPos();
+
+ if (Math.abs(poiPosition.getX() - sourcePosition.getX()) > range
+ || Math.abs(poiPosition.getZ() - sourcePosition.getZ()) > range) {
+ // out of range for square radius
+ continue;
+ }
+
+ // it's important that it's poiPosition.distSqr(source) : the value actually is different IF the values are swapped!
+ final double dataRange = poiPosition.distSqr(sourcePosition);
+
+ if (dataRange > maxDistanceSquared) {
+ // out of range for distance check
+ continue;
+ }
+
+ if (dataRange > furthestDistanceSquared && totalRecords >= max) {
+ // out of range for distance check
+ continue;
+ }
+
+ if (positionPredicate != null && !positionPredicate.test(poiPosition)) {
+ // filter by position
+ continue;
+ }
+
+ if (dataRange > furthestDistanceSquared) {
+ // we know totalRecords < max, so this entry is now our furthest
+ furthestDistanceSquared = dataRange;
+ }
+
+ closestRecords.computeIfAbsent(dataRange, (final double unused) -> {
+ return new ArrayList<>();
+ }).add(poiData);
+
+ if (++totalRecords >= max) {
+ if (closestRecords.size() >= 2) {
+ int entriesInClosest = 0;
+ final Iterator<Double2ObjectMap.Entry<List<PoiRecord>>> iterator = closestRecords.double2ObjectEntrySet().iterator();
+ double nextFurthestDistanceSquared = 0.0;
+
+ for (int i = 0, len = closestRecords.size() - 1; i < len; ++i) {
+ final Double2ObjectMap.Entry<List<PoiRecord>> recordEntry = iterator.next();
+ entriesInClosest += recordEntry.getValue().size();
+ nextFurthestDistanceSquared = recordEntry.getDoubleKey();
+ }
+
+ if (entriesInClosest >= max) {
+ // the last set of entries at range wont even be considered for sure... nuke em
+ final Double2ObjectMap.Entry<List<PoiRecord>> recordEntry = iterator.next();
+ totalRecords -= recordEntry.getValue().size();
+ iterator.remove();
+
+ furthestDistanceSquared = nextFurthestDistanceSquared;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ final List<PoiRecord> closestRecordsUnsorted = new ArrayList<>();
+
+ // we're done here, so now just flatten the map and sort it.
+
+ for (final List<PoiRecord> records : closestRecords.values()) {
+ closestRecordsUnsorted.addAll(records);
+ }
+
+ // uh oh! we might have multiple records that match the distance sorting!
+ // we need to re-order our results by the way vanilla would have iterated over them.
+ closestRecordsUnsorted.sort((record1, record2) -> {
+ // vanilla iterates the same way we do for data inside sections, so we know the ordering inside a section
+ // is fine and should be preserved (this sort is stable so we're good there)
+ // but they iterate sections by x then by z (like the following)
+ // for (int x = -dx; x <= dx; ++x)
+ // for (int z = -dz; z <= dz; ++z)
+ // ....
+ // so we need to reorder such that records with lower chunk z, then lower chunk x come first
+ final BlockPos pos1 = record1.getPos();
+ final BlockPos pos2 = record2.getPos();
+
+ final int cx1 = pos1.getX() >> 4;
+ final int cz1 = pos1.getZ() >> 4;
+
+ final int cx2 = pos2.getX() >> 4;
+ final int cz2 = pos2.getZ() >> 4;
+
+ if (cz2 != cz1) {
+ // want smaller z
+ return Integer.compare(cz1, cz2);
+ }
+
+ if (cx2 != cx1) {
+ // want smaller x
+ return Integer.compare(cx1, cx2);
+ }
+
+ // same chunk
+ // once vanilla has the chunk, it will iterate from all of the chunk sections starting from smaller y
+ // so now we just compare section y, wanting smaller section y
+
+ return Integer.compare(pos1.getY() >> 4, pos2.getY() >> 4);
+ });
+
+ // trim out any entries exceeding our maximum
+ for (int i = closestRecordsUnsorted.size() - 1; i >= max; --i) {
+ closestRecordsUnsorted.remove(i);
+ }
+
+ // now we match perfectly what vanilla would have outputted, without having to search the whole radius (hopefully).
+ ret.addAll(closestRecordsUnsorted);
+ }
+
+ public static BlockPos findAnyPoiPosition(final PoiManager poiStorage,
+ final Predicate<PoiType> villagePlaceType,
+ final Predicate<BlockPos> positionPredicate,
+ final BlockPos sourcePosition,
+ final int range, // distance on x y z axis
+ final PoiManager.Occupancy occupancy,
+ final boolean load) {
+ final PoiRecord ret = findAnyPoiRecord(
+ poiStorage, villagePlaceType, positionPredicate, sourcePosition, range, occupancy, load
+ );
+
+ return ret == null ? null : ret.getPos();
+ }
+
+ public static void findAnyPoiPositions(final PoiManager poiStorage,
+ final Predicate<PoiType> villagePlaceType,
+ final Predicate<BlockPos> positionPredicate,
+ final BlockPos sourcePosition,
+ final int range, // distance on x y z axis
+ final PoiManager.Occupancy occupancy,
+ final boolean load,
+ final int max,
+ final List<BlockPos> ret) {
+ final Set<BlockPos> positions = new HashSet<>();
+ // pos predicate is last thing that runs before adding to ret.
+ final Predicate<BlockPos> newPredicate = (final BlockPos pos) -> {
+ if (positionPredicate != null && !positionPredicate.test(pos)) {
+ return false;
+ }
+ return positions.add(pos.immutable());
+ };
+
+ final List<PoiRecord> toConvert = new ArrayList<>();
+ findAnyPoiRecords(
+ poiStorage, villagePlaceType, newPredicate, sourcePosition, range, occupancy, load, max, toConvert
+ );
+
+ for (final PoiRecord record : toConvert) {
+ ret.add(record.getPos());
+ }
+ }
+
+ public static PoiRecord findAnyPoiRecord(final PoiManager poiStorage,
+ final Predicate<PoiType> villagePlaceType,
+ final Predicate<BlockPos> positionPredicate,
+ final BlockPos sourcePosition,
+ final int range, // distance on x y z axis
+ final PoiManager.Occupancy occupancy,
+ final boolean load) {
+ final List<PoiRecord> ret = new ArrayList<>();
+ findAnyPoiRecords(poiStorage, villagePlaceType, positionPredicate, sourcePosition, range, occupancy, load, 1, ret);
+ return ret.isEmpty() ? null : ret.get(0);
+ }
+
+ public static void findAnyPoiRecords(final PoiManager poiStorage,
+ final Predicate<PoiType> villagePlaceType,
+ final Predicate<BlockPos> positionPredicate,
+ final BlockPos sourcePosition,
+ final int range, // distance on x y z axis
+ final PoiManager.Occupancy occupancy,
+ final boolean load,
+ final int max,
+ final List<PoiRecord> ret) {
+ // the biggest issue with the original mojang implementation is that they chain so many streams together
+ // the amount of streams chained just rolls performance, even if nothing is iterated over
+ final Predicate<? super PoiRecord> occupancyFilter = occupancy.getTest();
+ final double rangeSquared = range * range;
+
+ int added = 0;
+
+ // First up, we need to iterate the chunks
+ // all the values here are in chunk sections
+ final int lowerX = Mth.floor(sourcePosition.getX() - range) >> 4;
+ final int lowerY = Math.max(WorldUtil.getMinSection(poiStorage.world), Mth.floor(sourcePosition.getY() - range) >> 4);
+ final int lowerZ = Mth.floor(sourcePosition.getZ() - range) >> 4;
+ final int upperX = Mth.floor(sourcePosition.getX() + range) >> 4;
+ final int upperY = Math.min(WorldUtil.getMaxSection(poiStorage.world), Mth.floor(sourcePosition.getY() + range) >> 4);
+ final int upperZ = Mth.floor(sourcePosition.getZ() + range) >> 4;
+
+ // Vanilla iterates by x until max is reached then increases z
+ // vanilla also searches by increasing Y section value
+ for (int currZ = lowerZ; currZ <= upperZ; ++currZ) {
+ for (int currX = lowerX; currX <= upperX; ++currX) {
+ for (int currY = lowerY; currY <= upperY; ++currY) { // vanilla searches the entire chunk because they're actually stupid. just search the sections we need
+ final Optional<PoiSection> poiSectionOptional = load ? poiStorage.getOrLoad(CoordinateUtils.getChunkSectionKey(currX, currY, currZ)) :
+ poiStorage.get(CoordinateUtils.getChunkSectionKey(currX, currY, currZ));
+ final PoiSection poiSection = poiSectionOptional == null ? null : poiSectionOptional.orElse(null);
+ if (poiSection == null) {
+ continue;
+ }
+
+ final Map<PoiType, Set<PoiRecord>> sectionData = poiSection.getData();
+ if (sectionData.isEmpty()) {
+ continue;
+ }
+
+ // now we search the section data
+ for (final Map.Entry<PoiType, Set<PoiRecord>> entry : sectionData.entrySet()) {
+ if (!villagePlaceType.test(entry.getKey())) {
+ // filter out by poi type
+ continue;
+ }
+
+ // now we can look at the poi data
+ for (final PoiRecord poiData : entry.getValue()) {
+ if (!occupancyFilter.test(poiData)) {
+ // filter by occupancy
+ continue;
+ }
+
+ final BlockPos poiPosition = poiData.getPos();
+
+ if (Math.abs(poiPosition.getX() - sourcePosition.getX()) > range
+ || Math.abs(poiPosition.getZ() - sourcePosition.getZ()) > range) {
+ // out of range for square radius
+ continue;
+ }
+
+ if (poiPosition.distSqr(sourcePosition) > rangeSquared) {
+ // out of range for distance check
+ continue;
+ }
+
+ if (positionPredicate != null && !positionPredicate.test(poiPosition)) {
+ // filter by position
+ continue;
+ }
+
+ // found one!
+ ret.add(poiData);
+ if (++added >= max) {
+ return;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private PoiAccess() {
+ throw new RuntimeException();
+ }
+}
diff --git a/src/main/java/net/minecraft/world/entity/ai/behavior/AcquirePoi.java b/src/main/java/net/minecraft/world/entity/ai/behavior/AcquirePoi.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/world/entity/ai/behavior/AcquirePoi.java
+++ b/src/main/java/net/minecraft/world/entity/ai/behavior/AcquirePoi.java
@@ -0,0 +0,0 @@ public class AcquirePoi extends Behavior<PathfinderMob> {
return true;
}
};
- Set<BlockPos> set = poiManager.findAllClosestFirst(this.poiType.getPredicate(), predicate, entity.blockPosition(), 48, PoiManager.Occupancy.HAS_SPACE).limit(5L).collect(Collectors.toSet());
+ // Paper start - optimise POI access
+ java.util.List<BlockPos> poiposes = new java.util.ArrayList<>();
+ io.papermc.paper.util.PoiAccess.findNearestPoiPositions(poiManager, this.poiType.getPredicate(), predicate, entity.blockPosition(), 48, 48*48, PoiManager.Occupancy.HAS_SPACE, false, 5, poiposes);
+ Set<BlockPos> set = new java.util.HashSet<>(poiposes);
+ // Paper end - optimise POI access
Path path = entity.getNavigation().createPath(set, this.poiType.getValidRange());
if (path != null && path.canReach()) {
BlockPos blockPos = path.getTarget();
diff --git a/src/main/java/net/minecraft/world/entity/ai/sensing/NearestBedSensor.java b/src/main/java/net/minecraft/world/entity/ai/sensing/NearestBedSensor.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/world/entity/ai/sensing/NearestBedSensor.java
+++ b/src/main/java/net/minecraft/world/entity/ai/sensing/NearestBedSensor.java
@@ -0,0 +0,0 @@ public class NearestBedSensor extends Sensor<Mob> {
return true;
}
};
- Stream<BlockPos> stream = poiManager.findAll(PoiType.HOME.getPredicate(), predicate, entity.blockPosition(), 48, PoiManager.Occupancy.ANY);
- Path path = entity.getNavigation().createPath(stream, PoiType.HOME.getValidRange());
+ // Paper start - optimise POI access
+ java.util.List<BlockPos> poiposes = new java.util.ArrayList<>();
+ // don't ask me why it's unbounded. ask mojang.
+ io.papermc.paper.util.PoiAccess.findAnyPoiPositions(poiManager, PoiType.HOME.getPredicate(), predicate, entity.blockPosition(), 48, PoiManager.Occupancy.ANY, false, Integer.MAX_VALUE, poiposes);
+ Path path = entity.getNavigation().createPath(new java.util.HashSet<>(poiposes), PoiType.HOME.getValidRange());
+ // Paper end - optimise POI access
if (path != null && path.canReach()) {
BlockPos blockPos = path.getTarget();
Optional<PoiType> optional = poiManager.getType(blockPos);
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> {
public static final int VILLAGE_SECTION_SIZE = 1;
private final PoiManager.DistanceTracker distanceTracker;
private final LongSet loadedChunks = new LongOpenHashSet();
- private final net.minecraft.server.level.ServerLevel world; // Paper
+ public final net.minecraft.server.level.ServerLevel world; // Paper // Paper public
public PoiManager(File directory, DataFixer dataFixer, boolean dsync, LevelHeightAccessor world) {
super(directory, PoiSection::codec, PoiSection::new, dataFixer, DataFixTypes.POI_CHUNK, dsync, world);
@@ -0,0 +0,0 @@ public class PoiManager extends SectionStorage<PoiSection> {
}
public Optional<BlockPos> find(Predicate<PoiType> typePredicate, Predicate<BlockPos> posPredicate, BlockPos pos, int radius, PoiManager.Occupancy occupationStatus) {
- return this.findAll(typePredicate, posPredicate, pos, radius, occupationStatus).findFirst();
+ // Paper start - re-route to faster logic
+ BlockPos ret = io.papermc.paper.util.PoiAccess.findAnyPoiPosition(this, typePredicate, posPredicate, pos, radius, occupationStatus, false);
+ return Optional.ofNullable(ret);
+ // Paper end - re-route to faster logic
}
public Optional<BlockPos> findClosest(Predicate<PoiType> typePredicate, BlockPos pos, int radius, PoiManager.Occupancy occupationStatus) {
- return this.getInRange(typePredicate, pos, radius, occupationStatus).map(PoiRecord::getPos).min(Comparator.comparingDouble((blockPos2) -> {
- return blockPos2.distSqr(pos);
- }));
+ // Paper start - re-route to faster logic
+ BlockPos ret = io.papermc.paper.util.PoiAccess.findClosestPoiDataPosition(this, typePredicate, null, pos, radius, radius*radius, occupationStatus, false);
+ return Optional.ofNullable(ret);
+ // Paper end - re-route to faster logic
}
public Optional<BlockPos> findClosest(Predicate<PoiType> predicate, Predicate<BlockPos> predicate2, BlockPos blockPos, int i, PoiManager.Occupancy occupancy) {
- return this.getInRange(predicate, blockPos, i, occupancy).map(PoiRecord::getPos).filter(predicate2).min(Comparator.comparingDouble((blockPos2) -> {
- return blockPos2.distSqr(blockPos);
- }));
+ // Paper start - re-route to faster logic
+ BlockPos ret = io.papermc.paper.util.PoiAccess.findClosestPoiDataPosition(this, predicate, predicate2, blockPos, i, i*i, occupancy, false);
+ return Optional.ofNullable(ret);
+ // Paper end - re-route to faster logic
}
public Optional<BlockPos> take(Predicate<PoiType> typePredicate, Predicate<BlockPos> positionPredicate, BlockPos pos, int radius) {
- return this.getInRange(typePredicate, pos, radius, PoiManager.Occupancy.HAS_SPACE).filter((poi) -> {
- return positionPredicate.test(poi.getPos());
- }).findFirst().map((poi) -> {
- poi.acquireTicket();
- return poi.getPos();
- });
+ // Paper start - re-route to faster logic
+ PoiRecord ret = io.papermc.paper.util.PoiAccess.findAnyPoiRecord(
+ this, typePredicate, positionPredicate, pos, radius, PoiManager.Occupancy.HAS_SPACE, false
+ );
+ if (ret == null) {
+ return Optional.empty();
+ }
+ ret.acquireTicket();
+ return Optional.of(ret.getPos());
+ // Paper end - re-route to faster logic
}
public Optional<BlockPos> getRandom(Predicate<PoiType> typePredicate, Predicate<BlockPos> positionPredicate, PoiManager.Occupancy occupationStatus, BlockPos pos, int radius, Random random) {
- List<PoiRecord> list = this.getInRange(typePredicate, pos, radius, occupationStatus).collect(Collectors.toList());
- Collections.shuffle(list, random);
- return list.stream().filter((poiRecord) -> {
- return positionPredicate.test(poiRecord.getPos());
- }).findFirst().map(PoiRecord::getPos);
+ // Paper start - re-route to faster logic
+ List<PoiRecord> list = new java.util.ArrayList<>();
+ io.papermc.paper.util.PoiAccess.findAnyPoiRecords(
+ this, typePredicate, positionPredicate, pos, radius, occupationStatus, false, Integer.MAX_VALUE, list
+ );
+
+ // the old method shuffled the list and then tried to find the first element in it that
+ // matched positionPredicate, however we moved positionPredicate into the poi search. This means we can avoid a
+ // shuffle entirely, and just pick a random element from list
+ if (list.isEmpty()) {
+ return Optional.empty();
+ }
+
+ return Optional.of(list.get(random.nextInt(list.size())).getPos());
+ // Paper end - re-route to faster logic
}
public boolean release(BlockPos pos) {
diff --git a/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiSection.java b/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiSection.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiSection.java
+++ b/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiSection.java
@@ -0,0 +0,0 @@ import org.apache.logging.log4j.Logger;
public class PoiSection {
private static final Logger LOGGER = LogManager.getLogger();
private final Short2ObjectMap<PoiRecord> records = new Short2ObjectOpenHashMap<>();
- private final Map<PoiType, Set<PoiRecord>> byType = Maps.newHashMap();
+ private final Map<PoiType, Set<PoiRecord>> byType = Maps.newHashMap(); public final Map<PoiType, Set<PoiRecord>> getData() { return this.byType; } // Paper - public accessor
private final Runnable setDirty;
private boolean isValid;
diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/SectionStorage.java b/src/main/java/net/minecraft/world/level/chunk/storage/SectionStorage.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/world/level/chunk/storage/SectionStorage.java
+++ b/src/main/java/net/minecraft/world/level/chunk/storage/SectionStorage.java
@@ -0,0 +0,0 @@ public class SectionStorage<R> extends RegionFileStorage implements AutoCloseabl
}
@Nullable
- protected Optional<R> get(long pos) {
+ public Optional<R> get(long pos) { // Paper - public
return this.storage.get(pos);
}
- protected Optional<R> getOrLoad(long pos) {
+ public Optional<R> getOrLoad(long pos) { // Paper - public
if (this.outsideStoredRange(pos)) {
return Optional.empty();
} else {
diff --git a/src/main/java/net/minecraft/world/level/portal/PortalForcer.java b/src/main/java/net/minecraft/world/level/portal/PortalForcer.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/world/level/portal/PortalForcer.java
+++ b/src/main/java/net/minecraft/world/level/portal/PortalForcer.java
@@ -0,0 +0,0 @@ public class PortalForcer {
// int i = flag ? 16 : 128;
// CraftBukkit end
- villageplace.ensureLoadedAndValid(this.level, blockposition, i);
- Optional<PoiRecord> optional = villageplace.getInSquare((villageplacetype) -> {
- return villageplacetype == PoiType.NETHER_PORTAL;
- }, blockposition, i, PoiManager.Occupancy.ANY).sorted(Comparator.comparingDouble((PoiRecord villageplacerecord) -> { // CraftBukkit - decompile error
- return villageplacerecord.getPos().distSqr(blockposition);
- }).thenComparingInt((villageplacerecord) -> {
- return villageplacerecord.getPos().getY();
- })).filter((villageplacerecord) -> {
- return this.level.getBlockState(villageplacerecord.getPos()).hasProperty(BlockStateProperties.HORIZONTAL_AXIS);
- }).findFirst();
+ // Paper start - optimise portals
+ Optional<PoiRecord> optional;
+ java.util.List<PoiRecord> records = new java.util.ArrayList<>();
+ io.papermc.paper.util.PoiAccess.findClosestPoiDataRecords(
+ villageplace,
+ (PoiType type) -> {
+ return type == PoiType.NETHER_PORTAL;
+ },
+ (BlockPos pos) -> {
+ net.minecraft.world.level.chunk.ChunkAccess lowest = this.level.getChunk(pos.getX() >> 4, pos.getZ() >> 4, net.minecraft.world.level.chunk.ChunkStatus.EMPTY);
+ if (!lowest.getStatus().isOrAfter(net.minecraft.world.level.chunk.ChunkStatus.FULL)) {
+ // why would we generate the chunk?
+ return false;
+ }
+ return lowest.getBlockState(pos).hasProperty(BlockStateProperties.HORIZONTAL_AXIS);
+ },
+ blockposition, i, Double.MAX_VALUE, PoiManager.Occupancy.ANY, true, records
+ );
+
+ // this gets us most of the way there, but we bias towards lower y values.
+ PoiRecord lowestYRecord = null;
+ for (PoiRecord record : records) {
+ if (lowestYRecord == null) {
+ lowestYRecord = record;
+ } else if (lowestYRecord.getPos().getY() > record.getPos().getY()) {
+ lowestYRecord = record;
+ }
+ }
+ // now we're done
+ optional = Optional.ofNullable(lowestYRecord);
+ // Paper end - optimise portals
return optional.map((villageplacerecord) -> {
BlockPos blockposition1 = villageplacerecord.getPos();

View File

@ -0,0 +1,430 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Spottedleaf <spottedleaf@spottedleaf.dev>
Date: Thu, 27 Aug 2020 16:22:52 -0700
Subject: [PATCH] Optimise nearby player lookups
Use a distance map to map out close players.
Note that it's important that we cache the distance map value per chunk
since the penalty of a map lookup could outweigh the benefits of
searching less players (as it basically did in the outside range patch).
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 {
long key = net.minecraft.server.MCUtil.getCoordinateKey(this.pos);
this.playersInMobSpawnRange = this.chunkMap.playerMobSpawnMap.getObjectsInRange(key);
this.playersInChunkTickRange = this.chunkMap.playerChunkTickRangeMap.getObjectsInRange(key);
+ // Paper start - optimise checkDespawn
+ LevelChunk chunk = this.getFullChunkUnchecked();
+ if (chunk != null) {
+ chunk.updateGeneralAreaCache();
+ }
+ // Paper end - optimise checkDespawn
}
// Paper end - optimise isOutsideOfRange
long lastAutoSaveTime; // Paper - incremental autosave
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 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
public final com.destroystokyo.paper.util.misc.PlayerAreaMap playerMobSpawnMap; // this map is absent from updateMaps since it's controlled at the start of the chunkproviderserver tick
public final com.destroystokyo.paper.util.misc.PlayerAreaMap playerChunkTickRangeMap;
// Paper end - optimise PlayerChunkMap#isOutsideRange
+ // Paper start - optimise checkDespawn
+ public static final int GENERAL_AREA_MAP_SQUARE_RADIUS = 40;
+ public static final double GENERAL_AREA_MAP_ACCEPTABLE_SEARCH_RANGE = 16.0 * (GENERAL_AREA_MAP_SQUARE_RADIUS - 1);
+ public static final double GENERAL_AREA_MAP_ACCEPTABLE_SEARCH_RANGE_SQUARED = GENERAL_AREA_MAP_ACCEPTABLE_SEARCH_RANGE * GENERAL_AREA_MAP_ACCEPTABLE_SEARCH_RANGE;
+ public final com.destroystokyo.paper.util.misc.PlayerAreaMap playerGeneralAreaMap;
+ // Paper end - optimise checkDespawn
void addPlayerToDistanceMaps(ServerPlayer player) {
int chunkX = MCUtil.getChunkCoordinate(player.getX());
@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
this.playerChunkTickRangeMap.add(player, chunkX, chunkZ, DistanceManager.MOB_SPAWN_RANGE);
// Paper end - optimise PlayerChunkMap#isOutsideRange
this.playerChunkManager.addPlayer(player); // Paper - replace chunk loader
+ // Paper start - optimise checkDespawn
+ this.playerGeneralAreaMap.add(player, chunkX, chunkZ, GENERAL_AREA_MAP_SQUARE_RADIUS);
+ // Paper end - optimise checkDespawn
}
void removePlayerFromDistanceMaps(ServerPlayer player) {
@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
this.playerChunkTickRangeMap.remove(player);
// Paper end - optimise PlayerChunkMap#isOutsideRange
this.playerChunkManager.removePlayer(player); // Paper - replace chunk loader
+ // Paper start - optimise checkDespawn
+ this.playerGeneralAreaMap.remove(player);
+ // Paper end - optimise checkDespawn
}
void updateMaps(ServerPlayer player) {
@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
this.playerChunkTickRangeMap.update(player, chunkX, chunkZ, DistanceManager.MOB_SPAWN_RANGE);
// Paper end - optimise PlayerChunkMap#isOutsideRange
this.playerChunkManager.updatePlayer(player); // Paper - replace chunk loader
+ // Paper start - optimise checkDespawn
+ this.playerGeneralAreaMap.update(player, chunkX, chunkZ, GENERAL_AREA_MAP_SQUARE_RADIUS);
+ // Paper end - optimise checkDespawn
}
// Paper end
// Paper start
@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
}
});
// Paper end - optimise PlayerChunkMap#isOutsideRange
+ // Paper start - optimise checkDespawn
+ this.playerGeneralAreaMap = new com.destroystokyo.paper.util.misc.PlayerAreaMap(this.pooledLinkedPlayerHashSets,
+ (ServerPlayer player, int rangeX, int rangeZ, int currPosX, int currPosZ, int prevPosX, int prevPosZ,
+ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<ServerPlayer> newState) -> {
+ LevelChunk chunk = ChunkMap.this.level.getChunkSource().getChunkAtIfCachedImmediately(rangeX, rangeZ);
+ if (chunk != null) {
+ chunk.updateGeneralAreaCache(newState);
+ }
+ },
+ (ServerPlayer player, int rangeX, int rangeZ, int currPosX, int currPosZ, int prevPosX, int prevPosZ,
+ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<ServerPlayer> newState) -> {
+ LevelChunk chunk = ChunkMap.this.level.getChunkSource().getChunkAtIfCachedImmediately(rangeX, rangeZ);
+ if (chunk != null) {
+ chunk.updateGeneralAreaCache(newState);
+ }
+ });
+ // Paper end - optimise checkDespawn
}
// Paper start - Chunk Prioritization
@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
} else {
if (holder != null) {
holder.setTicketLevel(level);
- holder.updateRanges(); // Paper - optimise isOutsideOfRange
+ // Paper - move to correct place
}
if (holder != null) {
@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
holder = (ChunkHolder) this.pendingUnloads.remove(pos);
if (holder != null) {
holder.setTicketLevel(level);
+ holder.updateRanges(); // Paper - optimise isOutsideOfRange // Paper - move to correct place
} else {
holder = new ChunkHolder(new ChunkPos(pos), level, this.level, this.lightEngine, this.queueSorter, this);
// Paper 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 Level implements WorldGenLevel {
}
}
// Paper end - rewrite ticklistserver
+ // Paper start - optimise checkDespawn
+ public final List<ServerPlayer> playersAffectingSpawning = new java.util.ArrayList<>();
+ // Paper end - optimise checkDespawn
+ // Paper start - optimise get nearest players for entity AI
+ @Override
+ public final ServerPlayer getNearestPlayer(net.minecraft.world.entity.ai.targeting.TargetingConditions condition, @Nullable LivingEntity source,
+ double centerX, double centerY, double centerZ) {
+ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<ServerPlayer> nearby;
+ nearby = this.getChunkSource().chunkMap.playerGeneralAreaMap.getObjectsInRange(Mth.floor(centerX) >> 4, Mth.floor(centerZ) >> 4);
+
+ if (nearby == null) {
+ return null;
+ }
+
+ Object[] backingSet = nearby.getBackingSet();
+
+ double closestDistanceSquared = Double.MAX_VALUE;
+ ServerPlayer closest = null;
+
+ for (int i = 0, len = backingSet.length; i < len; ++i) {
+ Object _player = backingSet[i];
+ if (!(_player instanceof ServerPlayer)) {
+ continue;
+ }
+ ServerPlayer player = (ServerPlayer)_player;
+
+ double distanceSquared = player.distanceToSqr(centerX, centerY, centerZ);
+ if (distanceSquared < closestDistanceSquared && condition.test(source, player)) {
+ closest = player;
+ closestDistanceSquared = distanceSquared;
+ }
+ }
+
+ return closest;
+ }
+
+ @Override
+ public Player getNearestPlayer(net.minecraft.world.entity.ai.targeting.TargetingConditions pathfindertargetcondition, LivingEntity entityliving) {
+ return this.getNearestPlayer(pathfindertargetcondition, entityliving, entityliving.getX(), entityliving.getY(), entityliving.getZ());
+ }
+
+ @Override
+ public Player getNearestPlayer(net.minecraft.world.entity.ai.targeting.TargetingConditions pathfindertargetcondition,
+ double d0, double d1, double d2) {
+ return this.getNearestPlayer(pathfindertargetcondition, null, d0, d1, d2);
+ }
+
+ @Override
+ public List<Player> getNearbyPlayers(net.minecraft.world.entity.ai.targeting.TargetingConditions condition, LivingEntity source, AABB axisalignedbb) {
+ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<ServerPlayer> nearby;
+ double centerX = (axisalignedbb.maxX + axisalignedbb.minX) * 0.5;
+ double centerZ = (axisalignedbb.maxZ + axisalignedbb.minZ) * 0.5;
+ nearby = this.getChunkSource().chunkMap.playerGeneralAreaMap.getObjectsInRange(Mth.floor(centerX) >> 4, Mth.floor(centerZ) >> 4);
+
+ List<Player> ret = new java.util.ArrayList<>();
+
+ if (nearby == null) {
+ return ret;
+ }
+
+ Object[] backingSet = nearby.getBackingSet();
+
+ for (int i = 0, len = backingSet.length; i < len; ++i) {
+ Object _player = backingSet[i];
+ if (!(_player instanceof ServerPlayer)) {
+ continue;
+ }
+ ServerPlayer player = (ServerPlayer)_player;
+
+ if (axisalignedbb.contains(player.getX(), player.getY(), player.getZ()) && condition.test(source, player)) {
+ ret.add(player);
+ }
+ }
+
+ return ret;
+ }
+ // Paper end - optimise get nearest players for entity AI
// Add env and gen to constructor, WorldData -> WorldDataServer
public ServerLevel(MinecraftServer minecraftserver, Executor executor, LevelStorageSource.LevelStorageAccess convertable_conversionsession, ServerLevelData iworlddataserver, ResourceKey<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, org.bukkit.generator.BiomeProvider biomeProvider) {
@@ -0,0 +0,0 @@ public class ServerLevel extends Level implements WorldGenLevel {
}
public void tick(BooleanSupplier shouldKeepTicking) {
+ // Paper start - optimise checkDespawn
+ this.playersAffectingSpawning.clear();
+ for (ServerPlayer player : this.players) {
+ if (net.minecraft.world.entity.EntitySelector.affectsSpawning.test(player)) {
+ this.playersAffectingSpawning.add(player);
+ }
+ }
+ // Paper end - optimise checkDespawn
ProfilerFiller gameprofilerfiller = this.getProfiler();
this.handlingTick = true;
diff --git a/src/main/java/net/minecraft/world/entity/Mob.java b/src/main/java/net/minecraft/world/entity/Mob.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/world/entity/Mob.java
+++ b/src/main/java/net/minecraft/world/entity/Mob.java
@@ -0,0 +0,0 @@ public abstract class Mob extends LivingEntity {
if (this.level.getDifficulty() == Difficulty.PEACEFUL && this.shouldDespawnInPeaceful()) {
this.discard();
} else if (!this.isPersistenceRequired() && !this.requiresCustomPersistence()) {
- Player entityhuman = this.level.findNearbyPlayer(this, -1.0D, EntitySelector.affectsSpawning); // Paper
+ // Paper start - optimise checkDespawn
+ Player entityhuman = this.level.findNearbyPlayer(this, level.paperConfig.hardDespawnDistance + 1, EntitySelector.affectsSpawning); // Paper
+ if (entityhuman == null) {
+ entityhuman = ((ServerLevel)this.level).playersAffectingSpawning.isEmpty() ? null : ((ServerLevel)this.level).playersAffectingSpawning.get(0);
+ }
+ // Paper end - optimise checkDespawn
if (entityhuman != null) {
double d0 = entityhuman.distanceToSqr((Entity) this); // CraftBukkit - decompile error
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 ret;
}
// Paper end
+ // Paper start - optimise checkDespawn
+ public final List<net.minecraft.server.level.ServerPlayer> getNearbyPlayers(@Nullable Entity source, double sourceX, double sourceY,
+ double sourceZ, double maxRange, @Nullable Predicate<Entity> predicate) {
+ LevelChunk chunk;
+ if (maxRange < 0.0 || maxRange >= net.minecraft.server.level.ChunkMap.GENERAL_AREA_MAP_ACCEPTABLE_SEARCH_RANGE ||
+ (chunk = (LevelChunk)this.getChunkIfLoadedImmediately(Mth.floor(sourceX) >> 4, Mth.floor(sourceZ) >> 4)) == null) {
+ return this.getNearbyPlayersSlow(source, sourceX, sourceY, sourceZ, maxRange, predicate);
+ }
+
+ List<net.minecraft.server.level.ServerPlayer> ret = new java.util.ArrayList<>();
+ chunk.getNearestPlayers(sourceX, sourceY, sourceZ, predicate, maxRange, ret);
+ return ret;
+ }
+
+ private List<net.minecraft.server.level.ServerPlayer> getNearbyPlayersSlow(@Nullable Entity source, double sourceX, double sourceY,
+ double sourceZ, double maxRange, @Nullable Predicate<Entity> predicate) {
+ List<net.minecraft.server.level.ServerPlayer> ret = new java.util.ArrayList<>();
+ double maxRangeSquared = maxRange * maxRange;
+
+ for (net.minecraft.server.level.ServerPlayer player : (List<net.minecraft.server.level.ServerPlayer>)this.players()) {
+ if ((maxRange < 0.0 || player.distanceToSqr(sourceX, sourceY, sourceZ) < maxRangeSquared)) {
+ if (predicate == null || predicate.test(player)) {
+ ret.add(player);
+ }
+ }
+ }
+
+ return ret;
+ }
+
+ private net.minecraft.server.level.ServerPlayer getNearestPlayerSlow(@Nullable Entity source, double sourceX, double sourceY,
+ double sourceZ, double maxRange, @Nullable Predicate<Entity> predicate) {
+ net.minecraft.server.level.ServerPlayer closest = null;
+ double closestRangeSquared = maxRange < 0.0 ? Double.MAX_VALUE : maxRange * maxRange;
+
+ for (net.minecraft.server.level.ServerPlayer player : (List<net.minecraft.server.level.ServerPlayer>)this.players()) {
+ double distanceSquared = player.distanceToSqr(sourceX, sourceY, sourceZ);
+ if (distanceSquared < closestRangeSquared && (predicate == null || predicate.test(player))) {
+ closest = player;
+ closestRangeSquared = distanceSquared;
+ }
+ }
+
+ return closest;
+ }
+
+
+ public final net.minecraft.server.level.ServerPlayer getNearestPlayer(@Nullable Entity source, double sourceX, double sourceY,
+ double sourceZ, double maxRange, @Nullable Predicate<Entity> predicate) {
+ LevelChunk chunk;
+ if (maxRange < 0.0 || maxRange >= net.minecraft.server.level.ChunkMap.GENERAL_AREA_MAP_ACCEPTABLE_SEARCH_RANGE ||
+ (chunk = (LevelChunk)this.getChunkIfLoadedImmediately(Mth.floor(sourceX) >> 4, Mth.floor(sourceZ) >> 4)) == null) {
+ return this.getNearestPlayerSlow(source, sourceX, sourceY, sourceZ, maxRange, predicate);
+ }
+
+ return chunk.findNearestPlayer(sourceX, sourceY, sourceZ, maxRange, predicate);
+ }
+
+ @Override
+ public @Nullable Player getNearestPlayer(double d0, double d1, double d2, double d3, @Nullable Predicate<Entity> predicate) {
+ return this.getNearestPlayer(null, d0, d1, d2, d3, predicate);
+ }
+ // Paper end - optimise checkDespawn
protected Level(WritableLevelData worlddatamutable, ResourceKey<Level> resourcekey, final DimensionType dimensionmanager, Supplier<ProfilerFiller> supplier, boolean flag, boolean flag1, long i, org.bukkit.generator.ChunkGenerator gen, org.bukkit.generator.BiomeProvider biomeProvider, org.bukkit.World.Environment env, java.util.concurrent.Executor executor) { // Paper - Anti-Xray - Pass executor
this.spigotConfig = new org.spigotmc.SpigotWorldConfig(((net.minecraft.world.level.storage.PrimaryLevelData) worlddatamutable).getLevelName()); // Spigot
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 @@ public final class NaturalSpawner {
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 = (chunk instanceof LevelChunk) ? ((LevelChunk)chunk).findNearestPlayer(d0, i, d1, 576.0D, net.minecraft.world.entity.EntitySelector.NO_SPECTATORS) : world.getNearestPlayer(d0, (double) i, d1, -1.0D, false); // Paper - use chunk's player cache to optimize search in range
if (entityhuman != null) {
double d2 = entityhuman.distanceToSqr(d0, (double) i, d1);
@@ -0,0 +0,0 @@ public final class NaturalSpawner {
}
private static boolean isRightDistanceToPlayerAndSpawnPoint(ServerLevel world, ChunkAccess chunk, BlockPos.MutableBlockPos pos, double squaredDistance) {
- return squaredDistance <= 576.0D ? false : (world.getSharedSpawnPos().closerThan((Position) (new Vec3((double) pos.getX() + 0.5D, (double) pos.getY(), (double) pos.getZ() + 0.5D)), 24.0D) ? false : Objects.equals(new ChunkPos(pos), chunk.getPos()) || world.isPositionEntityTicking((BlockPos) pos));
+ return squaredDistance <= 576.0D ? false : (world.getSharedSpawnPos().closerThan((Position) (new Vec3((double) pos.getX() + 0.5D, (double) pos.getY(), (double) pos.getZ() + 0.5D)), 24.0D) ? false : Objects.equals(new ChunkPos(pos), chunk.getPos()) || world.isPositionEntityTicking((BlockPos) pos)); // Paper - diff on change, copy into caller
}
private static Boolean isValidSpawnPostitionForType(ServerLevel world, MobCategory group, StructureFeatureManager structureAccessor, ChunkGenerator chunkGenerator, MobSpawnSettings.SpawnerData spawnEntry, BlockPos.MutableBlockPos pos, double squaredDistance) { // Paper
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 {
}
}
// Paper end
+ // Paper start - optimise checkDespawn
+ private boolean playerGeneralAreaCacheSet;
+ private com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<net.minecraft.server.level.ServerPlayer> playerGeneralAreaCache;
+
+ public com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<net.minecraft.server.level.ServerPlayer> getPlayerGeneralAreaCache() {
+ if (!this.playerGeneralAreaCacheSet) {
+ this.updateGeneralAreaCache();
+ }
+ return this.playerGeneralAreaCache;
+ }
+
+ public void updateGeneralAreaCache() {
+ this.updateGeneralAreaCache(((ServerLevel)this.level).getChunkSource().chunkMap.playerGeneralAreaMap.getObjectsInRange(this.coordinateKey));
+ }
+
+ public void updateGeneralAreaCache(com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<net.minecraft.server.level.ServerPlayer> value) {
+ this.playerGeneralAreaCacheSet = true;
+ this.playerGeneralAreaCache = value;
+ }
+
+ public net.minecraft.server.level.ServerPlayer findNearestPlayer(double sourceX, double sourceY, double sourceZ,
+ double maxRange, java.util.function.Predicate<Entity> predicate) {
+ if (!this.playerGeneralAreaCacheSet) {
+ this.updateGeneralAreaCache();
+ }
+
+ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<net.minecraft.server.level.ServerPlayer> nearby = this.playerGeneralAreaCache;
+
+ if (nearby == null) {
+ return null;
+ }
+
+ Object[] backingSet = nearby.getBackingSet();
+ double closestDistance = maxRange < 0.0 ? Double.MAX_VALUE : maxRange * maxRange;
+ net.minecraft.server.level.ServerPlayer closest = null;
+ for (int i = 0, len = backingSet.length; i < len; ++i) {
+ Object _player = backingSet[i];
+ if (!(_player instanceof net.minecraft.server.level.ServerPlayer)) {
+ continue;
+ }
+ net.minecraft.server.level.ServerPlayer player = (net.minecraft.server.level.ServerPlayer)_player;
+
+ double distance = player.distanceToSqr(sourceX, sourceY, sourceZ);
+ if (distance < closestDistance && predicate.test(player)) {
+ closest = player;
+ closestDistance = distance;
+ }
+ }
+
+ return closest;
+ }
+
+ public void getNearestPlayers(double sourceX, double sourceY, double sourceZ, java.util.function.Predicate<Entity> predicate,
+ double range, java.util.List<net.minecraft.server.level.ServerPlayer> ret) {
+ if (!this.playerGeneralAreaCacheSet) {
+ this.updateGeneralAreaCache();
+ }
+
+ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<net.minecraft.server.level.ServerPlayer> nearby = this.playerGeneralAreaCache;
+
+ if (nearby == null) {
+ return;
+ }
+
+ double rangeSquared = range * range;
+
+ Object[] backingSet = nearby.getBackingSet();
+ for (int i = 0, len = backingSet.length; i < len; ++i) {
+ Object _player = backingSet[i];
+ if (!(_player instanceof net.minecraft.server.level.ServerPlayer)) {
+ continue;
+ }
+ net.minecraft.server.level.ServerPlayer player = (net.minecraft.server.level.ServerPlayer)_player;
+
+ if (range >= 0.0) {
+ double distanceSquared = player.distanceToSqr(sourceX, sourceY, sourceZ);
+ if (distanceSquared > rangeSquared) {
+ continue;
+ }
+ }
+
+ if (predicate == null || predicate.test(player)) {
+ ret.add(player);
+ }
+ }
+ }
+ // Paper end - optimise checkDespawn
public LevelChunk(ServerLevel worldserver, ProtoChunk protoChunk, @Nullable Consumer<LevelChunk> consumer) {
this(worldserver, protoChunk.getPos(), protoChunk.getBiomes(), protoChunk.getUpgradeData(), protoChunk.getBlockTicks(), protoChunk.getLiquidTicks(), protoChunk.getInhabitedTime(), protoChunk.getSections(), consumer);

View File

@ -0,0 +1,55 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Spottedleaf <spottedleaf@spottedleaf.dev>
Date: Tue, 22 Sep 2020 01:49:19 -0700
Subject: [PATCH] Optimise non-flush packet sending
Places like entity tracking make heavy use of packet sending,
and internally netty will use some very expensive thread wakeup
calls when scheduling.
Thanks to various hacks in ProtocolLib as well as other
plugins, we cannot simply use a queue of packets to group
send on execute. We have to call execute for each packet.
Tux's suggestion here is exactly what was needed - tag
the Runnable indicating it should not make a wakeup call.
Big thanks to Tux for making this possible as I had given
up on this optimisation before he came along.
Locally this patch drops the entity tracker tick by a full 1.5x.
diff --git a/src/main/java/net/minecraft/network/Connection.java b/src/main/java/net/minecraft/network/Connection.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/network/Connection.java
+++ b/src/main/java/net/minecraft/network/Connection.java
@@ -0,0 +0,0 @@ import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.Marker;
import org.apache.logging.log4j.MarkerManager;
+
+import io.netty.util.concurrent.AbstractEventExecutor; // Paper
public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
private static final float AVERAGE_PACKETS_SMOOTHING = 0.75F;
@@ -0,0 +0,0 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
if (this.channel.eventLoop().inEventLoop()) {
this.a(packet, callback, enumprotocol, enumprotocol1, flush); // Paper - add flush parameter
} else {
+ // Paper start - optimise packets that are not flushed
+ // note: since the type is not dynamic here, we need to actually copy the old executor code
+ // into two branches. On conflict, just re-copy - no changes were made inside the executor code.
+ if (!flush) {
+ AbstractEventExecutor.LazyRunnable run = () -> {
+ this.a(packet, callback, enumprotocol, enumprotocol1, flush); // Paper - add flush parameter
+ };
+ this.channel.eventLoop().execute(run);
+ } else { // Paper end - optimise packets that are not flushed
this.channel.eventLoop().execute(() -> {
- this.a(packet, callback, enumprotocol, enumprotocol1, flush); // Paper - add flush parameter
+ this.a(packet, callback, enumprotocol, enumprotocol1, flush); // Paper - add flush parameter // Paper - diff on change
});
+ } // Paper
}
}

View File

@ -0,0 +1,369 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Spottedleaf <Spottedleaf@users.noreply.github.com>
Date: Sun, 20 Jun 2021 16:19:26 -0700
Subject: [PATCH] Optimise random block ticking
Massive performance improvement for random block ticking.
The performance increase comes from the fact that the vast
majority of attempted block ticks (~95% in my testing) fail
because the randomly selected block is not tickable.
Now only tickable blocks are targeted, however this means that
the maximum number of block ticks occurs per chunk. However,
not all chunks are going to be targeted. The percent chance
of a chunk being targeted is based on how many tickable blocks
are in the chunk.
This means that while block ticks are spread out less, the
total number of blocks ticked per world tick remains the same.
Therefore, the chance of a random tickable block being ticked
remains the same.
diff --git a/src/main/java/io/papermc/paper/util/math/ThreadUnsafeRandom.java b/src/main/java/io/papermc/paper/util/math/ThreadUnsafeRandom.java
new file mode 100644
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
--- /dev/null
+++ b/src/main/java/io/papermc/paper/util/math/ThreadUnsafeRandom.java
@@ -0,0 +0,0 @@
+package io.papermc.paper.util.math;
+
+import java.util.Random;
+
+public final class ThreadUnsafeRandom extends Random {
+
+ // See javadoc and internal comments for java.util.Random where these values come from, how they are used, and the author for them.
+ private static final long multiplier = 0x5DEECE66DL;
+ private static final long addend = 0xBL;
+ private static final long mask = (1L << 48) - 1;
+
+ private static long initialScramble(long seed) {
+ return (seed ^ multiplier) & mask;
+ }
+
+ private long seed;
+
+ @Override
+ public void setSeed(long seed) {
+ // note: called by Random constructor
+ this.seed = initialScramble(seed);
+ }
+
+ @Override
+ protected int next(int bits) {
+ // avoid the expensive CAS logic used by superclass
+ return (int) (((this.seed = this.seed * multiplier + addend) & mask) >>> (48 - bits));
+ }
+
+ // Taken from
+ // https://lemire.me/blog/2016/06/27/a-fast-alternative-to-the-modulo-reduction/
+ // https://github.com/lemire/Code-used-on-Daniel-Lemire-s-blog/blob/master/2016/06/25/fastrange.c
+ // Original license is public domain
+ public static int fastRandomBounded(final long randomInteger, final long limit) {
+ // randomInteger must be [0, pow(2, 32))
+ // limit must be [0, pow(2, 32))
+ return (int)((randomInteger * limit) >>> 32);
+ }
+
+ @Override
+ public int nextInt(int bound) {
+ // yes this breaks random's spec
+ // however there's nothing that uses this class that relies on it
+ return fastRandomBounded(this.next(32) & 0xFFFFFFFFL, bound);
+ }
+}
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 Level implements WorldGenLevel {
entityplayer.stopSleepInBed(false, false);
});
}
+ // Paper start - optimise random block ticking
+ private final BlockPos.MutableBlockPos chunkTickMutablePosition = new BlockPos.MutableBlockPos();
+ private final io.papermc.paper.util.math.ThreadUnsafeRandom randomTickRandom = new io.papermc.paper.util.math.ThreadUnsafeRandom();
+ // Paper end
public void tickChunk(LevelChunk chunk, int randomTickSpeed) {
ChunkPos chunkcoordintpair = chunk.getPos();
@@ -0,0 +0,0 @@ public class ServerLevel extends Level implements WorldGenLevel {
ProfilerFiller gameprofilerfiller = this.getProfiler();
gameprofilerfiller.push("thunder");
- BlockPos blockposition;
+ final BlockPos.MutableBlockPos blockposition = this.chunkTickMutablePosition; // Paper - use mutable to reduce allocation rate, final to force compile fail on change
if (!this.paperConfig.disableThunder && flag && this.isThundering() && this.random.nextInt(100000) == 0) { // Paper - Disable thunder
- blockposition = this.findLightningTargetAround(this.getBlockRandomPos(j, 0, k, 15));
+ blockposition.set(this.findLightningTargetAround(this.getBlockRandomPos(j, 0, k, 15))); // Paper
if (this.isRainingAt(blockposition)) {
DifficultyInstance difficultydamagescaler = this.getCurrentDifficultyAt(blockposition);
boolean flag1 = this.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING) && this.random.nextDouble() < (double) difficultydamagescaler.getEffectiveDifficulty() * paperConfig.skeleHorseSpawnChance && !this.getBlockState(blockposition.below()).is(Blocks.LIGHTNING_ROD); // Paper
@@ -0,0 +0,0 @@ public class ServerLevel extends Level implements WorldGenLevel {
}
gameprofilerfiller.popPush("iceandsnow");
- if (!this.paperConfig.disableIceAndSnow && this.random.nextInt(16) == 0) { // Paper - Disable ice and snow
- blockposition = this.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING, this.getBlockRandomPos(j, 0, k, 15));
- BlockPos blockposition1 = blockposition.below();
+ if (!this.paperConfig.disableIceAndSnow && this.randomTickRandom.nextInt(16) == 0) { // Paper - Disable ice and snow // Paper - optimise random ticking
+ // Paper start - optimise chunk ticking
+ this.getRandomBlockPosition(j, 0, k, 15, blockposition);
+ int normalY = chunk.getHeight(Heightmap.Types.MOTION_BLOCKING, blockposition.getX() & 15, blockposition.getZ() & 15) + 1;
+ int downY = normalY - 1;
+ blockposition.setY(normalY);
+ // Paper end
Biome biomebase = this.getBiome(blockposition);
- if (biomebase.shouldFreeze((LevelReader) this, blockposition1)) {
- org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockFormEvent(this, blockposition1, Blocks.ICE.defaultBlockState(), null); // CraftBukkit
+ // Paper start - optimise chunk ticking
+ blockposition.setY(downY);
+ if (biomebase.shouldFreeze(this, blockposition)) {
+ org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockFormEvent(this, blockposition, Blocks.ICE.defaultBlockState(), null); // CraftBukkit
+ // Paper end
}
if (flag) {
+ blockposition.setY(normalY); // Paper
if (biomebase.shouldSnow(this, blockposition)) {
org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockFormEvent(this, blockposition, Blocks.SNOW.defaultBlockState(), null); // CraftBukkit
}
- BlockState iblockdata = this.getBlockState(blockposition1);
+ blockposition.setY(downY); // Paper
+ BlockState iblockdata = this.getBlockState(blockposition); // Paper
+ blockposition.setY(normalY); // Paper
Biome.Precipitation biomebase_precipitation = this.getBiome(blockposition).getPrecipitation();
- if (biomebase_precipitation == Biome.Precipitation.RAIN && biomebase.isColdEnoughToSnow(blockposition1)) {
+ blockposition.setY(downY); // Paper
+ if (biomebase_precipitation == Biome.Precipitation.RAIN && biomebase.isColdEnoughToSnow(blockposition)) { // Paper
biomebase_precipitation = Biome.Precipitation.SNOW;
}
- iblockdata.getBlock().handlePrecipitation(iblockdata, (Level) this, blockposition1, biomebase_precipitation);
+ iblockdata.getBlock().handlePrecipitation(iblockdata, (Level) this, blockposition, biomebase_precipitation); // Paper
}
}
- gameprofilerfiller.popPush("tickBlocks");
+ // Paper start - optimise random block ticking
+ gameprofilerfiller.popPush("randomTick");
timings.chunkTicksBlocks.startTiming(); // Paper
if (randomTickSpeed > 0) {
- LevelChunkSection[] achunksection = chunk.getSections();
- int l = achunksection.length;
-
- for (int i1 = 0; i1 < l; ++i1) {
- LevelChunkSection chunksection = achunksection[i1];
-
- if (chunksection != LevelChunk.EMPTY_SECTION && chunksection.isRandomlyTicking()) {
- int j1 = chunksection.bottomBlockY();
-
- for (int k1 = 0; k1 < randomTickSpeed; ++k1) {
- BlockPos blockposition2 = this.getBlockRandomPos(j, j1, k, 15);
-
- gameprofilerfiller.push("randomTick");
- BlockState iblockdata1 = chunksection.getBlockState(blockposition2.getX() - j, blockposition2.getY() - j1, blockposition2.getZ() - k);
+ LevelChunkSection[] sections = chunk.getSections();
+ int minSection = io.papermc.paper.util.WorldUtil.getMinSection(this);
+ for (int sectionIndex = 0; sectionIndex < sections.length; ++sectionIndex) {
+ LevelChunkSection section = sections[sectionIndex];
+ if (section == null || section.tickingList.size() == 0) {
+ continue;
+ }
- if (iblockdata1.isRandomlyTicking()) {
- iblockdata1.randomTick(this, blockposition2, this.random);
- }
+ int yPos = (sectionIndex + minSection) << 4;
+ for (int a = 0; a < randomTickSpeed; ++a) {
+ int tickingBlocks = section.tickingList.size();
+ int index = this.randomTickRandom.nextInt(16 * 16 * 16);
+ if (index >= tickingBlocks) {
+ continue;
+ }
- FluidState fluid = iblockdata1.getFluidState();
+ long raw = section.tickingList.getRaw(index);
+ int location = com.destroystokyo.paper.util.maplist.IBlockDataList.getLocationFromRaw(raw);
+ int randomX = location & 15;
+ int randomY = ((location >>> (4 + 4)) & 255) | yPos;
+ int randomZ = (location >>> 4) & 15;
- if (fluid.isRandomlyTicking()) {
- fluid.randomTick(this, blockposition2, this.random);
- }
+ BlockPos blockposition2 = blockposition.set(j + randomX, randomY, k + randomZ);
+ BlockState iblockdata = com.destroystokyo.paper.util.maplist.IBlockDataList.getBlockDataFromRaw(raw);
- gameprofilerfiller.pop();
- }
+ iblockdata.randomTick(this, blockposition2, this.randomTickRandom);
+ // We drop the fluid tick since LAVA is ALREADY TICKED by the above method (See LiquidBlock).
+ // TODO CHECK ON UPDATE
}
}
}
-
+ // Paper end - optimise random block ticking
timings.chunkTicksBlocks.stopTiming(); // Paper
gameprofilerfiller.pop();
}
diff --git a/src/main/java/net/minecraft/util/BitStorage.java b/src/main/java/net/minecraft/util/BitStorage.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/util/BitStorage.java
+++ b/src/main/java/net/minecraft/util/BitStorage.java
@@ -0,0 +0,0 @@ public class BitStorage {
}
}
+
+ // Paper start
+ public final void forEach(DataBitConsumer consumer) {
+ int i = 0;
+ long[] along = this.data;
+ int j = along.length;
+
+ for (int k = 0; k < j; ++k) {
+ long l = along[k];
+
+ for (int i1 = 0; i1 < this.valuesPerLong; ++i1) {
+ consumer.accept(i, (int) (l & this.mask));
+ l >>= this.bits;
+ ++i;
+ if (i >= this.size) {
+ return;
+ }
+ }
+ }
+ }
+
+ @FunctionalInterface
+ public static interface DataBitConsumer {
+
+ void accept(int location, int data);
+
+ }
+ // Paper end
}
diff --git a/src/main/java/net/minecraft/world/entity/animal/Turtle.java b/src/main/java/net/minecraft/world/entity/animal/Turtle.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/world/entity/animal/Turtle.java
+++ b/src/main/java/net/minecraft/world/entity/animal/Turtle.java
@@ -0,0 +0,0 @@ public class Turtle extends Animal {
}
public void setHomePos(BlockPos pos) {
- this.entityData.set(Turtle.HOME_POS, pos);
+ this.entityData.set(Turtle.HOME_POS, pos.immutable()); // Paper - called with mutablepos...
}
public BlockPos getHomePos() { // Paper - public
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 {
public abstract TagContainer getTagManager();
public BlockPos getBlockRandomPos(int x, int y, int z, int l) {
+ // Paper start - allow use of mutable pos
+ BlockPos.MutableBlockPos ret = new BlockPos.MutableBlockPos();
+ this.getRandomBlockPosition(x, y, z, l, ret);
+ return ret.immutable();
+ }
+ public final BlockPos.MutableBlockPos getRandomBlockPosition(int x, int y, int z, int l, BlockPos.MutableBlockPos out) {
+ // Paper end
this.randValue = this.randValue * 3 + 1013904223;
int i1 = this.randValue >> 2;
- return new BlockPos(x + (i1 & 15), y + (i1 >> 16 & l), z + (i1 >> 8 & 15));
+ out.set(x + (i1 & 15), y + (i1 >> 16 & l), z + (i1 >> 8 & 15)); // Paper - change to setValues call
+ return out; // Paper
}
public boolean noSave() {
diff --git a/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java
+++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java
@@ -0,0 +0,0 @@ public class LevelChunkSection {
private short tickingBlockCount;
private short tickingFluidCount;
public final PalettedContainer<BlockState> states; // Paper - package-private // Paper - public
+ public final com.destroystokyo.paper.util.maplist.IBlockDataList tickingList = new com.destroystokyo.paper.util.maplist.IBlockDataList(); // Paper
// Paper start - Anti-Xray - Add parameters
@Deprecated public LevelChunkSection(int yOffset) { this(yOffset, null, null, true); } // Notice for updates: Please make sure this constructor isn't used anywhere
@@ -0,0 +0,0 @@ public class LevelChunkSection {
--this.nonEmptyBlockCount;
if (blockState.isRandomlyTicking()) {
--this.tickingBlockCount;
+ // Paper start
+ this.tickingList.remove(x, y, z);
+ // Paper end
}
}
@@ -0,0 +0,0 @@ public class LevelChunkSection {
++this.nonEmptyBlockCount;
if (state.isRandomlyTicking()) {
++this.tickingBlockCount;
+ // Paper start
+ this.tickingList.add(x, y, z, state);
+ // Paper end
}
}
@@ -0,0 +0,0 @@ public class LevelChunkSection {
}
public void recalcBlockCounts() {
+ // Paper start
+ this.tickingList.clear();
+ // Paper end
this.nonEmptyBlockCount = 0;
this.tickingBlockCount = 0;
this.tickingFluidCount = 0;
- this.states.count((state, count) -> {
+ this.states.forEachLocation((state, location) -> { // Paper
FluidState fluidState = state.getFluidState();
if (!state.isAir()) {
- this.nonEmptyBlockCount = (short)(this.nonEmptyBlockCount + count);
+ this.nonEmptyBlockCount = (short)(this.nonEmptyBlockCount + 1); // Paper
if (state.isRandomlyTicking()) {
- this.tickingBlockCount = (short)(this.tickingBlockCount + count);
+ // Paper start
+ this.tickingBlockCount = (short)(this.tickingBlockCount + 1);
+ this.tickingList.add(location, state);
+ // Paper end
}
}
if (!fluidState.isEmpty()) {
- this.nonEmptyBlockCount = (short)(this.nonEmptyBlockCount + count);
+ this.nonEmptyBlockCount = (short)(this.nonEmptyBlockCount + 1); // Paper
if (fluidState.isRandomlyTicking()) {
- this.tickingFluidCount = (short)(this.tickingFluidCount + count);
+ this.tickingFluidCount = (short)(this.tickingFluidCount + 1); // Paper
}
}
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 @@ public class PalettedContainer<T> implements PaletteResize<T> {
public interface CountConsumer<T> {
void accept(T object, int count);
}
+
+ // Paper start
+ public void forEachLocation(PalettedContainer.CountConsumer<T> datapaletteblock_a) {
+ this.storage.forEach((int location, int data) -> {
+ datapaletteblock_a.accept(this.palette.valueFor(data), location);
+ });
+ }
+ // Paper end
}

View File

@ -23,8 +23,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ } + }
+ // Paper end + // Paper end
public void initCache() { // Paper start
if (!this.getBlock().hasDynamicShape()) { protected boolean shapeExceedsCube = true;
diff --git a/src/main/java/org/bukkit/craftbukkit/block/data/CraftBlockData.java b/src/main/java/org/bukkit/craftbukkit/block/data/CraftBlockData.java diff --git a/src/main/java/org/bukkit/craftbukkit/block/data/CraftBlockData.java b/src/main/java/org/bukkit/craftbukkit/block/data/CraftBlockData.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/org/bukkit/craftbukkit/block/data/CraftBlockData.java --- a/src/main/java/org/bukkit/craftbukkit/block/data/CraftBlockData.java

File diff suppressed because it is too large Load Diff

View File

@ -1,299 +0,0 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Aikar <aikar@aikar.co>
Date: Wed, 8 Apr 2020 03:06:30 -0400
Subject: [PATCH] Optimize PlayerChunkMap memory use for visibleChunks
No longer clones visible chunks which is causing massive memory
allocation issues, likely the source of Humongous Objects on large servers.
Instead we just synchronize, clear and rebuild, reusing the same object buffers
as before with only 2 small objects created (FastIterator/MapEntry)
This should result in siginificant memory use reduction and improved GC behavior.
diff --git a/src/main/java/com/destroystokyo/paper/util/map/Long2ObjectLinkedOpenHashMapFastCopy.java b/src/main/java/com/destroystokyo/paper/util/map/Long2ObjectLinkedOpenHashMapFastCopy.java
new file mode 100644
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
--- /dev/null
+++ b/src/main/java/com/destroystokyo/paper/util/map/Long2ObjectLinkedOpenHashMapFastCopy.java
@@ -0,0 +0,0 @@
+package com.destroystokyo.paper.util.map;
+
+import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap;
+
+public class Long2ObjectLinkedOpenHashMapFastCopy<V> extends Long2ObjectLinkedOpenHashMap<V> {
+
+ public void copyFrom(Long2ObjectLinkedOpenHashMapFastCopy<V> map) {
+ if (key.length != map.key.length) {
+ key = null;
+ key = new long[map.key.length];
+ }
+ if (value.length != map.value.length) {
+ value = null;
+ //noinspection unchecked
+ value = (V[]) new Object[map.value.length];
+ }
+ if (link.length != map.link.length) {
+ link = null;
+ link = new long[map.link.length];
+ }
+ System.arraycopy(map.key, 0, this.key, 0, map.key.length);
+ System.arraycopy(map.value, 0, this.value, 0, map.value.length);
+ System.arraycopy(map.link, 0, this.link, 0, map.link.length);
+ this.size = map.size;
+ this.mask = map.mask;
+ this.first = map.first;
+ this.last = map.last;
+ this.n = map.n;
+ this.maxFill = map.maxFill;
+ this.containsNullKey = map.containsNullKey;
+ }
+
+ @Override
+ public Long2ObjectLinkedOpenHashMapFastCopy<V> clone() {
+ Long2ObjectLinkedOpenHashMapFastCopy<V> clone = (Long2ObjectLinkedOpenHashMapFastCopy<V>) super.clone();
+ clone.copyFrom(this);
+ return clone;
+ }
+}
diff --git a/src/main/java/net/minecraft/server/MCUtil.java b/src/main/java/net/minecraft/server/MCUtil.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/server/MCUtil.java
+++ b/src/main/java/net/minecraft/server/MCUtil.java
@@ -0,0 +0,0 @@ public final class MCUtil {
ServerLevel world = ((org.bukkit.craftbukkit.CraftWorld)bukkitWorld).getHandle();
ChunkMap chunkMap = world.getChunkSource().chunkMap;
- Long2ObjectLinkedOpenHashMap<ChunkHolder> visibleChunks = chunkMap.visibleChunkMap;
+ Long2ObjectLinkedOpenHashMap<ChunkHolder> visibleChunks = chunkMap.getVisibleChunks();
DistanceManager chunkMapDistance = chunkMap.distanceManager;
List<ChunkHolder> allChunks = new ArrayList<>(visibleChunks.values());
List<ServerPlayer> players = world.players;
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 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
private static final int MIN_VIEW_DISTANCE = 3;
public static final int MAX_VIEW_DISTANCE = 33;
public static final int MAX_CHUNK_DISTANCE = 33 + ChunkStatus.maxDistance();
+ // Paper start - faster copying
+ public final Long2ObjectLinkedOpenHashMap<ChunkHolder> updatingChunkMap = new com.destroystokyo.paper.util.map.Long2ObjectLinkedOpenHashMapFastCopy<>(); // Paper - faster copying
+ public final Long2ObjectLinkedOpenHashMap<ChunkHolder> visibleChunkMap = new ProtectedVisibleChunksMap(); // Paper - faster copying
+
+ private class ProtectedVisibleChunksMap extends com.destroystokyo.paper.util.map.Long2ObjectLinkedOpenHashMapFastCopy<ChunkHolder> {
+ @Override
+ public ChunkHolder put(long k, ChunkHolder playerChunk) {
+ throw new UnsupportedOperationException("Updating visible Chunks");
+ }
+
+ @Override
+ public ChunkHolder remove(long k) {
+ throw new UnsupportedOperationException("Removing visible Chunks");
+ }
+
+ @Override
+ public ChunkHolder get(long k) {
+ return ChunkMap.this.getVisibleChunkIfPresent(k);
+ }
+
+ public ChunkHolder safeGet(long k) {
+ return super.get(k);
+ }
+ }
+ // Paper end
+ public final com.destroystokyo.paper.util.map.Long2ObjectLinkedOpenHashMapFastCopy<ChunkHolder> pendingVisibleChunks = new com.destroystokyo.paper.util.map.Long2ObjectLinkedOpenHashMapFastCopy<ChunkHolder>(); // Paper - this is used if the visible chunks is updated while iterating only
+ public transient com.destroystokyo.paper.util.map.Long2ObjectLinkedOpenHashMapFastCopy<ChunkHolder> visibleChunksClone; // Paper - used for async access of visible chunks, clone and cache only when needed
public static final int FORCED_TICKET_LEVEL = 31;
- public final Long2ObjectLinkedOpenHashMap<ChunkHolder> updatingChunkMap = new Long2ObjectLinkedOpenHashMap();
- public volatile Long2ObjectLinkedOpenHashMap<ChunkHolder> visibleChunkMap;
+ // public final Long2ObjectLinkedOpenHashMap<ChunkHolder> updatingChunkMap = new Long2ObjectLinkedOpenHashMap(); // Paper - moved up
+ // public volatile Long2ObjectLinkedOpenHashMap<ChunkHolder> visibleChunkMap; // Paper - moved up
private final Long2ObjectLinkedOpenHashMap<ChunkHolder> pendingUnloads;
public final LongSet entitiesInLevel;
public final ServerLevel level;
@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
public ChunkMap(ServerLevel world, LevelStorageSource.LevelStorageAccess session, DataFixer dataFixer, StructureManager structureManager, Executor executor, BlockableEventLoop<Runnable> mainThreadExecutor, LightChunkGetter chunkProvider, ChunkGenerator chunkGenerator, ChunkProgressListener worldGenerationProgressListener, ChunkStatusUpdateListener chunkStatusChangeListener, Supplier<DimensionDataStorage> persistentStateManagerFactory, int viewDistance, boolean dsync) {
super(new File(session.getDimensionPath(world.dimension()), "region"), dataFixer, dsync);
- this.visibleChunkMap = this.updatingChunkMap.clone();
+ //this.visibleChunks = this.updatingChunks.clone(); // Paper - no more cloning
this.pendingUnloads = new Long2ObjectLinkedOpenHashMap();
this.entitiesInLevel = new LongOpenHashSet();
this.toDrop = new LongOpenHashSet();
@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
return (ChunkHolder) this.updatingChunkMap.get(pos);
}
+ // Paper start - remove cloning of visible chunks unless accessed as a collection async
+ private static final boolean DEBUG_ASYNC_VISIBLE_CHUNKS = Boolean.getBoolean("paper.debug-async-visible-chunks");
+ private boolean isIterating = false;
+ private boolean hasPendingVisibleUpdate = false;
+ public void forEachVisibleChunk(java.util.function.Consumer<ChunkHolder> consumer) {
+ org.spigotmc.AsyncCatcher.catchOp("forEachVisibleChunk");
+ boolean prev = isIterating;
+ isIterating = true;
+ try {
+ for (ChunkHolder value : this.visibleChunkMap.values()) {
+ consumer.accept(value);
+ }
+ } finally {
+ this.isIterating = prev;
+ if (!this.isIterating && this.hasPendingVisibleUpdate) {
+ ((ProtectedVisibleChunksMap)this.visibleChunkMap).copyFrom(this.pendingVisibleChunks);
+ this.pendingVisibleChunks.clear();
+ this.hasPendingVisibleUpdate = false;
+ }
+ }
+ }
+ public Long2ObjectLinkedOpenHashMap<ChunkHolder> getVisibleChunks() {
+ if (Thread.currentThread() == this.level.thread) {
+ return this.visibleChunkMap;
+ } else {
+ synchronized (this.visibleChunkMap) {
+ if (DEBUG_ASYNC_VISIBLE_CHUNKS) new Throwable("Async getVisibleChunks").printStackTrace();
+ if (this.visibleChunksClone == null) {
+ this.visibleChunksClone = this.hasPendingVisibleUpdate ? this.pendingVisibleChunks.clone() : ((ProtectedVisibleChunksMap)this.visibleChunkMap).clone();
+ }
+ return this.visibleChunksClone;
+ }
+ }
+ }
+ // Paper end
+
@Nullable
public ChunkHolder getVisibleChunkIfPresent(long pos) {
- return (ChunkHolder) this.visibleChunkMap.get(pos);
+ // Paper start - mt safe get
+ if (Thread.currentThread() != this.level.thread) {
+ synchronized (this.visibleChunkMap) {
+ return (ChunkHolder) (this.hasPendingVisibleUpdate ? this.pendingVisibleChunks.get(pos) : ((ProtectedVisibleChunksMap)this.visibleChunkMap).safeGet(pos));
+ }
+ }
+ return (ChunkHolder) (this.hasPendingVisibleUpdate ? this.pendingVisibleChunks.get(pos) : ((ProtectedVisibleChunksMap)this.visibleChunkMap).safeGet(pos));
+ // Paper end
}
protected IntSupplier getChunkQueueLevel(long pos) {
@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
}
protected void saveAllChunks(boolean flush) {
+ Long2ObjectLinkedOpenHashMap<ChunkHolder> visibleChunks = this.getVisibleChunks(); // Paper remove clone of visible Chunks unless saving off main thread (watchdog kill)
if (flush) {
- List<ChunkHolder> list = (List) this.visibleChunkMap.values().stream().filter(ChunkHolder::wasAccessibleSinceLastSave).peek(ChunkHolder::refreshAccessibility).collect(Collectors.toList());
+ List<ChunkHolder> list = (List) visibleChunks.values().stream().filter(ChunkHolder::wasAccessibleSinceLastSave).peek(ChunkHolder::refreshAccessibility).collect(Collectors.toList()); // Paper - remove cloning of visible chunks
MutableBoolean mutableboolean = new MutableBoolean();
do {
@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
this.level.asyncChunkTaskManager.flush(); // Paper - flush to preserve behavior compat with pre-async behaviour
// this.i(); // Paper - nuke IOWorker
} else {
- this.visibleChunkMap.values().stream().filter(ChunkHolder::wasAccessibleSinceLastSave).forEach((playerchunk) -> {
+ visibleChunks.values().stream().filter(ChunkHolder::wasAccessibleSinceLastSave).forEach((playerchunk) -> {
ChunkAccess ichunkaccess = (ChunkAccess) playerchunk.getChunkToSave().getNow(null); // CraftBukkit - decompile error
if (ichunkaccess instanceof ImposterProtoChunk || ichunkaccess instanceof LevelChunk) {
@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
if (!this.modified) {
return false;
} else {
- this.visibleChunkMap = this.updatingChunkMap.clone();
+ // Paper start - stop cloning visibleChunks
+ synchronized (this.visibleChunkMap) {
+ if (isIterating) {
+ hasPendingVisibleUpdate = true;
+ this.pendingVisibleChunks.copyFrom((com.destroystokyo.paper.util.map.Long2ObjectLinkedOpenHashMapFastCopy<ChunkHolder>)this.updatingChunkMap);
+ } else {
+ hasPendingVisibleUpdate = false;
+ this.pendingVisibleChunks.clear();
+ ((ProtectedVisibleChunksMap)this.visibleChunkMap).copyFrom((com.destroystokyo.paper.util.map.Long2ObjectLinkedOpenHashMapFastCopy<ChunkHolder>)this.updatingChunkMap);
+ this.visibleChunksClone = null;
+ }
+ }
+ // Paper end
+
this.modified = false;
return true;
}
@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
}
protected Iterable<ChunkHolder> getChunks() {
- return Iterables.unmodifiableIterable(this.visibleChunkMap.values());
+ return Iterables.unmodifiableIterable(this.getVisibleChunks().values()); // Paper
}
void dumpChunks(Writer writer) throws IOException {
CsvOutput csvwriter = CsvOutput.builder().addColumn("x").addColumn("z").addColumn("level").addColumn("in_memory").addColumn("status").addColumn("full_status").addColumn("accessible_ready").addColumn("ticking_ready").addColumn("entity_ticking_ready").addColumn("ticket").addColumn("spawning").addColumn("block_entity_count").build(writer);
- ObjectBidirectionalIterator objectbidirectionaliterator = this.visibleChunkMap.long2ObjectEntrySet().iterator();
+ ObjectBidirectionalIterator objectbidirectionaliterator = this.getVisibleChunks().long2ObjectEntrySet().iterator(); // Paper
while (objectbidirectionaliterator.hasNext()) {
Entry<ChunkHolder> entry = (Entry) objectbidirectionaliterator.next();
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.lastSpawnState = spawnercreature_d;
this.level.getProfiler().pop();
- List<ChunkHolder> list = Lists.newArrayList(this.chunkMap.getChunks());
+ List<ChunkHolder> list = Lists.newArrayList(this.chunkMap.visibleChunkMap.values()); // Paper
Collections.shuffle(list);
//Paper start - call player naturally spawn event
diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
@@ -0,0 +0,0 @@ public class CraftWorld extends CraftRegionAccessor implements World {
@Override
public int getTileEntityCount() {
+ return net.minecraft.server.MCUtil.ensureMain(() -> {
// We don't use the full world tile entity list, so we must iterate chunks
Long2ObjectLinkedOpenHashMap<ChunkHolder> chunks = world.getChunkSource().chunkMap.visibleChunkMap;
int size = 0;
@@ -0,0 +0,0 @@ public class CraftWorld extends CraftRegionAccessor implements World {
size += chunk.blockEntities.size();
}
return size;
+ });
}
@Override
@@ -0,0 +0,0 @@ public class CraftWorld extends CraftRegionAccessor implements World {
@Override
public int getChunkCount() {
+ return net.minecraft.server.MCUtil.ensureMain(() -> {
int ret = 0;
for (ChunkHolder chunkHolder : world.getChunkSource().chunkMap.visibleChunkMap.values()) {
@@ -0,0 +0,0 @@ public class CraftWorld extends CraftRegionAccessor implements World {
}
}
- return ret;
+ return ret; });
}
@Override
@@ -0,0 +0,0 @@ public class CraftWorld extends CraftRegionAccessor implements World {
@Override
public Chunk[] getLoadedChunks() {
+ // Paper start
+ if (Thread.currentThread() != world.getLevel().thread) {
+ synchronized (world.getChunkSource().chunkMap.visibleChunkMap) {
+ Long2ObjectLinkedOpenHashMap<ChunkHolder> chunks = world.getChunkSource().chunkMap.visibleChunkMap;
+ return chunks.values().stream().map(ChunkHolder::getFullChunk).filter(Objects::nonNull).map(net.minecraft.world.level.chunk.LevelChunk::getBukkitChunk).toArray(Chunk[]::new);
+ }
+ }
+ // Paper end
Long2ObjectLinkedOpenHashMap<ChunkHolder> chunks = this.world.getChunkSource().chunkMap.visibleChunkMap;
return chunks.values().stream().map(ChunkHolder::getFullChunk).filter(Objects::nonNull).map(net.minecraft.world.level.chunk.LevelChunk::getBukkitChunk).toArray(Chunk[]::new);
}

View File

@ -137,9 +137,9 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ } + }
+ }); + });
+ // Paper end - optimise PlayerChunkMap#isOutsideRange + // Paper end - optimise PlayerChunkMap#isOutsideRange
// Paper start - no-tick view distance }
this.setNoTickViewDistance(this.level.paperConfig.noTickViewDistance);
this.playerViewDistanceTickMap = new com.destroystokyo.paper.util.misc.PlayerAreaMap(this.pooledLinkedPlayerHashSets, // Paper start
@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider @@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
} else { } else {
if (holder != null) { if (holder != null) {
@ -156,11 +156,6 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
- int chunkRange = level.spigotConfig.mobSpawnRange; - int chunkRange = level.spigotConfig.mobSpawnRange;
- chunkRange = (chunkRange > level.spigotConfig.viewDistance) ? (byte) level.spigotConfig.viewDistance : chunkRange; - chunkRange = (chunkRange > level.spigotConfig.viewDistance) ? (byte) level.spigotConfig.viewDistance : chunkRange;
- chunkRange = (chunkRange > 8) ? 8 : chunkRange; - chunkRange = (chunkRange > 8) ? 8 : chunkRange;
-
- final int finalChunkRange = chunkRange; // Paper for lambda below
- //double blockRange = (reducedRange) ? Math.pow(chunkRange << 4, 2) : 16384.0D; // Paper - use from event
- // Spigot end
- long i = chunkcoordintpair.toLong();
+ // Paper start - optimise isOutsideOfRange + // Paper start - optimise isOutsideOfRange
+ final boolean isOutsideOfRange(ChunkPos chunkcoordintpair, boolean reducedRange) { + final boolean isOutsideOfRange(ChunkPos chunkcoordintpair, boolean reducedRange) {
+ return this.isOutsideOfRange(this.getUpdatingChunkIfPresent(chunkcoordintpair.toLong()), chunkcoordintpair, reducedRange); + return this.isOutsideOfRange(this.getUpdatingChunkIfPresent(chunkcoordintpair.toLong()), chunkcoordintpair, reducedRange);
@ -174,6 +169,11 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ } + }
+ Object[] backingSet = playersInRange.getBackingSet(); + Object[] backingSet = playersInRange.getBackingSet();
- final int finalChunkRange = chunkRange; // Paper for lambda below
- //double blockRange = (reducedRange) ? Math.pow(chunkRange << 4, 2) : 16384.0D; // Paper - use from event
- // Spigot end
- long i = chunkcoordintpair.toLong();
-
- return !this.distanceManager.hasPlayersNearby(i) ? true : this.playerMap.getPlayers(i).noneMatch((entityplayer) -> { - return !this.distanceManager.hasPlayersNearby(i) ? true : this.playerMap.getPlayers(i).noneMatch((entityplayer) -> {
- // Paper start - add PlayerNaturallySpawnCreaturesEvent - // Paper start - add PlayerNaturallySpawnCreaturesEvent
- com.destroystokyo.paper.event.entity.PlayerNaturallySpawnCreaturesEvent event; - com.destroystokyo.paper.event.entity.PlayerNaturallySpawnCreaturesEvent event;
@ -333,7 +333,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
int k = this.level.getGameRules().getInt(GameRules.RULE_RANDOMTICKING); int k = this.level.getGameRules().getInt(GameRules.RULE_RANDOMTICKING);
boolean flag2 = level.ticksPerAnimalSpawns != 0L && worlddata.getGameTime() % level.ticksPerAnimalSpawns == 0L; // CraftBukkit boolean flag2 = level.ticksPerAnimalSpawns != 0L && worlddata.getGameTime() % level.ticksPerAnimalSpawns == 0L; // CraftBukkit
@@ -0,0 +0,0 @@ public class ServerChunkCache extends ChunkSource { @@ -0,0 +0,0 @@ public class ServerChunkCache extends ChunkSource {
List<ChunkHolder> list = Lists.newArrayList(this.chunkMap.visibleChunkMap.values()); // Paper List<ChunkHolder> list = Lists.newArrayList(this.chunkMap.getChunks());
Collections.shuffle(list); Collections.shuffle(list);
- //Paper start - call player naturally spawn event - //Paper start - call player naturally spawn event
@ -347,8 +347,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
- // Paper end - // Paper end
+ // Paper - moved natural spawn event up + // Paper - moved natural spawn event up
this.level.timings.chunkTicks.startTiming(); // Paper this.level.timings.chunkTicks.startTiming(); // Paper
final int[] chunksTicked = {0}; // Paper
list.forEach((playerchunk) -> { list.forEach((playerchunk) -> {
Optional<LevelChunk> optional = ((Either) playerchunk.getTickingChunkFuture().getNow(ChunkHolder.UNLOADED_LEVEL_CHUNK)).left();
@@ -0,0 +0,0 @@ public class ServerChunkCache extends ChunkSource { @@ -0,0 +0,0 @@ public class ServerChunkCache extends ChunkSource {
LevelChunk chunk = (LevelChunk) optional.get(); LevelChunk chunk = (LevelChunk) optional.get();
ChunkPos chunkcoordintpair = chunk.getPos(); ChunkPos chunkcoordintpair = chunk.getPos();
@ -359,8 +359,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
- if (flag1 && (this.spawnEnemies || this.spawnFriendlies) && this.level.getWorldBorder().isWithinBounds(chunk.getPos()) && !this.chunkMap.isOutsideOfRange(chunkcoordintpair, true)) { // Spigot - if (flag1 && (this.spawnEnemies || this.spawnFriendlies) && this.level.getWorldBorder().isWithinBounds(chunk.getPos()) && !this.chunkMap.isOutsideOfRange(chunkcoordintpair, true)) { // Spigot
+ if (flag1 && (this.spawnEnemies || this.spawnFriendlies) && this.level.getWorldBorder().isWithinBounds(chunk.getPos()) && !this.chunkMap.isOutsideOfRange(playerchunk, chunkcoordintpair, true)) { // Spigot // Paper - optimise isOutsideOfRange + if (flag1 && (this.spawnEnemies || this.spawnFriendlies) && this.level.getWorldBorder().isWithinBounds(chunk.getPos()) && !this.chunkMap.isOutsideOfRange(playerchunk, chunkcoordintpair, true)) { // Spigot // Paper - optimise isOutsideOfRange
NaturalSpawner.spawnForChunk(this.level, chunk, spawnercreature_d, this.spawnFriendlies, this.spawnEnemies, flag2); NaturalSpawner.spawnForChunk(this.level, chunk, spawnercreature_d, this.spawnFriendlies, this.spawnEnemies, flag2);
if (chunksTicked[0]++ % 10 == 0) this.level.getServer().midTickLoadChunks(); // Paper
} }
diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java 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 index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java --- a/src/main/java/net/minecraft/server/level/ServerPlayer.java

View File

@ -364,8 +364,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ commands = new HashMap<String, Command>(); + commands = new HashMap<String, Command>();
+ commands.put("paper", new PaperCommand("paper")); + commands.put("paper", new PaperCommand("paper"));
+ +
+ version = getInt("config-version", 21); + version = getInt("config-version", 22);
+ set("config-version", 21); + set("config-version", 22);
+ readConfig(PaperConfig.class, null); + readConfig(PaperConfig.class, null);
+ } + }
+ +

View File

@ -17,8 +17,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
--- a/src/main/java/com/destroystokyo/paper/PaperConfig.java --- a/src/main/java/com/destroystokyo/paper/PaperConfig.java
+++ b/src/main/java/com/destroystokyo/paper/PaperConfig.java +++ b/src/main/java/com/destroystokyo/paper/PaperConfig.java
@@ -0,0 +0,0 @@ public class PaperConfig { @@ -0,0 +0,0 @@ public class PaperConfig {
private static void midTickChunkTasks() { log("Async Chunks: Enabled - Chunks will be loaded much faster, without lag.");
midTickChunkTasks = getInt("settings.chunk-tasks-per-tick", midTickChunkTasks); }
} }
+ +
+ public static boolean allowBlockPermanentBreakingExploits = false; + public static boolean allowBlockPermanentBreakingExploits = false;
@ -32,7 +32,6 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ allowBlockPermanentBreakingExploits = getBoolean("settings.unsupported-settings.allow-permanent-block-break-exploits", allowBlockPermanentBreakingExploits); + allowBlockPermanentBreakingExploits = getBoolean("settings.unsupported-settings.allow-permanent-block-break-exploits", allowBlockPermanentBreakingExploits);
+ +
+ } + }
+
} }
diff --git a/src/main/java/net/minecraft/world/level/Explosion.java b/src/main/java/net/minecraft/world/level/Explosion.java diff --git a/src/main/java/net/minecraft/world/level/Explosion.java b/src/main/java/net/minecraft/world/level/Explosion.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644

View File

@ -0,0 +1,31 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Spottedleaf <Spottedleaf@users.noreply.github.com>
Date: Mon, 30 Aug 2021 04:26:40 -0700
Subject: [PATCH] Reduce worldgen thread worker count for low core count CPUs
diff --git a/src/main/java/net/minecraft/Util.java b/src/main/java/net/minecraft/Util.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/Util.java
+++ b/src/main/java/net/minecraft/Util.java
@@ -0,0 +0,0 @@ public class Util {
private static ExecutorService makeExecutor(String s, int priorityModifier) { // Paper - add priority
// Paper start - use simpler thread pool that allows 1 thread
- int i = Math.min(8, Math.max(Runtime.getRuntime().availableProcessors() - 2, 1));
+ // Paper start - also try to avoid suffocating the system with the worldgen workers
+ int cpus = Runtime.getRuntime().availableProcessors() / 2;
+ int i;
+ if (cpus <= 4) {
+ i = cpus <= 2 ? 1 : 2;
+ } else if (cpus <= 8) {
+ // [5, 8]
+ i = Math.max(3, cpus - 2);
+ } else {
+ i = cpus * 2 / 3;
+ }
+ i = Math.min(8, i);
+ // Paper end - also try to avoid suffocating the system with the worldgen workers
i = Integer.getInteger("Paper.WorkerThreadCount", i);
ExecutorService executorService;

View File

@ -0,0 +1,327 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Spottedleaf <spottedleaf@spottedleaf.dev>
Date: Thu, 27 Aug 2020 20:51:40 -0700
Subject: [PATCH] Remove streams for villager AI
diff --git a/src/main/java/net/minecraft/world/entity/ai/behavior/GateBehavior.java b/src/main/java/net/minecraft/world/entity/ai/behavior/GateBehavior.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/world/entity/ai/behavior/GateBehavior.java
+++ b/src/main/java/net/minecraft/world/entity/ai/behavior/GateBehavior.java
@@ -0,0 +0,0 @@ public class GateBehavior<E extends LivingEntity> extends Behavior<E> {
@Override
protected boolean canStillUse(ServerLevel world, E entity, long time) {
- return this.behaviors.stream().filter((behavior) -> {
- return behavior.getStatus() == Behavior.Status.RUNNING;
- }).anyMatch((behavior) -> {
- return behavior.canStillUse(world, entity, time);
- });
+ // Paper start - remove streams
+ List<ShufflingList.WeightedEntry<Behavior<? super E>>> entries = this.behaviors.entries;
+ for (int i = 0; i < entries.size(); i++) {
+ ShufflingList.WeightedEntry<Behavior<? super E>> entry = entries.get(i);
+ Behavior<? super E> behavior = entry.getData();
+ if (behavior.getStatus() == Status.RUNNING) {
+ if (behavior.canStillUse(world, entity, time)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ // Paper end - remove streams
}
@Override
@@ -0,0 +0,0 @@ public class GateBehavior<E extends LivingEntity> extends Behavior<E> {
@Override
protected void start(ServerLevel world, E entity, long time) {
this.orderPolicy.apply(this.behaviors);
- this.runningPolicy.apply(this.behaviors.stream(), world, entity, time);
+ this.runningPolicy.apply(this.behaviors.entries, world, entity, time); // Paper - remove streams
}
@Override
protected void tick(ServerLevel world, E entity, long time) {
- this.behaviors.stream().filter((behavior) -> {
- return behavior.getStatus() == Behavior.Status.RUNNING;
- }).forEach((behavior) -> {
- behavior.tickOrStop(world, entity, time);
- });
+ // Paper start - remove streams
+ List<ShufflingList.WeightedEntry<Behavior<? super E>>> entries = this.behaviors.entries;
+ for (int i = 0; i < entries.size(); i++) {
+ ShufflingList.WeightedEntry<Behavior<? super E>> entry = entries.get(i);
+ Behavior<? super E> behavior = entry.getData();
+ if (behavior.getStatus() == Status.RUNNING) {
+ behavior.tickOrStop(world, entity, time);
+ }
+ }
+ // Paper end - remove streams
}
@Override
protected void stop(ServerLevel world, E entity, long time) {
- this.behaviors.stream().filter((behavior) -> {
- return behavior.getStatus() == Behavior.Status.RUNNING;
- }).forEach((behavior) -> {
- behavior.doStop(world, entity, time);
- });
+ // Paper start - remove streams
+ List<ShufflingList.WeightedEntry<Behavior<? super E>>> entries = this.behaviors.entries;
+ for (int i = 0; i < entries.size(); i++) {
+ ShufflingList.WeightedEntry<Behavior<? super E>> entry = entries.get(i);
+ Behavior<? super E> behavior = entry.getData();
+ if (behavior.getStatus() == Status.RUNNING) {
+ behavior.doStop(world, entity, time);
+ }
+ }
+ // Paper end - remove streams
this.exitErasedMemories.forEach(entity.getBrain()::eraseMemory);
}
@@ -0,0 +0,0 @@ public class GateBehavior<E extends LivingEntity> extends Behavior<E> {
public static enum RunningPolicy {
RUN_ONE {
@Override
- public <E extends LivingEntity> void apply(Stream<Behavior<? super E>> tasks, ServerLevel world, E entity, long time) {
- tasks.filter((behavior) -> {
- return behavior.getStatus() == Behavior.Status.STOPPED;
- }).filter((behavior) -> {
- return behavior.tryStart(world, entity, time);
- }).findFirst();
+ // Paper start - remove streams
+ public <E extends LivingEntity> void apply(List<ShufflingList.WeightedEntry<Behavior<? super E>>> tasks, ServerLevel world, E entity, long time) {
+ for (int i = 0; i < tasks.size(); i++) {
+ ShufflingList.WeightedEntry<Behavior<? super E>> task = tasks.get(i);
+ Behavior<? super E> behavior = task.getData();
+ if (behavior.getStatus() == Status.STOPPED && behavior.tryStart(world, entity, time)) {
+ break;
+ }
+ }
+ // Paper end - remove streams
}
},
TRY_ALL {
@Override
- public <E extends LivingEntity> void apply(Stream<Behavior<? super E>> tasks, ServerLevel world, E entity, long time) {
- tasks.filter((behavior) -> {
- return behavior.getStatus() == Behavior.Status.STOPPED;
- }).forEach((behavior) -> {
- behavior.tryStart(world, entity, time);
- });
+ // Paper start - remove streams
+ public <E extends LivingEntity> void apply(List<ShufflingList.WeightedEntry<Behavior<? super E>>> tasks, ServerLevel world, E entity, long time) {
+ for (int i = 0; i < tasks.size(); i++) {
+ ShufflingList.WeightedEntry<Behavior<? super E>> task = tasks.get(i);
+ Behavior<? super E> behavior = task.getData();
+ if (behavior.getStatus() == Status.STOPPED) {
+ behavior.tryStart(world, entity, time);
+ }
+ }
+ // Paper end - remove streams
}
};
- public abstract <E extends LivingEntity> void apply(Stream<Behavior<? super E>> tasks, ServerLevel world, E entity, long time);
+ public abstract <E extends LivingEntity> void apply(List<ShufflingList.WeightedEntry<Behavior<? super E>>> tasks, ServerLevel world, E entity, long time); // Paper - remove streams
}
}
diff --git a/src/main/java/net/minecraft/world/entity/ai/behavior/SetLookAndInteract.java b/src/main/java/net/minecraft/world/entity/ai/behavior/SetLookAndInteract.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/world/entity/ai/behavior/SetLookAndInteract.java
+++ b/src/main/java/net/minecraft/world/entity/ai/behavior/SetLookAndInteract.java
@@ -0,0 +0,0 @@ public class SetLookAndInteract extends Behavior<LivingEntity> {
@Override
public boolean checkExtraStartConditions(ServerLevel world, LivingEntity entity) {
- return this.selfFilter.test(entity) && this.getVisibleEntities(entity).stream().anyMatch(this::isMatchingTarget);
+ // Paper start - remove streams
+ if (!this.selfFilter.test(entity)) {
+ return false;
+ }
+
+ List<LivingEntity> visibleEntities = this.getVisibleEntities(entity);
+ for (int i = 0; i < visibleEntities.size(); i++) {
+ LivingEntity livingEntity = visibleEntities.get(i);
+ if (this.isMatchingTarget(livingEntity)) {
+ return true;
+ }
+ }
+ return false;
+ // Paper end - remove streams
}
@Override
public void start(ServerLevel world, LivingEntity entity, long time) {
super.start(world, entity, time);
Brain<?> brain = entity.getBrain();
- brain.getMemory(MemoryModuleType.NEAREST_VISIBLE_LIVING_ENTITIES).ifPresent((list) -> {
- list.stream().filter((livingEntity2) -> {
- return livingEntity2.distanceToSqr(entity) <= (double)this.interactionRangeSqr;
- }).filter(this::isMatchingTarget).findFirst().ifPresent((livingEntity) -> {
- brain.setMemory(MemoryModuleType.INTERACTION_TARGET, livingEntity);
- brain.setMemory(MemoryModuleType.LOOK_TARGET, new EntityTracker(livingEntity, true));
- });
- });
+ // Paper start - remove streams
+ List<LivingEntity> list = brain.getMemory(MemoryModuleType.NEAREST_VISIBLE_LIVING_ENTITIES).orElse(null);
+ if (list != null) {
+ double maxRangeSquared = (double)this.interactionRangeSqr;
+ for (int i = 0; i < list.size(); i++) {
+ LivingEntity livingEntity2 = list.get(i);
+ if (livingEntity2.distanceToSqr(entity) <= maxRangeSquared) {
+ if (this.isMatchingTarget(livingEntity2)) {
+ brain.setMemory(MemoryModuleType.INTERACTION_TARGET, livingEntity2);
+ brain.setMemory(MemoryModuleType.LOOK_TARGET, new EntityTracker(livingEntity2, true));
+ break;
+ }
+ }
+ }
+ }
+ // Paper end - remove streams
}
private boolean isMatchingTarget(LivingEntity entity) {
diff --git a/src/main/java/net/minecraft/world/entity/ai/behavior/ShufflingList.java b/src/main/java/net/minecraft/world/entity/ai/behavior/ShufflingList.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/world/entity/ai/behavior/ShufflingList.java
+++ b/src/main/java/net/minecraft/world/entity/ai/behavior/ShufflingList.java
@@ -0,0 +0,0 @@ import java.util.Random;
import java.util.stream.Stream;
public class ShufflingList<U> {
- protected final List<ShufflingList.WeightedEntry<U>> entries;
+ public final List<ShufflingList.WeightedEntry<U>> entries; // Paper - public
private final Random random = new Random();
private final boolean isUnsafe; // Paper
diff --git a/src/main/java/net/minecraft/world/entity/ai/sensing/NearestItemSensor.java b/src/main/java/net/minecraft/world/entity/ai/sensing/NearestItemSensor.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/world/entity/ai/sensing/NearestItemSensor.java
+++ b/src/main/java/net/minecraft/world/entity/ai/sensing/NearestItemSensor.java
@@ -0,0 +0,0 @@ public class NearestItemSensor extends Sensor<Mob> {
protected void doTick(ServerLevel world, Mob entity) {
Brain<?> brain = entity.getBrain();
List<ItemEntity> list = world.getEntitiesOfClass(ItemEntity.class, entity.getBoundingBox().inflate(8.0D, 4.0D, 8.0D), (itemEntity) -> {
- return true;
+ return itemEntity.closerThan(entity, 9.0D) && entity.wantsToPickUp(itemEntity.getItem()); // Paper - move predicate into getEntities
});
- list.sort(Comparator.comparingDouble(entity::distanceToSqr));
+ list.sort((e1, e2) -> Double.compare(entity.distanceToSqr(e1), entity.distanceToSqr(e2))); // better to take the sort perf hit than using line of sight more than we need to.
+ // Paper start - remove streams
// Paper start - remove streams in favour of lists
ItemEntity nearest = null;
- for (ItemEntity entityItem : list) {
- if (entity.wantsToPickUp(entityItem.getItem()) && entityItem.closerThan(entity, 9.0D) && entity.hasLineOfSight(entityItem)) {
+ for (int i = 0; i < list.size(); i++) {
+ ItemEntity entityItem = list.get(i);
+ if (entity.hasLineOfSight(entityItem)) {
nearest = entityItem;
break;
}
}
+ // Paper end - remove streams
brain.setMemory(MemoryModuleType.NEAREST_VISIBLE_WANTED_ITEM, Optional.ofNullable(nearest));
// Paper end
}
diff --git a/src/main/java/net/minecraft/world/entity/ai/sensing/NearestLivingEntitySensor.java b/src/main/java/net/minecraft/world/entity/ai/sensing/NearestLivingEntitySensor.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/world/entity/ai/sensing/NearestLivingEntitySensor.java
+++ b/src/main/java/net/minecraft/world/entity/ai/sensing/NearestLivingEntitySensor.java
@@ -0,0 +0,0 @@ public class NearestLivingEntitySensor extends Sensor<LivingEntity> {
List<LivingEntity> list = world.getEntitiesOfClass(LivingEntity.class, aABB, (livingEntity2) -> {
return livingEntity2 != entity && livingEntity2.isAlive();
});
- list.sort(Comparator.comparingDouble(entity::distanceToSqr));
+ // Paper start - remove streams
+ list.sort((e1, e2) -> Double.compare(entity.distanceToSqr(e1), entity.distanceToSqr(e2)));
Brain<?> brain = entity.getBrain();
brain.setMemory(MemoryModuleType.NEAREST_LIVING_ENTITIES, list);
// Paper start - remove streams in favour of lists
- List<LivingEntity> visibleMobs = new java.util.ArrayList<>(list);
- visibleMobs.removeIf(otherEntityLiving -> !Sensor.isEntityTargetable(entity, otherEntityLiving));
+ List<LivingEntity> visibleMobs = new java.util.ArrayList<>();
+ for (int i = 0, len = list.size(); i < len; i++) {
+ LivingEntity nearby = list.get(i);
+ if (Sensor.isEntityTargetable(entity, nearby)) {
+ visibleMobs.add(nearby);
+ }
+ }
+ // Paper end - remove streams
brain.setMemory(MemoryModuleType.NEAREST_VISIBLE_LIVING_ENTITIES, visibleMobs);
// Paper end
}
diff --git a/src/main/java/net/minecraft/world/entity/ai/sensing/PlayerSensor.java b/src/main/java/net/minecraft/world/entity/ai/sensing/PlayerSensor.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/world/entity/ai/sensing/PlayerSensor.java
+++ b/src/main/java/net/minecraft/world/entity/ai/sensing/PlayerSensor.java
@@ -0,0 +0,0 @@ public class PlayerSensor extends Sensor<LivingEntity> {
@Override
protected void doTick(ServerLevel world, LivingEntity entity) {
- // Paper start - remove streams in favour of lists
- List<Player> players = new java.util.ArrayList<>(world.players());
- players.removeIf(player -> !EntitySelector.NO_SPECTATORS.test(player) || !entity.closerThan(player, 16.0D)); // Paper - removeIf only re-allocates once compared to iterator
+ // Paper start - remove streams
+ List<Player> players = (List)world.getNearbyPlayers(entity, entity.getX(), entity.getY(), entity.getZ(), 16.0D, EntitySelector.NO_SPECTATORS);
+ players.sort((e1, e2) -> Double.compare(entity.distanceToSqr(e1), entity.distanceToSqr(e2)));
Brain<?> brain = entity.getBrain();
brain.setMemory(MemoryModuleType.NEAREST_PLAYERS, players);
- Player nearest = null, nearestTargetable = null;
- for (Player player : players) {
- if (Sensor.isEntityTargetable(entity, player)) {
- if (nearest == null) nearest = player;
- if (Sensor.isEntityAttackable(entity, player)) {
- nearestTargetable = player;
- break; // Both variables are assigned, no reason to loop further
- }
+ Player firstTargetable = null;
+ Player firstAttackable = null;
+ for (int index = 0, len = players.size(); index < len; ++index) {
+ Player player = players.get(index);
+ if (firstTargetable == null && isEntityTargetable(entity, player)) {
+ firstTargetable = player;
+ }
+ if (firstAttackable == null && isEntityAttackable(entity, player)) {
+ firstAttackable = player;
+ }
+
+ if (firstAttackable != null && firstTargetable != null) {
+ break;
}
}
- brain.setMemory(MemoryModuleType.NEAREST_VISIBLE_PLAYER, nearest);
- brain.setMemory(MemoryModuleType.NEAREST_VISIBLE_ATTACKABLE_PLAYER, nearestTargetable);
- // Paper end
+
+ brain.setMemory(MemoryModuleType.NEAREST_VISIBLE_PLAYER, firstTargetable);
+ brain.setMemory(MemoryModuleType.NEAREST_VISIBLE_ATTACKABLE_PLAYER, Optional.ofNullable(firstAttackable));
+ // Paper end - remove streams
}
}
diff --git a/src/main/java/net/minecraft/world/entity/ai/sensing/VillagerBabiesSensor.java b/src/main/java/net/minecraft/world/entity/ai/sensing/VillagerBabiesSensor.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/world/entity/ai/sensing/VillagerBabiesSensor.java
+++ b/src/main/java/net/minecraft/world/entity/ai/sensing/VillagerBabiesSensor.java
@@ -0,0 +0,0 @@ public class VillagerBabiesSensor extends Sensor<LivingEntity> {
}
private List<LivingEntity> getNearestVillagerBabies(LivingEntity entities) {
- return this.getVisibleEntities(entities).stream().filter(this::isVillagerBaby).collect(Collectors.toList());
+ // Paper start - remove streams
+ List<LivingEntity> list = new java.util.ArrayList<>();
+ List<LivingEntity> visibleEntities = this.getVisibleEntities(entities);
+ for (int i = 0; i < visibleEntities.size(); i++) {
+ LivingEntity livingEntity = visibleEntities.get(i);
+ if (this.isVillagerBaby(livingEntity)) {
+ list.add(livingEntity);
+ }
+ }
+ return list;
+ // Paper end - remove streams
}
private boolean isVillagerBaby(LivingEntity entity) {

View File

@ -151,7 +151,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ int wrappedGoalSize = wrappedGoalSet.size(); + int wrappedGoalSize = wrappedGoalSet.size();
+ for (int i = 0; i < wrappedGoalSize; ++i) { + for (int i = 0; i < wrappedGoalSize; ++i) {
+ Goal.Flag type = PATHFINDER_GOAL_TYPES[Long.numberOfTrailingZeros(iterator1)]; + Goal.Flag type = PATHFINDER_GOAL_TYPES[Long.numberOfTrailingZeros(iterator1)];
+ iterator1 ^= com.destroystokyo.paper.util.math.IntegerUtil.getTrailingBit(iterator1); + iterator1 ^= io.papermc.paper.util.IntegerUtil.getTrailingBit(iterator1);
+ WrappedGoal wrapped = this.lockedFlags.getOrDefault(type, GoalSelector.NO_GOAL); + WrappedGoal wrapped = this.lockedFlags.getOrDefault(type, GoalSelector.NO_GOAL);
+ if (!wrapped.canBeReplacedBy(wrappedGoal)) { + if (!wrapped.canBeReplacedBy(wrappedGoal)) {
+ continue goal_update_loop; + continue goal_update_loop;
@ -166,7 +166,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ wrappedGoalSize = wrappedGoalSet.size(); + wrappedGoalSize = wrappedGoalSet.size();
+ for (int i = 0; i < wrappedGoalSize; ++i) { + for (int i = 0; i < wrappedGoalSize; ++i) {
+ Goal.Flag type = PATHFINDER_GOAL_TYPES[Long.numberOfTrailingZeros(iterator1)]; + Goal.Flag type = PATHFINDER_GOAL_TYPES[Long.numberOfTrailingZeros(iterator1)];
+ iterator1 ^= com.destroystokyo.paper.util.math.IntegerUtil.getTrailingBit(iterator1); + iterator1 ^= io.papermc.paper.util.IntegerUtil.getTrailingBit(iterator1);
+ WrappedGoal wrapped = this.lockedFlags.getOrDefault(type, GoalSelector.NO_GOAL); + WrappedGoal wrapped = this.lockedFlags.getOrDefault(type, GoalSelector.NO_GOAL);
+ +
+ wrapped.stop(); + wrapped.stop();

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,248 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Spottedleaf <Spottedleaf@users.noreply.github.com>
Date: Sun, 21 Mar 2021 16:25:42 -0700
Subject: [PATCH] Replace ticket level propagator
Mojang's propagator is slow, and this isn't surprising
given it's built on the same utilities the vanilla light engine
is built on. The simple propagator I wrote is approximately 4x
faster when simulating player movement. For a long time timing
reports have shown this function take up significant tick, (
approx 10% or more), and async sampling data shows the level
propagation alone takes up a significant amount. So this
should help with that. A big side effect is that mid-tick
will be more effective, since more time will be allocated
to actually processing chunk tasks vs the ticket level updates.
diff --git a/src/main/java/net/minecraft/server/level/DistanceManager.java b/src/main/java/net/minecraft/server/level/DistanceManager.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/server/level/DistanceManager.java
+++ b/src/main/java/net/minecraft/server/level/DistanceManager.java
@@ -0,0 +0,0 @@ import net.minecraft.world.level.chunk.LevelChunk;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
+import it.unimi.dsi.fastutil.longs.Long2IntLinkedOpenHashMap; // Paper
public abstract class DistanceManager {
static final Logger LOGGER = LogManager.getLogger();
@@ -0,0 +0,0 @@ public abstract class DistanceManager {
private static final int INITIAL_TICKET_LIST_CAPACITY = 4;
final Long2ObjectMap<ObjectSet<ServerPlayer>> playersPerChunk = new Long2ObjectOpenHashMap();
public final Long2ObjectOpenHashMap<SortedArraySet<Ticket<?>>> tickets = new Long2ObjectOpenHashMap();
- private final DistanceManager.ChunkTicketTracker ticketTracker = new DistanceManager.ChunkTicketTracker();
+ //private final DistanceManager.ChunkTicketTracker ticketTracker = new DistanceManager.ChunkTicketTracker(); // Paper - replace ticket level propagator
public static final int MOB_SPAWN_RANGE = 8; // private final ChunkMapDistance.b f = new ChunkMapDistance.b(8); // Paper - no longer used
//private final DistanceManager.PlayerTicketTracker playerTicketManager = new DistanceManager.PlayerTicketTracker(33); // Paper - no longer used
// Paper start use a queue, but still keep unique requirement
@@ -0,0 +0,0 @@ public abstract class DistanceManager {
this.mainThreadExecutor = mainThreadExecutor;
}
+ // Paper start - replace ticket level propagator
+ protected final Long2IntLinkedOpenHashMap ticketLevelUpdates = new Long2IntLinkedOpenHashMap() {
+ @Override
+ protected void rehash(int newN) {
+ // no downsizing allowed
+ if (newN < this.n) {
+ return;
+ }
+ super.rehash(newN);
+ }
+ };
+ protected final io.papermc.paper.util.misc.Delayed8WayDistancePropagator2D ticketLevelPropagator = new io.papermc.paper.util.misc.Delayed8WayDistancePropagator2D(
+ (long coordinate, byte oldLevel, byte newLevel) -> {
+ DistanceManager.this.ticketLevelUpdates.putAndMoveToLast(coordinate, convertBetweenTicketLevels(newLevel));
+ }
+ );
+ // function for converting between ticket levels and propagator levels and vice versa
+ // the problem is the ticket level propagator will propagate from a set source down to zero, whereas mojang expects
+ // levels to propagate from a set value up to a maximum value. so we need to convert the levels we put into the propagator
+ // and the levels we get out of the propagator
+
+ // this maps so that GOLDEN_TICKET + 1 will be 0 in the propagator, GOLDEN_TICKET will be 1, and so on
+ // we need GOLDEN_TICKET+1 as 0 because anything >= GOLDEN_TICKET+1 should be unloaded
+ public static int convertBetweenTicketLevels(final int level) {
+ return ChunkMap.MAX_CHUNK_DISTANCE - level + 1;
+ }
+
+ protected final int getPropagatedTicketLevel(final long coordinate) {
+ return convertBetweenTicketLevels(this.ticketLevelPropagator.getLevel(coordinate));
+ }
+
+ protected final void updateTicketLevel(final long coordinate, final int ticketLevel) {
+ if (ticketLevel > ChunkMap.MAX_CHUNK_DISTANCE) {
+ this.ticketLevelPropagator.removeSource(coordinate);
+ } else {
+ this.ticketLevelPropagator.setSource(coordinate, convertBetweenTicketLevels(ticketLevel));
+ }
+ }
+ // Paper end - replace ticket level propagator
+
protected void purgeStaleTickets() {
++this.ticketTickCounter;
ObjectIterator objectiterator = this.tickets.long2ObjectEntrySet().fastIterator();
@@ -0,0 +0,0 @@ public abstract class DistanceManager {
if ((entry.getValue()).removeIf((ticket) -> { // CraftBukkit - decompile error
return ticket.timedOut(this.ticketTickCounter);
})) {
- this.ticketTracker.update(entry.getLongKey(), DistanceManager.getTicketLevelAt((SortedArraySet) entry.getValue()), false);
+ this.updateTicketLevel(entry.getLongKey(), getTicketLevelAt(entry.getValue())); // Paper - replace ticket level propagator
}
if (((SortedArraySet) entry.getValue()).isEmpty()) {
@@ -0,0 +0,0 @@ public abstract class DistanceManager {
@Nullable
protected abstract ChunkHolder updateChunkScheduling(long pos, int level, @Nullable ChunkHolder holder, int k);
+ protected long ticketLevelUpdateCount; // Paper - replace ticket level propagator
public boolean runAllUpdates(ChunkMap playerchunkmap) {
//this.f.a(); // Paper - no longer used
org.spigotmc.AsyncCatcher.catchOp("DistanceManagerTick"); // Paper
//this.playerTicketManager.runAllUpdates(); // Paper - no longer used
- int i = Integer.MAX_VALUE - this.ticketTracker.runDistanceUpdates(Integer.MAX_VALUE);
- boolean flag = i != 0;
+ boolean flag = this.ticketLevelPropagator.propagateUpdates(); // Paper - replace ticket level propagator
if (flag) {
;
}
- // Paper start
- if (!this.pendingChunkUpdates.isEmpty()) {
- this.pollingPendingChunkUpdates = true; try { // Paper - Chunk priority
- while(!this.pendingChunkUpdates.isEmpty()) {
- ChunkHolder remove = this.pendingChunkUpdates.remove();
- remove.isUpdateQueued = false;
- remove.updateFutures(playerchunkmap, this.mainThreadExecutor);
- }
- } finally { this.pollingPendingChunkUpdates = false; } // Paper - Chunk priority
- // Paper end
- return true;
- } else {
- if (!this.ticketsToRelease.isEmpty()) {
- LongIterator longiterator = this.ticketsToRelease.iterator();
+ // Paper start - replace level propagator
+ ticket_update_loop:
+ while (!this.ticketLevelUpdates.isEmpty()) {
+ flag = true;
- while (longiterator.hasNext()) {
- long j = longiterator.nextLong();
+ boolean oldPolling = this.pollingPendingChunkUpdates;
+ this.pollingPendingChunkUpdates = true;
+ try {
+ for (java.util.Iterator<Long2IntMap.Entry> iterator = this.ticketLevelUpdates.long2IntEntrySet().fastIterator(); iterator.hasNext();) {
+ Long2IntMap.Entry entry = iterator.next();
+ long key = entry.getLongKey();
+ int newLevel = entry.getIntValue();
+ ChunkHolder chunk = this.getChunk(key);
+
+ if (chunk == null && newLevel > ChunkMap.MAX_CHUNK_DISTANCE) {
+ // not loaded and it shouldn't be loaded!
+ continue;
+ }
+
+ int currentLevel = chunk == null ? ChunkMap.MAX_CHUNK_DISTANCE + 1 : chunk.getTicketLevel();
+
+ if (currentLevel == newLevel) {
+ // nothing to do
+ continue;
+ }
- if (this.getTickets(j).stream().anyMatch((ticket) -> {
- return ticket.getType() == TicketType.PLAYER;
- })) {
- ChunkHolder playerchunk = playerchunkmap.getUpdatingChunkIfPresent(j);
+ this.updateChunkScheduling(key, newLevel, chunk, currentLevel);
+ }
- if (playerchunk == null) {
- throw new IllegalStateException();
+ long recursiveCheck = ++this.ticketLevelUpdateCount;
+ while (!this.ticketLevelUpdates.isEmpty()) {
+ long key = this.ticketLevelUpdates.firstLongKey();
+ int newLevel = this.ticketLevelUpdates.removeFirstInt();
+ ChunkHolder chunk = this.getChunk(key);
+
+ if (chunk == null) {
+ if (newLevel <= ChunkMap.MAX_CHUNK_DISTANCE) {
+ throw new IllegalStateException("Expected chunk holder to be created");
}
+ // not loaded and it shouldn't be loaded!
+ continue;
+ }
- CompletableFuture<Either<LevelChunk, ChunkHolder.ChunkLoadingFailure>> completablefuture = playerchunk.getEntityTickingChunkFuture();
+ int currentLevel = chunk.oldTicketLevel;
- completablefuture.thenAccept((either) -> {
- this.mainThreadExecutor.execute(() -> {
- this.ticketThrottlerReleaser.tell(ChunkTaskPriorityQueueSorter.release(() -> {
- }, j, false));
- });
- });
+ if (currentLevel == newLevel) {
+ // nothing to do
+ continue;
+ }
+
+ chunk.updateFutures(playerchunkmap, this.mainThreadExecutor);
+ if (recursiveCheck != this.ticketLevelUpdateCount) {
+ // back to the start, we must create player chunks and update the ticket level fields before
+ // processing the actual level updates
+ continue ticket_update_loop;
}
}
- this.ticketsToRelease.clear();
- }
+ for (;;) {
+ if (recursiveCheck != this.ticketLevelUpdateCount) {
+ continue ticket_update_loop;
+ }
+ ChunkHolder pendingUpdate = this.pendingChunkUpdates.poll();
+ if (pendingUpdate == null) {
+ break;
+ }
- return flag;
+ pendingUpdate.updateFutures(playerchunkmap, this.mainThreadExecutor);
+ }
+ } finally {
+ this.pollingPendingChunkUpdates = oldPolling;
+ }
}
+
+ return flag;
+ // Paper end - replace level propagator
}
boolean pollingPendingChunkUpdates = false; // Paper - Chunk priority
@@ -0,0 +0,0 @@ public abstract class DistanceManager {
ticket1.setCreatedTick(this.ticketTickCounter);
if (ticket.getTicketLevel() < j) {
- this.ticketTracker.update(i, ticket.getTicketLevel(), true);
+ this.updateTicketLevel(i, ticket.getTicketLevel()); // Paper - replace ticket level propagator
}
return ticket == ticket1; // CraftBukkit
@@ -0,0 +0,0 @@ public abstract class DistanceManager {
// Paper start - Chunk priority
int newLevel = getTicketLevelAt(arraysetsorted);
if (newLevel > oldLevel) {
- this.ticketTracker.update(i, newLevel, false);
+ this.updateTicketLevel(i, newLevel); // Paper // Paper - replace ticket level propagator
}
// Paper end
return removed; // CraftBukkit
@@ -0,0 +0,0 @@ public abstract class DistanceManager {
SortedArraySet<Ticket<?>> tickets = entry.getValue();
if (tickets.remove(target)) {
// copied from removeTicket
- this.ticketTracker.update(entry.getLongKey(), DistanceManager.getTicketLevelAt(tickets), false);
+ this.updateTicketLevel(entry.getLongKey(), getTicketLevelAt(tickets)); // Paper - replace ticket level propagator
// can't use entry after it's removed
if (tickets.isEmpty()) {

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,38 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Spottedleaf <Spottedleaf@users.noreply.github.com>
Date: Tue, 16 Feb 2021 00:16:56 -0800
Subject: [PATCH] Send full pos packets for hard colliding entities
Prevent collision problems due to desync (i.e boats)
Configurable under
`send-full-pos-for-hard-colliding-entities`
diff --git a/src/main/java/com/destroystokyo/paper/PaperConfig.java b/src/main/java/com/destroystokyo/paper/PaperConfig.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/com/destroystokyo/paper/PaperConfig.java
+++ b/src/main/java/com/destroystokyo/paper/PaperConfig.java
@@ -0,0 +0,0 @@ public class PaperConfig {
private static void lagCompensateBlockBreaking() {
lagCompensateBlockBreaking = getBoolean("settings.lag-compensate-block-breaking", true);
}
+
+ public static boolean sendFullPosForHardCollidingEntities;
+
+ private static void sendFullPosForHardCollidingEntities() {
+ sendFullPosForHardCollidingEntities = getBoolean("settings.send-full-pos-for-hard-colliding-entities", true);
+ }
}
diff --git a/src/main/java/net/minecraft/server/level/ServerEntity.java b/src/main/java/net/minecraft/server/level/ServerEntity.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/server/level/ServerEntity.java
+++ b/src/main/java/net/minecraft/server/level/ServerEntity.java
@@ -0,0 +0,0 @@ public class ServerEntity {
// Paper end - remove allocation of Vec3D here
boolean flag4 = k < -32768L || k > 32767L || l < -32768L || l > 32767L || i1 < -32768L || i1 > 32767L;
- if (!flag4 && this.teleportDelay <= 400 && !this.wasRiding && this.wasOnGround == this.entity.isOnGround()) {
+ if (!flag4 && this.teleportDelay <= 400 && !this.wasRiding && this.wasOnGround == this.entity.isOnGround() && !(com.destroystokyo.paper.PaperConfig.sendFullPosForHardCollidingEntities && this.entity.hardCollides())) { // Paper - send full pos for hard colliding entities to prevent collision problems due to desync
if ((!flag2 || !flag3) && !(this.entity instanceof AbstractArrow)) {
if (flag2) {
packet1 = new ClientboundMoveEntityPacket.Pos(this.entity.getId(), (short) ((int) k), (short) ((int) l), (short) ((int) i1), this.entity.isOnGround());

View File

@ -0,0 +1,43 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Spottedleaf <spottedleaf@spottedleaf.dev>
Date: Tue, 21 Apr 2020 01:53:22 -0700
Subject: [PATCH] Time scoreboard search
Plugins leaking scoreboards will make this very expensive,
let server owners debug it easily
diff --git a/src/main/java/co/aikar/timings/MinecraftTimings.java b/src/main/java/co/aikar/timings/MinecraftTimings.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/co/aikar/timings/MinecraftTimings.java
+++ b/src/main/java/co/aikar/timings/MinecraftTimings.java
@@ -0,0 +0,0 @@ public final class MinecraftTimings {
public static final Timing antiXrayUpdateTimer = Timings.ofSafe("anti-xray - update");
public static final Timing antiXrayObfuscateTimer = Timings.ofSafe("anti-xray - obfuscate");
public static final Timing distanceManagerTick = Timings.ofSafe("Distance Manager Tick"); // Paper - add timings for distance manager
+ public static final Timing scoreboardScoreSearch = Timings.ofSafe("Scoreboard score search"); // Paper - add timings for scoreboard search
public static final Timing midTickChunkTasks = Timings.ofSafe("Mid Tick Chunk Tasks");
diff --git a/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScoreboardManager.java b/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScoreboardManager.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScoreboardManager.java
+++ b/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScoreboardManager.java
@@ -0,0 +0,0 @@ public final class CraftScoreboardManager implements ScoreboardManager {
// CraftBukkit method
public void getScoreboardScores(ObjectiveCriteria criteria, String name, Consumer<Score> consumer) {
+ // Paper start - add timings for scoreboard search
+ // plugins leaking scoreboards will make this very expensive, let server owners debug it easily
+ co.aikar.timings.MinecraftTimings.scoreboardScoreSearch.startTimingIfSync();
+ try {
+ // Paper end - add timings for scoreboard search
for (CraftScoreboard scoreboard : this.scoreboards) {
Scoreboard board = scoreboard.board;
board.forAllObjectives(criteria, name, (score) -> consumer.accept(score));
}
+ } finally { // Paper start - add timings for scoreboard search
+ co.aikar.timings.MinecraftTimings.scoreboardScoreSearch.stopTimingIfSync();
+ }
+ // Paper end - add timings for scoreboard search
}
}

View File

@ -862,8 +862,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
this.profiler.push("snooper"); this.profiler.push("snooper");
@@ -0,0 +0,0 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa @@ -0,0 +0,0 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
}
io.papermc.paper.util.CachedLists.reset(); // Paper
this.profiler.pop(); this.profiler.pop();
+ +
+ // Paper start - move executeAll() into full server tick timing + // Paper start - move executeAll() into full server tick timing

View File

@ -0,0 +1,371 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Andrew Steinborn <git@steinborn.me>
Date: Mon, 26 Jul 2021 02:15:17 -0400
Subject: [PATCH] Use Velocity compression and cipher natives
diff --git a/build.gradle.kts b/build.gradle.kts
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -0,0 +0,0 @@ repositories {
}
}
// Paper end
+ maven("https://repo.velocitypowered.com/snapshots/") // Paper
}
dependencies {
@@ -0,0 +0,0 @@ dependencies {
implementation("io.netty:netty-all:4.1.65.Final") // Paper
implementation("org.quiltmc:tiny-mappings-parser:0.3.0") // Paper - needed to read mappings for stacktrace deobfuscation
+ implementation("com.velocitypowered:velocity-native:1.1.0-SNAPSHOT") // Paper
testImplementation("io.github.classgraph:classgraph:4.8.47") // Paper - mob goal test
testImplementation("junit:junit:4.13.1")
diff --git a/src/main/java/net/minecraft/network/CipherDecoder.java b/src/main/java/net/minecraft/network/CipherDecoder.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/network/CipherDecoder.java
+++ b/src/main/java/net/minecraft/network/CipherDecoder.java
@@ -0,0 +0,0 @@ import java.util.List;
import javax.crypto.Cipher;
public class CipherDecoder extends MessageToMessageDecoder<ByteBuf> {
- private final CipherBase cipher;
+ private final com.velocitypowered.natives.encryption.VelocityCipher cipher; // Paper
- public CipherDecoder(Cipher cipher) {
- this.cipher = new CipherBase(cipher);
+ public CipherDecoder(com.velocitypowered.natives.encryption.VelocityCipher cipher) { // Paper
+ this.cipher = cipher; // Paper
}
@Override
protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List<Object> list) throws Exception {
- list.add(this.cipher.decipher(channelHandlerContext, byteBuf));
+ // Paper start
+ ByteBuf compatible = com.velocitypowered.natives.util.MoreByteBufUtils.ensureCompatible(channelHandlerContext.alloc(), cipher, byteBuf);
+ try {
+ cipher.process(compatible);
+ list.add(compatible);
+ } catch (Exception e) {
+ compatible.release(); // compatible will never be used if we throw an exception
+ throw e;
+ }
+ // Paper end
}
+
+ // Paper start
+ @Override
+ public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
+ cipher.close();
+ }
+ // Paper end
}
diff --git a/src/main/java/net/minecraft/network/CipherEncoder.java b/src/main/java/net/minecraft/network/CipherEncoder.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/network/CipherEncoder.java
+++ b/src/main/java/net/minecraft/network/CipherEncoder.java
@@ -0,0 +0,0 @@ import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder;
import javax.crypto.Cipher;
+import java.util.List;
-public class CipherEncoder extends MessageToByteEncoder<ByteBuf> {
- private final CipherBase cipher;
+public class CipherEncoder extends io.netty.handler.codec.MessageToMessageEncoder<ByteBuf> { // Paper - change superclass
+ private final com.velocitypowered.natives.encryption.VelocityCipher cipher; // Paper
- public CipherEncoder(Cipher cipher) {
- this.cipher = new CipherBase(cipher);
+ public CipherEncoder(com.velocitypowered.natives.encryption.VelocityCipher cipher) { // Paper
+ this.cipher = cipher; // Paper
}
@Override
- protected void encode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, ByteBuf byteBuf2) throws Exception {
- this.cipher.encipher(byteBuf, byteBuf2);
+ protected void encode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List<Object> list) throws Exception {
+ // Paper start
+ ByteBuf compatible = com.velocitypowered.natives.util.MoreByteBufUtils.ensureCompatible(channelHandlerContext.alloc(), cipher, byteBuf);
+ try {
+ cipher.process(compatible);
+ list.add(compatible);
+ } catch (Exception e) {
+ compatible.release(); // compatible will never be used if we throw an exception
+ throw e;
+ }
+ // Paper end
}
+
+ // Paper start
+ @Override
+ public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
+ cipher.close();
+ }
+ // Paper end
}
diff --git a/src/main/java/net/minecraft/network/CompressionDecoder.java b/src/main/java/net/minecraft/network/CompressionDecoder.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/network/CompressionDecoder.java
+++ b/src/main/java/net/minecraft/network/CompressionDecoder.java
@@ -0,0 +0,0 @@ public class CompressionDecoder extends ByteToMessageDecoder {
public static final int MAXIMUM_COMPRESSED_LENGTH = 2097152;
public static final int MAXIMUM_UNCOMPRESSED_LENGTH = 8388608;
private final Inflater inflater;
+ private final com.velocitypowered.natives.compression.VelocityCompressor compressor; // Paper
private int threshold;
private boolean validateDecompressed;
+ // Paper start
public CompressionDecoder(int compressionThreshold, boolean bl) {
+ this(null, compressionThreshold, bl);
+ }
+ public CompressionDecoder(com.velocitypowered.natives.compression.VelocityCompressor compressor, int compressionThreshold, boolean bl) {
this.threshold = compressionThreshold;
this.validateDecompressed = bl;
- this.inflater = new Inflater();
+ this.inflater = compressor == null ? new Inflater() : null;
+ this.compressor = compressor;
+ // Paper end
}
@Override
@@ -0,0 +0,0 @@ public class CompressionDecoder extends ByteToMessageDecoder {
}
}
+ // Paper start
+ if (this.inflater != null) {
byte[] bs = new byte[friendlyByteBuf.readableBytes()];
friendlyByteBuf.readBytes(bs);
this.inflater.setInput(bs);
@@ -0,0 +0,0 @@ public class CompressionDecoder extends ByteToMessageDecoder {
this.inflater.inflate(cs);
list.add(Unpooled.wrappedBuffer(cs));
this.inflater.reset();
+ return;
+ }
+
+ int claimedUncompressedSize = i; // OBFHELPER
+ ByteBuf compatibleIn = com.velocitypowered.natives.util.MoreByteBufUtils.ensureCompatible(channelHandlerContext.alloc(), this.compressor, byteBuf);
+ ByteBuf uncompressed = com.velocitypowered.natives.util.MoreByteBufUtils.preferredBuffer(channelHandlerContext.alloc(), this.compressor, claimedUncompressedSize);
+ try {
+ this.compressor.inflate(compatibleIn, uncompressed, claimedUncompressedSize);
+ list.add(uncompressed);
+ byteBuf.clear();
+ } catch (Exception e) {
+ uncompressed.release();
+ throw e;
+ } finally {
+ compatibleIn.release();
+ }
+ // Paper end
}
}
}
+ // Paper start
+ @Override
+ public void handlerRemoved0(ChannelHandlerContext ctx) throws Exception {
+ if (this.compressor != null) {
+ this.compressor.close();
+ }
+ }
+ // Paper end
+
public void setThreshold(int compressionThreshold, boolean bl) {
this.threshold = compressionThreshold;
this.validateDecompressed = bl;
diff --git a/src/main/java/net/minecraft/network/CompressionEncoder.java b/src/main/java/net/minecraft/network/CompressionEncoder.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/network/CompressionEncoder.java
+++ b/src/main/java/net/minecraft/network/CompressionEncoder.java
@@ -0,0 +0,0 @@ import io.netty.handler.codec.MessageToByteEncoder;
import java.util.zip.Deflater;
public class CompressionEncoder extends MessageToByteEncoder<ByteBuf> {
- private final byte[] encodeBuf = new byte[8192];
+ private final byte[] encodeBuf; // Paper
private final Deflater deflater;
+ private final com.velocitypowered.natives.compression.VelocityCompressor compressor; // Paper
private int threshold;
+ // Paper start
public CompressionEncoder(int compressionThreshold) {
+ this(null, compressionThreshold);
+ }
+ public CompressionEncoder(com.velocitypowered.natives.compression.VelocityCompressor compressor, int compressionThreshold) {
this.threshold = compressionThreshold;
- this.deflater = new Deflater();
+ if (compressor == null) {
+ this.encodeBuf = new byte[8192];
+ this.deflater = new Deflater();
+ } else {
+ this.encodeBuf = null;
+ this.deflater = null;
+ }
+ this.compressor = compressor;
+ // Paper end
}
@Override
- protected void encode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, ByteBuf byteBuf2) {
+ protected void encode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, ByteBuf byteBuf2) throws Exception { // Paper
int i = byteBuf.readableBytes();
FriendlyByteBuf friendlyByteBuf = new FriendlyByteBuf(byteBuf2);
if (i < this.threshold) {
friendlyByteBuf.writeVarInt(0);
friendlyByteBuf.writeBytes(byteBuf);
} else {
+ // Paper start
+ if (this.deflater != null) {
byte[] bs = new byte[i];
byteBuf.readBytes(bs);
friendlyByteBuf.writeVarInt(bs.length);
@@ -0,0 +0,0 @@ public class CompressionEncoder extends MessageToByteEncoder<ByteBuf> {
}
this.deflater.reset();
+ return;
+ }
+
+ friendlyByteBuf.writeVarInt(i);
+ ByteBuf compatibleIn = com.velocitypowered.natives.util.MoreByteBufUtils.ensureCompatible(channelHandlerContext.alloc(), this.compressor, byteBuf);
+ try {
+ this.compressor.deflate(compatibleIn, byteBuf2);
+ } finally {
+ compatibleIn.release();
+ }
+ // Paper end
}
}
+ // Paper start
+ @Override
+ protected ByteBuf allocateBuffer(ChannelHandlerContext ctx, ByteBuf msg, boolean preferDirect) throws Exception{
+ if (this.compressor != null) {
+ // We allocate bytes to be compressed plus 1 byte. This covers two cases:
+ //
+ // - Compression
+ // According to https://github.com/ebiggers/libdeflate/blob/master/libdeflate.h#L103,
+ // if the data compresses well (and we do not have some pathological case) then the maximum
+ // size the compressed size will ever be is the input size minus one.
+ // - Uncompressed
+ // This is fairly obvious - we will then have one more than the uncompressed size.
+ int initialBufferSize = msg.readableBytes() + 1;
+ return com.velocitypowered.natives.util.MoreByteBufUtils.preferredBuffer(ctx.alloc(), this.compressor, initialBufferSize);
+ }
+
+ return super.allocateBuffer(ctx, msg, preferDirect);
+ }
+
+ @Override
+ public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
+ if (this.compressor != null) {
+ this.compressor.close();
+ }
+ }
+ // Paper end
+
public int getThreshold() {
return this.threshold;
}
diff --git a/src/main/java/net/minecraft/network/Connection.java b/src/main/java/net/minecraft/network/Connection.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/network/Connection.java
+++ b/src/main/java/net/minecraft/network/Connection.java
@@ -0,0 +0,0 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
return networkmanager;
}
- public void setEncryptionKey(Cipher decryptionCipher, Cipher encryptionCipher) {
- this.encrypted = true;
- this.channel.pipeline().addBefore("splitter", "decrypt", new CipherDecoder(decryptionCipher));
- this.channel.pipeline().addBefore("prepender", "encrypt", new CipherEncoder(encryptionCipher));
+ // Paper start
+// public void setEncryptionKey(Cipher decryptionCipher, Cipher encryptionCipher) {
+// this.encrypted = true;
+// this.channel.pipeline().addBefore("splitter", "decrypt", new CipherDecoder(decryptionCipher));
+// this.channel.pipeline().addBefore("prepender", "encrypt", new CipherEncoder(encryptionCipher));
+// }
+
+ public void setupEncryption(javax.crypto.SecretKey key) throws net.minecraft.util.CryptException {
+ if (!this.encrypted) {
+ try {
+ com.velocitypowered.natives.encryption.VelocityCipher decryption = com.velocitypowered.natives.util.Natives.cipher.get().forDecryption(key);
+ com.velocitypowered.natives.encryption.VelocityCipher encryption = com.velocitypowered.natives.util.Natives.cipher.get().forEncryption(key);
+
+ this.encrypted = true;
+ this.channel.pipeline().addBefore("splitter", "decrypt", new CipherDecoder(decryption));
+ this.channel.pipeline().addBefore("prepender", "encrypt", new CipherEncoder(encryption));
+ } catch (java.security.GeneralSecurityException e) {
+ throw new net.minecraft.util.CryptException(e);
+ }
+ }
}
+ // Paper end
public boolean isEncrypted() {
return this.encrypted;
@@ -0,0 +0,0 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
public void setupCompression(int compressionThreshold, boolean flag) {
if (compressionThreshold >= 0) {
+ com.velocitypowered.natives.compression.VelocityCompressor compressor = com.velocitypowered.natives.util.Natives.compress.get().create(-1); // Paper
if (this.channel.pipeline().get("decompress") instanceof CompressionDecoder) {
((CompressionDecoder) this.channel.pipeline().get("decompress")).setThreshold(compressionThreshold, flag);
} else {
- this.channel.pipeline().addBefore("decoder", "decompress", new CompressionDecoder(compressionThreshold, flag));
+ this.channel.pipeline().addBefore("decoder", "decompress", new CompressionDecoder(compressor, compressionThreshold, flag)); // Paper
}
if (this.channel.pipeline().get("compress") instanceof CompressionEncoder) {
((CompressionEncoder) this.channel.pipeline().get("compress")).setThreshold(compressionThreshold);
} else {
- this.channel.pipeline().addBefore("encoder", "compress", new CompressionEncoder(compressionThreshold));
+ this.channel.pipeline().addBefore("encoder", "compress", new CompressionEncoder(compressor, compressionThreshold)); // Paper
}
} else {
if (this.channel.pipeline().get("decompress") instanceof CompressionDecoder) {
diff --git a/src/main/java/net/minecraft/server/network/ServerConnectionListener.java b/src/main/java/net/minecraft/server/network/ServerConnectionListener.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/server/network/ServerConnectionListener.java
+++ b/src/main/java/net/minecraft/server/network/ServerConnectionListener.java
@@ -0,0 +0,0 @@ public class ServerConnectionListener {
ServerConnectionListener.LOGGER.info("Using default channel type");
}
+ // Paper start - indicate Velocity natives in use
+ ServerConnectionListener.LOGGER.info("Paper: Using " + com.velocitypowered.natives.util.Natives.compress.getLoadedVariant() + " compression from Velocity.");
+ ServerConnectionListener.LOGGER.info("Paper: Using " + com.velocitypowered.natives.util.Natives.cipher.getLoadedVariant() + " cipher from Velocity.");
+ // Paper end
+
this.channels.add(((ServerBootstrap) ((ServerBootstrap) (new ServerBootstrap()).channel(oclass)).childHandler(new ChannelInitializer<Channel>() {
protected void initChannel(Channel channel) {
try {
diff --git a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java
+++ b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java
@@ -0,0 +0,0 @@ public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener
}
SecretKey secretkey = packet.getSecretKey(privatekey);
- Cipher cipher = Crypt.getCipher(2, secretkey);
- Cipher cipher1 = Crypt.getCipher(1, secretkey);
+ // Paper start
+// Cipher cipher = Crypt.getCipher(2, secretkey);
+// Cipher cipher1 = Crypt.getCipher(1, secretkey);
+ // Paper end
s = (new BigInteger(Crypt.digestData("", this.server.getKeyPair().getPublic(), secretkey))).toString(16);
this.state = ServerLoginPacketListenerImpl.State.AUTHENTICATING;
- this.connection.setEncryptionKey(cipher, cipher1);
+ this.connection.setupEncryption(secretkey); // Paper
} catch (CryptException cryptographyexception) {
throw new IllegalStateException("Protocol error", cryptographyexception);
}

View File

@ -30,6 +30,9 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ static final org.spigotmc.TrackingRange.TrackingRangeType[] TRACKING_RANGE_TYPES = org.spigotmc.TrackingRange.TrackingRangeType.values(); + static final org.spigotmc.TrackingRange.TrackingRangeType[] TRACKING_RANGE_TYPES = org.spigotmc.TrackingRange.TrackingRangeType.values();
+ public final com.destroystokyo.paper.util.misc.PlayerAreaMap[] playerEntityTrackerTrackMaps; + public final com.destroystokyo.paper.util.misc.PlayerAreaMap[] playerEntityTrackerTrackMaps;
+ final int[] entityTrackerTrackRanges; + final int[] entityTrackerTrackRanges;
+ public final int getEntityTrackerRange(final int ordinal) {
+ return this.entityTrackerTrackRanges[ordinal];
+ }
+ +
+ private int convertSpigotRangeToVanilla(final int vanilla) { + private int convertSpigotRangeToVanilla(final int vanilla) {
+ return MinecraftServer.getServer().getScaledTrackingDistance(vanilla); + return MinecraftServer.getServer().getScaledTrackingDistance(vanilla);
@ -80,8 +83,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
int effectiveTickViewDistance = this.getEffectiveViewDistance(); int effectiveTickViewDistance = this.getEffectiveViewDistance();
int effectiveNoTickViewDistance = Math.max(this.getEffectiveNoTickViewDistance(), effectiveTickViewDistance); int effectiveNoTickViewDistance = Math.max(this.getEffectiveNoTickViewDistance(), effectiveTickViewDistance);
@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider @@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
this.poiManager = new PoiManager(new File(file, "poi"), dataFixer, dsync, world); });
this.setViewDistance(viewDistance); // Paper end - no-tick view distance
this.playerMobDistanceMap = this.level.paperConfig.perPlayerMobSpawns ? new com.destroystokyo.paper.util.PlayerMobDistanceMap() : null; // Paper this.playerMobDistanceMap = this.level.paperConfig.perPlayerMobSpawns ? new com.destroystokyo.paper.util.PlayerMobDistanceMap() : null; // Paper
+ // Paper start - use distance map to optimise entity tracker + // Paper start - use distance map to optimise entity tracker
+ this.playerEntityTrackerTrackMaps = new com.destroystokyo.paper.util.misc.PlayerAreaMap[TRACKING_RANGE_TYPES.length]; + this.playerEntityTrackerTrackMaps = new com.destroystokyo.paper.util.misc.PlayerAreaMap[TRACKING_RANGE_TYPES.length];
@ -122,9 +125,9 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ this.playerEntityTrackerTrackMaps[ordinal] = new com.destroystokyo.paper.util.misc.PlayerAreaMap(this.pooledLinkedPlayerHashSets); + this.playerEntityTrackerTrackMaps[ordinal] = new com.destroystokyo.paper.util.misc.PlayerAreaMap(this.pooledLinkedPlayerHashSets);
+ } + }
+ // Paper end - use distance map to optimise entity tracker + // Paper end - use distance map to optimise entity tracker
// Paper start - no-tick view distance }
this.setNoTickViewDistance(this.level.paperConfig.noTickViewDistance);
this.playerViewDistanceTickMap = new com.destroystokyo.paper.util.misc.PlayerAreaMap(this.pooledLinkedPlayerHashSets, // Paper start
@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider @@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
} }
@ -278,15 +281,6 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
public boolean equals(Object object) { public boolean equals(Object object) {
return object instanceof ChunkMap.TrackedEntity ? ((ChunkMap.TrackedEntity) object).entity.getId() == this.entity.getId() : false; return object instanceof ChunkMap.TrackedEntity ? ((ChunkMap.TrackedEntity) object).entity.getId() == this.entity.getId() : false;
} }
@@ -0,0 +0,0 @@ Sections go from 0..16. Now whenever a section is not empty, it can potentially
int j = entity.getType().clientTrackingRange() * 16;
j = org.spigotmc.TrackingRange.getEntityTrackingRange(entity, j); // Paper
- if (j > i) {
+ if (j < i) { // Paper - we need the lowest range thanks to the fact that our tracker doesn't account for passenger logic
i = j;
}
}
diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/world/entity/Entity.java --- a/src/main/java/net/minecraft/world/entity/Entity.java
@ -301,7 +295,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
import net.minecraft.server.level.ServerPlayer; import net.minecraft.server.level.ServerPlayer;
@@ -0,0 +0,0 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, n @@ -0,0 +0,0 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, n
} }
// CraftBukkit end // Paper end
+ // Paper start - optimise entity tracking + // Paper start - optimise entity tracking
+ final org.spigotmc.TrackingRange.TrackingRangeType trackingRangeType = org.spigotmc.TrackingRange.getTrackingRangeType(this); + final org.spigotmc.TrackingRange.TrackingRangeType trackingRangeType = org.spigotmc.TrackingRange.getTrackingRangeType(this);
@ -313,9 +307,27 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ } + }
+ +
+ public final com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<ServerPlayer> getPlayersInTrackRange() { + public final com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<ServerPlayer> getPlayersInTrackRange() {
+ // determine highest range of passengers
+ if (this.passengers.isEmpty()) {
+ return ((ServerLevel)this.level).getChunkSource().chunkMap.playerEntityTrackerTrackMaps[this.trackingRangeType.ordinal()] + return ((ServerLevel)this.level).getChunkSource().chunkMap.playerEntityTrackerTrackMaps[this.trackingRangeType.ordinal()]
+ .getObjectsInRange(MCUtil.getCoordinateKey(this)); + .getObjectsInRange(MCUtil.getCoordinateKey(this));
+ } + }
+ Iterable<Entity> passengers = this.getIndirectPassengers();
+ net.minecraft.server.level.ChunkMap chunkMap = ((ServerLevel)this.level).getChunkSource().chunkMap;
+ org.spigotmc.TrackingRange.TrackingRangeType type = this.trackingRangeType;
+ int range = chunkMap.getEntityTrackerRange(type.ordinal());
+
+ for (Entity passenger : passengers) {
+ org.spigotmc.TrackingRange.TrackingRangeType passengerType = passenger.trackingRangeType;
+ int passengerRange = chunkMap.getEntityTrackerRange(passengerType.ordinal());
+ if (passengerRange > range) {
+ type = passengerType;
+ range = passengerRange;
+ }
+ }
+
+ return chunkMap.playerEntityTrackerTrackMaps[type.ordinal()].getObjectsInRange(MCUtil.getCoordinateKey(this));
+ }
+ // Paper end - optimise entity tracking + // Paper end - optimise entity tracking
+ +
public Entity(EntityType<?> type, Level world) { public Entity(EntityType<?> type, Level world) {

View File

@ -19,7 +19,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ return this.chunkSource.getChunkAtIfLoadedImmediately(x, z); // Paper + return this.chunkSource.getChunkAtIfLoadedImmediately(x, z); // Paper
} }
// Paper start - Asynchronous IO // Paper start
diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java 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 index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java --- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java

View File

@ -0,0 +1,30 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Spottedleaf <Spottedleaf@users.noreply.github.com>
Date: Thu, 11 Mar 2021 21:17:02 -0800
Subject: [PATCH] Use hash table for maintaing changed block set
When a lot of block changes occur the iteration for checking can
add up a bit and cause a small performance impact.
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 @@ import net.minecraft.world.level.lighting.LevelLightEngine;
import net.minecraft.server.MinecraftServer;
// CraftBukkit end
+import it.unimi.dsi.fastutil.shorts.ShortOpenHashSet; // Paper
+
public class ChunkHolder {
public static final Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure> UNLOADED_CHUNK = Either.right(ChunkHolder.ChunkLoadingFailure.UNLOADED);
@@ -0,0 +0,0 @@ public class ChunkHolder {
if (i < 0 || i >= this.changedBlocksPerSection.length) return; // CraftBukkit - SPIGOT-6086, SPIGOT-6296
if (this.changedBlocksPerSection[i] == null) {
this.hasChangedSections = true;
- this.changedBlocksPerSection[i] = new ShortArraySet();
+ this.changedBlocksPerSection[i] = new ShortOpenHashSet(); // Paper - use a set to make setting constant-time
}
this.changedBlocksPerSection[i].add(SectionPos.sectionRelativePos(pos));

View File

@ -35,7 +35,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ +
+ public boolean perPlayerMobSpawns = false; + public boolean perPlayerMobSpawns = false;
+ private void perPlayerMobSpawns() { + private void perPlayerMobSpawns() {
+ perPlayerMobSpawns = getBoolean("per-player-mob-spawns", false); + if (PaperConfig.version < 22) {
+ set("per-player-mob-spawns", Boolean.TRUE);
+ }
+ perPlayerMobSpawns = getBoolean("per-player-mob-spawns", true);
+ } + }
} }
@ -557,15 +560,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
// CraftBukkit start - recursion-safe executor for Chunk loadCallback() and unloadCallback() // CraftBukkit start - recursion-safe executor for Chunk loadCallback() and unloadCallback()
public final CallbackExecutor callbackExecutor = new CallbackExecutor(); public final CallbackExecutor callbackExecutor = new CallbackExecutor();
@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider @@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
this.overworldDataStorage = persistentStateManagerFactory; ChunkMap.this.updateChunkTracking(player, new ChunkPos(rangeX, rangeZ), null, true, false); // unloaded, loaded
this.poiManager = new PoiManager(new File(file, "poi"), dataFixer, dsync, world); });
this.setViewDistance(viewDistance);
+ this.playerMobDistanceMap = this.level.paperConfig.perPlayerMobSpawns ? new com.destroystokyo.paper.util.PlayerMobDistanceMap() : null; // Paper
// Paper start - no-tick view distance
this.setNoTickViewDistance(this.level.paperConfig.noTickViewDistance);
this.playerViewDistanceTickMap = new com.destroystokyo.paper.util.misc.PlayerAreaMap(this.pooledLinkedPlayerHashSets,
@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
// Paper end - no-tick view distance // Paper end - no-tick view distance
+ this.playerMobDistanceMap = this.level.paperConfig.perPlayerMobSpawns ? new com.destroystokyo.paper.util.PlayerMobDistanceMap() : null; // Paper
} }
+ // Paper start + // Paper start

View File

@ -213,8 +213,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ // Paper end + // Paper end
+ +
protected void saveAllChunks(boolean flush) { protected void saveAllChunks(boolean flush) {
Long2ObjectLinkedOpenHashMap<ChunkHolder> visibleChunks = this.getVisibleChunks(); // Paper remove clone of visible Chunks unless saving off main thread (watchdog kill)
if (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 @@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
asyncSaveData, chunk); asyncSaveData, chunk);