This commit is contained in:
Brian Neumann-Fopiano
2026-08-24 02:27:26 -04:00
parent fe5651f854
commit e20bb86f40
228 changed files with 5716 additions and 3577 deletions
@@ -1,130 +0,0 @@
package art.arcane.iris.core.nms.v26_2_R1;
import art.arcane.iris.core.lifecycle.WorldReplacementSeed;
import art.arcane.iris.util.common.scheduling.J;
import io.papermc.paper.world.saveddata.PaperLevelOverrides;
import io.papermc.paper.world.saveddata.PaperWorldMetadata;
import net.minecraft.server.MinecraftServer;
import net.minecraft.world.level.storage.PrimaryLevelData;
import net.minecraft.world.level.storage.SavedDataStorage;
import org.bukkit.Bukkit;
import org.bukkit.craftbukkit.CraftServer;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.LinkOption;
import java.nio.file.Path;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
final class CurrentPaperWorldDataWriter {
private static final long SNAPSHOT_TIMEOUT_SECONDS = 30L;
private CurrentPaperWorldDataWriter() {
}
static void write(
Path sourceWorldDirectory,
Path targetWorldDirectory,
long seed
) throws IOException {
CraftServer craftServer = (CraftServer) Bukkit.getServer();
MinecraftServer server = craftServer.getHandle().getServer();
PaperLevelOverrides levelOverrides = captureLevelOverrides(craftServer, server);
Path targetWorld = targetWorldDirectory.toAbsolutePath().normalize();
UUID metadataUuid = UUID.randomUUID();
WorldReplacementSeed.copyWithAuthoritativeSeed(sourceWorldDirectory, targetWorld, seed);
try (SavedDataStorage savedDataStorage = new SavedDataStorage(
targetWorld.resolve("data"),
server.getFixerUpper(),
server.registryAccess()
)) {
savedDataStorage.set(PaperWorldMetadata.TYPE, new PaperWorldMetadata(metadataUuid));
savedDataStorage.set(PaperLevelOverrides.TYPE, levelOverrides);
}
List<Path> requiredDataFiles = List.of(
targetWorld.resolve("data/minecraft/world_gen_settings.dat"),
targetWorld.resolve("data/paper/metadata.dat"),
targetWorld.resolve("data/paper/level_overrides.dat")
);
for (Path requiredDataFile : requiredDataFiles) {
if (!Files.isRegularFile(requiredDataFile, LinkOption.NOFOLLOW_LINKS)) {
throw new IOException("Current Paper world data was not written: " + requiredDataFile);
}
}
long writtenSeed = WorldReplacementSeed.readAuthoritativeSeed(targetWorld);
if (writtenSeed != seed) {
throw new IOException("Current Paper world data did not retain the requested seed.");
}
try (SavedDataStorage verificationStorage = new SavedDataStorage(
targetWorld.resolve("data"),
server.getFixerUpper(),
server.registryAccess()
)) {
PaperWorldMetadata metadata = verificationStorage.get(PaperWorldMetadata.TYPE);
if (metadata == null || !metadataUuid.equals(metadata.uuid())) {
throw new IOException("Current Paper world metadata could not be verified.");
}
PaperLevelOverrides overrides = verificationStorage.get(PaperLevelOverrides.TYPE);
if (overrides == null || overrides.isInitialized()) {
throw new IOException("Current Paper level overrides could not be verified.");
}
}
}
private static PaperLevelOverrides captureLevelOverrides(
CraftServer craftServer,
MinecraftServer server
) throws IOException {
if (craftServer.isGlobalTickThread()) {
return createLevelOverrides(craftServer, server);
}
if (J.isFolia() && J.isPrimaryThread()) {
throw new IOException("Current Paper world data cannot be staged from a Folia region tick thread.");
}
CompletableFuture<PaperLevelOverrides> captured = new CompletableFuture<>();
boolean scheduled = J.runGlobal(() -> {
try {
captured.complete(createLevelOverrides(craftServer, server));
} catch (Throwable failure) {
captured.completeExceptionally(failure);
}
});
if (!scheduled) {
throw new IOException("Could not schedule the current Paper level-data snapshot on the global thread.");
}
try {
return captured.get(SNAPSHOT_TIMEOUT_SECONDS, TimeUnit.SECONDS);
} catch (InterruptedException failure) {
Thread.currentThread().interrupt();
throw new IOException("Interrupted while capturing current Paper level data.", failure);
} catch (ExecutionException failure) {
throw new IOException("Could not capture current Paper level data.", failure.getCause());
} catch (TimeoutException failure) {
throw new IOException("Timed out while capturing current Paper level data.", failure);
}
}
private static PaperLevelOverrides createLevelOverrides(
CraftServer craftServer,
MinecraftServer server
) throws IOException {
if (!craftServer.isGlobalTickThread()) {
throw new IOException("Current Paper level data must be captured on the global tick thread.");
}
if (!(server.getWorldData().overworldData() instanceof PrimaryLevelData primaryLevelData)) {
throw new IOException("Paper primary level data is unavailable for current world data staging.");
}
return PaperLevelOverrides.createFromLiveLevelData(primaryLevelData);
}
}
@@ -154,7 +154,6 @@ import org.jetbrains.annotations.NotNull;
import java.awt.Color;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
@@ -1572,8 +1571,7 @@ public class NMSBinding implements INMSBinding {
injected.set(true);
return true;
} catch (Throwable e) {
IrisLogging.error(C.RED + "Failed to inject Bukkit");
e.printStackTrace();
IrisLogging.reportError(C.RED + "Failed to inject Bukkit", e);
ResettableClassFileTransformer partialServerLevel = serverLevelTransformer;
ResettableClassFileTransformer partialStorageAccess = levelStorageAccessTransformer;
serverLevelTransformer = null;
@@ -1627,22 +1625,12 @@ public class NMSBinding implements INMSBinding {
try {
transformer.reset(Agent.getInstrumentation(), AgentBuilder.RedefinitionStrategy.RETRANSFORMATION);
} catch (Throwable e) {
IrisLogging.error(C.RED + "Failed to remove Bukkit world lifecycle injection");
e.printStackTrace();
IrisLogging.reportError(C.RED + "Failed to remove Bukkit world lifecycle injection", e);
}
}
}
}
@Override
public void writeCurrentPaperWorldData(
Path sourceWorldDirectory,
Path targetWorldDirectory,
long seed
) throws IOException {
CurrentPaperWorldDataWriter.write(sourceWorldDirectory, targetWorldDirectory, seed);
}
@Override
public boolean awaitServerShutdownBoundary(long timeout, TimeUnit unit) {
MinecraftServer server = ((CraftServer) Bukkit.getServer()).getHandle().getServer();
@@ -1,132 +0,0 @@
package art.arcane.iris.core.nms.v26_2_R1;
import art.arcane.iris.core.nms.INMSBinding;
import org.junit.Test;
import java.io.InputStream;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
public class NMSBindingCurrentPaperWorldDataContractTest {
@Test
public void bindingDelegatesWithoutLinkingPaperSavedDataClasses() throws Exception {
String bindingSource = Files.readString(bindingSourcePath()).replace("\r\n", "\n");
String writer = section(
bindingSource,
"public void writeCurrentPaperWorldData(",
"public boolean awaitServerShutdownBoundary("
);
assertTrue(writer.contains("CurrentPaperWorldDataWriter.write("));
assertFalse(bindingSource.contains("PaperWorldMetadata"));
assertFalse(bindingSource.contains("PaperLevelOverrides"));
assertFalse(bindingSource.contains("io.papermc.paper.world.saveddata"));
InputStream classResource = NMSBindingCurrentPaperWorldDataContractTest.class
.getResourceAsStream("NMSBinding.class");
assertNotNull(classResource);
try (InputStream input = classResource) {
String classFile = new String(input.readAllBytes(), StandardCharsets.ISO_8859_1);
assertFalse(classFile.contains("PaperWorldMetadata"));
assertFalse(classFile.contains("PaperLevelOverrides"));
assertFalse(classFile.contains("io/papermc/paper/world/saveddata"));
}
}
@Test
public void stagesAllCurrentPaperWorldDataFromLiveServerState() throws Exception {
String writer = Files.readString(writerSourcePath()).replace("\r\n", "\n");
assertTrue(writer.contains("WorldReplacementSeed.copyWithAuthoritativeSeed("));
assertTrue(writer.contains("UUID metadataUuid = UUID.randomUUID()"));
assertTrue(writer.contains("new PaperWorldMetadata(metadataUuid)"));
assertTrue(writer.contains("captureLevelOverrides(craftServer, server)"));
assertTrue(writer.indexOf("captureLevelOverrides(craftServer, server)")
< writer.indexOf("WorldReplacementSeed.copyWithAuthoritativeSeed("));
assertTrue(writer.contains("new SavedDataStorage("));
assertTrue(writer.contains("server.getFixerUpper()"));
assertTrue(writer.contains("server.registryAccess()"));
assertTrue(writer.contains("data/minecraft/world_gen_settings.dat"));
assertTrue(writer.contains("data/paper/metadata.dat"));
assertTrue(writer.contains("data/paper/level_overrides.dat"));
assertTrue(writer.contains("WorldReplacementSeed.readAuthoritativeSeed(targetWorld)"));
assertTrue(writer.contains("verificationStorage.get(PaperWorldMetadata.TYPE)"));
assertTrue(writer.contains("metadataUuid.equals(metadata.uuid())"));
assertTrue(writer.contains("verificationStorage.get(PaperLevelOverrides.TYPE)"));
assertTrue(writer.contains("overrides == null || overrides.isInitialized()"));
assertTrue(writer.contains("Files.isRegularFile(requiredDataFile, LinkOption.NOFOLLOW_LINKS)"));
assertFalse(writer.toLowerCase().contains("migrat"));
assertFalse(writer.toLowerCase().contains("fallback"));
}
@Test
public void capturesOnlyLiveLevelOverridesOnTheGlobalThread() throws Exception {
String source = Files.readString(writerSourcePath()).replace("\r\n", "\n");
String capture = section(
source,
"private static PaperLevelOverrides captureLevelOverrides(",
"private static PaperLevelOverrides createLevelOverrides("
);
String create = section(
source,
"private static PaperLevelOverrides createLevelOverrides(",
"\n }\n}"
);
assertTrue(capture.contains("craftServer.isGlobalTickThread()"));
assertTrue(capture.contains("J.isFolia() && J.isPrimaryThread()"));
assertTrue(capture.contains("J.runGlobal("));
assertTrue(capture.contains("createLevelOverrides(craftServer, server)"));
assertTrue(capture.contains("captured.get(SNAPSHOT_TIMEOUT_SECONDS"));
assertTrue(capture.contains("Thread.currentThread().interrupt()"));
assertTrue(create.contains("if (!craftServer.isGlobalTickThread())"));
assertTrue(create.indexOf("if (!craftServer.isGlobalTickThread())")
< create.indexOf("server.getWorldData().overworldData()"));
assertTrue(create.contains("PaperLevelOverrides.createFromLiveLevelData(primaryLevelData)"));
assertFalse(capture.contains("WorldReplacementSeed"));
assertFalse(capture.contains("SavedDataStorage"));
assertFalse(capture.contains("Files."));
assertFalse(create.contains("WorldReplacementSeed"));
assertFalse(create.contains("SavedDataStorage"));
assertFalse(create.contains("Files."));
}
@Test
public void unsupportedBindingsRejectCurrentPaperWorldDataStaging() {
INMSBinding binding = (INMSBinding) Proxy.newProxyInstance(
INMSBinding.class.getClassLoader(),
new Class<?>[]{INMSBinding.class},
(proxy, method, arguments) -> InvocationHandler.invokeDefault(proxy, method, arguments)
);
UnsupportedOperationException error = assertThrows(
UnsupportedOperationException.class,
() -> binding.writeCurrentPaperWorldData(Path.of("source"), Path.of("target"), 1L)
);
assertTrue(error.getMessage().contains("does not support current Paper world data staging"));
}
private static Path bindingSourcePath() {
return Path.of(System.getProperty("iris.nmsBindingSource"));
}
private static Path writerSourcePath() {
return bindingSourcePath().resolveSibling("CurrentPaperWorldDataWriter.java");
}
private static String section(String source, String startMarker, String endMarker) {
int start = source.indexOf(startMarker);
int end = source.indexOf(endMarker, start);
assertTrue("Missing source section starting with " + startMarker, start >= 0);
assertTrue("Missing source section ending with " + endMarker, end > start);
return source.substring(start, end);
}
}
@@ -172,9 +172,7 @@ public class Iris extends VolmitPlugin implements Listener, ReloadAware {
try {
InstanceState.updateInstanceId();
} catch (Throwable ex) {
System.err.println("[Iris] Failed to update instance id: " + ex.getClass().getSimpleName()
+ (ex.getMessage() == null ? "" : " - " + ex.getMessage()));
ex.printStackTrace();
IrisLogging.reportError("Failed to update the Iris instance id.", ex);
}
}
@@ -350,7 +348,6 @@ public class Iris extends VolmitPlugin implements Listener, ReloadAware {
try {
object.run();
} catch (Throwable e) {
e.printStackTrace();
Iris.reportError(e);
}
}, RNG.r.i(100, 1200));
@@ -490,7 +487,7 @@ public class Iris extends VolmitPlugin implements Listener, ReloadAware {
pw.close();
Iris.info("DUMPED! See " + fi.getAbsolutePath());
} catch (Throwable e) {
e.printStackTrace();
Iris.reportError("Failed to write the Iris thread dump.", e);
}
}
@@ -661,7 +658,7 @@ public class Iris extends VolmitPlugin implements Listener, ReloadAware {
(EngineWorldManagerProvider) IrisWorldManager::new);
IrisServices.register(WorldDeletionQueue.class, pendingWorldDeletes);
IrisServices.register(ManagedWorldLoader.class, (ManagedWorldLoader) this::loadManagedWorld);
SettingsHotloadWatch watch = new SettingsHotloadWatch(getDataFile("settings.json"));
SettingsHotloadWatch watch = new SettingsHotloadWatch(getDataFile("iris.json"));
settingsHotloadWatch = watch;
// Stale-temp cleanup must complete before services enable: StudioSVC.onEnable downloads
// packs through cache/temp on an async thread, and a concurrent delete of that folder
@@ -774,7 +771,7 @@ public class Iris extends VolmitPlugin implements Listener, ReloadAware {
private void autoStartStudio() {
if (IrisSettings.get().getStudio().isAutoStartDefaultStudio()) {
Iris.info("Starting up auto Studio!");
Iris.debug("Starting up auto Studio!");
try {
Player r = new KList<>(getServer().getOnlinePlayers()).getRandom();
Iris.service(StudioSVC.class).open(r != null ? new VolmitSender(r) : getSender(), 1337, IrisSettings.get().getGenerator().getDefaultWorldType(), (w) -> {
@@ -782,7 +779,7 @@ public class Iris extends VolmitPlugin implements Listener, ReloadAware {
final Location spawn = w.getSpawnLocation();
for (Player i : getServer().getOnlinePlayers()) {
final Runnable playerTask = () -> {
i.setGameMode(GameMode.CREATIVE);
i.setGameMode(GameMode.SPECTATOR);
BukkitPlatform.teleportAsync(i, spawn);
};
if (!J.runEntity(i, playerTask)) {
@@ -802,10 +799,9 @@ public class Iris extends VolmitPlugin implements Listener, ReloadAware {
audiences = new Bindings.Adventure(this);
BukkitPlatform.hostAudiences(audiences);
} catch (Throwable e) {
e.printStackTrace();
IrisSettings.get().getGeneral().setUseConsoleCustomColors(false);
IrisSettings.get().getGeneral().setUseCustomColorsIngame(false);
Iris.error("Failed to setup Adventure API... No custom colors :(");
Iris.reportError("Failed to set up Adventure; custom colors are disabled.", e);
}
}
@@ -903,14 +899,14 @@ public class Iris extends VolmitPlugin implements Listener, ReloadAware {
if (IrisToolbelt.isServerStopping()) {
quiesceRuntimeForServerShutdown("pre-unload:" + reason);
startPostStopFinisher();
Iris.info("Pre-unload hook deferred generator teardown until Paper closes its chunk schedulers.");
Iris.debug("Pre-unload hook deferred generator teardown until Paper closes its chunk schedulers.");
return;
}
if (alreadyDrained.get()) {
Iris.info("Pre-unload hook skipped; Iris already drained.");
Iris.debug("Pre-unload hook skipped; Iris already drained.");
return;
}
Iris.info("BileTools pre-unload hook fired (" + reason + "). Freezing all Iris worlds.");
Iris.debug("BileTools pre-unload hook fired (" + reason + "). Freezing all Iris worlds.");
drainOnce("pre-unload:" + reason, 45L);
}
@@ -1088,7 +1084,7 @@ public class Iris extends VolmitPlugin implements Listener, ReloadAware {
}
}
if (generators.isEmpty()) {
Iris.info("No Iris worlds to freeze.");
Iris.debug("No Iris worlds to freeze.");
return;
}
@@ -1112,7 +1108,7 @@ public class Iris extends VolmitPlugin implements Listener, ReloadAware {
try {
CompletableFuture.allOf(closes.toArray(new CompletableFuture<?>[0]))
.get(timeoutSeconds, TimeUnit.SECONDS);
Iris.info("All Iris chunk generators parked. Safe to unload.");
Iris.debug("All Iris chunk generators parked. Safe to unload.");
} catch (TimeoutException e) {
Iris.warn("Iris generator drain timed out after " + timeoutSeconds + "s; unload proceeding anyway.");
} catch (InterruptedException e) {
@@ -1201,7 +1197,6 @@ public class Iris extends VolmitPlugin implements Listener, ReloadAware {
try {
Iris.syncJobs.next().run();
} catch (Throwable e) {
e.printStackTrace();
Iris.reportError(e);
}
}
@@ -1209,7 +1204,7 @@ public class Iris extends VolmitPlugin implements Listener, ReloadAware {
}
private void bstats() {
if (IrisSettings.get().getGeneral().isPluginMetrics()) {
if (IrisSettings.get().getGeneral().isMetrics()) {
Bindings.setupBstats(this);
}
}
@@ -242,7 +242,7 @@ public class CommandDeveloper implements DirectorExecutor {
try (CountingDataInputStream in = CountingDataInputStream.wrap(new BufferedInputStream(new FileInputStream(base)))) {
TectonicPlate.read(1088, in, true, IrisEngineMantle.createRuntimeDataAdapter(activeEngine.getData()), IrisEngineMantle.createRuntimeHooks());
} catch (Throwable e) {
e.printStackTrace();
Iris.reportError("Failed to inspect the Iris tectonic plate.", e);
}
} else {
Matter.read(section);
@@ -282,7 +282,7 @@ public class CommandDeveloper implements DirectorExecutor {
MCAFile MCARegion = MCAUtil.read(mca);
}
} catch (Exception e) {
e.printStackTrace();
Iris.reportError("Failed to inspect Minecraft region files.", e);
}
}
@@ -339,7 +339,7 @@ public class CommandDeveloper implements DirectorExecutor {
}
}
} catch (Exception e) {
e.printStackTrace();
Iris.reportError("Failed to inspect server network interfaces.", e);
}
}
@@ -22,22 +22,16 @@ import art.arcane.iris.Iris;
import art.arcane.iris.core.BukkitWorldReconciler;
import art.arcane.iris.platform.bukkit.BukkitPlatform;
import art.arcane.iris.core.IrisSettings;
import art.arcane.iris.core.IrisStartupValidation;
import art.arcane.iris.core.DatapackInstallResult;
import art.arcane.iris.core.IrisWorldStorage;
import art.arcane.iris.core.IrisWorlds;
import art.arcane.iris.core.PendingWorldReplacementManager;
import art.arcane.iris.core.ServerConfigurator;
import art.arcane.iris.core.lifecycle.BukkitWorldConfiguration;
import art.arcane.iris.core.lifecycle.IrisWorldRemovalService;
import art.arcane.iris.core.lifecycle.LifecycleOperationCoordinator;
import art.arcane.iris.core.lifecycle.WorldLifecycleService;
import art.arcane.iris.core.loader.IrisData;
import art.arcane.iris.core.nms.INMS;
import art.arcane.iris.core.pack.AtomicDirectoryPublisher;
import art.arcane.iris.core.pack.PackDownloader;
import art.arcane.iris.core.pack.PackDirectoryResolver;
import art.arcane.iris.core.pack.PackValidationRegistry;
import art.arcane.iris.core.service.StudioSVC;
import art.arcane.iris.core.tools.IrisToolbelt;
import art.arcane.iris.engine.platform.PlatformChunkGenerator;
@@ -62,9 +56,7 @@ import org.bukkit.World;
import org.bukkit.entity.Player;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
@@ -160,16 +152,6 @@ public class CommandIris implements DirectorExecutor {
return;
}
if (J.isFolia()) {
boolean staged = stageFoliaWorldCreation(worldName, dimension, seed);
if (!staged) {
return;
}
sender().sendMessage(IrisLanguage.text(BukkitCommandMessagesExtended.COMMAND_IRIS_WORLD_STAGING_COMPLETED_RESTARTING_SERVER_GENERATE_LOAD, MessageArgument.untrusted("worldName", worldName)));
ServerConfigurator.restart("Iris staged Folia world \"" + worldName + "\" for startup.");
return;
}
try {
IrisToolbelt.createWorld()
.dimension(resolvedType)
@@ -263,116 +245,6 @@ public class CommandIris implements DirectorExecutor {
}
}
private boolean stageFoliaWorldCreation(String name, IrisDimension dimension, long seed) {
try {
IrisStartupValidation.requireWorldCreationReady();
PackValidationRegistry.requireLoadable(
dimension.getLoader().getDataFolder().getName());
} catch (RuntimeException exception) {
sender().sendMessage(C.RED + exception.getMessage());
return false;
}
NamespacedKey worldKey = IrisWorldStorage.managedKeyFromName(name);
LifecycleOperationCoordinator.Lease worldLease = null;
File worldFolder = IrisWorldStorage.requireSafeManagedDimensionRoot(worldKey);
Path stagedWorld = null;
try {
LifecycleOperationCoordinator coordinator = LifecycleOperationCoordinator.get();
worldLease = coordinator.acquire(
LifecycleOperationCoordinator.Domain.WORLD_MUTATION,
LifecycleOperationCoordinator.OperationKind.WORLD_CREATE,
worldKey.toString());
sender().sendMessage(IrisLanguage.text(BukkitCommandMessagesExtended.COMMAND_IRIS_RUNTIME_WORLD_CREATION_IS_DISABLED_ON_FOLIA));
sender().sendMessage(IrisLanguage.text(BukkitCommandMessagesExtended.COMMAND_IRIS_PREPARING_WORLD_FILES_BUKKIT_YML_NEXT_STARTUP));
if (worldFolder.exists()) {
sender().sendMessage(IrisLanguage.text(BukkitCommandMessagesExtended.COMMAND_IRIS_THAT_FOLDER_ALREADY_EXISTS));
return false;
}
DatapackInstallResult datapackResult = ServerConfigurator.installDataPacksIfChanged(true);
if (!datapackResult.succeeded()) {
sender().sendMessage(C.RED + "Failed to compile the Iris datapack. No world files were staged.");
return false;
}
Path targetWorld = worldFolder.toPath().toAbsolutePath().normalize();
Path namespaceRoot = targetWorld.getParent();
if (namespaceRoot == null) {
throw new IOException("Iris world target has no namespace directory: " + targetWorld);
}
Files.createDirectories(namespaceRoot);
stagedWorld = Files.createTempDirectory(namespaceRoot, ".iris-create-" + worldKey.getKey() + "-");
Path sourceOverworld = IrisWorldStorage.dimensionRoot(
IrisWorldStorage.levelRoot(),
NamespacedKey.minecraft("overworld")
).toPath();
INMS.get().writeCurrentPaperWorldData(sourceOverworld, stagedWorld, seed);
IrisDimension installed = Iris.service(StudioSVC.class).installIntoWorld(
sender(),
dimension,
stagedWorld.toFile()
);
if (installed == null) {
sender().sendMessage(IrisLanguage.text(BukkitCommandMessagesExtended.COMMAND_IRIS_FAILED_STAGE_WORLD_FILES_DIMENSION, MessageArgument.untrusted("value", dimension.getLoadKey())));
return false;
}
try (AtomicDirectoryPublisher.Publication publication = AtomicDirectoryPublisher.publishAbsent(
stagedWorld,
targetWorld
)) {
stagedWorld = null;
if (!registerWorldInBukkitYml(worldKey, dimension.getLoadKey(), seed)) {
return false;
}
publication.commit();
}
sender().sendMessage(IrisLanguage.text(BukkitCommandMessagesExtended.COMMAND_IRIS_STAGED_IRIS_WORLD_WITH_GENERATOR_IRIS_SEED, MessageArgument.untrusted("name", name), MessageArgument.untrusted("value", dimension.getLoadKey()), MessageArgument.untrusted("seed", seed)));
return true;
} catch (LifecycleOperationCoordinator.BusyException e) {
sender().sendMessage(C.YELLOW + e.getMessage());
return false;
} catch (Throwable e) {
sender().sendMessage(C.RED + "Failed to stage the complete Iris world: " + e.getMessage());
Iris.reportError("Failed to stage complete Folia world \"" + worldKey + "\".", e);
return false;
} finally {
if (stagedWorld != null) {
deleteDirectorySafely(stagedWorld.toFile());
}
if (worldLease != null) {
worldLease.close();
}
}
}
private boolean registerWorldInBukkitYml(NamespacedKey worldKey, String dimension, Long seed) {
String configuredWorldName = IrisWorldStorage.configuredWorldName(
worldKey,
IrisWorldStorage.levelRoot().getName()
);
try {
BukkitWorldConfiguration.register(BUKKIT_YML, configuredWorldName, dimension, seed);
Iris.info("Registered \"" + configuredWorldName + "\" in bukkit.yml");
return true;
} catch (IOException e) {
sender().sendMessage(IrisLanguage.text(BukkitCommandMessagesExtended.COMMAND_IRIS_FAILED_UPDATE_BUKKIT_YML, MessageArgument.untrusted("value", String.valueOf(e.getMessage()))));
Iris.error("Failed to update bukkit.yml!");
Iris.reportError(e);
return false;
}
}
private void deleteDirectorySafely(File directory) {
try {
AtomicDirectoryPublisher.deleteTree(directory.toPath());
} catch (IOException e) {
Iris.reportError("Failed to roll back staged world folder \"" + directory.getAbsolutePath() + "\".", e);
}
}
private boolean reportExpectedCreationInterruption(Throwable failure) {
Throwable current = failure;
while (current != null) {
@@ -24,6 +24,7 @@ import art.arcane.iris.core.link.WorldEditLink;
import art.arcane.iris.core.loader.IrisData;
import art.arcane.iris.core.pack.PackDirectoryResolver;
import art.arcane.iris.core.runtime.ObjectStudioActivation;
import art.arcane.iris.core.runtime.StudioOpenCoordinator;
import art.arcane.iris.core.runtime.WorldRuntimeControlService;
import art.arcane.iris.core.service.ObjectSVC;
import art.arcane.iris.core.service.StudioSVC;
@@ -171,24 +172,29 @@ public class CommandObject implements DirectorExecutor {
IrisDimension finalHost = hostDimension;
try {
Iris.service(StudioSVC.class).open(commandSender, seed, hostDimension.getLoadKey(), world -> {
if (world == null) return;
try {
WorldRuntimeControlService.get().applyObjectStudioWorldRules(world);
} catch (Throwable e) {
Iris.reportError("Failed to apply object studio world rules for " + world.getName(), e);
}
Iris.service(StudioSVC.class).open(
commandSender,
seed,
hostDimension.getLoadKey(),
StudioOpenCoordinator.StudioOpenKind.OBJECT,
world -> {
if (world == null) return;
try {
WorldRuntimeControlService.get().applyObjectStudioWorldRules(world);
} catch (Throwable e) {
Iris.reportError("Failed to apply object studio world rules for " + world.getName(), e);
}
if (commandSender.isPlayer()) {
Player p = commandSender.player();
if (p != null) {
Location target = new Location(world, 0.5D, 66D, 0.5D);
J.runEntity(p, () -> {
BukkitPlatform.teleportAsync(p, target).thenRun(() -> p.setGameMode(GameMode.CREATIVE));
});
}
}
});
if (commandSender.isPlayer()) {
Player p = commandSender.player();
if (p != null) {
Location target = new Location(world, 0.5D, 66D, 0.5D);
J.runEntity(p, () -> {
BukkitPlatform.teleportAsync(p, target).thenRun(() -> p.setGameMode(GameMode.CREATIVE));
});
}
}
});
} catch (Throwable e) {
Iris.reportError("Failed to open object studio world \"" + finalHost.getLoadKey() + "\".", e);
commandSender.sendMessage(IrisLanguage.text(BukkitCommandMessages.COMMAND_OBJECT_FAILED_OPEN_OBJECT_STUDIO, MessageArgument.untrusted("value", String.valueOf(e.getMessage()))));
@@ -360,7 +366,7 @@ public class CommandObject implements DirectorExecutor {
o.write(o.getLoadFile());
} catch (IOException e) {
sender().sendMessage(IrisLanguage.text(BukkitCommandMessagesExtended.COMMAND_OBJECT_FAILED_SAVE_OBJECT, MessageArgument.untrusted("value", o.getLoadFile()), MessageArgument.untrusted("value2", String.valueOf(e.getMessage()))));
e.printStackTrace();
Iris.reportError("Failed to save object " + o.getLoadFile() + ".", e);
}
}
@@ -426,7 +432,7 @@ public class CommandObject implements DirectorExecutor {
try {
IrisConverter.convertSchematics(sender());
} catch (Exception e) {
e.printStackTrace();
Iris.reportError("Failed to convert schematics to Iris objects.", e);
}
}
@@ -89,7 +89,6 @@ public class CommandPregen implements DirectorExecutor {
} catch (Throwable e) {
sender().sendMessage(IrisLanguage.text(BukkitCommandMessagesExtended.COMMAND_PREGEN_FAILED_START_PREGENERATION_SEE_CONSOLE_DETAILS));
Iris.reportError(e);
e.printStackTrace();
}
}
@@ -77,7 +77,6 @@ import art.arcane.volmlib.util.scheduling.PrecisionStopwatch;
import org.bukkit.Bukkit;
import org.bukkit.Chunk;
import org.bukkit.FluidCollisionMode;
import org.bukkit.GameMode;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.entity.Player;
@@ -573,7 +572,6 @@ public class CommandStudio implements DirectorExecutor {
IO.writeAll(report, fileText.toString("\n"));
} catch (IOException e) {
Iris.reportError(e);
e.printStackTrace();
}
sender().sendMessage(IrisLanguage.text(BukkitCommandMessagesExtended.COMMAND_STUDIO_DONE, MessageArgument.untrusted("value", report.getPath())));
@@ -663,9 +661,6 @@ public class CommandStudio implements DirectorExecutor {
Iris.reportError("Studio teleport failed for player \"" + player.getName() + "\".", failure);
return;
}
if (Boolean.TRUE.equals(teleported)) {
J.runEntity(player, () -> player.setGameMode(GameMode.CREATIVE));
}
});
}
@@ -831,7 +826,6 @@ public class CommandStudio implements DirectorExecutor {
sender().sendMessage(IrisLanguage.text(BukkitCommandMessagesExtended.COMMAND_STUDIO_REPORTED, MessageArgument.untrusted("value", ff.getPath())));
} catch (Throwable e) {
e.printStackTrace();
Iris.reportError(e);
}
}
@@ -18,14 +18,17 @@
package art.arcane.iris.core.gui;
import art.arcane.iris.platform.bukkit.BukkitPlatform;
import art.arcane.iris.platform.bukkit.BukkitWorldBinding;
import art.arcane.iris.core.runtime.WorldRuntimeControlService;
import art.arcane.iris.engine.IrisComplex;
import art.arcane.iris.engine.framework.Engine;
import art.arcane.iris.engine.framework.render.RenderType;
import art.arcane.iris.engine.object.IrisWorld;
import art.arcane.iris.platform.bukkit.BukkitPlatform;
import art.arcane.iris.platform.bukkit.BukkitWorldBinding;
import art.arcane.iris.spi.IrisLogging;
import art.arcane.iris.util.common.scheduling.J;
import art.arcane.volmlib.util.format.Form;
import org.bukkit.Chunk;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.entity.LivingEntity;
@@ -36,15 +39,21 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;
import static art.arcane.iris.util.common.data.registry.Attributes.MAX_HEALTH;
public final class BukkitVisionOverlay implements GuiOverlay {
private final Engine engine;
private final AtomicBoolean nativeTeleportActive = new AtomicBoolean();
private final AtomicBoolean playerRefreshQueued = new AtomicBoolean();
private final AtomicLong teleportSequence = new AtomicLong();
private final AtomicReference<VisionTeleportRequest> latestTeleport = new AtomicReference<>();
private volatile List<GuiMarker> playerMarkers = List.of();
public BukkitVisionOverlay(Engine engine) {
@@ -144,25 +153,198 @@ public final class BukkitVisionOverlay implements GuiOverlay {
@Override
public void teleport(double worldX, double worldZ) {
IrisWorld target = engine.getWorld();
if (!target.hasPlatformWorld()) {
VisionTeleportRequest request = new VisionTeleportRequest(
teleportSequence.incrementAndGet(),
VisionGUI.floorWorldCoordinate(worldX),
VisionGUI.floorWorldCoordinate(worldZ));
latestTeleport.set(request);
startTeleport(request);
}
private void startTeleport(VisionTeleportRequest request) {
if (!request.processing.compareAndSet(false, true)) {
return;
}
J.runGlobal(() -> {
boolean scheduled = J.runGlobal(() -> {
IrisWorld target = engine.getWorld();
if (!isCurrent(request, target)) {
finish(request);
return;
}
World world = BukkitWorldBinding.world(target);
if (world == null) {
finish(request);
return;
}
List<Player> players = BukkitWorldBinding.players(target);
if (players.isEmpty()) {
finish(request);
return;
}
Player player = players.get(0);
World world = player.getWorld();
int xx = (int) worldX;
int zz = (int) worldZ;
J.runRegion(world, xx >> 4, zz >> 4, () -> {
int yy = world.getHighestBlockYAt(xx, zz) + 1;
Location destination = new Location(world, xx, yy, zz);
J.runEntity(player, () -> BukkitPlatform.teleportAsync(player, destination));
});
requestTeleportChunk(request, target, player, world);
});
if (!scheduled) {
finish(request);
}
}
private void requestTeleportChunk(
VisionTeleportRequest request,
IrisWorld target,
Player player,
World world
) {
int blockX = request.blockX;
int blockZ = request.blockZ;
int chunkX = blockX >> 4;
int chunkZ = blockZ >> 4;
CompletableFuture<Chunk> requested;
try {
requested = WorldRuntimeControlService.get().requestChunkAsync(
world,
chunkX,
chunkZ,
true,
true
);
} catch (Throwable failure) {
fail(request, target, world, failure);
return;
}
if (requested == null) {
fail(request, target, world, new IllegalStateException(
"Vision destination chunk request returned no future."));
return;
}
requested.whenComplete((chunk, failure) -> {
if (!isCurrent(request, target)) {
finish(request);
return;
}
if (failure != null) {
fail(request, target, world, failure);
return;
}
if (chunk == null || chunk.getWorld() != world) {
fail(request, target, world, new IllegalStateException(
"Vision destination chunk request returned no chunk."));
return;
}
boolean scheduled = J.runRegion(world, chunkX, chunkZ, () -> {
if (!isCurrent(request, target)) {
finish(request);
return;
}
int yy = world.getHighestBlockYAt(blockX, blockZ) + 1;
Location destination = new Location(world, blockX, yy, blockZ);
if (!J.runEntity(player, () -> delegateTeleport(
request,
target,
player,
world,
destination))) {
fail(request, target, world, new IllegalStateException(
"Failed to schedule the Vision teleport on the player entity."));
}
});
if (!scheduled) {
fail(request, target, world, new IllegalStateException(
"Failed to schedule the Vision surface lookup on its owning region."));
}
});
}
private void delegateTeleport(
VisionTeleportRequest request,
IrisWorld target,
Player player,
World world,
Location destination
) {
if (!isCurrent(request, target) || !player.isOnline() || player.getWorld() != world) {
finish(request);
return;
}
if (!nativeTeleportActive.compareAndSet(false, true)) {
finish(request);
return;
}
if (!isCurrent(request, target)) {
nativeTeleportActive.set(false);
finish(request);
restartLatest(request);
return;
}
CompletableFuture<Boolean> teleport;
try {
teleport = BukkitPlatform.teleportAsync(player, destination);
} catch (Throwable failure) {
nativeTeleportActive.set(false);
fail(request, target, world, failure);
restartLatest(request);
return;
}
if (teleport == null) {
nativeTeleportActive.set(false);
fail(request, target, world, new IllegalStateException(
"Vision teleport returned no completion future."));
restartLatest(request);
return;
}
teleport.whenComplete((success, failure) -> {
nativeTeleportActive.set(false);
finish(request);
if (isCurrent(request, target)) {
if (failure != null) {
reportTeleportFailure(world, request.blockX, request.blockZ, failure);
} else if (!Boolean.TRUE.equals(success)) {
reportTeleportFailure(world, request.blockX, request.blockZ, new IllegalStateException(
"Vision teleport did not complete successfully."));
}
}
restartLatest(request);
});
}
private boolean isCurrent(VisionTeleportRequest request, IrisWorld target) {
VisionTeleportRequest current = latestTeleport.get();
return current != null
&& current.sequence == request.sequence
&& target != null
&& engine.getWorld() == target
&& target.hasPlatformWorld()
&& !engine.isClosing()
&& !engine.isClosed();
}
private void fail(
VisionTeleportRequest request,
IrisWorld target,
World world,
Throwable failure
) {
finish(request);
if (isCurrent(request, target)) {
reportTeleportFailure(world, request.blockX, request.blockZ, failure);
}
}
private void finish(VisionTeleportRequest request) {
request.processing.set(false);
}
private void restartLatest(VisionTeleportRequest completed) {
VisionTeleportRequest current = latestTeleport.get();
if (current != null && current != completed) {
startTeleport(current);
}
}
private void reportTeleportFailure(World world, int blockX, int blockZ, Throwable failure) {
IrisLogging.reportError("Vision could not teleport to " + world.getName() + "@"
+ blockX + "," + blockZ + ".", failure);
}
@Override
@@ -179,4 +361,18 @@ public final class BukkitVisionOverlay implements GuiOverlay {
};
return file == null ? null : file.getName();
}
private static final class VisionTeleportRequest {
private final long sequence;
private final int blockX;
private final int blockZ;
private final AtomicBoolean processing;
private VisionTeleportRequest(long sequence, int blockX, int blockZ) {
this.sequence = sequence;
this.blockX = blockX;
this.blockZ = blockZ;
processing = new AtomicBoolean(false);
}
}
}
@@ -605,7 +605,6 @@ public final class IrisEngineSVC implements IrisService {
private static void reportFailure(String message, Throwable exception) {
IrisLogging.reportError(exception);
IrisLogging.error("EngineSVC: " + message);
exception.printStackTrace();
}
private final class Registered {
@@ -20,6 +20,7 @@ package art.arcane.iris.core.service;
import art.arcane.iris.Iris;
import art.arcane.iris.core.protocol.EngineResolver;
import art.arcane.iris.core.protocol.IrisCursorRequestService;
import art.arcane.iris.core.protocol.IrisProtocolServer;
import art.arcane.iris.core.protocol.IrisServerTransport;
import art.arcane.iris.core.protocol.IrisSession;
@@ -55,6 +56,7 @@ public class IrisProtocolService implements IrisService, PluginMessageListener,
private IrisSessionRegistry registry;
private IrisProtocolServer protocolServer;
private IrisCursorRequestService cursorService;
private IrisVisionRequestService visionService;
@Override
@@ -66,6 +68,8 @@ public class IrisProtocolService implements IrisService, PluginMessageListener,
protocolServer = new IrisProtocolServer(registry, SERVER_CAPABILITIES, brand(), true);
EngineResolver engineResolver = IrisProtocolService::resolveEngine;
protocolServer.setEngineResolver(engineResolver);
cursorService = IrisCursorRequestService.create(engineResolver, registry);
protocolServer.setCursorInfoHandler(cursorService);
visionService = IrisVisionRequestService.create(engineResolver, registry);
protocolServer.setVisionTileHandler(visionService);
IrisServices.register(IrisProtocolServer.class, protocolServer);
@@ -81,13 +85,22 @@ public class IrisProtocolService implements IrisService, PluginMessageListener,
messenger.unregisterIncomingPluginChannel(Iris.instance, IrisProtocol.CHANNEL, this);
messenger.unregisterOutgoingPluginChannel(Iris.instance, IrisProtocol.CHANNEL);
IrisSessionRegistry current = registry;
IrisCursorRequestService cursor = cursorService;
IrisVisionRequestService vision = visionService;
if (current != null) {
for (IrisSession session : current.all()) {
current.unregister(session.id());
if (cursor != null) {
cursor.clearSession(session.id());
}
if (vision != null) {
vision.clearSession(session.id());
}
}
}
registry = null;
protocolServer = null;
cursorService = null;
visionService = null;
}
@@ -117,6 +130,10 @@ public class IrisProtocolService implements IrisService, PluginMessageListener,
}
String sessionId = event.getPlayer().getUniqueId().toString();
current.unregister(sessionId);
IrisCursorRequestService cursor = cursorService;
if (cursor != null) {
cursor.clearSession(sessionId);
}
IrisVisionRequestService vision = visionService;
if (vision != null) {
vision.clearSession(sessionId);
@@ -51,6 +51,7 @@ import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.block.Action;
import org.bukkit.event.player.PlayerInteractEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import org.bukkit.inventory.EquipmentSlot;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.ItemFlag;
@@ -61,18 +62,30 @@ import org.bukkit.util.BlockVector;
import org.bukkit.util.Vector;
import java.awt.Color;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicBoolean;
import static art.arcane.iris.util.common.data.registry.Particles.CRIT_MAGIC;
import static art.arcane.iris.util.common.data.registry.Particles.REDSTONE;
public class WandSVC implements IrisService {
private static final int MS_PER_TICK = Integer.parseInt(System.getProperty("iris.ms_per_tick", "30"));
private static final int PLAYER_RESCAN_INTERVAL_TICKS = 100;
private static ItemStack dust;
private static ItemStack wand;
private final Map<UUID, Player> activePlayers = new ConcurrentHashMap<>();
private final AtomicBoolean playerRescanScheduled = new AtomicBoolean(false);
private volatile boolean enabled;
private int taskId = -1;
private int ticksUntilPlayerRescan = 0;
public static void pasteSchematic(IrisObject s, Location at) {
s.place(at);
}
@@ -174,7 +187,6 @@ public class WandSVC implements IrisService {
return s;
} catch (Throwable e) {
e.printStackTrace();
Iris.reportError(e);
}
@@ -196,7 +208,6 @@ public class WandSVC implements IrisService {
return WorldMatter.createMatter(p.getName(), f[0], f[1]);
} catch (Throwable e) {
e.printStackTrace();
Iris.reportError(e);
}
@@ -307,8 +318,18 @@ public class WandSVC implements IrisService {
}
public static Location[] getCuboidFromItem(ItemStack is) {
if (is == null) {
return new Location[]{null, null};
}
ItemMeta im = is.getItemMeta();
return new Location[]{stringToLocation(im.getLore().get(0)), stringToLocation(im.getLore().get(1))};
if (im == null) {
return new Location[]{null, null};
}
List<String> lore = im.getLore();
if (lore == null || lore.size() < 2) {
return new Location[]{null, null};
}
return new Location[]{stringToLocation(lore.get(0)), stringToLocation(lore.get(1))};
}
public static Location[] getCuboid(Player p) {
@@ -343,30 +364,47 @@ public class WandSVC implements IrisService {
* @return True if it is
*/
public static boolean isWand(ItemStack is) {
if (is == null || is.getItemMeta() == null) {
if (is == null) {
return false;
}
Byte marker = is.getItemMeta().getPersistentDataContainer().get(wandKey(), PersistentDataType.BYTE);
if (marker != null && marker == (byte) 1) {
ItemMeta meta = is.getItemMeta();
if (meta == null) {
return false;
}
Byte marker = meta.getPersistentDataContainer().get(wandKey(), PersistentDataType.BYTE);
if (marker != null && marker.byteValue() == 1) {
return true;
}
return is.getType().equals(wand.getType()) &&
is.getItemMeta().getDisplayName().equals(wand.getItemMeta().getDisplayName()) &&
is.getItemMeta().getEnchants().equals(wand.getItemMeta().getEnchants()) &&
is.getItemMeta().getItemFlags().equals(wand.getItemMeta().getItemFlags());
ItemStack template = wand;
if (template == null || !is.getType().equals(template.getType())) {
return false;
}
ItemMeta templateMeta = template.getItemMeta();
return templateMeta != null
&& Objects.equals(meta.getDisplayName(), templateMeta.getDisplayName())
&& meta.getEnchants().equals(templateMeta.getEnchants())
&& meta.getItemFlags().equals(templateMeta.getItemFlags());
}
@Override
public void onEnable() {
wand = createWand();
dust = createDust();
J.ar(this::tickAll, 0);
enabled = true;
activePlayers.clear();
ticksUntilPlayerRescan = 0;
taskId = J.ar(this::tickAll, 1);
}
@Override
public void onDisable() {
enabled = false;
if (taskId != -1) {
J.car(taskId);
taskId = -1;
}
activePlayers.clear();
playerRescanScheduled.set(false);
}
/**
@@ -375,11 +413,16 @@ public class WandSVC implements IrisService {
*/
private void tickAll() {
try {
J.runGlobal(() -> {
for (Player p : Bukkit.getOnlinePlayers()) {
J.runEntity(p, () -> tick(p));
}
});
if (!enabled) {
return;
}
if (ticksUntilPlayerRescan-- <= 0) {
ticksUntilPlayerRescan = PLAYER_RESCAN_INTERVAL_TICKS;
rescanPlayers();
}
for (Player player : activePlayers.values()) {
J.runEntity(player, () -> tick(player));
}
} catch (Throwable e) {
Iris.reportError(e);
}
@@ -387,20 +430,53 @@ public class WandSVC implements IrisService {
public void tick(Player p) {
try {
try {
if ((IrisSettings.get().getWorld().worldEditWandCUI && isHoldingWand(p)) || isWand(p.getInventory().getItemInMainHand())) {
Location[] d = getCuboid(p);
if (d == null || d[0] == null || d[1] == null) return;
new WandSelection(new Cuboid(d[0], d[1]), p).draw();
}
} catch (Throwable e) {
Iris.reportError(e);
if (!p.isOnline()) {
activePlayers.remove(p.getUniqueId(), p);
return;
}
Location[] selection = getCuboid(p);
if (!hasCompleteSelection(selection)) {
activePlayers.remove(p.getUniqueId(), p);
return;
}
new WandSelection(new Cuboid(selection[0], selection[1]), p).draw();
} catch (Throwable e) {
e.printStackTrace();
Iris.reportError(e);
}
}
private void rescanPlayers() {
if (!playerRescanScheduled.compareAndSet(false, true)) {
return;
}
if (!J.runGlobal(() -> {
try {
if (!enabled) {
return;
}
for (Player player : Bukkit.getOnlinePlayers()) {
J.runEntity(player, () -> refreshPlayer(player));
}
} finally {
playerRescanScheduled.set(false);
}
})) {
playerRescanScheduled.set(false);
}
}
private void refreshPlayer(Player player) {
if (enabled && player.isOnline() && hasCompleteSelection(getCuboid(player))) {
activePlayers.put(player.getUniqueId(), player);
return;
}
activePlayers.remove(player.getUniqueId(), player);
}
private static boolean hasCompleteSelection(Location[] selection) {
return selection != null && selection.length >= 2 && selection[0] != null && selection[1] != null;
}
/**
* Draw the outline of a selected region
*
@@ -492,6 +568,7 @@ public class WandSVC implements IrisService {
return;
try {
if (isHoldingIrisWand(e.getPlayer())) {
activePlayers.put(e.getPlayer().getUniqueId(), e.getPlayer());
if (e.getAction().equals(Action.LEFT_CLICK_BLOCK)) {
e.setCancelled(true);
e.getPlayer().getInventory().setItemInMainHand(update(true, Objects.requireNonNull(e.getClickedBlock()).getLocation(), e.getPlayer().getInventory().getItemInMainHand()));
@@ -517,6 +594,11 @@ public class WandSVC implements IrisService {
}
}
@EventHandler
public void on(PlayerQuitEvent event) {
activePlayers.remove(event.getPlayer().getUniqueId());
}
/**
* Is the player holding Dust?
*
@@ -23,16 +23,18 @@ import art.arcane.volmlib.util.math.M;
import org.bukkit.Location;
import org.bukkit.Particle;
import org.bukkit.entity.Player;
import org.bukkit.util.Vector;
import java.awt.Color;
import static art.arcane.iris.util.common.data.registry.Particles.REDSTONE;
public class WandSelection {
private static final double STEP = 0.10;
private static final double MAX_DISTANCE = 256D;
private static final double MAX_DISTANCE_SQUARED = MAX_DISTANCE * MAX_DISTANCE;
private final Cuboid c;
private final Player p;
private static final double STEP = 0.10;
public WandSelection(Cuboid c, Player p) {
this.c = c;
@@ -45,57 +47,91 @@ public class WandSelection {
return;
}
double maxDistanceSquared = 256 * 256;
int particleCount = 0;
// cube!
Location[][] edges = {
{c.getLowerNE(), new Location(c.getWorld(), c.getUpperX() + 1, c.getLowerY(), c.getLowerZ())},
{c.getLowerNE(), new Location(c.getWorld(), c.getLowerX(), c.getUpperY() + 1, c.getLowerZ())},
{c.getLowerNE(), new Location(c.getWorld(), c.getLowerX(), c.getLowerY(), c.getUpperZ() + 1)},
{new Location(c.getWorld(), c.getUpperX() + 1, c.getLowerY(), c.getLowerZ()), new Location(c.getWorld(), c.getUpperX() + 1, c.getUpperY() + 1, c.getLowerZ())},
{new Location(c.getWorld(), c.getUpperX() + 1, c.getLowerY(), c.getLowerZ()), new Location(c.getWorld(), c.getUpperX() + 1, c.getLowerY(), c.getUpperZ() + 1)},
{new Location(c.getWorld(), c.getLowerX(), c.getUpperY() + 1, c.getLowerZ()), new Location(c.getWorld(), c.getUpperX() + 1, c.getUpperY() + 1, c.getLowerZ())},
{new Location(c.getWorld(), c.getLowerX(), c.getUpperY() + 1, c.getLowerZ()), new Location(c.getWorld(), c.getLowerX(), c.getUpperY() + 1, c.getUpperZ() + 1)},
{new Location(c.getWorld(), c.getLowerX(), c.getLowerY(), c.getUpperZ() + 1), new Location(c.getWorld(), c.getUpperX() + 1, c.getLowerY(), c.getUpperZ() + 1)},
{new Location(c.getWorld(), c.getLowerX(), c.getLowerY(), c.getUpperZ() + 1), new Location(c.getWorld(), c.getLowerX(), c.getUpperY() + 1, c.getUpperZ() + 1)},
{new Location(c.getWorld(), c.getUpperX() + 1, c.getUpperY() + 1, c.getLowerZ()), new Location(c.getWorld(), c.getUpperX() + 1, c.getUpperY() + 1, c.getUpperZ() + 1)},
{new Location(c.getWorld(), c.getLowerX(), c.getUpperY() + 1, c.getUpperZ() + 1), new Location(c.getWorld(), c.getUpperX() + 1, c.getUpperY() + 1, c.getUpperZ() + 1)},
{new Location(c.getWorld(), c.getUpperX() + 1, c.getLowerY(), c.getUpperZ() + 1), new Location(c.getWorld(), c.getUpperX() + 1, c.getUpperY() + 1, c.getUpperZ() + 1)}
};
double minX = c.getLowerX();
double minY = c.getLowerY();
double minZ = c.getLowerZ();
double maxX = c.getUpperX() + 1D;
double maxY = c.getUpperY() + 1D;
double maxZ = c.getUpperZ() + 1D;
double playerX = playerLoc.getX();
double playerY = playerLoc.getY();
double playerZ = playerLoc.getZ();
for (Location[] edge : edges) {
Vector direction = edge[1].toVector().subtract(edge[0].toVector());
double length = direction.length();
direction.normalize();
drawX(minX, maxX, minY, minZ, playerX, playerY, playerZ);
drawX(minX, maxX, maxY, minZ, playerX, playerY, playerZ);
drawX(minX, maxX, minY, maxZ, playerX, playerY, playerZ);
drawX(minX, maxX, maxY, maxZ, playerX, playerY, playerZ);
drawY(minY, maxY, minX, minZ, playerX, playerY, playerZ);
drawY(minY, maxY, maxX, minZ, playerX, playerY, playerZ);
drawY(minY, maxY, minX, maxZ, playerX, playerY, playerZ);
drawY(minY, maxY, maxX, maxZ, playerX, playerY, playerZ);
drawZ(minZ, maxZ, minX, minY, playerX, playerY, playerZ);
drawZ(minZ, maxZ, maxX, minY, playerX, playerY, playerZ);
drawZ(minZ, maxZ, minX, maxY, playerX, playerY, playerZ);
drawZ(minZ, maxZ, maxX, maxY, playerX, playerY, playerZ);
}
for (double d = 0; d <= length; d += STEP) {
Location particleLoc = edge[0].clone().add(direction.clone().multiply(d));
private void drawX(double start, double end, double y, double z, double playerX, double playerY, double playerZ) {
double fixedDistanceSquared = square(playerY - y) + square(playerZ - z);
drawAxis(start, end, playerX, fixedDistanceSquared, (double coordinate, double distanceSquared) ->
spawnParticle(coordinate, y, z, distanceSquared));
}
if (playerLoc.distanceSquared(particleLoc) > maxDistanceSquared) {
continue;
}
private void drawY(double start, double end, double x, double z, double playerX, double playerY, double playerZ) {
double fixedDistanceSquared = square(playerX - x) + square(playerZ - z);
drawAxis(start, end, playerY, fixedDistanceSquared, (double coordinate, double distanceSquared) ->
spawnParticle(x, coordinate, z, distanceSquared));
}
spawnParticle(particleLoc, playerLoc);
particleCount++;
}
private void drawZ(double start, double end, double x, double y, double playerX, double playerY, double playerZ) {
double fixedDistanceSquared = square(playerX - x) + square(playerY - y);
drawAxis(start, end, playerZ, fixedDistanceSquared, (double coordinate, double distanceSquared) ->
spawnParticle(x, y, coordinate, distanceSquared));
}
private void drawAxis(double start, double end, double playerCoordinate, double fixedDistanceSquared, AxisParticle particle) {
if (fixedDistanceSquared > MAX_DISTANCE_SQUARED) {
return;
}
double visibleRadius = Math.sqrt(MAX_DISTANCE_SQUARED - fixedDistanceSquared);
double visibleStart = Math.max(start, playerCoordinate - visibleRadius);
double visibleEnd = Math.min(end, playerCoordinate + visibleRadius);
if (visibleStart > visibleEnd) {
return;
}
int firstSample = (int) Math.max(0D, Math.ceil((visibleStart - start) / STEP));
int lastSample = (int) Math.floor((visibleEnd - start) / STEP);
for (int index = firstSample; index <= lastSample; index++) {
double coordinate = start + index * STEP;
double distanceSquared = fixedDistanceSquared + square(playerCoordinate - coordinate);
particle.spawn(coordinate, distanceSquared);
}
}
private void spawnParticle(Location particleLoc, Location playerLoc) {
double accuracy = M.lerpInverse(0, 64 * 64, playerLoc.distanceSquared(particleLoc));
private void spawnParticle(double x, double y, double z, double distanceSquared) {
double accuracy = M.lerpInverse(0, 64 * 64, distanceSquared);
double dist = M.lerp(0.125, 3.5, accuracy);
if (M.r(Math.min(dist * 5, 0.9D) * 0.995)) {
return;
}
float hue = (float) (0.5f + (Math.sin((particleLoc.getX() + particleLoc.getY() + particleLoc.getZ() + (p.getTicksLived() / 2f)) / 20f) / 2));
float hue = (float) (0.5f + (Math.sin((x + y + z + (p.getTicksLived() / 2f)) / 20f) / 2));
Color color = Color.getHSBColor(hue, 1, 1);
p.spawnParticle(REDSTONE, particleLoc,
p.spawnParticle(REDSTONE, x, y, z,
0, 0, 0, 0, 1,
new Particle.DustOptions(org.bukkit.Color.fromRGB(color.getRed(), color.getGreen(), color.getBlue()),
(float) dist * 3f));
}
private static double square(double value) {
return value * value;
}
@FunctionalInterface
private interface AxisParticle {
void spawn(double coordinate, double distanceSquared);
}
}
@@ -5,44 +5,31 @@ import org.junit.Test;
import java.nio.file.Files;
import java.nio.file.Path;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
public class CommandIrisFoliaCreateContractTest {
@Test
public void ordinaryFoliaCreateRestartsOnlyAfterSuccessfulStagingAndFeedback() throws Exception {
public void ordinaryFoliaCreateUsesTheSharedRuntimeCreationPath() throws Exception {
String source = Files.readString(Path.of(System.getProperty("iris.commandIrisSource")));
String foliaCreate = source.substring(
source.indexOf("if (J.isFolia()) {"),
source.indexOf(" try {", source.indexOf("if (J.isFolia()) {"))
String create = source.substring(
source.indexOf(" public void create("),
source.indexOf(" @Director(", source.indexOf(" public void create("))
);
int stage = foliaCreate.indexOf("stageFoliaWorldCreation(worldName, dimension, seed)");
int failureExit = foliaCreate.indexOf("if (!staged)");
int feedback = foliaCreate.indexOf("COMMAND_IRIS_WORLD_STAGING_COMPLETED_RESTARTING_SERVER_GENERATE_LOAD");
int restart = foliaCreate.indexOf("ServerConfigurator.restart(\"Iris staged Folia world");
assertTrue(stage >= 0);
assertTrue(stage < failureExit);
assertTrue(failureExit < feedback);
assertTrue(feedback < restart);
assertTrue(create.contains("IrisToolbelt.createWorld()"));
assertTrue(create.contains(".studio(false)"));
assertTrue(create.contains(".create();"));
assertFalse(create.contains("J.isFolia()"));
assertFalse(create.contains("ServerConfigurator.restart("));
}
@Test
public void foliaStagePublishesCurrentPaperDataBeforeRegisteringStartupAlias() throws Exception {
public void obsoleteFoliaStagingSurfaceIsRemoved() throws Exception {
String source = Files.readString(Path.of(System.getProperty("iris.commandIrisSource")));
int methodStart = source.indexOf("private boolean stageFoliaWorldCreation(");
int methodEnd = source.indexOf("private boolean registerWorldInBukkitYml(", methodStart);
String staging = source.substring(methodStart, methodEnd);
int currentPaperData = staging.indexOf("INMS.get().writeCurrentPaperWorldData(");
int pack = staging.indexOf("installIntoWorld(");
int publication = staging.indexOf("AtomicDirectoryPublisher.publishAbsent(");
int registration = staging.indexOf("registerWorldInBukkitYml(worldKey");
assertTrue(currentPaperData >= 0);
assertTrue(currentPaperData < pack);
assertTrue(pack < publication);
assertTrue(publication < registration);
assertTrue(source.contains("IrisWorldStorage.configuredWorldName("));
assertFalse(source.contains("stageFoliaWorldCreation"));
assertFalse(source.contains("writeCurrentPaperWorldData"));
assertFalse(source.contains("COMMAND_IRIS_RUNTIME_WORLD_CREATION_IS_DISABLED_ON_FOLIA"));
}
}
@@ -0,0 +1,255 @@
package art.arcane.iris.core.gui;
import art.arcane.iris.core.runtime.WorldRuntimeControlService;
import art.arcane.iris.engine.framework.Engine;
import art.arcane.iris.engine.object.IrisWorld;
import art.arcane.iris.platform.bukkit.BukkitPlatform;
import art.arcane.iris.platform.bukkit.BukkitWorldBinding;
import art.arcane.iris.util.common.scheduling.J;
import org.bukkit.Chunk;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.entity.Player;
import org.junit.Test;
import org.mockito.MockedStatic;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicInteger;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.same;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.mockStatic;
import static org.mockito.Mockito.when;
public class BukkitVisionOverlayFoliaContractTest {
@Test
public void teleportLoadsTheDestinationChunkBeforeItsOwningRegionReadsTheSurface() throws Exception {
String source = Files.readString(Path.of(
"src/main/java/art/arcane/iris/core/gui/BukkitVisionOverlay.java"
)).replace("\r\n", "\n");
String request = method(source, "private void requestTeleportChunk(");
assertBefore(request, "requestChunkAsync(", "requested.whenComplete(");
assertBefore(request, "requested.whenComplete(", "J.runRegion(");
assertBefore(request, "J.runRegion(", "world.getHighestBlockYAt(");
assertBefore(request, "world.getHighestBlockYAt(", "J.runEntity(");
assertTrue(request.contains("chunkX,\n chunkZ,\n true,\n true"));
assertEquals(1, occurrences(request, "world.getHighestBlockYAt("));
}
@Test
public void teleportReportsAsyncAndSchedulingFailuresWithDestinationContext() throws Exception {
String source = Files.readString(Path.of(
"src/main/java/art/arcane/iris/core/gui/BukkitVisionOverlay.java"
)).replace("\r\n", "\n");
String request = method(source, "private void requestTeleportChunk(");
String reporter = method(source, "private void reportTeleportFailure(");
assertTrue(request.contains("if (requested == null)"));
assertTrue(request.contains("if (failure != null)"));
assertTrue(request.contains("if (chunk == null ||"));
assertTrue(request.contains("if (!J.runEntity("));
assertTrue(request.contains("if (!scheduled)"));
assertTrue(reporter.contains("IrisLogging.reportError("));
assertTrue(reporter.contains("world.getName()"));
assertTrue(reporter.contains("blockX + \",\" + blockZ"));
}
@Test
public void teleportObservesNativeCompletionAndRejectsFalseSettlement() throws Exception {
String source = Files.readString(Path.of(
"src/main/java/art/arcane/iris/core/gui/BukkitVisionOverlay.java"
)).replace("\r\n", "\n");
String delegate = method(source, "private void delegateTeleport(");
assertTrue(delegate.contains("teleport.whenComplete("));
assertTrue(delegate.contains("!Boolean.TRUE.equals(success)"));
assertTrue(delegate.contains("restartLatest(request)"));
}
@Test
public void staleChunkCompletionCannotTeleportOverTheLatestRequest() {
VisionHarness harness = new VisionHarness();
CompletableFuture<Chunk> firstChunk = new CompletableFuture<>();
CompletableFuture<Chunk> secondChunk = new CompletableFuture<>();
harness.stubChunk(0, 0, firstChunk);
harness.stubChunk(2, 2, secondChunk);
try (harness) {
harness.overlay.teleport(1.5D, 1.5D);
harness.overlay.teleport(33.5D, 33.5D);
firstChunk.complete(harness.chunk);
assertEquals(0, harness.destinations.size());
secondChunk.complete(harness.chunk);
assertEquals(1, harness.destinations.size());
assertEquals(33, harness.destinations.get(0).getBlockX());
assertEquals(33, harness.destinations.get(0).getBlockZ());
}
}
@Test
public void latestRequestRunsAfterAnOlderNativeTeleportSettles() {
VisionHarness harness = new VisionHarness();
harness.stubChunk(0, 0, CompletableFuture.completedFuture(harness.chunk));
harness.stubChunk(2, 2, CompletableFuture.completedFuture(harness.chunk));
CompletableFuture<Boolean> firstTeleport = new CompletableFuture<>();
CompletableFuture<Boolean> secondTeleport = new CompletableFuture<>();
harness.nativeTeleports.add(firstTeleport);
harness.nativeTeleports.add(secondTeleport);
try (harness) {
harness.overlay.teleport(1.5D, 1.5D);
harness.overlay.teleport(33.5D, 33.5D);
assertEquals(1, harness.destinations.size());
firstTeleport.complete(true);
assertEquals(2, harness.destinations.size());
assertEquals(33, harness.destinations.get(1).getBlockX());
assertEquals(33, harness.destinations.get(1).getBlockZ());
secondTeleport.complete(true);
}
}
private static void assertBefore(String source, String first, String second) {
int firstIndex = source.indexOf(first);
int secondIndex = source.indexOf(second);
assertTrue("Missing source contract token: " + first, firstIndex >= 0);
assertTrue("Missing source contract token: " + second, secondIndex >= 0);
assertTrue(first + " must occur before " + second, firstIndex < secondIndex);
}
private static int occurrences(String source, String match) {
int count = 0;
int offset = 0;
while ((offset = source.indexOf(match, offset)) >= 0) {
count++;
offset += match.length();
}
return count;
}
private static String method(String source, String signature) {
int start = source.indexOf(signature);
assertTrue("Missing source contract signature: " + signature, start >= 0);
int openBrace = source.indexOf('{', start);
assertTrue("Missing source contract method body: " + signature, openBrace >= 0);
int depth = 0;
for (int index = openBrace; index < source.length(); index++) {
char current = source.charAt(index);
if (current == '{') {
depth++;
} else if (current == '}') {
depth--;
if (depth == 0) {
return source.substring(start, index + 1);
}
}
}
throw new IllegalArgumentException("Unclosed source contract method: " + signature);
}
private static final class VisionHarness implements AutoCloseable {
private final Engine engine;
private final IrisWorld target;
private final World world;
private final Player player;
private final Chunk chunk;
private final WorldRuntimeControlService runtime;
private final MockedStatic<J> scheduling;
private final MockedStatic<BukkitWorldBinding> binding;
private final MockedStatic<WorldRuntimeControlService> runtimeAccess;
private final MockedStatic<BukkitPlatform> platform;
private final List<Location> destinations;
private final List<CompletableFuture<Boolean>> nativeTeleports;
private final AtomicInteger nativeTeleportIndex;
private final BukkitVisionOverlay overlay;
private VisionHarness() {
engine = mock(Engine.class);
target = mock(IrisWorld.class);
world = mock(World.class);
player = mock(Player.class);
chunk = mock(Chunk.class);
runtime = mock(WorldRuntimeControlService.class);
destinations = new ArrayList<>();
nativeTeleports = new ArrayList<>();
nativeTeleportIndex = new AtomicInteger();
when(engine.getWorld()).thenReturn(target);
when(target.hasPlatformWorld()).thenReturn(true);
when(player.isOnline()).thenReturn(true);
when(player.getWorld()).thenReturn(world);
when(chunk.getWorld()).thenReturn(world);
when(world.getHighestBlockYAt(anyInt(), anyInt())).thenReturn(70);
scheduling = mockStatic(J.class);
scheduling.when(() -> J.runGlobal(any(Runnable.class))).thenAnswer(invocation -> {
invocation.getArgument(0, Runnable.class).run();
return true;
});
scheduling.when(() -> J.runRegion(
same(world),
anyInt(),
anyInt(),
any(Runnable.class)))
.thenAnswer(invocation -> {
invocation.getArgument(3, Runnable.class).run();
return true;
});
scheduling.when(() -> J.runEntity(same(player), any(Runnable.class))).thenAnswer(invocation -> {
invocation.getArgument(1, Runnable.class).run();
return true;
});
binding = mockStatic(BukkitWorldBinding.class);
binding.when(() -> BukkitWorldBinding.world(target)).thenReturn(world);
binding.when(() -> BukkitWorldBinding.players(target)).thenReturn(List.of(player));
runtimeAccess = mockStatic(WorldRuntimeControlService.class);
runtimeAccess.when(WorldRuntimeControlService::get).thenReturn(runtime);
platform = mockStatic(BukkitPlatform.class);
platform.when(() -> BukkitPlatform.teleportAsync(same(player), any(Location.class)))
.thenAnswer(invocation -> {
destinations.add(invocation.getArgument(1, Location.class));
int index = nativeTeleportIndex.getAndIncrement();
return index < nativeTeleports.size()
? nativeTeleports.get(index)
: CompletableFuture.completedFuture(true);
});
overlay = new BukkitVisionOverlay(engine);
}
private void stubChunk(
int chunkX,
int chunkZ,
CompletableFuture<Chunk> requested
) {
when(runtime.requestChunkAsync(
same(world),
eq(chunkX),
eq(chunkZ),
eq(true),
eq(true))).thenReturn(requested);
}
@Override
public void close() {
platform.close();
runtimeAccess.close();
binding.close();
scheduling.close();
}
}
}
@@ -11,15 +11,14 @@ import static org.junit.Assert.assertTrue;
public class StudioPlayerModeContractTest {
@Test
public void studioEntryKeepsPlayersEligibleForNaturalSpawning() throws IOException {
public void autoStartedStudioUsesSpectatorMode() throws IOException {
String plugin = Files.readString(Path.of("src/main/java/art/arcane/iris/Iris.java")).replace("\r\n", "\n");
String commands = Files.readString(Path.of(
"src/main/java/art/arcane/iris/core/commands/CommandStudio.java")).replace("\r\n", "\n");
assertFalse(plugin.contains("GameMode.SPECTATOR"));
assertFalse(commands.contains("GameMode.SPECTATOR"));
assertTrue(plugin.contains("GameMode.CREATIVE"));
assertTrue(commands.contains("GameMode.CREATIVE"));
assertFalse(plugin.contains("GameMode.CREATIVE"));
assertFalse(commands.contains("GameMode.CREATIVE"));
assertTrue(plugin.contains("GameMode.SPECTATOR"));
}
@Test
@@ -32,8 +31,23 @@ public class StudioPlayerModeContractTest {
assertTrue(method.contains("StudioSVC studioService = Iris.service(StudioSVC.class)"));
assertTrue(method.contains("studioService.teleportToActiveProject(player)"));
assertFalse(method.contains("setGameMode("));
assertFalse(method.contains("getActiveProject()"));
assertFalse(method.contains("BukkitPlatform.teleportAsync"));
assertFalse(method.contains("BukkitWorldBinding.spawnLocation"));
}
@Test
public void editingStudiosRemainCreative() throws IOException {
String objectCommands = Files.readString(Path.of(
"src/main/java/art/arcane/iris/core/commands/CommandObject.java")).replace("\r\n", "\n");
String jigsawCommands = Files.readString(Path.of(
"src/main/java/art/arcane/iris/core/commands/CommandJigsaw.java")).replace("\r\n", "\n");
assertTrue(objectCommands.contains("GameMode.CREATIVE"));
assertTrue(objectCommands.contains("StudioOpenCoordinator.StudioOpenKind.OBJECT"));
assertTrue(jigsawCommands.contains("GameMode.CREATIVE"));
assertFalse(objectCommands.contains("GameMode.SPECTATOR"));
assertFalse(jigsawCommands.contains("GameMode.SPECTATOR"));
}
}
@@ -146,7 +146,7 @@ public final class IrisClient {
case IrisMessage.VisionTile visionTile -> TILES.onVisionTile(visionTile);
case IrisMessage.VisionMarkers visionMarkers -> MARKERS.onMarkers(visionMarkers);
case IrisMessage.PregenRegionDelta delta -> REGIONS.onDelta(delta, PREGEN.activeJobId());
case IrisMessage.StudioHotload hotload -> TOASTS.enqueueHotload(hotload.packKey(), hotload.changedFiles(), hotload.failed(), hotload.message());
case IrisMessage.StudioHotload hotload -> onStudioHotload(hotload);
case IrisMessage.Toast toast -> TOASTS.enqueue(toast.kind(), toast.title(), toast.body());
default -> {
}
@@ -167,4 +167,29 @@ public final class IrisClient {
REGIONS.clear();
}
}
private static void onStudioHotload(IrisMessage.StudioHotload hotload) {
if (shouldInvalidateForHotload(DIMENSION.status(), hotload)) {
TILES.clear();
MARKERS.clear();
CURSOR.clear();
}
TOASTS.enqueueHotload(
hotload.packKey(),
hotload.changedFiles(),
hotload.failed(),
hotload.message()
);
}
static boolean shouldInvalidateForHotload(
IrisMessage.DimensionStatus status,
IrisMessage.StudioHotload hotload
) {
return status != null
&& status.irisWorld()
&& hotload != null
&& !hotload.failed()
&& status.packKey().equals(hotload.packKey());
}
}
@@ -18,6 +18,7 @@
package art.arcane.iris.fabric.mixin;
import art.arcane.iris.modded.ModdedIrisLog;
import art.arcane.iris.fabric.FabricForcedDatapackSources;
import net.minecraft.server.packs.repository.PackRepository;
import net.minecraft.server.packs.repository.RepositorySource;
@@ -26,7 +27,6 @@ import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import org.slf4j.LoggerFactory;
import java.util.Arrays;
@@ -42,7 +42,7 @@ public class PackRepositoryMixin {
}
// Client resource-pack repositories legitimately have no ServerPacksSource; a missing server-data
// repository is reported once at boot by ModdedForcedDatapack.verifyInjected().
LoggerFactory.getLogger("Iris").debug(
ModdedIrisLog.debug(
"Iris forced datapack source not attached: no ServerPacksSource among {} source(s) {}",
sources.length, Arrays.toString(sources));
}
@@ -82,8 +82,6 @@ import net.minecraft.world.level.levelgen.structure.Structure;
import net.minecraft.world.level.levelgen.structure.StructureSet;
import net.minecraft.world.level.levelgen.structure.StructureStart;
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplateManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.util.Arrays;
@@ -98,7 +96,6 @@ import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.IntBinaryOperator;
public final class IrisModdedChunkGenerator extends ChunkGenerator {
private static final Logger LOGGER = LoggerFactory.getLogger("Iris");
// Vanilla-shaped fallback for an unbound generator (matches IrisDimension defaults). getMinY,
// getSeaLevel and getGenDepth are called from world creation and client screens, so they must
// answer without disk I/O and without throwing before a level is bound.
@@ -368,7 +365,7 @@ public final class IrisModdedChunkGenerator extends ChunkGenerator {
// Bind time: a feature-order cycle is reported here, once, and degrades to features-off. Non-waiting for
// the same reason as repointAndBind: this method owns the generator monitor.
importedFeatures.prepareWithoutWaiting(bound);
LOGGER.info("Iris bound {}: chunk system {}", level.dimension().identifier(), ModdedGenPool.describeChunkSystem());
ModdedIrisLog.info("Iris bound {}: chunk system {}", level.dimension().identifier(), ModdedGenPool.describeChunkSystem());
}
private Engine bindEngine(ServerLevel level) {
@@ -601,7 +598,7 @@ public final class IrisModdedChunkGenerator extends ChunkGenerator {
try {
heightMetadata = configuredPack().metadata();
} catch (Throwable e) {
LOGGER.warn("Iris generator '{}' could not pre-resolve pack heights for {}:{}: {}",
ModdedIrisLog.warn("Iris generator '{}' could not pre-resolve pack heights for {}:{}: {}",
dimensionKey, activePack, activeDimensionKey, e.toString());
}
}
@@ -726,7 +723,7 @@ public final class IrisModdedChunkGenerator extends ChunkGenerator {
Engine generationEngine = engine();
ChunkPos pos = chunk.getPos();
lastChunkGenAt = System.currentTimeMillis();
LOGGER.debug("Iris generating chunk {},{}", pos.x(), pos.z());
ModdedIrisLog.debug("Iris generating chunk {},{}", pos.x(), pos.z());
PlatformBlockState air = IrisPlatforms.get().registries().air();
@@ -744,7 +741,7 @@ public final class IrisModdedChunkGenerator extends ChunkGenerator {
try (GenerationSessionLease lease = generationEngine.acquireGenerationLease("modded_chunk_pipeline");
IrisContext.Scope ignored = IrisContext.open(generationEngine, lease.sessionId(), null)) {
if (announced.compareAndSet(false, true)) {
LOGGER.info("Iris generating {} through IrisModdedChunkGenerator (dim={} first chunk {},{})",
ModdedIrisLog.info("Iris generating {} through IrisModdedChunkGenerator (dim={} first chunk {},{})",
dimensionKey, generationEngine.getDimension().getLoadKey(), pos.x(), pos.z());
}
int dimMinY = generationEngine.getMinHeight();
@@ -763,14 +760,14 @@ public final class IrisModdedChunkGenerator extends ChunkGenerator {
return chunk;
} catch (GenerationSessionException e) {
if (generationEngine.isClosing() || e.isExpectedTeardown()) {
LOGGER.debug("Iris chunk {},{} skipped: engine sealed for hotload/teardown", pos.x(), pos.z());
ModdedIrisLog.debug("Iris chunk {},{} skipped: engine sealed for hotload/teardown", pos.x(), pos.z());
throw new IllegalStateException(
"Iris chunk generation was rejected during an engine transition.", e);
}
LOGGER.error("Iris failed to generate chunk {},{}", pos.x(), pos.z(), e);
ModdedIrisLog.error("Iris failed to generate chunk {},{}", pos.x(), pos.z(), e);
throw new IllegalStateException("Iris generation failed for chunk " + pos.x() + "," + pos.z(), e);
} catch (Throwable e) {
LOGGER.error("Iris failed to generate chunk {},{}", pos.x(), pos.z(), e);
ModdedIrisLog.error("Iris failed to generate chunk {},{}", pos.x(), pos.z(), e);
throw new IllegalStateException("Iris generation failed for chunk " + pos.x() + "," + pos.z(), e);
}
}
@@ -18,8 +18,6 @@
package art.arcane.iris.modded;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
@@ -32,7 +30,6 @@ import java.util.Optional;
import java.util.UUID;
public final class MainWorldService {
private static final Logger LOGGER = LoggerFactory.getLogger("Iris");
private static final String MARKER_NAME = "mainworld.pending";
private static final String PROPERTIES_NAME = "server.properties";
/**
@@ -80,12 +77,12 @@ public final class MainWorldService {
if (!target.equals(currentType)) {
writeLevelProperties(properties, target, config.mainWorldSeed());
markPending();
LOGGER.warn("Iris main world '{}' staged: {} level-type set to {}. Restart again to generate it (this boot still uses the previous overworld; player data is kept).",
ModdedIrisLog.warn("Iris main world '{}' staged: {} level-type set to {}. Restart again to generate it (this boot still uses the previous overworld; player data is kept).",
pack, properties, target);
if (config.mainWorldAutoRestart()) {
LOGGER.warn("Iris mainWorldAutoRestart is enabled; stopping the JVM now with exit status {} so a restart wrapper brings the server back on the new main world.",
ModdedIrisLog.warn("Iris mainWorldAutoRestart is enabled; stopping the JVM now with exit status {} so a restart wrapper brings the server back on the new main world.",
AUTO_RESTART_EXIT_STATUS);
LOGGER.warn("Configure the start script to restart the server on exit status {} (status 0 means a clean stop, so it must not be reused for this).",
ModdedIrisLog.warn("Configure the start script to restart the server on exit status {} (status 0 means a clean stop, so it must not be reused for this).",
AUTO_RESTART_EXIT_STATUS);
System.exit(AUTO_RESTART_EXIT_STATUS);
}
@@ -102,16 +99,16 @@ public final class MainWorldService {
// bootstrap: there is no prior overworld to move aside, so this is nothing to quarantine, not a
// reason to refuse startup.
clearPending();
LOGGER.warn("Iris main world '{}' had nothing to quarantine: {} does not exist. Continuing boot; the overworld generates as {}.",
ModdedIrisLog.warn("Iris main world '{}' had nothing to quarantine: {} does not exist. Continuing boot; the overworld generates as {}.",
pack, missing.path(), target);
return;
}
Path recovery = quarantineVanillaDimensions(worldRoot);
clearPending();
LOGGER.warn("Iris main world '{}' generated fresh: moved the prior overworld/nether/end data from {} to {} so this boot regenerates them as {} (player data kept).",
ModdedIrisLog.warn("Iris main world '{}' generated fresh: moved the prior overworld/nether/end data from {} to {} so this boot regenerates them as {} (player data kept).",
pack, worldRoot, recovery, target);
} catch (Throwable e) {
LOGGER.error("Iris main world reconciliation failed", e);
ModdedIrisLog.error("Iris main world reconciliation failed", e);
throw new IllegalStateException(
"Iris refused startup after main-world reconciliation failed", e);
}
@@ -119,7 +116,7 @@ public final class MainWorldService {
public static boolean stage(String packRef, long seed) {
if (ModdedEngineBootstrap.loader().clientEnvironment()) {
LOGGER.error("Iris main-world replacement is only available on dedicated servers; use the Create World generator selector in singleplayer");
ModdedIrisLog.error("Iris main-world replacement is only available on dedicated servers; use the Create World generator selector in singleplayer");
return false;
}
Path instanceRoot = verifiedInstanceRoot("stage the Iris main world");
@@ -131,7 +128,7 @@ public final class MainWorldService {
markPending();
return true;
} catch (IOException e) {
LOGGER.error("Iris failed to stage the main world in server.properties", e);
ModdedIrisLog.error("Iris failed to stage the main world in server.properties", e);
return false;
}
}
@@ -140,7 +137,7 @@ public final class MainWorldService {
try {
clearPending();
} catch (IOException e) {
LOGGER.error("Iris failed to clear the pending main world marker", e);
ModdedIrisLog.error("Iris failed to clear the pending main world marker", e);
}
}
@@ -156,8 +153,8 @@ public final class MainWorldService {
if (Files.isRegularFile(workingDirectory.resolve(PROPERTIES_NAME))) {
return workingDirectory;
}
LOGGER.error("Iris refuses to {}: no {} in the server working directory {}", operation, PROPERTIES_NAME, workingDirectory);
LOGGER.error("Iris only edits main-world properties in the directory the dedicated server reads {} from, and it moves no world data outside it. Start the server from its instance directory, or clear mainWorldPack in irisworldgen/modded.json.", PROPERTIES_NAME);
ModdedIrisLog.error("Iris refuses to {}: no {} in the server working directory {}", operation, PROPERTIES_NAME, workingDirectory);
ModdedIrisLog.error("Iris only edits main-world properties in the directory the dedicated server reads {} from, and it moves no world data outside it. Start the server from its instance directory, or clear mainWorldPack in irisworldgen/modded.json.", PROPERTIES_NAME);
return null;
}
@@ -305,7 +302,7 @@ public final class MainWorldService {
return List.of(arguments.get());
}
} catch (RuntimeException unavailable) {
LOGGER.debug("Iris could not read the process arguments", unavailable);
ModdedIrisLog.debug("Iris could not read the process arguments", unavailable);
}
// Whitespace split only: sun.java.command is a flattened string with no quoting information, so a
// --universe or --world value containing spaces cannot be recovered from it. Deliberately not parsed
@@ -29,8 +29,6 @@ import net.minecraft.core.registries.Registries;
import net.minecraft.resources.Identifier;
import net.minecraft.server.MinecraftServer;
import net.minecraft.world.level.biome.Biome;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
@@ -39,7 +37,6 @@ import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Supplier;
public final class ModdedBiomeWriter implements PlatformBiomeWriter {
private static final Logger LOGGER = LoggerFactory.getLogger("Iris");
private static final String VANILLA_FALLBACK_KEY = "minecraft:plains";
private static final int MAX_CACHED_IDS = 4096;
/** NUL cannot occur in a pack or registry key, so the composite cache key stays unambiguous. */
@@ -61,7 +58,7 @@ public final class ModdedBiomeWriter implements PlatformBiomeWriter {
return 0;
}
if (key == null) {
LOGGER.warn("Iris biome writer got a null biome key; falling back to {}", VANILLA_FALLBACK_KEY);
ModdedIrisLog.warn("Iris biome writer got a null biome key; falling back to {}", VANILLA_FALLBACK_KEY);
return fallbackId(registry);
}
@@ -217,7 +214,7 @@ public final class ModdedBiomeWriter implements PlatformBiomeWriter {
private void reportMissingServer(String operation, String fallback) {
if (serverMissingReported.compareAndSet(false, true)) {
LOGGER.warn("Iris cannot {} before the Minecraft server is available; {}", operation, fallback);
ModdedIrisLog.warn("Iris cannot {} before the Minecraft server is available; {}", operation, fallback);
}
}
@@ -43,14 +43,11 @@ import net.minecraft.world.entity.item.ItemEntity;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.chunk.ChunkGenerator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
public final class ModdedBlockBreakHandler {
private static final Logger LOGGER = LoggerFactory.getLogger("Iris");
private static final ConcurrentHashMap<BreakKey, PendingBreak> PENDING = new ConcurrentHashMap<>();
private ModdedBlockBreakHandler() {
@@ -72,7 +69,7 @@ public final class ModdedBlockBreakHandler {
if (scheduler == null) {
// finishPending is the only thing that evicts an unconsumed entry. With no scheduler there is no
// sweep, so an entry inserted here would leak for the rest of the server uptime.
LOGGER.debug("Iris skipped block-break provenance at {},{},{}: scheduler unavailable",
ModdedIrisLog.debug("Iris skipped block-break provenance at {},{},{}: scheduler unavailable",
position.getX(), position.getY(), position.getZ());
return;
}
@@ -150,7 +147,7 @@ public final class ModdedBlockBreakHandler {
try {
return evaluate(level, position, pending);
} catch (Throwable error) {
LOGGER.error("Iris block-break processing failed at {},{},{} in {}", position.getX(), position.getY(), position.getZ(),
ModdedIrisLog.error("Iris block-break processing failed at {},{},{} in {}", position.getX(), position.getY(), position.getZ(),
level.dimension().identifier(), error);
return Result.empty();
}
@@ -182,7 +179,7 @@ public final class ModdedBlockBreakHandler {
try {
return evaluateDrops(level, position, brokenState, engine);
} catch (Throwable error) {
LOGGER.error("Iris managed block-drop processing failed at {},{},{} in {}", position.getX(), position.getY(), position.getZ(),
ModdedIrisLog.error("Iris managed block-drop processing failed at {},{},{} in {}", position.getX(), position.getY(), position.getZ(),
level.dimension().identifier(), error);
return Result.empty();
}
@@ -315,7 +312,7 @@ public final class ModdedBlockBreakHandler {
try {
return irisGenerator.commandEngine();
} catch (Throwable error) {
LOGGER.error("Iris could not resolve the engine for a block break in {}", level.dimension().identifier(), error);
ModdedIrisLog.error("Iris could not resolve the engine for a block break in {}", level.dimension().identifier(), error);
return null;
}
}
@@ -46,8 +46,6 @@ import net.minecraft.world.level.storage.DerivedLevelData;
import net.minecraft.world.level.storage.LevelStorageSource;
import net.minecraft.world.level.storage.ServerLevelData;
import net.minecraft.world.level.storage.WorldData;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.util.ArrayList;
@@ -60,7 +58,6 @@ import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
public final class ModdedDimensionManager {
private static final Logger LOGGER = LoggerFactory.getLogger("Iris");
private static final Object LOCK = new Object();
private static final ConcurrentHashMap<String, Handle> HANDLES = new ConcurrentHashMap<>();
private static final TicketType TELEPORT_WARM_TICKET = new TicketType(TicketType.NO_TIMEOUT,
@@ -137,7 +134,7 @@ public final class ModdedDimensionManager {
if (present == null || !(present.getChunkSource().getGenerator() instanceof IrisModdedChunkGenerator generator)) {
throw new IllegalStateException("Iris cannot inject dimension '" + dimensionId + "': a non-Iris level with that id is already loaded");
}
LOGGER.warn("Iris dimension '{}' is already present in the running server; reusing it", dimensionId);
ModdedIrisLog.warn("Iris dimension '{}' is already present in the running server; reusing it", dimensionId);
generator.repointAndBind(present, pack, packDimensionKey, seed);
Handle handle = new Handle(dimensionId, pack, packDimensionKey, seed, present, generator);
HANDLES.put(dimensionId, handle);
@@ -147,10 +144,10 @@ public final class ModdedDimensionManager {
try {
Handle handle = inject(server, serverAccess, dimensionId, key, pack, packDimensionKey, seed);
HANDLES.put(dimensionId, handle);
LOGGER.info("Iris injected runtime dimension '{}' (pack={} dim={} seed={})", dimensionId, pack, packDimensionKey, seed);
ModdedIrisLog.info("Iris injected runtime dimension '{}' (pack={} dim={} seed={})", dimensionId, pack, packDimensionKey, seed);
return handle;
} catch (Throwable e) {
LOGGER.error("Iris failed to inject runtime dimension '{}' (pack={} dim={} seed={})", dimensionId, pack, packDimensionKey, seed, e);
ModdedIrisLog.error("Iris failed to inject runtime dimension '{}' (pack={} dim={} seed={})", dimensionId, pack, packDimensionKey, seed, e);
throw new IllegalStateException("Iris runtime dimension injection failed for " + dimensionId, e);
}
}
@@ -233,11 +230,11 @@ public final class ModdedDimensionManager {
if (wipeStorage) {
ModdedDimensionStorage.wipe(server, key);
}
LOGGER.info("Iris removed runtime dimension '{}'", dimensionId);
ModdedIrisLog.info("Iris removed runtime dimension '{}'", dimensionId);
return true;
} catch (Throwable e) {
rollbackRemoval(server, serverAccess, key, level, generator, generatorUnbound, e);
LOGGER.error("Iris failed to remove runtime dimension '{}'", dimensionId, e);
ModdedIrisLog.error("Iris failed to remove runtime dimension '{}'", dimensionId, e);
throw new IllegalStateException("Iris runtime dimension removal failed for " + dimensionId, e);
}
}
@@ -256,7 +253,7 @@ public final class ModdedDimensionManager {
if (rollbackFailure != failure) {
failure.addSuppressed(rollbackFailure);
}
LOGGER.error("Iris failed to restore the engine for retained runtime dimension '{}'",
ModdedIrisLog.error("Iris failed to restore the engine for retained runtime dimension '{}'",
key.identifier(), rollbackFailure);
}
}
@@ -282,7 +279,7 @@ public final class ModdedDimensionManager {
.whenComplete((Object result, Throwable error) -> server.execute(() -> {
level.getChunkSource().removeTicketWithRadius(TELEPORT_WARM_TICKET, chunkPos, 1);
if (error != null) {
LOGGER.warn("Iris chunk warm for teleport into '{}' at {},{} failed: {}", dimensionId, chunkPos.x(), chunkPos.z(), error.toString());
ModdedIrisLog.warn("Iris chunk warm for teleport into '{}' at {},{} failed: {}", dimensionId, chunkPos.x(), chunkPos.z(), error.toString());
}
ServerPlayer target = server.getPlayerList().getPlayer(playerId);
if (target == null) {
@@ -325,7 +322,7 @@ public final class ModdedDimensionManager {
}
return dimension;
} catch (Throwable e) {
LOGGER.error("Iris could not load pack '{}' dimension '{}' for dimension type resolution",
ModdedIrisLog.error("Iris could not load pack '{}' dimension '{}' for dimension type resolution",
pack, packDimensionKey, e);
if (e instanceof Error fatalError) {
throw fatalError;
@@ -401,19 +398,19 @@ public final class ModdedDimensionManager {
}
} catch (Throwable cleanupError) {
failure.addSuppressed(cleanupError);
LOGGER.error("Iris failed to remove a partially injected level for {}", key.identifier(), cleanupError);
ModdedIrisLog.error("Iris failed to remove a partially injected level for {}", key.identifier(), cleanupError);
}
try {
generator.unbindEngine(level);
} catch (Throwable cleanupError) {
failure.addSuppressed(cleanupError);
LOGGER.error("Iris failed to close a partially bound engine for {}", key.identifier(), cleanupError);
ModdedIrisLog.error("Iris failed to close a partially bound engine for {}", key.identifier(), cleanupError);
}
try {
level.close();
} catch (Throwable cleanupError) {
failure.addSuppressed(cleanupError);
LOGGER.error("Iris failed to close a partially injected level for {}", key.identifier(), cleanupError);
ModdedIrisLog.error("Iris failed to close a partially injected level for {}", key.identifier(), cleanupError);
}
}
@@ -22,8 +22,6 @@ import art.arcane.volmlib.util.json.JSONArray;
import art.arcane.volmlib.util.json.JSONObject;
import net.minecraft.server.MinecraftServer;
import net.minecraft.world.level.storage.LevelResource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.nio.channels.FileChannel;
@@ -41,7 +39,6 @@ import java.util.regex.Matcher;
import java.util.regex.Pattern;
public final class ModdedDimensionRegistryStore {
private static final Logger LOGGER = LoggerFactory.getLogger("Iris");
private static final String FILE_NAME = "iris-dimensions.json";
private static final Pattern ID_FIELD = Pattern.compile("\"id\"\\s*:\\s*\"([^\"]+)\"");
@@ -68,13 +65,13 @@ public final class ModdedDimensionRegistryStore {
try {
return load(file);
} catch (RuntimeException corrupt) {
LOGGER.error("Iris persistent dimension registry at {} is corrupt; quarantining it and continuing boot",
ModdedIrisLog.error("Iris persistent dimension registry at {} is corrupt; quarantining it and continuing boot",
file, corrupt);
List<String> lostIds = salvageIds(file);
if (lostIds.isEmpty()) {
LOGGER.error("Iris could not recover any dimension ids from the corrupt registry; re-create the worlds with /iris world create");
ModdedIrisLog.error("Iris could not recover any dimension ids from the corrupt registry; re-create the worlds with /iris world create");
} else {
LOGGER.error("Iris lost {} persistent dimension(s) from the corrupt registry: {}",
ModdedIrisLog.error("Iris lost {} persistent dimension(s) from the corrupt registry: {}",
lostIds.size(), String.join(", ", lostIds));
}
quarantine(file);
@@ -93,7 +90,7 @@ public final class ModdedDimensionRegistryStore {
}
}
} catch (IOException | RuntimeException unreadable) {
LOGGER.warn("Iris could not scan the corrupt persistent dimension registry at {} for lost ids", file, unreadable);
ModdedIrisLog.warn("Iris could not scan the corrupt persistent dimension registry at {} for lost ids", file, unreadable);
}
return ids;
}
@@ -102,9 +99,9 @@ public final class ModdedDimensionRegistryStore {
Path broken = file.resolveSibling(FILE_NAME + ".broken-" + System.currentTimeMillis());
try {
Files.move(file, broken, StandardCopyOption.REPLACE_EXISTING);
LOGGER.error("Iris moved the corrupt persistent dimension registry to {}", broken);
ModdedIrisLog.error("Iris moved the corrupt persistent dimension registry to {}", broken);
} catch (IOException failure) {
LOGGER.error("Iris could not quarantine the corrupt persistent dimension registry at {}; delete it by hand",
ModdedIrisLog.error("Iris could not quarantine the corrupt persistent dimension registry at {}; delete it by hand",
file, failure);
}
}
@@ -134,14 +131,14 @@ public final class ModdedDimensionRegistryStore {
PersistentDimension previous = deduplicated.putIfAbsent(
id, new PersistentDimension(id, pack, dimension, entry.getLong("seed")));
if (previous != null) {
LOGGER.warn("Iris persistent dimension registry entry {} in {} duplicates id '{}'; keeping the first",
ModdedIrisLog.warn("Iris persistent dimension registry entry {} in {} duplicates id '{}'; keeping the first",
index, file, id);
}
} catch (RuntimeException invalidEntry) {
if (raw != null) {
unparsed.add(raw);
}
LOGGER.warn("Iris persistent dimension registry entry {} in {} is invalid ({}); kept verbatim: {}",
ModdedIrisLog.warn("Iris persistent dimension registry entry {} in {} is invalid ({}); kept verbatim: {}",
index, file, invalidEntry.getMessage(), raw);
}
}
@@ -23,8 +23,6 @@ import net.minecraft.server.MinecraftServer;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.dimension.DimensionType;
import net.minecraft.world.level.storage.LevelResource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.IOException;
@@ -35,7 +33,6 @@ import java.util.List;
import java.util.stream.Stream;
public final class ModdedDimensionStorage {
private static final Logger LOGGER = LoggerFactory.getLogger("Iris");
private static final List<String> CHUNK_DATA_FOLDERS = List.of("region", "entities", "poi", "mantle");
private ModdedDimensionStorage() {
@@ -56,7 +53,7 @@ public final class ModdedDimensionStorage {
"Iris failed to completely wipe dimension storage at "
+ storageFolder.getAbsolutePath(), e);
}
LOGGER.info("Iris wiped dimension storage at {}", storageFolder.getAbsolutePath());
ModdedIrisLog.info("Iris wiped dimension storage at {}", storageFolder.getAbsolutePath());
}
private static void deleteRecursively(Path root) throws IOException {
@@ -55,13 +55,10 @@ import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.chunk.LevelChunk;
import net.minecraft.world.level.levelgen.Heightmap;
import net.minecraft.world.level.storage.LevelData;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayDeque;
public final class ModdedEngineBootstrap {
private static final Logger LOGGER = LoggerFactory.getLogger("Iris");
private static final String[] CORE_SELF_TEST_CLASSES = {
"art.arcane.iris.engine.IrisEngine",
"art.arcane.iris.util.common.data.B",
@@ -156,7 +153,7 @@ public final class ModdedEngineBootstrap {
try {
generator.unbindEngine(level);
} catch (Throwable exception) {
LOGGER.error("Iris engine unload failed for {}", level.dimension().identifier(), exception);
ModdedIrisLog.error("Iris engine unload failed for {}", level.dimension().identifier(), exception);
if (exception instanceof RuntimeException runtimeException) {
throw runtimeException;
}
@@ -207,7 +204,7 @@ public final class ModdedEngineBootstrap {
if (failure != null) {
// The shutdown path must not propagate: propagating aborts the remaining loader stop handlers and
// can leave the level unsaved. Every stage already logged its own failure.
LOGGER.error("Iris modded shutdown completed with failures", failure);
ModdedIrisLog.error("Iris modded shutdown completed with failures", failure);
}
}
@@ -216,7 +213,7 @@ public final class ModdedEngineBootstrap {
action.run();
return failure;
} catch (Throwable stageFailure) {
LOGGER.error("Iris modded shutdown stage '{}' failed", stage, stageFailure);
ModdedIrisLog.error("Iris modded shutdown stage '{}' failed", stage, stageFailure);
if (failure == null) {
return stageFailure;
}
@@ -255,7 +252,7 @@ public final class ModdedEngineBootstrap {
BlockPos position = reconciledSpawnPosition(surfaceY, level.getMinY(), level.getHeight());
server.setRespawnData(LevelData.RespawnData.of(
level.dimension(), position, current.yaw(), current.pitch()));
LOGGER.info("Iris spawn reconciled for {} at {},{},{}", dimensionId,
ModdedIrisLog.info("Iris spawn reconciled for {} at {},{},{}", dimensionId,
position.getX(), position.getY(), position.getZ());
}
@@ -321,7 +318,7 @@ public final class ModdedEngineBootstrap {
Class.forName(className, true, classLoader);
loadedClasses++;
} catch (Throwable error) {
LOGGER.error("Iris core self-test failed to initialize {}", className, error);
ModdedIrisLog.error("Iris core self-test failed to initialize {}", className, error);
}
}
@@ -404,7 +401,7 @@ public final class ModdedEngineBootstrap {
ModdedIrisSplash.print(boundLoader);
} catch (Throwable splashFailure) {
// A cosmetic banner must never roll back the platform bind.
LOGGER.warn("Iris splash could not be printed", splashFailure);
ModdedIrisLog.warn("Iris splash could not be printed", splashFailure);
}
createdServices.enableAll();
runtime = new BoundRuntime(created, createdServices);
@@ -413,7 +410,7 @@ public final class ModdedEngineBootstrap {
} catch (Throwable failure) {
createdServices.rollback(failure);
rollback.restore(failure);
LOGGER.error("Iris modded platform binding failed", failure);
ModdedIrisLog.error("Iris modded platform binding failed", failure);
if (failure instanceof RuntimeException runtimeException) {
throw runtimeException;
}
@@ -39,8 +39,6 @@ import net.minecraft.server.packs.PathPackResources;
import net.minecraft.server.packs.repository.Pack;
import net.minecraft.server.packs.repository.PackSource;
import net.minecraft.server.packs.repository.RepositorySource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.IOException;
@@ -67,7 +65,6 @@ import java.util.function.Consumer;
import java.util.stream.Stream;
public final class ModdedForcedDatapack {
private static final Logger LOGGER = LoggerFactory.getLogger("Iris");
private static final String PACK_ID = "iris_worldgen";
private static final String PACK_FOLDER = "iris";
private static final String HASH_FILE_NAME = "packs.hash";
@@ -111,7 +108,7 @@ public final class ModdedForcedDatapack {
return requireReadablePack(current.directory());
} catch (RuntimeException unreadable) {
published = null;
LOGGER.error("Iris could not read the published forced datapack at {}; regenerating",
ModdedIrisLog.error("Iris could not read the published forced datapack at {}; regenerating",
current.directory(), unreadable);
}
}
@@ -125,19 +122,19 @@ public final class ModdedForcedDatapack {
reason = "stale cache (hash changed)";
} else {
if (hash.isEmpty() && STALE_SERVE_LOGGED.compareAndSet(false, true)) {
LOGGER.warn("Iris cannot hash the installed packs; serving the last generated forced datapack from {} unverified",
ModdedIrisLog.warn("Iris cannot hash the installed packs; serving the last generated forced datapack from {} unverified",
state.directory());
}
try {
return requireReadablePack(state.directory());
} catch (RuntimeException unreadable) {
published = null;
LOGGER.error("Iris could not read the published forced datapack at {}; regenerating",
ModdedIrisLog.error("Iris could not read the published forced datapack at {}; regenerating",
state.directory(), unreadable);
}
reason = "unreadable published pack";
}
LOGGER.info("Iris forced datapack cache is unusable ({}); generating it once now", reason);
ModdedIrisLog.info("Iris forced datapack cache is unusable ({}); generating it once now", reason);
return buildPack();
}
}
@@ -151,11 +148,11 @@ public final class ModdedForcedDatapack {
if (packs.isEmpty()) {
return;
}
LOGGER.error("===============================================================");
LOGGER.error("Iris forced datapack '{}' was never loaded by this server.", PACK_ID);
LOGGER.error("{} installed pack(s) at {} contributed no dimension types or custom biomes.", packs.size(), packsRoot);
LOGGER.error("Datapack source injection failed for this loader (mixin/event not applied), so world creation will fail and restarting will not fix it.");
LOGGER.error("===============================================================");
ModdedIrisLog.error("===============================================================");
ModdedIrisLog.error("Iris forced datapack '{}' was never loaded by this server.", PACK_ID);
ModdedIrisLog.error("{} installed pack(s) at {} contributed no dimension types or custom biomes.", packs.size(), packsRoot);
ModdedIrisLog.error("Datapack source injection failed for this loader (mixin/event not applied), so world creation will fail and restarting will not fix it.");
ModdedIrisLog.error("===============================================================");
}
public static Path datapackRoot() {
@@ -172,7 +169,7 @@ public final class ModdedForcedDatapack {
} catch (RuntimeException | Error generationFailure) {
Path lastKnownGood = packDirectory();
if (Files.isRegularFile(lastKnownGood.resolve("pack.mcmeta"))) {
LOGGER.error("Iris kept the last known-good generated datapack after regeneration failed",
ModdedIrisLog.error("Iris kept the last known-good generated datapack after regeneration failed",
generationFailure);
return requireReadablePack(lastKnownGood);
}
@@ -201,7 +198,7 @@ public final class ModdedForcedDatapack {
try {
return write();
} catch (Throwable e) {
LOGGER.error("Iris failed to generate the forced startup datapack", e);
ModdedIrisLog.error("Iris failed to generate the forced startup datapack", e);
if (e instanceof RuntimeException runtimeException) {
throw runtimeException;
}
@@ -222,10 +219,10 @@ public final class ModdedForcedDatapack {
String currentHash = packsHashOrEmpty();
PublishedState state = publishedState();
if (state != null && !currentHash.isEmpty() && state.packsHash().equals(currentHash)) {
LOGGER.debug("Iris forced datapack is current ({}); skipping regeneration", reason);
ModdedIrisLog.debug("Iris forced datapack is current ({}); skipping regeneration", reason);
return false;
}
LOGGER.info("Iris regenerating the forced datapack ({})", reason);
ModdedIrisLog.info("Iris regenerating the forced datapack ({})", reason);
regenerate();
return true;
}
@@ -240,7 +237,7 @@ public final class ModdedForcedDatapack {
try {
regenerateIfStale(reason);
} catch (Throwable failure) {
LOGGER.error("Iris forced datapack regeneration failed ({})", reason, failure);
ModdedIrisLog.error("Iris forced datapack regeneration failed ({})", reason, failure);
}
};
ModdedScheduler scheduler = ModdedEngineBootstrap.schedulerOrNull();
@@ -301,7 +298,7 @@ public final class ModdedForcedDatapack {
try {
return Files.readString(hashFile, StandardCharsets.UTF_8).trim();
} catch (IOException unreadable) {
LOGGER.warn("Iris could not read the forced datapack hash at {}", hashFile, unreadable);
ModdedIrisLog.warn("Iris could not read the forced datapack hash at {}", hashFile, unreadable);
return "";
}
}
@@ -332,7 +329,7 @@ public final class ModdedForcedDatapack {
try {
hash = packsHash();
} catch (IOException | RuntimeException failure) {
LOGGER.warn("Iris could not hash the installed packs directory", failure);
ModdedIrisLog.warn("Iris could not hash the installed packs directory", failure);
hash = "";
}
packsHashMemo = new HashMemo(hash, now);
@@ -412,9 +409,9 @@ public final class ModdedForcedDatapack {
if (!presetIds.isEmpty()) {
writeWorldPresetTag(stagingDirectory, presetIds);
}
LOGGER.info("Iris forced startup datapack staged: {} pack(s), {} world preset(s), {} custom biome(s) at {}", packCount, presetIds.size(), countBiomes(seenBiomes), stagingDirectory);
ModdedIrisLog.info("Iris forced startup datapack staged: {} pack(s), {} world preset(s), {} custom biome(s) at {}", packCount, presetIds.size(), countBiomes(seenBiomes), stagingDirectory);
if (packCount == 0) {
LOGGER.warn("Iris installed NO worldgen packs into the forced datapack - custom biomes and their colors will NOT generate. Install a pack with /iris download pack=overworld, /iris download pack=underworld, or /iris download link=<zip-url>, then restart before creating an Iris world.");
ModdedIrisLog.warn("Iris installed NO worldgen packs into the forced datapack - custom biomes and their colors will NOT generate. Install a pack with /iris download pack=overworld, /iris download pack=underworld, or /iris download link=<zip-url>, then restart before creating an Iris world.");
}
}
@@ -425,13 +422,13 @@ public final class ModdedForcedDatapack {
try {
validation = PackValidator.validateForDatapackBootstrap(sourcePack);
} catch (Throwable validationFailure) {
LOGGER.error("Iris excluded pack '{}' from Create World because validation failed",
ModdedIrisLog.error("Iris excluded pack '{}' from Create World because validation failed",
sourcePack.getName(), validationFailure);
rethrowIfUnrecoverable(validationFailure);
return false;
}
if (!validation.isLoadable()) {
LOGGER.error("Iris excluded pack '{}' from Create World: {} blocking validation error(s); first error: {}",
ModdedIrisLog.error("Iris excluded pack '{}' from Create World: {} blocking validation error(s); first error: {}",
sourcePack.getName(), validation.getBlockingErrors().size(),
validation.getBlockingErrors().getFirst());
return false;
@@ -447,7 +444,7 @@ public final class ModdedForcedDatapack {
try {
installed = installPack(sourcePack, fixer, packFolders, packBiomes, packPresetIds);
} catch (Throwable installationFailure) {
LOGGER.error("Iris excluded pack '{}' from Create World because datapack serialization failed",
ModdedIrisLog.error("Iris excluded pack '{}' from Create World because datapack serialization failed",
sourcePack.getName(), installationFailure);
rethrowIfUnrecoverable(installationFailure);
installed = false;
@@ -465,7 +462,7 @@ public final class ModdedForcedDatapack {
try {
clean(packStagingDirectory);
} catch (Throwable cleanupFailure) {
LOGGER.warn("Iris could not remove temporary datapack staging for pack '{}'",
ModdedIrisLog.warn("Iris could not remove temporary datapack staging for pack '{}'",
sourcePack.getName(), cleanupFailure);
}
}
@@ -728,7 +725,7 @@ public final class ModdedForcedDatapack {
try {
clean(backupDirectory);
} catch (Throwable cleanupError) {
LOGGER.warn("Iris published the forced datapack but could not remove backup {}",
ModdedIrisLog.warn("Iris published the forced datapack but could not remove backup {}",
backupDirectory, cleanupError);
}
}
@@ -18,8 +18,6 @@
package art.arcane.iris.modded;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
@@ -42,7 +40,6 @@ import java.util.concurrent.atomic.AtomicReference;
* missing hop serializes generation on the loader's chunk threads.
*/
public final class ModdedGenPool {
private static final Logger LOGGER = LoggerFactory.getLogger("Iris");
private static final long SHUTDOWN_DRAIN_MILLIS = 2_000L;
private static final String[] C2ME_MARKERS = {
"com.ishland.c2me.base.ModProperties",
@@ -116,7 +113,7 @@ public final class ModdedGenPool {
if (pool.awaitTermination(SHUTDOWN_DRAIN_MILLIS, TimeUnit.MILLISECONDS)) {
return;
}
LOGGER.debug("Iris gen pool did not drain in {}ms, forcing shutdown", SHUTDOWN_DRAIN_MILLIS);
ModdedIrisLog.debug("Iris gen pool did not drain in {}ms, forcing shutdown", SHUTDOWN_DRAIN_MILLIS);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
@@ -131,7 +128,7 @@ public final class ModdedGenPool {
if (detected == null) {
detected = new ChunkSystem(false, "vanilla");
}
LOGGER.info("Iris chunk system: {} (parallel={}, generation on {})",
ModdedIrisLog.info("Iris chunk system: {} (parallel={}, generation on {})",
detected.description(),
detected.parallel() ? "yes" : "no",
detected.parallel() ? "loader threads" : "Iris gen pool");
@@ -182,7 +179,7 @@ public final class ModdedGenPool {
try {
value = field.get(null);
} catch (Throwable e) {
LOGGER.debug("Iris chunk system probe could not read {}.{}: {}", configClass.getName(), section, e.toString());
ModdedIrisLog.debug("Iris chunk system probe could not read {}.{}: {}", configClass.getName(), section, e.toString());
continue;
}
if (value instanceof Boolean flag) {
@@ -209,7 +206,7 @@ public final class ModdedGenPool {
return flag;
}
} catch (Throwable e) {
LOGGER.debug("Iris chunk system probe could not read {}.{}: {}", type.getName(), name, e.toString());
ModdedIrisLog.debug("Iris chunk system probe could not read {}.{}: {}", type.getName(), name, e.toString());
}
}
Method method = declaredMethodOrNull(type, name);
@@ -219,7 +216,7 @@ public final class ModdedGenPool {
return flag;
}
} catch (Throwable e) {
LOGGER.debug("Iris chunk system probe could not call {}.{}(): {}", type.getName(), name, e.toString());
ModdedIrisLog.debug("Iris chunk system probe could not call {}.{}(): {}", type.getName(), name, e.toString());
}
}
}
@@ -241,7 +238,7 @@ public final class ModdedGenPool {
return number.intValue();
}
} catch (Throwable e) {
LOGGER.debug("Iris chunk system probe could not read {}.{}: {}", marker.getName(), name, e.toString());
ModdedIrisLog.debug("Iris chunk system probe could not read {}.{}: {}", marker.getName(), name, e.toString());
}
}
}
@@ -254,7 +251,7 @@ public final class ModdedGenPool {
try {
pool = field.get(null);
} catch (Throwable e) {
LOGGER.debug("Iris chunk system probe could not read {}.{}: {}", marker.getName(), poolName, e.toString());
ModdedIrisLog.debug("Iris chunk system probe could not read {}.{}: {}", marker.getName(), poolName, e.toString());
continue;
}
if (pool == null) {
@@ -281,7 +278,7 @@ public final class ModdedGenPool {
return number.intValue();
}
} catch (Throwable e) {
LOGGER.debug("Iris chunk system probe could not call {}.{}(): {}", current.getName(), name, e.toString());
ModdedIrisLog.debug("Iris chunk system probe could not call {}.{}(): {}", current.getName(), name, e.toString());
}
}
return null;
@@ -305,7 +302,7 @@ public final class ModdedGenPool {
} catch (NoSuchFieldException e) {
return null;
} catch (Throwable e) {
LOGGER.debug("Iris chunk system probe could not access field {}.{}: {}", type.getName(), name, e.toString());
ModdedIrisLog.debug("Iris chunk system probe could not access field {}.{}: {}", type.getName(), name, e.toString());
return null;
}
}
@@ -318,7 +315,7 @@ public final class ModdedGenPool {
} catch (NoSuchMethodException e) {
return null;
} catch (Throwable e) {
LOGGER.debug("Iris chunk system probe could not access method {}.{}(): {}", type.getName(), name, e.toString());
ModdedIrisLog.debug("Iris chunk system probe could not access method {}.{}(): {}", type.getName(), name, e.toString());
return null;
}
}
@@ -336,7 +333,7 @@ public final class ModdedGenPool {
try {
return Class.forName(name, false, ModdedGenPool.class.getClassLoader());
} catch (Throwable e) {
LOGGER.debug("Iris chunk system probe: {} absent ({})", name, e.getClass().getSimpleName());
ModdedIrisLog.debug("Iris chunk system probe: {} absent ({})", name, e.getClass().getSimpleName());
return null;
}
}
@@ -46,8 +46,6 @@ import net.minecraft.world.level.levelgen.RandomSupport;
import net.minecraft.world.level.levelgen.WorldgenRandom;
import net.minecraft.world.level.levelgen.XoroshiroRandomSource;
import net.minecraft.world.level.levelgen.placement.PlacedFeature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.Arrays;
@@ -76,7 +74,6 @@ import java.util.concurrent.locks.ReentrantLock;
* {@link #generationSettings} answers exactly what vanilla's default getter answers.
*/
final class ModdedImportedFeatureStage {
private static final Logger LOGGER = LoggerFactory.getLogger("Iris");
private static final String CYCLE_MARKER = "Feature order cycle found";
private static final long NO_GENERATION = Long.MIN_VALUE;
@@ -179,7 +176,7 @@ final class ModdedImportedFeatureStage {
try {
control = NativeFeatureGenerationPolicy.control(engine);
} catch (RuntimeException error) {
LOGGER.error("Iris could not read importedFeatures for this dimension; features off: {}",
ModdedIrisLog.error("Iris could not read importedFeatures for this dimension; features off: {}",
error.toString());
markInert(generation);
return;
@@ -193,7 +190,7 @@ final class ModdedImportedFeatureStage {
IrisContext.Scope ignored = IrisContext.open(engine, lease.sessionId(), null)) {
built = buildTable(engine, control, generation);
} catch (Throwable error) {
LOGGER.error("Iris importedFeatures is off for {}: feature table construction failed: {}",
ModdedIrisLog.error("Iris importedFeatures is off for {}: feature table construction failed: {}",
dimensionKey(engine), error.toString());
markInert(generation);
return;
@@ -207,7 +204,7 @@ final class ModdedImportedFeatureStage {
// Arm the worldcheck log watch here, before any chunk decorates: arming from the first pass instead
// missed every far-chunk write the first chunk made. No-op unless -Diris.worldcheck is set.
WorldCheckFeaturePlacement.arm();
LOGGER.info("Iris importedFeatures on for {}: {} biomes, {} steps, {} custom-biome derivative maps",
ModdedIrisLog.info("Iris importedFeatures on for {}: {} biomes, {} steps, {} custom-biome derivative maps",
dimensionKey(engine), built.biomes().size(), built.steps().size(),
built.derivatives().size());
}
@@ -222,7 +219,7 @@ final class ModdedImportedFeatureStage {
// detection depend on JVM hash order and turns a real cycle into an intermittent one.
List<Holder<Biome>> biomes = biomeSource.orderedPossibleBiomes();
if (biomes.isEmpty()) {
LOGGER.error("Iris importedFeatures is on but {} exposes no biomes; features off",
ModdedIrisLog.error("Iris importedFeatures is on but {} exposes no biomes; features off",
dimensionKey(engine));
return null;
}
@@ -245,7 +242,7 @@ final class ModdedImportedFeatureStage {
if (message == null || !message.contains(CYCLE_MARKER)) {
throw error;
}
LOGGER.error("Iris importedFeatures is off for {}: the registered placed features cannot be ordered."
ModdedIrisLog.error("Iris importedFeatures is off for {}: the registered placed features cannot be ordered."
+ " {}. Remove or reorder the conflicting content, or leave"
+ " importedFeatures.enabled false.",
dimensionKey(engine), message);
@@ -276,7 +273,7 @@ final class ModdedImportedFeatureStage {
derivative = biomeSource.registeredBiome(derivativeKey);
}
if (derivative == null) {
LOGGER.warn("Iris importedFeatures: vanilla derivative {} of biome {} is not registered;"
ModdedIrisLog.warn("Iris importedFeatures: vanilla derivative {} of biome {} is not registered;"
+ " its custom biomes generate no imported features",
derivativeKey, irisBiome.getLoadKey());
continue;
@@ -54,18 +54,55 @@ public final class ModdedIrisLog {
LOGGER.info("[Iris/DEBUG] " + clean(message));
}
public static void debug(String format, Object... arguments) {
RenderedLog rendered = render(format, arguments);
if (rendered.error() == null) {
debug(rendered.message());
return;
}
if (!debugEnabled()) {
LOGGER.debug(clean(rendered.message()), rendered.error());
return;
}
LOGGER.info("[Iris/DEBUG] " + clean(rendered.message()), rendered.error());
}
public static void info(String message) {
LOGGER.info(clean(message));
}
public static void info(String format, Object... arguments) {
RenderedLog rendered = render(format, arguments);
if (rendered.error() != null) {
LOGGER.info(clean(rendered.message()), rendered.error());
return;
}
info(rendered.message());
}
public static void warn(String message) {
LOGGER.warn(clean(message));
}
public static void warn(String format, Object... arguments) {
RenderedLog rendered = render(format, arguments);
if (rendered.error() != null) {
LOGGER.warn(clean(rendered.message()), rendered.error());
return;
}
warn(rendered.message());
}
public static void error(String message) {
LOGGER.error(clean(message));
}
public static void error(String format, Object... arguments) {
RenderedLog rendered = render(format, arguments);
error(rendered.message(), rendered.error());
}
public static void error(String message, Throwable error) {
if (error == null) {
error(message);
@@ -79,6 +116,34 @@ public final class ModdedIrisLog {
return IrisLogging.clean(message);
}
static RenderedLog render(String format, Object... arguments) {
String source = format == null ? "null" : format;
if (arguments == null || arguments.length == 0) {
return new RenderedLog(source, null);
}
int argumentCount = arguments.length;
Throwable error = arguments[argumentCount - 1] instanceof Throwable throwable ? throwable : null;
if (error != null) {
argumentCount--;
}
StringBuilder output = new StringBuilder(source.length() + argumentCount * 8);
int cursor = 0;
int argumentIndex = 0;
while (argumentIndex < argumentCount) {
int placeholder = source.indexOf("{}", cursor);
if (placeholder < 0) {
break;
}
output.append(source, cursor, placeholder);
output.append(String.valueOf(arguments[argumentIndex++]));
cursor = placeholder + 2;
}
output.append(source, cursor, source.length());
return new RenderedLog(output.toString(), error);
}
private static boolean debugEnabled() {
try {
IrisSettings settings = IrisSettings.settings != null ? IrisSettings.settings : IrisSettings.get();
@@ -97,4 +162,7 @@ public final class ModdedIrisLog {
DEBUG_SETTING_WARNING_LOGGED = true;
LOGGER.warn("Iris debug logging setting could not be read", error);
}
record RenderedLog(String message, Throwable error) {
}
}
@@ -18,8 +18,6 @@
package art.arcane.iris.modded;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.lang.reflect.Method;
import java.util.ArrayList;
@@ -40,7 +38,6 @@ import java.util.function.BooleanSupplier;
* references and is safe on a dedicated server.
*/
public final class ModdedMixinAudit {
private static final Logger LOGGER = LoggerFactory.getLogger("Iris");
private static final AtomicBoolean AUDITED = new AtomicBoolean(false);
private static final List<ExpectedMixin> EXPECTED = List.of(
@@ -95,20 +92,20 @@ public final class ModdedMixinAudit {
}
}
if (missing.isEmpty()) {
LOGGER.info("Iris mixin audit ok on {} ({} dist): {}", platform,
ModdedIrisLog.info("Iris mixin audit ok on {} ({} dist): {}", platform,
clientEnvironment ? "client" : "server", String.join(", ", applied));
return;
}
LOGGER.error("===============================================================");
LOGGER.error("Iris mixin audit FAILED on {} ({} dist): {} of {} expected mixin(s) were not applied.",
ModdedIrisLog.error("===============================================================");
ModdedIrisLog.error("Iris mixin audit FAILED on {} ({} dist): {} of {} expected mixin(s) were not applied.",
platform, clientEnvironment ? "client" : "server", missing.size(),
missing.size() + applied.size());
for (String entry : missing) {
LOGGER.error(" missing: {}", entry);
ModdedIrisLog.error(" missing: {}", entry);
}
LOGGER.error("The mixin config was not registered for this loader (fabric.mod.json mixins, neoforge.mods.toml [[mixins]], forge MixinConfigs manifest attribute).");
LOGGER.error("Entity persistence, custom mob loot, parallel structure safety, or Iris world-type labels are disabled until this is fixed.");
LOGGER.error("===============================================================");
ModdedIrisLog.error("The mixin config was not registered for this loader (fabric.mod.json mixins, neoforge.mods.toml [[mixins]], forge MixinConfigs manifest attribute).");
ModdedIrisLog.error("Entity persistence, custom mob loot, parallel structure safety, or Iris world-type labels are disabled until this is fixed.");
ModdedIrisLog.error("===============================================================");
}
private static boolean isApplied(ExpectedMixin expected) {
@@ -126,7 +123,7 @@ public final class ModdedMixinAudit {
}
return false;
} catch (ClassNotFoundException | LinkageError unavailable) {
LOGGER.warn("Iris mixin audit could not inspect {}", expected.targetClass(), unavailable);
ModdedIrisLog.warn("Iris mixin audit could not inspect {}", expected.targetClass(), unavailable);
return true;
}
}
@@ -24,11 +24,8 @@ import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public final class ModdedModConfig {
private static final Logger LOGGER = LoggerFactory.getLogger("Iris");
private static final Object LOCK = new Object();
private static volatile ModdedModConfig instance;
@@ -128,7 +125,7 @@ public final class ModdedModConfig {
json.optLong("mainWorldSeed", defaults.mainWorldSeed),
json.optBoolean("mainWorldAutoRestart", defaults.mainWorldAutoRestart));
} catch (RuntimeException | IOException e) {
LOGGER.error("Iris modded config at {} is invalid; using defaults", file, e);
ModdedIrisLog.error("Iris modded config at {} is invalid; using defaults", file, e);
return defaults;
}
}
@@ -145,7 +142,7 @@ public final class ModdedModConfig {
Files.createDirectories(file.getParent());
Files.writeString(file, json.toString(4), StandardCharsets.UTF_8);
} catch (IOException e) {
LOGGER.error("Iris failed to write modded config at {}", file, e);
ModdedIrisLog.error("Iris failed to write modded config at {}", file, e);
}
}
}
@@ -29,8 +29,6 @@ import art.arcane.iris.spi.PlatformBiome;
import art.arcane.iris.spi.PlatformBlockState;
import art.arcane.iris.util.project.hunk.Hunk;
import net.minecraft.server.MinecraftServer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.nio.charset.StandardCharsets;
@@ -48,7 +46,6 @@ import java.util.Map;
import java.util.TreeMap;
public final class ModdedParityProbe {
private static final Logger LOGGER = LoggerFactory.getLogger("Iris");
private static final String DIMENSION_KEY = "overworld";
private static final long SEED = 1337L;
private static final int BIOME_STEP = 4;
@@ -82,7 +79,7 @@ public final class ModdedParityProbe {
}
if (server == null) {
LOGGER.error("[parity] server did not become ready within 10 minutes");
ModdedIrisLog.error("[parity] server did not become ready within 10 minutes");
return;
}
@@ -90,10 +87,10 @@ public final class ModdedParityProbe {
try {
match = run(server, config);
} catch (Throwable e) {
LOGGER.error("[parity] probe failed", e);
ModdedIrisLog.error("[parity] probe failed", e);
}
LOGGER.info("[parity] shutting down dev server (result={})", match ? "MATCH" : "MISMATCH");
ModdedIrisLog.info("[parity] shutting down dev server (result={})", match ? "MATCH" : "MISMATCH");
server.halt(false);
}
@@ -112,7 +109,7 @@ public final class ModdedParityProbe {
File packSource = new File(packPath);
if (!packSource.isDirectory()) {
LOGGER.error("[parity] pack folder not found: {}", packSource.getAbsolutePath());
ModdedIrisLog.error("[parity] pack folder not found: {}", packSource.getAbsolutePath());
return false;
}
@@ -121,14 +118,14 @@ public final class ModdedParityProbe {
File workRoot = Files.createTempDirectory("iris-parity").toFile();
File pack = clonePack(packSource, workRoot);
LOGGER.info("[parity] pack: {}", packSource.getAbsolutePath());
LOGGER.info("[parity] work copy: {}", pack.getAbsolutePath());
LOGGER.info("[parity] radius: {} ({} chunks)", radius, (2 * radius + 1) * (2 * radius + 1));
ModdedIrisLog.info("[parity] pack: {}", packSource.getAbsolutePath());
ModdedIrisLog.info("[parity] work copy: {}", pack.getAbsolutePath());
ModdedIrisLog.info("[parity] radius: {} ({} chunks)", radius, (2 * radius + 1) * (2 * radius + 1));
IrisData data = IrisData.get(pack);
IrisDimension dimension = data.getDimensionLoader().load(DIMENSION_KEY);
if (dimension == null) {
LOGGER.error("[parity] dimension '{}' did not load from {}", DIMENSION_KEY, pack.getAbsolutePath());
ModdedIrisLog.error("[parity] dimension '{}' did not load from {}", DIMENSION_KEY, pack.getAbsolutePath());
return false;
}
@@ -147,7 +144,7 @@ public final class ModdedParityProbe {
int minY = dimension.getMinHeight();
int maxY = dimension.getMaxHeight();
int height = maxY - minY;
LOGGER.info("[parity] engine up: dim={} seed={} minY={} maxY={}", engine.getDimension().getLoadKey(), engine.getSeedManager().getSeed(), minY, maxY);
ModdedIrisLog.info("[parity] engine up: dim={} seed={} minY={} maxY={}", engine.getDimension().getLoadKey(), engine.getSeedManager().getSeed(), minY, maxY);
Map<String, String> goldenChunks = new HashMap<>();
String goldenCombined = null;
@@ -173,7 +170,7 @@ public final class ModdedParityProbe {
goldenChunks.put(line.substring(0, second), line);
}
}
LOGGER.info("[parity] golden: {} ({} chunks, combined={})", goldenPath, goldenChunks.size(), goldenCombined);
ModdedIrisLog.info("[parity] golden: {} ({} chunks, combined={})", goldenPath, goldenChunks.size(), goldenCombined);
}
PlatformBlockState airState = IrisPlatforms.get().registries().air();
@@ -198,9 +195,9 @@ public final class ModdedParityProbe {
if (!failures.isEmpty()) {
failed++;
LOGGER.error("[parity] chunk {},{} FAILED ({} error(s))", cx, cz, failures.size());
ModdedIrisLog.error("[parity] chunk {},{} FAILED ({} error(s))", cx, cz, failures.size());
for (Throwable failure : failures) {
LOGGER.error("[parity] chunk {},{} error", cx, cz, failure);
ModdedIrisLog.error("[parity] chunk {},{} error", cx, cz, failure);
}
continue;
}
@@ -212,9 +209,9 @@ public final class ModdedParityProbe {
String golden = goldenChunks.get(key);
if (golden != null && !golden.equals(line)) {
mismatches.add(key);
LOGGER.warn("[parity] chunk {} MISMATCH", key);
LOGGER.warn("[parity] golden: {}", golden);
LOGGER.warn("[parity] actual: {}", line);
ModdedIrisLog.warn("[parity] chunk {} MISMATCH", key);
ModdedIrisLog.warn("[parity] golden: {}", golden);
ModdedIrisLog.warn("[parity] actual: {}", line);
if (mismatches.size() == 1) {
diffDeep(cx, cz, blocks, height, minY);
}
@@ -231,9 +228,9 @@ public final class ModdedParityProbe {
boolean match = goldenChunks.isEmpty() ? combinedMatch : (chunkMatch && (radius != 8 || combinedMatch));
if (!goldenChunks.isEmpty()) {
LOGGER.info("[parity] per-chunk: {}/{} matched golden ({} failed)", body.size() - mismatches.size(), body.size(), failed);
ModdedIrisLog.info("[parity] per-chunk: {}/{} matched golden ({} failed)", body.size() - mismatches.size(), body.size(), failed);
}
LOGGER.info("[parity] combined={} expected={} {} ({}/{})",
ModdedIrisLog.info("[parity] combined={} expected={} {} ({}/{})",
combined.substring(0, 12), expected, match ? "MATCH" : "MISMATCH", body.size() - mismatches.size(), targets.size());
return match;
}
@@ -246,7 +243,7 @@ public final class ModdedParityProbe {
try {
Path goldenDump = Path.of(deepDir, cx + "_" + cz + ".txt");
if (!Files.exists(goldenDump)) {
LOGGER.warn("[parity] no deep dump for chunk {},{} at {}", cx, cz, goldenDump);
ModdedIrisLog.warn("[parity] no deep dump for chunk {},{} at {}", cx, cz, goldenDump);
return;
}
List<String> golden = Files.readAllLines(goldenDump, StandardCharsets.UTF_8);
@@ -267,15 +264,15 @@ public final class ModdedParityProbe {
String g = i < golden.size() ? golden.get(i) : "<missing>";
String a = i < actual.size() ? actual.get(i) : "<missing>";
if (!g.equals(a)) {
LOGGER.warn("[parity] deep diff line {}: golden='{}' actual='{}'", i, g, a);
ModdedIrisLog.warn("[parity] deep diff line {}: golden='{}' actual='{}'", i, g, a);
shown++;
}
}
File out = new File(IrisPlatforms.get().dataFolder("parity"), "deep-" + cx + "_" + cz + ".txt");
Files.write(out.toPath(), actual, StandardCharsets.UTF_8);
LOGGER.warn("[parity] full actual dump: {}", out.getAbsolutePath());
ModdedIrisLog.warn("[parity] full actual dump: {}", out.getAbsolutePath());
} catch (Throwable e) {
LOGGER.warn("[parity] deep diff failed", e);
ModdedIrisLog.warn("[parity] deep diff failed", e);
}
}
@@ -367,7 +364,7 @@ public final class ModdedParityProbe {
}
} else {
for (Throwable error : batch) {
LOGGER.warn("[parity] engine-init reported error (non-fatal)", error);
ModdedIrisLog.warn("[parity] engine-init reported error (non-fatal)", error);
}
quietSince = System.currentTimeMillis();
}
@@ -21,8 +21,6 @@ package art.arcane.iris.modded;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
@@ -31,7 +29,6 @@ import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
public final class ModdedPrimaryWorldRouter {
private static final Logger LOGGER = LoggerFactory.getLogger("Iris");
private static final int TICK_INTERVAL = 20;
private static final Set<UUID> routed = ConcurrentHashMap.newKeySet();
@@ -96,7 +93,7 @@ public final class ModdedPrimaryWorldRouter {
ModdedDimensionManager.teleport(player, server, primary, player.getX(), Double.MIN_VALUE, player.getZ());
routed.add(id);
} catch (Throwable e) {
LOGGER.error("Iris failed to route player {} to primary world '{}'", id, primary, e);
ModdedIrisLog.error("Iris failed to route player {} to primary world '{}'", id, primary, e);
}
}
}
@@ -19,6 +19,7 @@
package art.arcane.iris.modded;
import art.arcane.iris.core.protocol.EngineResolver;
import art.arcane.iris.core.protocol.IrisCursorRequestService;
import art.arcane.iris.core.protocol.IrisProtocolServer;
import art.arcane.iris.core.protocol.IrisSession;
import art.arcane.iris.core.protocol.IrisSessionRegistry;
@@ -30,8 +31,6 @@ import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.level.chunk.ChunkGenerator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Objects;
import java.util.UUID;
@@ -43,7 +42,6 @@ public final class ModdedProtocolHandler {
| IrisProtocol.CAPABILITY_CURSOR
| IrisProtocol.CAPABILITY_STUDIO;
private static final int DIMENSION_SYNC_INTERVAL_TICKS = 5;
private static final Logger LOGGER = LoggerFactory.getLogger("Iris");
private static final ConcurrentHashMap<String, Engine> SESSION_ENGINES = new ConcurrentHashMap<>();
private static final ConcurrentHashMap<String, String> SESSION_LEVELS = new ConcurrentHashMap<>();
@@ -53,6 +51,7 @@ public final class ModdedProtocolHandler {
private static volatile IrisSessionRegistry registry;
private static volatile IrisProtocolServer protocolServer;
private static volatile ModdedProtocolTransport transport;
private static volatile IrisCursorRequestService cursorRequests;
private static volatile IrisVisionRequestService visionRequests;
private static int dimensionSyncTicks;
@@ -80,11 +79,14 @@ public final class ModdedProtocolHandler {
return engine == null || engine.isClosed() ? null : engine;
};
protocol.setEngineResolver(engineResolver);
IrisCursorRequestService cursorService = IrisCursorRequestService.create(engineResolver, sessionRegistry);
protocol.setCursorInfoHandler(cursorService);
IrisVisionRequestService visionService = IrisVisionRequestService.create(engineResolver, sessionRegistry);
protocol.setVisionTileHandler(visionService);
registry = sessionRegistry;
transport = serverTransport;
protocolServer = protocol;
cursorRequests = cursorService;
visionRequests = visionService;
IrisServices.register(IrisProtocolServer.class, protocol);
if (server.getPlayerList() == null) {
@@ -98,10 +100,14 @@ public final class ModdedProtocolHandler {
public static void stop() {
IrisServices.remove(IrisProtocolServer.class);
IrisSessionRegistry current = registry;
IrisCursorRequestService cursor = cursorRequests;
IrisVisionRequestService vision = visionRequests;
if (current != null) {
for (IrisSession session : current.all()) {
current.unregister(session.id());
if (cursor != null) {
cursor.clearSession(session.id());
}
if (vision != null) {
vision.clearSession(session.id());
}
@@ -114,6 +120,7 @@ public final class ModdedProtocolHandler {
registry = null;
protocolServer = null;
transport = null;
cursorRequests = null;
visionRequests = null;
}
@@ -147,6 +154,10 @@ public final class ModdedProtocolHandler {
if (current != null) {
current.unregister(sessionId);
}
IrisCursorRequestService cursor = cursorRequests;
if (cursor != null) {
cursor.clearSession(sessionId);
}
IrisVisionRequestService vision = visionRequests;
if (vision != null) {
vision.clearSession(sessionId);
@@ -229,7 +240,7 @@ public final class ModdedProtocolHandler {
Engine engine = generator.engineIfBound();
return engine == null || engine.isClosed() ? null : engine;
} catch (Throwable failure) {
LOGGER.error("Iris dimension status engine lookup failed for {}", level.dimension().identifier(), failure);
ModdedIrisLog.error("Iris dimension status engine lookup failed for {}", level.dimension().identifier(), failure);
return null;
}
}
@@ -21,8 +21,6 @@ package art.arcane.iris.modded;
import art.arcane.iris.spi.PlatformScheduler;
import art.arcane.iris.spi.PlatformWorld;
import net.minecraft.server.MinecraftServer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
@@ -41,7 +39,6 @@ import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
public final class ModdedScheduler implements PlatformScheduler {
private static final Logger LOGGER = LoggerFactory.getLogger("Iris");
private static final int ASYNC_MAX_THREADS = Math.max(4, Runtime.getRuntime().availableProcessors());
private static final long ASYNC_KEEP_ALIVE_SECONDS = 30L;
private static final int ASYNC_BACKLOG_WARN = 8192;
@@ -85,10 +82,10 @@ public final class ModdedScheduler implements PlatformScheduler {
rejectionAwareTask.reject();
}
if (executor.isShutdown()) {
LOGGER.debug("Iris async task dropped: scheduler is shut down");
ModdedIrisLog.debug("Iris async task dropped: scheduler is shut down");
return;
}
LOGGER.error("Iris async task rejected by the executor (queued={} active={})",
ModdedIrisLog.error("Iris async task rejected by the executor (queued={} active={})",
executor.getQueue().size(), executor.getActiveCount());
};
}
@@ -217,7 +214,7 @@ public final class ModdedScheduler implements PlatformScheduler {
if (now - last < ASYNC_BACKLOG_WARN_INTERVAL_MILLIS || !lastBacklogWarnAt.compareAndSet(last, now)) {
return;
}
LOGGER.warn("Iris async backlog {} tasks (threads={}); async work is falling behind", queued, executor.getPoolSize());
ModdedIrisLog.warn("Iris async backlog {} tasks (threads={}); async work is falling behind", queued, executor.getPoolSize());
}
private void drain() {
@@ -262,7 +259,7 @@ public final class ModdedScheduler implements PlatformScheduler {
try {
task.run();
} catch (Throwable error) {
LOGGER.error("Iris scheduled task failed", error);
ModdedIrisLog.error("Iris scheduled task failed", error);
}
}
@@ -23,8 +23,6 @@ import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.storage.LevelStorageSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ConcurrentModificationException;
import java.util.LinkedHashMap;
@@ -35,7 +33,6 @@ import java.util.concurrent.Executor;
import java.util.function.Consumer;
public final class ModdedServerLevels implements ModdedServerAccess {
private static final Logger LOGGER = LoggerFactory.getLogger("Iris");
private static final int CAPTURE_ATTEMPTS = 16;
private static volatile Snapshot snapshot;
@@ -125,7 +122,7 @@ public final class ModdedServerLevels implements ModdedServerAccess {
Thread.onSpinWait();
}
}
LOGGER.error("Iris could not snapshot the level map after {} attempts; readers will see the previous snapshot", CAPTURE_ATTEMPTS);
ModdedIrisLog.error("Iris could not snapshot the level map after {} attempts; readers will see the previous snapshot", CAPTURE_ATTEMPTS);
Snapshot current = snapshot;
return current != null && current.server() == server ? current : null;
}
@@ -21,14 +21,11 @@ package art.arcane.iris.modded;
import art.arcane.iris.modded.service.ModdedService;
import art.arcane.iris.modded.service.ModdedTickableService;
import net.minecraft.server.MinecraftServer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.LinkedHashMap;
import java.util.Map;
public final class ModdedServiceManager {
private static final Logger LOGGER = LoggerFactory.getLogger("Iris");
private final Map<Class<? extends ModdedService>, ModdedService> services = new LinkedHashMap<>();
private boolean enabled = false;
@@ -103,7 +100,7 @@ public final class ModdedServiceManager {
service.onDisable();
} catch (Throwable serviceFailure) {
failed++;
LOGGER.error("Iris service onDisable failed for {}", service.getClass().getName(), serviceFailure);
ModdedIrisLog.error("Iris service onDisable failed for {}", service.getClass().getName(), serviceFailure);
if (failure == null) {
failure = serviceFailure;
} else if (serviceFailure != failure) {
@@ -113,7 +110,7 @@ public final class ModdedServiceManager {
}
enabled = false;
if (failure != null) {
LOGGER.error("Iris disabled all services with {} failure(s)", failed, failure);
ModdedIrisLog.error("Iris disabled all services with {} failure(s)", failed, failure);
}
}
@@ -132,7 +129,7 @@ public final class ModdedServiceManager {
try {
service.onServerTick(server);
} catch (Throwable error) {
LOGGER.error("Iris service tick failed for {}", service.getClass().getName(), error);
ModdedIrisLog.error("Iris service tick failed for {}", service.getClass().getName(), error);
}
}
@@ -143,12 +140,12 @@ public final class ModdedServiceManager {
if (cleanupError != failure) {
failure.addSuppressed(cleanupError);
}
LOGGER.error("Iris service rollback failed for {}", service.getClass().getName(), cleanupError);
ModdedIrisLog.error("Iris service rollback failed for {}", service.getClass().getName(), cleanupError);
}
}
private RuntimeException serviceFailure(ModdedService service, Throwable failure) {
LOGGER.error("Iris service onEnable failed for {}", service.getClass().getName(), failure);
ModdedIrisLog.error("Iris service onEnable failed for {}", service.getClass().getName(), failure);
if (failure instanceof RuntimeException runtimeException) {
return runtimeException;
}
@@ -29,8 +29,6 @@ import net.minecraft.commands.Commands;
import net.minecraft.network.chat.Component;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerPlayer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.IOException;
@@ -43,7 +41,6 @@ import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Stream;
public final class ModdedStartup {
private static final Logger LOGGER = LoggerFactory.getLogger("Iris");
private static final AtomicBoolean PREPARED = new AtomicBoolean(false);
private static final AtomicBoolean STARTED = new AtomicBoolean(false);
@@ -76,7 +73,7 @@ public final class ModdedStartup {
}
if (!PackDirectoryResolver.listVisiblePackDirectories(legacy).isEmpty()) {
File real = art.arcane.iris.spi.IrisPlatforms.get().packsFolderNoCreate();
LOGGER.warn("Iris found packs under the legacy directory {} - modded packs load from {} only. Move them there.",
ModdedIrisLog.warn("Iris found packs under the legacy directory {} - modded packs load from {} only. Move them there.",
legacy.getAbsolutePath(), real.getAbsolutePath());
return;
}
@@ -85,7 +82,7 @@ public final class ModdedStartup {
legacy.delete();
}
} catch (Throwable e) {
LOGGER.debug("Iris legacy packs directory check failed", e);
ModdedIrisLog.debug("Iris legacy packs directory check failed", e);
}
}
@@ -104,7 +101,7 @@ public final class ModdedStartup {
try {
ModdedForcedDatapack.regenerateIfStale("boot");
} catch (Throwable failure) {
LOGGER.error("Iris could not refresh the forced datapack at boot", failure);
ModdedIrisLog.error("Iris could not refresh the forced datapack at boot", failure);
}
}
@@ -133,7 +130,7 @@ public final class ModdedStartup {
List<File> packDirs = PackDirectoryResolver.listVisiblePackDirectories(packsRoot);
PackValidationRegistry.clear();
if (packDirs.isEmpty()) {
LOGGER.info("Iris found no packs to validate under {}; install one with /iris download pack=overworld, /iris download pack=underworld, or /iris download link=<zip-url>",
ModdedIrisLog.info("Iris found no packs to validate under {}; install one with /iris download pack=overworld, /iris download pack=underworld, or /iris download link=<zip-url>",
packsRoot.getAbsolutePath());
return;
}
@@ -142,19 +139,19 @@ public final class ModdedStartup {
PackValidationResult result = PackValidator.validate(packDir);
PackValidationRegistry.publish(result);
if (!result.isLoadable()) {
LOGGER.error("Iris pack '{}' FAILED validation with {} blocking error(s); world/studio creation will be refused. First error: {}",
ModdedIrisLog.error("Iris pack '{}' FAILED validation with {} blocking error(s); world/studio creation will be refused. First error: {}",
result.getPackName(), result.getBlockingErrors().size(),
result.getBlockingErrors().getFirst());
} else if (!result.getWarnings().isEmpty()) {
LOGGER.info("Iris pack '{}' validated ({} warning(s)).", result.getPackName(), result.getWarnings().size());
ModdedIrisLog.info("Iris pack '{}' validated ({} warning(s)).", result.getPackName(), result.getWarnings().size());
for (String warning : result.getWarnings()) {
LOGGER.warn(" [{}] {}", result.getPackName(), warning);
ModdedIrisLog.warn(" [{}] {}", result.getPackName(), warning);
}
} else {
LOGGER.info("Iris pack '{}' validated.", result.getPackName());
ModdedIrisLog.info("Iris pack '{}' validated.", result.getPackName());
}
} catch (Throwable e) {
LOGGER.error("Iris pack validation failed for '{}'", packDir.getName(), e);
ModdedIrisLog.error("Iris pack validation failed for '{}'", packDir.getName(), e);
String detail = e.getMessage();
if (detail == null || detail.isBlank()) {
detail = e.getClass().getSimpleName();
@@ -190,7 +187,7 @@ public final class ModdedStartup {
} catch (BrokenPackException e) {
throw e;
} catch (Throwable e) {
LOGGER.error("Iris required world-creation validation failed for '{}'", pack, e);
ModdedIrisLog.error("Iris required world-creation validation failed for '{}'", pack, e);
String detail = e.getMessage();
if (detail == null || detail.isBlank()) {
detail = e.getClass().getSimpleName();
@@ -220,17 +217,17 @@ public final class ModdedStartup {
try {
ModdedDimensionManager.create(server, dimension.id(), dimension.pack(), dimension.dimension(), dimension.seed());
injected++;
LOGGER.info("Iris re-injected {}/{} '{}' (pack={} dim={}) in {}ms",
ModdedIrisLog.info("Iris re-injected {}/{} '{}' (pack={} dim={}) in {}ms",
index, dimensions.size(), dimension.id(), dimension.pack(), dimension.dimension(),
System.currentTimeMillis() - dimensionStartedAt);
} catch (Throwable e) {
LOGGER.error("Iris failed to re-inject persistent dimension '{}' (pack={} dim={} seed={})", dimension.id(), dimension.pack(), dimension.dimension(), dimension.seed(), e);
ModdedIrisLog.error("Iris failed to re-inject persistent dimension '{}' (pack={} dim={} seed={})", dimension.id(), dimension.pack(), dimension.dimension(), dimension.seed(), e);
if (e instanceof OutOfMemoryError outOfMemory) {
throw outOfMemory;
}
}
}
LOGGER.info("Iris re-injected {}/{} persistent dimension(s) at startup in {}ms",
ModdedIrisLog.info("Iris re-injected {}/{} persistent dimension(s) at startup in {}ms",
injected, dimensions.size(), System.currentTimeMillis() - startedAt);
}
@@ -245,7 +242,7 @@ public final class ModdedStartup {
}
}
} catch (IOException | RuntimeException unreadable) {
LOGGER.debug("Iris could not stat {} for validation reuse; revalidating", root, unreadable);
ModdedIrisLog.debug("Iris could not stat {} for validation reuse; revalidating", root, unreadable);
return Long.MAX_VALUE;
}
return newest;
@@ -32,8 +32,6 @@ import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.chunk.ChunkAccess;
import net.minecraft.world.level.chunk.LevelChunkSection;
import net.minecraft.world.level.levelgen.Heightmap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
@@ -53,7 +51,6 @@ public final class ModdedWorldCheck {
private static final long SERVER_WAIT_TIMEOUT_MILLIS = 600000L;
private static final long SERVER_WAIT_INTERVAL_MILLIS = 250L;
private static final long SERVER_TASK_TIMEOUT_MILLIS = 900000L;
private static final Logger LOGGER = LoggerFactory.getLogger("Iris");
// halt, not exit: awaitStopAndExit already waited for MinecraftServer.halt(true), and exit() would run the
// shutdown hooks and block behind the server thread it just stopped, so a finished check could hang forever.
private static final ProcessExit PROCESS_EXIT = Runtime.getRuntime()::halt;
@@ -100,7 +97,7 @@ public final class ModdedWorldCheck {
}
if (server == null) {
LOGGER.error("[worldcheck] server did not finish starting within 10 minutes");
ModdedIrisLog.error("[worldcheck] server did not finish starting within 10 minutes");
return;
}
@@ -115,15 +112,15 @@ public final class ModdedWorldCheck {
}
)).get(SERVER_TASK_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
LOGGER.error("[worldcheck] coordinator interrupted", e);
ModdedIrisLog.error("[worldcheck] coordinator interrupted", e);
Thread.currentThread().interrupt();
} catch (TimeoutException e) {
LOGGER.error("[worldcheck] server task did not finish within {}ms", SERVER_TASK_TIMEOUT_MILLIS);
ModdedIrisLog.error("[worldcheck] server task did not finish within {}ms", SERVER_TASK_TIMEOUT_MILLIS);
} catch (Throwable e) {
LOGGER.error("[worldcheck] check failed", e);
ModdedIrisLog.error("[worldcheck] check failed", e);
} finally {
int resultCode = exitCode;
LOGGER.info("[worldcheck] shutting down dev server (result={})", resultCode == EXIT_PASS ? "PASS" : "FAIL");
ModdedIrisLog.info("[worldcheck] shutting down dev server (result={})", resultCode == EXIT_PASS ? "PASS" : "FAIL");
MinecraftServer serverRef = server;
if (serverRef != null && stopRequested.get()) {
awaitStopAndExit(() -> serverRef.halt(true), resultCode, processExit);
@@ -138,12 +135,12 @@ public final class ModdedWorldCheck {
try {
exitCode = check.getAsBoolean() ? EXIT_PASS : EXIT_FAILURE;
} catch (Throwable e) {
LOGGER.error("[worldcheck] check failed", e);
ModdedIrisLog.error("[worldcheck] check failed", e);
}
try {
requestStop.run();
} catch (Throwable e) {
LOGGER.error("[worldcheck] server stop request failed", e);
ModdedIrisLog.error("[worldcheck] server stop request failed", e);
return EXIT_FAILURE;
}
return exitCode;
@@ -159,7 +156,7 @@ public final class ModdedWorldCheck {
awaitStop.run();
} catch (Throwable e) {
exitCode = EXIT_FAILURE;
LOGGER.error("[worldcheck] waiting for server shutdown failed", e);
ModdedIrisLog.error("[worldcheck] waiting for server shutdown failed", e);
} finally {
if (interrupted) {
Thread.currentThread().interrupt();
@@ -171,25 +168,25 @@ public final class ModdedWorldCheck {
private static WorldCheckPreparation run(MinecraftServer server) {
ServerLevel level = targetLevel(server);
if (level == null) {
LOGGER.error("[worldcheck] no Iris dimension is loaded");
ModdedIrisLog.error("[worldcheck] no Iris dimension is loaded");
return new WorldCheckPreparation(false, false, false, false,
new NativeStructureGate(false, 0, false, null));
}
String levelId = level.dimension().identifier().toString();
String generatorClass = level.getChunkSource().getGenerator().getClass().getName();
LOGGER.info("[worldcheck] {} generator: {}", levelId, generatorClass);
ModdedIrisLog.info("[worldcheck] {} generator: {}", levelId, generatorClass);
IrisModdedChunkGenerator generator = level.getChunkSource().getGenerator() instanceof IrisModdedChunkGenerator iris
? iris : null;
boolean irisGenerator = generator != null;
if (!irisGenerator) {
LOGGER.error("[worldcheck] {} is NOT using IrisModdedChunkGenerator", levelId);
ModdedIrisLog.error("[worldcheck] {} is NOT using IrisModdedChunkGenerator", levelId);
}
boolean dimensionTypeOk = generator != null
&& WorldCheckDimensionContract.checkDimensionType(level, generator);
BlockPos spawn = level.getRespawnData().pos();
LOGGER.info("[worldcheck] spawn: {} {} {} (minY={} height={})", spawn.getX(), spawn.getY(), spawn.getZ(), level.getMinY(), level.getHeight());
ModdedIrisLog.info("[worldcheck] spawn: {} {} {} (minY={} height={})", spawn.getX(), spawn.getY(), spawn.getZ(), level.getMinY(), level.getHeight());
MessageDigest digest = WorldCheckPredicates.sha256();
List<String> samples = new ArrayList<>();
@@ -210,9 +207,9 @@ public final class ModdedWorldCheck {
}
for (int i = 0; i < Math.min(6, samples.size()); i++) {
LOGGER.info("[worldcheck] surface sample: {}", samples.get(i));
ModdedIrisLog.info("[worldcheck] surface sample: {}", samples.get(i));
}
LOGGER.info("[worldcheck] surface digest: {} ({} columns, {} distinct surface blocks: {})",
ModdedIrisLog.info("[worldcheck] surface digest: {} ({} columns, {} distinct surface blocks: {})",
HexFormat.of().formatHex(digest.digest()).substring(0, 12), samples.size(), surfaceKeys.size(), surfaceKeys);
ChunkAccess zeroChunk = level.getChunk(0, 0);
@@ -230,16 +227,16 @@ public final class ModdedWorldCheck {
columnKeys.add(BuiltInRegistries.BLOCK.getKey(state.getBlock()).toString());
}
}
LOGGER.info("[worldcheck] chunk 0,0: {} non-empty sections of {}; column blocks at (8,*,8): {}",
ModdedIrisLog.info("[worldcheck] chunk 0,0: {} non-empty sections of {}; column blocks at (8,*,8): {}",
nonEmptySections, zeroChunk.getSections().length, columnKeys);
boolean sectionsOk = nonEmptySections >= 4;
boolean varietyOk = columnKeys.size() >= 2 || surfaceKeys.size() >= 2;
if (!sectionsOk) {
LOGGER.error("[worldcheck] chunk 0,0 looks empty/vanilla-flat ({} non-empty sections)", nonEmptySections);
ModdedIrisLog.error("[worldcheck] chunk 0,0 looks empty/vanilla-flat ({} non-empty sections)", nonEmptySections);
}
if (!varietyOk) {
LOGGER.error("[worldcheck] generated terrain has no block variety (flat-world signature)");
ModdedIrisLog.error("[worldcheck] generated terrain has no block variety (flat-world signature)");
}
boolean entityMixinsOk = WorldCheckDimensionContract.checkEntityMixins(level);
@@ -262,7 +259,7 @@ public final class ModdedWorldCheck {
WorldCheckPredicates.qaEvent("village_poi_metric", "village", poiOk,
"inBounds=" + poi.inBounds() + ",outOfBounds=" + poi.outOfBounds());
if (!poiOk) {
LOGGER.error("[worldcheck] village POI audit failed: inBounds={} outOfBounds={}",
ModdedIrisLog.error("[worldcheck] village POI audit failed: inBounds={} outOfBounds={}",
poi.inBounds(), poi.outOfBounds());
}
} else {
@@ -271,12 +268,12 @@ public final class ModdedWorldCheck {
int passed = structureGate.nonVillagePassed()
+ (structureGate.villagePassBeforePoi() && poiOk ? 1 : 0);
boolean structurePass = structureGate.passBeforePoi() && poiOk;
LOGGER.info("[worldcheck] native structure gate: {}/{} passed", passed,
ModdedIrisLog.info("[worldcheck] native structure gate: {}/{} passed", passed,
WorldCheckStructureAudit.STRUCTURE_CHECKS.size());
WorldCheckPredicates.qaEvent("structure_aggregate", "all", structurePass,
"passed=" + passed + ",total=" + WorldCheckStructureAudit.STRUCTURE_CHECKS.size());
boolean pass = preparation.nonStructurePass() && structurePass;
LOGGER.info("[worldcheck] {}", pass ? "PASS" : "FAIL");
ModdedIrisLog.info("[worldcheck] {}", pass ? "PASS" : "FAIL");
WorldCheckPredicates.qaEvent("worldcheck_final", "all", pass,
"structures=" + WorldCheckStructureAudit.STRUCTURE_CHECKS.size()
+ ",terrain=" + preparation.terrainOk()
@@ -295,7 +292,7 @@ public final class ModdedWorldCheck {
if (requested != null) {
return requested;
}
LOGGER.error("[worldcheck] requested dimension '{}' is not loaded", target);
ModdedIrisLog.error("[worldcheck] requested dimension '{}' is not loaded", target);
return null;
}
@@ -31,8 +31,6 @@ import art.arcane.iris.modded.command.ModdedGuiHost;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.dimension.DimensionType;
import net.minecraft.world.level.storage.LevelResource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.util.ArrayList;
@@ -42,7 +40,6 @@ import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
public final class ModdedWorldEngines {
private static final Logger LOGGER = LoggerFactory.getLogger("Iris");
private static final ConcurrentHashMap<ServerLevel, Engine> ENGINES = new ConcurrentHashMap<>();
private ModdedWorldEngines() {
@@ -64,7 +61,7 @@ public final class ModdedWorldEngines {
try {
evictOrThrow(level);
} catch (Throwable e) {
LOGGER.error("Iris engine evict close failed for {}", level.dimension().identifier(), e);
ModdedIrisLog.error("Iris engine evict close failed for {}", level.dimension().identifier(), e);
}
}
@@ -80,7 +77,7 @@ public final class ModdedWorldEngines {
}
// The GUI host holds strong Engine/ServerLevel references with no other remove path.
ModdedGuiHost.unbind(removed[0]);
LOGGER.info("Iris engine evicted for {}", level.dimension().identifier());
ModdedIrisLog.info("Iris engine evicted for {}", level.dimension().identifier());
}
static Engine prepareReplacement(ServerLevel level, String pack, String dimensionKey, long seedOverride) {
@@ -111,7 +108,7 @@ public final class ModdedWorldEngines {
IrisData data = IrisData.openRuntime(packDir);
IrisDimension dimension = data.getDimensionLoader().load(dimensionKey);
if (dimension == null) {
LOGGER.error("Iris pack '{}' at {} does not contain dimension '{}' (expected dimensions/{}.json). Install a matching Iris pack and restart.",
ModdedIrisLog.error("Iris pack '{}' at {} does not contain dimension '{}' (expected dimensions/{}.json). Install a matching Iris pack and restart.",
pack, packDir.getAbsolutePath(), dimensionKey, dimensionKey);
throw new IllegalStateException("Iris dimension '" + dimensionKey + "' missing from pack " + packDir.getAbsolutePath());
}
@@ -142,7 +139,7 @@ public final class ModdedWorldEngines {
throw failure;
}
LOGGER.info("Iris engine up for {}: pack={} dim={} seed={} height={}..{}",
ModdedIrisLog.info("Iris engine up for {}: pack={} dim={} seed={} height={}..{}",
level.dimension().identifier(), packDir.getAbsolutePath(), dimension.getLoadKey(), seed, dimension.getMinHeight(), dimension.getMaxHeight());
return engine;
}
@@ -182,11 +179,11 @@ public final class ModdedWorldEngines {
return packDir;
}
LOGGER.error("===============================================================");
LOGGER.error("Iris pack '{}' is not installed.", pack);
LOGGER.error("Expected a pack folder at: {}", packDir.getAbsolutePath());
LOGGER.error("Install an Iris pack there (the folder must contain dimensions/{}.json) and restart the server.", dimensionKey);
LOGGER.error("===============================================================");
ModdedIrisLog.error("===============================================================");
ModdedIrisLog.error("Iris pack '{}' is not installed.", pack);
ModdedIrisLog.error("Expected a pack folder at: {}", packDir.getAbsolutePath());
ModdedIrisLog.error("Install an Iris pack there (the folder must contain dimensions/{}.json) and restart the server.", dimensionKey);
ModdedIrisLog.error("===============================================================");
throw new IllegalStateException("Iris pack not installed: " + packDir.getAbsolutePath());
}
@@ -215,9 +212,9 @@ public final class ModdedWorldEngines {
+ level.dimension().identifier());
}
}
LOGGER.info("Iris engine closed for {}", level.dimension().identifier());
ModdedIrisLog.info("Iris engine closed for {}", level.dimension().identifier());
} catch (Throwable e) {
LOGGER.error("Iris engine close failed for {}", level.dimension().identifier(), e);
ModdedIrisLog.error("Iris engine close failed for {}", level.dimension().identifier(), e);
if (failure == null) {
failure = e;
} else if (e != failure) {
@@ -25,11 +25,8 @@ import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.entity.item.ItemEntity;
import net.minecraft.world.item.Items;
import net.minecraft.world.level.dimension.DimensionType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
final class WorldCheckDimensionContract {
private static final Logger LOGGER = LoggerFactory.getLogger("Iris");
private WorldCheckDimensionContract() {
}
@@ -44,13 +41,13 @@ final class WorldCheckDimensionContract {
+ ",levelMinY=" + level.getMinY() + ",levelHeight=" + level.getHeight();
WorldCheckPredicates.qaEvent("dimension_type", dimension.getLoadKey(), pass, detail);
if (!pass) {
LOGGER.error("[worldcheck] dimension type mismatch for {}: {}", dimension.getLoadKey(), detail);
ModdedIrisLog.error("[worldcheck] dimension type mismatch for {}: {}", dimension.getLoadKey(), detail);
} else {
LOGGER.info("[worldcheck] dimension type contract: {}", detail);
ModdedIrisLog.info("[worldcheck] dimension type contract: {}", detail);
}
return pass;
} catch (Throwable error) {
LOGGER.error("[worldcheck] could not validate the Iris dimension type contract", error);
ModdedIrisLog.error("[worldcheck] could not validate the Iris dimension type contract", error);
WorldCheckPredicates.qaEvent("dimension_type", generator.activeDimensionKey(), false,
"validationError=" + error.getClass().getSimpleName() + ":" + error.getMessage());
return false;
@@ -102,7 +99,7 @@ final class WorldCheckDimensionContract {
WorldCheckPredicates.qaEvent("entity_mixin", "persistence", pass,
"vanilla=" + vanillaSave + ",suppressed=" + suppressed + ",restored=" + restored);
if (!pass) {
LOGGER.error("[worldcheck] shared entity mixins are not active on this loader");
ModdedIrisLog.error("[worldcheck] shared entity mixins are not active on this loader");
}
return pass;
}
@@ -20,14 +20,11 @@ package art.arcane.iris.modded;
import art.arcane.iris.engine.framework.StructureVerticalBounds;
import art.arcane.iris.modded.WorldCheckStructureAudit.StructureCheck;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
final class WorldCheckPredicates {
private static final Logger LOGGER = LoggerFactory.getLogger("Iris");
private WorldCheckPredicates() {
}
@@ -39,7 +36,7 @@ final class WorldCheckPredicates {
}
static void qaEvent(String event, String structure, boolean pass, String detail) {
LOGGER.info(qaEventJson(event, structure, pass, detail));
ModdedIrisLog.info(qaEventJson(event, structure, pass, detail));
}
static String qaEventJson(String event, String structure, boolean pass, String detail) {
@@ -48,8 +48,6 @@ import net.minecraft.world.level.levelgen.structure.StructureStart;
import net.minecraft.world.level.levelgen.structure.placement.ConcentricRingsStructurePlacement;
import net.minecraft.world.level.levelgen.structure.placement.RandomSpreadStructurePlacement;
import net.minecraft.world.level.levelgen.structure.placement.StructurePlacement;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.Arrays;
@@ -77,7 +75,6 @@ final class WorldCheckStructureAudit {
private static final int MAX_FOOTPRINT_CHUNKS = 96;
private static final int MAX_START_REFERENCE_CHUNKS = 16;
private static final int MAX_STRUCTURE_CANDIDATES = 1024;
private static final Logger LOGGER = LoggerFactory.getLogger("Iris");
private WorldCheckStructureAudit() {
}
@@ -123,13 +120,13 @@ final class WorldCheckStructureAudit {
}
}
boolean registryOk = registered.size() == check.registryKeys().size();
LOGGER.info("[worldcheck] {} registry: {}/{} resolved {}", check.label(), registered.size(),
ModdedIrisLog.info("[worldcheck] {} registry: {}/{} resolved {}", check.label(), registered.size(),
check.registryKeys().size(), registeredKeys);
WorldCheckPredicates.qaEvent("structure_registry", check.label(), registryOk,
"resolved=" + registered.size() + ",expected=" + check.registryKeys().size()
+ ",keys=" + String.join("|", registeredKeys));
if (!registryOk) {
LOGGER.error("[worldcheck] {} registry resolution failed; expected {}", check.label(), check.registryKeys());
ModdedIrisLog.error("[worldcheck] {} registry resolution failed; expected {}", check.label(), check.registryKeys());
WorldCheckPredicates.emitSkipped(check, "registry", "structure_reachability", "structure_locate",
"structure_start_reference", "structure_footprint", "structure_material",
"structure_block_entity");
@@ -149,12 +146,12 @@ final class WorldCheckStructureAudit {
}
}
boolean reachableOk = !reachable.isEmpty();
LOGGER.info("[worldcheck] {} biome-reachable through Iris: {}", check.label(), reachableKeys);
ModdedIrisLog.info("[worldcheck] {} biome-reachable through Iris: {}", check.label(), reachableKeys);
WorldCheckPredicates.qaEvent("structure_reachability", check.label(), reachableOk,
"reachable=" + reachable.size() + ",registered=" + registered.size()
+ ",keys=" + String.join("|", reachableKeys));
if (!reachableOk) {
LOGGER.error("[worldcheck] {} cannot generate in any biome produced by this Iris pack", check.label());
ModdedIrisLog.error("[worldcheck] {} cannot generate in any biome produced by this Iris pack", check.label());
WorldCheckPredicates.emitSkipped(check, "reachability", "structure_locate", "structure_start_reference",
"structure_footprint", "structure_material", "structure_block_entity");
return new StructureCheckResult(false, null);
@@ -170,7 +167,7 @@ final class WorldCheckStructureAudit {
"method=placement_candidates,millis=" + locateMillis + ",radius=" + check.locateRadius()
+ ",result=" + (foundKey == null ? "none" : foundKey));
if (found == null) {
LOGGER.error("[worldcheck] {} native placement candidates produced no valid start within {} rings after {}ms",
ModdedIrisLog.error("[worldcheck] {} native placement candidates produced no valid start within {} rings after {}ms",
check.label(), check.locateRadius(), locateMillis);
WorldCheckPredicates.emitSkipped(check, "locate", "structure_start_reference", "structure_footprint",
"structure_material", "structure_block_entity");
@@ -178,11 +175,11 @@ final class WorldCheckStructureAudit {
}
BlockPos position = found.getFirst();
LOGGER.info("[worldcheck] {} generated candidate: {} {} {} in {}ms (radius={}, result={})",
ModdedIrisLog.info("[worldcheck] {} generated candidate: {} {} {} in {}ms (radius={}, result={})",
check.label(), position.getX(), position.getY(), position.getZ(), locateMillis,
check.locateRadius(), foundKey);
if (!locateOk) {
LOGGER.error("[worldcheck] {} candidate scan returned unexpected structure {}", check.label(), foundKey);
ModdedIrisLog.error("[worldcheck] {} candidate scan returned unexpected structure {}", check.label(), foundKey);
WorldCheckPredicates.emitSkipped(check, "locate", "structure_start_reference", "structure_footprint",
"structure_material", "structure_block_entity");
return new StructureCheckResult(false, null);
@@ -196,13 +193,13 @@ final class WorldCheckStructureAudit {
boolean validStart = start != null && start.isValid();
int references = targetChunk.getReferencesForStructure(structure).size();
boolean startReferenceOk = WorldCheckPredicates.hasNativeStructureEvidence(validStart, references);
LOGGER.info("[worldcheck] {} target chunk {},{}: valid start={}, references={}",
ModdedIrisLog.info("[worldcheck] {} target chunk {},{}: valid start={}, references={}",
check.label(), chunkX, chunkZ, validStart, references);
WorldCheckPredicates.qaEvent("structure_start_reference", check.label(), startReferenceOk,
"chunk=" + chunkX + "," + chunkZ + ",validStart=" + validStart
+ ",references=" + references);
if (!startReferenceOk || !validStart) {
LOGGER.error("[worldcheck] {} located at chunk {},{} but no resolvable valid start was generated",
ModdedIrisLog.error("[worldcheck] {} located at chunk {},{} but no resolvable valid start was generated",
check.label(), chunkX, chunkZ);
WorldCheckPredicates.emitSkipped(check, "start_reference", "structure_footprint", "structure_material",
"structure_block_entity");
@@ -221,7 +218,7 @@ final class WorldCheckStructureAudit {
"configured=" + decision.yShift() + ",applied="
+ (appliedShift == null ? "unrecorded" : appliedShift));
if (!verticalShiftOk) {
LOGGER.error("[worldcheck] {} expected vertical shift {} but generation recorded {}",
ModdedIrisLog.error("[worldcheck] {} expected vertical shift {} but generation recorded {}",
check.label(), decision.yShift(), appliedShift);
}
@@ -229,7 +226,7 @@ final class WorldCheckStructureAudit {
boolean footprintOk = footprint.inspectedChunks() > 0
&& footprint.evidenceChunks() == footprint.inspectedChunks()
&& footprint.coveredPieces() == footprint.totalPieces();
LOGGER.info("[worldcheck] {} footprint: chunks={}/{} evidence={} pieces={}/{}",
ModdedIrisLog.info("[worldcheck] {} footprint: chunks={}/{} evidence={} pieces={}/{}",
check.label(), footprint.inspectedChunks(), footprint.availableChunks(),
footprint.evidenceChunks(), footprint.coveredPieces(), footprint.totalPieces());
WorldCheckPredicates.qaEvent("structure_footprint", check.label(), footprintOk,
@@ -239,7 +236,7 @@ final class WorldCheckStructureAudit {
boolean materialOk = WorldCheckPredicates.hasCharacteristicMaterialEvidence(footprint.characteristicBlocks(),
footprint.characteristicChunks(), footprint.materialScannedChunks());
LOGGER.info("[worldcheck] {} material: blocks={} chunks={}/{}",
ModdedIrisLog.info("[worldcheck] {} material: blocks={} chunks={}/{}",
check.label(), footprint.characteristicBlocks(), footprint.characteristicChunks(),
footprint.materialScannedChunks());
WorldCheckPredicates.qaEvent("structure_material", check.label(), materialOk,
@@ -250,7 +247,7 @@ final class WorldCheckStructureAudit {
if (check.label().equals("mansion")) {
boolean overlap = footprint.vegetationBlocks() > 0;
vegetationOk = WorldCheckPredicates.mansionVegetationPass(footprint.vegetationBlocks());
LOGGER.info("[worldcheck] mansion vegetation metric: remaining log/leaf blocks={} columns={} overlap={}",
ModdedIrisLog.info("[worldcheck] mansion vegetation metric: remaining log/leaf blocks={} columns={} overlap={}",
footprint.vegetationBlocks(), footprint.vegetationColumns(), overlap);
WorldCheckPredicates.qaEvent("mansion_vegetation_metric", check.label(), vegetationOk,
"remainingLogsOrLeaves=" + footprint.vegetationBlocks() + ",columns="
@@ -261,7 +258,7 @@ final class WorldCheckStructureAudit {
PendingVillagePoi pendingPoi = null;
if (check.label().equals("village")) {
foundationOk = WorldCheckPredicates.villageFoundationPass(footprint.foundationGapColumns());
LOGGER.info("[worldcheck] village foundation metric: bases={} cobblestone={} columns={} unsupported={}",
ModdedIrisLog.info("[worldcheck] village foundation metric: bases={} cobblestone={} columns={} unsupported={}",
footprint.foundationBaseColumns(), footprint.foundationBlocks(),
footprint.foundationColumns(), footprint.foundationGapColumns());
WorldCheckPredicates.qaEvent("village_foundation_metric", check.label(), foundationOk,
@@ -272,26 +269,26 @@ final class WorldCheckStructureAudit {
}
boolean blockEntityOk = footprint.blockEntityStates() == footprint.blockEntitiesPresent();
LOGGER.info("[worldcheck] {} block entities: state blocks={}, present={}, missing={}",
ModdedIrisLog.info("[worldcheck] {} block entities: state blocks={}, present={}, missing={}",
check.label(), footprint.blockEntityStates(), footprint.blockEntitiesPresent(),
footprint.blockEntityStates() - footprint.blockEntitiesPresent());
WorldCheckPredicates.qaEvent("structure_block_entity", check.label(), blockEntityOk,
"states=" + footprint.blockEntityStates() + ",present=" + footprint.blockEntitiesPresent()
+ ",missing=" + (footprint.blockEntityStates() - footprint.blockEntitiesPresent()));
if (!footprintOk) {
LOGGER.error("[worldcheck] {} structure footprint is incomplete", check.label());
ModdedIrisLog.error("[worldcheck] {} structure footprint is incomplete", check.label());
}
if (!materialOk) {
LOGGER.error("[worldcheck] {} has no distributed characteristic structure material", check.label());
ModdedIrisLog.error("[worldcheck] {} has no distributed characteristic structure material", check.label());
}
if (!blockEntityOk) {
LOGGER.error("[worldcheck] {} generated block-entity states without matching block entities", check.label());
ModdedIrisLog.error("[worldcheck] {} generated block-entity states without matching block entities", check.label());
}
if (!vegetationOk) {
LOGGER.error("[worldcheck] mansion vegetation still intersects the generated structure footprint");
ModdedIrisLog.error("[worldcheck] mansion vegetation still intersects the generated structure footprint");
}
if (!foundationOk) {
LOGGER.error("[worldcheck] village has unsupported foundation columns after stilt placement");
ModdedIrisLog.error("[worldcheck] village has unsupported foundation columns after stilt placement");
}
boolean pass = verticalShiftOk && footprintOk && materialOk && blockEntityOk
&& vegetationOk && foundationOk;
@@ -18,6 +18,7 @@
package art.arcane.iris.modded.api;
import art.arcane.iris.modded.ModdedIrisLog;
import art.arcane.iris.engine.framework.Engine;
import art.arcane.iris.modded.ModdedBlockResolution;
import net.minecraft.core.BlockPos;
@@ -25,8 +26,6 @@ import net.minecraft.resources.Identifier;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.level.block.state.BlockState;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.Collection;
@@ -57,7 +56,6 @@ import java.util.concurrent.CopyOnWriteArrayList;
* next provider.
*/
public final class ModdedCustomContentRegistry {
private static final Logger LOGGER = LoggerFactory.getLogger("Iris");
private static final List<ModdedDataProvider> PROVIDERS = new CopyOnWriteArrayList<>();
private static final Map<String, BlockState> CUSTOM_BLOCKS = new ConcurrentHashMap<>();
private static volatile boolean scanned = false;
@@ -77,14 +75,14 @@ public final class ModdedCustomContentRegistry {
}
Identifier identifier = Identifier.tryParse(namespace + ":" + key);
if (identifier == null) {
LOGGER.warn("Iris custom block data registration rejected invalid id {}:{}", namespace, key);
ModdedIrisLog.warn("Iris custom block data registration rejected invalid id {}:{}", namespace, key);
return;
}
BlockState parsed;
try {
parsed = ModdedBlockResolution.strictParse(state).handle();
} catch (Throwable error) {
LOGGER.error("Iris custom block data '{}:{}' has unparseable state '{}'", namespace, key, state, error);
ModdedIrisLog.error("Iris custom block data '{}:{}' has unparseable state '{}'", namespace, key, state, error);
return;
}
DiscoveryBatch activeBatch = discoveryBatch;
@@ -93,7 +91,7 @@ public final class ModdedCustomContentRegistry {
} else {
activeBatch.customBlocks.put(identifier.toString(), parsed);
}
LOGGER.info("Iris registered custom block data {}:{} -> {}", namespace, key, state);
ModdedIrisLog.info("Iris registered custom block data {}:{} -> {}", namespace, key, state);
}
/**
@@ -112,7 +110,7 @@ public final class ModdedCustomContentRegistry {
}
for (ModdedDataProvider existing : PROVIDERS) {
if (existing.modId().equals(provider.modId())) {
LOGGER.warn("Iris custom content provider for '{}' already registered; ignoring duplicate", provider.modId());
ModdedIrisLog.warn("Iris custom content provider for '{}' already registered; ignoring duplicate", provider.modId());
return;
}
}
@@ -120,9 +118,9 @@ public final class ModdedCustomContentRegistry {
try {
provider.init();
} catch (Throwable error) {
LOGGER.error("Iris custom content provider '{}' failed to initialize", provider.modId(), error);
ModdedIrisLog.error("Iris custom content provider '{}' failed to initialize", provider.modId(), error);
}
LOGGER.info("Iris registered custom content provider '{}'", provider.modId());
ModdedIrisLog.info("Iris registered custom content provider '{}'", provider.modId());
}
/**
@@ -159,7 +157,7 @@ public final class ModdedCustomContentRegistry {
CUSTOM_BLOCKS.putAll(batch.customBlocks);
scanned = true;
for (ModdedDataProvider provider : batch.additions) {
LOGGER.info("Iris registered custom content provider '{}'", provider.modId());
ModdedIrisLog.info("Iris registered custom content provider '{}'", provider.modId());
}
return new Discovery(previousProviders, previousCustomBlocks,
previousDiscoveryComplete, true);
@@ -171,7 +169,7 @@ public final class ModdedCustomContentRegistry {
failure.addSuppressed(rollbackFailure);
}
}
LOGGER.warn("Iris custom content provider discovery failed at {}",
ModdedIrisLog.warn("Iris custom content provider discovery failed at {}",
providerIdentity(failingProvider), failure);
if (failure instanceof RuntimeException runtimeException) {
throw runtimeException;
@@ -242,7 +240,7 @@ public final class ModdedCustomContentRegistry {
try {
types = provider.getTypes(type);
} catch (Throwable error) {
LOGGER.error("Iris custom content provider '{}' failed listing {} types", provider.modId(), type, error);
ModdedIrisLog.error("Iris custom content provider '{}' failed listing {} types", provider.modId(), type, error);
continue;
}
if (types == null) {
@@ -288,7 +286,7 @@ public final class ModdedCustomContentRegistry {
return resolved;
}
} catch (Throwable error) {
LOGGER.error("Iris custom content provider '{}' failed resolving block {}", provider.modId(), key, error);
ModdedIrisLog.error("Iris custom content provider '{}' failed resolving block {}", provider.modId(), key, error);
}
}
return null;
@@ -302,7 +300,7 @@ public final class ModdedCustomContentRegistry {
public static void processBlockPlacement(Engine engine, ServerLevel level, BlockPos position, String key) {
Identifier base = parseIdentifier(key);
if (base == null) {
LOGGER.warn("Iris deferred custom block placement rejected invalid id {}", key);
ModdedIrisLog.warn("Iris deferred custom block placement rejected invalid id {}", key);
return;
}
Map<String, String> state = parseState(key);
@@ -314,11 +312,11 @@ public final class ModdedCustomContentRegistry {
provider.processBlockPlacement(new ModdedBlockPlacementContext(
engine, level, position.immutable(), base, state, level.getBlockState(position)));
} catch (Throwable error) {
LOGGER.error("Iris custom content provider '{}' failed post-placement for {} at {}", provider.modId(), key, position, error);
ModdedIrisLog.error("Iris custom content provider '{}' failed post-placement for {} at {}", provider.modId(), key, position, error);
}
return;
}
LOGGER.warn("Iris deferred custom block placement has no provider for {}", key);
ModdedIrisLog.warn("Iris deferred custom block placement has no provider for {}", key);
}
/**
@@ -343,7 +341,7 @@ public final class ModdedCustomContentRegistry {
return entity;
}
} catch (Throwable error) {
LOGGER.error("Iris custom content provider '{}' failed spawning mob {}", provider.modId(), key, error);
ModdedIrisLog.error("Iris custom content provider '{}' failed spawning mob {}", provider.modId(), key, error);
}
}
return null;
@@ -439,7 +437,7 @@ public final class ModdedCustomContentRegistry {
"Iris custom content provider returned a null mod id");
for (ModdedDataProvider existing : providers) {
if (modId.equals(existing.modId())) {
LOGGER.warn("Iris custom content provider for '{}' already registered; ignoring duplicate", modId);
ModdedIrisLog.warn("Iris custom content provider for '{}' already registered; ignoring duplicate", modId);
return;
}
}
@@ -18,6 +18,7 @@
package art.arcane.iris.modded.command;
import art.arcane.iris.modded.ModdedIrisLog;
import art.arcane.iris.core.IrisSettings;
import art.arcane.iris.core.localization.IrisLanguage;
import art.arcane.iris.core.localization.IrisMessages;
@@ -51,8 +52,6 @@ import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.level.chunk.ChunkGenerator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.IOException;
@@ -67,7 +66,6 @@ import java.util.concurrent.TimeUnit;
import art.arcane.volmlib.util.localization.MessageArgument;
public final class IrisModdedCommands {
private static final Logger LOGGER = LoggerFactory.getLogger("Iris");
private static final long DOWNLOAD_SHUTDOWN_POLL_SECONDS = 15L;
private static final Object DOWNLOAD_MONITOR = new Object();
@@ -82,7 +80,7 @@ public final class IrisModdedCommands {
LiteralCommandNode<CommandSourceStack> root = dispatcher.register(ModdedCommandTree.rootTree());
dispatcher.register(Commands.literal("ir").redirect(root));
dispatcher.register(Commands.literal("irs").redirect(root));
IrisLogging.info("Iris /iris command tree registered");
IrisLogging.debug("Iris /iris command tree registered");
}
public static void openDownloadAdmission() {
@@ -108,7 +106,7 @@ public final class IrisModdedCommands {
try {
if (!execution.await(DOWNLOAD_SHUTDOWN_POLL_SECONDS, TimeUnit.SECONDS) && !warned) {
warned = true;
LOGGER.warn(execution.isPublishing()
ModdedIrisLog.warn(execution.isPublishing()
? "Waiting for atomic pack publication to finish before Iris shutdown."
: "Waiting for the active pack download to cancel before Iris shutdown.");
}
@@ -354,7 +352,7 @@ public final class IrisModdedCommands {
accepted = scheduler.asyncIfRunning(execution, execution::cancel);
} catch (Throwable error) {
execution.cancel();
LOGGER.error("Iris pack download dispatch failed for {}", target, error);
ModdedIrisLog.error("Iris pack download dispatch failed for {}", target, error);
fail(source, IrisLanguage.plain(
ModdedCommandMessages.IRIS_MODDED_COMMANDS_PACK_DOWNLOAD_FAILED_SEE_CONSOLE,
MessageArgument.untrusted("pack", target),
@@ -363,7 +361,7 @@ public final class IrisModdedCommands {
}
if (!accepted) {
execution.cancel();
LOGGER.error("Iris pack download dispatch rejected for {} because the scheduler is shut down", target);
ModdedIrisLog.error("Iris pack download dispatch rejected for {} because the scheduler is shut down", target);
fail(source, IrisLanguage.plain(
ModdedCommandMessages.IRIS_MODDED_COMMANDS_PACK_DOWNLOAD_FAILED_SEE_CONSOLE,
MessageArgument.untrusted("pack", target),
@@ -416,7 +414,7 @@ public final class IrisModdedCommands {
dispatchDownloadFeedback(source, () -> fail(source, error.getMessage()));
return;
} catch (IOException | RuntimeException error) {
LOGGER.error("Iris pack download failed for {}", target, error);
ModdedIrisLog.error("Iris pack download failed for {}", target, error);
}
dispatchDownloadFeedback(source, () -> fail(source, IrisLanguage.plain(
ModdedCommandMessages.IRIS_MODDED_COMMANDS_PACK_DOWNLOAD_FAILED_SEE_CONSOLE,
@@ -512,7 +510,7 @@ public final class IrisModdedCommands {
try {
return irisGenerator.commandEngine();
} catch (Throwable e) {
LOGGER.error("Iris engine lookup failed for {}", level.dimension().identifier(), e);
ModdedIrisLog.error("Iris engine lookup failed for {}", level.dimension().identifier(), e);
return null;
}
}
@@ -18,6 +18,7 @@
package art.arcane.iris.modded.command;
import art.arcane.iris.modded.ModdedIrisLog;
import art.arcane.iris.spi.IrisPlatforms;
import art.arcane.iris.core.pack.PackDirectoryResolver;
import art.arcane.iris.engine.framework.Engine;
@@ -40,8 +41,6 @@ import net.minecraft.core.registries.Registries;
import net.minecraft.resources.Identifier;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.levelgen.structure.Structure;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.util.ArrayList;
@@ -66,7 +65,6 @@ final class ModdedCommandSuggestions {
static final SuggestionProvider<CommandSourceStack> PACK_NAMES = (CommandContext<CommandSourceStack> context, SuggestionsBuilder builder) -> suggestPackNames(context, builder);
static final SuggestionProvider<CommandSourceStack> DIMENSION_NAMES = (CommandContext<CommandSourceStack> context, SuggestionsBuilder builder) -> suggestDimensionNames(context, builder);
private static final Logger LOGGER = LoggerFactory.getLogger("Iris");
private static final int TAB_FAILURE_KEYS_MAX = 256;
private static final Set<String> REPORTED_TAB_FAILURES = ConcurrentHashMap.newKeySet();
private static final long PACK_NAME_CACHE_TTL_MS = 3_000L;
@@ -180,7 +178,7 @@ final class ModdedCommandSuggestions {
if (REPORTED_TAB_FAILURES.size() > TAB_FAILURE_KEYS_MAX) {
REPORTED_TAB_FAILURES.clear();
}
LOGGER.warn("Iris tab-complete for {} in {} failed; suggestions will be empty", suggestion, origin, error);
ModdedIrisLog.warn("Iris tab-complete for {} in {} failed; suggestions will be empty", suggestion, origin, error);
}
private static String tabOrigin(CommandSourceStack source) {
@@ -18,6 +18,7 @@
package art.arcane.iris.modded.command;
import art.arcane.iris.modded.ModdedIrisLog;
import art.arcane.iris.core.loader.IrisData;
import art.arcane.iris.core.nms.datapack.DataVersion;
import art.arcane.iris.core.pack.PackDirectoryResolver;
@@ -35,8 +36,6 @@ import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.dimension.DimensionType;
import net.minecraft.world.level.storage.LevelResource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.IOException;
@@ -56,7 +55,6 @@ import art.arcane.iris.core.localization.ModdedCommandMessages;
import art.arcane.iris.core.localization.RuntimeUiMessages;
import art.arcane.volmlib.util.localization.MessageArgument;
public final class ModdedDatapackCommands {
private static final Logger LOGGER = LoggerFactory.getLogger("Iris");
private static final Predicate<CommandSourceStack> GATE = Commands.hasPermission(Commands.LEVEL_GAMEMASTERS);
private static final String WORLD_PACK_NAME = "iris";
@@ -170,7 +168,7 @@ public final class ModdedDatapackCommands {
try {
json = dimension.getDimensionType().toJson(DataVersion.getLatest().get());
} catch (Throwable e) {
LOGGER.error("Iris dimension type generation failed for {}", dimension.getLoadKey(), e);
ModdedIrisLog.error("Iris dimension type generation failed for {}", dimension.getLoadKey(), e);
IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_DATAPACK_COMMANDS_DIMENSION_TYPE_GENERATION_FAILED, MessageArgument.untrusted("value", level.dimension().identifier()), MessageArgument.untrusted("value2", String.valueOf(e.getMessage()))));
continue;
}
@@ -180,7 +178,7 @@ public final class ModdedDatapackCommands {
Files.writeString(output.toPath(), json, StandardCharsets.UTF_8);
written.add(output.getPath());
} catch (IOException e) {
LOGGER.error("Iris dimension type write failed for {}", output, e);
ModdedIrisLog.error("Iris dimension type write failed for {}", output, e);
IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_DATAPACK_COMMANDS_FAILED_WRITE, MessageArgument.untrusted("output", output), MessageArgument.untrusted("value", String.valueOf(e.getMessage()))));
}
}
@@ -204,7 +202,7 @@ public final class ModdedDatapackCommands {
Files.writeString(mcmeta.toPath(), meta, StandardCharsets.UTF_8);
written.add(mcmeta.getPath());
} catch (IOException e) {
LOGGER.error("Iris pack.mcmeta write failed for {}", mcmeta, e);
ModdedIrisLog.error("Iris pack.mcmeta write failed for {}", mcmeta, e);
IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_DATAPACK_COMMANDS_FAILED_WRITE_2, MessageArgument.untrusted("mcmeta", mcmeta), MessageArgument.untrusted("value", String.valueOf(e.getMessage()))));
return 0;
}
@@ -237,7 +235,7 @@ public final class ModdedDatapackCommands {
}
}
} catch (Throwable e) {
LOGGER.error("Iris datapack import scan failed for pack {}", pack.getName(), e);
ModdedIrisLog.error("Iris datapack import scan failed for pack {}", pack.getName(), e);
}
}
@@ -18,13 +18,12 @@
package art.arcane.iris.modded.command;
import art.arcane.iris.modded.ModdedIrisLog;
import art.arcane.iris.spi.IrisPlatforms;
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import com.mojang.brigadier.context.CommandContext;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.commands.Commands;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.net.InetAddress;
import java.net.NetworkInterface;
@@ -37,7 +36,6 @@ import art.arcane.iris.core.localization.IrisLanguage;
import art.arcane.iris.core.localization.ModdedCommandMessages;
import art.arcane.volmlib.util.localization.MessageArgument;
final class ModdedDeveloperCommands {
private static final Logger LOGGER = LoggerFactory.getLogger("Iris");
private static final Predicate<CommandSourceStack> GATE = Commands.hasPermission(Commands.LEVEL_GAMEMASTERS);
private ModdedDeveloperCommands() {
@@ -71,7 +69,7 @@ final class ModdedDeveloperCommands {
}
return 1;
} catch (SocketException error) {
LOGGER.error("Iris developer network dump failed", error);
ModdedIrisLog.error("Iris developer network dump failed", error);
ModdedCommandFeedback.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_DEVELOPER_COMMANDS_NETWORK_SCAN_FAILED, MessageArgument.untrusted("value", error.getClass().getSimpleName())));
return 0;
}
@@ -18,6 +18,7 @@
package art.arcane.iris.modded.command;
import art.arcane.iris.modded.ModdedIrisLog;
import art.arcane.iris.core.localization.IrisLanguage;
import art.arcane.iris.core.localization.RuntimeUiMessages;
import art.arcane.iris.engine.framework.Engine;
@@ -46,8 +47,6 @@ import net.minecraft.server.level.ServerPlayer;
import net.minecraft.sounds.SoundEvents;
import net.minecraft.sounds.SoundSource;
import net.minecraft.world.level.biome.Biome;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayDeque;
import java.util.ArrayList;
@@ -62,7 +61,6 @@ import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Supplier;
public final class ModdedDustRevealer {
private static final Logger LOGGER = LoggerFactory.getLogger("Iris");
private static final int MAX_HITS = 2_048;
private static final int PARTICLE_BATCH_SIZE = 64;
private static final DustParticleOptions REVEAL_DUST = new DustParticleOptions(0xFFD24A, 1.2F);
@@ -231,7 +229,7 @@ public final class ModdedDustRevealer {
}
private static void revealFailure(ModdedScheduler scheduler, RevealRun run, Throwable error) {
LOGGER.error("Iris dust reveal failed for {} at {}", run.key(), coordinates(run.origin()), error);
ModdedIrisLog.error("Iris dust reveal failed for {} at {}", run.key(), coordinates(run.origin()), error);
scheduler.global(() -> {
if (ACTIVE_RUNS.remove(run.playerId(), run)) {
run.player().sendSystemMessage(Component.literal(
@@ -415,7 +413,7 @@ public final class ModdedDustRevealer {
}
}
} catch (Throwable error) {
LOGGER.error("Iris dust column-object lookup failed at {}, {}, {}",
ModdedIrisLog.error("Iris dust column-object lookup failed at {}, {}, {}",
x, relativeY + minHeight, z, error);
}
return null;
@@ -458,7 +456,7 @@ public final class ModdedDustRevealer {
try {
return supplier.get();
} catch (Throwable error) {
LOGGER.error("Iris dust {} failed", operation, error);
ModdedIrisLog.error("Iris dust {} failed", operation, error);
return null;
}
}
@@ -18,6 +18,7 @@
package art.arcane.iris.modded.command;
import art.arcane.iris.modded.ModdedIrisLog;
import art.arcane.iris.core.gui.GuiHost;
import art.arcane.iris.core.loader.IrisRegistrant;
import art.arcane.iris.core.localization.IrisLanguage;
@@ -29,14 +30,11 @@ import art.arcane.volmlib.util.localization.MessageArgument;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.core.BlockPos;
import net.minecraft.server.level.ServerPlayer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.awt.Desktop;
import java.io.File;
final class ModdedEditCommands {
private static final Logger LOGGER = LoggerFactory.getLogger("Iris");
private ModdedEditCommands() {
}
@@ -123,7 +121,7 @@ final class ModdedEditCommands {
try {
Desktop.getDesktop().open(file);
} catch (Throwable e) {
LOGGER.error("Iris edit failed to open {}", file, e);
ModdedIrisLog.error("Iris edit failed to open {}", file, e);
IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.IRIS_MODDED_COMMANDS_COULD_NOT_OPEN, MessageArgument.untrusted("value", file.getName()), MessageArgument.untrusted("value2", e.getClass().getSimpleName())));
return 0;
}
@@ -18,6 +18,7 @@
package art.arcane.iris.modded.command;
import art.arcane.iris.modded.ModdedIrisLog;
import art.arcane.iris.core.runtime.GoldenHashEngine;
import art.arcane.iris.engine.framework.Engine;
import art.arcane.iris.modded.ModdedBlockBuffer;
@@ -29,8 +30,6 @@ import art.arcane.iris.util.project.hunk.Hunk;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerLevel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -47,7 +46,6 @@ public final class ModdedGoldenHash {
VERIFY
}
private static final Logger LOGGER = LoggerFactory.getLogger("Iris");
private static final AtomicBoolean ACTIVE = new AtomicBoolean(false);
private final CommandSourceStack source;
@@ -96,7 +94,7 @@ public final class ModdedGoldenHash {
MessageArgument.trusted("threads", Math.max(1, threads)),
MessageArgument.untrusted("mode", mode)
));
LOGGER.info("goldenhash start: dim={} seed={} radius={} threads={} mode={} file={}",
ModdedIrisLog.info("goldenhash start: dim={} seed={} radius={} threads={} mode={} file={}",
engine.getDimension().getLoadKey(), engine.getSeedManager().getSeed(), boundedRadius, Math.max(1, threads), mode, scan.hashEngine.getGoldenFile().getName());
Thread thread = new Thread(() -> {
try {
@@ -182,7 +180,7 @@ public final class ModdedGoldenHash {
@Override
public void chunkFailed(int chunkX, int chunkZ, Throwable error) {
LOGGER.error("goldenhash chunk {},{} failed", chunkX, chunkZ, error);
ModdedIrisLog.error("goldenhash chunk {},{} failed", chunkX, chunkZ, error);
ModdedGoldenHash.this.fail(IrisLanguage.plain(
RuntimeProgressMessages.GOLDEN_CHUNK_FAILED,
MessageArgument.trusted("x", chunkX),
@@ -18,6 +18,7 @@
package art.arcane.iris.modded.command;
import art.arcane.iris.modded.ModdedIrisLog;
import art.arcane.iris.core.localization.IrisLanguage;
import art.arcane.iris.core.localization.ModdedCommandMessages;
import art.arcane.iris.engine.framework.Engine;
@@ -51,8 +52,6 @@ import net.minecraft.world.entity.Relative;
import net.minecraft.world.level.chunk.ChunkGenerator;
import net.minecraft.world.level.levelgen.Heightmap;
import net.minecraft.world.level.levelgen.structure.Structure;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Optional;
import java.util.Set;
@@ -64,7 +63,6 @@ import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;
final class ModdedLocateCommands {
private static final Logger LOGGER = LoggerFactory.getLogger("Iris");
private static final long LOCATE_TIMEOUT_MS = 120000L;
private static final int NATIVE_STRUCTURE_LOCATE_RADIUS = 100;
private static final ConcurrentHashMap<UUID, CompletableFuture<Position2>> ACTIVE_LOCATE_REQUESTS = new ConcurrentHashMap<>();
@@ -260,7 +258,7 @@ final class ModdedLocateCommands {
server.execute(() -> teleportToStructure(source, level, player, targetX, targetY, targetZ,
"Iris-placed structure " + key));
} catch (Throwable e) {
LOGGER.error("Iris structure locate failed for {}", key, e);
ModdedIrisLog.error("Iris structure locate failed for {}", key, e);
server.execute(() -> IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.IRIS_MODDED_COMMANDS_SEARCH_FAILED, MessageArgument.untrusted("value", e.getClass().getSimpleName()))));
}
}, "Iris Structure Locator");
@@ -302,7 +300,7 @@ final class ModdedLocateCommands {
teleportToStructure(source, level, player, targetX, targetY, targetZ,
"native structure " + target.key());
} catch (Throwable e) {
LOGGER.error("Native structure locate failed for {}", target.key(), e);
ModdedIrisLog.error("Native structure locate failed for {}", target.key(), e);
IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.IRIS_MODDED_COMMANDS_SEARCH_NATIVE_STRUCTURE_FAILED, MessageArgument.untrusted("value", target.key()), MessageArgument.untrusted("value2", e.getClass().getSimpleName())));
}
}
@@ -518,7 +516,7 @@ final class ModdedLocateCommands {
return;
}
if (failure != null) {
LOGGER.error("Iris locate failed for {}", label, failure);
ModdedIrisLog.error("Iris locate failed for {}", label, failure);
server.execute(() -> {
if (ACTIVE_LOCATE_REQUESTS.remove(playerId, search)) {
IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.IRIS_MODDED_COMMANDS_SEARCH_FAILED_2, MessageArgument.untrusted("failure", failure)));
@@ -18,6 +18,7 @@
package art.arcane.iris.modded.command;
import art.arcane.iris.modded.ModdedIrisLog;
import art.arcane.iris.core.loader.IrisData;
import art.arcane.iris.core.tools.TreePlausibilizeBatch;
import art.arcane.iris.core.tools.TreePlausibilizer;
@@ -54,8 +55,6 @@ import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.BlockHitResult;
import net.minecraft.world.phys.HitResult;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.IOException;
@@ -74,7 +73,6 @@ import art.arcane.iris.core.localization.ModdedCommandMessages;
import art.arcane.iris.core.localization.RuntimeUiMessages;
import art.arcane.volmlib.util.localization.MessageArgument;
public final class ModdedObjectCommands {
private static final Logger LOGGER = LoggerFactory.getLogger("Iris");
private static final Predicate<CommandSourceStack> GATE = Commands.hasPermission(Commands.LEVEL_GAMEMASTERS);
private static final long MAX_SAVE_VOLUME = 500000L;
private static final long MAX_AUTOSELECT_VOLUME = 100000L;
@@ -313,7 +311,7 @@ public final class ModdedObjectCommands {
try {
object.write(file);
} catch (IOException e) {
LOGGER.error("Iris object save failed for {}", file.getAbsolutePath(), e);
ModdedIrisLog.error("Iris object save failed for {}", file.getAbsolutePath(), e);
if (finalClaimed) {
// Never leave a 0-byte claim file permanently blocking non-overwrite saves.
file.delete();
@@ -332,7 +330,7 @@ public final class ModdedObjectCommands {
tileNote.append(" (").append(tilesSkipped[0]).append(" tile state(s) could not be captured)");
}
server.execute(() -> IrisModdedCommands.ok(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_OBJECT_COMMANDS_SAVED_OBJECTS_IOB_X_X_BLOCK_S, MessageArgument.untrusted("value", engine.getData().getDataFolder().getName()), MessageArgument.untrusted("name", name), MessageArgument.untrusted("w", w), MessageArgument.untrusted("h", h), MessageArgument.untrusted("d", d), MessageArgument.untrusted("value2", object.getBlocks().size()), MessageArgument.untrusted("tileNote", tileNote))));
LOGGER.info("Iris object save: {} {}x{}x{} blocks={} tilesSaved={} tilesSkipped={} -> {}", name, w, h, d, object.getBlocks().size(), tilesSaved[0], tilesSkipped[0], file.getAbsolutePath());
ModdedIrisLog.info("Iris object save: {} {}x{}x{} blocks={} tilesSaved={} tilesSkipped={} -> {}", name, w, h, d, object.getBlocks().size(), tilesSaved[0], tilesSkipped[0], file.getAbsolutePath());
});
return 1;
}
@@ -378,7 +376,7 @@ public final class ModdedObjectCommands {
String blockKey = BuiltInRegistries.BLOCK.getKey(state.getBlock()).toString();
return ModdedTileData.capture(blockKey, snbt);
} catch (Throwable e) {
LOGGER.error("Iris tile capture failed at {} {} {}", pos.getX(), pos.getY(), pos.getZ(), e);
ModdedIrisLog.error("Iris tile capture failed at {} {} {}", pos.getX(), pos.getY(), pos.getZ(), e);
return null;
}
}
@@ -391,7 +389,7 @@ public final class ModdedObjectCommands {
try {
object = IrisData.loadAnyObject(key, engine == null ? null : engine.getData());
} catch (Throwable e) {
LOGGER.error("Iris object load failed for {}", key, e);
ModdedIrisLog.error("Iris object load failed for {}", key, e);
}
if (object == null || object.getBlocks().size() == 0) {
IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_OBJECT_COMMANDS_UNKNOWN_EMPTY_OBJECT, MessageArgument.untrusted("key", key)));
@@ -419,7 +417,7 @@ public final class ModdedObjectCommands {
try {
object.place(target.getX(), target.getY() + object.getCenter().getY(), target.getZ(), placer, placement, new RNG(), null);
} catch (Throwable e) {
LOGGER.error("Iris paste failed for {}", key, e);
ModdedIrisLog.error("Iris paste failed for {}", key, e);
ModdedObjectUndo.record(player == null ? ModdedObjectUndo.CONSOLE : player.getUUID(), level, placer.undoSnapshot());
IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_OBJECT_COMMANDS_PASTE_FAILED_PARTIAL_CHANGES_RECORDED_UNDO, MessageArgument.untrusted("value", e.getClass().getSimpleName())));
return 0;
@@ -428,7 +426,7 @@ public final class ModdedObjectCommands {
ModdedObjectUndo.record(owner, level, placer.undoSnapshot());
String tileNote = tileNote(placer);
IrisModdedCommands.ok(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_OBJECT_COMMANDS_PLACED_AT_ROT_WRITE_S_NON_AIR, MessageArgument.untrusted("key", key), MessageArgument.untrusted("value", target.getX()), MessageArgument.untrusted("value2", target.getY()), MessageArgument.untrusted("value3", target.getZ()), MessageArgument.untrusted("rotation", rotation), MessageArgument.untrusted("value4", placer.writes()), MessageArgument.untrusted("value5", placer.nonAirWrites()), MessageArgument.untrusted("tileNote", tileNote)));
LOGGER.info("Iris paste: {} at {},{},{} rot={} writes={} nonAir={} tilesRestored={} tilesSkipped={}",
ModdedIrisLog.info("Iris paste: {} at {},{},{} rot={} writes={} nonAir={} tilesRestored={} tilesSkipped={}",
key, target.getX(), target.getY(), target.getZ(), rotation, placer.writes(), placer.nonAirWrites(), placer.restoredTiles(), placer.skippedTiles());
return placer.writes() > 0 ? 1 : 0;
}
@@ -608,7 +606,7 @@ public final class ModdedObjectCommands {
try {
object = IrisData.loadAnyObject(key, engine == null ? null : engine.getData());
} catch (Throwable e) {
LOGGER.error("Iris object load failed for {}", key, e);
ModdedIrisLog.error("Iris object load failed for {}", key, e);
}
if (object == null) {
IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_OBJECT_COMMANDS_UNKNOWN_OBJECT, MessageArgument.untrusted("key", key)));
@@ -648,7 +646,7 @@ public final class ModdedObjectCommands {
try {
object = IrisData.loadAnyObject(key, engine == null ? null : engine.getData());
} catch (Throwable e) {
LOGGER.error("Iris object load failed for {}", key, e);
ModdedIrisLog.error("Iris object load failed for {}", key, e);
}
if (object == null) {
IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_OBJECT_COMMANDS_UNKNOWN_OBJECT_2, MessageArgument.untrusted("key", key)));
@@ -665,7 +663,7 @@ public final class ModdedObjectCommands {
try {
object.write(file);
} catch (IOException e) {
LOGGER.error("Iris object shrink save failed for {}", file.getAbsolutePath(), e);
ModdedIrisLog.error("Iris object shrink save failed for {}", file.getAbsolutePath(), e);
IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_OBJECT_COMMANDS_FAILED_SAVE_OBJECT_2, MessageArgument.untrusted("value", file.getName()), MessageArgument.untrusted("value2", String.valueOf(e.getMessage()))));
return 0;
}
@@ -18,6 +18,7 @@
package art.arcane.iris.modded.command;
import art.arcane.iris.modded.ModdedIrisLog;
import art.arcane.iris.core.loader.IrisData;
import art.arcane.iris.engine.framework.Engine;
import art.arcane.iris.engine.object.IObjectPlacer;
@@ -37,14 +38,11 @@ import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.levelgen.Heightmap;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.HashMap;
import java.util.Map;
final class ModdedObjectPlacer implements IObjectPlacer {
private static final Logger LOGGER = LoggerFactory.getLogger("Iris");
private static final int DEFAULT_FLUID_HEIGHT = 63;
private final ServerLevel level;
@@ -199,7 +197,7 @@ final class ModdedObjectPlacer implements IObjectPlacer {
}
restoredTiles++;
} catch (Throwable e) {
LOGGER.error("Iris tile restore failed at {} {} {}", xx, yy, zz, e);
ModdedIrisLog.error("Iris tile restore failed at {} {} {}", xx, yy, zz, e);
skippedTiles++;
}
}
@@ -18,13 +18,12 @@
package art.arcane.iris.modded.command;
import art.arcane.iris.modded.ModdedIrisLog;
import net.minecraft.core.BlockPos;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.state.BlockState;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayDeque;
import java.util.Deque;
@@ -34,7 +33,6 @@ import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicBoolean;
public final class ModdedObjectUndo {
private static final Logger LOGGER = LoggerFactory.getLogger("Iris");
private static final int MAX_ENTRIES_PER_OWNER = 32;
private static final ConcurrentHashMap<UUID, Deque<Entry>> UNDOS = new ConcurrentHashMap<>();
private static final AtomicBoolean INITIALIZED = new AtomicBoolean(false);
@@ -48,7 +46,7 @@ public final class ModdedObjectUndo {
public static void init() {
if (INITIALIZED.compareAndSet(false, true)) {
LOGGER.info("Iris object undo service ready (bounded to {} paste(s) per player)", MAX_ENTRIES_PER_OWNER);
ModdedIrisLog.info("Iris object undo service ready (bounded to {} paste(s) per player)", MAX_ENTRIES_PER_OWNER);
}
}
@@ -93,7 +91,7 @@ public final class ModdedObjectUndo {
// dimension id must never have blocks replayed into the dead ServerLevel.
MinecraftServer server = entry.level().getServer();
if (server == null || server.getLevel(entry.level().dimension()) != entry.level()) {
LOGGER.warn("Iris object undo: skipped a stale entry for removed dimension {}",
ModdedIrisLog.warn("Iris object undo: skipped a stale entry for removed dimension {}",
entry.level().dimension().identifier());
continue;
}
@@ -103,10 +101,10 @@ public final class ModdedObjectUndo {
entry.level().setBlock(block.getKey(), block.getValue(), Block.UPDATE_CLIENTS | Block.UPDATE_KNOWN_SHAPE);
writes++;
} catch (Throwable e) {
LOGGER.error("Iris object undo: failed to revert a block at {}", block.getKey(), e);
ModdedIrisLog.error("Iris object undo: failed to revert a block at {}", block.getKey(), e);
}
}
LOGGER.info("Iris object undo: reverted {} block(s) in {}", writes, entry.level().dimension().identifier());
ModdedIrisLog.info("Iris object undo: reverted {} block(s) in {}", writes, entry.level().dimension().identifier());
reverted++;
}
return reverted;
@@ -18,6 +18,7 @@
package art.arcane.iris.modded.command;
import art.arcane.iris.modded.ModdedIrisLog;
import art.arcane.iris.spi.IrisPlatforms;
import art.arcane.iris.core.pack.PackDirectoryResolver;
import art.arcane.iris.core.pack.PackResourceCleanup;
@@ -31,8 +32,6 @@ import com.mojang.brigadier.context.CommandContext;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.commands.Commands;
import net.minecraft.server.MinecraftServer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.util.ArrayList;
@@ -44,7 +43,6 @@ import art.arcane.iris.core.localization.IrisLanguage;
import art.arcane.iris.core.localization.ModdedCommandMessages;
import art.arcane.volmlib.util.localization.MessageArgument;
public final class ModdedPackCommands {
private static final Logger LOGGER = LoggerFactory.getLogger("Iris");
private static final Predicate<CommandSourceStack> GATE = Commands.hasPermission(Commands.LEVEL_GAMEMASTERS);
private ModdedPackCommands() {
@@ -139,7 +137,7 @@ public final class ModdedPackCommands {
}
server.execute(() -> report(source, result));
} catch (Throwable e) {
LOGGER.error("Iris pack validation failed for {}", target.getName(), e);
ModdedIrisLog.error("Iris pack validation failed for {}", target.getName(), e);
server.execute(() -> IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_PACK_COMMANDS_VALIDATION_FAILED, MessageArgument.untrusted("value", target.getName()), MessageArgument.untrusted("value2", String.valueOf(e.getMessage())))));
broken++;
}
@@ -18,6 +18,7 @@
package art.arcane.iris.modded.command;
import art.arcane.iris.modded.ModdedIrisLog;
import art.arcane.iris.core.IrisSettings;
import art.arcane.iris.core.pregenerator.PregenListener;
import art.arcane.iris.core.pregenerator.PregenMantleBackpressure;
@@ -31,8 +32,6 @@ import net.minecraft.server.level.ChunkResult;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.TicketType;
import net.minecraft.world.level.ChunkPos;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
@@ -48,7 +47,6 @@ import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
public final class ModdedPregenMethod implements PregeneratorMethod {
private static final Logger LOGGER = LoggerFactory.getLogger("Iris");
private static final TicketType PREGEN_TICKET = new TicketType(TicketType.NO_TIMEOUT, TicketType.FLAG_LOADING | TicketType.FLAG_KEEP_DIMENSION_ACTIVE);
private static final int ADAPTIVE_TIMEOUT_STEP = 3;
private static final long ADAPTIVE_RECOVERY_INTERVAL = 64L;
@@ -106,7 +104,7 @@ public final class ModdedPregenMethod implements PregeneratorMethod {
@Override
public void init() {
pauseGuard.suspend();
LOGGER.info("Iris modded pregen init: dim={} mode={} inFlightCap={} timeout={}s workerPool={} chunkSystem={}",
ModdedIrisLog.info("Iris modded pregen init: dim={} mode={} inFlightCap={} timeout={}s workerPool={} chunkSystem={}",
level.dimension().identifier(),
sync ? "sync" : "async",
sync ? 1 : maxInFlight,
@@ -114,7 +112,7 @@ public final class ModdedPregenMethod implements PregeneratorMethod {
describeWorkerPool(),
ModdedGenPool.describeChunkSystem());
if (!sync && !ModdedGenPool.parallelChunkSystem()) {
LOGGER.info("Iris pregen note: this loader uses the vanilla main-thread chunk system, which caps pregen throughput. For Bukkit-level speed on Fabric install C2ME (Concurrent Chunk Management Engine); on servers use Paper.");
ModdedIrisLog.info("Iris pregen note: this loader uses the vanilla main-thread chunk system, which caps pregen throughput. For Bukkit-level speed on Fabric install C2ME (Concurrent Chunk Management Engine); on servers use Paper.");
}
}
@@ -128,7 +126,7 @@ public final class ModdedPregenMethod implements PregeneratorMethod {
Thread.currentThread().interrupt();
}
}
LOGGER.info("Iris modded pregen done: dim={} completed={} peakInFlight={} finalLimit={}",
ModdedIrisLog.info("Iris modded pregen done: dim={} completed={} peakInFlight={} finalLimit={}",
level.dimension().identifier(), completed.get(), inFlightPeak.get(), adaptiveLimit.get());
if (deferFinalSaveIfRequested()) {
return;
@@ -245,7 +243,7 @@ public final class ModdedPregenMethod implements PregeneratorMethod {
}
long remainingNanos = deadline - System.nanoTime();
if (remainingNanos <= 0L) {
LOGGER.warn("Iris pregen level save did not complete in time for {}", level.dimension().identifier());
ModdedIrisLog.warn("Iris pregen level save did not complete in time for {}", level.dimension().identifier());
return;
}
long waitMillis = Math.max(1L, Math.min(FINAL_SAVE_POLL_MILLIS,
@@ -260,7 +258,7 @@ public final class ModdedPregenMethod implements PregeneratorMethod {
continue;
} catch (ExecutionException e) {
Throwable cause = e.getCause() == null ? e : e.getCause();
LOGGER.error("Iris pregen level save failed for {}", level.dimension().identifier(), cause);
ModdedIrisLog.error("Iris pregen level save failed for {}", level.dimension().identifier(), cause);
throw new IllegalStateException("Iris pregen level save failed for "
+ level.dimension().identifier(), cause);
}
@@ -337,7 +335,7 @@ public final class ModdedPregenMethod implements PregeneratorMethod {
try {
Object result = loadFuture.get(timeoutSeconds, TimeUnit.SECONDS);
if (result instanceof ChunkResult<?> chunkResult && !chunkResult.isSuccess()) {
LOGGER.warn("Iris pregen chunk {},{} returned no chunk: {}", x, z, chunkResult.getError());
ModdedIrisLog.warn("Iris pregen chunk {},{} returned no chunk: {}", x, z, chunkResult.getError());
listener.onChunkFailed(x, z);
return;
}
@@ -364,7 +362,7 @@ public final class ModdedPregenMethod implements PregeneratorMethod {
// this abort the pregen thread spins hot against the dead chunk source until the JVM dies.
if (level.getServer().isStopped() || !level.getServer().isRunning()) {
if (SERVER_DEAD_LOGGED.compareAndSet(false, true)) {
LOGGER.error("Iris pregen aborting: the server is no longer running (dim={})", level.dimension().identifier());
ModdedIrisLog.error("Iris pregen aborting: the server is no longer running (dim={})", level.dimension().identifier());
}
listener.onChunkFailed(x, z);
ModdedPregenJob.stop();
@@ -401,7 +399,7 @@ public final class ModdedPregenMethod implements PregeneratorMethod {
return;
}
if (result instanceof ChunkResult<?> chunkResult && !chunkResult.isSuccess()) {
LOGGER.warn("Iris pregen chunk {},{} returned no chunk: {}", x, z, chunkResult.getError());
ModdedIrisLog.warn("Iris pregen chunk {},{} returned no chunk: {}", x, z, chunkResult.getError());
listener.onChunkFailed(x, z);
return;
}
@@ -425,10 +423,10 @@ public final class ModdedPregenMethod implements PregeneratorMethod {
private void logChunkFailure(int x, int z, Throwable failure) {
Throwable cause = unwrap(failure);
if (failureDetailLogged.compareAndSet(false, true)) {
LOGGER.warn("Iris pregen chunk {},{} failed; first failure follows", x, z, cause);
ModdedIrisLog.warn("Iris pregen chunk {},{} failed; first failure follows", x, z, cause);
return;
}
LOGGER.warn("Iris pregen chunk {},{} failed: {}", x, z, cause.toString());
ModdedIrisLog.warn("Iris pregen chunk {},{} failed: {}", x, z, cause.toString());
}
private void markFinished() {
@@ -500,7 +498,7 @@ public final class ModdedPregenMethod implements PregeneratorMethod {
try {
engine.getMantle().forceCleanupChunk(x, z);
} catch (Throwable e) {
LOGGER.debug("Iris pregen mantle cleanup skipped for {},{}: {}", x, z, e.toString());
ModdedIrisLog.debug("Iris pregen mantle cleanup skipped for {},{}: {}", x, z, e.toString());
}
}
@@ -566,7 +564,7 @@ public final class ModdedPregenMethod implements PregeneratorMethod {
try {
current = dedicated.pauseWhenEmptySeconds();
} catch (Throwable e) {
LOGGER.warn("Iris pregen could not read pause-when-empty-seconds: {}", e.toString());
ModdedIrisLog.warn("Iris pregen could not read pause-when-empty-seconds: {}", e.toString());
return;
}
if (current <= 0) {
@@ -591,7 +589,7 @@ public final class ModdedPregenMethod implements PregeneratorMethod {
}
suspendedFrom.set(current);
armCrashRestore();
LOGGER.info("Iris pregen: suspending pause-when-empty (was {}s), restored when the job ends", current);
ModdedIrisLog.info("Iris pregen: suspending pause-when-empty (was {}s), restored when the job ends", current);
}
private void restore() {
@@ -606,9 +604,9 @@ public final class ModdedPregenMethod implements PregeneratorMethod {
}
try {
dedicated.setPauseWhenEmptySeconds(previous);
LOGGER.info("Iris pregen: {} pause-when-empty ({}s)", what, previous);
ModdedIrisLog.info("Iris pregen: {} pause-when-empty ({}s)", what, previous);
} catch (Throwable e) {
LOGGER.error("Iris pregen could not restore pause-when-empty-seconds={}: {}. Set pause-when-empty-seconds={} in server.properties.",
ModdedIrisLog.error("Iris pregen could not restore pause-when-empty-seconds={}: {}. Set pause-when-empty-seconds={} in server.properties.",
previous, e.toString(), previous);
}
}
@@ -655,11 +653,11 @@ public final class ModdedPregenMethod implements PregeneratorMethod {
if (!pauseStillArmed()) {
return;
}
LOGGER.error("Iris pregen is timing out on an empty server while pause-when-empty-seconds is active: the paused server stops ticking. Set pause-when-empty-seconds=0 in server.properties, or keep a player online while pregenerating.");
ModdedIrisLog.error("Iris pregen is timing out on an empty server while pause-when-empty-seconds is active: the paused server stops ticking. Set pause-when-empty-seconds=0 in server.properties, or keep a player online while pregenerating.");
}
private void refuse(int current, String reason) {
LOGGER.error("Iris pregen could not suspend pause-when-empty-seconds={} ({}). The server stops ticking once empty, which stalls pregen: set pause-when-empty-seconds=0 in server.properties, or keep a player online while pregenerating.",
ModdedIrisLog.error("Iris pregen could not suspend pause-when-empty-seconds={} ({}). The server stops ticking once empty, which stalls pregen: set pause-when-empty-seconds=0 in server.properties, or keep a player online while pregenerating.",
current, reason);
}
}
@@ -18,6 +18,7 @@
package art.arcane.iris.modded.command;
import art.arcane.iris.modded.ModdedIrisLog;
import art.arcane.iris.core.tools.WorldMaintenance;
import art.arcane.iris.engine.framework.Engine;
import art.arcane.iris.engine.mantle.EngineMantle;
@@ -51,8 +52,6 @@ import net.minecraft.world.level.chunk.LevelChunkSection;
import net.minecraft.world.level.chunk.status.ChunkStatus;
import net.minecraft.world.level.levelgen.Heightmap;
import net.minecraft.world.phys.AABB;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
@@ -65,7 +64,6 @@ import java.util.concurrent.atomic.AtomicInteger;
import art.arcane.iris.core.localization.IrisLanguage;
import art.arcane.iris.core.localization.ModdedCommandMessages;
public final class ModdedRegen {
private static final Logger LOGGER = LoggerFactory.getLogger("Iris");
private static final int APPLY_AHEAD = 8;
private static final long CHUNK_SLOT_TIMEOUT_MILLIS = 120000L;
private static final long FINAL_APPLY_TIMEOUT_MILLIS = 300000L;
@@ -101,7 +99,7 @@ public final class ModdedRegen {
ModdedRegen job = new ModdedRegen(source, level, generator, engine, centerX, centerZ, radius);
int chunks = (job.radius * 2 + 1) * (job.radius * 2 + 1);
job.ok("Regen started: " + chunks + " chunk(s) around " + centerX + "," + centerZ + ". Deleting and regenerating in place.");
LOGGER.info("Iris regen start: dim={} center={},{} radius={} chunks={}",
ModdedIrisLog.info("Iris regen start: dim={} center={},{} radius={} chunks={}",
level.dimension().identifier(), centerX, centerZ, job.radius, chunks);
Thread thread = new Thread(job::run, "Iris Regenerate");
thread.setDaemon(true);
@@ -117,11 +115,11 @@ public final class ModdedRegen {
List<int[]> targets = ChunkSpiral.centerOut(centerX, centerZ, radius);
int applied = regenerate(targets);
ok("Regen finished: " + applied + "/" + targets.size() + " chunk(s) in " + Form.duration(M.ms() - startedAt, 2));
LOGGER.info("Iris regen done: {}/{} chunks in {}ms", applied, targets.size(), M.ms() - startedAt);
ModdedIrisLog.info("Iris regen done: {}/{} chunks in {}ms", applied, targets.size(), M.ms() - startedAt);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} catch (Throwable e) {
LOGGER.error("Iris regen failed", e);
ModdedIrisLog.error("Iris regen failed", e);
fail("Regen failed: " + e);
} finally {
WorldMaintenance.endWorldMaintenance(worldIdentity, "regen");
@@ -155,7 +153,7 @@ public final class ModdedRegen {
int chunkZ = target[1];
if (!inFlight.tryAcquire(CHUNK_SLOT_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)) {
aborted.set(true);
LOGGER.error("Iris regen aborted: chunk {},{} waited {}ms for an apply slot ({}/{} done)",
ModdedIrisLog.error("Iris regen aborted: chunk {},{} waited {}ms for an apply slot ({}/{} done)",
chunkX, chunkZ, CHUNK_SLOT_TIMEOUT_MILLIS, completed.get(), total);
fail("Regen aborted: apply pipeline stalled at " + completed.get() + "/" + total + " chunk(s)");
break;
@@ -173,7 +171,7 @@ public final class ModdedRegen {
try {
engine.generate(chunkX << 4, chunkZ << 4, blocks, biomes, false);
} catch (Throwable e) {
LOGGER.error("Iris regen chunk {},{} generation failed", chunkX, chunkZ, e);
ModdedIrisLog.error("Iris regen chunk {},{} generation failed", chunkX, chunkZ, e);
fail("Chunk " + chunkX + "," + chunkZ + " generation FAILED: " + e.getClass().getSimpleName());
completed.incrementAndGet();
inFlight.release();
@@ -190,7 +188,7 @@ public final class ModdedRegen {
success = true;
applied.incrementAndGet();
} catch (Throwable e) {
LOGGER.error("Iris regen chunk {},{} apply failed", chunkX, chunkZ, e);
ModdedIrisLog.error("Iris regen chunk {},{} apply failed", chunkX, chunkZ, e);
fail("Chunk " + chunkX + "," + chunkZ + " apply FAILED: " + e.getClass().getSimpleName());
} finally {
int done = completed.incrementAndGet();
@@ -212,7 +210,7 @@ public final class ModdedRegen {
if (!allApplied.await(FINAL_APPLY_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)) {
aborted.set(true);
long outstanding = allApplied.getCount();
LOGGER.error("Iris regen aborted: {} of {} chunk(s) did not finish within {}ms",
ModdedIrisLog.error("Iris regen aborted: {} of {} chunk(s) did not finish within {}ms",
outstanding, total, FINAL_APPLY_TIMEOUT_MILLIS);
fail("Regen aborted: " + outstanding + " of " + total + " chunk(s) never finished");
}
@@ -18,6 +18,7 @@
package art.arcane.iris.modded.command;
import art.arcane.iris.modded.ModdedIrisLog;
import art.arcane.iris.core.loader.IrisData;
import art.arcane.iris.core.structure.StructureIndexService;
import art.arcane.iris.engine.framework.Engine;
@@ -40,8 +41,6 @@ import net.minecraft.commands.Commands;
import net.minecraft.commands.SharedSuggestionProvider;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.util.List;
@@ -53,7 +52,6 @@ import art.arcane.iris.core.localization.IrisLanguage;
import art.arcane.iris.core.localization.ModdedCommandMessages;
import art.arcane.volmlib.util.localization.MessageArgument;
public final class ModdedStructureCommands {
private static final Logger LOGGER = LoggerFactory.getLogger("Iris");
private static final Predicate<CommandSourceStack> GATE = Commands.hasPermission(Commands.LEVEL_GAMEMASTERS);
private static final SuggestionProvider<CommandSourceStack> IRIS_STRUCTURE_KEYS = (CommandContext<CommandSourceStack> context, SuggestionsBuilder builder) -> suggestIrisStructureKeys(context, builder);
@@ -213,7 +211,7 @@ public final class ModdedStructureCommands {
piece.getObject().place(piece.getX(), piece.getY(), piece.getZ(), placer, config, rng, null, null, data);
}
} catch (Throwable e) {
LOGGER.error("Iris structure place failed for {}", key, e);
ModdedIrisLog.error("Iris structure place failed for {}", key, e);
ModdedObjectUndo.record(owner, level, placer.undoSnapshot());
IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_STRUCTURE_COMMANDS_PLACE_FAILED_PARTIAL_CHANGES_RECORDED_UNDO, MessageArgument.untrusted("value", e.getClass().getSimpleName())));
return 0;
@@ -18,6 +18,7 @@
package art.arcane.iris.modded.command;
import art.arcane.iris.modded.ModdedIrisLog;
import art.arcane.iris.core.IrisSettings;
import art.arcane.iris.core.gui.GuiHost;
import art.arcane.iris.core.gui.NoiseExplorerGUI;
@@ -63,8 +64,6 @@ import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.entity.Relative;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.zeroturnaround.zip.ZipUtil;
import java.awt.Desktop;
@@ -85,7 +84,6 @@ import art.arcane.iris.core.localization.IrisLanguage;
import art.arcane.iris.core.localization.ModdedCommandMessages;
import art.arcane.volmlib.util.localization.MessageArgument;
public final class ModdedStudioCommands {
private static final Logger LOGGER = LoggerFactory.getLogger("Iris");
private static final Predicate<CommandSourceStack> GATE = Commands.hasPermission(Commands.LEVEL_GAMEMASTERS);
private static final Pattern PROJECT_NAME = Pattern.compile("[a-z0-9_-]+");
private static final Pattern STUDIO_ID_SANITIZER = Pattern.compile("[^a-z0-9_-]");
@@ -284,7 +282,7 @@ public final class ModdedStudioCommands {
try {
workspace = ModdedWorkspaceGenerator.writeWorkspace(IrisData.get(folder), folder, open);
} catch (Throwable e) {
LOGGER.error("Iris workspace write failed for {}", folder, e);
ModdedIrisLog.error("Iris workspace write failed for {}", folder, e);
IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_STUDIO_COMMANDS_FAILED_WRITE_WORKSPACE, MessageArgument.untrusted("value", folder.getAbsolutePath()), MessageArgument.untrusted("value2", String.valueOf(e.getMessage()))));
return 0;
}
@@ -299,7 +297,7 @@ public final class ModdedStudioCommands {
try {
Desktop.getDesktop().open(workspace);
} catch (Throwable e) {
LOGGER.error("Iris workspace open failed for {}", workspace, e);
ModdedIrisLog.error("Iris workspace open failed for {}", workspace, e);
IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_STUDIO_COMMANDS_COULD_NOT_OPEN, MessageArgument.untrusted("value", workspace.getName()), MessageArgument.untrusted("value2", e.getClass().getSimpleName())));
return 0;
}
@@ -380,8 +378,9 @@ public final class ModdedStudioCommands {
try {
File packFolder = new File(ModdedPackCommands.packsRoot(), pack);
if (!new File(packFolder, "dimensions/" + pack + ".json").isFile()) {
server.execute(() -> IrisModdedCommands.fail(source, "Pack '" + pack
+ "' is not installed. Use /iris download pack=overworld or pack=underworld, then restart."));
server.execute(() -> IrisModdedCommands.fail(source, IrisLanguage.plain(
ModdedCommandMessages.MODDED_STUDIO_COMMANDS_REQUIRED_PACK_IS_NOT_INSTALLED_INSTALL_THEN_RESTART,
MessageArgument.untrusted("pack", pack))));
return;
}
IrisData data = IrisData.get(packFolder);
@@ -393,7 +392,7 @@ public final class ModdedStudioCommands {
try {
ModdedWorkspaceGenerator.writeWorkspace(data, packFolder, true);
} catch (Throwable workspaceError) {
LOGGER.error("Iris workspace write failed for {}", packFolder, workspaceError);
ModdedIrisLog.error("Iris workspace write failed for {}", packFolder, workspaceError);
server.execute(() -> IrisModdedCommands.fail(source, IrisLanguage.plain(
ModdedCommandMessages.MODDED_STUDIO_COMMANDS_FAILED_WRITE_WORKSPACE,
MessageArgument.untrusted("value", packFolder.getAbsolutePath()),
@@ -407,7 +406,7 @@ public final class ModdedStudioCommands {
}
});
} catch (Throwable e) {
LOGGER.error("Iris studio open failed for {}", pack, e);
ModdedIrisLog.error("Iris studio open failed for {}", pack, e);
server.execute(() -> IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_STUDIO_COMMANDS_STUDIO_OPEN_FAILED, MessageArgument.untrusted("value", e.getClass().getSimpleName()), MessageArgument.trusted("errorMessage", IrisLanguage.errorDetail(e)))));
}
}
@@ -417,7 +416,7 @@ public final class ModdedStudioCommands {
try {
handle = ModdedDimensionManager.create(server, dimensionId, pack, pack, seed);
} catch (Throwable e) {
LOGGER.error("Iris console studio injection failed for {} ({})", dimensionId, pack, e);
ModdedIrisLog.error("Iris console studio injection failed for {} ({})", dimensionId, pack, e);
IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_STUDIO_COMMANDS_STUDIO_INJECTION_FAILED, MessageArgument.untrusted("value", e.getClass().getSimpleName()), MessageArgument.trusted("errorMessage", IrisLanguage.errorDetail(e))));
return;
}
@@ -430,7 +429,7 @@ public final class ModdedStudioCommands {
surface = engine.getMinHeight() + engine.getHeight(8, 8, false) + 2;
}
} catch (Throwable e) {
LOGGER.error("Iris console studio surface probe failed for {}", dimensionId, e);
ModdedIrisLog.error("Iris console studio surface probe failed for {}", dimensionId, e);
}
IrisModdedCommands.ok(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_STUDIO_COMMANDS_CONSOLE_STUDIO_OPEN_NOW_RUNS_SEED_TRANSIENT_NOT_WRITTEN_IRIS, MessageArgument.untrusted("dimensionId", dimensionId), MessageArgument.untrusted("pack", pack), MessageArgument.untrusted("seed", seed)));
IrisModdedCommands.ok(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_STUDIO_COMMANDS_ENTER_IT_WITH_EXECUTE_RUN_TP_S_8_5_8, MessageArgument.untrusted("dimensionId", dimensionId), MessageArgument.untrusted("surface", surface)));
@@ -447,7 +446,7 @@ public final class ModdedStudioCommands {
try {
handle = ModdedDimensionManager.create(server, dimensionId, pack, pack, seed);
} catch (Throwable e) {
LOGGER.error("Iris studio injection failed for {} ({})", dimensionId, pack, e);
ModdedIrisLog.error("Iris studio injection failed for {} ({})", dimensionId, pack, e);
IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_STUDIO_COMMANDS_STUDIO_INJECTION_FAILED_2, MessageArgument.untrusted("value", e.getClass().getSimpleName()), MessageArgument.trusted("errorMessage", IrisLanguage.errorDetail(e))));
return;
}
@@ -460,7 +459,7 @@ public final class ModdedStudioCommands {
surface = engine.getMinHeight() + engine.getHeight(8, 8, false) + 2;
}
} catch (Throwable e) {
LOGGER.error("Iris studio surface probe failed for {}", dimensionId, e);
ModdedIrisLog.error("Iris studio surface probe failed for {}", dimensionId, e);
}
player.teleportTo(studio, 8.5D, surface, 8.5D, java.util.Set.<Relative>of(), player.getYRot(), player.getXRot(), false);
IrisModdedCommands.ok(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_STUDIO_COMMANDS_STUDIO_OPEN_NOW_RUNS_SEED_USE_IRIS_STUDIO_CLOSE_WHEN, MessageArgument.untrusted("dimensionId", dimensionId), MessageArgument.untrusted("pack", pack), MessageArgument.untrusted("seed", seed)));
@@ -480,7 +479,7 @@ public final class ModdedStudioCommands {
try {
ModdedDimensionManager.remove(server, dimensionId, true);
} catch (Throwable e) {
LOGGER.error("Iris studio close failed for {}", dimensionId, e);
ModdedIrisLog.error("Iris studio close failed for {}", dimensionId, e);
IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_STUDIO_COMMANDS_STUDIO_CLOSE_FAILED, MessageArgument.untrusted("value", e.getClass().getSimpleName()), MessageArgument.trusted("errorMessage", IrisLanguage.errorDetail(e))));
return 0;
}
@@ -553,7 +552,7 @@ public final class ModdedStudioCommands {
surface = engine.getMinHeight() + engine.getHeight(8, 8, false) + 2;
}
} catch (Throwable e) {
LOGGER.error("Iris tpstudio surface probe failed", e);
ModdedIrisLog.error("Iris tpstudio surface probe failed", e);
}
player.teleportTo(studio, 8.5D, surface, 8.5D, java.util.Set.<Relative>of(), player.getYRot(), player.getXRot(), false);
IrisModdedCommands.ok(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_STUDIO_COMMANDS_TELEPORTED_YOUR_STUDIO, MessageArgument.untrusted("dimensionId", dimensionId)));
@@ -593,22 +592,23 @@ public final class ModdedStudioCommands {
try {
File templateFolder = new File(packsRoot, template);
if (!new File(templateFolder, "dimensions/" + template + ".json").isFile()) {
server.execute(() -> IrisModdedCommands.fail(source, "Template pack '" + template
+ "' is not installed. Install its zip with /iris download link=<zip-url>, then restart."));
server.execute(() -> IrisModdedCommands.fail(source, IrisLanguage.plain(
ModdedCommandMessages.MODDED_STUDIO_COMMANDS_REQUIRED_PACK_IS_NOT_INSTALLED_INSTALL_THEN_RESTART,
MessageArgument.untrusted("pack", template))));
return;
}
IrisProjectCopier.copyProject(templateFolder, target, template, name);
try {
ModdedWorkspaceGenerator.writeWorkspace(IrisData.get(target), target);
} catch (IOException e) {
LOGGER.error("Iris studio create workspace generation failed for {}", name, e);
ModdedIrisLog.error("Iris studio create workspace generation failed for {}", name, e);
}
server.execute(() -> {
IrisModdedCommands.ok(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_STUDIO_COMMANDS_CREATED_PROJECT_AT, MessageArgument.untrusted("name", name), MessageArgument.untrusted("value", target.getAbsolutePath())));
IrisModdedCommands.ok(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_STUDIO_COMMANDS_EDIT_DIMENSIONS_JSON_REST_PACK_VSCODE_WORKSPACE_WITH_JSON_SCHEMA, MessageArgument.untrusted("name", name)));
});
} catch (Throwable e) {
LOGGER.error("Iris studio create failed for {}", name, e);
ModdedIrisLog.error("Iris studio create failed for {}", name, e);
server.execute(() -> IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_STUDIO_COMMANDS_PROJECT_CREATION_FAILED, MessageArgument.untrusted("value", e.getClass().getSimpleName()), MessageArgument.trusted("errorMessage", IrisLanguage.errorDetail(e)))));
}
}, "Iris Studio Create");
@@ -630,7 +630,7 @@ public final class ModdedStudioCommands {
File result = compilePackage(folder, dimKey);
server.execute(() -> IrisModdedCommands.ok(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_STUDIO_COMMANDS_PACKAGE_COMPILED, MessageArgument.untrusted("value", result.getAbsolutePath()))));
} catch (Throwable e) {
LOGGER.error("Iris package failed for {}", dimKey, e);
ModdedIrisLog.error("Iris package failed for {}", dimKey, e);
server.execute(() -> IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_STUDIO_COMMANDS_PACKAGING_FAILED, MessageArgument.untrusted("value", e.getClass().getSimpleName()), MessageArgument.trusted("errorMessage", IrisLanguage.errorDetail(e)))));
}
}, "Iris Studio Package");
@@ -728,7 +728,7 @@ public final class ModdedStudioCommands {
IO.copyFile(objectFile, new File(folder, "objects/" + objectKey + ".iob"));
hashes.append(IO.hash(objectFile));
} catch (Throwable e) {
LOGGER.error("Iris package failed to copy object {}", objectKey, e);
ModdedIrisLog.error("Iris package failed to copy object {}", objectKey, e);
}
}
@@ -793,7 +793,7 @@ public final class ModdedStudioCommands {
IO.writeAll(new File(folder, category + "/" + key + ".json"), json);
return IO.hash(json);
} catch (Throwable e) {
LOGGER.error("Iris package failed to write {}/{}", category, key, e);
ModdedIrisLog.error("Iris package failed to write {}/{}", category, key, e);
return "";
}
}
@@ -838,7 +838,7 @@ public final class ModdedStudioCommands {
IrisModdedCommands.ok(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_STUDIO_COMMANDS_RARITY, MessageArgument.untrusted("key", key), MessageArgument.untrusted("rarity", rarity), MessageArgument.untrusted("value", Form.f((double) count.get() / totalTasks * 100, 2))));
}));
} catch (Throwable e) {
LOGGER.error("Iris region sampling failed", e);
ModdedIrisLog.error("Iris region sampling failed", e);
server.execute(() -> IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_STUDIO_COMMANDS_REGION_SAMPLING_FAILED, MessageArgument.untrusted("value", e.getClass().getSimpleName()))));
}
}, "Iris Region Sampler");
@@ -18,6 +18,7 @@
package art.arcane.iris.modded.command;
import art.arcane.iris.modded.ModdedIrisLog;
import art.arcane.iris.engine.framework.Engine;
import art.arcane.iris.engine.framework.IrisStructureLocator;
import art.arcane.iris.engine.framework.NativeStructureGenerationPolicy;
@@ -31,8 +32,6 @@ import net.minecraft.core.registries.Registries;
import net.minecraft.resources.Identifier;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.levelgen.structure.Structure;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Collection;
import java.util.HashSet;
@@ -44,7 +43,6 @@ import java.util.TreeMap;
import java.util.function.Predicate;
final class ModdedUnregisteredStructures {
private static final Logger LOGGER = LoggerFactory.getLogger("Iris");
private ModdedUnregisteredStructures() {
}
@@ -67,13 +65,13 @@ final class ModdedUnregisteredStructures {
.filter((ExcludedStructure entry) -> entry.status() == ReportStatus.UNPLACED)
.count();
long hidden = excluded.size() - unregistered - unplaced;
LOGGER.info("[Iris goto unregistered] {} structure candidate(s) excluded from /iris goto structure in {}",
ModdedIrisLog.info("[Iris goto unregistered] {} structure candidate(s) excluded from /iris goto structure in {}",
excluded.size(), dimension);
for (ExcludedStructure entry : excluded) {
LOGGER.info("[Iris goto unregistered] [{}] {} - {}",
ModdedIrisLog.info("[Iris goto unregistered] [{}] {} - {}",
entry.status().label(), entry.key(), entry.reason());
}
LOGGER.info("[Iris goto unregistered] Inventory scope is the live registry, this pack's "
ModdedIrisLog.info("[Iris goto unregistered] Inventory scope is the live registry, this pack's "
+ "nativeStructures placements, and structureLoader editable resources. This is deterministic "
+ "eligibility analysis and performs no chunk search; absent unmanaged datapack resources "
+ "cannot be inferred after registry loading.");
@@ -82,7 +80,7 @@ final class ModdedUnregisteredStructures {
+ unregistered + " unregistered, " + unplaced + " unplaced).");
return 1;
} catch (Throwable error) {
LOGGER.error("Iris failed to build the excluded structure report for {}",
ModdedIrisLog.error("Iris failed to build the excluded structure report for {}",
level.dimension().identifier(), error);
IrisModdedCommands.fail(source,
"Iris could not build the excluded structure report; see the server console.");
@@ -18,6 +18,7 @@
package art.arcane.iris.modded.command;
import art.arcane.iris.modded.ModdedIrisLog;
import art.arcane.iris.core.localization.IrisLanguage;
import art.arcane.iris.core.localization.RuntimeUiMessages;
import art.arcane.volmlib.util.localization.MessageArgument;
@@ -42,8 +43,6 @@ import net.minecraft.world.item.component.CustomData;
import net.minecraft.world.item.component.ItemLore;
import net.minecraft.world.item.component.TooltipDisplay;
import net.minecraft.world.level.Level;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.awt.Color;
import java.util.List;
@@ -52,7 +51,6 @@ import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ThreadLocalRandom;
public final class ModdedWandService {
private static final Logger LOGGER = LoggerFactory.getLogger("Iris");
private static final ConcurrentHashMap<UUID, Selection> SELECTIONS = new ConcurrentHashMap<>();
private static final String WAND_TAG = "iris_wand";
private static final String DUST_TAG = "iris_dust";
@@ -213,7 +211,7 @@ public final class ModdedWandService {
draw(player.level(), player, selection);
}
} catch (Throwable e) {
LOGGER.error("Iris wand selection draw failed", e);
ModdedIrisLog.error("Iris wand selection draw failed", e);
}
}
@@ -18,6 +18,7 @@
package art.arcane.iris.modded.command;
import art.arcane.iris.modded.ModdedIrisLog;
import art.arcane.iris.core.localization.IrisLanguage;
import art.arcane.iris.core.localization.IrisMessages;
import art.arcane.iris.core.localization.ModdedCommandMessages;
@@ -66,8 +67,6 @@ import net.minecraft.world.level.levelgen.Heightmap;
import net.minecraft.world.level.storage.loot.LootTable;
import net.minecraft.world.phys.BlockHitResult;
import net.minecraft.world.phys.HitResult;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
@@ -77,7 +76,6 @@ import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Predicate;
public final class ModdedWhatCommands {
private static final Logger LOGGER = LoggerFactory.getLogger("Iris");
private static final Predicate<CommandSourceStack> GATE =
Commands.hasPermission(Commands.LEVEL_GAMEMASTERS);
private static final SuggestionProvider<CommandSourceStack> MARKER_TYPES =
@@ -343,7 +341,7 @@ public final class ModdedWhatCommands {
MessageArgument.untrusted("object", object)));
}
} catch (Throwable error) {
LOGGER.error("Iris object lookup failed for /iris what block at {}, {}, {}",
ModdedIrisLog.error("Iris object lookup failed for /iris what block at {}, {}, {}",
pos.getX(), pos.getY(), pos.getZ(), error);
}
}
@@ -486,7 +484,7 @@ public final class ModdedWhatCommands {
private static void markerFailure(CommandSourceStack source,
ModdedScheduler scheduler, MarkerRun run, Throwable error) {
LOGGER.error("Iris marker scan failed for {}", run.marker(), error);
ModdedIrisLog.error("Iris marker scan failed for {}", run.marker(), error);
scheduler.global(() -> {
if (ACTIVE_MARKER_RUNS.remove(run.playerId(), run)) {
IrisModdedCommands.fail(source, IrisLanguage.plain(
@@ -514,7 +512,7 @@ public final class ModdedWhatCommands {
private static void logLookupFailure(CommandSourceStack source,
String operation, Throwable error,
TextKey message) {
LOGGER.error("Iris /what {} lookup failed in {}", operation,
ModdedIrisLog.error("Iris /what {} lookup failed in {}", operation,
source.getLevel().dimension().identifier(), error);
IrisModdedCommands.fail(source, IrisLanguage.plain(
message,
@@ -18,6 +18,7 @@
package art.arcane.iris.modded.command;
import art.arcane.iris.modded.ModdedIrisLog;
import art.arcane.iris.core.loader.IrisData;
import art.arcane.iris.core.pack.BrokenPackException;
import art.arcane.iris.core.pack.PackValidationRegistry;
@@ -42,8 +43,6 @@ import net.minecraft.commands.SharedSuggestionProvider;
import net.minecraft.commands.arguments.IdentifierArgument;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerLevel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.util.ArrayList;
@@ -57,7 +56,6 @@ import art.arcane.iris.core.localization.ModdedCommandMessages;
import art.arcane.iris.core.localization.RuntimeUiMessages;
import art.arcane.volmlib.util.localization.MessageArgument;
public final class ModdedWorldCommands {
private static final Logger LOGGER = LoggerFactory.getLogger("Iris");
private static final Predicate<CommandSourceStack> GATE = Commands.hasPermission(Commands.LEVEL_GAMEMASTERS);
private static final String DEFAULT_NAMESPACE = "irisworldgen";
private static final long DEFAULT_SEED = 1337L;
@@ -174,8 +172,9 @@ public final class ModdedWorldCommands {
if (packFolder.isDirectory()) {
return enableInstalled(source, server, dimensionId, pack, packDimension, seed);
}
IrisModdedCommands.fail(source, "Pack '" + pack
+ "' is not installed. Use /iris download pack=overworld or pack=underworld, then restart.");
IrisModdedCommands.fail(source, IrisLanguage.plain(
ModdedCommandMessages.MODDED_WORLD_COMMANDS_REQUIRED_PACK_IS_NOT_INSTALLED_INSTALL_THEN_RESTART,
MessageArgument.untrusted("pack", pack)));
return 0;
}
@@ -189,7 +188,7 @@ public final class ModdedWorldCommands {
try {
ModdedDimensionManager.createPersistent(server, dimensionId, pack, packDimension, seed);
} catch (Throwable e) {
LOGGER.error("Iris world injection failed for {} (pack={} dim={})", dimensionId, pack, packDimension, e);
ModdedIrisLog.error("Iris world injection failed for {} (pack={} dim={})", dimensionId, pack, packDimension, e);
IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_WORLD_COMMANDS_FAILED_INJECT_IRIS_WORLD, MessageArgument.untrusted("dimensionId", dimensionId), MessageArgument.untrusted("value", e.getClass().getSimpleName()), MessageArgument.trusted("errorMessage", IrisLanguage.errorDetail(e))));
return 0;
}
@@ -220,7 +219,7 @@ public final class ModdedWorldCommands {
try {
ModdedDimensionManager.createPersistent(server, dimensionId, pack, packDimension, seed);
} catch (Throwable e) {
LOGGER.error("Iris primary world injection failed for {} (pack={} dim={})", dimensionId, pack, packDimension, e);
ModdedIrisLog.error("Iris primary world injection failed for {} (pack={} dim={})", dimensionId, pack, packDimension, e);
IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_WORLD_COMMANDS_FAILED_INJECT_IRIS_PRIMARY_WORLD, MessageArgument.untrusted("value", e.getClass().getSimpleName()), MessageArgument.trusted("errorMessage", IrisLanguage.errorDetail(e))));
return 0;
}
@@ -265,8 +264,9 @@ public final class ModdedWorldCommands {
if (packFolder.isDirectory()) {
return applyMainWorld(source, pack, packDimension, packRaw, seed);
}
IrisModdedCommands.fail(source, "Pack '" + pack
+ "' is not installed. Use /iris download pack=overworld or pack=underworld, then restart.");
IrisModdedCommands.fail(source, IrisLanguage.plain(
ModdedCommandMessages.MODDED_WORLD_COMMANDS_REQUIRED_PACK_IS_NOT_INSTALLED_INSTALL_THEN_RESTART,
MessageArgument.untrusted("pack", pack)));
return 0;
}
@@ -279,7 +279,7 @@ public final class ModdedWorldCommands {
return 0;
}
} catch (Throwable e) {
LOGGER.error("Iris main world pack load failed for {} (dim={})", pack, packDimension, e);
ModdedIrisLog.error("Iris main world pack load failed for {} (dim={})", pack, packDimension, e);
if (PackValidationRegistry.get(pack) == null) {
IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_WORLD_COMMANDS_PACK_IS_NOT_READY_YET_STILL_LOADING_VALIDATING_TRY_COMMAND, MessageArgument.untrusted("pack", pack)));
return 0;
@@ -384,7 +384,7 @@ public final class ModdedWorldCommands {
try {
removed = ModdedDimensionManager.removePersistent(server, dimensionId, wipeStorage);
} catch (Throwable e) {
LOGGER.error("Iris world removal failed for {}", dimensionId, e);
ModdedIrisLog.error("Iris world removal failed for {}", dimensionId, e);
IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_WORLD_COMMANDS_FAILED_REMOVE_IRIS_WORLD, MessageArgument.untrusted("dimensionId", dimensionId), MessageArgument.untrusted("value", e.getClass().getSimpleName()), MessageArgument.trusted("errorMessage", IrisLanguage.errorDetail(e))));
return 0;
}
@@ -18,6 +18,7 @@
package art.arcane.iris.modded.service;
import art.arcane.iris.modded.ModdedIrisLog;
import art.arcane.iris.core.IrisSettings;
import art.arcane.iris.core.service.EngineMaintenance;
import art.arcane.iris.engine.framework.Engine;
@@ -27,8 +28,6 @@ import art.arcane.iris.modded.ModdedWorldEngines;
import art.arcane.iris.spi.IrisLogging;
import art.arcane.iris.util.project.context.IrisContext;
import net.minecraft.server.MinecraftServer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Collection;
import java.util.Collections;
@@ -42,7 +41,6 @@ import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
public final class ModdedEngineMaintenanceService implements ModdedTickableService {
private static final Logger LOGGER = LoggerFactory.getLogger("Iris");
private static final long MAINTENANCE_PERIOD_MILLIS = 2_000L;
private static final long SAVE_PERIOD_MILLIS = 60_000L;
private static final long SHUTDOWN_TIMEOUT_SECONDS = 30L;
@@ -120,7 +118,7 @@ public final class ModdedEngineMaintenanceService implements ModdedTickableServi
} catch (RejectedExecutionException exception) {
inFlight.remove(engine);
if (active == service && !active.isShutdown()) {
LOGGER.error("Iris rejected engine maintenance for {}", engineName(engine), exception);
ModdedIrisLog.error("Iris rejected engine maintenance for {}", engineName(engine), exception);
}
}
}
@@ -148,7 +146,7 @@ public final class ModdedEngineMaintenanceService implements ModdedTickableServi
EngineMaintenance.Outcome outcome = EngineMaintenance.run(engine);
if (outcome.unloadedTectonicPlates() > 0) {
LOGGER.debug("Iris unloaded {} tectonic plates in {}ms for {}",
ModdedIrisLog.debug("Iris unloaded {} tectonic plates in {}ms for {}",
outcome.unloadedTectonicPlates(), outcome.unloadDurationMillis(), engineName(engine));
}
} catch (GenerationSessionException exception) {
@@ -156,13 +154,13 @@ public final class ModdedEngineMaintenanceService implements ModdedTickableServi
return;
}
IrisLogging.reportError(exception);
LOGGER.error("Iris engine maintenance session failed for {}", engineName(engine), exception);
ModdedIrisLog.error("Iris engine maintenance session failed for {}", engineName(engine), exception);
} catch (Throwable exception) {
if (EngineMaintenance.isMantleClosed(exception)) {
return;
}
IrisLogging.reportError(exception);
LOGGER.error("Iris engine maintenance failed for {}", engineName(engine), exception);
ModdedIrisLog.error("Iris engine maintenance failed for {}", engineName(engine), exception);
}
}
@@ -180,7 +178,7 @@ public final class ModdedEngineMaintenanceService implements ModdedTickableServi
return;
}
IrisLogging.reportError(exception);
LOGGER.error("Iris engine save failed for {}", engineName(engine), exception);
ModdedIrisLog.error("Iris engine save failed for {}", engineName(engine), exception);
}
}
@@ -212,13 +210,13 @@ public final class ModdedEngineMaintenanceService implements ModdedTickableServi
IllegalStateException failure = new IllegalStateException(
"Iris engine maintenance workers did not stop after shutdownNow");
IrisLogging.reportError(failure);
LOGGER.error("Iris engine maintenance did not terminate; active engine lifecycle leases will block unsafe shutdown", failure);
ModdedIrisLog.error("Iris engine maintenance did not terminate; active engine lifecycle leases will block unsafe shutdown", failure);
return false;
} catch (InterruptedException exception) {
active.shutdownNow();
Thread.currentThread().interrupt();
IrisLogging.reportError(exception);
LOGGER.error("Interrupted while draining Iris engine maintenance", exception);
ModdedIrisLog.error("Interrupted while draining Iris engine maintenance", exception);
return false;
}
}
@@ -18,11 +18,10 @@
package art.arcane.iris.modded.service;
import art.arcane.iris.modded.ModdedIrisLog;
import art.arcane.iris.core.loader.IrisData;
import art.arcane.iris.engine.framework.MeteredCache;
import art.arcane.iris.engine.framework.PreservationRegistry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.lang.ref.WeakReference;
import java.util.List;
@@ -31,7 +30,6 @@ import java.util.concurrent.ExecutorService;
import java.util.concurrent.atomic.AtomicBoolean;
public final class ModdedPreservationService implements ModdedService, PreservationRegistry {
private static final Logger LOGGER = LoggerFactory.getLogger("Iris");
private static final long DEREFERENCE_INTERVAL_MILLIS = 60000L;
private final List<Thread> threads = new CopyOnWriteArrayList<>();
@@ -107,17 +105,17 @@ public final class ModdedPreservationService implements ModdedService, Preservat
}
try {
thread.interrupt();
LOGGER.info("Iris preservation interrupted thread {}", thread.getName());
ModdedIrisLog.info("Iris preservation interrupted thread {}", thread.getName());
} catch (Throwable error) {
LOGGER.error("Iris preservation failed to interrupt thread {}", thread.getName(), error);
ModdedIrisLog.error("Iris preservation failed to interrupt thread {}", thread.getName(), error);
}
}
for (ExecutorService service : services) {
try {
service.shutdownNow();
LOGGER.info("Iris preservation shut down executor {}", service);
ModdedIrisLog.info("Iris preservation shut down executor {}", service);
} catch (Throwable error) {
LOGGER.error("Iris preservation failed to shut down executor {}", service, error);
ModdedIrisLog.error("Iris preservation failed to shut down executor {}", service, error);
}
}
}
@@ -60,6 +60,6 @@ public final class ModdedSettingsHotloadService implements ModdedTickableService
}
private static File settingsFile() {
return IrisPlatforms.get().dataFile("settings.json");
return IrisPlatforms.get().dataFile("iris.json");
}
}
@@ -18,6 +18,7 @@
package art.arcane.iris.modded.service;
import art.arcane.iris.modded.ModdedIrisLog;
import art.arcane.iris.core.gui.PregeneratorJob;
import art.arcane.iris.core.loader.IrisData;
import art.arcane.iris.core.tools.WorldMaintenance;
@@ -37,8 +38,6 @@ import art.arcane.volmlib.util.collection.KList;
import art.arcane.volmlib.util.io.ReactiveFolder;
import art.arcane.volmlib.util.scheduling.ChronoLatch;
import net.minecraft.server.MinecraftServer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.util.HashSet;
@@ -52,7 +51,6 @@ import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.atomic.AtomicBoolean;
public final class ModdedStudioHotloadService implements ModdedTickableService, EnginePlatformHooks {
private static final Logger LOGGER = LoggerFactory.getLogger("Iris");
private static final String STUDIO_DIMENSION_PREFIX = "irisworldgen:studio_";
private static final long POLL_MILLIS = 250L;
private static final long CHECK_LATCH_MILLIS = 1_000L;
@@ -237,7 +235,7 @@ public final class ModdedStudioHotloadService implements ModdedTickableService,
folder.check();
}
} catch (Throwable e) {
LOGGER.error("Iris studio hotload check failed for {}", dimensionId, e);
ModdedIrisLog.error("Iris studio hotload check failed for {}", dimensionId, e);
}
}
@@ -249,9 +247,9 @@ public final class ModdedStudioHotloadService implements ModdedTickableService,
try {
engine.hotloadSilently();
generator.onHotload();
LOGGER.info("Iris studio hotload {} pack={} {}ms", dimensionId, engine.getDimension().getLoadKey(), System.currentTimeMillis() - start);
ModdedIrisLog.info("Iris studio hotload {} pack={} {}ms", dimensionId, engine.getDimension().getLoadKey(), System.currentTimeMillis() - start);
} catch (Throwable e) {
LOGGER.error("Iris studio hotload failed for {}", dimensionId, e);
ModdedIrisLog.error("Iris studio hotload failed for {}", dimensionId, e);
throw new IllegalStateException("Iris studio hotload failed for " + dimensionId, e);
}
}
@@ -275,7 +273,7 @@ public final class ModdedStudioHotloadService implements ModdedTickableService,
IrisData data = engine.getData();
ModdedWorkspaceGenerator.writeWorkspace(data, data.getDataFolder());
} catch (Throwable e) {
LOGGER.error("Iris {} failed for {}", operation, engine.getDimension().getLoadKey(), e);
ModdedIrisLog.error("Iris {} failed for {}", operation, engine.getDimension().getLoadKey(), e);
}
}
@@ -1,5 +1,6 @@
package art.arcane.iris.modded.service;
import art.arcane.iris.modded.ModdedIrisLog;
import art.arcane.iris.modded.ModdedEngineBootstrap;
import art.arcane.iris.modded.ModdedScheduler;
import net.minecraft.core.BlockPos;
@@ -12,8 +13,6 @@ import net.minecraft.sounds.SoundSource;
import net.minecraft.world.entity.item.ItemEntity;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.block.state.BlockState;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.Collection;
@@ -21,7 +20,6 @@ import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
final class ModdedTreeFellerPresentation {
private static final Logger LOGGER = LoggerFactory.getLogger("Iris");
private static final int MIN_BLOCKS_PER_PULSE = 4;
private static final int MAX_BLOCKS_PER_PULSE = 64;
private static final int TARGET_EROSION_PULSES = 60;
@@ -215,13 +213,13 @@ final class ModdedTreeFellerPresentation {
private void reportEffectFailure(Throwable error) {
if (effectFailureReported.compareAndSet(false, true)) {
LOGGER.error("Iris modded tree-feller presentation failed", error);
ModdedIrisLog.error("Iris modded tree-feller presentation failed", error);
}
}
private void reportDeliveryFailure(Throwable error) {
if (deliveryFailureReported.compareAndSet(false, true)) {
LOGGER.error("Iris modded tree-feller drop delivery failed", error);
ModdedIrisLog.error("Iris modded tree-feller drop delivery failed", error);
}
}
}
@@ -1,5 +1,6 @@
package art.arcane.iris.modded.service;
import art.arcane.iris.modded.ModdedIrisLog;
import art.arcane.iris.core.IrisSettings;
import art.arcane.iris.core.service.tree.TreeDefinitionIndex;
import art.arcane.iris.core.service.tree.TreeMarkerTraversal;
@@ -23,8 +24,6 @@ import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockState;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Collections;
import java.util.List;
@@ -38,7 +37,6 @@ import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.BooleanSupplier;
public final class ModdedTreeFellerService implements ModdedTickableService {
private static final Logger LOGGER = LoggerFactory.getLogger("Iris");
private static final ThreadLocal<Integer> BREAK_PROBE_DEPTH = ThreadLocal.withInitial(() -> 0);
private final AtomicBoolean enabled = new AtomicBoolean();
@@ -245,7 +243,7 @@ public final class ModdedTreeFellerService implements ModdedTickableService {
(x, y, z) -> markerAt(prepared.engine(), prepared.minimumY(), x, y, z)
);
} catch (Throwable error) {
LOGGER.error("Iris modded tree-feller discovery failed", error);
ModdedIrisLog.error("Iris modded tree-feller discovery failed", error);
discovery = new TreeMarkerTraversal.Discovery(List.of(), false);
}
TreeMarkerTraversal.Discovery resolved = discovery;
@@ -0,0 +1,38 @@
package art.arcane.iris.client;
import art.arcane.iris.spi.protocol.IrisMessage;
import org.junit.Test;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
public class IrisClientHotloadTest {
private static final IrisMessage.DimensionStatus OVERWORLD = new IrisMessage.DimensionStatus(
"overworld",
"pack",
1L,
-64,
320,
true
);
@Test
public void successfulCurrentPackHotloadInvalidatesWorldCaches() {
IrisMessage.StudioHotload hotload = new IrisMessage.StudioHotload("pack", 0, false, "");
assertTrue(IrisClient.shouldInvalidateForHotload(OVERWORLD, hotload));
}
@Test
public void failedOrUnrelatedHotloadRetainsWorldCaches() {
assertFalse(IrisClient.shouldInvalidateForHotload(
OVERWORLD,
new IrisMessage.StudioHotload("pack", 0, true, "failed")
));
assertFalse(IrisClient.shouldInvalidateForHotload(
OVERWORLD,
new IrisMessage.StudioHotload("other", 0, false, "")
));
assertFalse(IrisClient.shouldInvalidateForHotload(null, null));
}
}
@@ -5,7 +5,10 @@ import org.junit.Test;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.stream.Stream;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
/**
@@ -24,4 +27,49 @@ public class ModdedIrisLogLevelCoverageTest {
String body = source.substring(router, source.indexOf("public static void debug(", router));
assertTrue(body, body.contains("default -> info(message);"));
}
@Test
public void slf4jStyleArgumentsAndTrailingThrowableArePreserved() {
RuntimeException failure = new RuntimeException("broken");
ModdedIrisLog.RenderedLog rendered = ModdedIrisLog.render("chunk {},{} failed", 4, 9, failure);
assertEquals("chunk 4,9 failed", rendered.message());
assertEquals(failure, rendered.error());
}
@Test
public void formattedDebugThrowableUsesTheVisibleDebugRouteWhenEnabled() throws IOException {
String source = Files.readString(Path.of(System.getProperty("iris.moddedCommonSources"),
"art/arcane/iris/modded/ModdedIrisLog.java"));
int start = source.indexOf("public static void debug(String format, Object... arguments)");
int end = source.indexOf("public static void info(String message)", start);
assertTrue("formatted debug overload not found", start >= 0);
assertTrue("formatted debug overload boundary not found", end > start);
String body = source.substring(start, end);
assertTrue(body, body.contains("if (!debugEnabled())"));
assertTrue(body, body.contains("LOGGER.info(\"[Iris/DEBUG] \" + clean(rendered.message()), rendered.error())"));
}
@Test
public void moddedProductionUsesTheIrisLogFrontDoor() throws IOException {
Path root = Path.of(System.getProperty("iris.moddedCommonSources"));
try (Stream<Path> files = Files.walk(root)) {
List<Path> bypasses = files
.filter(path -> path.toString().endsWith(".java"))
.filter(path -> !path.getFileName().toString().equals("ModdedIrisLog.java"))
.filter(path -> {
try {
String source = Files.readString(path);
return source.contains("LoggerFactory.getLogger")
|| source.contains("private static final Logger LOGGER");
} catch (IOException unreadable) {
throw new IllegalStateException(unreadable);
}
})
.toList();
assertTrue(bypasses.toString(), bypasses.isEmpty());
}
}
}
@@ -91,7 +91,7 @@ public class ModdedPlatformPathsTest {
File iris = new File(temporaryFolder.getRoot(), "iris");
assertEquals(iris, platform.dataFolder());
assertEquals(new File(iris, "settings.json"), platform.dataFile("settings.json"));
assertEquals(new File(iris, "iris.json"), platform.dataFile("iris.json"));
assertEquals(new File(iris, "parity"), platform.dataFolder("parity"));
}