mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-10-11 19:14:11 +00:00
Fixed spawn prefetch startup
- Spawn hydrology checks existing chunks asynchronously without generating them - Prefetch admission rejects closed or replaced generation runtimes - Studio entry retains tracked hydrology preparation - Admitted planning failures retain their stacktraces during shutdown - Regression coverage checks startup admission and delayed completion
This commit is contained in:
@@ -37,6 +37,7 @@ import art.arcane.iris.core.runtime.jigsaw.JigsawStudioActivation;
|
||||
import art.arcane.iris.core.runtime.jigsaw.JigsawStudioSession;
|
||||
import art.arcane.iris.core.service.StudioSVC;
|
||||
import art.arcane.iris.core.tools.IrisToolbelt;
|
||||
import art.arcane.iris.engine.IrisComplex;
|
||||
import art.arcane.iris.engine.IrisEngine;
|
||||
import art.arcane.iris.engine.data.cache.AtomicCache;
|
||||
import art.arcane.iris.engine.data.chunk.TerrainChunk;
|
||||
@@ -52,6 +53,7 @@ import art.arcane.iris.engine.history.GenerationAdmission;
|
||||
import art.arcane.iris.engine.history.GenerationHistoryRuntimeRouter;
|
||||
import art.arcane.iris.engine.history.IrisBoundarySignatureSampler;
|
||||
import art.arcane.iris.engine.history.TransitionGenerationPlan;
|
||||
import art.arcane.iris.engine.hydrology.runtime.IrisHydrologyRuntime;
|
||||
import art.arcane.iris.engine.object.IrisDimensionContractException;
|
||||
import art.arcane.iris.engine.object.IrisDimension;
|
||||
import art.arcane.iris.engine.object.IrisDimensionRuntimeContract;
|
||||
@@ -101,6 +103,7 @@ import java.util.Locale;
|
||||
import java.util.Objects;
|
||||
import java.util.Random;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CompletionException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
@@ -258,41 +261,73 @@ public class BukkitChunkGenerator extends ChunkGenerator implements PlatformChun
|
||||
}
|
||||
|
||||
/** Starts nearest-first hydrology planning around a new world's initial spawn. */
|
||||
private void prefetchSpawnHydrology(Engine engine, World world) {
|
||||
private CompletableFuture<Void> prefetchSpawnHydrology(Engine engine, World world) {
|
||||
if (usesFlatStudioTerrain()) {
|
||||
return;
|
||||
return CompletableFuture.completedFuture(null);
|
||||
}
|
||||
if (engine.getComplex() == null || engine.getComplex().getHydrologyRuntime() == null) {
|
||||
return;
|
||||
IrisComplex complex = engine.getComplex();
|
||||
IrisHydrologyRuntime hydrology = complex == null ? null : complex.getHydrologyRuntime();
|
||||
if (hydrology == null) {
|
||||
return CompletableFuture.completedFuture(null);
|
||||
}
|
||||
Location spawn = getInitialSpawnLocation(world);
|
||||
int spawnX = spawn.getBlockX();
|
||||
int spawnZ = spawn.getBlockZ();
|
||||
if (world.isChunkGenerated(spawnX >> 4, spawnZ >> 4)) {
|
||||
return;
|
||||
}
|
||||
if (studio && engine.getDimension().getStudioMode() == StudioMode.NORMAL
|
||||
&& engine instanceof IrisEngine irisEngine) {
|
||||
irisEngine.startStudioEntryHydrology(spawnX, spawnZ);
|
||||
return;
|
||||
}
|
||||
int reach = Math.max(16, hydrologyTileSize() / 2);
|
||||
engine.getComplex().getHydrologyRuntime().prefetchArea(
|
||||
spawnX - reach,
|
||||
spawnZ - reach,
|
||||
spawnX + reach - 1,
|
||||
spawnZ + reach - 1,
|
||||
spawnX,
|
||||
spawnZ
|
||||
);
|
||||
return requestChunkAsync(world, spawn.getBlockX() >> 4, spawn.getBlockZ() >> 4, false)
|
||||
.exceptionally(failure -> {
|
||||
if (closing || this.engine != engine || engine.isClosing() || engine.isClosed()) {
|
||||
return null;
|
||||
}
|
||||
throw new CompletionException(failure);
|
||||
})
|
||||
.thenAcceptAsync(chunk -> {
|
||||
if (chunk == null) {
|
||||
startSpawnHydrology(engine, hydrology, spawn);
|
||||
}
|
||||
})
|
||||
.whenComplete((ignored, failure) -> {
|
||||
if (failure != null) {
|
||||
IrisLogging.reportError("Failed to prefetch spawn hydrology for " + world.getName(), failure);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private int hydrologyTileSize() {
|
||||
Engine activeEngine = engine;
|
||||
if (activeEngine == null || activeEngine.getComplex() == null || activeEngine.getComplex().getHydrologyRuntime() == null) {
|
||||
return 0;
|
||||
private void startSpawnHydrology(Engine expectedEngine, IrisHydrologyRuntime hydrology, Location spawn) {
|
||||
if (closing) {
|
||||
return;
|
||||
}
|
||||
GenerationStagePermit permit;
|
||||
try {
|
||||
permit = acquireGenerationStage("spawn_hydrology_prefetch");
|
||||
} catch (IllegalStateException failure) {
|
||||
if (closing && failure.getCause() == null) {
|
||||
return;
|
||||
}
|
||||
throw failure;
|
||||
}
|
||||
try (permit) {
|
||||
if (engine != expectedEngine || expectedEngine.isClosing() || expectedEngine.isClosed() || usesFlatStudioTerrain()) {
|
||||
return;
|
||||
}
|
||||
IrisComplex complex = expectedEngine.getComplex();
|
||||
if (complex == null || complex.getHydrologyRuntime() != hydrology) {
|
||||
return;
|
||||
}
|
||||
int spawnX = spawn.getBlockX();
|
||||
int spawnZ = spawn.getBlockZ();
|
||||
if (studio && expectedEngine.getDimension().getStudioMode() == StudioMode.NORMAL
|
||||
&& expectedEngine instanceof IrisEngine irisEngine) {
|
||||
irisEngine.startStudioEntryHydrology(spawnX, spawnZ);
|
||||
return;
|
||||
}
|
||||
int reach = Math.max(16, hydrology.settings().routing().tileSize() / 2);
|
||||
hydrology.prefetchArea(
|
||||
spawnX - reach,
|
||||
spawnZ - reach,
|
||||
spawnX + reach - 1,
|
||||
spawnZ + reach - 1,
|
||||
spawnX,
|
||||
spawnZ
|
||||
);
|
||||
}
|
||||
return activeEngine.getComplex().getHydrologyRuntime().settings().routing().tileSize();
|
||||
}
|
||||
|
||||
private void updateSpawnLocation(World world) {
|
||||
|
||||
+20
-7
@@ -3,7 +3,9 @@ package art.arcane.iris.engine.platform;
|
||||
import art.arcane.iris.engine.IrisEngine;
|
||||
import art.arcane.iris.engine.IrisComplex;
|
||||
import art.arcane.iris.engine.framework.Engine;
|
||||
import art.arcane.iris.engine.hydrology.HydrologyPlannerSettings;
|
||||
import art.arcane.iris.engine.hydrology.runtime.IrisHydrologyRuntime;
|
||||
import org.bukkit.Chunk;
|
||||
import org.bukkit.World;
|
||||
import art.arcane.iris.engine.object.StudioMode;
|
||||
import art.arcane.iris.engine.object.IrisDimension;
|
||||
@@ -14,11 +16,14 @@ import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.Mockito.CALLS_REAL_METHODS;
|
||||
import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
@@ -35,23 +40,31 @@ public class BukkitChunkGeneratorInitializationModeTest {
|
||||
Field studio = BukkitChunkGenerator.class.getDeclaredField("studio");
|
||||
studio.setAccessible(true);
|
||||
studio.setBoolean(generator, true);
|
||||
Field loadLock = BukkitChunkGenerator.class.getDeclaredField("loadLock");
|
||||
loadLock.setAccessible(true);
|
||||
loadLock.set(generator, new BukkitChunkGenerator.GenerationStageGate(2, generator::isClosing));
|
||||
doReturn(false).when(generator).usesFlatStudioTerrain();
|
||||
World world = mock(World.class);
|
||||
when(world.getMinHeight()).thenReturn(-64);
|
||||
when(world.getMaxHeight()).thenReturn(320);
|
||||
when(world.getChunkAtAsync(0, 0, false)).thenReturn(CompletableFuture.completedFuture(null));
|
||||
IrisEngine engine = mock(IrisEngine.class);
|
||||
generator.setEngine(engine);
|
||||
IrisDimension dimension = mock(IrisDimension.class);
|
||||
when(engine.getDimension()).thenReturn(dimension);
|
||||
when(dimension.getStudioMode()).thenReturn(StudioMode.NORMAL);
|
||||
IrisComplex complex = mock(IrisComplex.class);
|
||||
IrisHydrologyRuntime hydrology = mock(IrisHydrologyRuntime.class);
|
||||
HydrologyPlannerSettings settings = mock(HydrologyPlannerSettings.class, RETURNS_DEEP_STUBS);
|
||||
when(settings.routing().tileSize()).thenReturn(512);
|
||||
when(hydrology.settings()).thenReturn(settings);
|
||||
when(engine.getComplex()).thenReturn(complex);
|
||||
when(complex.getHydrologyRuntime()).thenReturn(hydrology);
|
||||
Method prefetch = BukkitChunkGenerator.class.getDeclaredMethod(
|
||||
"prefetchSpawnHydrology", Engine.class, World.class);
|
||||
prefetch.setAccessible(true);
|
||||
|
||||
prefetch.invoke(generator, engine, world);
|
||||
((CompletableFuture<?>) prefetch.invoke(generator, engine, world)).get(5L, TimeUnit.SECONDS);
|
||||
|
||||
verify(engine).startStudioEntryHydrology(0, 0);
|
||||
verifyNoInteractions(hydrology);
|
||||
@@ -60,21 +73,21 @@ public class BukkitChunkGeneratorInitializationModeTest {
|
||||
assertEquals(0.5D, generator.getInitialSpawnLocation(world).getZ(), 0D);
|
||||
|
||||
when(dimension.getStudioMode()).thenReturn(StudioMode.BIOME_BUFFET_1x1);
|
||||
prefetch.invoke(generator, engine, world);
|
||||
((CompletableFuture<?>) prefetch.invoke(generator, engine, world)).get(5L, TimeUnit.SECONDS);
|
||||
verify(engine, times(1)).startStudioEntryHydrology(0, 0);
|
||||
verify(hydrology).prefetchArea(anyInt(), anyInt(), anyInt(), anyInt(), anyInt(), anyInt());
|
||||
when(dimension.getStudioMode()).thenReturn(StudioMode.NORMAL);
|
||||
|
||||
IrisEngine authoring = mock(IrisEngine.class);
|
||||
doReturn(true).when(generator).usesFlatStudioTerrain();
|
||||
prefetch.invoke(generator, authoring, world);
|
||||
((CompletableFuture<?>) prefetch.invoke(generator, authoring, world)).get(5L, TimeUnit.SECONDS);
|
||||
verifyNoInteractions(authoring);
|
||||
|
||||
IrisEngine generated = mock(IrisEngine.class);
|
||||
when(generated.getComplex()).thenReturn(complex);
|
||||
doReturn(false).when(generator).usesFlatStudioTerrain();
|
||||
when(world.isChunkGenerated(0, 0)).thenReturn(true);
|
||||
prefetch.invoke(generator, generated, world);
|
||||
when(world.getChunkAtAsync(0, 0, false)).thenReturn(CompletableFuture.completedFuture(mock(Chunk.class)));
|
||||
((CompletableFuture<?>) prefetch.invoke(generator, generated, world)).get(5L, TimeUnit.SECONDS);
|
||||
verify(generated, never()).startStudioEntryHydrology(anyInt(), anyInt());
|
||||
}
|
||||
|
||||
@@ -99,8 +112,8 @@ public class BukkitChunkGeneratorInitializationModeTest {
|
||||
public void authoringStartupDoesNotPrefetchTerrainHydrology() throws IOException {
|
||||
String source = Files.readString(Path.of(
|
||||
"src/main/java/art/arcane/iris/engine/platform/BukkitChunkGenerator.java"));
|
||||
int start = source.indexOf("private void prefetchSpawnHydrology(");
|
||||
int end = source.indexOf("private int hydrologyTileSize()", start);
|
||||
int start = source.indexOf("private CompletableFuture<Void> prefetchSpawnHydrology(");
|
||||
int end = source.indexOf("private void updateSpawnLocation(", start);
|
||||
String prefetch = source.substring(start, end);
|
||||
|
||||
assertTrue(prefetch.indexOf("if (usesFlatStudioTerrain())") >= 0);
|
||||
|
||||
+237
@@ -0,0 +1,237 @@
|
||||
package art.arcane.iris.engine.platform;
|
||||
|
||||
import art.arcane.iris.engine.IrisComplex;
|
||||
import art.arcane.iris.engine.IrisEngine;
|
||||
import art.arcane.iris.engine.framework.Engine;
|
||||
import art.arcane.iris.engine.hydrology.HydrologyPlannerSettings;
|
||||
import art.arcane.iris.engine.hydrology.runtime.IrisHydrologyRuntime;
|
||||
import art.arcane.iris.engine.object.IrisDimension;
|
||||
import art.arcane.iris.engine.object.StudioMode;
|
||||
import org.bukkit.Chunk;
|
||||
import org.bukkit.World;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertSame;
|
||||
import static org.junit.Assert.assertThrows;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.CALLS_REAL_METHODS;
|
||||
import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
|
||||
import static org.mockito.Mockito.doAnswer;
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoInteractions;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
public class BukkitChunkGeneratorSpawnHydrologyTest {
|
||||
@Test
|
||||
public void startupWaitsAsynchronouslyWithoutHoldingGenerationAdmission() throws Exception {
|
||||
Fixture fixture = new Fixture();
|
||||
CompletableFuture<Void> prefetch = fixture.prefetch();
|
||||
|
||||
assertFalse(prefetch.isDone());
|
||||
assertEquals(2, fixture.gate.availablePermits());
|
||||
verifyNoInteractions(fixture.hydrology);
|
||||
verify(fixture.world).getChunkAtAsync(0, 0, false);
|
||||
verify(fixture.world, never()).isChunkGenerated(anyInt(), anyInt());
|
||||
verify(fixture.world, never()).getChunkAtAsync(anyInt(), anyInt(), eq(true));
|
||||
|
||||
fixture.lookup.complete(null);
|
||||
prefetch.get(5L, TimeUnit.SECONDS);
|
||||
|
||||
verify(fixture.hydrology).prefetchArea(-256, -256, 255, 255, 0, 0);
|
||||
assertEquals(2, fixture.gate.availablePermits());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void existingSpawnDoesNotPrefetchHydrology() throws Exception {
|
||||
Fixture fixture = new Fixture();
|
||||
CompletableFuture<Void> prefetch = fixture.prefetch();
|
||||
fixture.lookup.complete(mock(Chunk.class));
|
||||
prefetch.get(5L, TimeUnit.SECONDS);
|
||||
|
||||
verifyNoInteractions(fixture.hydrology);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lateLookupDoesNotEnterClosingGenerator() throws Exception {
|
||||
Fixture fixture = new Fixture();
|
||||
CompletableFuture<Void> prefetch = fixture.prefetch();
|
||||
setField(fixture.generator, "closing", true);
|
||||
fixture.lookup.complete(null);
|
||||
prefetch.get(5L, TimeUnit.SECONDS);
|
||||
|
||||
verifyNoInteractions(fixture.hydrology);
|
||||
assertEquals(2, fixture.gate.availablePermits());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void closeRejectsCompletionAlreadyWaitingForAdmission() throws Exception {
|
||||
Fixture fixture = new Fixture();
|
||||
CountDownLatch acquiring = new CountDownLatch(1);
|
||||
doAnswer(invocation -> {
|
||||
acquiring.countDown();
|
||||
return invocation.callRealMethod();
|
||||
}).when(fixture.generator).acquireGenerationStage(anyString());
|
||||
CompletableFuture<Void> prefetch = fixture.prefetch();
|
||||
fixture.gate.acquireExclusive();
|
||||
try {
|
||||
fixture.lookup.complete(null);
|
||||
assertTrue(acquiring.await(5L, TimeUnit.SECONDS));
|
||||
setField(fixture.generator, "closing", true);
|
||||
} finally {
|
||||
fixture.gate.releaseExclusive();
|
||||
}
|
||||
prefetch.get(5L, TimeUnit.SECONDS);
|
||||
|
||||
verifyNoInteractions(fixture.hydrology);
|
||||
assertEquals(2, fixture.gate.availablePermits());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lateLookupDoesNotEnterReplacementEngine() throws Exception {
|
||||
Fixture fixture = new Fixture();
|
||||
CompletableFuture<Void> prefetch = fixture.prefetch();
|
||||
fixture.generator.setEngine(mock(Engine.class));
|
||||
fixture.lookup.complete(null);
|
||||
prefetch.get(5L, TimeUnit.SECONDS);
|
||||
|
||||
verifyNoInteractions(fixture.hydrology);
|
||||
assertEquals(2, fixture.gate.availablePermits());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lateLookupDoesNotEnterSealedEngine() throws Exception {
|
||||
Fixture fixture = new Fixture();
|
||||
CompletableFuture<Void> prefetch = fixture.prefetch();
|
||||
when(fixture.engine.isClosing()).thenReturn(true);
|
||||
fixture.lookup.complete(null);
|
||||
prefetch.get(5L, TimeUnit.SECONDS);
|
||||
|
||||
verifyNoInteractions(fixture.hydrology);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lateLookupDoesNotEnterReplacementHydrology() throws Exception {
|
||||
Fixture fixture = new Fixture();
|
||||
CompletableFuture<Void> prefetch = fixture.prefetch();
|
||||
IrisHydrologyRuntime replacement = mock(IrisHydrologyRuntime.class);
|
||||
when(fixture.complex.getHydrologyRuntime()).thenReturn(replacement);
|
||||
fixture.lookup.complete(null);
|
||||
prefetch.get(5L, TimeUnit.SECONDS);
|
||||
|
||||
verifyNoInteractions(fixture.hydrology, replacement);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void normalStudioUsesTrackedEntryPreparation() throws Exception {
|
||||
Fixture fixture = new Fixture();
|
||||
setField(fixture.generator, "studio", true);
|
||||
CompletableFuture<Void> prefetch = fixture.prefetch();
|
||||
fixture.lookup.complete(null);
|
||||
prefetch.get(5L, TimeUnit.SECONDS);
|
||||
|
||||
verify(fixture.engine).startStudioEntryHydrology(0, 0);
|
||||
verifyNoInteractions(fixture.hydrology);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void failedLookupDoesNotStartHydrology() throws Exception {
|
||||
Fixture fixture = new Fixture();
|
||||
CompletableFuture<Void> prefetch = fixture.prefetch();
|
||||
IllegalStateException failure = new IllegalStateException("Native chunk lookup failed");
|
||||
fixture.lookup.completeExceptionally(failure);
|
||||
|
||||
ExecutionException result = assertThrows(ExecutionException.class,
|
||||
() -> prefetch.get(5L, TimeUnit.SECONDS));
|
||||
assertSame(failure, result.getCause());
|
||||
verifyNoInteractions(fixture.hydrology);
|
||||
assertEquals(2, fixture.gate.availablePermits());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lateLookupFailureDoesNotFailClosedGenerator() throws Exception {
|
||||
Fixture fixture = new Fixture();
|
||||
CompletableFuture<Void> prefetch = fixture.prefetch();
|
||||
setField(fixture.generator, "closing", true);
|
||||
fixture.lookup.completeExceptionally(new IllegalStateException("World unloaded"));
|
||||
prefetch.get(5L, TimeUnit.SECONDS);
|
||||
|
||||
verifyNoInteractions(fixture.hydrology);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void admittedPlanningFailureSurvivesConcurrentClose() throws Exception {
|
||||
Fixture fixture = new Fixture();
|
||||
IllegalStateException failure = new IllegalStateException("Planner scheduling failed");
|
||||
doAnswer(invocation -> {
|
||||
setField(fixture.generator, "closing", true);
|
||||
throw failure;
|
||||
}).when(fixture.hydrology).prefetchArea(anyInt(), anyInt(), anyInt(), anyInt(), anyInt(), anyInt());
|
||||
CompletableFuture<Void> prefetch = fixture.prefetch();
|
||||
fixture.lookup.complete(null);
|
||||
|
||||
ExecutionException result = assertThrows(ExecutionException.class,
|
||||
() -> prefetch.get(5L, TimeUnit.SECONDS));
|
||||
assertSame(failure, result.getCause());
|
||||
assertEquals(2, fixture.gate.availablePermits());
|
||||
}
|
||||
|
||||
private static void setField(BukkitChunkGenerator generator, String name, Object value)
|
||||
throws ReflectiveOperationException {
|
||||
Field field = BukkitChunkGenerator.class.getDeclaredField(name);
|
||||
field.setAccessible(true);
|
||||
field.set(generator, value);
|
||||
}
|
||||
|
||||
private static final class Fixture {
|
||||
private final BukkitChunkGenerator generator = mock(BukkitChunkGenerator.class, CALLS_REAL_METHODS);
|
||||
private final BukkitChunkGenerator.GenerationStageGate gate =
|
||||
new BukkitChunkGenerator.GenerationStageGate(2, generator::isClosing);
|
||||
private final IrisEngine engine = mock(IrisEngine.class);
|
||||
private final IrisComplex complex = mock(IrisComplex.class);
|
||||
private final IrisHydrologyRuntime hydrology = mock(IrisHydrologyRuntime.class);
|
||||
private final World world = mock(World.class);
|
||||
private final CompletableFuture<Chunk> lookup = new CompletableFuture<>();
|
||||
|
||||
private Fixture() throws ReflectiveOperationException {
|
||||
setField(generator, "loadLock", gate);
|
||||
generator.setEngine(engine);
|
||||
doReturn(false).when(generator).usesFlatStudioTerrain();
|
||||
when(engine.getComplex()).thenReturn(complex);
|
||||
when(complex.getHydrologyRuntime()).thenReturn(hydrology);
|
||||
HydrologyPlannerSettings settings = mock(HydrologyPlannerSettings.class, RETURNS_DEEP_STUBS);
|
||||
when(settings.routing().tileSize()).thenReturn(512);
|
||||
when(hydrology.settings()).thenReturn(settings);
|
||||
IrisDimension dimension = mock(IrisDimension.class);
|
||||
when(engine.getDimension()).thenReturn(dimension);
|
||||
when(dimension.getStudioMode()).thenReturn(StudioMode.NORMAL);
|
||||
when(world.getMinHeight()).thenReturn(-64);
|
||||
when(world.getMaxHeight()).thenReturn(320);
|
||||
when(world.getChunkAtAsync(0, 0, false)).thenReturn(lookup);
|
||||
when(world.isChunkGenerated(anyInt(), anyInt()))
|
||||
.thenThrow(new AssertionError("World initialization must not query chunk status synchronously"));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private CompletableFuture<Void> prefetch() throws ReflectiveOperationException {
|
||||
Method prefetch = BukkitChunkGenerator.class.getDeclaredMethod(
|
||||
"prefetchSpawnHydrology", Engine.class, World.class);
|
||||
prefetch.setAccessible(true);
|
||||
return (CompletableFuture<Void>) prefetch.invoke(generator, engine, world);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user