From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: Kevin Raneri Date: Tue, 9 Nov 2021 14:01:56 -0500 Subject: [PATCH] Pufferfish API Changes Pufferfish Copyright (C) 2022 Pufferfish Studios LLC This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . diff --git a/build.gradle.kts b/build.gradle.kts index 001c2b963205012f340db0d539e4033c748124ce..93390eaead78d229902df0f7c8f9b30cb6174de7 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -40,6 +40,7 @@ dependencies { apiAndDocs("net.kyori:adventure-text-serializer-plain") api("org.apache.logging.log4j:log4j-api:2.17.1") api("org.slf4j:slf4j-api:1.8.0-beta4") + api("io.sentry:sentry:5.4.0") // Pufferfish implementation("org.ow2.asm:asm:9.2") implementation("org.ow2.asm:asm-commons:9.2") @@ -81,6 +82,13 @@ val generateApiVersioningFile by tasks.registering { } } +// Pufferfish Start +tasks.withType { + val compilerArgs = options.compilerArgs + compilerArgs.add("--add-modules=jdk.incubator.vector") +} +// Pufferfish End + tasks.jar { from(generateApiVersioningFile.map { it.outputs.files.singleFile }) { into("META-INF/maven/${project.group}/${project.name}") diff --git a/src/main/java/gg/pufferfish/pufferfish/sentry/SentryContext.java b/src/main/java/gg/pufferfish/pufferfish/sentry/SentryContext.java new file mode 100644 index 0000000000000000000000000000000000000000..10310fdd53de28efb8a8250f6d3b0c8eb08fb68a --- /dev/null +++ b/src/main/java/gg/pufferfish/pufferfish/sentry/SentryContext.java @@ -0,0 +1,161 @@ +package gg.pufferfish.pufferfish.sentry; + +import com.google.gson.Gson; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.Map; +import java.util.TreeMap; +import org.apache.logging.log4j.ThreadContext; +import org.bukkit.command.Command; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.bukkit.event.Event; +import org.bukkit.event.player.PlayerEvent; +import org.bukkit.plugin.Plugin; +import org.bukkit.plugin.RegisteredListener; +import org.jetbrains.annotations.Nullable; + +public class SentryContext { + + private static final Gson GSON = new Gson(); + + public static void setPluginContext(@Nullable Plugin plugin) { + if (plugin != null) { + ThreadContext.put("pufferfishsentry_pluginname", plugin.getName()); + ThreadContext.put("pufferfishsentry_pluginversion", plugin.getDescription().getVersion()); + } + } + + public static void removePluginContext() { + ThreadContext.remove("pufferfishsentry_pluginname"); + ThreadContext.remove("pufferfishsentry_pluginversion"); + } + + public static void setSenderContext(@Nullable CommandSender sender) { + if (sender != null) { + ThreadContext.put("pufferfishsentry_playername", sender.getName()); + if (sender instanceof Player player) { + ThreadContext.put("pufferfishsentry_playerid", player.getUniqueId().toString()); + } + } + } + + public static void removeSenderContext() { + ThreadContext.remove("pufferfishsentry_playername"); + ThreadContext.remove("pufferfishsentry_playerid"); + } + + public static void setEventContext(Event event, RegisteredListener registration) { + setPluginContext(registration.getPlugin()); + + try { + // Find the player that was involved with this event + Player player = null; + if (event instanceof PlayerEvent) { + player = ((PlayerEvent) event).getPlayer(); + } else { + Class eventClass = event.getClass(); + + Field playerField = null; + + for (Field field : eventClass.getDeclaredFields()) { + if (field.getType().equals(Player.class)) { + playerField = field; + break; + } + } + + if (playerField != null) { + playerField.setAccessible(true); + player = (Player) playerField.get(event); + } + } + + if (player != null) { + setSenderContext(player); + } + } catch (Exception e) {} // We can't really safely log exceptions. + + ThreadContext.put("pufferfishsentry_eventdata", GSON.toJson(serializeFields(event))); + } + + public static void removeEventContext() { + removePluginContext(); + removeSenderContext(); + ThreadContext.remove("pufferfishsentry_eventdata"); + } + + private static Map serializeFields(Object object) { + Map fields = new TreeMap<>(); + fields.put("_class", object.getClass().getName()); + for (Field declaredField : object.getClass().getDeclaredFields()) { + try { + if (Modifier.isStatic(declaredField.getModifiers())) { + continue; + } + + String fieldName = declaredField.getName(); + if (fieldName.equals("handlers")) { + continue; + } + declaredField.setAccessible(true); + Object value = declaredField.get(object); + if (value != null) { + fields.put(fieldName, value.toString()); + } else { + fields.put(fieldName, ""); + } + } catch (Exception e) {} // We can't really safely log exceptions. + } + return fields; + } + + public static class State { + + private Plugin plugin; + private Command command; + private String commandLine; + private Event event; + private RegisteredListener registeredListener; + + public Plugin getPlugin() { + return plugin; + } + + public void setPlugin(Plugin plugin) { + this.plugin = plugin; + } + + public Command getCommand() { + return command; + } + + public void setCommand(Command command) { + this.command = command; + } + + public String getCommandLine() { + return commandLine; + } + + public void setCommandLine(String commandLine) { + this.commandLine = commandLine; + } + + public Event getEvent() { + return event; + } + + public void setEvent(Event event) { + this.event = event; + } + + public RegisteredListener getRegisteredListener() { + return registeredListener; + } + + public void setRegisteredListener(RegisteredListener registeredListener) { + this.registeredListener = registeredListener; + } + } +} diff --git a/src/main/java/gg/pufferfish/pufferfish/simd/SIMDChecker.java b/src/main/java/gg/pufferfish/pufferfish/simd/SIMDChecker.java new file mode 100644 index 0000000000000000000000000000000000000000..77cf83bd096bbf6bfa7e510d97716b514384d9ff --- /dev/null +++ b/src/main/java/gg/pufferfish/pufferfish/simd/SIMDChecker.java @@ -0,0 +1,29 @@ +package gg.pufferfish.pufferfish.simd; + +import jdk.incubator.vector.IntVector; + +/** + * Basically, java is annoying and we have to push this out to its own class. + */ +@Deprecated +public class SIMDChecker { + + @Deprecated + public static boolean canEnable() { + try { + if (SIMDDetection.getJavaVersion() != 17) { + return false; + } else { + IntVector oneVector = IntVector.broadcast(IntVector.SPECIES_256, 1); + IntVector twoVector = IntVector.broadcast(IntVector.SPECIES_256, 2); + IntVector result = oneVector.add(twoVector); + if (result.toArray()[0] == 3) { + // Vectorization works! Let's enable it! + return true; + } + } + } catch (NoClassDefFoundError | Exception ignored) {} // Basically, we don't do anything. This lets us detect if it's not functional and disable it. + return false; + } + +} diff --git a/src/main/java/gg/pufferfish/pufferfish/simd/SIMDDetection.java b/src/main/java/gg/pufferfish/pufferfish/simd/SIMDDetection.java new file mode 100644 index 0000000000000000000000000000000000000000..1bbec0b032a4765f5e55a1475a1b7401f72164e7 --- /dev/null +++ b/src/main/java/gg/pufferfish/pufferfish/simd/SIMDDetection.java @@ -0,0 +1,32 @@ +package gg.pufferfish.pufferfish.simd; + +import jdk.incubator.vector.IntVector; + +@Deprecated +public class SIMDDetection { + + public static boolean isEnabled = false; + public static boolean versionLimited = false; + + @Deprecated + public static boolean canEnable() { + try { + return SIMDChecker.canEnable(); + } catch (NoClassDefFoundError | Exception ignored) { + return false; + } + } + + @Deprecated + public static int getJavaVersion() { + // https://stackoverflow.com/a/2591122 + String version = System.getProperty("java.version"); + if(version.startsWith("1.")) { + version = version.substring(2, 3); + } else { + int dot = version.indexOf("."); + if(dot != -1) { version = version.substring(0, dot); } + } return Integer.parseInt(version); + } + +} diff --git a/src/main/java/gg/pufferfish/pufferfish/simd/VectorMapPalette.java b/src/main/java/gg/pufferfish/pufferfish/simd/VectorMapPalette.java new file mode 100644 index 0000000000000000000000000000000000000000..48b67864752d6da4e2cc626e746eeb7c32b6524f --- /dev/null +++ b/src/main/java/gg/pufferfish/pufferfish/simd/VectorMapPalette.java @@ -0,0 +1,79 @@ +package gg.pufferfish.pufferfish.simd; + +import java.awt.Color; +import jdk.incubator.vector.FloatVector; +import jdk.incubator.vector.IntVector; +import jdk.incubator.vector.VectorMask; +import org.bukkit.map.MapPalette; + +@Deprecated +public class VectorMapPalette { + + @Deprecated + public static void matchColorVectorized(int[] in, byte[] out) { + int speciesLength = IntVector.SPECIES_256.length(); + int i; + for (i = 0; i < in.length; i += speciesLength) { + float[] redsArr = new float[speciesLength]; + float[] bluesArr = new float[speciesLength]; + float[] greensArr = new float[speciesLength]; + int[] alphasArr = new int[speciesLength]; + + for (int j = 0; j < speciesLength; j++) { + alphasArr[j] = (in[i + j] >> 24) & 0xFF; + redsArr[j] = (in[i + j] >> 16) & 0xFF; + greensArr[j] = (in[i + j] >> 8) & 0xFF; + bluesArr[j] = (in[i + j] >> 0) & 0xFF; + } + + IntVector alphas = IntVector.fromArray(IntVector.SPECIES_256, alphasArr, 0); + FloatVector reds = FloatVector.fromArray(FloatVector.SPECIES_256, redsArr, 0); + FloatVector greens = FloatVector.fromArray(FloatVector.SPECIES_256, greensArr, 0); + FloatVector blues = FloatVector.fromArray(FloatVector.SPECIES_256, bluesArr, 0); + IntVector resultIndex = IntVector.zero(IntVector.SPECIES_256); + VectorMask modificationMask = VectorMask.fromLong(IntVector.SPECIES_256, 0b11111111); + + modificationMask = modificationMask.and(alphas.lt(128).not()); + FloatVector bestDistances = FloatVector.broadcast(FloatVector.SPECIES_256, Float.MAX_VALUE); + + for (int c = 4; c < MapPalette.colors.length; c++) { + // We're using 32-bit floats here because it's 2x faster and nobody will know the difference. + // For correctness, the original algorithm uses 64-bit floats instead. Completely unnecessary. + FloatVector compReds = FloatVector.broadcast(FloatVector.SPECIES_256, MapPalette.colors[c].getRed()); + FloatVector compGreens = FloatVector.broadcast(FloatVector.SPECIES_256, MapPalette.colors[c].getGreen()); + FloatVector compBlues = FloatVector.broadcast(FloatVector.SPECIES_256, MapPalette.colors[c].getBlue()); + + FloatVector rMean = reds.add(compReds).div(2.0f); + FloatVector rDiff = reds.sub(compReds); + FloatVector gDiff = greens.sub(compGreens); + FloatVector bDiff = blues.sub(compBlues); + + FloatVector weightR = rMean.div(256.0f).add(2); + FloatVector weightG = FloatVector.broadcast(FloatVector.SPECIES_256, 4.0f); + FloatVector weightB = FloatVector.broadcast(FloatVector.SPECIES_256, 255.0f).sub(rMean).div(256.0f).add(2.0f); + + FloatVector distance = weightR.mul(rDiff).mul(rDiff).add(weightG.mul(gDiff).mul(gDiff)).add(weightB.mul(bDiff).mul(bDiff)); + + // Now we compare to the best distance we've found. + // This mask contains a "1" if better, and a "0" otherwise. + VectorMask bestDistanceMask = distance.lt(bestDistances); + bestDistances = bestDistances.blend(distance, bestDistanceMask); // Update the best distances + + // Update the result array + // We also AND with the modification mask because we don't want to interfere if the alpha value isn't large enough. + resultIndex = resultIndex.blend(c, bestDistanceMask.cast(IntVector.SPECIES_256).and(modificationMask)); // Update the results + } + + for (int j = 0; j < speciesLength; j++) { + int index = resultIndex.lane(j); + out[i + j] = (byte) (index < 128 ? index : -129 + (index - 127)); + } + } + + // For the final ones, fall back to the regular method + for (; i < in.length; i++) { + out[i] = MapPalette.matchColor(new Color(in[i], true)); + } + } + +} diff --git a/src/main/java/org/bukkit/map/MapPalette.java b/src/main/java/org/bukkit/map/MapPalette.java index b937441d2fb46b108644c49fcf073859765aa02e..d95b01bfd0657cf089c0f5412453cca08e36c02f 100644 --- a/src/main/java/org/bukkit/map/MapPalette.java +++ b/src/main/java/org/bukkit/map/MapPalette.java @@ -1,5 +1,6 @@ package org.bukkit.map; +import gg.pufferfish.pufferfish.simd.SIMDDetection; import java.awt.Color; import java.awt.Graphics2D; import java.awt.Image; @@ -34,7 +35,7 @@ public final class MapPalette { } @NotNull - static final Color[] colors = { + public static final Color[] colors = { // Pufferfish - public access c(0, 0, 0), c(0, 0, 0), c(0, 0, 0), c(0, 0, 0), c(89, 125, 39), c(109, 153, 48), c(127, 178, 56), c(67, 94, 29), c(174, 164, 115), c(213, 201, 140), c(247, 233, 163), c(130, 123, 86), @@ -205,9 +206,15 @@ public final class MapPalette { temp.getRGB(0, 0, temp.getWidth(), temp.getHeight(), pixels, 0, temp.getWidth()); byte[] result = new byte[temp.getWidth() * temp.getHeight()]; + // Pufferfish start + if (!SIMDDetection.isEnabled) { for (int i = 0; i < pixels.length; i++) { result[i] = matchColor(new Color(pixels[i], true)); } + } else { + gg.pufferfish.pufferfish.simd.VectorMapPalette.matchColorVectorized(pixels, result); + } + // Pufferfish end return result; } diff --git a/src/main/java/org/bukkit/plugin/SimplePluginManager.java b/src/main/java/org/bukkit/plugin/SimplePluginManager.java index 42da20011544075a9bea63a12ae86f2f21720667..16d0e517afd3fae21f2666c6b4e1e38dcf5ea64b 100644 --- a/src/main/java/org/bukkit/plugin/SimplePluginManager.java +++ b/src/main/java/org/bukkit/plugin/SimplePluginManager.java @@ -582,7 +582,9 @@ public final class SimplePluginManager implements PluginManager { // Paper start private void handlePluginException(String msg, Throwable ex, Plugin plugin) { + gg.pufferfish.pufferfish.sentry.SentryContext.setPluginContext(plugin); // Pufferfish server.getLogger().log(Level.SEVERE, msg, ex); + gg.pufferfish.pufferfish.sentry.SentryContext.removePluginContext(); // Pufferfish callEvent(new ServerExceptionEvent(new ServerPluginEnableDisableException(msg, ex, plugin))); } // Paper end @@ -641,9 +643,11 @@ public final class SimplePluginManager implements PluginManager { )); } } catch (Throwable ex) { + gg.pufferfish.pufferfish.sentry.SentryContext.setEventContext(event, registration); // Pufferfish // Paper start - error reporting String msg = "Could not pass event " + event.getEventName() + " to " + registration.getPlugin().getDescription().getFullName(); server.getLogger().log(Level.SEVERE, msg, ex); + gg.pufferfish.pufferfish.sentry.SentryContext.removeEventContext(); // Pufferfish if (!(event instanceof ServerExceptionEvent)) { // We don't want to cause an endless event loop callEvent(new ServerExceptionEvent(new ServerEventException(msg, ex, registration.getPlugin(), registration.getListener(), event))); } diff --git a/src/main/java/org/bukkit/plugin/java/JavaPluginLoader.java b/src/main/java/org/bukkit/plugin/java/JavaPluginLoader.java index c8b11793c6a3baabc1c9566e0463ab1d6e293827..2b9218ddd262e89180588c3014dad328317dd8db 100644 --- a/src/main/java/org/bukkit/plugin/java/JavaPluginLoader.java +++ b/src/main/java/org/bukkit/plugin/java/JavaPluginLoader.java @@ -369,7 +369,9 @@ public final class JavaPluginLoader implements PluginLoader { try { jPlugin.setEnabled(true); } catch (Throwable ex) { + gg.pufferfish.pufferfish.sentry.SentryContext.setPluginContext(plugin); // Pufferfish server.getLogger().log(Level.SEVERE, "Error occurred while enabling " + plugin.getDescription().getFullName() + " (Is it up to date?)", ex); + gg.pufferfish.pufferfish.sentry.SentryContext.removePluginContext(); // Pufferfish // Paper start - Disable plugins that fail to load this.server.getPluginManager().disablePlugin(jPlugin); return; @@ -398,7 +400,9 @@ public final class JavaPluginLoader implements PluginLoader { try { jPlugin.setEnabled(false); } catch (Throwable ex) { + gg.pufferfish.pufferfish.sentry.SentryContext.setPluginContext(plugin); // Pufferfish server.getLogger().log(Level.SEVERE, "Error occurred while disabling " + plugin.getDescription().getFullName() + " (Is it up to date?)", ex); + gg.pufferfish.pufferfish.sentry.SentryContext.removePluginContext(); // Pufferfish } if (cloader instanceof PluginClassLoader) { diff --git a/src/main/java/org/bukkit/plugin/java/PluginClassLoader.java b/src/main/java/org/bukkit/plugin/java/PluginClassLoader.java index 9938ebb38353f4aa2adf1bb08cd1c347ddd9fc88..dc76cdbe93a0229a8ff552e4048613e3d8e050ce 100644 --- a/src/main/java/org/bukkit/plugin/java/PluginClassLoader.java +++ b/src/main/java/org/bukkit/plugin/java/PluginClassLoader.java @@ -46,6 +46,8 @@ public final class PluginClassLoader extends URLClassLoader { // Spigot private final Set seenIllegalAccess = Collections.newSetFromMap(new ConcurrentHashMap<>()); private java.util.logging.Logger logger; // Paper - add field + private boolean closed = false; // Pufferfish + static { ClassLoader.registerAsParallelCapable(); } @@ -151,6 +153,7 @@ public final class PluginClassLoader extends URLClassLoader { // Spigot throw new ClassNotFoundException(name); } + public boolean _airplane_hasClass(@NotNull String name) { return this.classes.containsKey(name); } // Pufferfish @Override protected Class findClass(String name) throws ClassNotFoundException { if (name.startsWith("org.bukkit.") || name.startsWith("net.minecraft.")) { @@ -158,7 +161,7 @@ public final class PluginClassLoader extends URLClassLoader { // Spigot } Class result = classes.get(name); - if (result == null) { + if (result == null && !this.closed) { // Pufferfish String path = name.replace('.', '/').concat(".class"); JarEntry entry = jar.getJarEntry(path); @@ -213,6 +216,7 @@ public final class PluginClassLoader extends URLClassLoader { // Spigot try { super.close(); } finally { + this.closed = true; // Pufferfish jar.close(); } }