This commit is contained in:
Brian Neumann-Fopiano
2026-02-16 06:07:25 -05:00
parent 0fd8bcc488
commit 440c1bec6c
32 changed files with 1009 additions and 394 deletions
+7
View File
@@ -2,6 +2,7 @@ import io.github.slimjar.func.slimjarHelper
import io.github.slimjar.resolver.data.Mirror
import org.ajoberstar.grgit.Grgit
import org.gradle.jvm.toolchain.JavaLanguageVersion
import org.gradle.jvm.tasks.Jar
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
import java.net.URI
@@ -157,6 +158,8 @@ slimJar {
relocate("com.github.benmanes.caffeine", "$lib.caffeine")
}
val embeddedAgentJar = project(":core:agent").tasks.named<Jar>("jar")
tasks {
/**
* We need parameter meta for the decree command system
@@ -182,10 +185,14 @@ tasks {
}
shadowJar {
dependsOn(embeddedAgentJar)
mergeServiceFiles()
//minimize()
relocate("io.github.slimjar", "$lib.slimjar")
exclude("modules/loader-agent.isolated-jar")
from(embeddedAgentJar.map { it.archiveFile }) {
rename { "agent.jar" }
}
}
sentryCollectSourcesJava {
+49 -16
View File
@@ -98,8 +98,10 @@ public class Iris extends VolmitPlugin implements Listener {
static {
try {
InstanceState.updateInstanceId();
} catch (Throwable ignored) {
} catch (Throwable ex) {
System.err.println("[Iris] Failed to update instance id: " + ex.getClass().getSimpleName()
+ (ex.getMessage() == null ? "" : " - " + ex.getMessage()));
ex.printStackTrace();
}
}
@@ -135,8 +137,12 @@ public class Iris extends VolmitPlugin implements Listener {
if (slicedClass == null || i.isAnnotationPresent(slicedClass)) {
try {
v.add(i.getDeclaredConstructor().newInstance());
} catch (Throwable ignored) {
} catch (Throwable ex) {
Iris.warn("Skipped class initialization for %s: %s%s",
i.getName(),
ex.getClass().getSimpleName(),
ex.getMessage() == null ? "" : " - " + ex.getMessage());
Iris.reportError(ex);
}
}
}
@@ -152,8 +158,12 @@ public class Iris extends VolmitPlugin implements Listener {
if (slicedClass == null || i.isAnnotationPresent(slicedClass)) {
try {
v.add(i);
} catch (Throwable ignored) {
} catch (Throwable ex) {
Iris.warn("Skipped class discovery entry for %s: %s%s",
i.getName(),
ex.getClass().getSimpleName(),
ex.getMessage() == null ? "" : " - " + ex.getMessage());
Iris.reportError(ex);
}
}
}
@@ -181,8 +191,9 @@ public class Iris extends VolmitPlugin implements Listener {
} catch (Throwable e) {
try {
instance.getLogger().info(instance.getTag() + string.replaceAll("(<([^>]+)>)", ""));
} catch (Throwable ignored1) {
} catch (Throwable inner) {
System.err.println("[Iris] Failed to emit log message: " + inner.getMessage());
inner.printStackTrace(System.err);
}
}
}
@@ -307,8 +318,7 @@ public class Iris extends VolmitPlugin implements Listener {
@SuppressWarnings("deprecation")
public static void later(NastyRunnable object) {
try {
Bukkit.getScheduler().scheduleAsyncDelayedTask(instance, () ->
{
J.a(() -> {
try {
object.run();
} catch (Throwable e) {
@@ -316,8 +326,10 @@ public class Iris extends VolmitPlugin implements Listener {
Iris.reportError(e);
}
}, RNG.r.i(100, 1200));
} catch (IllegalPluginAccessException ignored) {
} catch (IllegalPluginAccessException ex) {
Iris.verbose("Skipping deferred task registration because plugin access is unavailable: "
+ ex.getClass().getSimpleName()
+ (ex.getMessage() == null ? "" : " - " + ex.getMessage()));
}
}
@@ -476,7 +488,12 @@ public class Iris extends VolmitPlugin implements Listener {
public void addShutdownHook() {
if (shutdownHook != null) {
try {
Runtime.getRuntime().removeShutdownHook(shutdownHook);
} catch (IllegalStateException ex) {
Iris.debug("Skipping shutdown hook replacement because JVM shutdown is already in progress.");
return;
}
}
shutdownHook = new Thread(() -> {
Bukkit.getWorlds()
@@ -487,9 +504,15 @@ public class Iris extends VolmitPlugin implements Listener {
MultiBurst.burst.close();
MultiBurst.ioBurst.close();
if (services != null) {
services.clear();
});
}
}, "Iris-ShutdownHook");
try {
Runtime.getRuntime().addShutdownHook(shutdownHook);
} catch (IllegalStateException ex) {
Iris.debug("Skipping shutdown hook registration because JVM shutdown is already in progress.");
}
}
public void checkForBukkitWorlds(Predicate<String> filter) {
@@ -563,12 +586,14 @@ public class Iris extends VolmitPlugin implements Listener {
public void onDisable() {
if (IrisSafeguard.isForceShutdown()) return;
if (services != null) {
services.values().forEach(IrisService::onDisable);
}
if (configHotloadEngine != null) {
configHotloadEngine.clear();
configHotloadEngine = null;
}
Bukkit.getScheduler().cancelTasks(this);
J.cancelPluginTasks();
HandlerList.unregisterAll((Plugin) this);
postShutdown.forEach(Runnable::run);
super.onDisable();
@@ -632,7 +657,12 @@ public class Iris extends VolmitPlugin implements Listener {
try {
return IO.readAll(file);
} catch (Throwable ignored) {
} catch (Throwable ex) {
Iris.warn("Failed to read settings file %s: %s%s",
file.getAbsolutePath(),
ex.getClass().getSimpleName(),
ex.getMessage() == null ? "" : " - " + ex.getMessage());
Iris.reportError(ex);
return null;
}
}
@@ -765,7 +795,10 @@ public class Iris extends VolmitPlugin implements Listener {
JsonObject json = JsonParser.parseReader(r).getAsJsonObject();
if (json.has("version"))
version = json.get("version").getAsString();
} catch (IOException | JsonParseException ignored) {
} catch (IOException | JsonParseException ex) {
Iris.verbose("Failed to read dimension version metadata for " + dimName + ": "
+ ex.getClass().getSimpleName()
+ (ex.getMessage() == null ? "" : " - " + ex.getMessage()));
}
Iris.info(" " + dimName + " v" + version);
}
@@ -207,7 +207,6 @@ public class IrisSettings {
@Data
public static class IrisSettingsGeneral {
public boolean DoomsdayAnnihilationSelfDestructMode = false;
public boolean commandSounds = true;
public boolean debug = false;
public boolean dumpMantleOnError = false;
@@ -104,7 +104,13 @@ public class ServerConfigurator {
}
public static boolean installDataPacks(boolean fullInstall) {
return installDataPacks(DataVersion.getDefault(), fullInstall);
IDataFixer fixer = DataVersion.getDefault();
if (fixer == null) {
DataVersion fallback = DataVersion.getLatest();
Iris.warn("Primary datapack fixer was null, forcing latest fixer: " + fallback.getVersion());
fixer = fallback.get();
}
return installDataPacks(fixer, fullInstall);
}
public static boolean installDataPacks(IDataFixer fixer, boolean fullInstall) {
@@ -39,7 +39,6 @@ import art.arcane.volmlib.util.io.IO;
import art.arcane.iris.util.misc.ServerProperties;
import art.arcane.iris.util.plugin.VolmitSender;
import art.arcane.iris.util.scheduling.J;
import lombok.SneakyThrows;
import org.bukkit.Bukkit;
import org.bukkit.World;
import org.bukkit.configuration.ConfigurationSection;
@@ -120,6 +119,13 @@ public class CommandIris implements DecreeExecutor {
return;
}
if (J.isFolia()) {
if (stageFoliaWorldCreation(name, dimension, seed, main)) {
sender().sendMessage(C.GREEN + "World staging completed. Restart the server to generate/load \"" + name + "\".");
}
return;
}
try {
worldCreation = true;
IrisToolbelt.createWorld()
@@ -147,21 +153,99 @@ public class CommandIris implements DecreeExecutor {
if (main) sender().sendMessage(C.GREEN + "Your world will automatically be set as the main world when the server restarts.");
}
@SneakyThrows
private void updateMainWorld(String newName) {
private boolean updateMainWorld(String newName) {
try {
File worlds = Bukkit.getWorldContainer();
var data = ServerProperties.DATA;
try (var in = new FileInputStream(ServerProperties.SERVER_PROPERTIES)) {
data.load(in);
}
File oldWorldFolder = new File(worlds, ServerProperties.LEVEL_NAME);
File newWorldFolder = new File(worlds, newName);
if (!newWorldFolder.exists() && !newWorldFolder.mkdirs()) {
Iris.warn("Could not create target main world folder: " + newWorldFolder.getAbsolutePath());
}
for (String sub : List.of("datapacks", "playerdata", "advancements", "stats")) {
IO.copyDirectory(new File(worlds, ServerProperties.LEVEL_NAME + "/" + sub).toPath(), new File(worlds, newName + "/" + sub).toPath());
File source = new File(oldWorldFolder, sub);
if (!source.exists()) {
continue;
}
IO.copyDirectory(source.toPath(), new File(newWorldFolder, sub).toPath());
}
data.setProperty("level-name", newName);
try (var out = new FileOutputStream(ServerProperties.SERVER_PROPERTIES)) {
data.store(out, null);
}
return true;
} catch (Throwable e) {
Iris.error("Failed to update server.properties main world to \"" + newName + "\"");
Iris.reportError(e);
return false;
}
}
private boolean stageFoliaWorldCreation(String name, IrisDimension dimension, long seed, boolean main) {
sender().sendMessage(C.YELLOW + "Runtime world creation is disabled on Folia.");
sender().sendMessage(C.YELLOW + "Preparing world files and bukkit.yml for next startup...");
File worldFolder = new File(Bukkit.getWorldContainer(), name);
IrisDimension installed = Iris.service(StudioSVC.class).installIntoWorld(sender(), dimension.getLoadKey(), worldFolder);
if (installed == null) {
sender().sendMessage(C.RED + "Failed to stage world files for dimension \"" + dimension.getLoadKey() + "\".");
return false;
}
if (!registerWorldInBukkitYml(name, dimension.getLoadKey(), seed)) {
return false;
}
if (main) {
if (updateMainWorld(name)) {
sender().sendMessage(C.GREEN + "Updated server.properties level-name to \"" + name + "\".");
} else {
sender().sendMessage(C.RED + "World was staged, but failed to update server.properties main world.");
return false;
}
}
sender().sendMessage(C.GREEN + "Staged Iris world \"" + name + "\" with generator Iris:" + dimension.getLoadKey() + " and seed " + seed + ".");
if (main) {
sender().sendMessage(C.GREEN + "This world is now configured as main for next restart.");
}
return true;
}
private boolean registerWorldInBukkitYml(String worldName, String dimension, Long seed) {
YamlConfiguration yml = YamlConfiguration.loadConfiguration(BUKKIT_YML);
ConfigurationSection worlds = yml.getConfigurationSection("worlds");
if (worlds == null) {
worlds = yml.createSection("worlds");
}
ConfigurationSection worldSection = worlds.getConfigurationSection(worldName);
if (worldSection == null) {
worldSection = worlds.createSection(worldName);
}
String generator = "Iris:" + dimension;
worldSection.set("generator", generator);
if (seed != null) {
worldSection.set("seed", seed);
}
try {
yml.save(BUKKIT_YML);
Iris.info("Registered \"" + worldName + "\" in bukkit.yml");
return true;
} catch (IOException e) {
sender().sendMessage(C.RED + "Failed to update bukkit.yml: " + e.getMessage());
Iris.error("Failed to update bukkit.yml!");
Iris.reportError(e);
return false;
}
}
@Decree(description = "Teleport to another world", aliases = {"tp"}, sync = true)
@@ -439,22 +523,23 @@ public class CommandIris implements DecreeExecutor {
sender().sendMessage(C.GOLD + world + " is not an iris world.");
return;
}
sender().sendMessage(C.GREEN + "Loading world: " + world);
YamlConfiguration yml = YamlConfiguration.loadConfiguration(BUKKIT_YML);
String gen = "Iris:" + dimension;
ConfigurationSection section = yml.contains("worlds") ? yml.getConfigurationSection("worlds") : yml.createSection("worlds");
if (!section.contains(world)) {
section.createSection(world).set("generator", gen);
try {
yml.save(BUKKIT_YML);
Iris.info("Registered \"" + world + "\" in bukkit.yml");
} catch (IOException e) {
Iris.error("Failed to update bukkit.yml!");
e.printStackTrace();
if (dimension == null) {
sender().sendMessage(C.RED + "Could not determine Iris dimension for " + world + ".");
return;
}
sender().sendMessage(C.GREEN + "Loading world: " + world);
if (!registerWorldInBukkitYml(world, dimension, null)) {
return;
}
if (J.isFolia()) {
sender().sendMessage(C.YELLOW + "Folia cannot load new worlds at runtime. Restart the server to load \"" + world + "\".");
return;
}
Iris.instance.checkForBukkitWorlds(world::equals);
sender().sendMessage(C.GREEN + world + " loaded successfully.");
}
@@ -498,7 +583,12 @@ public class CommandIris implements DecreeExecutor {
for (String key : data.getDimensionLoader().getPossibleKeys()) {
options.add(key);
}
} catch (Throwable ignored) {
} catch (Throwable ex) {
Iris.warn("Failed to read dimension keys from pack %s: %s%s",
pack.getName(),
ex.getClass().getSimpleName(),
ex.getMessage() == null ? "" : " - " + ex.getMessage());
Iris.reportError(ex);
}
}
}
@@ -115,6 +115,13 @@ public class CommandStudio implements DecreeExecutor {
IrisDimension dimension,
@Param(defaultValue = "1337", description = "The seed to generate the studio with", aliases = "s")
long seed) {
if (J.isFolia()) {
sender().sendMessage(C.RED + "Studio world opening is disabled on Folia.");
sender().sendMessage(C.YELLOW + "Folia does not currently support runtime world creation via Bukkit.createWorld().");
sender().sendMessage(C.YELLOW + "Use Paper/Purpur for Studio mode, or preconfigure worlds and restart.");
return;
}
sender().sendMessage(C.GREEN + "Opening studio for the \"" + dimension.getName() + "\" pack (seed: " + seed + ")");
Iris.service(StudioSVC.class).open(sender(), seed, dimension.getLoadKey());
}
@@ -329,10 +336,10 @@ public class CommandStudio implements DecreeExecutor {
var player = player();
var engine = engine();
ta.set(Bukkit.getScheduler().scheduleSyncRepeatingTask(Iris.instance, () ->
ta.set(J.sr(() ->
{
if (!player.getOpenInventory().getType().equals(InventoryType.CHEST)) {
Bukkit.getScheduler().cancelTask(ta.get());
J.csr(ta.get());
sender.sendMessage(C.GREEN + "Opened inventory!");
return;
}
@@ -342,7 +349,7 @@ public class CommandStudio implements DecreeExecutor {
}
engine.addItems(true, inv, new RNG(RNG.r.imax()), tables, InventorySlotType.STORAGE, player.getWorld(), player.getLocation().getBlockX(), player.getLocation().getBlockY(), player.getLocation().getBlockZ(), 1);
}, 0, fast ? 5 : 35));
}, fast ? 5 : 35));
sender().sendMessage(C.GREEN + "Opening inventory now!");
player().openInventory(inv);
@@ -93,7 +93,9 @@ public class INMS {
Iris.reportError(e);
e.printStackTrace();
}
} catch (ClassNotFoundException|NoClassDefFoundError classNotFoundException) {}
} catch (ClassNotFoundException|NoClassDefFoundError classNotFoundException) {
Iris.warn("Failed to load NMS binding class for " + code + ": " + classNotFoundException.getMessage());
}
Iris.info("Craftbukkit " + code + " <-> " + NMSBinding1X.class.getSimpleName() + " Successfully Bound");
Iris.warn("Note: Some features of Iris may not work the same since you are on an unsupported version of Minecraft.");
@@ -1,5 +1,6 @@
package art.arcane.iris.core.nms.datapack;
import art.arcane.iris.Iris;
import art.arcane.iris.core.nms.INMS;
import art.arcane.iris.core.nms.datapack.v1192.DataFixerV1192;
import art.arcane.iris.core.nms.datapack.v1206.DataFixerV1206;
@@ -36,7 +37,21 @@ public enum DataVersion {
}
public static IDataFixer getDefault() {
return INMS.get().getDataVersion().get();
DataVersion version = INMS.get().getDataVersion();
if (version == null || version == UNSUPPORTED) {
DataVersion fallback = getLatest();
Iris.warn("Unsupported datapack version mapping detected, falling back to latest fixer: " + fallback.getVersion());
return fallback.get();
}
IDataFixer fixer = version.get();
if (fixer == null) {
DataVersion fallback = getLatest();
Iris.warn("Null datapack fixer for " + version.getVersion() + ", falling back to latest fixer: " + fallback.getVersion());
return fallback.get();
}
return fixer;
}
public static DataVersion getLatest() {
@@ -22,7 +22,6 @@ import org.bukkit.World;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.world.WorldUnloadEvent;
import org.bukkit.scheduler.BukkitRunnable;
import java.io.File;
import java.io.FileWriter;
@@ -235,16 +234,13 @@ public class DeepSearchPregenerator extends Thread implements Listener {
}
save();
jobs.remove(world.getName());
new BukkitRunnable() {
@Override
public void run() {
while (deepFile.exists()){
J.a(() -> {
while (deepFile.exists()) {
deepFile.delete();
J.sleep(1000);
}
Iris.info("DeepSearch: " + C.IRIS + world.getName() + C.BLUE + " File deleted and instance closed.");
}
}.runTaskLater(Iris.instance, 20L);
}, 20);
} catch (Exception e) {
Iris.error("Failed to shutdown DeepSearch for " + world.getName());
e.printStackTrace();
@@ -271,4 +267,3 @@ public class DeepSearchPregenerator extends Thread implements Listener {
boolean paused = false;
}
}
@@ -19,7 +19,6 @@ import org.bukkit.World;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.world.WorldUnloadEvent;
import org.bukkit.scheduler.BukkitRunnable;
import java.io.File;
import java.io.IOException;
@@ -165,7 +164,10 @@ public class LazyPregenerator extends Thread implements Listener {
}
try {
latch.await();
} catch (InterruptedException ignored) {}
} catch (InterruptedException ex) {
Thread.currentThread().interrupt();
Iris.verbose("Lazy pregenerator worker interrupted while waiting for chunk " + chunk + ".");
}
lazyGeneratedChunks.addAndGet(1);
});
}
@@ -257,16 +259,13 @@ public class LazyPregenerator extends Thread implements Listener {
}
save();
jobs.remove(world.getName());
new BukkitRunnable() {
@Override
public void run() {
while (lazyFile.exists()){
J.a(() -> {
while (lazyFile.exists()) {
lazyFile.delete();
J.sleep(1000);
}
Iris.info("LazyGen: " + C.IRIS + world.getName() + C.BLUE + " File deleted and instance closed.");
}
}.runTaskLater(Iris.instance, 20L);
}, 20);
} catch (Exception e) {
Iris.error("Failed to shutdown Lazygen for " + world.getName());
e.printStackTrace();
@@ -26,7 +26,6 @@ import org.bukkit.World;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.world.WorldUnloadEvent;
import org.bukkit.scheduler.BukkitRunnable;
import org.checkerframework.checker.units.qual.N;
import java.io.File;
@@ -233,7 +232,9 @@ public class TurboPregenerator extends Thread implements Listener {
});
try {
latch.await();
} catch (InterruptedException ignored) {
} catch (InterruptedException ex) {
Thread.currentThread().interrupt();
Iris.verbose("Turbo pregenerator worker interrupted while waiting for chunk " + chunk + ".");
}
turboGeneratedChunks.addAndGet(1);
});
@@ -321,16 +322,13 @@ public class TurboPregenerator extends Thread implements Listener {
}
save();
jobs.remove(world.getName());
new BukkitRunnable() {
@Override
public void run() {
J.a(() -> {
while (turboFile.exists()) {
turboFile.delete();
J.sleep(1000);
}
Iris.info("turboGen: " + C.IRIS + world.getName() + C.BLUE + " File deleted and instance closed.");
}
}.runTaskLater(Iris.instance, 20L);
}, 20);
} catch (Exception e) {
Iris.error("Failed to shutdown turbogen for " + world.getName());
e.printStackTrace();
@@ -23,7 +23,6 @@ import art.arcane.iris.core.IrisSettings;
import art.arcane.iris.core.loader.IrisData;
import art.arcane.iris.core.tools.IrisToolbelt;
import art.arcane.volmlib.util.board.Board;
import art.arcane.volmlib.util.board.BoardManager;
import art.arcane.volmlib.util.board.BoardProvider;
import art.arcane.volmlib.util.board.BoardSettings;
import art.arcane.volmlib.util.board.ScoreDirection;
@@ -43,40 +42,43 @@ import org.bukkit.event.player.PlayerQuitEvent;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
public class BoardSVC implements IrisService, BoardProvider {
private final KMap<Player, PlayerBoard> boards = new KMap<>();
private ScheduledExecutorService executor;
private BoardManager<Board> manager;
private BoardSettings settings;
private boolean boardEnabled;
@Override
public void onEnable() {
executor = Executors.newScheduledThreadPool(0, Thread.ofVirtual().factory());
manager = new BoardManager<>(Iris.instance, BoardSettings.builder()
boardEnabled = true;
settings = BoardSettings.builder()
.boardProvider(this)
.scoreDirection(ScoreDirection.DOWN)
.build(), Board::new);
.build();
for (Player player : Iris.instance.getServer().getOnlinePlayers()) {
J.runEntity(player, () -> updatePlayer(player));
}
}
@Override
public void onDisable() {
executor.shutdownNow();
manager.onDisable();
boardEnabled = false;
for (PlayerBoard board : new ArrayList<>(boards.values())) {
board.cancel();
}
boards.clear();
settings = null;
}
@EventHandler
public void on(PlayerChangedWorldEvent e) {
J.s(() -> updatePlayer(e.getPlayer()));
J.runEntity(e.getPlayer(), () -> updatePlayer(e.getPlayer()));
}
@EventHandler
public void on(PlayerJoinEvent e) {
J.s(() -> updatePlayer(e.getPlayer()));
J.runEntity(e.getPlayer(), () -> updatePlayer(e.getPlayer()));
}
@EventHandler
@@ -85,16 +87,34 @@ public class BoardSVC implements IrisService, BoardProvider {
}
public void updatePlayer(Player p) {
if (!boardEnabled || settings == null) {
return;
}
if (!J.isOwnedByCurrentRegion(p)) {
J.runEntity(p, () -> updatePlayer(p));
return;
}
if (IrisToolbelt.isIrisStudioWorld(p.getWorld())) {
manager.remove(p);
manager.setup(p);
boards.computeIfAbsent(p, PlayerBoard::new);
} else remove(p);
}
private void remove(Player player) {
manager.remove(player);
if (player == null) {
return;
}
if (!J.isOwnedByCurrentRegion(player)) {
J.runEntity(player, () -> remove(player));
return;
}
var board = boards.remove(player);
if (board != null) board.task.cancel(true);
if (board != null) {
board.cancel();
}
}
@Override
@@ -104,27 +124,60 @@ public class BoardSVC implements IrisService, BoardProvider {
@Override
public List<String> getLines(Player player) {
return boards.computeIfAbsent(player, PlayerBoard::new).lines;
PlayerBoard board = boards.get(player);
if (board == null) {
return List.of();
}
return board.lines;
}
@Data
public class PlayerBoard {
private final Player player;
private final ScheduledFuture<?> task;
private final Board board;
private volatile List<String> lines;
private volatile boolean cancelled;
public PlayerBoard(Player player) {
this.player = player;
this.board = new Board(player, settings);
this.lines = new ArrayList<>();
this.task = executor.scheduleAtFixedRate(this::tick, 0, 1, TimeUnit.SECONDS);
this.cancelled = false;
schedule(0);
}
private void schedule(int delayTicks) {
if (cancelled || !boardEnabled || !player.isOnline()) {
return;
}
J.runEntity(player, this::tick, delayTicks);
}
private void tick() {
if (cancelled || !boardEnabled || !player.isOnline()) {
return;
}
if (!IrisToolbelt.isIrisStudioWorld(player.getWorld())) {
boards.remove(player);
cancel();
return;
}
if (!Iris.service(StudioSVC.class).isProjectOpen()) {
board.update();
schedule(20);
return;
}
update();
board.update();
schedule(20);
}
public void cancel() {
cancelled = true;
J.runEntity(player, board::remove);
}
public void update() {
@@ -24,7 +24,7 @@ import art.arcane.iris.core.edit.BukkitBlockEditor;
import art.arcane.volmlib.util.collection.KMap;
import art.arcane.volmlib.util.math.M;
import art.arcane.iris.util.plugin.IrisService;
import org.bukkit.Bukkit;
import art.arcane.iris.util.scheduling.J;
import org.bukkit.World;
import org.bukkit.block.Biome;
import org.bukkit.block.data.BlockData;
@@ -33,16 +33,21 @@ import org.bukkit.event.world.WorldUnloadEvent;
public class EditSVC implements IrisService {
private KMap<World, BlockEditor> editors;
private int updateTaskId = -1;
public static boolean deletingWorld = false;
@Override
public void onEnable() {
this.editors = new KMap<>();
Bukkit.getScheduler().scheduleSyncRepeatingTask(Iris.instance, this::update, 1000, 1000);
updateTaskId = J.sr(this::update, 1000);
}
@Override
public void onDisable() {
if (updateTaskId != -1) {
J.csr(updateTaskId);
updateTaskId = -1;
}
flushNow();
}
@@ -24,6 +24,7 @@ import org.bukkit.event.world.WorldLoadEvent;
import org.bukkit.event.world.WorldUnloadEvent;
import org.jetbrains.annotations.Nullable;
import java.util.Locale;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
@@ -231,6 +232,20 @@ public class IrisEngineSVC implements IrisService {
updateTicker.start();
}
private static boolean isMantleClosed(Throwable throwable) {
Throwable current = throwable;
while (current != null) {
String message = current.getMessage();
if (message != null && message.toLowerCase(Locale.ROOT).contains("mantle is closed")) {
return true;
}
current = current.getCause();
}
return false;
}
private final class Registered {
private final String name;
private final PlatformChunkGenerator access;
@@ -261,12 +276,19 @@ public class IrisEngineSVC implements IrisService {
if (trimmer == null || trimmer.isDone() || trimmer.isCancelled()) {
trimmer = service.scheduleAtFixedRate(() -> {
Engine engine = getEngine();
if (engine == null || !engine.getMantle().getMantle().shouldReduce(engine))
if (engine == null
|| engine.isClosed()
|| engine.getMantle().getMantle().isClosed()
|| !engine.getMantle().getMantle().shouldReduce(engine))
return;
try {
engine.getMantle().trim(tectonicLimit());
} catch (Throwable e) {
if (isMantleClosed(e)) {
close();
return;
}
Iris.reportError(e);
Iris.error("EngineSVC: Failed to trim for " + name);
e.printStackTrace();
@@ -277,7 +299,10 @@ public class IrisEngineSVC implements IrisService {
if (unloader == null || unloader.isDone() || unloader.isCancelled()) {
unloader = service.scheduleAtFixedRate(() -> {
Engine engine = getEngine();
if (engine == null || !engine.getMantle().getMantle().shouldReduce(engine))
if (engine == null
|| engine.isClosed()
|| engine.getMantle().getMantle().isClosed()
|| !engine.getMantle().getMantle().shouldReduce(engine))
return;
try {
@@ -287,6 +312,10 @@ public class IrisEngineSVC implements IrisService {
Iris.debug(C.GOLD + "Unloaded " + C.YELLOW + count + " TectonicPlates in " + C.RED + Form.duration(System.currentTimeMillis() - unloadStart, 2));
}
} catch (Throwable e) {
if (isMantleClosed(e)) {
close();
return;
}
Iris.reportError(e);
Iris.error("EngineSVC: Failed to unload for " + name);
e.printStackTrace();
@@ -22,7 +22,6 @@ import art.arcane.iris.Iris;
import art.arcane.iris.util.plugin.IrisService;
import art.arcane.iris.util.scheduling.J;
import lombok.Getter;
import org.bukkit.Bukkit;
import org.bukkit.block.Block;
import org.bukkit.block.data.BlockData;
@@ -71,7 +70,7 @@ public class ObjectSVC implements IrisService {
*/
private void revert(Map<Block, BlockData> blocks) {
Iterator<Map.Entry<Block, BlockData>> it = blocks.entrySet().iterator();
Bukkit.getScheduler().runTask(Iris.instance, () -> {
J.s(() -> {
int amount = 0;
while (it.hasNext()) {
Map.Entry<Block, BlockData> entry = it.next();
@@ -341,8 +341,8 @@ public class StudioSVC implements IrisService {
});
} catch (Exception e) {
Iris.reportError(e);
sender.sendMessage("Error when creating studio world:");
e.printStackTrace();
sender.sendMessage("Failed to open studio world: " + e.getMessage());
Iris.error("Studio world creation failed: " + e.getMessage());
}
}
@@ -114,6 +114,10 @@ public class IrisCreator {
throw new IrisException("You cannot invoke create() on the main thread.");
}
if (J.isFolia()) {
throw new IrisException("Folia does not support runtime world creation via Bukkit.createWorld(). Configure worlds before startup and restart the server.");
}
IrisDimension d = IrisToolbelt.getDimension(dimension());
if (d == null) {
@@ -171,6 +175,9 @@ public class IrisCreator {
world = J.sfut(() -> INMS.get().createWorld(wc)).get();
} catch (Throwable e) {
done.set(true);
if (containsCreateWorldUnsupportedOperation(e)) {
throw new IrisException("Runtime world creation is not supported on this server variant. Configure worlds before startup and restart the server.", e);
}
throw new IrisException("Failed to create world!", e);
}
@@ -226,6 +233,22 @@ public class IrisCreator {
return world;
}
private static boolean containsCreateWorldUnsupportedOperation(Throwable throwable) {
Throwable cursor = throwable;
while (cursor != null) {
if (cursor instanceof UnsupportedOperationException) {
for (StackTraceElement element : cursor.getStackTrace()) {
if ("org.bukkit.craftbukkit.CraftServer".equals(element.getClassName())
&& "createWorld".equals(element.getMethodName())) {
return true;
}
}
}
cursor = cursor.getCause();
}
return false;
}
private void addToBukkitYml() {
YamlConfiguration yml = YamlConfiguration.loadConfiguration(BUKKIT_YML);
String gen = "Iris:" + dimension;
@@ -62,7 +62,6 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@@ -192,38 +191,85 @@ public class IrisWorldManager extends EngineAssignedWorldManager {
private void discoverChunks() {
var mantle = getEngine().getMantle().getMantle();
for (Player i : getEngine().getWorld().realWorld().getPlayers()) {
int r = 1;
World world = getEngine().getWorld().realWorld();
if (world == null) {
return;
}
for (int x = -r; x <= r; x++) {
for (int z = -r; z <= r; z++) {
mantle.getChunk(i.getLocation().getChunk()).flag(MantleFlag.DISCOVERED, true);
J.s(() -> {
for (Player player : world.getPlayers()) {
if (player == null || !player.isOnline()) {
continue;
}
J.runEntity(player, () -> {
int centerX = player.getLocation().getBlockX() >> 4;
int centerZ = player.getLocation().getBlockZ() >> 4;
int radius = 1;
for (int x = -radius; x <= radius; x++) {
for (int z = -radius; z <= radius; z++) {
mantle.getChunk(centerX + x, centerZ + z).flag(MantleFlag.DISCOVERED, true);
}
}
});
}
});
}
private void updateChunks() {
for (Player i : getEngine().getWorld().realWorld().getPlayers()) {
int r = 1;
Chunk c = i.getLocation().getChunk();
for (int x = -r; x <= r; x++) {
for (int z = -r; z <= r; z++) {
if (c.getWorld().isChunkLoaded(c.getX() + x, c.getZ() + z) && Chunks.isSafe(getEngine().getWorld().realWorld(), c.getX() + x, c.getZ() + z)) {
if (IrisSettings.get().getWorld().isPostLoadBlockUpdates()) {
getEngine().updateChunk(c.getWorld().getChunkAt(c.getX() + x, c.getZ() + z));
World world = getEngine().getWorld().realWorld();
if (world == null) {
return;
}
if (IrisSettings.get().getWorld().isMarkerEntitySpawningSystem()) {
Chunk cx = getEngine().getWorld().realWorld().getChunkAt(c.getX() + x, c.getZ() + z);
int finalX = c.getX() + x;
int finalZ = c.getZ() + z;
J.a(() -> getMantle().raiseFlag(finalX, finalZ, MantleFlag.INITIAL_SPAWNED_MARKER,
() -> {
J.a(() -> spawnIn(cx, true), RNG.r.i(5, 200));
getSpawnersFromMarkers(cx).forEach((blockf, spawners) -> {
J.s(() -> {
for (Player player : world.getPlayers()) {
if (player == null || !player.isOnline()) {
continue;
}
J.runEntity(player, () -> {
int centerX = player.getLocation().getBlockX() >> 4;
int centerZ = player.getLocation().getBlockZ() >> 4;
int radius = 1;
for (int x = -radius; x <= radius; x++) {
for (int z = -radius; z <= radius; z++) {
int targetX = centerX + x;
int targetZ = centerZ + z;
J.runRegion(world, targetX, targetZ, () -> updateChunkRegion(world, targetX, targetZ));
}
}
});
}
});
}
private void updateChunkRegion(World world, int chunkX, int chunkZ) {
if (world == null || !world.isChunkLoaded(chunkX, chunkZ) || !Chunks.isSafe(world, chunkX, chunkZ)) {
return;
}
Chunk chunk = world.getChunkAt(chunkX, chunkZ);
if (IrisSettings.get().getWorld().isPostLoadBlockUpdates()) {
getEngine().updateChunk(chunk);
}
if (!IrisSettings.get().getWorld().isMarkerEntitySpawningSystem()) {
return;
}
getMantle().raiseFlag(chunkX, chunkZ, MantleFlag.INITIAL_SPAWNED_MARKER, () -> {
int delay = RNG.r.i(5, 200);
J.runRegion(world, chunkX, chunkZ, () -> {
if (!world.isChunkLoaded(chunkX, chunkZ)) {
return;
}
spawnIn(world.getChunkAt(chunkX, chunkZ), true);
}, delay);
getSpawnersFromMarkers(chunk).forEach((blockf, spawners) -> {
if (spawners.isEmpty()) {
return;
}
@@ -232,12 +278,7 @@ public class IrisWorldManager extends EngineAssignedWorldManager {
IrisSpawner s = new KList<>(spawners).getRandom();
spawn(block, s, true);
});
}));
}
}
}
}
}
});
}
private boolean onAsyncTick() {
@@ -274,8 +315,12 @@ public class IrisWorldManager extends EngineAssignedWorldManager {
}
int spawnBuffer = RNG.r.i(2, 12);
World world = getEngine().getWorld().realWorld();
if (world == null) {
return false;
}
Chunk[] cc = getEngine().getWorld().realWorld().getLoadedChunks();
Chunk[] cc = getLoadedChunksSnapshot(world);
while (spawnBuffer-- > 0) {
if (cc.length == 0) {
Iris.debug("Can't spawn. No chunks!");
@@ -283,18 +328,60 @@ public class IrisWorldManager extends EngineAssignedWorldManager {
}
Chunk c = cc[RNG.r.nextInt(cc.length)];
if (!c.isLoaded() || !Chunks.isSafe(c.getWorld(), c.getX(), c.getZ())) {
continue;
}
spawnIn(c, false);
spawnChunkSafely(world, c.getX(), c.getZ(), false);
}
energy -= (actuallySpawned / 2D);
return actuallySpawned > 0;
}
private Chunk[] getLoadedChunksSnapshot(World world) {
if (world == null) {
return new Chunk[0];
}
CompletableFuture<Chunk[]> future = new CompletableFuture<>();
J.s(() -> {
try {
future.complete(world.getLoadedChunks());
} catch (Throwable e) {
future.completeExceptionally(e);
}
});
try {
return future.get(2, TimeUnit.SECONDS);
} catch (Throwable e) {
Iris.reportError(e);
return new Chunk[0];
}
}
private void spawnChunkSafely(World world, int chunkX, int chunkZ, boolean initial) {
if (world == null) {
return;
}
CompletableFuture<Void> future = new CompletableFuture<>();
J.runRegion(world, chunkX, chunkZ, () -> {
try {
if (!world.isChunkLoaded(chunkX, chunkZ) || !Chunks.isSafe(world, chunkX, chunkZ)) {
return;
}
spawnIn(world.getChunkAt(chunkX, chunkZ), initial);
} finally {
future.complete(null);
}
});
try {
future.get(5, TimeUnit.SECONDS);
} catch (Throwable e) {
Iris.reportError(e);
}
}
private void fixEnergy() {
energy = M.clip(energy, 1D, getDimension().getMaximumEnergy());
}
@@ -317,7 +404,7 @@ public class IrisWorldManager extends EngineAssignedWorldManager {
IrisPosition block = new IrisPosition(blockf.getX(), blockf.getY() + getEngine().getWorld().minHeight(), blockf.getZ());
IrisSpawner s = new KList<>(spawners).getRandom();
spawn(block, s, false);
J.a(() -> getMantle().raiseFlag(c.getX(), c.getZ(), MantleFlag.INITIAL_SPAWNED_MARKER,
J.runRegion(c.getWorld(), c.getX(), c.getZ(), () -> getMantle().raiseFlag(c.getX(), c.getZ(), MantleFlag.INITIAL_SPAWNED_MARKER,
() -> spawn(block, s, true)));
});
}
@@ -359,7 +446,7 @@ public class IrisWorldManager extends EngineAssignedWorldManager {
try {
spawn(c, v);
} catch (Throwable e) {
J.s(() -> spawn(c, v));
J.runRegion(c.getWorld(), c.getX(), c.getZ(), () -> spawn(c, v));
}
}
@@ -537,8 +624,11 @@ public class IrisWorldManager extends EngineAssignedWorldManager {
public void execute(Future<Chunk> chunkFuture) {
try {
chunkFuture.get();
} catch (InterruptedException | ExecutionException ignored) {
} catch (InterruptedException ex) {
Thread.currentThread().interrupt();
Iris.verbose("Chunk warmup interrupted while loading async teleport chunk.");
} catch (ExecutionException ex) {
Iris.reportError(ex);
}
}
@@ -562,19 +652,10 @@ public class IrisWorldManager extends EngineAssignedWorldManager {
IrisPosition pos = new IrisPosition((c.getX() << 4) + x, y, (c.getZ() << 4) + z);
if (mark.isEmptyAbove()) {
AtomicBoolean remove = new AtomicBoolean(false);
boolean remove = c.getBlock(x, y + 1, z).getBlockData().getMaterial().isSolid()
|| c.getBlock(x, y + 2, z).getBlockData().getMaterial().isSolid();
try {
J.sfut(() -> {
if (c.getBlock(x, y + 1, z).getBlockData().getMaterial().isSolid() || c.getBlock(x, y + 2, z).getBlockData().getMaterial().isSolid()) {
remove.set(true);
}
}).get();
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
if (remove.get()) {
if (remove) {
b.add(pos);
return;
}
@@ -297,20 +297,20 @@ public interface Engine extends DataProvider, Fallible, LootProvider, BlockUpdat
try {
Semaphore semaphore = new Semaphore(1024);
chunk.raiseFlagUnchecked(MantleFlag.ETCHED, () -> {
chunk.raiseFlagUnchecked(MantleFlag.TILE, run(semaphore, () -> {
chunk.raiseFlagUnchecked(MantleFlag.TILE, run(semaphore, c, () -> {
chunk.iterate(TileWrapper.class, (x, y, z, v) -> {
Block block = c.getBlock(x & 15, y + getWorld().minHeight(), z & 15);
if (!TileData.setTileState(block, v.getData()))
Iris.warn("Failed to set tile entity data at [%d %d %d | %s] for tile %s!", block.getX(), block.getY(), block.getZ(), block.getType().getKey(), v.getData().getMaterial().getKey());
});
}, 0));
chunk.raiseFlagUnchecked(MantleFlag.CUSTOM, run(semaphore, () -> {
chunk.raiseFlagUnchecked(MantleFlag.CUSTOM, run(semaphore, c, () -> {
chunk.iterate(Identifier.class, (x, y, z, v) -> {
Iris.service(ExternalDataSVC.class).processUpdate(this, c.getBlock(x & 15, y + getWorld().minHeight(), z & 15), v);
});
}, 0));
chunk.raiseFlagUnchecked(MantleFlag.UPDATE, run(semaphore, () -> {
chunk.raiseFlagUnchecked(MantleFlag.UPDATE, run(semaphore, c, () -> {
PrecisionStopwatch p = PrecisionStopwatch.start();
int[][] grid = new int[16][16];
for (int x = 0; x < 16; x++) {
@@ -362,19 +362,22 @@ public interface Engine extends DataProvider, Fallible, LootProvider, BlockUpdat
return;
for (var script : scripts) {
getExecution().updateChunk(script, chunk, c, (delay, task) -> run(semaphore, task, delay));
getExecution().updateChunk(script, chunk, c, (delay, task) -> run(semaphore, c, task, delay));
}
});
try {
semaphore.acquire(1024);
} catch (InterruptedException ignored) {}
} catch (InterruptedException ex) {
Thread.currentThread().interrupt();
Iris.reportError(ex);
}
} finally {
chunk.release();
}
}
private static Runnable run(Semaphore semaphore, Runnable runnable, int delay) {
private static Runnable run(Semaphore semaphore, Chunk contextChunk, Runnable runnable, int delay) {
return () -> {
try {
semaphore.acquire();
@@ -382,13 +385,14 @@ public interface Engine extends DataProvider, Fallible, LootProvider, BlockUpdat
throw new RuntimeException(e);
}
J.s(() -> {
int effectiveDelay = J.isFolia() ? 0 : delay;
J.runRegion(contextChunk.getWorld(), contextChunk.getX(), contextChunk.getZ(), () -> {
try {
runnable.run();
} finally {
semaphore.release();
}
}, delay);
}, effectiveDelay);
};
}
@@ -24,6 +24,7 @@ import art.arcane.volmlib.util.collection.KList;
import art.arcane.iris.util.format.C;
import art.arcane.iris.util.math.Position2;
import art.arcane.iris.util.plugin.VolmitSender;
import art.arcane.iris.util.scheduling.J;
import org.bukkit.*;
import org.bukkit.entity.EnderSignal;
import org.bukkit.entity.Player;
@@ -53,7 +54,7 @@ public abstract class EngineAssignedWorldManager extends EngineAssignedComponent
public EngineAssignedWorldManager(Engine engine) {
super(engine, "World");
Iris.instance.registerListener(this);
taskId = Bukkit.getScheduler().scheduleSyncRepeatingTask(Iris.instance, this::onTick, 0, 0);
taskId = J.sr(this::onTick, 1);
}
@EventHandler
@@ -137,6 +138,8 @@ public abstract class EngineAssignedWorldManager extends EngineAssignedComponent
public void close() {
super.close();
Iris.instance.unregisterListener(this);
Bukkit.getScheduler().cancelTask(taskId);
if (taskId != -1) {
J.csr(taskId);
}
}
}
@@ -23,6 +23,7 @@ import art.arcane.iris.engine.object.annotations.ArrayType;
import art.arcane.iris.engine.object.annotations.Desc;
import art.arcane.iris.engine.object.annotations.Required;
import art.arcane.iris.engine.object.annotations.Snippet;
import art.arcane.iris.util.scheduling.J;
import art.arcane.volmlib.util.collection.KList;
import lombok.Data;
import lombok.NoArgsConstructor;
@@ -73,10 +74,12 @@ public class IrisCommand {
.replaceAll("\\Q{y}\\E", String.valueOf(at.getBlockY()))
.replaceAll("\\Q{z}\\E", String.valueOf(at.getBlockZ()));
final String finalCommand = command;
int safeDelay = (int) Math.max(0, Math.min(Integer.MAX_VALUE, delay));
if (repeat) {
Bukkit.getScheduler().scheduleSyncRepeatingTask(Iris.instance, () -> Bukkit.getServer().dispatchCommand(Bukkit.getConsoleSender(), finalCommand), delay, repeatDelay);
int safeRepeatDelay = (int) Math.max(1, Math.min(Integer.MAX_VALUE, repeatDelay));
J.s(() -> J.sr(() -> Bukkit.getServer().dispatchCommand(Bukkit.getConsoleSender(), finalCommand), safeRepeatDelay), safeDelay);
} else {
Bukkit.getScheduler().scheduleSyncDelayedTask(Iris.instance, () -> Bukkit.getServer().dispatchCommand(Bukkit.getConsoleSender(), finalCommand), delay);
J.s(() -> Bukkit.getServer().dispatchCommand(Bukkit.getConsoleSender(), finalCommand), safeDelay);
}
}
}
@@ -338,9 +338,7 @@ public class IrisEntity extends IrisRegistrant {
if (e instanceof Villager) {
Villager villager = (Villager) e;
villager.setRemoveWhenFarAway(false);
Bukkit.getScheduler().scheduleSyncDelayedTask(Iris.instance, () -> {
villager.setPersistent(true);
}, 1);
J.s(() -> villager.setPersistent(true), 1);
}
if (e instanceof Mob) {
@@ -80,8 +80,10 @@ public class LegacyTileData extends TileData {
@Override
public void toBukkit(Block block) {
if (!J.runAt(block.getLocation(), () -> handler.toBukkit(block))) {
J.s(() -> handler.toBukkit(block));
}
}
@Override
public void toBinary(DataOutputStream out) throws IOException {
@@ -23,8 +23,8 @@ import art.arcane.iris.core.IrisSettings;
import art.arcane.volmlib.util.collection.KList;
import art.arcane.volmlib.util.collection.KMap;
import art.arcane.iris.util.format.C;
import art.arcane.iris.util.scheduling.J;
import art.arcane.volmlib.util.reflect.V;
import org.bukkit.Bukkit;
import org.bukkit.Sound;
import org.bukkit.command.CommandSender;
@@ -171,7 +171,7 @@ public class VirtualCommand {
for (String i : command.getRequiredPermissions()) {
if (!sender.hasPermission(i)) {
failed = true;
Bukkit.getScheduler().scheduleSyncDelayedTask(Iris.instance, () -> sender.sendMessage("- " + C.WHITE + i), 0);
J.s(() -> sender.sendMessage("- " + C.WHITE + i), 0);
}
}
@@ -245,7 +245,7 @@ public abstract class VolmitPlugin extends JavaPlugin implements Listener {
@Override
public void onDisable() {
stop();
Bukkit.getScheduler().cancelTasks(this);
J.cancelPluginTasks();
unregisterListener(this);
unregisterAll();
}
@@ -339,6 +339,10 @@ public abstract class VolmitPlugin extends JavaPlugin implements Listener {
@Override
public List<String> onTabComplete(CommandSender sender, Command command,
String alias, String[] args) {
if (commands == null || commands.isEmpty()) {
return super.onTabComplete(sender, command, alias, args);
}
KList<String> chain = new KList<>();
for (String i : args) {
@@ -370,6 +374,9 @@ public abstract class VolmitPlugin extends JavaPlugin implements Listener {
if (bad) {
return false;
}
if (commands == null || commands.isEmpty()) {
return false;
}
KList<String> chain = new KList<>();
chain.add(args);
@@ -470,6 +477,9 @@ public abstract class VolmitPlugin extends JavaPlugin implements Listener {
if (bad) {
return;
}
if (commands == null || commands.isEmpty()) {
return;
}
for (VirtualCommand i : commands.v()) {
try {
unregisterCommand(i.getCommand());
@@ -485,8 +495,15 @@ public abstract class VolmitPlugin extends JavaPlugin implements Listener {
return;
}
for (org.bukkit.permissions.Permission i : computePermissions()) {
if (i == null) {
continue;
}
try {
Bukkit.getPluginManager().removePermission(i);
v("Unregistered Permission " + i.getName());
} catch (Throwable e) {
Iris.reportError(e);
}
}
}
@@ -20,29 +20,39 @@ package art.arcane.iris.util.scheduling;
import art.arcane.iris.Iris;
import art.arcane.iris.core.service.PreservationSVC;
import art.arcane.iris.util.parallel.MultiBurst;
import art.arcane.volmlib.util.function.NastyFunction;
import art.arcane.volmlib.util.function.NastyFuture;
import art.arcane.volmlib.util.function.NastyRunnable;
import art.arcane.volmlib.util.function.NastySupplier;
import art.arcane.volmlib.util.math.FinalInteger;
import art.arcane.volmlib.util.scheduling.AR;
import art.arcane.volmlib.util.scheduling.FoliaScheduler;
import art.arcane.volmlib.util.scheduling.JSupport;
import art.arcane.volmlib.util.scheduling.SR;
import art.arcane.volmlib.util.scheduling.SchedulerBridge;
import art.arcane.volmlib.util.scheduling.StartupQueueSupport;
import art.arcane.volmlib.util.math.FinalInteger;
import art.arcane.iris.util.parallel.MultiBurst;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.entity.Entity;
import org.bukkit.plugin.Plugin;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
@SuppressWarnings("ALL")
public class J {
private static int tid = 0;
private static final long TICK_MS = 50L;
private static final AtomicInteger TASK_IDS = new AtomicInteger(1);
private static final Map<Integer, Runnable> REPEATING_CANCELLERS = new ConcurrentHashMap<>();
private static final StartupQueueSupport STARTUP_QUEUE = new StartupQueueSupport();
static {
@@ -100,10 +110,13 @@ public class J {
}
public static void aBukkit(Runnable a) {
if (!Bukkit.getPluginManager().isPluginEnabled(Iris.instance)) {
if (!isPluginEnabled()) {
return;
}
Bukkit.getScheduler().scheduleAsyncDelayedTask(Iris.instance, a);
if (!runAsyncImmediate(a)) {
a(a, 0);
}
}
public static <T> Future<T> a(Callable<T> a) {
@@ -142,89 +155,210 @@ public class J {
return JSupport.attempt(t::get, i, Iris::reportError);
}
/**
* Dont call this unless you know what you are doing!
*/
public static void executeAfterStartupQueue() {
JSupport.executeAfterStartupQueue(STARTUP_QUEUE, J::s, J::a);
}
/**
* Schedule a sync task to be run right after startup. If the server has already
* started ticking, it will simply run it in a sync task.
* <p>
* If you dont know if you should queue this or not, do so, it's pretty
* forgiving.
*
* @param r the runnable
*/
public static void ass(Runnable r) {
JSupport.enqueueAfterStartupSync(STARTUP_QUEUE, r, J::s);
}
/**
* Schedule an async task to be run right after startup. If the server has
* already started ticking, it will simply run it in an async task.
* <p>
* If you dont know if you should queue this or not, do so, it's pretty
* forgiving.
*
* @param r the runnable
*/
public static void asa(Runnable r) {
JSupport.enqueueAfterStartupAsync(STARTUP_QUEUE, r, J::a);
}
/**
* Queue a sync task
*
* @param r the runnable
*/
public static void s(Runnable r) {
if (!Bukkit.getPluginManager().isPluginEnabled(Iris.instance)) {
public static boolean isFolia() {
return FoliaScheduler.isFolia(Iris.instance);
}
public static boolean isPrimaryThread() {
return FoliaScheduler.isPrimaryThread();
}
public static boolean isOwnedByCurrentRegion(Entity entity) {
return FoliaScheduler.isOwnedByCurrentRegion(entity);
}
public static boolean isOwnedByCurrentRegion(World world, int chunkX, int chunkZ) {
return FoliaScheduler.isOwnedByCurrentRegion(world, chunkX, chunkZ);
}
public static boolean runEntity(Entity entity, Runnable runnable) {
if (entity == null || runnable == null) {
return false;
}
if (isFolia()) {
if (isOwnedByCurrentRegion(entity)) {
runnable.run();
return true;
}
return runEntityImmediate(entity, runnable);
}
if (isPrimaryThread()) {
runnable.run();
return true;
}
s(runnable);
return true;
}
public static boolean runEntity(Entity entity, Runnable runnable, int delayTicks) {
if (entity == null || runnable == null) {
return false;
}
if (delayTicks <= 0) {
return runEntity(entity, runnable);
}
if (isFolia() && runEntityDelayed(entity, runnable, delayTicks)) {
return true;
}
s(() -> runEntity(entity, runnable), delayTicks);
return true;
}
public static boolean runRegion(World world, int chunkX, int chunkZ, Runnable runnable) {
if (world == null || runnable == null) {
return false;
}
if (isFolia() && isOwnedByCurrentRegion(world, chunkX, chunkZ)) {
runnable.run();
return true;
}
if (runRegionImmediate(world, chunkX, chunkZ, runnable)) {
return true;
}
if (isFolia()) {
Iris.verbose("Failed to schedule immediate region task for " + world.getName() + "@" + chunkX + "," + chunkZ + " on Folia.");
return false;
}
s(runnable);
return true;
}
public static boolean runRegion(World world, int chunkX, int chunkZ, Runnable runnable, int delayTicks) {
if (world == null || runnable == null) {
return false;
}
if (delayTicks <= 0) {
return runRegion(world, chunkX, chunkZ, runnable);
}
if (runRegionDelayed(world, chunkX, chunkZ, runnable, delayTicks)) {
return true;
}
if (isFolia()) {
Iris.verbose("Failed to schedule delayed region task for " + world.getName() + "@" + chunkX + "," + chunkZ
+ " (" + delayTicks + "t) on Folia.");
return false;
}
s(runnable, delayTicks);
return true;
}
public static boolean runAt(Location location, Runnable runnable) {
if (location == null || runnable == null || location.getWorld() == null) {
return false;
}
return runRegion(location.getWorld(), location.getBlockX() >> 4, location.getBlockZ() >> 4, runnable);
}
public static boolean runAt(Location location, Runnable runnable, int delayTicks) {
if (location == null || runnable == null || location.getWorld() == null) {
return false;
}
return runRegion(location.getWorld(), location.getBlockX() >> 4, location.getBlockZ() >> 4, runnable, delayTicks);
}
public static void cancelPluginTasks() {
if (Iris.instance == null) {
return;
}
FoliaScheduler.cancelTasks(Iris.instance);
try {
Bukkit.getScheduler().cancelTasks(Iris.instance);
} catch (UnsupportedOperationException ex) {
// Folia blocks BukkitScheduler usage.
Iris.verbose("Skipping BukkitScheduler#cancelTasks for Iris on this server.");
}
}
public static void s(Runnable r) {
if (!isPluginEnabled()) {
return;
}
if (!runGlobalImmediate(r)) {
try {
Bukkit.getScheduler().scheduleSyncDelayedTask(Iris.instance, r);
} catch (UnsupportedOperationException e) {
throw new IllegalStateException("Failed to schedule sync task (Folia scheduler unavailable, BukkitScheduler unsupported).", e);
}
}
}
public static CompletableFuture sfut(Runnable r) {
CompletableFuture f = new CompletableFuture();
if (!Bukkit.getPluginManager().isPluginEnabled(Iris.instance)) {
if (!isPluginEnabled()) {
return null;
}
Bukkit.getScheduler().scheduleSyncDelayedTask(Iris.instance, () -> {
s(() -> {
r.run();
f.complete(null);
});
return f;
}
public static <T> CompletableFuture<T> sfut(Supplier<T> r) {
CompletableFuture<T> f = new CompletableFuture<>();
if (!Bukkit.getPluginManager().isPluginEnabled(Iris.instance)) {
if (!isPluginEnabled()) {
return null;
}
Bukkit.getScheduler().scheduleSyncDelayedTask(Iris.instance, () -> {
s(() -> {
try {
f.complete(r.get());
} catch (Throwable e) {
f.completeExceptionally(e);
}
});
return f;
}
public static CompletableFuture sfut(Runnable r, int delay) {
CompletableFuture f = new CompletableFuture();
if (!Bukkit.getPluginManager().isPluginEnabled(Iris.instance)) {
if (!isPluginEnabled()) {
return null;
}
Bukkit.getScheduler().scheduleSyncDelayedTask(Iris.instance, () -> {
s(() -> {
r.run();
f.complete(null);
}, delay);
return f;
}
@@ -237,53 +371,58 @@ public class J {
return f;
}
/**
* Queue a sync task
*
* @param r the runnable
* @param delay the delay to wait in ticks before running
*/
public static void s(Runnable r, int delay) {
try {
if (!Bukkit.getPluginManager().isPluginEnabled(Iris.instance)) {
if (!isPluginEnabled()) {
return;
}
if (delay <= 0) {
s(r);
return;
}
try {
if (!runGlobalDelayed(r, delay)) {
Bukkit.getScheduler().scheduleSyncDelayedTask(Iris.instance, r, delay);
}
} catch (Throwable e) {
Iris.reportError(e);
}
}
/**
* Cancel a sync repeating task
*
* @param id the task id
*/
public static void csr(int id) {
Bukkit.getScheduler().cancelTask(id);
cancelRepeatingTask(id);
}
/**
* Start a sync repeating task
*
* @param r the runnable
* @param interval the interval
* @return the task id
*/
public static int sr(Runnable r, int interval) {
if (!Bukkit.getPluginManager().isPluginEnabled(Iris.instance)) {
if (!isPluginEnabled()) {
return -1;
}
return Bukkit.getScheduler().scheduleSyncRepeatingTask(Iris.instance, r, 0, interval);
int safeInterval = Math.max(1, interval);
RepeatingState state = new RepeatingState();
int taskId = trackRepeatingTask(() -> state.cancelled = true);
Runnable[] loop = new Runnable[1];
loop[0] = () -> {
if (state.cancelled || !isPluginEnabled()) {
REPEATING_CANCELLERS.remove(taskId);
return;
}
r.run();
if (state.cancelled || !isPluginEnabled()) {
REPEATING_CANCELLERS.remove(taskId);
return;
}
s(loop[0], safeInterval);
};
s(loop[0]);
return taskId;
}
/**
* Start a sync repeating task for a limited amount of ticks
*
* @param r the runnable
* @param interval the interval in ticks
* @param intervals the maximum amount of intervals to run
*/
public static void sr(Runnable r, int interval, int intervals) {
FinalInteger fi = new FinalInteger(0);
@@ -300,50 +439,60 @@ public class J {
};
}
/**
* Call an async task dealyed
*
* @param r the runnable
* @param delay the delay to wait before running
*/
@SuppressWarnings("deprecation")
public static void a(Runnable r, int delay) {
if (Bukkit.getPluginManager().isPluginEnabled(Iris.instance)) {
Bukkit.getScheduler().scheduleAsyncDelayedTask(Iris.instance, r, delay);
if (!isPluginEnabled()) {
return;
}
if (delay <= 0) {
if (!runAsyncImmediate(r)) {
a(r);
}
return;
}
if (!runAsyncDelayed(r, delay)) {
a(() -> {
if (sleep(ticksToMilliseconds(delay))) {
r.run();
}
});
}
}
/**
* Cancel an async repeat task
*
* @param id the id
*/
public static void car(int id) {
Bukkit.getScheduler().cancelTask(id);
cancelRepeatingTask(id);
}
/**
* Start an async repeat task
*
* @param r the runnable
* @param interval the interval in ticks
* @return the task id
*/
@SuppressWarnings("deprecation")
public static int ar(Runnable r, int interval) {
if (!Bukkit.getPluginManager().isPluginEnabled(Iris.instance)) {
if (!isPluginEnabled()) {
return -1;
}
return Bukkit.getScheduler().scheduleAsyncRepeatingTask(Iris.instance, r, 0, interval);
int safeInterval = Math.max(1, interval);
RepeatingState state = new RepeatingState();
int taskId = trackRepeatingTask(() -> state.cancelled = true);
Runnable[] loop = new Runnable[1];
loop[0] = () -> {
if (state.cancelled || !isPluginEnabled()) {
REPEATING_CANCELLERS.remove(taskId);
return;
}
r.run();
if (state.cancelled || !isPluginEnabled()) {
REPEATING_CANCELLERS.remove(taskId);
return;
}
a(loop[0], safeInterval);
};
a(loop[0], 0);
return taskId;
}
/**
* Start an async repeating task for a limited time
*
* @param r the runnable
* @param interval the interval
* @param intervals the intervals to run
*/
public static void ar(Runnable r, int interval, int intervals) {
FinalInteger fi = new FinalInteger(0);
@@ -359,4 +508,61 @@ public class J {
}
};
}
private static int trackRepeatingTask(Runnable cancelAction) {
int id = TASK_IDS.getAndIncrement();
REPEATING_CANCELLERS.put(id, cancelAction);
return id;
}
private static void cancelRepeatingTask(int id) {
Runnable cancelAction = REPEATING_CANCELLERS.remove(id);
if (cancelAction != null) {
cancelAction.run();
}
}
private static boolean isPluginEnabled() {
return Iris.instance != null && Bukkit.getPluginManager().isPluginEnabled(Iris.instance);
}
private static long ticksToMilliseconds(int ticks) {
return Math.max(0L, ticks) * TICK_MS;
}
private static boolean runGlobalImmediate(Runnable runnable) {
return FoliaScheduler.runGlobal(Iris.instance, runnable);
}
private static boolean runGlobalDelayed(Runnable runnable, int delayTicks) {
return FoliaScheduler.runGlobal(Iris.instance, runnable, Math.max(0, delayTicks));
}
private static boolean runRegionImmediate(World world, int chunkX, int chunkZ, Runnable runnable) {
return FoliaScheduler.runRegion(Iris.instance, world, chunkX, chunkZ, runnable);
}
private static boolean runRegionDelayed(World world, int chunkX, int chunkZ, Runnable runnable, int delayTicks) {
return FoliaScheduler.runRegion(Iris.instance, world, chunkX, chunkZ, runnable, Math.max(0, delayTicks));
}
private static boolean runAsyncImmediate(Runnable runnable) {
return FoliaScheduler.runAsync(Iris.instance, runnable);
}
private static boolean runAsyncDelayed(Runnable runnable, int delayTicks) {
return FoliaScheduler.runAsync(Iris.instance, runnable, Math.max(0, delayTicks));
}
private static boolean runEntityImmediate(Entity entity, Runnable runnable) {
return FoliaScheduler.runEntity(Iris.instance, entity, runnable);
}
private static boolean runEntityDelayed(Entity entity, Runnable runnable, int delayTicks) {
return FoliaScheduler.runEntity(Iris.instance, entity, runnable, Math.max(0, delayTicks));
}
private static final class RepeatingState {
private volatile boolean cancelled;
}
}
@@ -5,6 +5,7 @@ import net.bytebuddy.agent.ByteBuddyAgent;
import net.bytebuddy.dynamic.loading.ClassReloadingStrategy;
import java.io.File;
import java.io.InputStream;
import java.lang.instrument.Instrumentation;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;
@@ -30,17 +31,57 @@ public class Agent {
public static boolean install() {
if (isInstalled())
return true;
if (!ensureAgentJar())
return false;
try {
Files.copy(Iris.instance.getResource("agent.jar"), AGENT_JAR.toPath(), StandardCopyOption.REPLACE_EXISTING);
Iris.info("Installing Java Agent...");
Iris.info("Note: JVM [Attach Listener/ERROR] [STDERR] warning lines during this step are expected and not Iris errors.");
ByteBuddyAgent.attach(AGENT_JAR, ByteBuddyAgent.ProcessProvider.ForCurrentVm.INSTANCE);
} catch (Throwable e) {
e.printStackTrace();
Iris.error("Failed to install Java Agent: " + e.getMessage());
Iris.reportError(e);
}
return doGetInstrumentation() != null;
}
private static boolean ensureAgentJar() {
File parent = AGENT_JAR.getParentFile();
if (parent != null && !parent.exists() && !parent.mkdirs() && !parent.exists()) {
Iris.error("Failed to create Iris plugin data folder for Java agent: " + parent.getAbsolutePath());
return false;
}
try (InputStream in = openBundledAgentJar()) {
if (in == null) {
if (AGENT_JAR.isFile() && AGENT_JAR.length() > 0) {
Iris.warn("Bundled agent.jar not found in Iris plugin jar. Reusing existing " + AGENT_JAR.getAbsolutePath());
return true;
}
Iris.error("Bundled agent.jar was not found in Iris plugin jar. Rebuild/deploy Iris with embedded agent.jar.");
return false;
}
Files.copy(in, AGENT_JAR.toPath(), StandardCopyOption.REPLACE_EXISTING);
return true;
} catch (Throwable e) {
Iris.error("Failed to prepare Java agent jar: " + e.getMessage());
Iris.reportError(e);
return false;
}
}
private static InputStream openBundledAgentJar() {
InputStream stream = Iris.instance.getResource("agent.jar");
if (stream != null) {
return stream;
}
return Agent.class.getClassLoader().getResourceAsStream("agent.jar");
}
private static Instrumentation doGetInstrumentation() {
try {
return (Instrumentation) Class.forName(NAME, true, ClassLoader.getSystemClassLoader()).getMethod("getInstrumentation").invoke(null);
@@ -3,7 +3,6 @@ package art.arcane.iris.util.profile;
import art.arcane.volmlib.util.math.M;
import art.arcane.iris.util.scheduling.J;
import art.arcane.volmlib.util.scheduling.Looper;
import org.bukkit.Bukkit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
@@ -52,12 +51,13 @@ public abstract class MsptTimings extends Looper {
protected abstract void update(int mspt);
private boolean startTickTask() {
if (taskId != -1 && (Bukkit.getScheduler().isQueued(taskId) || Bukkit.getScheduler().isCurrentlyRunning(taskId)))
if (taskId != -1)
return false;
taskId = J.sr(() -> {
if (isInterrupted()) {
J.csr(taskId);
taskId = -1;
return;
}
@@ -1,14 +1,12 @@
package art.arcane.iris.core.safeguard
import art.arcane.iris.Iris
import art.arcane.iris.core.IrisSettings
import art.arcane.iris.core.safeguard.task.Diagnostic
import art.arcane.iris.core.safeguard.task.Task
import art.arcane.iris.core.safeguard.task.ValueWithDiagnostics
import art.arcane.iris.core.safeguard.task.tasks
import art.arcane.iris.util.format.C
import art.arcane.iris.util.scheduling.J
import org.bukkit.Bukkit
import java.util.*
object IrisSafeguard {
@@ -93,51 +91,18 @@ object IrisSafeguard {
private fun warning() {
Iris.warn(C.GOLD.toString() + "Iris is running in Warning Mode")
Iris.warn("")
Iris.warn(C.DARK_GRAY.toString() + "--==<" + C.GOLD + " IMPORTANT " + C.DARK_GRAY + ">==--")
Iris.warn(C.GOLD.toString() + "Iris is running in warning mode which may cause the following issues:")
Iris.warn("- Data Loss")
Iris.warn("- Errors")
Iris.warn("- Broken worlds")
Iris.warn("- Unexpected behavior.")
Iris.warn("- And perhaps further complications.")
Iris.warn(C.GRAY.toString() + "Some startup checks need attention. Review the messages above for tuning suggestions.")
Iris.warn(C.GRAY.toString() + "Iris will continue startup normally.")
Iris.warn("")
}
private fun unstable() {
Iris.error(C.DARK_RED.toString() + "Iris is running in Unstable Mode")
Iris.error(C.DARK_RED.toString() + "Iris is running in Danger Mode")
Iris.error("")
Iris.error(C.DARK_GRAY.toString() + "--==<" + C.RED + " IMPORTANT " + C.DARK_GRAY + ">==--")
Iris.error("Iris is running in unstable mode which may cause the following issues:")
Iris.error(C.DARK_RED.toString() + "Server Issues")
Iris.error("- Server won't boot")
Iris.error("- Data Loss")
Iris.error("- Unexpected behavior.")
Iris.error("- And More...")
Iris.error(C.DARK_RED.toString() + "World Issues")
Iris.error("- Worlds can't load due to corruption.")
Iris.error("- Worlds may slowly corrupt until they can't load.")
Iris.error("- World data loss.")
Iris.error("- And More...")
Iris.error(C.DARK_RED.toString() + "ATTENTION: " + C.RED + "While running Iris in unstable mode, you won't be eligible for support.")
if (IrisSettings.get().general.isDoomsdayAnnihilationSelfDestructMode) {
Iris.error(C.DARK_RED.toString() + "Boot Unstable is set to true, continuing with the startup process in 10 seconds.")
Iris.error("Critical startup checks failed. Iris will continue startup in 10 seconds.")
Iris.error("Review and resolve the errors above as soon as possible.")
J.sleep(10000L)
} else {
Iris.error(C.DARK_RED.toString() + "Go to plugins/iris/settings.json and set DoomsdayAnnihilationSelfDestructMode to true if you wish to proceed.")
Iris.error(C.DARK_RED.toString() + "The server will shutdown in 10 seconds.")
J.sleep(10000L)
Iris.error(C.DARK_RED.toString() + "Shutting down server.")
forceShutdown = true
try {
Bukkit.getPluginManager().disablePlugins()
} finally {
Runtime.getRuntime().halt(42)
}
}
Iris.info("")
}
}
@@ -13,18 +13,25 @@ import art.arcane.iris.util.misc.getHardware
import org.bukkit.Bukkit
import java.util.Locale
import java.util.stream.Collectors
import javax.tools.ToolProvider
import kotlin.properties.PropertyDelegateProvider
import kotlin.properties.ReadOnlyProperty
private val memory by task {
val mem = getHardware.getProcessMemory()
if (mem >= 5999) STABLE.withDiagnostics()
else STABLE.withDiagnostics(
when {
mem >= 3072 -> STABLE.withDiagnostics()
mem > 2048 -> STABLE.withDiagnostics(
INFO.create("Memory Recommendation"),
INFO.create("- 3GB+ process memory is recommended for Iris."),
INFO.create("- Process Memory: $mem MB")
)
else -> WARNING.withDiagnostics(
WARN.create("Low Memory"),
WARN.create("- 6GB+ Ram is recommended"),
WARN.create("- Iris is running with 2GB or less process memory."),
WARN.create("- 3GB+ process memory is recommended for Iris."),
WARN.create("- Process Memory: $mem MB")
)
}
}
private val incompatibilities by task {
@@ -49,6 +56,7 @@ private val incompatibilities by task {
private val software by task {
val supported = setOf(
"folia",
"purpur",
"pufferfish",
"paper",
@@ -59,7 +67,7 @@ private val software by task {
if (supported.any { server.name.contains(it, true) }) STABLE.withDiagnostics()
else WARNING.withDiagnostics(
WARN.create("Unsupported Server Software"),
WARN.create("- Please consider using Paper or Purpur instead.")
WARN.create("- Please consider using Folia, Paper, or Purpur instead.")
)
}
@@ -118,12 +126,17 @@ private val diskSpace by task {
private val java by task {
val version = Iris.getJavaVersion()
val jdk = runCatching { ToolProvider.getSystemJavaCompiler() }.getOrNull() != null
if (version in setOf(21) && jdk) STABLE.withDiagnostics()
else WARNING.withDiagnostics(
WARN.create("Unsupported Java version"),
WARN.create("- Please consider using JDK 21 Instead of ${if(jdk) "JDK" else "JRE"} $version")
when {
version == 21 -> STABLE.withDiagnostics()
version > 21 -> STABLE.withDiagnostics(
INFO.create("Java Runtime"),
INFO.create("- Running Java $version. Iris is tested primarily on Java 21.")
)
else -> WARNING.withDiagnostics(
WARN.create("Unsupported Java version"),
WARN.create("- Java 21+ is recommended. Current runtime: Java $version")
)
}
}
@@ -141,7 +154,7 @@ val tasks = listOf(
private val server get() = Bukkit.getServer()
private fun isPaperPreferredServer(): Boolean {
val name = server.name.lowercase(Locale.ROOT)
return name.contains("paper") || name.contains("purpur") || name.contains("pufferfish")
return name.contains("folia") || name.contains("paper") || name.contains("purpur") || name.contains("pufferfish")
}
private fun <T> MutableList<T>.addAll(vararg values: T) = values.forEach(this::add)
fun task(action: () -> ValueWithDiagnostics<Mode>) = PropertyDelegateProvider<Any?, ReadOnlyProperty<Any?, Task>> { _, _ ->
+1
View File
@@ -1,6 +1,7 @@
name: ${name}
version: ${version}
main: ${main}
folia-supported: true
load: STARTUP
authors: [ cyberpwn, NextdoorPsycho, Vatuu ]
website: volmit.com
@@ -215,25 +215,46 @@ public class NMSBinding implements INMSBinding {
@Override
public void deserializeTile(KMap<String, Object> map, Location pos) {
net.minecraft.nbt.CompoundTag tag = (net.minecraft.nbt.CompoundTag) convertToTag(map, 0, 64);
if (map == null || pos == null || pos.getWorld() == null) {
return;
}
Tag converted = convertToTag(map, 0, 64);
if (!(converted instanceof net.minecraft.nbt.CompoundTag tag)) {
return;
}
var level = ((CraftWorld) pos.getWorld()).getHandle();
var blockPos = new BlockPos(pos.getBlockX(), pos.getBlockY(), pos.getBlockZ());
J.s(() -> merge(level, blockPos, tag));
if (!J.runAt(pos, () -> merge(level, blockPos, tag))) {
Iris.warn("[NMS] Failed to schedule tile deserialize at " + blockPos + " in world " + pos.getWorld().getName());
}
}
private void merge(ServerLevel level, BlockPos blockPos, net.minecraft.nbt.CompoundTag tag) {
if (level == null || blockPos == null || tag == null) {
return;
}
try {
var blockEntity = level.getBlockEntity(blockPos);
if (blockEntity == null) {
Iris.warn("[NMS] BlockEntity not found at " + blockPos);
var state = level.getBlockState(blockPos);
if (!state.hasBlockEntity())
if (!state.hasBlockEntity()) {
return;
}
blockEntity = ((EntityBlock) state.getBlock())
.newBlockEntity(blockPos, state);
}
var accessor = new BlockDataAccessor(blockEntity, blockPos);
accessor.setData(accessor.getData().merge(tag));
} catch (Throwable e) {
Iris.warn("[NMS] Failed to merge tile data at " + blockPos + ": " + e.getMessage());
Iris.reportError(e);
}
}
private Tag convertToTag(Object object, int depth, int maxDepth) {