mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-10-26 22:10:18 +00:00
Improved saved world startup
- Startup holds generation admission through recovery and runtime attachment - Prepared startup state removes the second recovery scan before generator readiness - Recovery batches allocated chunks by region with reusable snapshot buffers - Region decoding uses up to four workers within processor and heap limits - Oversized regions and external chunks use bounded direct reads - Status parsing validates chunk coordinates without constructing the full NBT tree - Region snapshots reject changed files, allocation headers and truncated payloads - Native zlib decompression uses jlibdeflate with a Java inflater fallback - Chunk readers reuse region handles and decompression buffers during recovery and boundary capture - Semantic shards reuse validated summaries with invalidation on mutation - Semantic key validation avoids regular expression allocation - Saved biome indexes use buffered sequential reads with reusable CRC state - Pack inspection reads validated manifest and pack state without opening terrain indexes - Datapack compilation reuses pack inspection within each operation - Kernel staging avoids duplicate pack hashing when no activation is pending - Pack copying and directory synchronization use up to four available processors - Boundary publication batches directory synchronization around durable catalog publication - Configured world reconciliation reuses ready runtimes after identity and saved-state checks - Explicit world loads retain the full validation path - Regression tests cover startup admission, recovery, malformed data, resource limits, publication and reconciliation
This commit is contained in:
1 parent
30c533feaf
commit
98ddb6644d
35 files changed
+3301
-197
No files matched your search
+85
-1
@@ -27,6 +27,11 @@ import art.arcane.iris.world.lifecycle.BukkitWorldConfiguration;
|
||||
import art.arcane.iris.world.lifecycle.LifecycleOperationCoordinator;
|
||||
import art.arcane.iris.platform.bukkit.nms.INMS;
|
||||
import art.arcane.iris.generation.terrain.IrisDimension;
|
||||
import art.arcane.iris.generation.runtime.IrisEngine;
|
||||
import art.arcane.iris.platform.generation.BukkitChunkGenerator;
|
||||
import art.arcane.iris.world.history.GenerationEpoch;
|
||||
import art.arcane.iris.world.history.GenerationHistory;
|
||||
import art.arcane.iris.world.history.GenerationHistoryRuntimeRouter;
|
||||
import art.arcane.iris.platform.bukkit.BukkitEnvironment;
|
||||
import art.arcane.iris.localization.C;
|
||||
import art.arcane.iris.platform.bootstrap.ServerProperties;
|
||||
@@ -182,7 +187,7 @@ public final class BukkitWorldReconciler {
|
||||
});
|
||||
}
|
||||
|
||||
private CompletableFuture<LoadResult> loadConfiguredWorld(
|
||||
CompletableFuture<LoadResult> loadConfiguredWorld(
|
||||
File configurationFile,
|
||||
String configuredWorldName,
|
||||
NamespacedKey worldKey,
|
||||
@@ -196,6 +201,28 @@ public final class BukkitWorldReconciler {
|
||||
return CompletableFuture.completedFuture(LoadResult.busy(worldKey, failure));
|
||||
}
|
||||
|
||||
try {
|
||||
IrisStartupValidation.requireWorldCreationReady();
|
||||
Optional<World> loaded = backend.loadedWorld(worldKey);
|
||||
if (loaded.isPresent() && backend.validateLoadedRuntime(
|
||||
loaded.get(), worldKey, dimension, seed) == LoadedRuntimeStatus.READY) {
|
||||
ReconciliationResult result = verifyLoadedWorld(worldKey, loaded.get(), true);
|
||||
BukkitWorldConfiguration.Registration registration = BukkitWorldConfiguration.Registration.UNCHANGED;
|
||||
if (result.succeeded()) {
|
||||
try {
|
||||
registration = BukkitWorldConfiguration.register(configurationFile, configuredWorldName, dimension, seed);
|
||||
} catch (Throwable failure) {
|
||||
lease.close();
|
||||
return CompletableFuture.completedFuture(LoadResult.configurationFailure(worldKey, failure));
|
||||
}
|
||||
}
|
||||
lease.close();
|
||||
return CompletableFuture.completedFuture(new LoadResult(result, registration, false, true, null));
|
||||
}
|
||||
} catch (Throwable failure) {
|
||||
lease.close();
|
||||
return CompletableFuture.completedFuture(LoadResult.dimensionFailure(worldKey, failure));
|
||||
}
|
||||
return loadWithLease(configurationFile, configuredWorldName, worldKey, dimension, seed, lease);
|
||||
}
|
||||
|
||||
@@ -434,11 +461,19 @@ public final class BukkitWorldReconciler {
|
||||
|
||||
boolean isIrisWorld(World world);
|
||||
|
||||
LoadedRuntimeStatus validateLoadedRuntime(World world, NamespacedKey worldKey, String dimension, Long seed)
|
||||
throws IOException;
|
||||
|
||||
DimensionResolution resolveDimension(NamespacedKey worldKey);
|
||||
|
||||
void requireDimensionLoadable(NamespacedKey worldKey, String dimension);
|
||||
}
|
||||
|
||||
enum LoadedRuntimeStatus {
|
||||
READY,
|
||||
UNAVAILABLE
|
||||
}
|
||||
|
||||
public enum ReconciliationStatus {
|
||||
LOADED,
|
||||
ALREADY_LOADED,
|
||||
@@ -717,6 +752,55 @@ public final class BukkitWorldReconciler {
|
||||
return IrisToolbelt.isIrisWorld(world);
|
||||
}
|
||||
|
||||
@Override
|
||||
public LoadedRuntimeStatus validateLoadedRuntime(
|
||||
World world, NamespacedKey worldKey, String dimension, Long seed) throws IOException {
|
||||
if (!(world.getGenerator() instanceof BukkitChunkGenerator generator)
|
||||
|| generator.isStudio()
|
||||
|| generator.isClosing()
|
||||
|| generator.getInitializationFailure() != null
|
||||
|| !generator.getStartupReady().isDone()
|
||||
|| generator.getStartupReady().isCompletedExceptionally()
|
||||
|| !(generator.getEngine() instanceof IrisEngine engine)
|
||||
|| engine.isClosed() || engine.isClosing()) {
|
||||
return LoadedRuntimeStatus.UNAVAILABLE;
|
||||
}
|
||||
GenerationHistory history = generator.getGenerationHistory();
|
||||
GenerationHistoryRuntimeRouter router = engine.getGenerationHistoryRuntimeRouter().orElse(null);
|
||||
if (history == null || router == null || router.history() != history) {
|
||||
return LoadedRuntimeStatus.UNAVAILABLE;
|
||||
}
|
||||
GenerationEpoch active = history.activeEpoch();
|
||||
if (!worldKey.equals(WorldIdentity.key(world))
|
||||
|| !worldKey.toString().equals(engine.getWorld().identity())
|
||||
|| !dimension.equals(generator.getDimensionKey())
|
||||
|| !dimension.equals(active.dimensionContract().dimensionKey())
|
||||
|| !dimension.equals(engine.getDimension().getLoadKey())
|
||||
|| seed != null && seed.longValue() != active.worldSeed()) {
|
||||
throw new IOException("Loaded Iris runtime does not match configured world " + worldKey + ".");
|
||||
}
|
||||
Path expectedRoot = WorldCreatorCompat.persistentDimensionRoot(worldKey).toPath().toAbsolutePath().normalize();
|
||||
File levelRoot = IrisWorldStorage.levelRoot();
|
||||
Path storedRoot = IrisWorldStorage.requireFrozenDimensionRoot(
|
||||
Bukkit.getWorldContainer(), levelRoot, configuredWorldName(worldKey), worldKey)
|
||||
.toPath().toAbsolutePath().normalize();
|
||||
if (!expectedRoot.equals(storedRoot) || !storedRoot.equals(history.paths().dimensionRoot())
|
||||
|| !storedRoot.equals(engine.getWorld().worldFolder().toPath().toAbsolutePath().normalize())
|
||||
|| engine.getData().isClosed()) {
|
||||
throw new IOException("Loaded Iris runtime storage does not match world " + worldKey + ".");
|
||||
}
|
||||
GenerationHistory.PackInspection inspection = GenerationHistory.inspectPacks(storedRoot);
|
||||
if (!active.equals(inspection.manifest().activeEpoch())
|
||||
|| !history.activeActivation().equals(inspection.manifest().activeActivation())
|
||||
|| !history.pendingActivation().equals(inspection.manifest().pendingActivation())
|
||||
|| !inspection.activePackRoot().equals(engine.getData().getDataFolder().toPath().toAbsolutePath().normalize())
|
||||
|| !Files.isDirectory(storedRoot.resolve("iris/generation/semantics"), LinkOption.NOFOLLOW_LINKS)
|
||||
|| !Files.isRegularFile(storedRoot.resolve("iris/generation/semantics/index.isix"), LinkOption.NOFOLLOW_LINKS)) {
|
||||
throw new IOException("Loaded Iris runtime history does not match saved world " + worldKey + ".");
|
||||
}
|
||||
return LoadedRuntimeStatus.READY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DimensionResolution resolveDimension(NamespacedKey worldKey) {
|
||||
File dimensionsDirectory = new File(snapshotRoot(worldKey), "dimensions");
|
||||
|
||||
+9
-12
@@ -383,8 +383,9 @@ public final class IrisWorldGeneratorResolver {
|
||||
IrisDimension dimension = null;
|
||||
if (dimensionRoot != null) {
|
||||
long worldSeed = requireStoredWorldSeed(configuredWorldName, dimensionRoot);
|
||||
GenerationHistory history = requireGenerationHistory(dimensionRoot, id, worldSeed);
|
||||
File pack = requireActivePack(history);
|
||||
LoadedHistory loaded = requireGenerationHistory(dimensionRoot, id, worldSeed);
|
||||
GenerationHistory history = loaded.history();
|
||||
File pack = loaded.activePack();
|
||||
requireSnapshotLoadable(pack);
|
||||
dimension = requireHistoricalDimension(history, pack, id);
|
||||
}
|
||||
@@ -615,8 +616,9 @@ public final class IrisWorldGeneratorResolver {
|
||||
+ worldKey + ".");
|
||||
}
|
||||
long worldSeed = requireStoredWorldSeed(worldName, dimensionRoot);
|
||||
GenerationHistory history = requireGenerationHistory(dimensionRoot, id, worldSeed);
|
||||
File snapshotRoot = requireActivePack(history);
|
||||
LoadedHistory loaded = requireGenerationHistory(dimensionRoot, id, worldSeed);
|
||||
GenerationHistory history = loaded.history();
|
||||
File snapshotRoot = loaded.activePack();
|
||||
PackValidationResult validation = validateSnapshot(snapshotRoot);
|
||||
if (hasPendingExternalContent(validation, IrisServices.getOrNull(ExternalDataSVC.class))) {
|
||||
return deferFrozenWorldGenerator(worldName, worldKey, dimensionRoot, snapshotRoot, id, history);
|
||||
@@ -705,7 +707,7 @@ public final class IrisWorldGeneratorResolver {
|
||||
}
|
||||
}
|
||||
|
||||
private static GenerationHistory requireGenerationHistory(
|
||||
private static LoadedHistory requireGenerationHistory(
|
||||
File dimensionRoot,
|
||||
String dimensionKey,
|
||||
long worldSeed
|
||||
@@ -724,7 +726,7 @@ public final class IrisWorldGeneratorResolver {
|
||||
history.requireRegistryDefinitions(
|
||||
GenerationRegistryContractFactory.captureRequiredDefinitions(retainedContracts)
|
||||
);
|
||||
return history;
|
||||
return new LoadedHistory(history, history.paths().packRoot(history.activeEpoch().epochId()).toFile());
|
||||
} catch (IOException failure) {
|
||||
throw new IllegalStateException("Iris generation history is unusable at " + root + ".", failure);
|
||||
}
|
||||
@@ -825,12 +827,7 @@ public final class IrisWorldGeneratorResolver {
|
||||
);
|
||||
}
|
||||
|
||||
private static File requireActivePack(GenerationHistory history) {
|
||||
try {
|
||||
return history.activePackRoot().toFile();
|
||||
} catch (IOException failure) {
|
||||
throw new IllegalStateException("Active Iris generation pack is unusable.", failure);
|
||||
}
|
||||
private record LoadedHistory(GenerationHistory history, File activePack) {
|
||||
}
|
||||
|
||||
private static long requireStoredWorldSeed(String worldName, File dimensionRoot) {
|
||||
|
||||
Reference in new issue
Block a user