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

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 {

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) {
Runtime.getRuntime().removeShutdownHook(shutdownHook);
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();
services.clear();
});
Runtime.getRuntime().addShutdownHook(shutdownHook);
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;
services.values().forEach(IrisService::onDisable);
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);
}

View File

@@ -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;

View File

@@ -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) {

View File

@@ -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,20 +153,98 @@ 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) {
File worlds = Bukkit.getWorldContainer();
var data = ServerProperties.DATA;
try (var in = new FileInputStream(ServerProperties.SERVER_PROPERTIES)) {
data.load(in);
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")) {
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;
}
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());
}
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;
}
data.setProperty("level-name", newName);
try (var out = new FileOutputStream(ServerProperties.SERVER_PROPERTIES)) {
data.store(out, null);
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;
}
}
@@ -439,22 +523,23 @@ public class CommandIris implements DecreeExecutor {
sender().sendMessage(C.GOLD + world + " is not an iris world.");
return;
}
if (dimension == null) {
sender().sendMessage(C.RED + "Could not determine Iris dimension for " + 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();
return;
}
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);
}
}
}

View File

@@ -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);

View File

@@ -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.");

View File

@@ -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() {

View File

@@ -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()){
deepFile.delete();
J.sleep(1000);
}
Iris.info("DeepSearch: " + C.IRIS + world.getName() + C.BLUE + " File deleted and instance closed.");
J.a(() -> {
while (deepFile.exists()) {
deepFile.delete();
J.sleep(1000);
}
}.runTaskLater(Iris.instance, 20L);
Iris.info("DeepSearch: " + C.IRIS + world.getName() + C.BLUE + " File deleted and instance closed.");
}, 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;
}
}

View File

@@ -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()){
lazyFile.delete();
J.sleep(1000);
}
Iris.info("LazyGen: " + C.IRIS + world.getName() + C.BLUE + " File deleted and instance closed.");
J.a(() -> {
while (lazyFile.exists()) {
lazyFile.delete();
J.sleep(1000);
}
}.runTaskLater(Iris.instance, 20L);
Iris.info("LazyGen: " + C.IRIS + world.getName() + C.BLUE + " File deleted and instance closed.");
}, 20);
} catch (Exception e) {
Iris.error("Failed to shutdown Lazygen for " + world.getName());
e.printStackTrace();

View File

@@ -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() {
while (turboFile.exists()) {
turboFile.delete();
J.sleep(1000);
}
Iris.info("turboGen: " + C.IRIS + world.getName() + C.BLUE + " File deleted and instance closed.");
J.a(() -> {
while (turboFile.exists()) {
turboFile.delete();
J.sleep(1000);
}
}.runTaskLater(Iris.instance, 20L);
Iris.info("turboGen: " + C.IRIS + world.getName() + C.BLUE + " File deleted and instance closed.");
}, 20);
} catch (Exception e) {
Iris.error("Failed to shutdown turbogen for " + world.getName());
e.printStackTrace();

View File

@@ -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() {

View File

@@ -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();
}

View File

@@ -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();

View File

@@ -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();
@@ -88,4 +87,4 @@ public class ObjectSVC implements IrisService {
}
});
}
}
}

View File

@@ -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());
}
}

View File

@@ -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;

View File

@@ -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,52 +191,94 @@ public class IrisWorldManager extends EngineAssignedWorldManager {
private void discoverChunks() {
var mantle = getEngine().getMantle().getMantle();
for (Player i : getEngine().getWorld().realWorld().getPlayers()) {
int r = 1;
for (int x = -r; x <= r; x++) {
for (int z = -r; z <= r; z++) {
mantle.getChunk(i.getLocation().getChunk()).flag(MantleFlag.DISCOVERED, true);
}
}
World world = getEngine().getWorld().realWorld();
if (world == null) {
return;
}
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;
World world = getEngine().getWorld().realWorld();
if (world == null) {
return;
}
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)) {
J.s(() -> {
for (Player player : world.getPlayers()) {
if (player == null || !player.isOnline()) {
continue;
}
if (IrisSettings.get().getWorld().isPostLoadBlockUpdates()) {
getEngine().updateChunk(c.getWorld().getChunkAt(c.getX() + x, c.getZ() + z));
}
J.runEntity(player, () -> {
int centerX = player.getLocation().getBlockX() >> 4;
int centerZ = player.getLocation().getBlockZ() >> 4;
int radius = 1;
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) -> {
if (spawners.isEmpty()) {
return;
}
IrisPosition block = new IrisPosition(blockf.getX(), blockf.getY() + getEngine().getWorld().minHeight(), blockf.getZ());
IrisSpawner s = new KList<>(spawners).getRandom();
spawn(block, s, true);
});
}));
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;
}
IrisPosition block = new IrisPosition(blockf.getX(), blockf.getY() + getEngine().getWorld().minHeight(), blockf.getZ());
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;
}

View File

@@ -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);
};
}

View File

@@ -21,10 +21,11 @@ package art.arcane.iris.engine.framework;
import art.arcane.iris.Iris;
import art.arcane.iris.core.events.IrisEngineHotloadEvent;
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 org.bukkit.*;
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;
import org.bukkit.event.EventHandler;
@@ -50,11 +51,11 @@ public abstract class EngineAssignedWorldManager extends EngineAssignedComponent
taskId = -1;
}
public EngineAssignedWorldManager(Engine engine) {
super(engine, "World");
Iris.instance.registerListener(this);
taskId = Bukkit.getScheduler().scheduleSyncRepeatingTask(Iris.instance, this::onTick, 0, 0);
}
public EngineAssignedWorldManager(Engine engine) {
super(engine, "World");
Iris.instance.registerListener(this);
taskId = J.sr(this::onTick, 1);
}
@EventHandler
public void on(IrisEngineHotloadEvent e) {
@@ -134,9 +135,11 @@ public abstract class EngineAssignedWorldManager extends EngineAssignedComponent
}
@Override
public void close() {
super.close();
Iris.instance.unregisterListener(this);
Bukkit.getScheduler().cancelTask(taskId);
}
}
public void close() {
super.close();
Iris.instance.unregisterListener(this);
if (taskId != -1) {
J.csr(taskId);
}
}
}

View File

@@ -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,11 +74,13 @@ 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);
}
}
}
}
}

View File

@@ -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) {

View File

@@ -80,7 +80,9 @@ public class LegacyTileData extends TileData {
@Override
public void toBukkit(Block block) {
J.s(() -> handler.toBukkit(block));
if (!J.runAt(block.getLocation(), () -> handler.toBukkit(block))) {
J.s(() -> handler.toBukkit(block));
}
}
@Override

View File

@@ -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);
}
}

View File

@@ -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()) {
Bukkit.getPluginManager().removePermission(i);
v("Unregistered Permission " + i.getName());
if (i == null) {
continue;
}
try {
Bukkit.getPluginManager().removePermission(i);
v("Unregistered Permission " + i.getName());
} catch (Throwable e) {
Iris.reportError(e);
}
}
}

View File

@@ -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;
}
Bukkit.getScheduler().scheduleSyncDelayedTask(Iris.instance, r);
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) {
if (!isPluginEnabled()) {
return;
}
if (delay <= 0) {
s(r);
return;
}
try {
if (!Bukkit.getPluginManager().isPluginEnabled(Iris.instance)) {
return;
if (!runGlobalDelayed(r, delay)) {
Bukkit.getScheduler().scheduleSyncDelayedTask(Iris.instance, 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;
}
}

View File

@@ -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);

View File

@@ -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;
}

View File

@@ -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.")
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.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)
Iris.info("")
}
}
}

View File

@@ -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(
WARN.create("Low Memory"),
WARN.create("- 6GB+ Ram is recommended"),
WARN.create("- Process Memory: $mem MB")
)
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("- 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>> { _, _ ->

View File

@@ -1,11 +1,12 @@
name: ${name}
version: ${version}
main: ${main}
load: STARTUP
authors: [ cyberpwn, NextdoorPsycho, Vatuu ]
name: ${name}
version: ${version}
main: ${main}
folia-supported: true
load: STARTUP
authors: [ cyberpwn, NextdoorPsycho, Vatuu ]
website: volmit.com
description: More than a Dimension!
commands:
iris:
aliases: [ ir, irs ]
api-version: '${apiVersion}'
api-version: '${apiVersion}'

View File

@@ -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) {
var blockEntity = level.getBlockEntity(blockPos);
if (blockEntity == null) {
Iris.warn("[NMS] BlockEntity not found at " + blockPos);
var state = level.getBlockState(blockPos);
if (!state.hasBlockEntity())
return;
blockEntity = ((EntityBlock) state.getBlock())
.newBlockEntity(blockPos, state);
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()) {
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);
}
var accessor = new BlockDataAccessor(blockEntity, blockPos);
accessor.setData(accessor.getData().merge(tag));
}
private Tag convertToTag(Object object, int depth, int maxDepth) {
@@ -853,4 +874,4 @@ public class NMSBinding implements INMSBinding {
}
}
}
}
}