mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-11-01 17:00:26 +00:00
Merge branch 'client-fixes' into review
This commit is contained in:
commit
0cb3417755
55 files changed
+1421
-135
No files matched your search
+76
-10
@@ -79,6 +79,8 @@ import art.arcane.iris.spi.IrisServices;
|
||||
import art.arcane.iris.generation.concurrent.MultiBurst;
|
||||
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
public final class ModdedEngineBootstrap {
|
||||
private static final String[] CORE_SELF_TEST_CLASSES = {
|
||||
@@ -87,12 +89,17 @@ public final class ModdedEngineBootstrap {
|
||||
"art.arcane.iris.pack.loading.IrisData"
|
||||
};
|
||||
private static final Object LOCK = new Object();
|
||||
private static final Object STOP_LOCK = new Object();
|
||||
private static final AtomicBoolean EXIT_FLUSH_REGISTERED = new AtomicBoolean();
|
||||
private static final long EXIT_FLUSH_SERVER_WAIT_SECONDS = 30L;
|
||||
private static final ModdedServiceManager UNBOUND_SERVICE_MANAGER = new ModdedServiceManager();
|
||||
private static volatile NativeModdedLoader loader;
|
||||
private static volatile BoundRuntime runtime;
|
||||
private static volatile NativeModdedServer currentServer;
|
||||
private static volatile NativeModdedServer spawnCaptureServer;
|
||||
private static volatile boolean initialSpawnWasDefault;
|
||||
private static volatile boolean stoppedPending;
|
||||
private static volatile Thread stoppingThread;
|
||||
|
||||
private ModdedEngineBootstrap() {
|
||||
}
|
||||
@@ -119,10 +126,12 @@ public final class ModdedEngineBootstrap {
|
||||
}
|
||||
|
||||
public static void start(NativeModdedServer server) {
|
||||
finishPreviousServer("server start");
|
||||
registerExitFlush();
|
||||
captureInitialSpawn(server);
|
||||
currentServer = server;
|
||||
bind();
|
||||
// Pair of the stop() burst-pools stage. Load-bearing on integrated servers: once
|
||||
// Pair of the stopped() burst-pools stage. Load-bearing on integrated servers: once
|
||||
// closed, MultiBurst falls back to a same-thread executor, so a second world load
|
||||
// without reopen() would silently run every burst inline.
|
||||
MultiBurst.burst.reopen();
|
||||
@@ -150,6 +159,7 @@ public final class ModdedEngineBootstrap {
|
||||
}
|
||||
|
||||
public static void serverAboutToStart(NativeModdedServer server) {
|
||||
finishPreviousServer("server start");
|
||||
captureInitialSpawn(server);
|
||||
ModdedStartup.prepareForStartup();
|
||||
}
|
||||
@@ -180,20 +190,16 @@ public final class ModdedEngineBootstrap {
|
||||
try {
|
||||
generator.unbindEngine(world);
|
||||
} catch (Throwable exception) {
|
||||
// Never propagate into the loader's unload loop: a throw there skips closing every later level. A
|
||||
// runtime dimension removal retries the eviction itself and fails with the retained mapping.
|
||||
ModdedIrisLog.error("Iris engine unload failed for {}", world.name(), exception);
|
||||
if (exception instanceof RuntimeException runtimeException) {
|
||||
throw runtimeException;
|
||||
}
|
||||
if (exception instanceof Error fatalError) {
|
||||
throw fatalError;
|
||||
}
|
||||
throw new IllegalStateException("Iris engine unload failed for "
|
||||
+ world.name(), exception);
|
||||
}
|
||||
}
|
||||
|
||||
public static void stop() {
|
||||
NativeModdedServer stoppingServer = currentServer;
|
||||
stoppingThread = Thread.currentThread();
|
||||
stoppedPending = true;
|
||||
Throwable failure = null;
|
||||
failure = runStopStage(failure, "pack downloads", IrisModdedCommands::shutdownDownloads);
|
||||
failure = runStopStage(failure, "world check", () -> ModdedWorldCheck.serverStopped(stoppingServer));
|
||||
@@ -205,6 +211,62 @@ public final class ModdedEngineBootstrap {
|
||||
failure = runStopStage(failure, "studio commands", ModdedStudioCommands::clear);
|
||||
failure = runStopStage(failure, "gui host", ModdedGuiHost::clear);
|
||||
failure = runStopStage(failure, "services", () -> services().disableAll());
|
||||
reportStopFailure("stopping", failure);
|
||||
}
|
||||
|
||||
/**
|
||||
* The loaders still drain queued chunk generation after server stopping and only close levels afterwards, so
|
||||
* engines, their mantle and generation history, and the generation pools stay open until the server stopped.
|
||||
* Engines of unloaded levels are already closed by {@link #levelUnloaded}; this closes whatever remains.
|
||||
* Fabric fires it at the tail of stopServer, so a throw there skips it: the next server start or the JVM
|
||||
* exit runs it instead.
|
||||
*/
|
||||
public static void stopped() {
|
||||
synchronized (STOP_LOCK) {
|
||||
stoppedPending = false;
|
||||
stoppingThread = null;
|
||||
closeStoppedServer();
|
||||
}
|
||||
}
|
||||
|
||||
private static void finishPreviousServer(String trigger) {
|
||||
synchronized (STOP_LOCK) {
|
||||
if (!stoppedPending) {
|
||||
return;
|
||||
}
|
||||
ModdedIrisLog.warn("Iris closing the engines the previous server left open ({})", trigger);
|
||||
stopped();
|
||||
}
|
||||
}
|
||||
|
||||
private static void registerExitFlush() {
|
||||
if (EXIT_FLUSH_REGISTERED.compareAndSet(false, true)) {
|
||||
Runtime.getRuntime().addShutdownHook(new Thread(ModdedEngineBootstrap::flushAtExit, "Iris Exit Flush"));
|
||||
}
|
||||
}
|
||||
|
||||
private static void flushAtExit() {
|
||||
if (!stoppedPending) {
|
||||
return;
|
||||
}
|
||||
Thread serverThread = stoppingThread;
|
||||
if (serverThread != null && serverThread != Thread.currentThread()) {
|
||||
try {
|
||||
serverThread.join(TimeUnit.SECONDS.toMillis(EXIT_FLUSH_SERVER_WAIT_SECONDS));
|
||||
} catch (InterruptedException interrupted) {
|
||||
Thread.currentThread().interrupt();
|
||||
return;
|
||||
}
|
||||
if (serverThread.isAlive()) {
|
||||
ModdedIrisLog.warn("Iris skipped its exit flush: the server thread is still stopping");
|
||||
return;
|
||||
}
|
||||
}
|
||||
finishPreviousServer("JVM exit");
|
||||
}
|
||||
|
||||
private static void closeStoppedServer() {
|
||||
Throwable failure = null;
|
||||
failure = runStopStage(failure, "world engines", ModdedWorldEngines::shutdown);
|
||||
failure = runStopStage(failure, "primary world router", ModdedPrimaryWorldRouter::clear);
|
||||
failure = runStopStage(failure, "dimension manager", ModdedDimensionManager::clear);
|
||||
@@ -226,10 +288,14 @@ public final class ModdedEngineBootstrap {
|
||||
spawnCaptureServer = null;
|
||||
initialSpawnWasDefault = false;
|
||||
});
|
||||
reportStopFailure("stopped", failure);
|
||||
}
|
||||
|
||||
private static void reportStopFailure(String phase, Throwable failure) {
|
||||
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.
|
||||
ModdedIrisLog.error("Iris modded shutdown completed with failures", failure);
|
||||
ModdedIrisLog.error("Iris modded shutdown ({}) completed with failures", phase, failure);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+3
@@ -25,6 +25,7 @@ import art.arcane.volmlib.nativelib.terrain.feature.NativeFeatureTable;
|
||||
import art.arcane.volmlib.nativelib.minecraft26_2.modded.NativeModdedImportedFeatures;
|
||||
|
||||
import art.arcane.iris.generation.runtime.Engine;
|
||||
import art.arcane.iris.generation.runtime.GenerationFailures;
|
||||
import art.arcane.iris.generation.runtime.GenerationSessionLease;
|
||||
import art.arcane.iris.structure.nativegen.NativeFeatureGenerationPolicy;
|
||||
import art.arcane.iris.structure.nativegen.IrisImportedFeatureControl;
|
||||
@@ -163,6 +164,7 @@ final class ModdedImportedFeatureStage implements NativeModdedGeneratorPolicy.Fe
|
||||
try {
|
||||
control = NativeFeatureGenerationPolicy.control(engine);
|
||||
} catch (RuntimeException error) {
|
||||
GenerationFailures.rethrowEngineFailure(error);
|
||||
ModdedIrisLog.error("Iris could not read importedFeatures for this dimension; features off: {}",
|
||||
error.toString(), error);
|
||||
markInert(runtimeIdentity, generation);
|
||||
@@ -177,6 +179,7 @@ final class ModdedImportedFeatureStage implements NativeModdedGeneratorPolicy.Fe
|
||||
IrisContext.Scope ignored = IrisContext.open(engine, lease.sessionId(), null)) {
|
||||
built = nativeFeatures.buildTable(new ModdedImportedFeaturePolicy(engine), control, generation);
|
||||
} catch (Throwable error) {
|
||||
GenerationFailures.rethrowEngineFailure(error);
|
||||
ModdedIrisLog.error("Iris importedFeatures is off for {}: feature table construction failed: {}",
|
||||
dimensionKey(engine), error.toString(), error);
|
||||
markInert(runtimeIdentity, generation);
|
||||
|
||||
+1
@@ -16,6 +16,7 @@ public final class ModdedLifecycleCallbacks {
|
||||
ModdedEngineBootstrap::start,
|
||||
ModdedEngineBootstrap::serverStarted,
|
||||
ModdedEngineBootstrap::stop,
|
||||
ModdedEngineBootstrap::stopped,
|
||||
ModdedEngineBootstrap::levelLoaded,
|
||||
ModdedEngineBootstrap::levelUnloaded,
|
||||
ModdedProtocolHandler::onPlayerJoin,
|
||||
|
||||
@@ -40,29 +40,31 @@ import java.util.function.BooleanSupplier;
|
||||
* references and is safe on a dedicated server.
|
||||
*/
|
||||
public final class ModdedMixinAudit {
|
||||
private static final String ENTITY_CONFIG = "volmlib.entity.mixins.json";
|
||||
private static final String CLIENT_CONFIG = "volmlib.client.mixins.json";
|
||||
private static final AtomicBoolean AUDITED = new AtomicBoolean(false);
|
||||
|
||||
private static final List<ExpectedMixin> EXPECTED = List.of(
|
||||
new ExpectedMixin("EntityPersistenceMixin", "entity",
|
||||
static final List<ExpectedMixin> EXPECTED = List.of(
|
||||
new ExpectedMixin(ENTITY_CONFIG, "EntityPersistenceMixin",
|
||||
NativeMixinTarget.ENTITY, "iris$applyGeneratedPersistence",
|
||||
false, NativeMixinFlags::entityPersistenceRan),
|
||||
new ExpectedMixin("LivingEntityLootMixin", "entity",
|
||||
new ExpectedMixin(ENTITY_CONFIG, "LivingEntityLootMixin",
|
||||
NativeMixinTarget.LIVING_ENTITY, "iris$replaceBaseLoot",
|
||||
false, NativeMixinFlags::livingEntityLootRan),
|
||||
new ExpectedMixin("MobAwarenessMixin", "entity",
|
||||
new ExpectedMixin(ENTITY_CONFIG, "MobAwarenessMixin",
|
||||
NativeMixinTarget.MOB, "iris$tickUnawareMob",
|
||||
false, NativeMixinFlags::mobAwarenessRan),
|
||||
new ExpectedMixin("StructureTemplatePaletteConcurrencyMixin", "common",
|
||||
new ExpectedMixin(ENTITY_CONFIG, "StructureTemplatePaletteConcurrencyMixin",
|
||||
NativeMixinTarget.STRUCTURE_PALETTE,
|
||||
"iris$installConcurrentBlockCache",
|
||||
false, NativeMixinFlags::structureTemplatePaletteRan),
|
||||
new ExpectedMixin("NativeWorldOpenFlowsMixin", "client",
|
||||
new ExpectedMixin(CLIENT_CONFIG, "NativeWorldOpenFlowsMixin",
|
||||
NativeMixinTarget.WORLD_OPEN_FLOWS,
|
||||
"iris$openWorldCheckWorldStemCompatibility",
|
||||
"volmlib$openWorldCheckWorldStemCompatibility",
|
||||
true, NativeMixinFlags::worldOpenFlowsRan),
|
||||
new ExpectedMixin("NativeWorldTypeEntryMixin", "client",
|
||||
new ExpectedMixin(CLIENT_CONFIG, "NativeWorldTypeEntryMixin",
|
||||
NativeMixinTarget.WORLD_TYPE_ENTRY,
|
||||
"iris$describePreset",
|
||||
"volmlib$describePreset",
|
||||
true, NativeMixinFlags::worldTypeEntryRan));
|
||||
|
||||
private ModdedMixinAudit() {
|
||||
@@ -90,7 +92,7 @@ public final class ModdedMixinAudit {
|
||||
if (isApplied(expected)) {
|
||||
applied.add(expected.mixinName() + (expected.ran().getAsBoolean() ? "" : " (not yet exercised)"));
|
||||
} else {
|
||||
missing.add(expected.config() + '/' + expected.mixinName() + " -> " + expected.targetClass());
|
||||
missing.add(expected.config() + '/' + expected.mixinName() + " -> " + expected.targetClass().className());
|
||||
}
|
||||
}
|
||||
if (missing.isEmpty()) {
|
||||
@@ -119,7 +121,7 @@ public final class ModdedMixinAudit {
|
||||
}
|
||||
}
|
||||
|
||||
private record ExpectedMixin(String mixinName, String config, NativeMixinTarget targetClass, String handlerMethod,
|
||||
boolean clientOnly, BooleanSupplier ran) {
|
||||
record ExpectedMixin(String config, String mixinName, NativeMixinTarget targetClass, String handlerMethod,
|
||||
boolean clientOnly, BooleanSupplier ran) {
|
||||
}
|
||||
}
|
||||
@@ -195,7 +195,8 @@ public final class ModdedScheduler implements PlatformScheduler {
|
||||
mainQueue.clear();
|
||||
delayedQueue.clear();
|
||||
mainThread = null;
|
||||
// Shutdown stage that always runs (ModdedEngineBootstrap.stop): release the level snapshot with it.
|
||||
// Runs from ModdedEngineBootstrap.stopped, or its fallback at the next server start or JVM exit when the
|
||||
// loader skipped it: release the level snapshot with it.
|
||||
ModdedServerLevels.forget();
|
||||
}
|
||||
|
||||
|
||||
@@ -426,9 +426,9 @@ public final class ModdedWorldEngines {
|
||||
Engine engine = entry.getValue();
|
||||
try {
|
||||
// Latch the generator's unloading flag BEFORE closing (unbindEngine sets it,
|
||||
// then evicts): chunk-system drain work running after this stage would
|
||||
// otherwise see a closed engine and silently rebuild a fresh engine + Mantle
|
||||
// that no teardown stage ever closes, writing plates after the final save.
|
||||
// then evicts): late chunk work would otherwise see a closed engine and
|
||||
// silently rebuild a fresh engine + Mantle that no teardown stage ever
|
||||
// closes, writing plates after the final save.
|
||||
IrisModdedChunkGenerator generator = NativeWorldGenerators.find(level, IrisModdedChunkGenerator.class);
|
||||
if (generator != null) {
|
||||
generator.unbindEngine(level);
|
||||
|
||||
+36
-4
@@ -60,12 +60,16 @@ import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.CompletionException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import art.arcane.volmlib.util.localization.MessageArgument;
|
||||
|
||||
public final class IrisModdedCommands {
|
||||
private static final long DOWNLOAD_SHUTDOWN_POLL_SECONDS = 15L;
|
||||
// The first chunk of a cold dimension also loads the pack and binds the engine; the deadline only bounds a
|
||||
// destination whose chunk never completes (a refusing generator or a removed dimension).
|
||||
private static final long TELEPORT_DEADLINE_SECONDS = 120L;
|
||||
private static final Object DOWNLOAD_MONITOR = new Object();
|
||||
|
||||
static final SuggestionProvider<NativeCommandSource> PACK_NAMES = ModdedCommandSuggestions.PACK_NAMES;
|
||||
@@ -133,7 +137,7 @@ public final class IrisModdedCommands {
|
||||
NativeModdedServer server = source.server();
|
||||
CompletableFuture<Boolean> teleport = NativeWorldTeleport.teleport(player, new NativeWorldTeleport.Destination(
|
||||
server, ModdedDimensionManager.level(server, dimensionId), 8.5D, Double.MIN_VALUE, 8.5D,
|
||||
System.nanoTime() + TimeUnit.SECONDS.toNanos(10L)));
|
||||
System.nanoTime() + TimeUnit.SECONDS.toNanos(TELEPORT_DEADLINE_SECONDS)));
|
||||
teleport.whenComplete((success, failure) -> {
|
||||
if (Boolean.TRUE.equals(success) && failure == null) {
|
||||
return;
|
||||
@@ -142,14 +146,42 @@ public final class IrisModdedCommands {
|
||||
ModdedIrisLog.error("Iris teleport into '{}' failed for {}",
|
||||
dimensionId, player.id(), failure);
|
||||
}
|
||||
server.execute(() -> fail(source, IrisLanguage.plain(
|
||||
ModdedCommandMessages.IRIS_MODDED_COMMANDS_TELEPORT_FAILED_DIMENSION_IS_NOT_LOADED,
|
||||
MessageArgument.untrusted("dimensionId", dimensionId))));
|
||||
server.execute(() -> fail(source, teleportFailure(dimensionId, player.name(), failure,
|
||||
ModdedDimensionManager.level(server, dimensionId) != null, server.player(player.id()) != null)));
|
||||
});
|
||||
ok(source, IrisLanguage.plain(ModdedCommandMessages.IRIS_MODDED_COMMANDS_TELEPORTING, MessageArgument.untrusted("value", player.name()), MessageArgument.untrusted("dimensionId", dimensionId)));
|
||||
return 1;
|
||||
}
|
||||
|
||||
static String teleportFailure(String dimensionId, String playerName, Throwable failure,
|
||||
boolean dimensionLoaded, boolean playerOnline) {
|
||||
if (failure != null) {
|
||||
return IrisLanguage.plain(ModdedCommandMessages.IRIS_MODDED_COMMANDS_TELEPORT_FAILED_REASON,
|
||||
MessageArgument.untrusted("dimensionId", dimensionId),
|
||||
MessageArgument.untrusted("reason", failureReason(failure)));
|
||||
}
|
||||
if (!dimensionLoaded) {
|
||||
return IrisLanguage.plain(ModdedCommandMessages.IRIS_MODDED_COMMANDS_TELEPORT_FAILED_DIMENSION_IS_NOT_LOADED,
|
||||
MessageArgument.untrusted("dimensionId", dimensionId));
|
||||
}
|
||||
if (!playerOnline) {
|
||||
return IrisLanguage.plain(ModdedCommandMessages.IRIS_MODDED_COMMANDS_TELEPORT_CANCELLED_PLAYER_OFFLINE,
|
||||
MessageArgument.untrusted("dimensionId", dimensionId),
|
||||
MessageArgument.untrusted("value", playerName));
|
||||
}
|
||||
return IrisLanguage.plain(ModdedCommandMessages.IRIS_MODDED_COMMANDS_TELEPORT_REFUSED,
|
||||
MessageArgument.untrusted("dimensionId", dimensionId));
|
||||
}
|
||||
|
||||
private static String failureReason(Throwable failure) {
|
||||
Throwable cause = failure;
|
||||
while (cause instanceof CompletionException && cause.getCause() != null) {
|
||||
cause = cause.getCause();
|
||||
}
|
||||
String message = cause.getMessage();
|
||||
return message == null || message.isBlank() ? cause.getClass().getSimpleName() : message;
|
||||
}
|
||||
|
||||
static int evacuate(NativeCommandSource source, NativeWorld target) {
|
||||
NativeModdedServer server = source.server();
|
||||
NativeWorld level = target != null ? target : source.world();
|
||||
|
||||
+15
@@ -71,6 +71,18 @@ public final class ModdedCommandMessages {
|
||||
"iris.modded.irismoddedcommands.teleport_failed_dimension_is_not_loaded",
|
||||
"Teleport failed: dimension " + "{dimensionId}" + " is not loaded."
|
||||
);
|
||||
public static final TextKey IRIS_MODDED_COMMANDS_TELEPORT_FAILED_REASON = TextKey.of(
|
||||
"iris.modded.irismoddedcommands.teleport_failed_reason",
|
||||
"Teleport into " + "{dimensionId}" + " failed: " + "{reason}"
|
||||
);
|
||||
public static final TextKey IRIS_MODDED_COMMANDS_TELEPORT_CANCELLED_PLAYER_OFFLINE = TextKey.of(
|
||||
"iris.modded.irismoddedcommands.teleport_cancelled_player_offline",
|
||||
"Teleport into " + "{dimensionId}" + " cancelled: " + "{value}" + " is no longer online."
|
||||
);
|
||||
public static final TextKey IRIS_MODDED_COMMANDS_TELEPORT_REFUSED = TextKey.of(
|
||||
"iris.modded.irismoddedcommands.teleport_refused",
|
||||
"Teleport into " + "{dimensionId}" + " was refused by the server."
|
||||
);
|
||||
public static final TextKey IRIS_MODDED_COMMANDS_TELEPORTING = TextKey.of(
|
||||
"iris.modded.irismoddedcommands.teleporting",
|
||||
"Teleporting " + "{value}" + " to " + "{dimensionId}" + "..."
|
||||
@@ -1266,6 +1278,9 @@ public final class ModdedCommandMessages {
|
||||
IRIS_MODDED_COMMANDS_CONSOLE_MUST_NAME_PLAYER_IRIS_TP_DIMENSION_PLAYER,
|
||||
IRIS_MODDED_COMMANDS_IS_NOT_GENERATED_BY_IRIS,
|
||||
IRIS_MODDED_COMMANDS_TELEPORT_FAILED_DIMENSION_IS_NOT_LOADED,
|
||||
IRIS_MODDED_COMMANDS_TELEPORT_FAILED_REASON,
|
||||
IRIS_MODDED_COMMANDS_TELEPORT_CANCELLED_PLAYER_OFFLINE,
|
||||
IRIS_MODDED_COMMANDS_TELEPORT_REFUSED,
|
||||
IRIS_MODDED_COMMANDS_TELEPORTING,
|
||||
IRIS_MODDED_COMMANDS_IS_NOT_GENERATED_BY_IRIS_2,
|
||||
IRIS_MODDED_COMMANDS_CANNOT_EVACUATE_PRIMARY_WORLD_THERE_IS_NOWHERE_SEND_PLAYERS,
|
||||
|
||||
+61
-9
@@ -30,6 +30,7 @@ import art.arcane.iris.studio.view.PregeneratorJob;
|
||||
import art.arcane.iris.integration.Identifier;
|
||||
import art.arcane.iris.generation.cache.Cache;
|
||||
import art.arcane.iris.generation.runtime.Engine;
|
||||
import art.arcane.iris.generation.runtime.GenerationFailures;
|
||||
import art.arcane.iris.generation.block.TileData;
|
||||
import art.arcane.iris.modded.IrisModdedChunkGenerator;
|
||||
import art.arcane.iris.modded.ModdedBlockResolution;
|
||||
@@ -38,6 +39,7 @@ import art.arcane.iris.modded.ModdedTileData;
|
||||
import art.arcane.iris.modded.api.ModdedCustomContentRegistry;
|
||||
import art.arcane.volmlib.nativelib.minecraft26_2.modded.NativeBlockPlacement;
|
||||
import art.arcane.iris.spi.IrisLogging;
|
||||
import art.arcane.iris.world.history.SavedBiomeUnavailableException;
|
||||
import art.arcane.iris.world.storage.matter.TileWrapper;
|
||||
import art.arcane.volmlib.util.mantle.flag.MantleFlag;
|
||||
import art.arcane.volmlib.util.mantle.runtime.Mantle;
|
||||
@@ -47,6 +49,7 @@ import art.arcane.volmlib.util.matter.MatterCavern;
|
||||
import art.arcane.volmlib.util.matter.MatterUpdate;
|
||||
import art.arcane.volmlib.util.scheduling.PrecisionStopwatch;
|
||||
import art.arcane.volmlib.nativelib.minecraft26_2.modded.NativeModdedServer;
|
||||
import it.unimi.dsi.fastutil.ints.IntArrayList;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
@@ -149,7 +152,7 @@ public final class ModdedChunkUpdateService implements ModdedTickableService {
|
||||
int centerZ = player.z() >> 4;
|
||||
for (int dx = -PLAYER_CHUNK_RADIUS; dx <= PLAYER_CHUNK_RADIUS; dx++) {
|
||||
for (int dz = -PLAYER_CHUNK_RADIUS; dz <= PLAYER_CHUNK_RADIUS; dz++) {
|
||||
updateChunk(engine, level, centerX + dx, centerZ + dz);
|
||||
updatePassChunk(engine, level, centerX + dx, centerZ + dz);
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -157,15 +160,29 @@ public final class ModdedChunkUpdateService implements ModdedTickableService {
|
||||
|
||||
private void updateForcedChunks(Engine engine, NativeWorld level) {
|
||||
new NativeWorldMaintenance(level).forEachForcedChunk(chunkKey ->
|
||||
updateChunk(engine, level, (int) chunkKey, (int) (chunkKey >> 32)));
|
||||
updatePassChunk(engine, level, (int) chunkKey, (int) (chunkKey >> 32)));
|
||||
}
|
||||
|
||||
public void updateRegeneratedChunk(Engine engine, NativeWorld level, int chunkX, int chunkZ) {
|
||||
updateChunk(engine, level, chunkX, chunkZ, false);
|
||||
}
|
||||
|
||||
private void updateChunk(Engine engine, NativeWorld level, int chunkX, int chunkZ) {
|
||||
updateChunk(engine, level, chunkX, chunkZ, true);
|
||||
/**
|
||||
* One chunk of a level pass. A failed chunk keeps its flags and retries on a later pass without stopping the
|
||||
* chunks after it; only a closed engine ends the level's pass.
|
||||
*/
|
||||
private void updatePassChunk(Engine engine, NativeWorld level, int chunkX, int chunkZ) {
|
||||
try {
|
||||
updateChunk(engine, level, chunkX, chunkZ, true);
|
||||
} catch (Throwable failure) {
|
||||
if (engine.isClosed() || engine.getMantle().getMantle().isClosed()) {
|
||||
throw failure;
|
||||
}
|
||||
if (IrisLogging.errorOnce("chunk-update:" + level.name() + ":" + chunkX + "," + chunkZ + ":" + failure,
|
||||
"Iris chunk update failed at " + chunkX + "," + chunkZ + " in " + level.name() + ", retrying each pass: " + failure)) {
|
||||
IrisLogging.reportError(failure);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void updateChunk(Engine engine, NativeWorld level, int chunkX, int chunkZ, boolean requireNeighbors) {
|
||||
@@ -195,6 +212,11 @@ public final class ModdedChunkUpdateService implements ModdedTickableService {
|
||||
chunk.raiseFlagUnchecked(MantleFlag.CUSTOM, () -> runCustomPass(engine, level, chunkX, chunkZ, chunk));
|
||||
chunk.raiseFlagUnchecked(MantleFlag.UPDATE, () -> runUpdatePass(engine, level, chunkX, chunkZ, chunk));
|
||||
});
|
||||
} catch (SavedBiomeUnavailableException unavailable) {
|
||||
if (!unavailable.isLoading() || unavailable.getSuppressed().length != 0) {
|
||||
throw unavailable;
|
||||
}
|
||||
IrisLogging.debug("Iris chunk update deferred at " + chunkX + "," + chunkZ + ": saved biomes loading");
|
||||
} finally {
|
||||
chunk.release();
|
||||
}
|
||||
@@ -315,15 +337,40 @@ public final class ModdedChunkUpdateService implements ModdedTickableService {
|
||||
}
|
||||
}
|
||||
|
||||
chunk.iterate(MatterUpdate.class, (Integer x, Integer yf, Integer z, MatterUpdate v) -> {
|
||||
if (v != null && v.isUpdate()) {
|
||||
update(engine, level, x, yf + minHeight, z, baseX, baseZ, chunk);
|
||||
}
|
||||
});
|
||||
IntArrayList completed = new IntArrayList();
|
||||
try {
|
||||
chunk.iterate(MatterUpdate.class, (Integer x, Integer yf, Integer z, MatterUpdate v) -> {
|
||||
if (v != null && v.isUpdate()) {
|
||||
update(engine, level, x, yf + minHeight, z, baseX, baseZ, chunk);
|
||||
completed.add((yf << 8) | ((x & 15) << 4) | (z & 15));
|
||||
}
|
||||
});
|
||||
} catch (RuntimeException | Error failure) {
|
||||
forgetCompletedUpdates(chunk, completed, failure);
|
||||
throw failure;
|
||||
}
|
||||
chunk.deleteSlices(MatterUpdate.class);
|
||||
engine.getMetrics().getUpdates().put(stopwatch.getMilliseconds());
|
||||
}
|
||||
|
||||
private static void forgetCompletedUpdates(MantleChunk<Matter> chunk, IntArrayList completed, Throwable failure) {
|
||||
try {
|
||||
for (int index = 0; index < completed.size(); index++) {
|
||||
int position = completed.getInt(index);
|
||||
int y = position >>> 8;
|
||||
Matter section = chunk.get(y >> 4);
|
||||
if (section != null && section.hasSlice(MatterUpdate.class)) {
|
||||
section.<MatterUpdate>getSlice(MatterUpdate.class)
|
||||
.set((position >> 4) & 15, y & 15, position & 15, null);
|
||||
}
|
||||
}
|
||||
} catch (RuntimeException | Error cleanupFailure) {
|
||||
if (cleanupFailure != failure) {
|
||||
failure.addSuppressed(cleanupFailure);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void update(Engine engine, NativeWorld level, int x, int y, int z, int baseX, int baseZ, MantleChunk<Matter> chunk) {
|
||||
if (y < level.minHeight() || y >= level.maxHeight() - 1) {
|
||||
return;
|
||||
@@ -337,7 +384,12 @@ public final class ModdedChunkUpdateService implements ModdedTickableService {
|
||||
}
|
||||
try {
|
||||
ModdedLootApplier.apply(engine, level, pos, state, chunk);
|
||||
} catch (SavedBiomeUnavailableException unavailable) {
|
||||
GenerationFailures.rethrowEngineFailure(unavailable);
|
||||
IrisLogging.warnOnce("chunk-update-loot:" + level.name() + ":" + unavailable.getMessage(),
|
||||
"Iris left the container at " + pos + " in " + level.name() + " without loot: " + unavailable.getMessage());
|
||||
} catch (Throwable e) {
|
||||
GenerationFailures.rethrowEngineFailure(e);
|
||||
IrisLogging.reportError(e);
|
||||
}
|
||||
} else {
|
||||
|
||||
Reference in new issue
Block a user