diff --git a/patches/api/0027-Use-ASM-for-event-executors.patch b/patches/api/0027-Use-ASM-for-event-executors.patch new file mode 100644 index 00000000..f56ae245 --- /dev/null +++ b/patches/api/0027-Use-ASM-for-event-executors.patch @@ -0,0 +1,397 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: mechoriet +Date: Sun, 23 Apr 2023 12:54:53 +0200 +Subject: [PATCH] Use ASM for event executors. + + +diff --git a/build.gradle.kts b/build.gradle.kts +index 22a5fe9c59d1b2a8283e1ebb503c77b2be892fb5..7bc6a7c5daf7f63aca77935a8634b2d2f64bd5e6 100644 +--- a/build.gradle.kts ++++ b/build.gradle.kts +@@ -9,6 +9,8 @@ java { + } + + dependencies { ++ implementation("org.ow2.asm:asm:9.5") // PandaSpigot ++ implementation("org.ow2.asm:asm-commons:9.5") // PandaSpigot + api("commons-lang:commons-lang:2.6") + api("org.avaje:ebean:2.8.1") + api("com.googlecode.json-simple:json-simple:1.1.1") +diff --git a/src/main/java/com/destroystokyo/paper/event/executor/ASMEventExecutorGenerator.java b/src/main/java/com/destroystokyo/paper/event/executor/ASMEventExecutorGenerator.java +new file mode 100644 +index 0000000000000000000000000000000000000000..72f3da6ecc3af06a93ebb1021f33dfdcc828ee0f +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/event/executor/ASMEventExecutorGenerator.java +@@ -0,0 +1,42 @@ ++package com.destroystokyo.paper.event.executor; ++import java.lang.reflect.Method; ++import java.util.concurrent.atomic.AtomicInteger; ++ ++import org.bukkit.plugin.EventExecutor; ++import org.objectweb.asm.ClassWriter; ++import org.objectweb.asm.Type; ++import org.objectweb.asm.commons.GeneratorAdapter; ++ ++import static org.objectweb.asm.Opcodes.*; ++public class ASMEventExecutorGenerator { ++ public static byte[] generateEventExecutor(Method m, String name) { ++ ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS); ++ writer.visit(V1_8, ACC_PUBLIC, name.replace('.', '/'), null, Type.getInternalName(Object.class), new String[] {Type.getInternalName(EventExecutor.class)}); ++ // Generate constructor ++ GeneratorAdapter methodGenerator = new GeneratorAdapter(writer.visitMethod(ACC_PUBLIC, "", "()V", null, null), ACC_PUBLIC, "", "()V"); ++ methodGenerator.loadThis(); ++ methodGenerator.visitMethodInsn(INVOKESPECIAL, Type.getInternalName(Object.class), "", "()V", false); // Invoke the super class (Object) constructor ++ methodGenerator.returnValue(); ++ methodGenerator.endMethod(); ++ // Generate the execute method ++ methodGenerator = new GeneratorAdapter(writer.visitMethod(ACC_PUBLIC, "execute", "(Lorg/bukkit/event/Listener;Lorg/bukkit/event/Event;)V", null, null), ACC_PUBLIC, "execute", "(Lorg/bukkit/event/Listener;Lorg/bukkit/event/Listener;)V");; ++ methodGenerator.loadArg(0); ++ methodGenerator.checkCast(Type.getType(m.getDeclaringClass())); ++ methodGenerator.loadArg(1); ++ methodGenerator.checkCast(Type.getType(m.getParameterTypes()[0])); ++ methodGenerator.visitMethodInsn(INVOKEVIRTUAL, Type.getInternalName(m.getDeclaringClass()), m.getName(), Type.getMethodDescriptor(m), m.getDeclaringClass().isInterface()); ++ if (m.getReturnType() != void.class) { ++ methodGenerator.pop(); ++ } ++ methodGenerator.returnValue(); ++ methodGenerator.endMethod(); ++ writer.visitEnd(); ++ return writer.toByteArray(); ++ } ++ ++ public static AtomicInteger NEXT_ID = new AtomicInteger(1); ++ public static String generateName() { ++ int id = NEXT_ID.getAndIncrement(); ++ return "com.destroystokyo.paper.event.executor.asm.generated.GeneratedEventExecutor" + id; ++ } ++} +diff --git a/src/main/java/com/destroystokyo/paper/event/executor/MethodHandleEventExecutor.java b/src/main/java/com/destroystokyo/paper/event/executor/MethodHandleEventExecutor.java +new file mode 100644 +index 0000000000000000000000000000000000000000..c11a7685cc9e0fc04ffa00827b536d93859ebb10 +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/event/executor/MethodHandleEventExecutor.java +@@ -0,0 +1,40 @@ ++package com.destroystokyo.paper.event.executor; ++ ++import org.bukkit.event.Event; ++import org.bukkit.event.EventException; ++import org.bukkit.event.Listener; ++import org.bukkit.plugin.EventExecutor; ++ ++import java.lang.invoke.MethodHandle; ++import java.lang.invoke.MethodHandles; ++import java.lang.reflect.Method; ++ ++public class MethodHandleEventExecutor implements EventExecutor { ++ private final Class eventClass; ++ private final MethodHandle handle; ++ ++ public MethodHandleEventExecutor(Class eventClass, MethodHandle handle) { ++ this.eventClass = eventClass; ++ this.handle = handle; ++ } ++ ++ public MethodHandleEventExecutor(Class eventClass, Method m) { ++ this.eventClass = eventClass; ++ try { ++ m.setAccessible(true); ++ this.handle = MethodHandles.lookup().unreflect(m); ++ } catch (IllegalAccessException e) { ++ throw new AssertionError("Unable to set accessible", e); ++ } ++ } ++ ++ @Override ++ public void execute(Listener listener, Event event) throws EventException { ++ if (!eventClass.isInstance(event)) return; ++ try { ++ handle.invoke(listener, event); ++ } catch (Throwable t) { ++ throw new EventException(t); ++ } ++ } ++} +diff --git a/src/main/java/com/destroystokyo/paper/event/executor/StaticMethodHandleEventExecutor.java b/src/main/java/com/destroystokyo/paper/event/executor/StaticMethodHandleEventExecutor.java +new file mode 100644 +index 0000000000000000000000000000000000000000..f75820330e9c7b29ddae56e0fc35c9be21c133c0 +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/event/executor/StaticMethodHandleEventExecutor.java +@@ -0,0 +1,38 @@ ++package com.destroystokyo.paper.event.executor; ++ ++import com.google.common.base.Preconditions; ++import org.bukkit.event.Event; ++import org.bukkit.event.EventException; ++import org.bukkit.event.Listener; ++import org.bukkit.plugin.EventExecutor; ++ ++import java.lang.invoke.MethodHandle; ++import java.lang.invoke.MethodHandles; ++import java.lang.reflect.Method; ++import java.lang.reflect.Modifier; ++ ++public class StaticMethodHandleEventExecutor implements EventExecutor { ++ private final Class eventClass; ++ private final MethodHandle handle; ++ ++ public StaticMethodHandleEventExecutor(Class eventClass, Method m) { ++ Preconditions.checkArgument(Modifier.isStatic(m.getModifiers()), "Not a static method: %s", m); ++ this.eventClass = eventClass; ++ try { ++ m.setAccessible(true); ++ this.handle = MethodHandles.lookup().unreflect(m); ++ } catch (IllegalAccessException e) { ++ throw new AssertionError("Unable to set accessible", e); ++ } ++ } ++ ++ @Override ++ public void execute(Listener listener, Event event) throws EventException { ++ if (!eventClass.isInstance(event)) return; ++ try { ++ handle.invoke(event); ++ } catch (Throwable t) { ++ throw new EventException(t); ++ } ++ } ++} +diff --git a/src/main/java/com/destroystokyo/paper/event/executor/asm/ASMEventExecutorGenerator.java b/src/main/java/com/destroystokyo/paper/event/executor/asm/ASMEventExecutorGenerator.java +new file mode 100644 +index 0000000000000000000000000000000000000000..5b8e92337df834eadd057a5fc40fcbe47f602ae9 +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/event/executor/asm/ASMEventExecutorGenerator.java +@@ -0,0 +1,47 @@ ++package com.destroystokyo.paper.event.executor.asm; ++ ++import org.bukkit.plugin.EventExecutor; ++import org.objectweb.asm.ClassWriter; ++import org.objectweb.asm.Type; ++import org.objectweb.asm.commons.GeneratorAdapter; ++ ++import java.lang.reflect.Method; ++import java.util.concurrent.atomic.AtomicInteger; ++ ++import static org.objectweb.asm.Opcodes.ACC_PUBLIC; ++import static org.objectweb.asm.Opcodes.INVOKESPECIAL; ++import static org.objectweb.asm.Opcodes.INVOKEVIRTUAL; ++import static org.objectweb.asm.Opcodes.V1_8; ++ ++public class ASMEventExecutorGenerator { ++ public static byte[] generateEventExecutor(Method m, String name) { ++ ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS); ++ writer.visit(V1_8, ACC_PUBLIC, name.replace('.', '/'), null, Type.getInternalName(Object.class), new String[] {Type.getInternalName(EventExecutor.class)}); ++ // Generate constructor ++ GeneratorAdapter methodGenerator = new GeneratorAdapter(writer.visitMethod(ACC_PUBLIC, "", "()V", null, null), ACC_PUBLIC, "", "()V"); ++ methodGenerator.loadThis(); ++ methodGenerator.visitMethodInsn(INVOKESPECIAL, Type.getInternalName(Object.class), "", "()V", false); // Invoke the super class (Object) constructor ++ methodGenerator.returnValue(); ++ methodGenerator.endMethod(); ++ // Generate the execute method ++ methodGenerator = new GeneratorAdapter(writer.visitMethod(ACC_PUBLIC, "execute", "(Lorg/bukkit/event/Listener;Lorg/bukkit/event/Event;)V", null, null), ACC_PUBLIC, "execute", "(Lorg/bukkit/event/Listener;Lorg/bukkit/event/Listener;)V");; ++ methodGenerator.loadArg(0); ++ methodGenerator.checkCast(Type.getType(m.getDeclaringClass())); ++ methodGenerator.loadArg(1); ++ methodGenerator.checkCast(Type.getType(m.getParameterTypes()[0])); ++ methodGenerator.visitMethodInsn(INVOKEVIRTUAL, Type.getInternalName(m.getDeclaringClass()), m.getName(), Type.getMethodDescriptor(m), m.getDeclaringClass().isInterface()); ++ if (m.getReturnType() != void.class) { ++ methodGenerator.pop(); ++ } ++ methodGenerator.returnValue(); ++ methodGenerator.endMethod(); ++ writer.visitEnd(); ++ return writer.toByteArray(); ++ } ++ ++ public static AtomicInteger NEXT_ID = new AtomicInteger(1); ++ public static String generateName() { ++ int id = NEXT_ID.getAndIncrement(); ++ return "com.destroystokyo.paper.event.executor.asm.generated.GeneratedEventExecutor" + id; ++ } ++} +diff --git a/src/main/java/com/destroystokyo/paper/event/executor/asm/ClassDefiner.java b/src/main/java/com/destroystokyo/paper/event/executor/asm/ClassDefiner.java +new file mode 100644 +index 0000000000000000000000000000000000000000..cc0f17f30c7809a13968a10c7252f78b5e079dcb +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/event/executor/asm/ClassDefiner.java +@@ -0,0 +1,30 @@ ++package com.destroystokyo.paper.event.executor.asm; ++ ++public interface ClassDefiner { ++ ++ /** ++ * Returns if the defined classes can bypass access checks ++ * ++ * @return if classes bypass access checks ++ */ ++ default boolean isBypassAccessChecks() { ++ return false; ++ } ++ ++ /** ++ * Define a class ++ * ++ * @param parentLoader the parent classloader ++ * @param name the name of the class ++ * @param data the class data to load ++ * @return the defined class ++ * @throws ClassFormatError if the class data is invalid ++ * @throws NullPointerException if any of the arguments are null ++ */ ++ Class defineClass(ClassLoader parentLoader, String name, byte[] data); ++ ++ static ClassDefiner getInstance() { ++ return SafeClassDefiner.INSTANCE; ++ } ++ ++} +diff --git a/src/main/java/com/destroystokyo/paper/event/executor/asm/SafeClassDefiner.java b/src/main/java/com/destroystokyo/paper/event/executor/asm/SafeClassDefiner.java +new file mode 100644 +index 0000000000000000000000000000000000000000..4520acbfb5aaa58baa0001e1cef096ec9412b1c3 +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/event/executor/asm/SafeClassDefiner.java +@@ -0,0 +1,60 @@ ++package com.destroystokyo.paper.event.executor.asm; ++ ++import com.google.common.base.Preconditions; ++ ++import java.util.concurrent.ConcurrentHashMap; ++import java.util.concurrent.ConcurrentMap; ++ ++public class SafeClassDefiner implements ClassDefiner { ++ static final SafeClassDefiner INSTANCE = new SafeClassDefiner(); ++ ++ private SafeClassDefiner() {} ++ ++ private final ConcurrentMap loaders = new ConcurrentHashMap<>(); ++ ++ @Override ++ public Class defineClass(ClassLoader parentLoader, String name, byte[] data) { ++ GeneratedClassLoader loader = loaders.computeIfAbsent(parentLoader, GeneratedClassLoader::new); ++ synchronized (loader.getClassLoadingLock(name)) { ++ Preconditions.checkState(!loader.hasClass(name), "%s already defined", name); ++ Class c = loader.define(name, data); ++ assert c.getName().equals(name); ++ return c; ++ } ++ } ++ ++ private static class GeneratedClassLoader extends ClassLoader { ++ static { ++ ClassLoader.registerAsParallelCapable(); ++ } ++ ++ protected GeneratedClassLoader(ClassLoader parent) { ++ super(parent); ++ } ++ ++ private Class define(String name, byte[] data) { ++ synchronized (getClassLoadingLock(name)) { ++ assert !hasClass(name); ++ Class c = defineClass(name, data, 0, data.length); ++ resolveClass(c); ++ return c; ++ } ++ } ++ ++ @Override ++ public Object getClassLoadingLock(String name) { ++ return super.getClassLoadingLock(name); ++ } ++ ++ public boolean hasClass(String name) { ++ synchronized (getClassLoadingLock(name)) { ++ try { ++ Class.forName(name); ++ return true; ++ } catch (ClassNotFoundException e) { ++ return false; ++ } ++ } ++ } ++ } ++} +diff --git a/src/main/java/org/bukkit/plugin/EventExecutor.java b/src/main/java/org/bukkit/plugin/EventExecutor.java +index 3b2c99ea7b30c8c4c03ffaca91c83d2e63338396..62be2db44f11ad03b58071e8b88c2105607f184b 100644 +--- a/src/main/java/org/bukkit/plugin/EventExecutor.java ++++ b/src/main/java/org/bukkit/plugin/EventExecutor.java +@@ -4,9 +4,54 @@ import org.bukkit.event.Event; + import org.bukkit.event.EventException; + import org.bukkit.event.Listener; + ++// PandaSpigot start ++import java.lang.reflect.Method; ++import java.lang.reflect.Modifier; ++import com.destroystokyo.paper.event.executor.MethodHandleEventExecutor; ++import com.destroystokyo.paper.event.executor.StaticMethodHandleEventExecutor; ++import com.destroystokyo.paper.event.executor.asm.ASMEventExecutorGenerator; ++import com.destroystokyo.paper.event.executor.asm.ClassDefiner; ++import com.google.common.base.Preconditions; ++// PandaSpigot end ++ + /** + * Interface which defines the class for event call backs to plugins + */ + public interface EventExecutor { + public void execute(Listener listener, Event event) throws EventException; ++ ++ // PandaSpigot start ++ public static EventExecutor create(Method m, Class eventClass) { ++ Preconditions.checkNotNull(m, "Null method"); ++ Preconditions.checkArgument(m.getParameterCount() != 0, "Incorrect number of arguments %s", m.getParameterCount()); ++ Preconditions.checkArgument(m.getParameterTypes()[0] == eventClass, "First parameter %s doesn't match event class %s", m.getParameterTypes()[0], eventClass); ++ ClassDefiner definer = ClassDefiner.getInstance(); ++ if (Modifier.isStatic(m.getModifiers())) { ++ return new StaticMethodHandleEventExecutor(eventClass, m); ++ } if (definer.isBypassAccessChecks() || Modifier.isPublic(m.getDeclaringClass().getModifiers()) && Modifier.isPublic(m.getModifiers())) { ++ String name = ASMEventExecutorGenerator.generateName(); ++ byte[] classData = ASMEventExecutorGenerator.generateEventExecutor(m, name); ++ Class c = definer.defineClass(m.getDeclaringClass().getClassLoader(), name, classData).asSubclass(EventExecutor.class); ++ try { ++ EventExecutor asmExecutor = c.newInstance(); ++ // Define a wrapper to conform to bukkit stupidity (passing in events that don't match and wrapper exception) ++ return new EventExecutor() { ++ @Override ++ public void execute(Listener listener, Event event) throws EventException { ++ if (!eventClass.isInstance(event)) return; ++ try { ++ asmExecutor.execute(listener, event); ++ } catch (Exception e) { ++ throw new EventException(e); ++ } ++ } ++ }; ++ } catch (InstantiationException | IllegalAccessException e) { ++ throw new AssertionError("Unable to initialize generated event executor", e); ++ } ++ } else { ++ return new MethodHandleEventExecutor(eventClass, m); ++ } ++ } ++ // PandaSpigot end + } +diff --git a/src/main/java/org/bukkit/plugin/java/JavaPluginLoader.java b/src/main/java/org/bukkit/plugin/java/JavaPluginLoader.java +index bc6b9d12fe2e008fbac6c7fcc30495e5ff3591c9..c09a6fe397885813f13e96fbaa91b2e68236bc60 100644 +--- a/src/main/java/org/bukkit/plugin/java/JavaPluginLoader.java ++++ b/src/main/java/org/bukkit/plugin/java/JavaPluginLoader.java +@@ -291,20 +291,7 @@ public final class JavaPluginLoader implements PluginLoader { + } + } + +- EventExecutor executor = new co.aikar.timings.TimedEventExecutor(new EventExecutor() { // Spigot +- public void execute(Listener listener, Event event) throws EventException { +- try { +- if (!eventClass.isAssignableFrom(event.getClass())) { +- return; +- } +- method.invoke(listener, event); +- } catch (InvocationTargetException ex) { +- throw new EventException(ex.getCause()); +- } catch (Throwable t) { +- throw new EventException(t); +- } +- } +- }, plugin, method, eventClass); // Spigot ++ EventExecutor executor = new co.aikar.timings.TimedEventExecutor(EventExecutor.create(method, eventClass), plugin, method, eventClass); // Spigot // PandaSpigot - Use factory method `EventExecutor.create()` + if (false) { // Spigot - RL handles useTimings check now + eventSet.add(new TimedRegisteredListener(listener, executor, eh.priority(), plugin, eh.ignoreCancelled())); + } else { diff --git a/patches/api/0028-Performance-Concurrency-Improvements-to-Permissions-.patch b/patches/api/0028-Performance-Concurrency-Improvements-to-Permissions-.patch new file mode 100644 index 00000000..aa33a2b2 --- /dev/null +++ b/patches/api/0028-Performance-Concurrency-Improvements-to-Permissions-.patch @@ -0,0 +1,99 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: mechoriet +Date: Sun, 23 Apr 2023 13:07:05 +0200 +Subject: [PATCH] Performance & Concurrency Improvements to Permissions no + double lookup + + +diff --git a/src/main/java/org/bukkit/permissions/PermissibleBase.java b/src/main/java/org/bukkit/permissions/PermissibleBase.java +index 3b95061aad53083219ca653c1ab5c629c2254e05..aa607fe975b7740238082dc5034ad8556171108e 100644 +--- a/src/main/java/org/bukkit/permissions/PermissibleBase.java ++++ b/src/main/java/org/bukkit/permissions/PermissibleBase.java +@@ -68,8 +68,11 @@ public class PermissibleBase implements Permissible { + + String name = inName.toLowerCase(); + +- if (isPermissionSet(name)) { +- return permissions.get(name).getValue(); ++ // PandaSpigot start ++ PermissionAttachmentInfo info = permissions.get(name); ++ if(info != null) { ++ return info.getValue(); ++ // PandaSpigot end + } else { + Permission perm = Bukkit.getServer().getPluginManager().getPermission(name); + +@@ -88,13 +91,16 @@ public class PermissibleBase implements Permissible { + + String name = perm.getName().toLowerCase(); + +- if (isPermissionSet(name)) { +- return permissions.get(name).getValue(); ++ // PandaSpigot start ++ PermissionAttachmentInfo info = permissions.get(name); ++ if(info != null) { ++ return info.getValue(); ++ // PandaSpigot end + } + return perm.getDefault().getValue(isOp()); + } + +- public PermissionAttachment addAttachment(Plugin plugin, String name, boolean value) { ++ public synchronized PermissionAttachment addAttachment(Plugin plugin, String name, boolean value) { // PandaSpigot - synchronized + if (name == null) { + throw new IllegalArgumentException("Permission name cannot be null"); + } else if (plugin == null) { +@@ -111,7 +117,7 @@ public class PermissibleBase implements Permissible { + return result; + } + +- public PermissionAttachment addAttachment(Plugin plugin) { ++ public synchronized PermissionAttachment addAttachment(Plugin plugin) { // PandaSpigot - synchronized + if (plugin == null) { + throw new IllegalArgumentException("Plugin cannot be null"); + } else if (!plugin.isEnabled()) { +@@ -126,7 +132,7 @@ public class PermissibleBase implements Permissible { + return result; + } + +- public void removeAttachment(PermissionAttachment attachment) { ++ public synchronized void removeAttachment(PermissionAttachment attachment) { // PandaSpigot - synchronized + if (attachment == null) { + throw new IllegalArgumentException("Attachment cannot be null"); + } +@@ -145,7 +151,7 @@ public class PermissibleBase implements Permissible { + } + } + +- public void recalculatePermissions() { ++ public synchronized void recalculatePermissions() { // PandaSpigot - synchronized + clearPermissions(); + Set defaults = Bukkit.getServer().getPluginManager().getDefaultPermissions(isOp()); + Bukkit.getServer().getPluginManager().subscribeToDefaultPerms(isOp(), parent); +@@ -192,7 +198,7 @@ public class PermissibleBase implements Permissible { + } + } + +- public PermissionAttachment addAttachment(Plugin plugin, String name, boolean value, int ticks) { ++ public synchronized PermissionAttachment addAttachment(Plugin plugin, String name, boolean value, int ticks) { // PandaSpigot - synchronized + if (name == null) { + throw new IllegalArgumentException("Permission name cannot be null"); + } else if (plugin == null) { +@@ -210,7 +216,7 @@ public class PermissibleBase implements Permissible { + return result; + } + +- public PermissionAttachment addAttachment(Plugin plugin, int ticks) { ++ public synchronized PermissionAttachment addAttachment(Plugin plugin, int ticks) { // PandaSpigot - synchronized + if (plugin == null) { + throw new IllegalArgumentException("Plugin cannot be null"); + } else if (!plugin.isEnabled()) { +@@ -228,7 +234,7 @@ public class PermissibleBase implements Permissible { + } + } + +- public Set getEffectivePermissions() { ++ public synchronized Set getEffectivePermissions() { // PandaSpigot - synchronized + return new HashSet(permissions.values()); + } + diff --git a/patches/server/0001-Fix-Decompilation-errors.patch b/patches/server/0001-Fix-Decompilation-errors.patch index b844b2f0..ef394775 100644 --- a/patches/server/0001-Fix-Decompilation-errors.patch +++ b/patches/server/0001-Fix-Decompilation-errors.patch @@ -549,6 +549,19 @@ index 4bf790cdffdbc8950450644a813d9c7bb539793d..0446e2be5003e8aa785618de48767d16 } } +diff --git a/src/main/java/net/minecraft/server/EntityPlayer.java b/src/main/java/net/minecraft/server/EntityPlayer.java +index 0c49a256cc481af1ceb7a873b03763e1d942a362..7a7bb58d8632c34259215b1372140ececf5c825a 100644 +--- a/src/main/java/net/minecraft/server/EntityPlayer.java ++++ b/src/main/java/net/minecraft/server/EntityPlayer.java +@@ -462,7 +462,7 @@ public class EntityPlayer extends EntityHuman implements ICrafting { + EntityLiving entityliving = this.bt(); + + if (entityliving != null) { +- EntityTypes.MonsterEggInfo entitytypes_monsteregginfo = (EntityTypes.MonsterEggInfo) EntityTypes.eggInfo.get(Integer.valueOf(EntityTypes.a(entityliving))); ++ EntityTypes.MonsterEggInfo entitytypes_monsteregginfo = (EntityTypes.MonsterEggInfo) EntityTypes.eggInfo.get(EntityTypes.a(entityliving)); + + if (entitytypes_monsteregginfo != null) { + this.b(entitytypes_monsteregginfo.e); diff --git a/src/main/java/net/minecraft/server/EntityTameableAnimal.java b/src/main/java/net/minecraft/server/EntityTameableAnimal.java index 2b170a15a42b257e92178df8124fd4ce64fa26c9..1cc087d0470c60e26f7c585e1d50156bce6b1f46 100644 --- a/src/main/java/net/minecraft/server/EntityTameableAnimal.java @@ -2563,6 +2576,45 @@ index 63fbf9447e1b7250613ee39e72c3d7fae35f3081..906aa3d7e54887a29e241a90abe50e84 } public void a(IJsonStatistic ijsonstatistic) { +diff --git a/src/main/java/net/minecraft/server/StructureGenerator.java b/src/main/java/net/minecraft/server/StructureGenerator.java +index e76acfc4f39287f8f3078ce1b2516d4ca8fc4c6c..91f2e401f2be2baf3cfc350b0c338f5fae2e9b2d 100644 +--- a/src/main/java/net/minecraft/server/StructureGenerator.java ++++ b/src/main/java/net/minecraft/server/StructureGenerator.java +@@ -18,14 +18,14 @@ public abstract class StructureGenerator extends WorldGenBase { + + protected final void a(World world, final int i, final int j, int k, int l, ChunkSnapshot chunksnapshot) { + this.a(world); +- if (!this.e.containsKey(Long.valueOf(ChunkCoordIntPair.a(i, j)))) { ++ if (!this.e.containsKey(ChunkCoordIntPair.a(i, j))) { + this.b.nextInt(); + + try { + if (this.a(i, j)) { + StructureStart structurestart = this.b(i, j); + +- this.e.put(Long.valueOf(ChunkCoordIntPair.a(i, j)), structurestart); ++ this.e.put(ChunkCoordIntPair.a(i, j), structurestart); + this.a(i, j, structurestart); + } + +@@ -42,7 +42,7 @@ public abstract class StructureGenerator extends WorldGenBase { + return this.a(); + } + }); +- crashreportsystemdetails.a("Chunk location", (Object) String.format("%d,%d", new Object[] { Integer.valueOf(i), Integer.valueOf(j)})); ++ crashreportsystemdetails.a("Chunk location", (Object) String.format("%d,%d", new Object[] {i, j})); + crashreportsystemdetails.a("Chunk pos hash", new Callable() { + public String a() throws Exception { + return String.valueOf(ChunkCoordIntPair.a(i, j)); +@@ -226,7 +226,7 @@ public abstract class StructureGenerator extends WorldGenBase { + StructureStart structurestart = WorldGenFactory.a(nbttagcompound1, world); + + if (structurestart != null) { +- this.e.put(Long.valueOf(ChunkCoordIntPair.a(i, j)), structurestart); ++ this.e.put(ChunkCoordIntPair.a(i, j), structurestart); + } + } + } diff --git a/src/main/java/net/minecraft/server/WeightedRandom.java b/src/main/java/net/minecraft/server/WeightedRandom.java index 2bbcfa3c7bbb9f304f4817dd1f9a260e7df366b1..58789a31ed2d1fdda2c33a576957c3ed9e30ec78 100644 --- a/src/main/java/net/minecraft/server/WeightedRandom.java diff --git a/patches/server/0023-Player-Chunk-Load-Unload-Events.patch b/patches/server/0023-Player-Chunk-Load-Unload-Events.patch index c054155e..7db145be 100644 --- a/patches/server/0023-Player-Chunk-Load-Unload-Events.patch +++ b/patches/server/0023-Player-Chunk-Load-Unload-Events.patch @@ -5,7 +5,7 @@ Subject: [PATCH] Player Chunk Load/Unload Events diff --git a/src/main/java/net/minecraft/server/EntityPlayer.java b/src/main/java/net/minecraft/server/EntityPlayer.java -index 0c49a256cc481af1ceb7a873b03763e1d942a362..23524af2dd05a6f14ff3bddbf524c5d4d483290f 100644 +index 7a7bb58d8632c34259215b1372140ececf5c825a..f147724e99c3ca8bd72bfaf3da6c92aefb5cd4de 100644 --- a/src/main/java/net/minecraft/server/EntityPlayer.java +++ b/src/main/java/net/minecraft/server/EntityPlayer.java @@ -241,6 +241,7 @@ public class EntityPlayer extends EntityHuman implements ICrafting { diff --git a/patches/server/0052-Add-packet-limiter-config.patch b/patches/server/0052-Add-packet-limiter-config.patch index edcdfd55..f2d0ac49 100644 --- a/patches/server/0052-Add-packet-limiter-config.patch +++ b/patches/server/0052-Add-packet-limiter-config.patch @@ -305,7 +305,7 @@ index 0000000000000000000000000000000000000000..d61d44e8e1126ed3bea6e633cc0bdebb + } +} diff --git a/src/main/java/net/minecraft/server/NetworkManager.java b/src/main/java/net/minecraft/server/NetworkManager.java -index df8ff41f691626de37f706df187269bee032358f..5c631068db79615bf51ed357bc9bda2b0ebb0c3b 100644 +index df8ff41f691626de37f706df187269bee032358f..87ecc1b9a46bfcc146b7b42f142cac46c02faa9c 100644 --- a/src/main/java/net/minecraft/server/NetworkManager.java +++ b/src/main/java/net/minecraft/server/NetworkManager.java @@ -113,6 +113,22 @@ public class NetworkManager extends SimpleChannelInboundHandler { @@ -325,7 +325,7 @@ index df8ff41f691626de37f706df187269bee032358f..5c631068db79615bf51ed357bc9bda2b + this.close(reason[0]); + this.k(); + this.stopReadingPackets = true; -+ }, null); ++ }, (GenericFutureListener>) null); + } + // PandaSpigot end - packet limiter public NetworkManager(EnumProtocolDirection enumprotocoldirection) { diff --git a/patches/server/0059-Optimise-removeQueue.patch b/patches/server/0059-Optimise-removeQueue.patch index dc5a2355..96b824b7 100644 --- a/patches/server/0059-Optimise-removeQueue.patch +++ b/patches/server/0059-Optimise-removeQueue.patch @@ -5,7 +5,7 @@ Subject: [PATCH] Optimise removeQueue diff --git a/src/main/java/net/minecraft/server/EntityPlayer.java b/src/main/java/net/minecraft/server/EntityPlayer.java -index 77068cd022e677fbf284df3d8aff95325089ce4f..0a4acf1e7b78eaeeca2606dbc46cdc5d703b8fad 100644 +index f147724e99c3ca8bd72bfaf3da6c92aefb5cd4de..22eda596ea5b06f3346814e9d8cecb15f828f58d 100644 --- a/src/main/java/net/minecraft/server/EntityPlayer.java +++ b/src/main/java/net/minecraft/server/EntityPlayer.java @@ -34,7 +34,7 @@ public class EntityPlayer extends EntityHuman implements ICrafting { diff --git a/patches/server/0066-Fix-issue-with-ProtocolSupport.patch b/patches/server/0066-Fix-issue-with-ProtocolSupport.patch index f8825cd5..8165ce4f 100644 --- a/patches/server/0066-Fix-issue-with-ProtocolSupport.patch +++ b/patches/server/0066-Fix-issue-with-ProtocolSupport.patch @@ -13,23 +13,44 @@ The arrays in encryption packets are limited to 256 bytes. diff --git a/src/main/java/com/hpfxd/pandaspigot/CompatHacks.java b/src/main/java/com/hpfxd/pandaspigot/CompatHacks.java new file mode 100644 -index 0000000000000000000000000000000000000000..c45655f5232934db455aaa5d2d3ff721a5733051 +index 0000000000000000000000000000000000000000..1a36e0c9869ff4d196ab2910cf281146bef5985a --- /dev/null +++ b/src/main/java/com/hpfxd/pandaspigot/CompatHacks.java -@@ -0,0 +1,11 @@ +@@ -0,0 +1,32 @@ +package com.hpfxd.pandaspigot; + +import org.bukkit.Bukkit; + +public class CompatHacks { -+ private CompatHacks() {} -+ public static boolean hasProtocolSupport() { -+ return Bukkit.getPluginManager().isPluginEnabled("ProtocolSupport"); ++ ++ private static CompatHacks instance; ++ private boolean isChecked = false, isLoaded = false; ++ ++ public CompatHacks() { ++ instance = this; ++ } ++ ++ public boolean hasProtocolSupport() { ++ return isChecked ? isLoaded : processCheck(); ++ } ++ ++ ++ private boolean processCheck() { ++ if (!isChecked) { ++ isChecked = true; ++ isLoaded = Bukkit.getPluginManager().isPluginEnabled("ProtocolSupport"); ++ } ++ return isLoaded; + } ++ ++ public static CompatHacks getInstance() { ++ return instance == null ? new CompatHacks() : instance; ++ } ++ +} + diff --git a/src/main/java/net/minecraft/server/PacketDataSerializer.java b/src/main/java/net/minecraft/server/PacketDataSerializer.java -index 6c46349fb24856bb2b0f94d84536f64d96daeece..ad33280bb8baab581a4ac17b5fe78022134c676b 100644 +index 6c46349fb24856bb2b0f94d84536f64d96daeece..2f1b500d4dd797475363cfc1e15b37aa4120d032 100644 --- a/src/main/java/net/minecraft/server/PacketDataSerializer.java +++ b/src/main/java/net/minecraft/server/PacketDataSerializer.java @@ -31,7 +31,21 @@ public class PacketDataSerializer extends ByteBuf { @@ -49,7 +70,7 @@ index 6c46349fb24856bb2b0f94d84536f64d96daeece..ad33280bb8baab581a4ac17b5fe78022 + * it's still much better than the old system of having no limit, + * which would leave the server vulnerable to packets up to 2 GIGABYTES in size. + */ -+ this.allowLargePackets = com.hpfxd.pandaspigot.CompatHacks.hasProtocolSupport(); ++ this.allowLargePackets = com.hpfxd.pandaspigot.CompatHacks.getInstance().hasProtocolSupport(); + // PandaSpigot end this.a = bytebuf; } diff --git a/patches/server/0073-Add-World-Util-Methods.patch b/patches/server/0073-Add-World-Util-Methods.patch index b90973db..5fa30fc9 100644 --- a/patches/server/0073-Add-World-Util-Methods.patch +++ b/patches/server/0073-Add-World-Util-Methods.patch @@ -19,8 +19,28 @@ index 03fe76355813497a5db33ad185db0e4b40c6f85a..df28cca6175ff3ddf4992c44a0760ef1 public int a(BlockPosition blockposition, int i) { int j = blockposition.getX() & 15; int k = blockposition.getY(); +diff --git a/src/main/java/net/minecraft/server/IChunkProvider.java b/src/main/java/net/minecraft/server/IChunkProvider.java +index c68ffe0e040f239f093a4543bc07463b27f96228..208ecb17be61f35e880f2ed252b69093129623e8 100644 +--- a/src/main/java/net/minecraft/server/IChunkProvider.java ++++ b/src/main/java/net/minecraft/server/IChunkProvider.java +@@ -10,6 +10,15 @@ public interface IChunkProvider { + + Chunk getChunkAt(BlockPosition blockposition); + ++ ++ // PandaSpigot start - add default method to getChunkIfLoaded ++ default Chunk getChunkIfLoaded(int i, int j) { ++ if (!this.isChunkLoaded(i, j)) { ++ return null; ++ } ++ return this.getOrCreateChunk(i, j); ++ } ++ // PandaSpigot end + void getChunkAt(IChunkProvider ichunkprovider, int i, int j); + + boolean a(IChunkProvider ichunkprovider, Chunk chunk, int i, int j); diff --git a/src/main/java/net/minecraft/server/World.java b/src/main/java/net/minecraft/server/World.java -index b75d78da71f24a0b02bf709d026389add0556489..56d9a764bbf521f512e959efaa864c445a198917 100644 +index 084f107bf019b0f2e00d17a470632780db490abc..b4a410a4c2a7d8072fd1acedc82646103c7fd0a1 100644 --- a/src/main/java/net/minecraft/server/World.java +++ b/src/main/java/net/minecraft/server/World.java @@ -93,7 +93,7 @@ public abstract class World implements IBlockAccess { @@ -45,7 +65,129 @@ index b75d78da71f24a0b02bf709d026389add0556489..56d9a764bbf521f512e959efaa864c44 public Chunk getChunkIfLoaded(int x, int z) { return ((ChunkProviderServer) this.chunkProvider).getChunkIfLoaded(x, z); } -@@ -652,10 +658,46 @@ public abstract class World implements IBlockAccess { +@@ -297,6 +303,12 @@ public abstract class World implements IBlockAccess { + return this.getType(blockposition).getBlock().getMaterial() == Material.AIR; + } + ++ // PandaSpigot start - xyz version of isLoaded ++ public boolean isLoaded(int x, int z) { ++ return getChunkIfLoaded(x >> 4, z >> 4) != null; ++ } ++ // PandaSpigot end ++ + public boolean isLoaded(BlockPosition blockposition) { + return getChunkIfLoaded(blockposition.getX() >> 4, blockposition.getZ() >> 4) != null; // PandaSpigot + } +@@ -362,25 +374,92 @@ public abstract class World implements IBlockAccess { + return this.chunkProvider.getOrCreateChunk(i, j); + } + +- public boolean setTypeAndData(BlockPosition blockposition, IBlockData iblockdata, int i) { ++ ++ // PandaSpigot start - add captured versions ++ private void setCapturedBlockType(BlockPosition blockposition, IBlockData iblockdata, int i) { ++ BlockState blockstate = null; ++ Iterator it = capturedBlockStates.iterator(); ++ while (it.hasNext()) { ++ BlockState previous = it.next(); ++ if (previous.getX() == blockposition.getX() && previous.getY() == blockposition.getY() && previous.getZ() == blockposition.getZ()) { ++ blockstate = previous; ++ it.remove(); ++ break; ++ } ++ } ++ if (blockstate == null) { ++ blockstate = org.bukkit.craftbukkit.block.CraftBlockState.getBlockState(this, blockposition.getX(), blockposition.getY(), blockposition.getZ(), i); ++ } ++ blockstate.setTypeId(CraftMagicNumbers.getId(iblockdata.getBlock())); ++ blockstate.setRawData((byte) iblockdata.getBlock().toLegacyData(iblockdata)); ++ this.capturedBlockStates.add(blockstate); ++ } ++ ++ public boolean setTypeAndDataIfLoaded(BlockPosition blockposition, IBlockData iblockdata, int i) { + // CraftBukkit start - tree generation + if (this.captureTreeGeneration) { +- BlockState blockstate = null; +- Iterator it = capturedBlockStates.iterator(); +- while (it.hasNext()) { +- BlockState previous = it.next(); +- if (previous.getX() == blockposition.getX() && previous.getY() == blockposition.getY() && previous.getZ() == blockposition.getZ()) { +- blockstate = previous; +- it.remove(); +- break; +- } +- } +- if (blockstate == null) { +- blockstate = org.bukkit.craftbukkit.block.CraftBlockState.getBlockState(this, blockposition.getX(), blockposition.getY(), blockposition.getZ(), i); +- } +- blockstate.setTypeId(CraftMagicNumbers.getId(iblockdata.getBlock())); +- blockstate.setRawData((byte) iblockdata.getBlock().toLegacyData(iblockdata)); ++ this.setCapturedBlockType(blockposition, iblockdata, i); ++ return true; ++ } ++ // CraftBukkit end ++ int x = blockposition.getX(); ++ int y = blockposition.getY(); ++ int z = blockposition.getZ(); ++ Chunk chunk = this.getChunkIfLoaded(x >> 4, z >> 4); ++ if (chunk == null) { ++ return false; ++ } ++ ++ if (!this.isValidLocation(x, y, z)) { ++ return false; ++ } ++ ++ if (!this.isClientSide && this.worldData.getType() == WorldType.DEBUG_ALL_BLOCK_STATES) { ++ return false; ++ } ++ ++ Block block = iblockdata.getBlock(); ++ ++ // CraftBukkit start - capture blockstates ++ BlockState blockstate = null; ++ if (this.captureBlockStates) { ++ blockstate = org.bukkit.craftbukkit.block.CraftBlockState.getBlockState(this, x, y, z, i); + this.capturedBlockStates.add(blockstate); ++ } ++ IBlockData iblockdata1 = chunk.a(blockposition, iblockdata); ++ ++ if (iblockdata1 == null) { ++ // CraftBukkit start - remove blockstate if failed ++ if (this.captureBlockStates) { ++ this.capturedBlockStates.remove(blockstate); ++ } ++ // CraftBukkit end ++ return false; ++ } ++ Block block1 = iblockdata1.getBlock(); ++ ++ if (block.p() != block1.p() || block.r() != block1.r()) { ++ this.methodProfiler.a("checkLight"); ++ this.x(blockposition); ++ this.methodProfiler.b(); ++ } ++ ++ // CraftBukkit start ++ if (!this.captureBlockStates) { // Don't notify clients or update physics while capturing blockstates ++ // Modularize client and physic updates ++ notifyAndUpdatePhysics(blockposition, chunk, block1, block, i); ++ } ++ // CraftBukkit end ++ ++ return true; ++ } ++ // PandaSpigot end ++ ++ public boolean setTypeAndData(BlockPosition blockposition, IBlockData iblockdata, int i) { ++ // CraftBukkit start - tree generation ++ if (this.captureTreeGeneration) { ++ this.setCapturedBlockType(blockposition, iblockdata, i); // PandaSpigot ++ // PandaSpigot end + return true; + } + // CraftBukkit end +@@ -652,6 +731,41 @@ public abstract class World implements IBlockAccess { } } @@ -87,27 +229,26 @@ index b75d78da71f24a0b02bf709d026389add0556489..56d9a764bbf521f512e959efaa864c44 public int getLightLevel(BlockPosition blockposition) { return this.c(blockposition, true); } - -+ public final int getLight(BlockPosition blockposition, boolean checkNeighbors) { return this.c(blockposition, checkNeighbors); } // PandaSpigot - OBFHELPER - public int c(BlockPosition blockposition, boolean flag) { - if (blockposition.getX() >= -30000000 && blockposition.getZ() >= -30000000 && blockposition.getX() < 30000000 && blockposition.getZ() < 30000000) { - if (flag && this.getType(blockposition).getBlock().s()) { -@@ -766,6 +808,22 @@ public abstract class World implements IBlockAccess { +@@ -766,6 +880,26 @@ public abstract class World implements IBlockAccess { return this.worldProvider.p()[this.getLightLevel(blockposition)]; } + // PandaSpigot start - Add getTypeIfLoaded + public IBlockData getTypeIfLoaded(BlockPosition blockposition) { ++ return this.getTypeIfLoaded(blockposition.getX(), blockposition.getY(), blockposition.getZ()); ++ } ++ ++ public IBlockData getTypeIfLoaded(int x, int y, int z) { + if (this.captureTreeGeneration) { + for (BlockState previous : this.capturedBlockStates) { -+ if (previous.getX() == blockposition.getX() && previous.getY() == blockposition.getY() && previous.getZ() == blockposition.getZ()) { ++ if (previous.getX() == x && previous.getY() == y && previous.getZ() == z) { + return CraftMagicNumbers.getBlock(previous.getTypeId()).fromLegacyData(previous.getRawData()); + } + } + } -+ Chunk chunk = this.getChunkIfLoaded(blockposition); ++ Chunk chunk = this.getChunkIfLoaded(x >> 4, z >> 4); + if (chunk != null) { -+ return this.isValidLocation(blockposition) ? chunk.getBlockData(blockposition) : Blocks.AIR.getBlockData(); ++ return this.isValidLocation(x, y, z) ? chunk.getBlockData(x, y, z) : Blocks.AIR.getBlockData(); + } + return null; + } diff --git a/patches/server/0075-Avoid-loading-chunks-in-multiple-places.patch b/patches/server/0075-Avoid-loading-chunks-in-multiple-places.patch index 5ce72625..f1f44131 100644 --- a/patches/server/0075-Avoid-loading-chunks-in-multiple-places.patch +++ b/patches/server/0075-Avoid-loading-chunks-in-multiple-places.patch @@ -192,10 +192,10 @@ index e67aa0423f6a28b286174662f29ffc32a74992d9..22c3ac7a71043ab82e7d3c95c71e02fa } diff --git a/src/main/java/net/minecraft/server/World.java b/src/main/java/net/minecraft/server/World.java -index a429dc5e950a31557d444d0a50ed71d13f13d555..46ebdb5e22592658cbd8172390b52edac5344e0f 100644 +index b4a410a4c2a7d8072fd1acedc82646103c7fd0a1..7d5dceda2ec207f7bcf6dd34fe8abc9ca64f2110 100644 --- a/src/main/java/net/minecraft/server/World.java +++ b/src/main/java/net/minecraft/server/World.java -@@ -874,7 +874,10 @@ public abstract class World implements IBlockAccess { +@@ -950,7 +950,10 @@ public abstract class World implements IBlockAccess { int i1 = MathHelper.floor(vec3d.b); int j1 = MathHelper.floor(vec3d.c); BlockPosition blockposition = new BlockPosition(l, i1, j1); @@ -207,7 +207,7 @@ index a429dc5e950a31557d444d0a50ed71d13f13d555..46ebdb5e22592658cbd8172390b52eda Block block = iblockdata.getBlock(); if ((!flag1 || block.a(this, blockposition, iblockdata) != null) && block.a(iblockdata, flag)) { -@@ -976,7 +979,10 @@ public abstract class World implements IBlockAccess { +@@ -1052,7 +1055,10 @@ public abstract class World implements IBlockAccess { i1 = MathHelper.floor(vec3d.b) - (enumdirection == EnumDirection.UP ? 1 : 0); j1 = MathHelper.floor(vec3d.c) - (enumdirection == EnumDirection.SOUTH ? 1 : 0); blockposition = new BlockPosition(l, i1, j1); @@ -219,7 +219,7 @@ index a429dc5e950a31557d444d0a50ed71d13f13d555..46ebdb5e22592658cbd8172390b52eda Block block1 = iblockdata1.getBlock(); if (!flag1 || block1.a(this, blockposition, iblockdata1) != null) { -@@ -2712,8 +2718,11 @@ public abstract class World implements IBlockAccess { +@@ -2788,8 +2794,11 @@ public abstract class World implements IBlockAccess { for (int i1 = i; i1 <= j; ++i1) { for (int j1 = k; j1 <= l; ++j1) { diff --git a/patches/server/0076-Use-less-resources-for-collisions.patch b/patches/server/0076-Use-less-resources-for-collisions.patch index bed71701..9352123b 100644 --- a/patches/server/0076-Use-less-resources-for-collisions.patch +++ b/patches/server/0076-Use-less-resources-for-collisions.patch @@ -42,7 +42,7 @@ index 58e485c2e5845dda15c33ee1beb170860535f88b..854cf888d64e50bfd85420bb5dcc9386 try { iblockdata.getBlock().a(this.world, blockposition2, iblockdata, this); diff --git a/src/main/java/net/minecraft/server/World.java b/src/main/java/net/minecraft/server/World.java -index 870e4f920fc336e38f331699eb0d8d91c43112d8..ad5db898164f575b37fdc9c8800660eded6dc45c 100644 +index 2af41076d5136f0d658653a7e466b8eabd644359..a3e93280787b2a324011a4155808094731f8f7c9 100644 --- a/src/main/java/net/minecraft/server/World.java +++ b/src/main/java/net/minecraft/server/World.java @@ -296,8 +296,13 @@ public abstract class World implements IBlockAccess { @@ -60,7 +60,7 @@ index 870e4f920fc336e38f331699eb0d8d91c43112d8..ad5db898164f575b37fdc9c8800660ed public boolean isEmpty(BlockPosition blockposition) { return this.getType(blockposition).getBlock().getMaterial() == Material.AIR; -@@ -361,8 +366,13 @@ public abstract class World implements IBlockAccess { +@@ -367,8 +372,13 @@ public abstract class World implements IBlockAccess { } public Chunk getChunkAtWorldCoords(BlockPosition blockposition) { @@ -75,7 +75,7 @@ index 870e4f920fc336e38f331699eb0d8d91c43112d8..ad5db898164f575b37fdc9c8800660ed public Chunk getChunkAt(int i, int j) { return this.chunkProvider.getOrCreateChunk(i, j); -@@ -830,27 +840,36 @@ public abstract class World implements IBlockAccess { +@@ -906,27 +916,36 @@ public abstract class World implements IBlockAccess { return getType( blockposition, true ); } diff --git a/patches/server/0078-Fix-MC-117075-TE-Unload-Lag-Spike.patch b/patches/server/0078-Fix-MC-117075-TE-Unload-Lag-Spike.patch index 90a04e58..1db3cc83 100644 --- a/patches/server/0078-Fix-MC-117075-TE-Unload-Lag-Spike.patch +++ b/patches/server/0078-Fix-MC-117075-TE-Unload-Lag-Spike.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Fix MC-117075: TE Unload Lag Spike diff --git a/src/main/java/net/minecraft/server/World.java b/src/main/java/net/minecraft/server/World.java -index ba185d2ef2b7970f566aa9c5980dad1e6c80e6f0..e7ccc5ef7e288068cc5f08772eb8dd4ee6b6f3f7 100644 +index 6f09a5e1208c32e03bffe37176aaa4569c7f5244..136a08ed701bb33ca3f7ce372ca1939af436a45a 100644 --- a/src/main/java/net/minecraft/server/World.java +++ b/src/main/java/net/minecraft/server/World.java -@@ -1607,7 +1607,11 @@ public abstract class World implements IBlockAccess { +@@ -1683,7 +1683,11 @@ public abstract class World implements IBlockAccess { this.M = true; // CraftBukkit start - From below, clean up tile entities before ticking them if (!this.c.isEmpty()) { diff --git a/patches/server/0080-Optimize-tnt-entity-and-falling-block-movement.patch b/patches/server/0080-Optimize-tnt-entity-and-falling-block-movement.patch index 14c88b33..eec0c90e 100644 --- a/patches/server/0080-Optimize-tnt-entity-and-falling-block-movement.patch +++ b/patches/server/0080-Optimize-tnt-entity-and-falling-block-movement.patch @@ -83,10 +83,10 @@ index b7d410eeb974eee7b26dce8e3b02ff2051e74f93..6671d4920f4b808d61fd458ef3f26f21 this.blocks.addAll(hashset); float f3 = this.size * 2.0F; diff --git a/src/main/java/net/minecraft/server/World.java b/src/main/java/net/minecraft/server/World.java -index e7ccc5ef7e288068cc5f08772eb8dd4ee6b6f3f7..db2d19ca09b3e721ecc291ad7bd887b3786b597c 100644 +index 136a08ed701bb33ca3f7ce372ca1939af436a45a..784a13358432ee4d36d3f228ab013636d91a61ea 100644 --- a/src/main/java/net/minecraft/server/World.java +++ b/src/main/java/net/minecraft/server/World.java -@@ -1350,6 +1350,8 @@ public abstract class World implements IBlockAccess { +@@ -1426,6 +1426,8 @@ public abstract class World implements IBlockAccess { // Spigot end if (entity instanceof EntityItem) return arraylist; // PaperSpigot - Optimize item movement diff --git a/patches/server/0081-Optimize-Captured-TileEntity-Lookup.patch b/patches/server/0081-Optimize-Captured-TileEntity-Lookup.patch index 4e074c3e..6b1a8a47 100644 --- a/patches/server/0081-Optimize-Captured-TileEntity-Lookup.patch +++ b/patches/server/0081-Optimize-Captured-TileEntity-Lookup.patch @@ -10,10 +10,10 @@ Optimize to check if the captured list even has values in it, and also to just do a get call since the value can never be null. diff --git a/src/main/java/net/minecraft/server/World.java b/src/main/java/net/minecraft/server/World.java -index db2d19ca09b3e721ecc291ad7bd887b3786b597c..ddbc724da605b26652371f2c51ab2609a7c7ef1e 100644 +index 784a13358432ee4d36d3f228ab013636d91a61ea..b25ae7b232ad4120d84595d4d1cbf881071abe4c 100644 --- a/src/main/java/net/minecraft/server/World.java +++ b/src/main/java/net/minecraft/server/World.java -@@ -2095,12 +2095,15 @@ public abstract class World implements IBlockAccess { +@@ -2171,12 +2171,15 @@ public abstract class World implements IBlockAccess { return null; } else { // CraftBukkit start diff --git a/patches/server/0087-Optimize-Light-Recalculations.patch b/patches/server/0087-Optimize-Light-Recalculations.patch index 4a7ac11b..a3abf558 100644 --- a/patches/server/0087-Optimize-Light-Recalculations.patch +++ b/patches/server/0087-Optimize-Light-Recalculations.patch @@ -20,10 +20,10 @@ index 3971e7dfdb9b05f44c8e0735d88e95da72be9f22..9c22cf970288d24a431df836409fc802 this.q = true; diff --git a/src/main/java/net/minecraft/server/World.java b/src/main/java/net/minecraft/server/World.java -index ddbc724da605b26652371f2c51ab2609a7c7ef1e..9b939a9e9f0c1d15588c1ebce8ba6e67be212d25 100644 +index b25ae7b232ad4120d84595d4d1cbf881071abe4c..27eca8851a504a22a99618a23e8817c7b4fac081 100644 --- a/src/main/java/net/minecraft/server/World.java +++ b/src/main/java/net/minecraft/server/World.java -@@ -527,8 +527,11 @@ public abstract class World implements IBlockAccess { +@@ -600,8 +600,11 @@ public abstract class World implements IBlockAccess { } if (!this.worldProvider.o()) { @@ -37,7 +37,7 @@ index ddbc724da605b26652371f2c51ab2609a7c7ef1e..9b939a9e9f0c1d15588c1ebce8ba6e67 } } -@@ -2680,9 +2683,15 @@ public abstract class World implements IBlockAccess { +@@ -2756,9 +2759,15 @@ public abstract class World implements IBlockAccess { * PaperSpigot - Asynchronous lighting updates */ public boolean updateLight(final EnumSkyBlock enumskyblock, final BlockPosition position) { diff --git a/patches/server/0088-Optimize-armor-stands.patch b/patches/server/0088-Optimize-armor-stands.patch index aea0a726..561235cb 100644 --- a/patches/server/0088-Optimize-armor-stands.patch +++ b/patches/server/0088-Optimize-armor-stands.patch @@ -37,10 +37,10 @@ index 2ea4a5fd32629a7931189786f86dce99d1db6d63..f8415dd404e14d85290724e66abc8a1a + // PandaSpigot end } diff --git a/src/main/java/net/minecraft/server/World.java b/src/main/java/net/minecraft/server/World.java -index 9b939a9e9f0c1d15588c1ebce8ba6e67be212d25..38ebe35f6fa434103b52e86262c7f4cc32d486e3 100644 +index 27eca8851a504a22a99618a23e8817c7b4fac081..5ddfc23643c2c1304e935a0ad554beb5f4b9a128 100644 --- a/src/main/java/net/minecraft/server/World.java +++ b/src/main/java/net/minecraft/server/World.java -@@ -1355,6 +1355,7 @@ public abstract class World implements IBlockAccess { +@@ -1431,6 +1431,7 @@ public abstract class World implements IBlockAccess { if (entity instanceof EntityItem) return arraylist; // PaperSpigot - Optimize item movement if (entity instanceof EntityTNTPrimed) return arraylist; // PandaSpigot - Optimize tnt entity movement if (entity instanceof EntityFallingBlock) return arraylist; // PandaSpigot - Optimize falling block movement diff --git a/patches/server/0099-Avoid-double-I-O-operation-on-load-player-file.patch b/patches/server/0099-Avoid-double-I-O-operation-on-load-player-file.patch new file mode 100644 index 00000000..8d6bb46d --- /dev/null +++ b/patches/server/0099-Avoid-double-I-O-operation-on-load-player-file.patch @@ -0,0 +1,29 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: mechoriet +Date: Wed, 19 Apr 2023 21:27:52 +0200 +Subject: [PATCH] Avoid double I/O operation on load player file + + +diff --git a/src/main/java/net/minecraft/server/WorldNBTStorage.java b/src/main/java/net/minecraft/server/WorldNBTStorage.java +index ba13f3f20bb86f667178f51f4d3d1df63f5acf2d..7aec6bbfbe365b424c02e95c5e2b5b7badaec641 100644 +--- a/src/main/java/net/minecraft/server/WorldNBTStorage.java ++++ b/src/main/java/net/minecraft/server/WorldNBTStorage.java +@@ -209,7 +209,8 @@ public class WorldNBTStorage implements IDataManager, IPlayerFileData { + File file = new File(this.playerDir, entityhuman.getUniqueID().toString() + ".dat"); + // Spigot Start + boolean usingWrongFile = false; +- if ( org.bukkit.Bukkit.getOnlineMode() && !file.exists() ) // PaperSpigot - Check online mode first ++ boolean normalFile = file.isFile(); // PandaSpigot ++ if ( org.bukkit.Bukkit.getOnlineMode() && !normalFile ) // PaperSpigot - Check online mode first // PandaSpigot + { + file = new File( this.playerDir, UUID.nameUUIDFromBytes( ( "OfflinePlayer:" + entityhuman.getName() ).getBytes( "UTF-8" ) ).toString() + ".dat"); + if ( file.exists() ) +@@ -220,7 +221,7 @@ public class WorldNBTStorage implements IDataManager, IPlayerFileData { + } + // Spigot End + +- if (file.exists() && file.isFile()) { ++ if ( normalFile ) { // PandaSpigot + nbttagcompound = NBTCompressedStreamTools.a((InputStream) (new FileInputStream(file))); + } + // Spigot Start diff --git a/patches/server/0100-Don-t-go-below-0-for-pickupDelay-breaks-picking-up-i.patch b/patches/server/0100-Don-t-go-below-0-for-pickupDelay-breaks-picking-up-i.patch new file mode 100644 index 00000000..d6ba9a80 --- /dev/null +++ b/patches/server/0100-Don-t-go-below-0-for-pickupDelay-breaks-picking-up-i.patch @@ -0,0 +1,27 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: mechoriet +Date: Wed, 19 Apr 2023 21:30:30 +0200 +Subject: [PATCH] Don't go below 0 for pickupDelay, breaks picking up items by + Aikar + + +diff --git a/src/main/java/net/minecraft/server/EntityItem.java b/src/main/java/net/minecraft/server/EntityItem.java +index 1f8b1c726ef7343ee7feb1e4ea4c1ef8cc453723..6440d5efc6c1e6181b8c518160cf580c94931a12 100644 +--- a/src/main/java/net/minecraft/server/EntityItem.java ++++ b/src/main/java/net/minecraft/server/EntityItem.java +@@ -62,6 +62,7 @@ public class EntityItem extends Entity { + // CraftBukkit start - Use wall time for pickup and despawn timers + int elapsedTicks = MinecraftServer.currentTick - this.lastTick; + if (this.pickupDelay != 32767) this.pickupDelay -= elapsedTicks; ++ this.pickupDelay = Math.max(0, this.pickupDelay); // PandaSpigot - don't go below 0 + if (this.age != -32768) this.age += elapsedTicks; + this.lastTick = MinecraftServer.currentTick; + // CraftBukkit end +@@ -127,6 +128,7 @@ public class EntityItem extends Entity { + // CraftBukkit start - Use wall time for pickup and despawn timers + int elapsedTicks = MinecraftServer.currentTick - this.lastTick; + if (this.pickupDelay != 32767) this.pickupDelay -= elapsedTicks; ++ this.pickupDelay = Math.max(0, this.pickupDelay); // PandaSpigot - don't go below 0 + if (this.age != -32768) this.age += elapsedTicks; + this.lastTick = MinecraftServer.currentTick; + // CraftBukkit end diff --git a/patches/server/0101-Check-channel-before-reading-and-change-isOpen-calls.patch b/patches/server/0101-Check-channel-before-reading-and-change-isOpen-calls.patch new file mode 100644 index 00000000..dff14fc4 --- /dev/null +++ b/patches/server/0101-Check-channel-before-reading-and-change-isOpen-calls.patch @@ -0,0 +1,38 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: mechoriet +Date: Wed, 19 Apr 2023 21:50:45 +0200 +Subject: [PATCH] Check channel before reading and change isOpen calls to + IsActive + + +diff --git a/src/main/java/net/minecraft/server/NetworkManager.java b/src/main/java/net/minecraft/server/NetworkManager.java +index 87ecc1b9a46bfcc146b7b42f142cac46c02faa9c..4f5cde057ba5b2d8cff3e40edd172286afaca01e 100644 +--- a/src/main/java/net/minecraft/server/NetworkManager.java ++++ b/src/main/java/net/minecraft/server/NetworkManager.java +@@ -174,7 +174,7 @@ public class NetworkManager extends SimpleChannelInboundHandler { + } + + protected void a(ChannelHandlerContext channelhandlercontext, Packet packet) throws Exception { +- if (this.channel.isOpen()) { ++ if (g()) { // PandaSpigot + // PandaSpigot start - packet limiter + if (this.stopReadingPackets) { + return; +@@ -503,7 +503,7 @@ public class NetworkManager extends SimpleChannelInboundHandler { + + public boolean isConnected() { return this.g(); } // PandaSpigot - OBFHELPER + public boolean g() { +- return this.channel != null && this.channel.isOpen(); ++ return this.channel != null && this.channel.isActive(); // PandaSpigot + } + + public boolean h() { +@@ -548,7 +548,7 @@ public class NetworkManager extends SimpleChannelInboundHandler { + } + + public void l() { +- if (this.channel != null && !this.channel.isOpen()) { ++ if (this.channel != null && !this.channel.isActive()) { // PandaSpigot + if (!this.p) { + this.p = true; + if (this.j() != null) { diff --git a/patches/server/0102-Reduce-new-BlockPosition-generation.patch b/patches/server/0102-Reduce-new-BlockPosition-generation.patch new file mode 100644 index 00000000..2c913128 --- /dev/null +++ b/patches/server/0102-Reduce-new-BlockPosition-generation.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: mechoriet +Date: Sun, 9 Jul 2023 21:45:58 -0300 +Subject: [PATCH] Reduce new BlockPosition generation + + +diff --git a/src/main/java/net/minecraft/server/EntityPlayer.java b/src/main/java/net/minecraft/server/EntityPlayer.java +index 22eda596ea5b06f3346814e9d8cecb15f828f58d..82d0228978937e50c6b859b6047dd8b0fde7a38a 100644 +--- a/src/main/java/net/minecraft/server/EntityPlayer.java ++++ b/src/main/java/net/minecraft/server/EntityPlayer.java +@@ -242,7 +242,7 @@ public class EntityPlayer extends EntityHuman implements ICrafting { + ChunkCoordIntPair chunkcoordintpair = (ChunkCoordIntPair) iterator1.next(); + + if (chunkcoordintpair != null) { +- if (this.world.isLoaded(new BlockPosition(chunkcoordintpair.x << 4, 0, chunkcoordintpair.z << 4))) { ++ if (this.world.isLoaded(chunkcoordintpair.x << 4, chunkcoordintpair.z << 4)) { // PandaSpigot - use int version instead of creating a throw away blockpos + chunk = this.world.getChunkAt(chunkcoordintpair.x, chunkcoordintpair.z); + if (chunk.isReady()) { + arraylist.add(chunk); diff --git a/patches/server/0103-Send-nearby-packets-to-world-player-list-not-server.patch b/patches/server/0103-Send-nearby-packets-to-world-player-list-not-server.patch new file mode 100644 index 00000000..f8eb353e --- /dev/null +++ b/patches/server/0103-Send-nearby-packets-to-world-player-list-not-server.patch @@ -0,0 +1,114 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: mechoriet +Date: Sat, 29 Apr 2023 20:18:25 +0200 +Subject: [PATCH] Send nearby packets to world player list not server + +particularly useful for multiworld servers with a lot of players should reduce work/iterating over bigger lists then necessary + +diff --git a/src/main/java/net/minecraft/server/PlayerList.java b/src/main/java/net/minecraft/server/PlayerList.java +index ada9cf25b091c2df24301bb5e82d1f76fbd929e5..d36455467d2187af21d94ee9717fe67614815849 100644 +--- a/src/main/java/net/minecraft/server/PlayerList.java ++++ b/src/main/java/net/minecraft/server/PlayerList.java +@@ -1057,16 +1057,32 @@ public abstract class PlayerList { + } + + public void sendPacketNearby(EntityHuman entityhuman, double d0, double d1, double d2, double d3, int i, Packet packet) { +- for (int j = 0; j < this.players.size(); ++j) { +- EntityPlayer entityplayer = (EntityPlayer) this.players.get(j); ++ // PandaSpigot start - send nearby packets to only world if needed ++ sendPacketNearby(entityhuman, d0, d1, d2, d3, i, null, packet); // Retained for compatibility ++ } ++ ++ public void sendPacketNearby(EntityHuman entityhuman, double d0, double d1, double d2, double d3, WorldServer world, Packet packet) { ++ sendPacketNearby(entityhuman, d0, d1, d2, d3, world.dimension, world, packet); ++ } ++ ++ public void sendPacketNearby(EntityHuman entityhuman, double d0, double d1, double d2, double d3, int i, WorldServer world, Packet packet) { ++ if (world == null && entityhuman != null && entityhuman.world instanceof WorldServer) { ++ world = (WorldServer) entityhuman.world; ++ } + ++ List players1 = world == null ? players : world.players; ++ for (int j = 0; j < players1.size(); ++j) { ++ EntityHuman entity = players1.get(j); ++ if (!(entity instanceof EntityPlayer)) continue; ++ EntityPlayer entityplayer = (EntityPlayer) players1.get(j); ++ // PandaSpigot stop + // CraftBukkit start - Test if player receiving packet can see the source of the packet + if (entityhuman != null && entityhuman instanceof EntityPlayer && !entityplayer.getBukkitEntity().canSee(((EntityPlayer) entityhuman).getBukkitEntity())) { + continue; + } + // CraftBukkit end + +- if (entityplayer != entityhuman && entityplayer.dimension == i) { ++ if (entityplayer != entityhuman && (world != null || entityplayer.dimension == i)) { // PandaSpigot + double d4 = d0 - entityplayer.locX; + double d5 = d1 - entityplayer.locY; + double d6 = d2 - entityplayer.locZ; +diff --git a/src/main/java/net/minecraft/server/WorldManager.java b/src/main/java/net/minecraft/server/WorldManager.java +index 923887bd010fa1fe1958a50ae39196bfa7f07bcd..68f0c59a5727c540206edc7139596a2ad1d407b2 100644 +--- a/src/main/java/net/minecraft/server/WorldManager.java ++++ b/src/main/java/net/minecraft/server/WorldManager.java +@@ -30,7 +30,7 @@ public class WorldManager implements IWorldAccess { + + public void a(EntityHuman entityhuman, String s, double d0, double d1, double d2, float f, float f1) { + // CraftBukkit - this.world.dimension +- this.a.getPlayerList().sendPacketNearby(entityhuman, d0, d1, d2, f > 1.0F ? (double) (16.0F * f) : 16.0D, this.world.dimension, new PacketPlayOutNamedSoundEffect(s, d0, d1, d2, f, f1)); ++ this.a.getPlayerList().sendPacketNearby(entityhuman, d0, d1, d2, f > 1.0F ? (double) (16.0F * f) : 16.0D, this.world, new PacketPlayOutNamedSoundEffect(s, d0, d1, d2, f, f1)); + } + + public void a(int i, int j, int k, int l, int i1, int j1) {} +@@ -45,7 +45,7 @@ public class WorldManager implements IWorldAccess { + + public void a(EntityHuman entityhuman, int i, BlockPosition blockposition, int j) { + // CraftBukkit - this.world.dimension +- this.a.getPlayerList().sendPacketNearby(entityhuman, (double) blockposition.getX(), (double) blockposition.getY(), (double) blockposition.getZ(), 64.0D, this.world.dimension, new PacketPlayOutWorldEvent(i, blockposition, j, false)); ++ this.a.getPlayerList().sendPacketNearby(entityhuman, (double) blockposition.getX(), (double) blockposition.getY(), (double) blockposition.getZ(), 64.0D, this.world, new PacketPlayOutWorldEvent(i, blockposition, j, false)); // PandaSpigot - provide world instead of dimension id + } + + public void a(int i, BlockPosition blockposition, int j) { +@@ -53,7 +53,6 @@ public class WorldManager implements IWorldAccess { + } + + public void b(int i, BlockPosition blockposition, int j) { +- Iterator iterator = this.a.getPlayerList().v().iterator(); + + // CraftBukkit start + EntityHuman entityhuman = null; +@@ -62,8 +61,13 @@ public class WorldManager implements IWorldAccess { + // CraftBukkit end + + PacketPlayOutBlockBreakAnimation packet = null; // PandaSpigot - Cache block break animation packet ++ // functional list that between world list for entity or playerlist ++ java.util.List list = entity != null ? entity.world.players : this.a.getPlayerList().v(); ++ Iterator iterator = list.iterator(); + while (iterator.hasNext()) { +- EntityPlayer entityplayer = (EntityPlayer) iterator.next(); ++ EntityHuman human = iterator.next(); ++ if (!(human instanceof EntityPlayer)) continue; ++ EntityPlayer entityplayer = (EntityPlayer) human; + + if (entityplayer != null && entityplayer.world == this.world && entityplayer.getId() != i) { + double d0 = (double) blockposition.getX() - entityplayer.locX; +diff --git a/src/main/java/net/minecraft/server/WorldServer.java b/src/main/java/net/minecraft/server/WorldServer.java +index d68deb81f9f7dfb82591d5876daaac9a3a258dc6..8741af722d57fe0e89a6f8ecb2670556dc0ec2c7 100644 +--- a/src/main/java/net/minecraft/server/WorldServer.java ++++ b/src/main/java/net/minecraft/server/WorldServer.java +@@ -1044,7 +1044,7 @@ public class WorldServer extends World implements IAsyncTaskHandler { + return false; + } + if (super.strikeLightning(entity)) { +- this.server.getPlayerList().sendPacketNearby(entity.locX, entity.locY, entity.locZ, 512.0D, dimension, new PacketPlayOutSpawnEntityWeather(entity)); ++ this.server.getPlayerList().sendPacketNearby((EntityHuman) null,entity.locX, entity.locY, entity.locZ, 512.0D, this, new PacketPlayOutSpawnEntityWeather(entity)); // provide world to only send to affected ones instead of all + // CraftBukkit end + return true; + } else { +@@ -1117,7 +1117,7 @@ public class WorldServer extends World implements IAsyncTaskHandler { + + if (this.a(blockactiondata)) { + // CraftBukkit - this.worldProvider.dimension -> this.dimension +- this.server.getPlayerList().sendPacketNearby((double) blockactiondata.a().getX(), (double) blockactiondata.a().getY(), (double) blockactiondata.a().getZ(), 64.0D, dimension, new PacketPlayOutBlockAction(blockactiondata.a(), blockactiondata.d(), blockactiondata.b(), blockactiondata.c())); ++ this.server.getPlayerList().sendPacketNearby((EntityHuman) null,(double) blockactiondata.a().getX(), (double) blockactiondata.a().getY(), (double) blockactiondata.a().getZ(), 64.0D, this, new PacketPlayOutBlockAction(blockactiondata.a(), blockactiondata.d(), blockactiondata.b(), blockactiondata.c())); // provide world to only send to affected ones instead of all + } + } + diff --git a/patches/server/0104-Improved-Async-Task-Scheduler.patch b/patches/server/0104-Improved-Async-Task-Scheduler.patch new file mode 100644 index 00000000..7e12b5ba --- /dev/null +++ b/patches/server/0104-Improved-Async-Task-Scheduler.patch @@ -0,0 +1,395 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: mechoriet +Date: Sun, 30 Apr 2023 16:45:13 +0200 +Subject: [PATCH] Improved Async Task Scheduler + + +diff --git a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftAsyncDebugger.java b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftAsyncDebugger.java +deleted file mode 100644 +index d80ae50d76eb98be5b676361d7816b89ebf1c2ae..0000000000000000000000000000000000000000 +--- a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftAsyncDebugger.java ++++ /dev/null +@@ -1,37 +0,0 @@ +-package org.bukkit.craftbukkit.scheduler; +- +-import org.bukkit.plugin.Plugin; +- +- +-class CraftAsyncDebugger { +- private CraftAsyncDebugger next = null; +- private final int expiry; +- private final Plugin plugin; +- private final Class clazz; +- +- CraftAsyncDebugger(final int expiry, final Plugin plugin, final Class clazz) { +- this.expiry = expiry; +- this.plugin = plugin; +- this.clazz = clazz; +- +- } +- +- final CraftAsyncDebugger getNextHead(final int time) { +- CraftAsyncDebugger next, current = this; +- while (time > current.expiry && (next = current.next) != null) { +- current = next; +- } +- return current; +- } +- +- final CraftAsyncDebugger setNext(final CraftAsyncDebugger next) { +- return this.next = next; +- } +- +- StringBuilder debugTo(final StringBuilder string) { +- for (CraftAsyncDebugger next = this; next != null; next = next.next) { +- string.append(next.plugin.getDescription().getName()).append(':').append(next.clazz.getName()).append('@').append(next.expiry).append(','); +- } +- return string; +- } +-} +diff --git a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftAsyncScheduler.java b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftAsyncScheduler.java +new file mode 100644 +index 0000000000000000000000000000000000000000..12b40e0799a95245db30c086248db2c57b1351c3 +--- /dev/null ++++ b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftAsyncScheduler.java +@@ -0,0 +1,127 @@ ++/* ++ * Copyright (c) 2018 Daniel Ennis (Aikar) MIT License ++ * ++ * Permission is hereby granted, free of charge, to any person obtaining ++ * a copy of this software and associated documentation files (the ++ * "Software"), to deal in the Software without restriction, including ++ * without limitation the rights to use, copy, modify, merge, publish, ++ * distribute, sublicense, and/or sell copies of the Software, and to ++ * permit persons to whom the Software is furnished to do so, subject to ++ * the following conditions: ++ * ++ * The above copyright notice and this permission notice shall be ++ * included in all copies or substantial portions of the Software. ++ * ++ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, ++ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF ++ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND ++ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE ++ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION ++ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION ++ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ++ */ ++ ++package org.bukkit.craftbukkit.scheduler; ++ ++import com.google.common.util.concurrent.ThreadFactoryBuilder; ++import org.bukkit.plugin.Plugin; ++import org.github.paperspigot.ServerSchedulerReportingWrapper; ++ ++import java.util.ArrayList; ++import java.util.Iterator; ++import java.util.List; ++import java.util.concurrent.Executor; ++import java.util.concurrent.Executors; ++import java.util.concurrent.SynchronousQueue; ++import java.util.concurrent.ThreadPoolExecutor; ++import java.util.concurrent.TimeUnit; ++ ++public class CraftAsyncScheduler extends CraftScheduler { ++ ++ private final ThreadPoolExecutor executor = new ThreadPoolExecutor( ++ 4, Integer.MAX_VALUE,30L, TimeUnit.SECONDS, new SynchronousQueue<>(), ++ new ThreadFactoryBuilder().setNameFormat("Craft Scheduler Thread - %1$d").build()); ++ private final Executor management = Executors.newSingleThreadExecutor(new ThreadFactoryBuilder() ++ .setNameFormat("Craft Async Scheduler Management Thread").build()); ++ private final List temp = new ArrayList<>(); ++ ++ CraftAsyncScheduler() { ++ super(true); ++ executor.allowCoreThreadTimeOut(true); ++ executor.prestartAllCoreThreads(); ++ } ++ ++ @Override ++ public void cancelTask(int taskId) { ++ this.management.execute(() -> this.removeTask(taskId)); ++ } ++ ++ private synchronized void removeTask(int taskId) { ++ parsePending(); ++ this.pending.removeIf((task) -> { ++ if (task.getTaskId() == taskId) { ++ task.cancel0(); ++ return true; ++ } ++ return false; ++ }); ++ } ++ ++ @Override ++ public void mainThreadHeartbeat(int currentTick) { ++ this.currentTick = currentTick; ++ this.management.execute(() -> this.runTasks(currentTick)); ++ } ++ ++ private synchronized void runTasks(int currentTick) { ++ parsePending(); ++ while (!this.pending.isEmpty() && this.pending.peek().getNextRun() <= currentTick) { ++ CraftTask task = this.pending.remove(); ++ if (executeTask(task)) { ++ final long period = task.getPeriod(); ++ if (period > 0) { ++ task.setNextRun(currentTick + period); ++ temp.add(task); ++ } ++ } ++ parsePending(); ++ } ++ this.pending.addAll(temp); ++ temp.clear(); ++ } ++ ++ private boolean executeTask(CraftTask task) { ++ if (isValid(task)) { ++ this.runners.put(task.getTaskId(), task); ++ this.executor.execute(new ServerSchedulerReportingWrapper(task)); ++ return true; ++ } ++ return false; ++ } ++ ++ @Override ++ public synchronized void cancelTasks(Plugin plugin) { ++ parsePending(); ++ for (Iterator iterator = this.pending.iterator(); iterator.hasNext(); ) { ++ CraftTask task = iterator.next(); ++ if (task.getTaskId() != -1 && (plugin == null || task.getOwner().equals(plugin))) { ++ task.cancel0(); ++ iterator.remove(); ++ } ++ } ++ } ++ ++ @Override ++ public synchronized void cancelAllTasks() { ++ cancelTasks(null); ++ } ++ ++ /** ++ * Task is not cancelled ++ * @param runningTask ++ * @return ++ */ ++ static boolean isValid(CraftTask runningTask) { ++ return runningTask.getPeriod() >= CraftTask.NO_REPEATING; ++ } ++} +diff --git a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java +index f036709c4895639d6f3af764c209e1caa643d5dd..b7a2b150c9341e12eeb09f988e903d8de358c82c 100644 +--- a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java ++++ b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java +@@ -60,7 +60,7 @@ public class CraftScheduler implements BukkitScheduler { + /** + * Main thread logic only + */ +- private final PriorityQueue pending = new PriorityQueue(10, ++ final PriorityQueue pending = new PriorityQueue(10, + new Comparator() { + public int compare(final CraftTask o1, final CraftTask o2) { + return (int) (o1.getNextRun() - o2.getNextRun()); +@@ -73,17 +73,30 @@ public class CraftScheduler implements BukkitScheduler { + /** + * These are tasks that are currently active. It's provided for 'viewing' the current state. + */ +- private final ConcurrentHashMap runners = new ConcurrentHashMap(); +- private volatile int currentTick = -1; +- private final Executor executor = Executors.newCachedThreadPool(new com.google.common.util.concurrent.ThreadFactoryBuilder().setNameFormat("Craft Scheduler Thread - %1$d").build()); // Spigot +- private CraftAsyncDebugger debugHead = new CraftAsyncDebugger(-1, null, null) {@Override StringBuilder debugTo(StringBuilder string) {return string;}}; +- private CraftAsyncDebugger debugTail = debugHead; ++ final ConcurrentHashMap runners = new ConcurrentHashMap(); ++ volatile int currentTick = -1; ++ //private final Executor executor = Executors.newCachedThreadPool(new com.google.common.util.concurrent.ThreadFactoryBuilder().setNameFormat("Craft Scheduler Thread - %1$d").build()); // Spigot + private static final int RECENT_TICKS; + + static { + RECENT_TICKS = 30; + } ++ // Paper start ++ private final CraftScheduler asyncScheduler; ++ private final boolean isAsyncScheduler; ++ public CraftScheduler() { ++ this(false); ++ } + ++ public CraftScheduler(boolean isAsync) { ++ this.isAsyncScheduler = isAsync; ++ if (isAsync) { ++ this.asyncScheduler = this; ++ } else { ++ this.asyncScheduler = new CraftAsyncScheduler(); ++ } ++ } ++ // Paper end + public int scheduleSyncDelayedTask(final Plugin plugin, final Runnable task) { + return this.scheduleSyncDelayedTask(plugin, task, 0l); + } +@@ -150,7 +163,7 @@ public class CraftScheduler implements BukkitScheduler { + } else if (period < -1l) { + period = -1l; + } +- return handle(new CraftAsyncTask(runners, plugin, runnable, nextId(), period), delay); ++ return handle(new CraftAsyncTask(this.asyncScheduler.runners, plugin, runnable, nextId(), period), delay); // Paper + } + + public Future callSyncMethod(final Plugin plugin, final Callable task) { +@@ -164,6 +177,11 @@ public class CraftScheduler implements BukkitScheduler { + if (taskId <= 0) { + return; + } ++ // Paper start ++ if (!this.isAsyncScheduler) { ++ this.asyncScheduler.cancelTask(taskId); ++ } ++ // Paper end + CraftTask task = runners.get(taskId); + if (task != null) { + task.cancel0(); +@@ -203,6 +221,11 @@ public class CraftScheduler implements BukkitScheduler { + + public void cancelTasks(final Plugin plugin) { + Validate.notNull(plugin, "Cannot cancel tasks of null plugin"); ++ // Paper start ++ if (!this.isAsyncScheduler) { ++ this.asyncScheduler.cancelTasks(plugin); ++ } ++ // Paper end + final CraftTask task = new CraftTask( + new Runnable() { + public void run() { +@@ -240,6 +263,11 @@ public class CraftScheduler implements BukkitScheduler { + } + + public void cancelAllTasks() { ++ // Paper start ++ if (!this.isAsyncScheduler) { ++ this.asyncScheduler.cancelAllTasks(); ++ } ++ // Paper end + final CraftTask task = new CraftTask( + new Runnable() { + public void run() { +@@ -282,6 +310,11 @@ public class CraftScheduler implements BukkitScheduler { + if (taskId <= 0) { + return false; + } ++ // Paper start ++ if (!this.isAsyncScheduler && this.asyncScheduler.isQueued(taskId)) { ++ return true; ++ } ++ // Paper end + for (CraftTask task = head.getNext(); task != null; task = task.getNext()) { + if (task.getTaskId() == taskId) { + return task.getPeriod() >= -1l; // The task will run +@@ -292,6 +325,12 @@ public class CraftScheduler implements BukkitScheduler { + } + + public List getActiveWorkers() { ++ // Paper start ++ if (!isAsyncScheduler) { ++ //noinspection TailRecursion ++ return this.asyncScheduler.getActiveWorkers(); ++ } ++ // Paper end + final ArrayList workers = new ArrayList(); + for (final CraftTask taskObj : runners.values()) { + // Iterator will be a best-effort (may fail to grab very new values) if called from an async thread +@@ -328,6 +367,11 @@ public class CraftScheduler implements BukkitScheduler { + pending.add(task); + } + } ++ // Paper start ++ if (!this.isAsyncScheduler) { ++ pending.addAll(this.asyncScheduler.getPendingTasks()); ++ } ++ // Paper end + return pending; + } + +@@ -335,6 +379,11 @@ public class CraftScheduler implements BukkitScheduler { + * This method is designed to never block or wait for locks; an immediate execution of all current tasks. + */ + public void mainThreadHeartbeat(final int currentTick) { ++ // Paper start ++ if (!this.isAsyncScheduler) { ++ this.asyncScheduler.mainThreadHeartbeat(currentTick); ++ } ++ // Paper end + this.currentTick = currentTick; + final List temp = this.temp; + parsePending(); +@@ -367,8 +416,7 @@ public class CraftScheduler implements BukkitScheduler { + } + parsePending(); + } else { +- debugTail = debugTail.setNext(new CraftAsyncDebugger(currentTick + RECENT_TICKS, task.getOwner(), task.getTaskClass())); +- executor.execute(new ServerSchedulerReportingWrapper(task)); // Paper ++ task.getOwner().getLogger().log(Level.SEVERE, "Unexpected Async Task in the Sync Scheduler. Report this to Paper"); // Paper + // We don't need to parse pending + // (async tasks must live with race-conditions if they attempt to cancel between these few lines of code) + } +@@ -382,10 +430,9 @@ public class CraftScheduler implements BukkitScheduler { + } + pending.addAll(temp); + temp.clear(); +- debugHead = debugHead.getNextHead(currentTick); + } + +- private void addTask(final CraftTask task) { ++ protected void addTask(final CraftTask task) { + final AtomicReference tail = this.tail; + CraftTask tailTask = tail.get(); + while (!tail.compareAndSet(tailTask, task)) { +@@ -394,7 +441,13 @@ public class CraftScheduler implements BukkitScheduler { + tailTask.setNext(task); + } + +- private CraftTask handle(final CraftTask task, final long delay) { ++ protected CraftTask handle(final CraftTask task, final long delay) { // Paper ++ // Paper start ++ if (!this.isAsyncScheduler && !task.isSync()) { ++ this.asyncScheduler.handle(task, delay); ++ return task; ++ } ++ // Paper end + task.setNextRun(currentTick + delay); + addTask(task); + return task; +@@ -412,7 +465,7 @@ public class CraftScheduler implements BukkitScheduler { + return ids.incrementAndGet(); + } + +- private void parsePending() { ++ void parsePending() { + CraftTask head = this.head; + CraftTask task = head.getNext(); + CraftTask lastTask = head; +@@ -441,7 +494,6 @@ public class CraftScheduler implements BukkitScheduler { + public String toString() { + int debugTick = currentTick; + StringBuilder string = new StringBuilder("Recent tasks from ").append(debugTick - RECENT_TICKS).append('-').append(debugTick).append('{'); +- debugHead.debugTo(string); + return string.append('}').toString(); + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftTask.java b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftTask.java +index 4b1e352361e8f92f34901b23ff4326f0ec5cbd14..44cbcdac82d11f23986676b3ca29c05e98ec80c3 100644 +--- a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftTask.java ++++ b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftTask.java +@@ -10,6 +10,11 @@ import org.bukkit.scheduler.BukkitTask; + public class CraftTask implements BukkitTask, Runnable { // Spigot + + private volatile CraftTask next = null; ++ public static final int ERROR = 0; ++ public static final int NO_REPEATING = -1; ++ public static final int CANCEL = -2; ++ public static final int PROCESS_FOR_FUTURE = -3; ++ public static final int DONE_FOR_FUTURE = -4; + /** + * -1 means no repeating
+ * -2 means cancel