diff --git a/build.gradle b/build.gradle index 88f7a3b94..1d1e26ea0 100644 --- a/build.gradle +++ b/build.gradle @@ -33,6 +33,7 @@ plugins { id "io.github.goooler.shadow" version "8.1.7" id "de.undercouch.download" version "5.0.1" id "xyz.jpenilla.run-paper" version "2.3.1" + id "io.sentry.jvm.gradle" version "5.7.0" } @@ -113,6 +114,7 @@ shadowJar { relocate 'io.papermc.lib', 'com.volmit.iris.util.paper' relocate 'net.kyori', 'com.volmit.iris.util.kyori' relocate 'org.bstats', 'com.volmit.util.metrics' + relocate "io.sentry", "com.volmit.iris.util.sentry" archiveFileName.set("Iris-${project.version}.jar") dependencies { @@ -132,6 +134,7 @@ configurations.configureEach { allprojects { apply plugin: 'java' + apply plugin: 'io.sentry.jvm.gradle' repositories { mavenCentral() @@ -203,6 +206,16 @@ allprojects { archiveClassifier.set('javadoc') from javadoc.destinationDir } + + sentry { + includeSourceContext = true + + org = "volmit-software" + projectName = "iris" + authToken = hasProperty("sentry.auth.token") ? + property("sentry.auth.token") : + System.getenv("SENTRY_AUTH_TOKEN") + } } if (JavaVersion.current().toString() != "21") { @@ -279,3 +292,39 @@ def registerCustomOutputTaskUnix(name, path) { } tasks.build.dependsOn(shadowJar) + +def cli = file("sentry-cli.exe") +tasks.register("downloadCli", Download) { + group = "sentry" + src "https://release-registry.services.sentry.io/apps/sentry-cli/latest?response=download&arch=x86_64&platform=${System.getProperty("os.name")}&package=sentry-cli" + dest cli + + doLast { + cli.setExecutable(true) + } +} + +tasks.register("release") { + group = "sentry" + dependsOn("downloadCli") + doLast { + def authToken = project.hasProperty("sentry.auth.token") ? + project.property("sentry.auth.token") : + System.getenv("SENTRY_AUTH_TOKEN") + def org = "volmit-software" + def projectName = "iris" + exec { + executable(cli) + args("releases", "new", "--auth-token", authToken, "-o", org, "-p", projectName, version) + } + exec { + executable(cli) + args("releases", "set-commits", "--auth-token", authToken, "-o", org, "-p", projectName, version, "--auto") + } + exec { + executable(cli) + args("releases", "finalize", "--auth-token", authToken, "-o", org, "-p", projectName, version) + } + cli.delete() + } +} \ No newline at end of file diff --git a/core/src/main/java/com/volmit/iris/Iris.java b/core/src/main/java/com/volmit/iris/Iris.java index 5bf32b68f..43384f127 100644 --- a/core/src/main/java/com/volmit/iris/Iris.java +++ b/core/src/main/java/com/volmit/iris/Iris.java @@ -32,7 +32,6 @@ import com.volmit.iris.core.nms.v1X.NMSBinding1X; import com.volmit.iris.core.pregenerator.LazyPregenerator; import com.volmit.iris.core.service.StudioSVC; import com.volmit.iris.core.tools.IrisToolbelt; -import com.volmit.iris.core.tools.IrisWorldCreator; import com.volmit.iris.engine.EnginePanic; import com.volmit.iris.engine.object.IrisCompat; import com.volmit.iris.engine.object.IrisContextInjector; @@ -64,7 +63,10 @@ import com.volmit.iris.util.reflect.ShadeFix; import com.volmit.iris.util.scheduling.J; import com.volmit.iris.util.scheduling.Queue; import com.volmit.iris.util.scheduling.ShurikenQueue; +import com.volmit.iris.util.sentry.Attachments; +import com.volmit.iris.util.sentry.IrisLogger; import io.papermc.lib.PaperLib; +import io.sentry.Sentry; import net.kyori.adventure.platform.bukkit.BukkitAudiences; import net.kyori.adventure.text.serializer.ComponentSerializer; import org.bstats.bukkit.Metrics; @@ -392,6 +394,7 @@ public class Iris extends VolmitPlugin implements Listener { } public static void reportError(Throwable e) { + Sentry.captureException(e); if (IrisSettings.get().getGeneral().isDebug()) { String n = e.getClass().getCanonicalName() + "-" + e.getStackTrace()[0].getClassName() + "-" + e.getStackTrace()[0].getLineNumber(); @@ -456,6 +459,7 @@ public class Iris extends VolmitPlugin implements Listener { instance = this; services = new KMap<>(); setupAudience(); + setupSentry(); initialize("com.volmit.iris.core.service").forEach((i) -> services.put((Class) i.getClass(), (IrisService) i)); INMS.get(); IO.delete(new File("iris")); @@ -527,6 +531,7 @@ public class Iris extends VolmitPlugin implements Listener { } } catch (Throwable e) { e.printStackTrace(); + reportError(e); } } @@ -544,7 +549,7 @@ public class Iris extends VolmitPlugin implements Listener { }); }); } catch (IrisException e) { - e.printStackTrace(); + reportError(e); } } } @@ -940,4 +945,33 @@ public class Iris extends VolmitPlugin implements Listener { return -1; } } + + private static void setupSentry() { + var settings = IrisSettings.get().getSentry(); + if (settings.disableAutoReporting || Sentry.isEnabled()) return; + Iris.info("Enabling Sentry for anonymous error reporting. You can disable this in the settings."); + Sentry.init(options -> { + options.setDsn("https://b16ecc222e9c1e0c48faecacb906fd89@o4509451052646400.ingest.de.sentry.io/4509452722765904"); + if (settings.debug) { + options.setLogger(new IrisLogger()); + options.setDebug(true); + } + + options.setAttachServerName(false); + options.setEnableUncaughtExceptionHandler(false); + options.setRelease(Iris.instance.getDescription().getVersion()); + options.setBeforeSend((event, hint) -> { + event.setTag("iris.safeguard", IrisSafeguard.mode()); + event.setTag("iris.nms", INMS.get().getClass().getCanonicalName()); + return event; + }); + }); + Sentry.configureScope(scope -> { + scope.addAttachment(Attachments.PLUGINS); + scope.setTag("server", Bukkit.getVersion()); + scope.setTag("server.type", Bukkit.getName()); + scope.setTag("server.api", Bukkit.getBukkitVersion()); + }); + Runtime.getRuntime().addShutdownHook(new Thread(Sentry::close)); + } } diff --git a/core/src/main/java/com/volmit/iris/core/IrisSettings.java b/core/src/main/java/com/volmit/iris/core/IrisSettings.java index b7e5bae20..20c0fa265 100644 --- a/core/src/main/java/com/volmit/iris/core/IrisSettings.java +++ b/core/src/main/java/com/volmit/iris/core/IrisSettings.java @@ -46,6 +46,7 @@ public class IrisSettings { private IrisSettingsPerformance performance = new IrisSettingsPerformance(); private IrisSettingsUpdater updater = new IrisSettingsUpdater(); private IrisSettingsPregen pregen = new IrisSettingsPregen(); + private IrisSettingsSentry sentry = new IrisSettingsSentry(); public static int getThreadCount(int c) { return switch (c) { @@ -222,6 +223,12 @@ public class IrisSettings { } } + @Data + public static class IrisSettingsSentry { + public boolean disableAutoReporting = false; + public boolean debug = false; + } + @Data public static class IrisSettingsGUI { public boolean useServerLaunchedGuis = true; diff --git a/core/src/main/java/com/volmit/iris/core/commands/CommandDeveloper.java b/core/src/main/java/com/volmit/iris/core/commands/CommandDeveloper.java index 5d6090b00..29d4fd80f 100644 --- a/core/src/main/java/com/volmit/iris/core/commands/CommandDeveloper.java +++ b/core/src/main/java/com/volmit/iris/core/commands/CommandDeveloper.java @@ -71,6 +71,11 @@ public class CommandDeveloper implements DecreeExecutor { .engineStatus(sender()); } + @Decree(description = "Send a test exception to sentry") + public void Sentry() { + Iris.reportError(new Exception("This is a test")); + } + @Decree(description = "Test") public void dumpThreads() { try { diff --git a/core/src/main/java/com/volmit/iris/core/link/WorldEditLink.java b/core/src/main/java/com/volmit/iris/core/link/WorldEditLink.java index 42e335a10..638c66cc2 100644 --- a/core/src/main/java/com/volmit/iris/core/link/WorldEditLink.java +++ b/core/src/main/java/com/volmit/iris/core/link/WorldEditLink.java @@ -47,6 +47,7 @@ public class WorldEditLink { } catch (Throwable e) { Iris.error("Could not get selection"); e.printStackTrace(); + Iris.reportError(e); active.reset(); active.aquire(() -> false); } diff --git a/core/src/main/java/com/volmit/iris/core/loader/IrisData.java b/core/src/main/java/com/volmit/iris/core/loader/IrisData.java index e51c93176..a272be0d7 100644 --- a/core/src/main/java/com/volmit/iris/core/loader/IrisData.java +++ b/core/src/main/java/com/volmit/iris/core/loader/IrisData.java @@ -300,6 +300,7 @@ public class IrisData implements ExclusionStrategy, TypeAdapterFactory { return r; } catch (Throwable e) { + Iris.reportError(e); e.printStackTrace(); Iris.error("Failed to create loader! " + registrant.getCanonicalName()); } diff --git a/core/src/main/java/com/volmit/iris/core/pregenerator/ChunkUpdater.java b/core/src/main/java/com/volmit/iris/core/pregenerator/ChunkUpdater.java index 9f9bde99e..572ce98d4 100644 --- a/core/src/main/java/com/volmit/iris/core/pregenerator/ChunkUpdater.java +++ b/core/src/main/java/com/volmit/iris/core/pregenerator/ChunkUpdater.java @@ -109,6 +109,7 @@ public class ChunkUpdater { } } } catch (Exception e) { + Iris.reportError(e); e.printStackTrace(); } }, 0, 3, TimeUnit.SECONDS); @@ -314,6 +315,7 @@ public class ChunkUpdater { world.save(); }).get(); } catch (Throwable e) { + Iris.reportError(e); e.printStackTrace(); } } diff --git a/core/src/main/java/com/volmit/iris/core/pregenerator/IrisPregenerator.java b/core/src/main/java/com/volmit/iris/core/pregenerator/IrisPregenerator.java index 3201bebf4..dbd734a56 100644 --- a/core/src/main/java/com/volmit/iris/core/pregenerator/IrisPregenerator.java +++ b/core/src/main/java/com/volmit/iris/core/pregenerator/IrisPregenerator.java @@ -66,8 +66,10 @@ public class IrisPregenerator { private final KSet net; private final ChronoLatch cl; private final ChronoLatch saveLatch = new ChronoLatch(30000); + private final IrisPackBenchmarking benchmarking; public IrisPregenerator(PregenTask task, PregeneratorMethod generator, PregenListener listener) { + benchmarking = IrisPackBenchmarking.getInstance(); this.listener = listenify(listener); cl = new ChronoLatch(5000); generatedRegions = new KSet<>(); @@ -135,7 +137,7 @@ public class IrisPregenerator { double percentage = ((double) generated.get() / (double) totalChunks.get()) * 100; Iris.info("%s: %s of %s (%.0f%%), %s/s ETA: %s", - IrisPackBenchmarking.benchmarkInProgress ? "Benchmarking" : "Pregen", + benchmarking != null ? "Benchmarking" : "Pregen", Form.f(generated.get()), Form.f(totalChunks.get()), percentage, @@ -174,10 +176,10 @@ public class IrisPregenerator { task.iterateRegions((x, z) -> visitRegion(x, z, false)); Iris.info("Pregen took " + Form.duration((long) p.getMilliseconds())); shutdown(); - if (!IrisPackBenchmarking.benchmarkInProgress) { + if (benchmarking == null) { Iris.info(C.IRIS + "Pregen stopped."); } else { - IrisPackBenchmarking.instance.finishedBenchmark(chunksPerSecondHistory); + benchmarking.finishedBenchmark(chunksPerSecondHistory); } } diff --git a/core/src/main/java/com/volmit/iris/core/pregenerator/cache/PregenCacheImpl.java b/core/src/main/java/com/volmit/iris/core/pregenerator/cache/PregenCacheImpl.java index bf48e06e9..b9a3de943 100644 --- a/core/src/main/java/com/volmit/iris/core/pregenerator/cache/PregenCacheImpl.java +++ b/core/src/main/java/com/volmit/iris/core/pregenerator/cache/PregenCacheImpl.java @@ -7,6 +7,7 @@ import com.volmit.iris.Iris; import com.volmit.iris.util.data.Varint; import com.volmit.iris.util.documentation.ChunkCoordinates; import com.volmit.iris.util.documentation.RegionCoordinates; +import com.volmit.iris.util.io.IO; import com.volmit.iris.util.parallel.HyperLock; import lombok.RequiredArgsConstructor; import net.jpountz.lz4.LZ4BlockInputStream; @@ -70,6 +71,7 @@ class PregenCacheImpl implements PregenCache { return new Plate(key, in); } catch (IOException e){ Iris.error("Failed to read pregen cache " + file); + Iris.reportError(e); e.printStackTrace(); return new Plate(key); } @@ -82,10 +84,11 @@ class PregenCacheImpl implements PregenCache { hyperLock.lock(plate.pos.x, plate.pos.z); try { File file = fileForPlate(plate.pos); - try (var out = new DataOutputStream(new LZ4BlockOutputStream(new FileOutputStream(file)))) { - plate.write(out); + try { + IO.write(file, out -> new DataOutputStream(new LZ4BlockOutputStream(out)), plate::write); } catch (IOException e) { Iris.error("Failed to write pregen cache " + file); + Iris.reportError(e); e.printStackTrace(); } } finally { diff --git a/core/src/main/java/com/volmit/iris/core/pregenerator/methods/AsyncPregenMethod.java b/core/src/main/java/com/volmit/iris/core/pregenerator/methods/AsyncPregenMethod.java index 757dfcf8f..0dcfffcfc 100644 --- a/core/src/main/java/com/volmit/iris/core/pregenerator/methods/AsyncPregenMethod.java +++ b/core/src/main/java/com/volmit/iris/core/pregenerator/methods/AsyncPregenMethod.java @@ -157,7 +157,10 @@ public class AsyncPregenMethod implements PregeneratorMethod { } catch (Throwable e) { Iris.warn("Failed to increase worker threads, if you are on paper or a fork of it please increase it manually to " + adjusted); Iris.warn("For more information see https://docs.papermc.io/paper/reference/global-configuration#chunk_system_worker_threads"); - if (e instanceof InvocationTargetException) e.printStackTrace(); + if (e instanceof InvocationTargetException) { + Iris.reportError(e); + e.printStackTrace(); + } } return 0; }); @@ -173,6 +176,7 @@ public class AsyncPregenMethod implements PregeneratorMethod { method.invoke(pool, i); return 0; } catch (Throwable e) { + Iris.reportError(e); Iris.error("Failed to reset worker threads"); e.printStackTrace(); } @@ -201,6 +205,7 @@ public class AsyncPregenMethod implements PregeneratorMethod { }).get(); } catch (InterruptedException ignored) { } catch (Throwable e) { + Iris.reportError(e); e.printStackTrace(); } finally { semaphore.release(); @@ -219,6 +224,7 @@ public class AsyncPregenMethod implements PregeneratorMethod { public void generate(int x, int z, PregenListener listener) { PaperLib.getChunkAtAsync(world, x, z, true, urgent) .exceptionally(e -> { + Iris.reportError(e); e.printStackTrace(); return null; }) diff --git a/core/src/main/java/com/volmit/iris/core/safeguard/IrisSafeguard.java b/core/src/main/java/com/volmit/iris/core/safeguard/IrisSafeguard.java index 5217be9f2..2c9eb9be1 100644 --- a/core/src/main/java/com/volmit/iris/core/safeguard/IrisSafeguard.java +++ b/core/src/main/java/com/volmit/iris/core/safeguard/IrisSafeguard.java @@ -20,5 +20,15 @@ public class IrisSafeguard { Iris.instance.splash(); UtilsSFG.splash(); } + + public static String mode() { + if (unstablemode) { + return "unstable"; + } else if (warningmode) { + return "warning"; + } else { + return "stable"; + } + } } diff --git a/core/src/main/java/com/volmit/iris/core/service/GlobalCacheSVC.java b/core/src/main/java/com/volmit/iris/core/service/GlobalCacheSVC.java index 32d6af184..e8c194974 100644 --- a/core/src/main/java/com/volmit/iris/core/service/GlobalCacheSVC.java +++ b/core/src/main/java/com/volmit/iris/core/service/GlobalCacheSVC.java @@ -23,9 +23,11 @@ public class GlobalCacheSVC implements IrisService { private static final Cache REFERENCE_CACHE = Caffeine.newBuilder().weakValues().build(); private final KMap globalCache = new KMap<>(); private transient boolean lastState; + private static boolean disabled = true; @Override public void onEnable() { + disabled = false; lastState = !IrisSettings.get().getWorld().isGlobalPregenCache(); if (lastState) return; Bukkit.getWorlds().forEach(this::createCache); @@ -33,7 +35,8 @@ public class GlobalCacheSVC implements IrisService { @Override public void onDisable() { - globalCache.values().forEach(PregenCache::write); + disabled = true; + globalCache.qclear((world, cache) -> cache.write()); } @Nullable @@ -99,6 +102,7 @@ public class GlobalCacheSVC implements IrisService { } private static PregenCache createDefault0(String worldName) { + if (disabled) return PregenCache.EMPTY; return PregenCache.create(new File(Bukkit.getWorldContainer(), String.join(File.separator, worldName, "iris", "pregen"))).sync(); } } diff --git a/core/src/main/java/com/volmit/iris/core/tools/IrisPackBenchmarking.java b/core/src/main/java/com/volmit/iris/core/tools/IrisPackBenchmarking.java index d9c6b0bd0..4f39a7527 100644 --- a/core/src/main/java/com/volmit/iris/core/tools/IrisPackBenchmarking.java +++ b/core/src/main/java/com/volmit/iris/core/tools/IrisPackBenchmarking.java @@ -12,7 +12,6 @@ import com.volmit.iris.util.format.Form; import com.volmit.iris.util.io.IO; import com.volmit.iris.util.scheduling.J; import com.volmit.iris.util.scheduling.PrecisionStopwatch; -import lombok.Getter; import org.bukkit.Bukkit; import java.io.File; @@ -24,28 +23,28 @@ import java.util.Collections; public class IrisPackBenchmarking { - @Getter - public static IrisPackBenchmarking instance; - public static boolean benchmarkInProgress = false; + private static final ThreadLocal instance = new ThreadLocal<>(); private final PrecisionStopwatch stopwatch = new PrecisionStopwatch(); private final IrisDimension dimension; private final int radius; private final boolean gui; public IrisPackBenchmarking(IrisDimension dimension, int radius, boolean gui) { - instance = this; this.dimension = dimension; this.radius = radius; this.gui = gui; runBenchmark(); } + public static IrisPackBenchmarking getInstance() { + return instance.get(); + } + private void runBenchmark() { Thread.ofVirtual() .name("PackBenchmarking") .start(() -> { Iris.info("Setting up benchmark environment "); - benchmarkInProgress = true; IO.delete(new File(Bukkit.getWorldContainer(), "benchmark")); createBenchmark(); while (!IrisToolbelt.isIrisWorld(Bukkit.getWorld("benchmark"))) { @@ -59,10 +58,6 @@ public class IrisPackBenchmarking { } - public boolean getBenchmarkInProgress() { - return benchmarkInProgress; - } - public void finishedBenchmark(KList cps) { try { String time = Form.duration((long) stopwatch.getMilliseconds()); @@ -132,13 +127,18 @@ public class IrisPackBenchmarking { } private void startBenchmark() { - IrisToolbelt.pregenerate(PregenTask - .builder() - .gui(gui) - .radiusX(radius) - .radiusZ(radius) - .build(), Bukkit.getWorld("benchmark") - ); + try { + instance.set(this); + IrisToolbelt.pregenerate(PregenTask + .builder() + .gui(gui) + .radiusX(radius) + .radiusZ(radius) + .build(), Bukkit.getWorld("benchmark") + ); + } finally { + instance.remove(); + } } private double calculateAverage(KList list) { diff --git a/core/src/main/java/com/volmit/iris/engine/framework/EnginePlayer.java b/core/src/main/java/com/volmit/iris/engine/framework/EnginePlayer.java index 803819831..2350b3140 100644 --- a/core/src/main/java/com/volmit/iris/engine/framework/EnginePlayer.java +++ b/core/src/main/java/com/volmit/iris/engine/framework/EnginePlayer.java @@ -47,9 +47,7 @@ public class EnginePlayer { } public void tick() { - sample(); - - if (!IrisSettings.get().getWorld().isEffectSystem()) + if (sample() || !IrisSettings.get().getWorld().isEffectSystem()) return; J.a(() -> { @@ -81,22 +79,22 @@ public class EnginePlayer { return M.ms() - lastSample; } - public void sample() { + public boolean sample() { + Location current = player.getLocation().clone(); + if (current.getWorld() != engine.getWorld().realWorld()) + return true; try { - if (ticksSinceLastSample() > 55 && player.getLocation().distanceSquared(lastLocation) > 9 * 9) { - lastLocation = player.getLocation().clone(); + if (ticksSinceLastSample() > 55 && current.distanceSquared(lastLocation) > 9 * 9) { + lastLocation = current; lastSample = M.ms(); - sampleBiomeRegion(); + biome = engine.getBiome(current); + region = engine.getRegion(current); } + return false; } catch (Throwable e) { Iris.reportError(e); } - } - - private void sampleBiomeRegion() { - Location l = player.getLocation(); - biome = engine.getBiome(l); - region = engine.getRegion(l); + return true; } } diff --git a/core/src/main/java/com/volmit/iris/engine/mantle/MantleWriter.java b/core/src/main/java/com/volmit/iris/engine/mantle/MantleWriter.java index 4c32af6d8..b71051472 100644 --- a/core/src/main/java/com/volmit/iris/engine/mantle/MantleWriter.java +++ b/core/src/main/java/com/volmit/iris/engine/mantle/MantleWriter.java @@ -60,8 +60,9 @@ public class MantleWriter implements IObjectPlacer, AutoCloseable { this.x = x; this.z = z; - for (int i = -radius; i <= radius; i++) { - for (int j = -radius; j <= radius; j++) { + int r = radius / 4; + for (int i = -r; i <= r; i++) { + for (int j = -r; j <= r; j++) { cachedChunks.put(Cache.key(i + x, j + z), mantle.getChunk(i + x, j + z).use()); } } @@ -143,7 +144,7 @@ public class MantleWriter implements IObjectPlacer, AutoCloseable { if (cx >= this.x - radius && cx <= this.x + radius && cz >= this.z - radius && cz <= this.z + radius) { - MantleChunk chunk = cachedChunks.get(Cache.key(cx, cz)); + MantleChunk chunk = cachedChunks.computeIfAbsent(Cache.key(cx, cz), k -> mantle.getChunk(cx, cz).use()); if (chunk == null) { Iris.error("Mantle Writer Accessed " + cx + "," + cz + " and came up null (and yet within bounds!)"); @@ -152,6 +153,8 @@ public class MantleWriter implements IObjectPlacer, AutoCloseable { Matter matter = chunk.getOrCreate(y >> 4); matter.slice(matter.getClass(t)).set(x & 15, y & 15, z & 15, t); + } else { + Iris.error("Mantle Writer Accessed chunk out of bounds" + cx + "," + cz); } } @@ -639,9 +642,10 @@ public class MantleWriter implements IObjectPlacer, AutoCloseable { @Override public void close() { - cachedChunks.values().removeIf(c -> { - c.release(); - return true; - }); + var iterator = cachedChunks.values().iterator(); + while (iterator.hasNext()) { + iterator.next().release(); + iterator.remove(); + } } } diff --git a/core/src/main/java/com/volmit/iris/util/collection/KMap.java b/core/src/main/java/com/volmit/iris/util/collection/KMap.java index 01baab32b..4a7f938e0 100644 --- a/core/src/main/java/com/volmit/iris/util/collection/KMap.java +++ b/core/src/main/java/com/volmit/iris/util/collection/KMap.java @@ -23,11 +23,9 @@ import com.volmit.iris.util.function.Consumer2; import com.volmit.iris.util.function.Consumer3; import com.volmit.iris.util.scheduling.Queue; -import java.util.Collections; -import java.util.Comparator; -import java.util.Enumeration; -import java.util.Map; +import java.util.*; import java.util.concurrent.ConcurrentHashMap; +import java.util.function.BiConsumer; import java.util.function.BiFunction; @SuppressWarnings("ALL") @@ -373,6 +371,20 @@ public class KMap extends ConcurrentHashMap { return g; } + public KMap qclear(BiConsumer action) { + final Iterator> it = entrySet().iterator(); + while (it.hasNext()) { + final Map.Entry entry = it.next(); + it.remove(); + try { + action.accept(entry.getKey(), entry.getValue()); + } catch (Throwable e) { + Iris.reportError(e); + } + } + return this; + } + /** * Create a keypair queue * diff --git a/core/src/main/java/com/volmit/iris/util/io/IO.java b/core/src/main/java/com/volmit/iris/util/io/IO.java index 554058244..281f08096 100644 --- a/core/src/main/java/com/volmit/iris/util/io/IO.java +++ b/core/src/main/java/com/volmit/iris/util/io/IO.java @@ -24,9 +24,13 @@ import com.google.gson.JsonObject; import com.google.gson.JsonParser; import com.volmit.iris.Iris; import com.volmit.iris.util.format.Form; +import org.apache.commons.io.function.IOConsumer; +import org.apache.commons.io.function.IOFunction; import java.io.*; import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.StandardCopyOption; import java.security.DigestInputStream; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; @@ -1638,4 +1642,19 @@ public class IO { int ch2 = input2.read(); return (ch2 == -1); } + + public static void write(File file, IOFunction builder, IOConsumer action) throws IOException { + File dir = new File(file.getParentFile(), ".tmp"); + dir.mkdirs(); + dir.deleteOnExit(); + File temp = File.createTempFile("iris",".bin", dir); + try { + try (var out = builder.apply(new FileOutputStream(temp))) { + action.accept(out); + } + Files.move(temp.toPath(), file.toPath(), StandardCopyOption.REPLACE_EXISTING); + } finally { + temp.delete(); + } + } } diff --git a/core/src/main/java/com/volmit/iris/util/mantle/Mantle.java b/core/src/main/java/com/volmit/iris/util/mantle/Mantle.java index 77b849272..9de145a3f 100644 --- a/core/src/main/java/com/volmit/iris/util/mantle/Mantle.java +++ b/core/src/main/java/com/volmit/iris/util/mantle/Mantle.java @@ -34,6 +34,7 @@ import com.volmit.iris.util.documentation.RegionCoordinates; import com.volmit.iris.util.format.C; import com.volmit.iris.util.format.Form; import com.volmit.iris.util.function.Consumer4; +import com.volmit.iris.util.io.IO; import com.volmit.iris.util.math.M; import com.volmit.iris.util.matter.Matter; import com.volmit.iris.util.matter.MatterSlice; @@ -84,7 +85,7 @@ public class Mantle { this.worldHeight = worldHeight; this.ioTrim = new AtomicBoolean(false); this.ioTectonicUnload = new AtomicBoolean(false); - dataFolder.mkdirs(); + new File(dataFolder, ".tmp").mkdirs(); loadedRegions = new KMap<>(); lastUse = new KMap<>(); ioBurst = MultiBurst.burst; @@ -387,6 +388,7 @@ public class Mantle { } })); loadedRegions.clear(); + IO.delete(new File(dataFolder, ".tmp")); try { b.complete(); diff --git a/core/src/main/java/com/volmit/iris/util/mantle/TectonicPlate.java b/core/src/main/java/com/volmit/iris/util/mantle/TectonicPlate.java index 82c259aaf..009993321 100644 --- a/core/src/main/java/com/volmit/iris/util/mantle/TectonicPlate.java +++ b/core/src/main/java/com/volmit/iris/util/mantle/TectonicPlate.java @@ -222,13 +222,16 @@ public class TectonicPlate { */ public void write(File file) throws IOException { PrecisionStopwatch p = PrecisionStopwatch.start(); - File temp = File.createTempFile("iris-tectonic-plate", ".bin"); - try (DataOutputStream dos = new DataOutputStream(new LZ4BlockOutputStream(new FileOutputStream(temp)))) { - write(dos); + File temp = File.createTempFile("iris-tectonic-plate", ".bin", new File(file.getParentFile(), ".tmp")); + try { + try (DataOutputStream dos = new DataOutputStream(new LZ4BlockOutputStream(new FileOutputStream(temp)))) { + write(dos); + } + Files.move(temp.toPath(), file.toPath(), StandardCopyOption.REPLACE_EXISTING); + Iris.debug("Saved Tectonic Plate " + C.DARK_GREEN + file.getName() + C.RED + " in " + Form.duration(p.getMilliseconds(), 2)); + } finally { + temp.delete(); } - Files.move(temp.toPath(), file.toPath(), StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE); - Iris.debug("Saved Tectonic Plate " + C.DARK_GREEN + file.getName() + C.RED + " in " + Form.duration(p.getMilliseconds(), 2)); - temp.delete(); } /** diff --git a/core/src/main/java/com/volmit/iris/util/matter/slices/BlockMatter.java b/core/src/main/java/com/volmit/iris/util/matter/slices/BlockMatter.java index 5322ca5de..15afb4376 100644 --- a/core/src/main/java/com/volmit/iris/util/matter/slices/BlockMatter.java +++ b/core/src/main/java/com/volmit/iris/util/matter/slices/BlockMatter.java @@ -18,10 +18,10 @@ package com.volmit.iris.util.matter.slices; +import com.volmit.iris.util.data.B; import com.volmit.iris.util.data.IrisCustomData; import com.volmit.iris.util.data.palette.Palette; import com.volmit.iris.util.matter.Sliced; -import org.bukkit.Bukkit; import org.bukkit.Material; import org.bukkit.World; import org.bukkit.block.data.BlockData; @@ -63,6 +63,6 @@ public class BlockMatter extends RawMatter { @Override public BlockData readNode(DataInputStream din) throws IOException { - return Bukkit.createBlockData(din.readUTF()); + return B.get(din.readUTF()); } } diff --git a/core/src/main/java/com/volmit/iris/util/scheduling/Looper.java b/core/src/main/java/com/volmit/iris/util/scheduling/Looper.java index e5be20b84..72ba9ff03 100644 --- a/core/src/main/java/com/volmit/iris/util/scheduling/Looper.java +++ b/core/src/main/java/com/volmit/iris/util/scheduling/Looper.java @@ -36,7 +36,6 @@ public abstract class Looper extends Thread { //noinspection BusyWait Thread.sleep(m); } catch (InterruptedException e) { - Iris.reportError(e); break; } catch (Throwable e) { Iris.reportError(e); diff --git a/core/src/main/java/com/volmit/iris/util/sentry/Attachments.java b/core/src/main/java/com/volmit/iris/util/sentry/Attachments.java new file mode 100644 index 000000000..a338ea6ce --- /dev/null +++ b/core/src/main/java/com/volmit/iris/util/sentry/Attachments.java @@ -0,0 +1,41 @@ +package com.volmit.iris.util.sentry; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.volmit.iris.util.collection.KMap; +import io.sentry.Attachment; +import org.bukkit.Bukkit; + +import java.nio.charset.StandardCharsets; +import java.util.concurrent.Callable; + +public class Attachments { + private static final Gson GSON = new GsonBuilder().disableHtmlEscaping().create(); + public static final Attachment PLUGINS = jsonProvider(Attachments::plugins, "plugins.json"); + + public static Attachment json(Object object, String name) { + return new Attachment(GSON.toJson(object).getBytes(StandardCharsets.UTF_8), name, "application/json", "event.attachment", true); + } + + public static Attachment jsonProvider(Callable object, String name) { + return new Attachment(() -> GSON.toJson(object.call()).getBytes(StandardCharsets.UTF_8), name, "application/json", "event.attachment", true); + } + + private static KMap plugins() { + KMap enabled = new KMap<>(); + KMap disabled = new KMap<>(); + + var pm = Bukkit.getPluginManager(); + for (var plugin : pm.getPlugins()) { + if (plugin.isEnabled()) { + enabled.put(plugin.getName(), plugin.getDescription().getVersion()); + } else { + disabled.put(plugin.getName(), plugin.getDescription().getVersion()); + } + } + + return new KMap() + .qput("enabled", enabled) + .qput("disabled", disabled); + } +} diff --git a/core/src/main/java/com/volmit/iris/util/sentry/IrisLogger.java b/core/src/main/java/com/volmit/iris/util/sentry/IrisLogger.java new file mode 100644 index 000000000..e51d407c5 --- /dev/null +++ b/core/src/main/java/com/volmit/iris/util/sentry/IrisLogger.java @@ -0,0 +1,47 @@ +package com.volmit.iris.util.sentry; + +import com.volmit.iris.Iris; +import io.sentry.ILogger; +import io.sentry.SentryLevel; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.io.PrintWriter; +import java.io.StringWriter; + +public class IrisLogger implements ILogger { + @Override + public void log(@NotNull SentryLevel level, @NotNull String message, @Nullable Object... args) { + Iris.msg(String.format("%s: %s", level, String.format(message, args))); + } + + @Override + public void log(@NotNull SentryLevel level, @NotNull String message, @Nullable Throwable throwable) { + if (throwable == null) { + log(level, message); + } else { + Iris.msg(String.format("%s: %s\n%s", level, String.format(message, throwable), captureStackTrace(throwable))); + } + } + + @Override + public void log(@NotNull SentryLevel level, @Nullable Throwable throwable, @NotNull String message, @Nullable Object... args) { + if (throwable == null) { + log(level, message, args); + } else { + Iris.msg(String.format("%s: %s\n%s", level, String.format(message, throwable), captureStackTrace(throwable))); + } + } + + @Override + public boolean isEnabled(@Nullable SentryLevel level) { + return true; + } + + private @NotNull String captureStackTrace(@NotNull Throwable throwable) { + StringWriter stringWriter = new StringWriter(); + PrintWriter printWriter = new PrintWriter(stringWriter); + throwable.printStackTrace(printWriter); + return stringWriter.toString(); + } +}