mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-10-26 22:10:18 +00:00
Basin hydrology
- The route-and-reject hydrology system and its probes are removed - Hydrology is configured under dimension hydrology and per biome, region and dimension hydrologyPolicy - riverPolicy and the old hydrology config types are gone - Grid kernels cover priority flood, D8 routing, flow accumulation, basin extraction, breaching and stream burning - A basin spills at its pour point and an interior bump can never be the spill - Region plans cover 4096-block regions on a 64-block grid for major rivers and lakes - Window plans cover 512-block windows on an 8-block grid for reaches and ponds - Plans are cached in memory, persisted per world under iris/hydrology and keyed by pack, seed, world height and settings - Only the main runtime of an engine persists plans and stores swap on hotload and default runtime change - Demand rasters outrank prefetch and stopping pregen withdraws its queued prefetch - Ponds come from natural basins, seeded cups and chained cups with strictly falling levels - A pond level never exceeds its rim plus maxFill or the end grade of any inflow - Pond outlet loops are opened at their lowest pond - Reaches split at junctions, pond outlets and 256-block grid lines - Reaches meander, fit the valley, grade to a running minimum and smooth with Chaikin passes - Drops of two blocks or more get a lip and one-block corner steps become dry rim ledges - Cascades and waterfalls write falling fluid from the mantle at level eight - Inflows into region lakes end at the lake level - River mouths and water beside the sea sit at the sea surface - The per-chunk raster gives terrain height, water head, role, tier, fluid, cave ceiling and shore distance - WaterRole codes are stable and exposed to packs through the WATER_ROLE engine stream - WATER_HEAD replaces the old river engine streams and an unknown stream name fails the pack load - Banks take SMOOTH, LINEAR, CONCAVE, TERRACED or CLIFF profiles - Containment berms grow as tall as the water needs - Columns within two blocks of water stay solid and keep no Terrain3D overhang - Seam dams hold water where new chunks meet chunks generated earlier - The transition blend skips hydrology-owned columns - Caves stay at least two blocks below river and pond beds - The painter is the only writer of still water - Objects cannot open water, falls or bank ground and solid objects may stand in water - On-water objects need fluid at their anchor column - Floating child islands stay out of channels - Sea inlets run inland from cliff coasts at sea level and end in a chamber pool - Inlet passages are sealed against carvers, subterrain features and the post pass - Cave pools settle into carved lows, keep one fluid and stay out of inlet space - Pool and inlet cells use their own cave actions in mantle data - Water vegetation lists sit on tiers, ponds and hydrologyPolicy with SEA_FLOOR, SEA_SURFACE and SHORE_LINE parts - Pack validation compiles hydrology settings and rejects empty or water-holding palettes - Chunk generation fails closed when its 3x3 water rasters are not planned - /iris find covers stream, river, major river, cascade, waterfall, mouth, pond, lake, dam, sea inlet and cave pool - Generation history records the new feature types and drops old hydrology entries - The studio map draws water from rasters up close and from plans when zoomed out - Pregen prepares the current region and one region ahead - hydrologyProbe, riverTransectProbe, hydrologyBlockProbe, seaInletProbe and cavePoolProbe report containment, rises, seams, tick conversion and missing surface - Test forks run with a 2 GB heap, a 60-minute timeout and the Mockito agent loaded explicitly - Generation with hydrology off is unchanged
This commit is contained in:
1 parent
2d211cf106
commit
db0f98d103
710 files changed
+41791
-91883
No files matched your search
+2
-20
@@ -25,7 +25,7 @@ import art.arcane.iris.generation.runtime.Engine;
|
||||
import art.arcane.iris.structure.placement.IrisStructureLocator;
|
||||
import art.arcane.iris.structure.nativegen.NativeStructureGenerationPolicy;
|
||||
import art.arcane.iris.structure.placement.StructureReachability;
|
||||
import art.arcane.iris.generation.hydrology.HydrologyFeatureQuery;
|
||||
import art.arcane.iris.generation.hydrology.query.HydrologyFeatureType;
|
||||
import art.arcane.iris.world.history.GenerationFindCatalog;
|
||||
import art.arcane.iris.generation.biome.IrisBiome;
|
||||
import art.arcane.iris.structure.nativegen.IrisNativeStructureDecision;
|
||||
@@ -61,7 +61,7 @@ final class ModdedCommandSuggestions {
|
||||
static final SuggestionProvider<NativeCommandSource> OBJECT_KEYS = (CommandContext<NativeCommandSource> context, SuggestionsBuilder builder) -> suggestObjectKeys(context, builder);
|
||||
static final SuggestionProvider<NativeCommandSource> STRUCTURE_KEYS = (CommandContext<NativeCommandSource> context, SuggestionsBuilder builder) -> suggestStructureKeys(context, builder);
|
||||
static final SuggestionProvider<NativeCommandSource> POI_TYPES = (CommandContext<NativeCommandSource> context, SuggestionsBuilder builder) -> NativeCommandRegistration.suggest(List.of("buried_treasure"), builder);
|
||||
static final SuggestionProvider<NativeCommandSource> HYDROLOGY_TYPES = (CommandContext<NativeCommandSource> context, SuggestionsBuilder builder) -> suggestHydrologyTypes(context, builder);
|
||||
static final SuggestionProvider<NativeCommandSource> HYDROLOGY_TYPES = (CommandContext<NativeCommandSource> context, SuggestionsBuilder builder) -> NativeCommandRegistration.suggest(HydrologyFeatureType.keys(), builder);
|
||||
static final SuggestionProvider<NativeCommandSource> PACK_NAMES = (CommandContext<NativeCommandSource> context, SuggestionsBuilder builder) -> suggestPackNames(context, builder);
|
||||
static final SuggestionProvider<NativeCommandSource> DIMENSION_NAMES = (CommandContext<NativeCommandSource> context, SuggestionsBuilder builder) -> suggestDimensionNames(context, builder);
|
||||
|
||||
@@ -87,24 +87,6 @@ final class ModdedCommandSuggestions {
|
||||
return builder.buildFuture();
|
||||
}
|
||||
|
||||
private static CompletableFuture<Suggestions> suggestHydrologyTypes(
|
||||
CommandContext<NativeCommandSource> context,
|
||||
SuggestionsBuilder builder
|
||||
) {
|
||||
ModdedCommandFeedback.tab(context.getSource());
|
||||
try {
|
||||
Engine engine = IrisModdedCommands.engineFor(context.getSource().world());
|
||||
if (engine != null && engine.getComplex().getHydrologyRuntime() != null) {
|
||||
return NativeCommandRegistration.suggest(
|
||||
engine.getComplex().getHydrologyRuntime().featureQueryKeys(), builder);
|
||||
}
|
||||
} catch (Throwable error) {
|
||||
warnTabFailure("hydrology types", context.getSource(), error);
|
||||
}
|
||||
return NativeCommandRegistration.suggest(
|
||||
HydrologyFeatureQuery.suggestions(List.of()), builder);
|
||||
}
|
||||
|
||||
static Set<String> reachableBiomeKeys(Engine engine) {
|
||||
return reachableBiomeKeys(GenerationFindCatalog.biomes(engine));
|
||||
}
|
||||
|
||||
+13
-22
@@ -39,8 +39,7 @@ import art.arcane.iris.structure.placement.StructureReachability;
|
||||
import art.arcane.iris.generation.runtime.WrongEngineBroException;
|
||||
import art.arcane.iris.world.history.GenerationSemanticQueries;
|
||||
import art.arcane.iris.world.history.GenerationFindCatalog;
|
||||
import art.arcane.iris.generation.hydrology.HydrologyFeatureQuery;
|
||||
import art.arcane.iris.generation.hydrology.runtime.IrisHydrologyRuntime;
|
||||
import art.arcane.iris.generation.hydrology.query.HydrologyFeatureType;
|
||||
import art.arcane.iris.generation.biome.IrisBiome;
|
||||
import art.arcane.iris.structure.nativegen.IrisNativeStructureDecision;
|
||||
import art.arcane.iris.generation.terrain.IrisRegion;
|
||||
@@ -143,39 +142,31 @@ final class ModdedLocateCommands {
|
||||
IrisModdedCommands.fail(source, "This dimension is not generated by Iris.");
|
||||
return 0;
|
||||
}
|
||||
IrisHydrologyRuntime runtime = engine.getComplex().getHydrologyRuntime();
|
||||
HydrologyFeatureQuery query;
|
||||
HydrologyFeatureType featureType;
|
||||
try {
|
||||
query = HydrologyFeatureQuery.parse(type);
|
||||
featureType = HydrologyFeatureType.parse(type);
|
||||
} catch (IllegalArgumentException error) {
|
||||
IrisModdedCommands.fail(source, error.getMessage());
|
||||
return 0;
|
||||
}
|
||||
String label = featureType.key().replace('_', ' ');
|
||||
int originX = player.blockX();
|
||||
int originZ = player.blockZ();
|
||||
int requestedDistance = runtime == null
|
||||
? 8192
|
||||
: Math.min(8192, runtime.settings().routing().tileSize() * 15);
|
||||
int maximumDistance = runtime == null
|
||||
? requestedDistance
|
||||
: runtime.maximumFeatureSearchDistance(originX, originZ, requestedDistance);
|
||||
int maximumDistance = GenerationSemanticQueries.RIVER_SEARCH_DISTANCE;
|
||||
NativeModdedServer server = source.server();
|
||||
IrisModdedCommands.ok(source, "Searching accepted hydrology plans for " + type + "...");
|
||||
IrisModdedCommands.ok(source, GenerationSemanticQueries.riverSearchMessage(featureType, maximumDistance));
|
||||
MultiBurst.burst.completeValueAsync(() -> GenerationSemanticQueries.nearestRiver(
|
||||
engine, query, originX, originZ, maximumDistance,
|
||||
(int visited) -> server.execute(() -> IrisModdedCommands.ok(source,
|
||||
"Searched " + visited + " hydrology tiles for " + type + "..."))).orElse(null))
|
||||
engine, featureType, originX, originZ, maximumDistance).orElse(null))
|
||||
.whenComplete((GenerationSemanticQueries.RiverResult feature, Throwable error) -> server.execute(() -> {
|
||||
if (error != null) {
|
||||
ModdedIrisLog.error("Hydrology locate failed for {}", type, error);
|
||||
IrisModdedCommands.fail(source,
|
||||
"Could not locate " + type + " hydrology: " + error.getClass().getSimpleName());
|
||||
Throwable cause = error instanceof CompletionException && error.getCause() != null ? error.getCause() : error;
|
||||
ModdedIrisLog.error("Hydrology locate failed for {}", featureType.key(), cause);
|
||||
IrisModdedCommands.fail(source, "Could not search for " + label + ": " + cause.getClass().getSimpleName()
|
||||
+ (cause.getMessage() == null ? "" : " - " + cause.getMessage()));
|
||||
return;
|
||||
}
|
||||
if (feature == null) {
|
||||
IrisModdedCommands.fail(source,
|
||||
"No accepted " + type + " hydrology feature found within "
|
||||
+ maximumDistance + " blocks.");
|
||||
IrisModdedCommands.fail(source, GenerationSemanticQueries.riverNotFoundMessage(featureType, maximumDistance));
|
||||
return;
|
||||
}
|
||||
teleportToStructure(
|
||||
@@ -185,7 +176,7 @@ final class ModdedLocateCommands {
|
||||
feature.x(),
|
||||
feature.y() + 2,
|
||||
feature.z(),
|
||||
type + " hydrology feature"
|
||||
label
|
||||
);
|
||||
}));
|
||||
return 1;
|
||||
|
||||
+14
@@ -23,6 +23,7 @@ import art.arcane.volmlib.nativelib.terrain.NativePregenRuntime;
|
||||
import art.arcane.volmlib.nativelib.terrain.NativePregenRuntime.ChunkLoad;
|
||||
import art.arcane.iris.configuration.IrisSettings;
|
||||
import art.arcane.iris.world.pregen.MantleHeapPressure;
|
||||
import art.arcane.iris.world.pregen.PregenHydrology;
|
||||
import art.arcane.iris.world.pregen.PregenListener;
|
||||
import art.arcane.iris.world.pregen.PregenMantleBackpressure;
|
||||
import art.arcane.iris.world.pregen.PregeneratorMethod;
|
||||
@@ -30,6 +31,7 @@ import art.arcane.iris.generation.runtime.Engine;
|
||||
import art.arcane.iris.modded.ModdedGenPool;
|
||||
import art.arcane.volmlib.util.mantle.runtime.Mantle;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
@@ -71,6 +73,8 @@ public final class ModdedPregenMethod implements PregeneratorMethod {
|
||||
private final PregenMantleBackpressure backpressure;
|
||||
private final PauseWhenEmptyGuard pauseGuard;
|
||||
private final LongSupplier clock;
|
||||
private final AtomicBoolean closing = new AtomicBoolean();
|
||||
private final PregenHydrology hydrology;
|
||||
|
||||
public ModdedPregenMethod(Configuration configuration) {
|
||||
this(configuration, System::nanoTime);
|
||||
@@ -80,6 +84,7 @@ public final class ModdedPregenMethod implements PregeneratorMethod {
|
||||
this.clock = Objects.requireNonNull(clock);
|
||||
runtime = configuration.runtime();
|
||||
engine = configuration.engine();
|
||||
hydrology = new PregenHydrology(() -> engine);
|
||||
sync = configuration.sync();
|
||||
pauseGuard = new PauseWhenEmptyGuard(runtime);
|
||||
IrisSettings.IrisSettingsPregen pregen = IrisSettings.get().getPregen();
|
||||
@@ -121,6 +126,8 @@ public final class ModdedPregenMethod implements PregeneratorMethod {
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
closing.set(true);
|
||||
hydrology.close();
|
||||
try {
|
||||
awaitPendingChunks();
|
||||
ModdedIrisLog.info("Iris modded pregen closing: dim={} completed={} pending={} peakInFlight={} finalLimit={}",
|
||||
@@ -341,6 +348,13 @@ public final class ModdedPregenMethod implements PregeneratorMethod {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRegionsAhead(List<long[]> regions) {
|
||||
if (!closing.get()) {
|
||||
hydrology.prepare(regions);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMethod(int x, int z) {
|
||||
return "Modded";
|
||||
|
||||
+149
@@ -0,0 +1,149 @@
|
||||
package art.arcane.iris.modded.command;
|
||||
|
||||
import art.arcane.iris.generation.hydrology.engine.HydrologyRuntime;
|
||||
import art.arcane.iris.generation.hydrology.query.HydrologyFeatureQuery;
|
||||
import art.arcane.iris.generation.hydrology.query.HydrologyFeatureRef;
|
||||
import art.arcane.iris.generation.hydrology.query.HydrologyFeatureType;
|
||||
import art.arcane.iris.generation.runtime.Engine;
|
||||
import art.arcane.iris.generation.runtime.IrisComplex;
|
||||
import art.arcane.iris.generation.terrain.IrisDimension;
|
||||
import art.arcane.iris.world.history.GenerationSemanticQueries;
|
||||
import art.arcane.volmlib.nativelib.minecraft26_2.modded.NativeCommandSource;
|
||||
import art.arcane.volmlib.nativelib.minecraft26_2.modded.NativeModdedServer;
|
||||
import art.arcane.volmlib.nativelib.minecraft26_2.modded.NativeProtocolPlayer;
|
||||
import art.arcane.volmlib.nativelib.minecraft26_2.modded.NativeStructureQueries;
|
||||
import art.arcane.volmlib.nativelib.terrain.NativeWorld;
|
||||
import org.junit.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.MockedConstruction;
|
||||
import org.mockito.MockedStatic;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyDouble;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.doAnswer;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.mockConstruction;
|
||||
import static org.mockito.Mockito.mockStatic;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
public class ModdedLocateRiverTest {
|
||||
private static final int DISTANCE = GenerationSemanticQueries.RIVER_SEARCH_DISTANCE;
|
||||
|
||||
@Test
|
||||
public void unknownTypeFailsBeforeSearching() {
|
||||
Fixture fixture = new Fixture();
|
||||
try (MockedStatic<IrisModdedCommands> commands = fixture.commands()) {
|
||||
assertEquals(0, ModdedLocateCommands.gotoRiver(fixture.source, "ocean_trench"));
|
||||
|
||||
commands.verify(() -> IrisModdedCommands.fail(eq(fixture.source), anyString()));
|
||||
commands.verify(() -> IrisModdedCommands.ok(eq(fixture.source), anyString()), never());
|
||||
verify(fixture.server, never()).execute(any());
|
||||
verify(fixture.runtime, never()).nearestFeature(anyInt(), anyInt(), any(), anyInt(), any());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void missingFeatureReportsNotFound() throws Exception {
|
||||
Fixture fixture = new Fixture();
|
||||
when(fixture.runtime.nearestFeature(anyInt(), anyInt(), any(), anyInt(), any())).thenReturn(Optional.empty());
|
||||
try (MockedStatic<IrisModdedCommands> commands = fixture.commands()) {
|
||||
assertEquals(1, ModdedLocateCommands.gotoRiver(fixture.source, "river"));
|
||||
fixture.finishOnServerThread();
|
||||
|
||||
commands.verify(() -> IrisModdedCommands.fail(fixture.source, "No river found within " + DISTANCE + " blocks."));
|
||||
verify(fixture.player, never()).teleport(any(), anyDouble(), anyDouble(), anyDouble());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void plannerFailureIsReportedAsAnErrorNotAsNotFound() throws Exception {
|
||||
Fixture fixture = new Fixture();
|
||||
when(fixture.runtime.nearestFeature(anyInt(), anyInt(), any(), anyInt(), any()))
|
||||
.thenThrow(new IllegalStateException("window planner exploded"));
|
||||
try (MockedStatic<IrisModdedCommands> commands = fixture.commands()) {
|
||||
assertEquals(1, ModdedLocateCommands.gotoRiver(fixture.source, "major_river"));
|
||||
fixture.finishOnServerThread();
|
||||
|
||||
commands.verify(() -> IrisModdedCommands.fail(fixture.source,
|
||||
"Could not search for major river: IllegalStateException - window planner exploded"));
|
||||
commands.verify(() -> IrisModdedCommands.fail(eq(fixture.source), eq("No major river found within " + DISTANCE + " blocks.")),
|
||||
never());
|
||||
verify(fixture.player, never()).teleport(any(), anyDouble(), anyDouble(), anyDouble());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void foundFeatureTeleportsAboveItFromThePlayersPosition() throws Exception {
|
||||
Fixture fixture = new Fixture();
|
||||
when(fixture.runtime.nearestFeature(anyInt(), anyInt(), any(), anyInt(), any()))
|
||||
.thenReturn(Optional.of(new HydrologyFeatureRef(9L, HydrologyFeatureType.WATERFALL, 400, 130, -250)));
|
||||
when(fixture.player.teleport(fixture.world, 400.5D, 68D, -249.5D)).thenReturn(true);
|
||||
try (MockedStatic<IrisModdedCommands> commands = fixture.commands();
|
||||
MockedConstruction<NativeStructureQueries> queries = mockConstruction(NativeStructureQueries.class)) {
|
||||
assertEquals(1, ModdedLocateCommands.gotoRiver(fixture.source, "waterfall"));
|
||||
fixture.finishOnServerThread();
|
||||
|
||||
ArgumentCaptor<HydrologyFeatureQuery> query = ArgumentCaptor.forClass(HydrologyFeatureQuery.class);
|
||||
verify(fixture.runtime).nearestFeature(eq(120), eq(-75), query.capture(), eq(DISTANCE), any());
|
||||
assertEquals(HydrologyFeatureQuery.of(HydrologyFeatureType.WATERFALL), query.getValue());
|
||||
verify(queries.constructed().getFirst()).loadChunk(400 >> 4, -250 >> 4);
|
||||
verify(fixture.player).teleport(fixture.world, 400.5D, 68D, -249.5D);
|
||||
commands.verify(() -> IrisModdedCommands.ok(eq(fixture.source), anyString()), times(2));
|
||||
commands.verify(() -> IrisModdedCommands.fail(eq(fixture.source), anyString()), never());
|
||||
}
|
||||
}
|
||||
|
||||
private static final class Fixture {
|
||||
private final NativeCommandSource source = mock(NativeCommandSource.class);
|
||||
private final NativeWorld world = mock(NativeWorld.class);
|
||||
private final NativeProtocolPlayer player = mock(NativeProtocolPlayer.class);
|
||||
private final NativeModdedServer server = mock(NativeModdedServer.class);
|
||||
private final Engine engine = mock(Engine.class);
|
||||
private final HydrologyRuntime runtime = mock(HydrologyRuntime.class);
|
||||
private final CompletableFuture<Runnable> completion = new CompletableFuture<>();
|
||||
|
||||
private Fixture() {
|
||||
IrisComplex complex = mock(IrisComplex.class);
|
||||
IrisDimension dimension = mock(IrisDimension.class);
|
||||
when(source.player()).thenReturn(player);
|
||||
when(source.world()).thenReturn(world);
|
||||
when(source.server()).thenReturn(server);
|
||||
when(player.blockX()).thenReturn(120);
|
||||
when(player.blockZ()).thenReturn(-75);
|
||||
when(player.connected()).thenReturn(true);
|
||||
when(player.inWorld(world)).thenReturn(true);
|
||||
when(world.minHeight()).thenReturn(-64);
|
||||
when(world.maxHeight()).thenReturn(320);
|
||||
when(engine.getComplex()).thenReturn(complex);
|
||||
when(engine.getDimension()).thenReturn(dimension);
|
||||
when(dimension.getMinHeight()).thenReturn(-64);
|
||||
when(complex.getHydrologyRuntime()).thenReturn(runtime);
|
||||
when(runtime.enabled()).thenReturn(true);
|
||||
doAnswer(invocation -> {
|
||||
completion.complete(invocation.getArgument(0));
|
||||
return null;
|
||||
}).when(server).execute(any());
|
||||
}
|
||||
|
||||
private MockedStatic<IrisModdedCommands> commands() {
|
||||
MockedStatic<IrisModdedCommands> commands = mockStatic(IrisModdedCommands.class);
|
||||
commands.when(() -> IrisModdedCommands.engineFor(world)).thenReturn(engine);
|
||||
return commands;
|
||||
}
|
||||
|
||||
private void finishOnServerThread() throws Exception {
|
||||
completion.get(10L, TimeUnit.SECONDS).run();
|
||||
}
|
||||
}
|
||||
}
|
||||
+24
@@ -1,6 +1,7 @@
|
||||
package art.arcane.iris.modded.command;
|
||||
|
||||
import art.arcane.iris.configuration.IrisSettings;
|
||||
import art.arcane.iris.generation.hydrology.engine.HydrologyPregeneration;
|
||||
import art.arcane.iris.generation.runtime.Engine;
|
||||
import art.arcane.iris.modded.ModdedGenPool;
|
||||
import art.arcane.iris.world.pregen.MantleHeapPressure;
|
||||
@@ -11,8 +12,10 @@ import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.InOrder;
|
||||
import org.mockito.MockedStatic;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
@@ -31,9 +34,11 @@ import static org.junit.Assert.assertThrows;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyList;
|
||||
import static org.mockito.Mockito.doAnswer;
|
||||
import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
|
||||
import static org.mockito.Mockito.doThrow;
|
||||
import static org.mockito.Mockito.inOrder;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.mockStatic;
|
||||
import static org.mockito.Mockito.never;
|
||||
@@ -89,6 +94,25 @@ public class ModdedPregenMethodLifecycleTest {
|
||||
verify(runtime, never()).execute(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void closeWithdrawsQueuedHydrologyPrefetchAndRefusesLaterAnnouncements() {
|
||||
HydrologyPregeneration epoch = mock(HydrologyPregeneration.class);
|
||||
when(engine.getComplex().getHydrologyRuntime().openPregeneration()).thenReturn(epoch);
|
||||
when(runtime.isServerThread()).thenReturn(true);
|
||||
assertTrue(method.deferFinalSaveToServerThread());
|
||||
List<long[]> ahead = List.of(new long[]{0L, 0L}, new long[]{1L, 0L});
|
||||
|
||||
method.onRegionsAhead(ahead);
|
||||
method.close();
|
||||
method.onRegionsAhead(ahead);
|
||||
|
||||
InOrder order = inOrder(epoch);
|
||||
order.verify(epoch).prepare(ahead);
|
||||
order.verify(epoch).close();
|
||||
verify(epoch, times(1)).prepare(anyList());
|
||||
method.completeDeferredFinalSave();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void asynchronousCallerCannotClaimTheShutdownSave() {
|
||||
assertFalse(method.deferFinalSaveToServerThread());
|
||||
|
||||
Reference in new issue
Block a user