mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-08-27 04:37:47 +00:00
dwa
This commit is contained in:
@@ -236,6 +236,19 @@ public final class ModdedPlatform implements IrisPlatform {
|
||||
*/
|
||||
@Override
|
||||
public void reportError(Throwable error) {
|
||||
reportThrottled("Iris reported error", error);
|
||||
}
|
||||
|
||||
/**
|
||||
* Contextual reports run the same throttle and emit exactly one trace through the modded
|
||||
* log; the SPI default's raw stderr copy would bypass the per-signature suppression.
|
||||
*/
|
||||
@Override
|
||||
public void reportError(String context, Throwable error) {
|
||||
reportThrottled(context == null || context.isBlank() ? "Iris reported error" : context, error);
|
||||
}
|
||||
|
||||
private void reportThrottled(String message, Throwable error) {
|
||||
if (error == null) {
|
||||
return;
|
||||
}
|
||||
@@ -247,7 +260,7 @@ public final class ModdedPlatform implements IrisPlatform {
|
||||
sink.accept(error);
|
||||
return;
|
||||
}
|
||||
ModdedIrisLog.error("Iris reported error", error);
|
||||
ModdedIrisLog.error(message, error);
|
||||
Consumer<Throwable> capture = CAPTURE_SINK;
|
||||
if (capture != null) {
|
||||
try {
|
||||
|
||||
@@ -800,6 +800,18 @@ public final class ModdedWorldManager implements EngineWorldManager {
|
||||
}
|
||||
if (!mantleWarmupExecutorStopped) {
|
||||
try {
|
||||
// Drain, don't interrupt: an interrupt inside a FileChannel plate read closes
|
||||
// the channel and the read-failure fallback installs an empty plate that a
|
||||
// later flush persists over real data. Queued tasks no-op on the closed flag,
|
||||
// so the await only covers a single in-flight load; escalate on timeout only.
|
||||
mantleWarmupExecutor.shutdown();
|
||||
if (!mantleWarmupExecutor.awaitTermination(5L, java.util.concurrent.TimeUnit.SECONDS)) {
|
||||
IrisLogging.warn("Iris mantle warm-up did not stop before world manager close; forcing interrupt");
|
||||
mantleWarmupExecutor.shutdownNow();
|
||||
}
|
||||
mantleWarmupExecutorStopped = true;
|
||||
} catch (InterruptedException interrupted) {
|
||||
Thread.currentThread().interrupt();
|
||||
mantleWarmupExecutor.shutdownNow();
|
||||
mantleWarmupExecutorStopped = true;
|
||||
} catch (Throwable e) {
|
||||
|
||||
@@ -20,6 +20,7 @@ package art.arcane.iris.modded.api;
|
||||
|
||||
import art.arcane.iris.core.tools.WorldMaintenance;
|
||||
import art.arcane.iris.engine.framework.Engine;
|
||||
import art.arcane.iris.engine.framework.EngineLifecycleTasks;
|
||||
import art.arcane.iris.modded.IrisModdedChunkGenerator;
|
||||
import art.arcane.iris.modded.command.ModdedPregenJob;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
@@ -130,14 +131,17 @@ public final class IrisModdedAPI {
|
||||
* never create or load one - or nothing of {@code type} is stored there. A {@code y} outside the engine's
|
||||
* height range reads as null rather than throwing.
|
||||
*
|
||||
* @throws IllegalStateException if the engine's mantle has already been closed
|
||||
* A closing or closed engine refuses the operation quietly (null / no-op) instead of racing its shutdown.
|
||||
*/
|
||||
public static <T> T getMantleData(ServerLevel level, int x, int y, int z, Class<T> type) {
|
||||
Engine engine = getEngine(level);
|
||||
if (engine == null) {
|
||||
return null;
|
||||
}
|
||||
return engine.getMantle().getMantle().get(x, y - engine.getMinHeight(), z, type);
|
||||
// Lease-gated so the engine shutdown drain covers this public accessor; a mantle
|
||||
// mid-close refuses the lease instead of racing the region flush.
|
||||
return EngineLifecycleTasks.call(engine, "modded_api_mantle_get",
|
||||
() -> engine.getMantle().getMantle().get(x, y - engine.getMinHeight(), z, type), null);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -151,28 +155,30 @@ public final class IrisModdedAPI {
|
||||
* Values written under a custom type are discarded when Iris trims a mantle region unless the type is
|
||||
* declared with {@link #retainMantleDataForSlice(Class)}.
|
||||
*
|
||||
* @throws IllegalStateException if the engine's mantle has already been closed
|
||||
* A closing or closed engine refuses the operation quietly (null / no-op) instead of racing its shutdown.
|
||||
*/
|
||||
public static <T> void setMantleData(ServerLevel level, int x, int y, int z, T data) {
|
||||
Engine engine = getEngine(level);
|
||||
if (engine == null || data == null) {
|
||||
return;
|
||||
}
|
||||
engine.getMantle().getMantle().set(x, y - engine.getMinHeight(), z, data);
|
||||
EngineLifecycleTasks.run(engine, "modded_api_mantle_set",
|
||||
() -> engine.getMantle().getMantle().set(x, y - engine.getMinHeight(), z, data));
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes any mantle value of {@code type} at world coordinates. A non-Iris level or an out-of-range
|
||||
* {@code y} is a silent no-op. Like a write, this creates the mantle region if it is absent.
|
||||
*
|
||||
* @throws IllegalStateException if the engine's mantle has already been closed
|
||||
* A closing or closed engine refuses the operation quietly (null / no-op) instead of racing its shutdown.
|
||||
*/
|
||||
public static <T> void deleteMantleData(ServerLevel level, int x, int y, int z, Class<T> type) {
|
||||
Engine engine = getEngine(level);
|
||||
if (engine == null) {
|
||||
return;
|
||||
}
|
||||
engine.getMantle().getMantle().remove(x, y - engine.getMinHeight(), z, type);
|
||||
EngineLifecycleTasks.run(engine, "modded_api_mantle_delete",
|
||||
() -> engine.getMantle().getMantle().remove(x, y - engine.getMinHeight(), z, type));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+12
@@ -69,6 +69,9 @@ final class ModdedCommandSuggestions {
|
||||
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;
|
||||
private static volatile Set<String> cachedPackNames;
|
||||
private static volatile long cachedPackNamesAt;
|
||||
|
||||
private ModdedCommandSuggestions() {
|
||||
}
|
||||
@@ -228,6 +231,13 @@ final class ModdedCommandSuggestions {
|
||||
|
||||
private static CompletableFuture<Suggestions> suggestPackNames(CommandContext<CommandSourceStack> context, SuggestionsBuilder builder) {
|
||||
ModdedCommandFeedback.tab(context.getSource());
|
||||
// Suggestion packets arrive per keystroke; a short-lived snapshot keeps the directory
|
||||
// walk off the hot path without ever serving stale names for more than a few seconds.
|
||||
long now = System.currentTimeMillis();
|
||||
Set<String> cached = cachedPackNames;
|
||||
if (cached != null && now - cachedPackNamesAt < PACK_NAME_CACHE_TTL_MS) {
|
||||
return SharedSuggestionProvider.suggest(cached, builder);
|
||||
}
|
||||
Set<String> names = new TreeSet<>();
|
||||
names.add("overworld");
|
||||
try {
|
||||
@@ -249,6 +259,8 @@ final class ModdedCommandSuggestions {
|
||||
} catch (Throwable e) {
|
||||
warnTabFailure("pack names", context.getSource(), e);
|
||||
}
|
||||
cachedPackNames = names;
|
||||
cachedPackNamesAt = now;
|
||||
return SharedSuggestionProvider.suggest(names, builder);
|
||||
}
|
||||
|
||||
|
||||
+4
-3
@@ -70,9 +70,10 @@ final class ModdedCommandTree {
|
||||
.then(Commands.argument("dimension", StringArgumentType.greedyString()).suggests(ModdedCommandSuggestions.DIMENSION_NAMES)
|
||||
.executes((CommandContext<CommandSourceStack> context) -> IrisModdedCommands.info(context.getSource(), StringArgumentType.getString(context, "dimension")))));
|
||||
|
||||
// ModdedWhatCommands.tree() gates itself at LEVEL_GAMEMASTERS; the /iris what overlays are read-only,
|
||||
// so the root builder relaxes the whole subtree here instead of forking that file.
|
||||
root.then(ModdedWhatCommands.tree().requires(READ_ONLY));
|
||||
// ModdedWhatCommands.tree() gates itself at LEVEL_GAMEMASTERS and keeps that gate:
|
||||
// "what markers" drives lease-gated 9x9 mantle scans, and the Bukkit twin puts the
|
||||
// whole tree behind an op-default permission. requires() would overwrite, not AND.
|
||||
root.then(ModdedWhatCommands.tree());
|
||||
|
||||
root.then(teleportTree("teleport"));
|
||||
root.then(teleportTree("tp"));
|
||||
|
||||
+19
-5
@@ -285,7 +285,20 @@ public final class ModdedObjectCommands {
|
||||
return 0;
|
||||
}
|
||||
File file = new File(engine.getData().getDataFolder(), "objects" + File.separator + name.replace('/', File.separatorChar) + ".iob");
|
||||
if (file.exists() && !overwrite) {
|
||||
// Atomic path claim ON the server thread: the async write below turned a plain
|
||||
// exists() check into a TOCTOU where two rapid saves interleaved into one file.
|
||||
File parent = file.getParentFile();
|
||||
if (parent != null) {
|
||||
parent.mkdirs();
|
||||
}
|
||||
boolean claimed;
|
||||
try {
|
||||
claimed = file.createNewFile();
|
||||
} catch (IOException e) {
|
||||
IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_OBJECT_COMMANDS_FAILED_SAVE_OBJECT, MessageArgument.untrusted("value", String.valueOf(e.getMessage()))));
|
||||
return 0;
|
||||
}
|
||||
if (!claimed && !overwrite) {
|
||||
IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_OBJECT_COMMANDS_FILE_ALREADY_EXISTS_USE_IRIS_OBJECT_SAVE_OVERWRITE, MessageArgument.untrusted("name", name)));
|
||||
return 0;
|
||||
}
|
||||
@@ -295,15 +308,16 @@ public final class ModdedObjectCommands {
|
||||
// async-safe), but the disk write of a local, unshared object is not tick work.
|
||||
IrisObject object = capture(level, min, max, w, h, d, tilesSkipped, tilesSaved);
|
||||
MinecraftServer server = source.getServer();
|
||||
boolean finalClaimed = claimed;
|
||||
J.a(() -> {
|
||||
File parent = file.getParentFile();
|
||||
if (parent != null) {
|
||||
parent.mkdirs();
|
||||
}
|
||||
try {
|
||||
object.write(file);
|
||||
} catch (IOException e) {
|
||||
LOGGER.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();
|
||||
}
|
||||
server.execute(() -> IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_OBJECT_COMMANDS_FAILED_SAVE_OBJECT, MessageArgument.untrusted("value", String.valueOf(e.getMessage())))));
|
||||
return;
|
||||
}
|
||||
|
||||
+14
-5
@@ -85,13 +85,16 @@ public final class ModdedChunkUpdateService implements ModdedTickableService {
|
||||
ExecutorService active = warmupExecutor;
|
||||
warmupExecutor = null;
|
||||
if (active != null) {
|
||||
// Drop queued warm-ups (pure prefetch) and AWAIT: the very next shutdown stage
|
||||
// closes every Mantle, and an in-flight mantle.getChunk would fault a plate back
|
||||
// in after the close-time flush.
|
||||
active.shutdownNow();
|
||||
// NEVER interrupt first: an interrupt inside a FileChannel plate read closes the
|
||||
// channel (ClosedByInterruptException) and the read-failure fallback installs an
|
||||
// EMPTY plate that the very next shutdown stage flushes over real data. Queued
|
||||
// warm-ups self-cancel (warmupExecutor is already null), so shutdown() + await
|
||||
// only waits on the single in-flight load; escalate only on timeout.
|
||||
active.shutdown();
|
||||
try {
|
||||
if (!active.awaitTermination(5L, TimeUnit.SECONDS)) {
|
||||
IrisLogging.warn("Iris mantle warm-up did not stop before engine close");
|
||||
IrisLogging.warn("Iris mantle warm-up did not stop before engine close; forcing interrupt");
|
||||
active.shutdownNow();
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
@@ -279,6 +282,12 @@ public final class ModdedChunkUpdateService implements ModdedTickableService {
|
||||
}
|
||||
try {
|
||||
active.execute(() -> {
|
||||
// Self-cancel on shutdown: warmupExecutor is nulled first in onDisable, so
|
||||
// queued prefetches no-op instantly and only an in-flight load is awaited.
|
||||
if (warmupExecutor == null || mantle.isClosed()) {
|
||||
warmupQueue.remove(key);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
mantle.getChunk(chunkX, chunkZ);
|
||||
} catch (Throwable e) {
|
||||
|
||||
Reference in New Issue
Block a user