even even even even even even even even even even even even even even even even even even even even even even even even even even even even even even even even even even even even even even even even even even even even even even even even even even even even even even even even even even even even even even even even even even even even even even more patches

This commit is contained in:
Jason Penilla 2021-11-24 22:31:14 -08:00 committed by MiniDigger | Martin
parent dc58f85df2
commit f04f3321e3
30 changed files with 221 additions and 181 deletions

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..57d0cd3ad6f972e986c72a57f1a6e36003f190c2
--- /dev/null
+++ b/src/main/java/io/papermc/paper/util/table/ZeroCollidingReferenceStateTable.java
@@ -0,0 +1,160 @@
+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 5c30f43ba7db43cc2613ddaf6ea0d0810d3d08d7..5be5eabc222b9e20c083ff83fae52010b19ea854 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
@@ -40,11 +40,13 @@ 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) {
@@ -85,11 +87,11 @@ 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 {
@@ -98,24 +100,18 @@ 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) {
@@ -134,7 +130,7 @@ 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 ff1a0d125edd2ea10c870cbb62ae9aa23644b6dc..233215280f8494dbc33a2fd0b14e37e59f1cb643 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
@@ -7,6 +7,13 @@ 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 0bca0f971dac994bd8b6ecd87e8b33e26c0f18f9..edd3c745efb40ee79a1393199c7a27ddaa2f8026 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
@@ -15,6 +15,15 @@ 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);
@@ -28,6 +37,14 @@ 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 72f508321ebffcca31240fbdd068b4d185454cbc..d16156f8a4a2507e114dc651fd0af9cdffb3c8e0 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
@@ -13,6 +13,16 @@ 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 1d0f0099571e7295f5f83004c45b6e992a4af83a..bf4a39671425b56e7096008a8e43094eebbb2d05 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
@@ -22,6 +22,17 @@ 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 a6b438543a12f5ecf05fb631ef53b18d4d253dff..c2642f798c49f79d34e599517d64d73b6e7676c6 100644
--- a/src/main/java/net/minecraft/network/Connection.java
+++ b/src/main/java/net/minecraft/network/Connection.java
@@ -452,7 +452,14 @@ 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 bcf53ec07b8eeec7a88fb67e6fb908362e6f51b0..acc12307f61e1e055896b68fe16654c9c4a606a0 100644
--- a/src/main/java/net/minecraft/network/protocol/PacketUtils.java
+++ b/src/main/java/net/minecraft/network/protocol/PacketUtils.java
@@ -20,6 +20,24 @@ 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 {
@@ -30,6 +48,8 @@ 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
@@ -53,6 +73,12 @@ 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 5d4f20a31ad99b4e808bb9a7aaa2153666af493f..928ac2d5b93b93aa7494374f4f3446557ebebd6d 100644
--- a/src/main/java/net/minecraft/server/level/ServerLevel.java
+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
@@ -973,7 +973,26 @@ 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
@@ -1013,7 +1032,13 @@ 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 db6c11694e6316c50a3f0a138e09542fdae45718..62fe626aa198d19ccfb48f47b8f771b6037cff9c 100644
--- a/src/main/java/net/minecraft/world/entity/Entity.java
+++ b/src/main/java/net/minecraft/world/entity/Entity.java
@@ -860,7 +860,42 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, i
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 {
@@ -1022,6 +1057,13 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, i
this.level.getProfiler().pop();
}
}
+ // Paper start - detailed watchdog information
+ } finally {
+ synchronized (this.posLock) { // Paper
+ this.moveVector = null;
+ } // Paper
+ }
+ // Paper end - detailed watchdog information
}
protected boolean isHorizontalCollisionMinor(Vec3 adjustedMovement) {
@@ -3798,7 +3840,9 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, i
}
public void setDeltaMovement(Vec3 velocity) {
+ synchronized (this.posLock) { // Paper
this.deltaMovement = velocity;
+ } // Paper
}
public void setDeltaMovement(double x, double y, double z) {
@@ -3874,7 +3918,9 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, i
}
// 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 dcfbe77bdb25d9c58ffb7b75c48bdb580bc0de47..bee38307494188800886a1622fed229b88dbd8f1 100644
--- a/src/main/java/org/spigotmc/WatchdogThread.java
+++ b/src/main/java/org/spigotmc/WatchdogThread.java
@@ -23,6 +23,78 @@ 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" );
@@ -121,6 +193,7 @@ 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,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 0dcf75c5c792650d7a5b9354222df16bcd1cfbd2..14610e6144ec144ebbec6fb0945c67bb0ea86795 100644
--- a/src/main/java/net/minecraft/core/BlockPos.java
+++ b/src/main/java/net/minecraft/core/BlockPos.java
@@ -484,9 +484,9 @@ 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;
}
@@ -550,19 +550,19 @@ 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 6de2ea6641433206027015695a0d10c80fe0e2f5..b9d30deecb63efd4cd8c840b710edba5b956bd93 100644
--- a/src/main/java/net/minecraft/core/Vec3i.java
+++ b/src/main/java/net/minecraft/core/Vec3i.java
@@ -19,9 +19,9 @@ 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
private static Function<Vec3i, DataResult<Vec3i>> checkOffsetAxes(int maxAbsValue) {
return (vec) -> {

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 eada966d7f108a6081be7a848f5c1dfcb1eed676..a977f7483f37df473096b2234dc1308bbaa6a8b6 100644
--- a/src/main/java/co/aikar/timings/MinecraftTimings.java
+++ b/src/main/java/co/aikar/timings/MinecraftTimings.java
@@ -44,6 +44,7 @@ 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 7908e35a5842beb422edfed624561caffe08e6ef..8cad7941263bb1c48884c4fd883200eb2634db5d 100644
--- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java
+++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java
@@ -788,6 +788,7 @@ public class ServerChunkCache extends ChunkSource {
public boolean runDistanceManagerUpdates() {
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();
@@ -797,6 +798,7 @@ 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

@ -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 2f3e2a404f55f09ae4db8261e495275e31228034..6d66f83afbeb650b10669fd7eeb24a315951fa86 100644
--- a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftAsyncTask.java
+++ b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftAsyncTask.java
@@ -25,7 +25,10 @@ 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!
@@ -92,6 +95,7 @@ class CraftAsyncTask extends CraftTask {
}
}
}
+ } finally { thread.setName(nameBefore); } // Paper - name worker thread according
}
LinkedList<BukkitWorker> getWorkers() {

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 9cc34ff8ec5db5a87faf0afb574543f01c8381c2..367c932ae6abfbc73a42afaa143cefcedea52ffb 100644
--- a/src/main/java/net/minecraft/world/level/Level.java
+++ b/src/main/java/net/minecraft/world/level/Level.java
@@ -395,6 +395,15 @@ 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,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 ce2cc249439d525d2089302f3febde758aa96929..e311e1dd8b0e07b8787ec42a58c4e6559ac661ed 100644
--- a/src/main/java/com/destroystokyo/paper/PaperConfig.java
+++ b/src/main/java/com/destroystokyo/paper/PaperConfig.java
@@ -507,4 +507,102 @@ public class PaperConfig {
itemValidationBookAuthorLength = getInt("settings.item-validation.book.author", itemValidationBookAuthorLength);
itemValidationBookPageLength = getInt("settings.item-validation.book.page", itemValidationBookPageLength);
}
+
+ 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 c2642f798c49f79d34e599517d64d73b6e7676c6..241b086bd096a4bc2175835b2505deda1c143f09 100644
--- a/src/main/java/net/minecraft/network/Connection.java
+++ b/src/main/java/net/minecraft/network/Connection.java
@@ -126,6 +126,22 @@ 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;
@@ -206,6 +222,45 @@ 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

@ -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 e311e1dd8b0e07b8787ec42a58c4e6559ac661ed..dbf8f1b605b8abbfd668e05a3e6fc004784f934a 100644
--- a/src/main/java/com/destroystokyo/paper/PaperConfig.java
+++ b/src/main/java/com/destroystokyo/paper/PaperConfig.java
@@ -605,4 +605,10 @@ 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 3ab74a721d9d9bec82a68a30bf523292a3038587..17a59fba538f8a7bdd96964c6acda94b33655d27 100644
--- a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java
+++ b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java
@@ -54,14 +54,28 @@ 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;
@@ -128,7 +142,7 @@ 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;
@@ -148,7 +162,7 @@ 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
}
}
@@ -156,6 +170,12 @@ 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);
@@ -224,7 +244,7 @@ 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);
@@ -277,12 +297,12 @@ 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()) {
@@ -299,12 +319,18 @@ 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)) {
@@ -316,7 +342,7 @@ 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
}
}
@@ -324,7 +350,13 @@ 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

@ -0,0 +1,27 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Jason Penilla <11360596+jpenilla@users.noreply.github.com>
Date: Sat, 16 Oct 2021 17:38:35 -0700
Subject: [PATCH] Use correct LevelStem registry when loading default
end/nether
diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java
index 86b57776a42261053237d62f3b666793457c5e2f..58fa998b07d08c8ca3fd1e3f6fb7ee94de359cd6 100644
--- a/src/main/java/net/minecraft/server/MinecraftServer.java
+++ b/src/main/java/net/minecraft/server/MinecraftServer.java
@@ -634,7 +634,14 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
long i = generatorsettings.seed();
long j = BiomeManager.obfuscateSeed(i);
List<CustomSpawner> list = ImmutableList.of(new PhantomSpawner(), new PatrolSpawner(), new CatSpawner(), new VillageSiege(), new WanderingTraderSpawner(iworlddataserver));
- LevelStem worlddimension = (LevelStem) registrymaterials.get(dimensionKey);
+ // Paper start - Use correct LevelStem registry
+ final LevelStem worlddimension;
+ if (dimensionKey == LevelStem.END || dimensionKey == LevelStem.NETHER) {
+ worlddimension = generatorsettings.dimensions().get(dimensionKey);
+ } else {
+ worlddimension = registrymaterials.get(dimensionKey);
+ }
+ // Paper end
DimensionType dimensionmanager;
ChunkGenerator chunkgenerator;

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 3c1724a86cccd3d66459f6c21ed358b47d2d0eac..e490116a75c5a07e501339ca5e299fb425af84f4 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
@@ -44,6 +44,7 @@ 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,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 8cad7941263bb1c48884c4fd883200eb2634db5d..7c8198009de30229dcce73ae5c0ca60ba7224cef 100644
--- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java
+++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java
@@ -975,7 +975,24 @@ public class ServerChunkCache extends ChunkSource {
});
gameprofilerfiller.pop();
gameprofilerfiller.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
}
}

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 3bf658ad473ebfcb4f4ca6efff2ee068167b1a62..7ad35b3586ae670602cc63e67d2f01fc5d10b638 100644
--- a/src/main/java/net/minecraft/world/level/BlockGetter.java
+++ b/src/main/java/net/minecraft/world/level/BlockGetter.java
@@ -84,7 +84,7 @@ 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,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 a977f7483f37df473096b2234dc1308bbaa6a8b6..bb5f079a10b15d7b9f2ab5a8af67c559ffa5b371 100644
--- a/src/main/java/co/aikar/timings/MinecraftTimings.java
+++ b/src/main/java/co/aikar/timings/MinecraftTimings.java
@@ -45,6 +45,7 @@ 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 8ccfe9488db44d7d2cf4040a5b4cead33da1d5f4..1a3b1eb7b70b9a668aa33ea943c13890eaa23a05 100644
--- a/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScoreboardManager.java
+++ b/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScoreboardManager.java
@@ -113,9 +113,18 @@ 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

@ -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 dbf8f1b605b8abbfd668e05a3e6fc004784f934a..95b52e9f6d2b87c525b9eac88fae2e73b08bca82 100644
--- a/src/main/java/com/destroystokyo/paper/PaperConfig.java
+++ b/src/main/java/com/destroystokyo/paper/PaperConfig.java
@@ -611,4 +611,10 @@ 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 94704a258ce7183aeb0ccec0b9106e40efd08821..703ac671b19636859648f16a5431b2700791e7d5 100644
--- a/src/main/java/net/minecraft/server/level/ServerEntity.java
+++ b/src/main/java/net/minecraft/server/level/ServerEntity.java
@@ -172,7 +172,7 @@ 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,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 7ad35b3586ae670602cc63e67d2f01fc5d10b638..ae8800a37ecd117126d976bcce1b91d56e7587b8 100644
--- a/src/main/java/net/minecraft/world/level/BlockGetter.java
+++ b/src/main/java/net/minecraft/world/level/BlockGetter.java
@@ -84,6 +84,7 @@ 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,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 b244713d4a5bc6eb3d9536a56fdc9d688ca0d756..925da0baa59f742dbe727c6323cc90b65159f314 100644
--- a/src/main/java/net/minecraft/server/level/ChunkMap.java
+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java
@@ -105,6 +105,7 @@ import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.bukkit.entity.Player;
// CraftBukkit end
+import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet; // Paper
public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider {
@@ -1828,7 +1829,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
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 afd4dc6f69389f43c1a95069840e01a33ac86b63..5d95f3cce8f5190bc2172a1fe0e83166062f0f3d 100644
--- a/src/main/java/net/minecraft/world/level/block/FarmBlock.java
+++ b/src/main/java/net/minecraft/world/level/block/FarmBlock.java
@@ -139,19 +139,28 @@ 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

@ -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 b27c8db914cca3ff0ea8a24acddb9cb9870ce21d..4814e719e0b898464692075170889fdb2729a26a 100644
--- a/src/main/java/net/minecraft/world/level/entity/EntityTickList.java
+++ b/src/main/java/net/minecraft/world/level/entity/EntityTickList.java
@@ -9,57 +9,42 @@ 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,408 @@
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..e8b4053babe46999980b92643125405064af1c04
--- /dev/null
+++ b/src/main/java/io/papermc/paper/util/math/ThreadUnsafeRandom.java
@@ -0,0 +1,46 @@
+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 928ac2d5b93b93aa7494374f4f3446557ebebd6d..5935c442a6534ad51d191a72bc8d2043fa25e2ac 100644
--- a/src/main/java/net/minecraft/server/level/ServerLevel.java
+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
@@ -639,6 +639,10 @@ 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();
@@ -648,10 +652,10 @@ 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.spigotConfig.thunderChance > 0 && this.random.nextInt(this.spigotConfig.thunderChance) == 0) { // Spigot // 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
@@ -674,65 +678,78 @@ 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(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.coldEnoughToSnow(blockposition1)) {
+ blockposition.setY(downY); // Paper
+ if (biomebase_precipitation == Biome.Precipitation.RAIN && biomebase.coldEnoughToSnow(blockposition)) { // Paper
biomebase_precipitation = Biome.Precipitation.SNOW;
}
- iblockdata.getBlock().handlePrecipitation(iblockdata, this, blockposition1, biomebase_precipitation);
+ iblockdata.getBlock().handlePrecipitation(iblockdata, 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.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 62251727788d48a461ea6f7945771d7d6bdc7282..106610ccc74b70b557b01c61262d56c4f1147acf 100644
--- a/src/main/java/net/minecraft/util/BitStorage.java
+++ b/src/main/java/net/minecraft/util/BitStorage.java
@@ -20,4 +20,15 @@ public interface BitStorage {
void unpack(int[] is);
BitStorage copy();
+
+ // Paper start
+ void forEach(DataBitConsumer consumer);
+
+ @FunctionalInterface
+ interface DataBitConsumer {
+
+ void accept(int location, int data);
+
+ }
+ // Paper end
}
diff --git a/src/main/java/net/minecraft/util/SimpleBitStorage.java b/src/main/java/net/minecraft/util/SimpleBitStorage.java
index 6426d6c2c31ead49444fe56e2230266290fa79dd..881a2318aac72526e0451688af58c620e4f525d1 100644
--- a/src/main/java/net/minecraft/util/SimpleBitStorage.java
+++ b/src/main/java/net/minecraft/util/SimpleBitStorage.java
@@ -124,6 +124,28 @@ public class SimpleBitStorage implements BitStorage {
return this.bits;
}
+ // Paper start
+ @Override
+ 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;
+ }
+ }
+ }
+ }
+ // Paper end
+
@Override
public void getAll(IntConsumer action) {
int i = 0;
diff --git a/src/main/java/net/minecraft/util/ZeroBitStorage.java b/src/main/java/net/minecraft/util/ZeroBitStorage.java
index 172e7a0761724cc802387e613258830a0defb04a..55c486349ddb295d4d4c6fa9214cdae90f851948 100644
--- a/src/main/java/net/minecraft/util/ZeroBitStorage.java
+++ b/src/main/java/net/minecraft/util/ZeroBitStorage.java
@@ -46,6 +46,15 @@ public class ZeroBitStorage implements BitStorage {
return 0;
}
+ // Paper start
+ @Override
+ public void forEach(DataBitConsumer consumer) {
+ for(int i = 0; i < this.size; ++i) {
+ consumer.accept(i, 0);
+ }
+ }
+ // Paper end
+
@Override
public void getAll(IntConsumer action) {
for(int i = 0; i < this.size; ++i) {
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 60e1111f3c2c43398f21c541248f38524f41f4fb..56e9c0d15249562ebea8eb451d4bcc9ff5e7d594 100644
--- a/src/main/java/net/minecraft/world/entity/animal/Turtle.java
+++ b/src/main/java/net/minecraft/world/entity/animal/Turtle.java
@@ -87,7 +87,7 @@ 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() {
diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java
index 367c932ae6abfbc73a42afaa143cefcedea52ffb..6be462975523dae9ff436ba1643b2042ec66e554 100644
--- a/src/main/java/net/minecraft/world/level/Level.java
+++ b/src/main/java/net/minecraft/world/level/Level.java
@@ -1352,10 +1352,18 @@ 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 f9e871cff9318e6fea92d5b7a7035b3bfa259bca..c579be24944b310151738d375c53e2d52b32edb7 100644
--- a/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java
+++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java
@@ -25,6 +25,7 @@ public class LevelChunkSection {
private short tickingFluidCount;
public final PalettedContainer<BlockState> states;
private final PalettedContainer<Biome> biomes;
+ public final com.destroystokyo.paper.util.maplist.IBlockDataList tickingList = new com.destroystokyo.paper.util.maplist.IBlockDataList(); // Paper
public LevelChunkSection(int chunkPos, PalettedContainer<BlockState> blockStateContainer, PalettedContainer<Biome> biomeContainer) {
this.bottomBlockY = LevelChunkSection.getBottomBlockY(chunkPos);
@@ -79,6 +80,9 @@ public class LevelChunkSection {
--this.nonEmptyBlockCount;
if (iblockdata1.isRandomlyTicking()) {
--this.tickingBlockCount;
+ // Paper start
+ this.tickingList.remove(x, y, z);
+ // Paper end
}
}
@@ -90,6 +94,9 @@ public class LevelChunkSection {
++this.nonEmptyBlockCount;
if (state.isRandomlyTicking()) {
++this.tickingBlockCount;
+ // Paper start
+ this.tickingList.add(x, y, z, state);
+ // Paper end
}
}
@@ -121,23 +128,29 @@ 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((iblockdata, i) -> {
+ this.states.forEachLocation((iblockdata, i) -> { // Paper
FluidState fluid = iblockdata.getFluidState();
if (!iblockdata.isAir()) {
- this.nonEmptyBlockCount = (short) (this.nonEmptyBlockCount + i);
+ this.nonEmptyBlockCount = (short) (this.nonEmptyBlockCount + 1); // Paper
if (iblockdata.isRandomlyTicking()) {
- this.tickingBlockCount = (short) (this.tickingBlockCount + i);
+ // Paper start
+ this.tickingBlockCount = (short)(this.tickingBlockCount + 1);
+ this.tickingList.add(i, iblockdata);
+ // Paper end
}
}
if (!fluid.isEmpty()) {
- this.nonEmptyBlockCount = (short) (this.nonEmptyBlockCount + i);
+ this.nonEmptyBlockCount = (short) (this.nonEmptyBlockCount + 1); // Paper
if (fluid.isRandomlyTicking()) {
- this.tickingFluidCount = (short) (this.tickingFluidCount + i);
+ 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 bd003e980ac0fe8c4e4b5df5b4002ae3ada39862..283622b3657e0a5c0b1da9c4b234924d64c867bb 100644
--- a/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java
+++ b/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java
@@ -283,6 +283,14 @@ public class PalettedContainer<T> implements PaletteResize<T> {
}
}
+ // Paper start
+ public void forEachLocation(PalettedContainer.CountConsumer<T> consumer) {
+ this.data.storage.forEach((int location, int data) -> {
+ consumer.accept(this.data.palette.valueFor(data), location);
+ });
+ }
+ // Paper end
+
@FunctionalInterface
public interface CountConsumer<T> {
void accept(T object, int count);