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:
parent
dc58f85df2
commit
f04f3321e3
30 changed files with 221 additions and 181 deletions
|
@ -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;
|
|
@ -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, "------------------------------" );
|
||||
//
|
|
@ -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) -> {
|
40
patches/server/0749-Distance-manager-tick-timings.patch
Normal file
40
patches/server/0749-Distance-manager-tick-timings.patch
Normal 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
|
|
@ -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() {
|
|
@ -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
|
||||
}
|
||||
|
205
patches/server/0752-Add-packet-limiter-config.patch
Normal file
205
patches/server/0752-Add-packet-limiter-config.patch
Normal 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) {
|
155
patches/server/0753-Lag-compensate-block-breaking.patch
Normal file
155
patches/server/0753-Lag-compensate-block-breaking.patch
Normal 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
|
||||
}
|
|
@ -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;
|
||||
|
|
@ -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
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
43
patches/server/0758-Time-scoreboard-search.patch
Normal file
43
patches/server/0758-Time-scoreboard-search.patch
Normal 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
|
||||
}
|
||||
}
|
|
@ -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());
|
22
patches/server/0760-Do-not-run-raytrace-logic-for-AIR.patch
Normal file
22
patches/server/0760-Do-not-run-raytrace-logic-for-AIR.patch
Normal 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();
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||
}
|
||||
}
|
408
patches/server/0764-Optimise-random-block-ticking.patch
Normal file
408
patches/server/0764-Optimise-random-block-ticking.patch
Normal 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);
|
Loading…
Add table
Add a link
Reference in a new issue