diff --git a/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedPregenMethod.java b/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedPregenMethod.java index 46080f526..11aa965f3 100644 --- a/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedPregenMethod.java +++ b/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedPregenMethod.java @@ -348,6 +348,11 @@ public final class ModdedPregenMethod implements PregeneratorMethod { return false; } + @Override + public void onRegionBounds(int minRegionX, int minRegionZ, int maxRegionX, int maxRegionZ) { + hydrology.bound(minRegionX, minRegionZ, maxRegionX, maxRegionZ); + } + @Override public void onRegionsAhead(List regions) { if (!closing.get()) { diff --git a/core/src/main/java/art/arcane/iris/generation/hydrology/engine/FineLatticeSource.java b/core/src/main/java/art/arcane/iris/generation/hydrology/engine/FineLatticeSource.java deleted file mode 100644 index 73db7475d..000000000 --- a/core/src/main/java/art/arcane/iris/generation/hydrology/engine/FineLatticeSource.java +++ /dev/null @@ -1,53 +0,0 @@ -package art.arcane.iris.generation.hydrology.engine; - -import art.arcane.iris.generation.hydrology.grid.HeightLattice; -import art.arcane.iris.generation.hydrology.grid.HydrologySampleBuffer; -import art.arcane.iris.generation.hydrology.grid.HydrologyTerrainSource; - -import java.util.Objects; - -final class FineLatticeSource implements HydrologyTerrainSource { - private final HeightLattice fine; - private final HydrologyTerrainSource terrain; - - FineLatticeSource(HeightLattice fine, HydrologyTerrainSource terrain) { - this.fine = Objects.requireNonNull(fine, "fine"); - this.terrain = Objects.requireNonNull(terrain, "terrain"); - } - - @Override - public void sample(int[] xs, int[] zs, HydrologySampleBuffer out, boolean baseOnly) { - if (baseOnly) { - throw new IllegalArgumentException("The fine lattice holds natural heights only"); - } - if (xs.length != zs.length) { - throw new IllegalArgumentException("Fine lattice read has " + xs.length + " x and " + zs.length + " z coordinates"); - } - int cell = fine.cell(); - int[] cellXs = new int[xs.length]; - int[] cellZs = new int[zs.length]; - for (int index = 0; index < xs.length; index++) { - if (Math.floorMod(xs[index], cell) != 0 || Math.floorMod(zs[index], cell) != 0) { - throw new IllegalArgumentException("Column " + xs[index] + "," + zs[index] + " is not a node of the " + cell + "-block fine lattice"); - } - cellXs[index] = Math.floorDiv(xs[index], cell); - cellZs[index] = Math.floorDiv(zs[index], cell); - } - fine.fill(cellXs, cellZs, out); - } - - @Override - public int seaLevel() { - return terrain.seaLevel(); - } - - @Override - public int minY() { - return terrain.minY(); - } - - @Override - public int maxY() { - return terrain.maxY(); - } -} diff --git a/core/src/main/java/art/arcane/iris/generation/hydrology/engine/HydrologyPlanners.java b/core/src/main/java/art/arcane/iris/generation/hydrology/engine/HydrologyPlanners.java index 7ff67fb26..88bea3a5e 100644 --- a/core/src/main/java/art/arcane/iris/generation/hydrology/engine/HydrologyPlanners.java +++ b/core/src/main/java/art/arcane/iris/generation/hydrology/engine/HydrologyPlanners.java @@ -11,23 +11,21 @@ import art.arcane.iris.generation.hydrology.water.ReachShaper; import java.util.Objects; -public record HydrologyPlanners(RegionPlanner regions, WindowPlanner windows, HeightLattice windowLattice) { +public record HydrologyPlanners(RegionPlanner regions, WindowPlanner windows, HeightLattice fineLattice) { public HydrologyPlanners { Objects.requireNonNull(regions, "regions"); Objects.requireNonNull(windows, "windows"); - Objects.requireNonNull(windowLattice, "windowLattice"); + Objects.requireNonNull(fineLattice, "fineLattice"); } public static HydrologyPlanners create(HydrologySettings settings, HydrologyTerrainSource source) { Objects.requireNonNull(settings, "settings"); Objects.requireNonNull(source, "source"); HeightLattice fine = new HeightLattice(source, WindowPlanner.FINE_CELL, false, HydrologyPlanCache.FINE_LATTICE_CELLS); - HeightLattice window = new HeightLattice(new FineLatticeSource(fine, source), WindowPlanner.CELL, false, - HydrologyPlanCache.WINDOW_LATTICE_CELLS); PondPlanner ponds = new PondPlanner(settings); ReachShaper shaper = new ReachShaper(settings); RegionPlanner regions = new RegionPlanner(settings, new HeightLattice(source, RegionPlanner.CELL, true, HydrologyPlanCache.REGION_LATTICE_CELLS), fine, ponds, shaper); - return new HydrologyPlanners(regions, new WindowPlanner(settings, window, fine, ponds, shaper), window); + return new HydrologyPlanners(regions, new WindowPlanner(settings, fine, ponds, shaper), fine); } } diff --git a/core/src/main/java/art/arcane/iris/generation/hydrology/engine/HydrologyPregeneration.java b/core/src/main/java/art/arcane/iris/generation/hydrology/engine/HydrologyPregeneration.java index 7e17ebc22..94f29f4e0 100644 --- a/core/src/main/java/art/arcane/iris/generation/hydrology/engine/HydrologyPregeneration.java +++ b/core/src/main/java/art/arcane/iris/generation/hydrology/engine/HydrologyPregeneration.java @@ -18,6 +18,12 @@ public final class HydrologyPregeneration implements AutoCloseable { } } + public synchronized void bound(int minRegionX, int minRegionZ, int maxRegionX, int maxRegionZ) { + if (!closed) { + runtime.boundPregeneration(epoch, minRegionX, minRegionZ, maxRegionX, maxRegionZ); + } + } + @Override public synchronized void close() { if (!closed) { diff --git a/core/src/main/java/art/arcane/iris/generation/hydrology/engine/HydrologyRuntime.java b/core/src/main/java/art/arcane/iris/generation/hydrology/engine/HydrologyRuntime.java index 2f525c3d9..8c38c8c2f 100644 --- a/core/src/main/java/art/arcane/iris/generation/hydrology/engine/HydrologyRuntime.java +++ b/core/src/main/java/art/arcane/iris/generation/hydrology/engine/HydrologyRuntime.java @@ -36,6 +36,7 @@ import java.io.UncheckedIOException; import java.nio.file.Path; import java.util.ArrayList; import java.util.Collection; +import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; @@ -77,6 +78,7 @@ public final class HydrologyRuntime { private final RasterFailures regionPreviewFailures; private final Set previews; private final LinkedHashMap prefetches; + private final Map pregenAreas; private final HydrologyBinding binding; private final Function stores; private final boolean caveFluidPolicies; @@ -99,6 +101,7 @@ public final class HydrologyRuntime { this.regionPreviewFailures = null; this.previews = null; this.prefetches = null; + this.pregenAreas = null; this.binding = HydrologyBinding.UNBOUND; this.stores = identity -> null; this.caveFluidPolicies = false; @@ -125,6 +128,7 @@ public final class HydrologyRuntime { this.regionPreviewFailures = new RasterFailures(ticker); this.previews = new HashSet<>(); this.prefetches = new LinkedHashMap<>(PREFETCH_CAPACITY * 2, 0.75f, true); + this.pregenAreas = new HashMap<>(); this.binding = Objects.requireNonNull(binding, "binding"); this.stores = Objects.requireNonNull(stores, "stores"); this.caveFluidPolicies = caveFluidPolicies; @@ -354,8 +358,28 @@ public final class HydrologyRuntime { } } + void boundPregeneration(Object epoch, int minRegionX, int minRegionZ, int maxRegionX, int maxRegionZ) { + Objects.requireNonNull(epoch, "epoch"); + if (maxRegionX < minRegionX || maxRegionZ < minRegionZ) { + throw new IllegalArgumentException("Invalid pregeneration bounds " + minRegionX + "," + minRegionZ + " to " + maxRegionX + "," + + maxRegionZ); + } + if (cache == null) { + return; + } + synchronized (prefetches) { + pregenAreas.put(epoch, new PregenArea(minRegionX - 1, minRegionZ - 1, maxRegionX + 1, maxRegionZ + 1)); + prefetches.clear(); + } + } + void cancelPregeneration(Object epoch) { if (cache != null) { + synchronized (prefetches) { + if (pregenAreas.remove(epoch) != null) { + prefetches.clear(); + } + } cache.cancel(epoch); } } @@ -667,7 +691,7 @@ public final class HydrologyRuntime { && (!known.prepared().isDone() || known.evictions() == evictions)) { return; } - CompletableFuture prepared = cache.prepareWindowsAround(chunkX, chunkZ, chunkX, chunkZ, 1 + settings.prefetchRings()); + CompletableFuture prepared = prepareAround(chunkX, chunkZ, owner, 1 + settings.prefetchRings()); prefetches.put(owner, new WindowPrefetch(prepared, evictions)); if (prefetches.size() > PREFETCH_CAPACITY) { prefetches.pollFirstEntry(); @@ -675,6 +699,24 @@ public final class HydrologyRuntime { } } + private CompletableFuture prepareAround(int chunkX, int chunkZ, WindowKey owner, int rings) { + PregenArea covering = null; + for (PregenArea area : pregenAreas.values()) { + if (area.contains(owner)) { + covering = covering == null ? area : covering.union(area); + } + } + if (covering == null) { + return cache.prepareWindowsAround(chunkX, chunkZ, chunkX, chunkZ, rings); + } + int minX = Math.max(owner.wx() - rings, covering.minX()); + int minZ = Math.max(owner.wz() - rings, covering.minZ()); + int maxX = Math.min(owner.wx() + rings, covering.maxX()); + int maxZ = Math.min(owner.wz() + rings, covering.maxZ()); + return cache.prepareWindowsAround(minX * CHUNKS_PER_WINDOW, minZ * CHUNKS_PER_WINDOW, (maxX + 1) * CHUNKS_PER_WINDOW - 1, + (maxZ + 1) * CHUNKS_PER_WINDOW - 1, 0); + } + private static List neighbourhood(int chunkX, int chunkZ) { WindowKey own = WindowKey.containing(chunkX << 4, chunkZ << 4); List keys = new ArrayList<>(9); @@ -754,6 +796,16 @@ public final class HydrologyRuntime { private record WindowPrefetch(CompletableFuture prepared, long evictions) { } + private record PregenArea(int minX, int minZ, int maxX, int maxZ) { + private boolean contains(WindowKey key) { + return key.wx() > minX && key.wx() < maxX && key.wz() > minZ && key.wz() < maxZ; + } + + private PregenArea union(PregenArea other) { + return new PregenArea(Math.min(minX, other.minX), Math.min(minZ, other.minZ), Math.max(maxX, other.maxX), Math.max(maxZ, other.maxZ)); + } + } + private static final class LastRaster { private long key; private ChunkWaterRaster raster; diff --git a/core/src/main/java/art/arcane/iris/generation/hydrology/grid/HeightLattice.java b/core/src/main/java/art/arcane/iris/generation/hydrology/grid/HeightLattice.java index 1f1d5c7f4..cc1804606 100644 --- a/core/src/main/java/art/arcane/iris/generation/hydrology/grid/HeightLattice.java +++ b/core/src/main/java/art/arcane/iris/generation/hydrology/grid/HeightLattice.java @@ -1,34 +1,59 @@ package art.arcane.iris.generation.hydrology.grid; +import java.util.ArrayList; import java.util.Arrays; +import java.util.Comparator; +import java.util.List; +import java.util.Map; import java.util.Objects; import java.util.concurrent.CancellationException; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.ExecutionException; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.atomic.AtomicReferenceArray; public final class HeightLattice { + private static final int TILE_SHIFT = 5; + private static final int COLUMN_TILE_SHIFT = 3; + private static final int COLUMN_CELLS = 1 << 20; + private static final CompletableFuture READY = CompletableFuture.completedFuture(null); + private static final int[] NO_NODES = new int[0]; + private static final Tile[] NO_TILES = new Tile[0]; + private final HydrologyTerrainSource source; private final int cell; private final boolean baseOnly; - private final int capacityCells; - private final ConcurrentHashMap cells; - private final ConcurrentLinkedQueue insertionOrder; + private final int tileShift; + private final int tileMask; + private final int tileCells; + private final int capacityTiles; + private final ConcurrentHashMap tiles; + private final AtomicLong clock; private final AtomicBoolean evicting; + private final HeightLattice columns; public HeightLattice(HydrologyTerrainSource source, int cell, boolean baseOnly, int capacityCells) { + this(source, cell, baseOnly, capacityCells, TILE_SHIFT, new HeightLattice(source, 1, baseOnly, COLUMN_CELLS, COLUMN_TILE_SHIFT, null)); + } + + private HeightLattice(HydrologyTerrainSource source, int cell, boolean baseOnly, int capacityCells, int tileShift, HeightLattice columns) { if (cell <= 0 || capacityCells <= 0) { throw new IllegalArgumentException("Invalid lattice cell=" + cell + " capacityCells=" + capacityCells); } this.source = Objects.requireNonNull(source, "source"); this.cell = cell; this.baseOnly = baseOnly; - this.capacityCells = capacityCells; - this.cells = new ConcurrentHashMap<>(); - this.insertionOrder = new ConcurrentLinkedQueue<>(); + this.tileShift = tileShift; + this.tileMask = (1 << tileShift) - 1; + this.tileCells = 1 << (2 * tileShift); + this.capacityTiles = Math.ceilDiv(capacityCells, tileCells); + this.tiles = new ConcurrentHashMap<>(); + this.clock = new AtomicLong(); this.evicting = new AtomicBoolean(); + this.columns = columns; } public int cell() { @@ -67,19 +92,24 @@ public final class HeightLattice { throw new IllegalArgumentException("Column sample has " + xs.length + " x and " + zs.length + " z coordinates"); } requireCapacity(out, xs.length); - if (xs.length > 0) { - source.sample(xs, zs, out, baseOnly); + if (xs.length == 0) { + return; } + if (columns == null) { + source.sample(xs, zs, out, baseOnly); + return; + } + columns.resolve(new Scatter(xs, zs), xs.length, out); } private void resolve(Nodes nodes, int count, HydrologySampleBuffer out) { - Slot[] slots = new Slot[count]; - int unresolved = count; - while (unresolved > 0) { - CompletableFuture batch = claimAndSample(nodes, slots); - unresolved = awaitForeign(batch, slots); + Pass pass = new Pass(nodes, out, clock.incrementAndGet()); + int[] open = null; + int remaining = count; + while (remaining > 0) { + open = pass.run(open, remaining); + remaining = open == null ? 0 : open.length; } - copy(slots, out); evictOverflow(); } @@ -94,130 +124,33 @@ public final class HeightLattice { Math.multiplyExact(Math.addExact(cell0, Math.multiplyExact(extent - 1, stride)), cell); } - private CompletableFuture claimAndSample(Nodes nodes, Slot[] slots) { - CompletableFuture batch = new CompletableFuture<>(); - try { - int[] xs = new int[slots.length]; - int[] zs = new int[slots.length]; - int claimed = claim(batch, nodes, slots, xs, zs); - if (claimed > 0) { - HydrologySampleBuffer samples = new HydrologySampleBuffer(claimed); - source.sample(Arrays.copyOf(xs, claimed), Arrays.copyOf(zs, claimed), samples, baseOnly); - batch.complete(samples); - } - } catch (Throwable failure) { - try { - release(batch, nodes, slots); - } finally { - batch.completeExceptionally(failure); - } - throw failure; - } - return batch; - } - - private int claim(CompletableFuture batch, Nodes nodes, Slot[] slots, int[] xs, int[] zs) { - int claimed = 0; - for (int index = 0; index < slots.length; index++) { - if (slots[index] != null) { - continue; - } - int cellX = nodes.cellX(index); - int cellZ = nodes.cellZ(index); - Long key = key(cellX, cellZ); - Slot existing = cells.get(key); - if (existing == null) { - Slot fresh = new Slot(batch, claimed); - existing = cells.putIfAbsent(key, fresh); - if (existing == null) { - slots[index] = fresh; - insertionOrder.offer(key); - xs[claimed] = cellX * cell; - zs[claimed] = cellZ * cell; - claimed++; - continue; - } - } - slots[index] = existing; - } - return claimed; - } - - private void release(CompletableFuture batch, Nodes nodes, Slot[] slots) { - for (int index = 0; index < slots.length; index++) { - Slot slot = slots[index]; - if (slot != null && slot.batch() == batch) { - cells.remove(key(nodes.cellX(index), nodes.cellZ(index)), slot); - } - } - } - - private static int awaitForeign(CompletableFuture own, Slot[] slots) { - int unresolved = 0; - for (int index = 0; index < slots.length; index++) { - CompletableFuture batch = slots[index].batch(); - if (batch != own && !await(batch)) { - slots[index] = null; - unresolved++; - } - } - return unresolved; - } - - private static boolean await(CompletableFuture batch) { - if (!batch.isDone()) { - try { - batch.get(); - } catch (ExecutionException | CancellationException failure) { - return false; - } catch (InterruptedException interruption) { - Thread.currentThread().interrupt(); - throw new CancellationException("Hydrology lattice wait interrupted."); - } - } - return !batch.isCompletedExceptionally(); - } - - private static void copy(Slot[] slots, HydrologySampleBuffer out) { - float[] height = out.height(); - boolean[] ocean = out.ocean(); - float[] rainfall = out.rainfall(); - float[] pondDensity = out.pondDensity(); - byte[] permission = out.permission(); - String[] fluidProfile = out.fluidProfile(); - for (int index = 0; index < slots.length; index++) { - Slot slot = slots[index]; - HydrologySampleBuffer samples = slot.batch().join(); - int sample = slot.index(); - height[index] = samples.height()[sample]; - ocean[index] = samples.ocean()[sample]; - rainfall[index] = samples.rainfall()[sample]; - pondDensity[index] = samples.pondDensity()[sample]; - permission[index] = samples.permission()[sample]; - fluidProfile[index] = samples.fluidProfile()[sample]; + private Tile tile(int tileX, int tileZ) { + Long key = key(tileX, tileZ); + Tile known = tiles.get(key); + if (known != null) { + return known; } + return tiles.computeIfAbsent(key, ignored -> new Tile(tileCells)); } private void evictOverflow() { - if (cells.size() <= capacityCells || !evicting.compareAndSet(false, true)) { + if (tiles.size() <= capacityTiles || !evicting.compareAndSet(false, true)) { return; } try { - int excess = cells.size() - capacityCells; - while (excess > 0) { - Long key = insertionOrder.poll(); - if (key == null) { - return; + List> idle = new ArrayList<>(tiles.size()); + for (Map.Entry entry : tiles.entrySet()) { + if (entry.getValue().pending.get() == 0) { + idle.add(entry); } - Slot slot = cells.get(key); - if (slot == null) { - continue; - } - if (!slot.batch().isDone()) { - insertionOrder.offer(key); - excess--; - } else if (cells.remove(key, slot)) { - excess--; + } + idle.sort(Comparator.comparingLong(entry -> entry.getValue().used)); + int target = capacityTiles - (capacityTiles >> 3); + for (int index = 0; index < idle.size() && tiles.size() > target; index++) { + Map.Entry entry = idle.get(index); + Tile tile = entry.getValue(); + if (tile.pending.get() == 0) { + tiles.remove(entry.getKey(), tile); } } } finally { @@ -232,7 +165,219 @@ public final class HeightLattice { return mixed ^ (mixed >>> 31); } - private record Slot(CompletableFuture batch, int index) { + private static boolean await(CompletableFuture batch) { + if (!batch.isDone()) { + try { + batch.get(); + } catch (ExecutionException | CancellationException failure) { + return false; + } catch (InterruptedException interruption) { + Thread.currentThread().interrupt(); + throw new CancellationException("Hydrology lattice wait interrupted."); + } + } + return !batch.isCompletedExceptionally(); + } + + private static void copy(HydrologySampleBuffer samples, int sample, HydrologySampleBuffer out, int index) { + out.height()[index] = samples.height()[sample]; + out.ocean()[index] = samples.ocean()[sample]; + out.rainfall()[index] = samples.rainfall()[sample]; + out.pondDensity()[index] = samples.pondDensity()[sample]; + out.permission()[index] = samples.permission()[sample]; + out.fluidProfile()[index] = samples.fluidProfile()[sample]; + } + + private final class Pass { + private final Nodes nodes; + private final HydrologySampleBuffer out; + private final long stamp; + private int[] claimedIndex = NO_NODES; + private Tile[] claimedTile = NO_TILES; + private int[] claimedLocal = NO_NODES; + private int claimed; + private int[] waitingIndex = NO_NODES; + private Tile[] waitingTile = NO_TILES; + private int[] waitingLocal = NO_NODES; + private int waiting; + private Tile current; + private int currentX; + private int currentZ; + + private Pass(Nodes nodes, HydrologySampleBuffer out, long stamp) { + this.nodes = nodes; + this.out = out; + this.stamp = stamp; + } + + private int[] run(int[] open, int remaining) { + claimed = 0; + waiting = 0; + CompletableFuture batch = new CompletableFuture<>(); + for (int position = 0; position < remaining; position++) { + visit(open == null ? position : open[position], batch); + } + if (claimed > 0) { + sample(batch); + } + return awaitWaiting(); + } + + private void visit(int index, CompletableFuture batch) { + int cellX = nodes.cellX(index); + int cellZ = nodes.cellZ(index); + Tile tile = tileOf(cellX >> tileShift, cellZ >> tileShift); + int local = ((cellZ & tileMask) << tileShift) | (cellX & tileMask); + while (true) { + CompletableFuture mark = tile.marks.get(local); + if (mark == READY) { + tile.copy(local, out, index); + return; + } + if (mark != null) { + addWaiting(index, tile, local); + return; + } + if (tile.marks.compareAndSet(local, null, batch)) { + tile.pending.incrementAndGet(); + addClaimed(index, tile, local); + return; + } + } + } + + private Tile tileOf(int tileX, int tileZ) { + if (current == null || currentX != tileX || currentZ != tileZ) { + current = tile(tileX, tileZ); + currentX = tileX; + currentZ = tileZ; + current.used = stamp; + } + return current; + } + + private void sample(CompletableFuture batch) { + try { + int[] xs = new int[claimed]; + int[] zs = new int[claimed]; + for (int position = 0; position < claimed; position++) { + xs[position] = nodes.cellX(claimedIndex[position]) * cell; + zs[position] = nodes.cellZ(claimedIndex[position]) * cell; + } + HydrologySampleBuffer samples = new HydrologySampleBuffer(claimed); + source.sample(xs, zs, samples, baseOnly); + for (int position = 0; position < claimed; position++) { + Tile tile = claimedTile[position]; + int local = claimedLocal[position]; + tile.store(local, samples, position); + tile.marks.set(local, READY); + tile.pending.decrementAndGet(); + copy(samples, position, out, claimedIndex[position]); + } + batch.complete(null); + } catch (Throwable failure) { + try { + release(batch); + } finally { + batch.completeExceptionally(failure); + } + throw failure; + } + } + + private void release(CompletableFuture batch) { + for (int position = 0; position < claimed; position++) { + Tile tile = claimedTile[position]; + if (tile.marks.compareAndSet(claimedLocal[position], batch, null)) { + tile.pending.decrementAndGet(); + } + } + } + + private int[] awaitWaiting() { + int[] open = null; + int unresolved = 0; + for (int position = 0; position < waiting; position++) { + Tile tile = waitingTile[position]; + int local = waitingLocal[position]; + CompletableFuture mark = tile.marks.get(local); + if (mark != READY && (mark == null || !await(mark) || tile.marks.get(local) != READY)) { + if (open == null) { + open = new int[waiting - position]; + } + open[unresolved++] = waitingIndex[position]; + continue; + } + tile.copy(local, out, waitingIndex[position]); + } + return open == null ? null : Arrays.copyOf(open, unresolved); + } + + private void addClaimed(int index, Tile tile, int local) { + if (claimed == claimedIndex.length) { + int grown = Math.max(16, claimed * 2); + claimedIndex = Arrays.copyOf(claimedIndex, grown); + claimedTile = Arrays.copyOf(claimedTile, grown); + claimedLocal = Arrays.copyOf(claimedLocal, grown); + } + claimedIndex[claimed] = index; + claimedTile[claimed] = tile; + claimedLocal[claimed] = local; + claimed++; + } + + private void addWaiting(int index, Tile tile, int local) { + if (waiting == waitingIndex.length) { + int grown = Math.max(16, waiting * 2); + waitingIndex = Arrays.copyOf(waitingIndex, grown); + waitingTile = Arrays.copyOf(waitingTile, grown); + waitingLocal = Arrays.copyOf(waitingLocal, grown); + } + waitingIndex[waiting] = index; + waitingTile[waiting] = tile; + waitingLocal[waiting] = local; + waiting++; + } + } + + private static final class Tile { + private final float[] height; + private final boolean[] ocean; + private final float[] rainfall; + private final float[] pondDensity; + private final byte[] permission; + private final String[] fluidProfile; + private final AtomicReferenceArray> marks; + private final AtomicInteger pending = new AtomicInteger(); + private volatile long used; + + private Tile(int cells) { + this.height = new float[cells]; + this.ocean = new boolean[cells]; + this.rainfall = new float[cells]; + this.pondDensity = new float[cells]; + this.permission = new byte[cells]; + this.fluidProfile = new String[cells]; + this.marks = new AtomicReferenceArray<>(cells); + } + + private void store(int local, HydrologySampleBuffer samples, int sample) { + height[local] = samples.height()[sample]; + ocean[local] = samples.ocean()[sample]; + rainfall[local] = samples.rainfall()[sample]; + pondDensity[local] = samples.pondDensity()[sample]; + permission[local] = samples.permission()[sample]; + fluidProfile[local] = samples.fluidProfile()[sample]; + } + + private void copy(int local, HydrologySampleBuffer out, int index) { + out.height()[index] = height[local]; + out.ocean()[index] = ocean[local]; + out.rainfall()[index] = rainfall[local]; + out.pondDensity()[index] = pondDensity[local]; + out.permission()[index] = permission[local]; + out.fluidProfile()[index] = fluidProfile[local]; + } } private interface Nodes { diff --git a/core/src/main/java/art/arcane/iris/generation/hydrology/plan/HydrologyPlanCache.java b/core/src/main/java/art/arcane/iris/generation/hydrology/plan/HydrologyPlanCache.java index ec2365dc8..4269e145b 100644 --- a/core/src/main/java/art/arcane/iris/generation/hydrology/plan/HydrologyPlanCache.java +++ b/core/src/main/java/art/arcane/iris/generation/hydrology/plan/HydrologyPlanCache.java @@ -32,10 +32,10 @@ import java.util.function.Consumer; import java.util.function.Function; public final class HydrologyPlanCache { - private static final int FRONT = 3; - public static final int WINDOW_LATTICE_CELLS = frontCells(WindowKey.SIZE, WindowPlanner.CELL); - public static final int FINE_LATTICE_CELLS = frontCells(WindowKey.SIZE, WindowPlanner.FINE_CELL); - public static final int REGION_LATTICE_CELLS = frontCells(RegionKey.SIZE, RegionPlanner.CELL); + private static final int FINE_FRONT = 9; + private static final int REGION_FRONT = 5; + public static final int FINE_LATTICE_CELLS = frontCells(FINE_FRONT, WindowKey.SIZE, WindowPlanner.FINE_CELL); + public static final int REGION_LATTICE_CELLS = frontCells(REGION_FRONT, RegionKey.SIZE, RegionPlanner.CELL); private static final int WINDOW_CAPACITY = 128; private static final int REGION_CAPACITY = 64; private static final long DEMAND = Long.MIN_VALUE; @@ -312,8 +312,8 @@ public final class HydrologyPlanCache { return Math.max(1, parallelism / 2); } - private static int frontCells(int unit, int cell) { - int cells = (FRONT + 2) * unit / cell; + private static int frontCells(int front, int unit, int cell) { + int cells = (front + 2) * unit / cell; return cells * cells; } diff --git a/core/src/main/java/art/arcane/iris/generation/hydrology/plan/WindowPlanner.java b/core/src/main/java/art/arcane/iris/generation/hydrology/plan/WindowPlanner.java index 82df753de..07238e748 100644 --- a/core/src/main/java/art/arcane/iris/generation/hydrology/plan/WindowPlanner.java +++ b/core/src/main/java/art/arcane/iris/generation/hydrology/plan/WindowPlanner.java @@ -36,6 +36,7 @@ public final class WindowPlanner { public static final int CELL = 8; public static final int FINE_CELL = 4; private static final int WINDOW_CELLS = WindowKey.SIZE / CELL; + private static final int FINE_STRIDE = CELL / FINE_CELL; private static final int SPAN = 3 * WINDOW_CELLS; private static final int REACH_OVERHANG = 256; private static final float BASIN_DEPTH = 0.5f; @@ -43,22 +44,17 @@ public final class WindowPlanner { private static final long SALT_HEAD = 0x5354524541484dL; private final HydrologySettings settings; - private final HeightLattice lattice; private final HeightLattice fine; private final PondPlanner ponds; private final ReachShaper shaper; private final SeaInletPlanner inlets; - public WindowPlanner(HydrologySettings settings, HeightLattice lattice, HeightLattice fine, PondPlanner ponds, ReachShaper shaper) { + public WindowPlanner(HydrologySettings settings, HeightLattice fine, PondPlanner ponds, ReachShaper shaper) { this.settings = Objects.requireNonNull(settings, "settings"); - this.lattice = Objects.requireNonNull(lattice, "lattice"); this.fine = Objects.requireNonNull(fine, "fine"); this.ponds = Objects.requireNonNull(ponds, "ponds"); this.shaper = Objects.requireNonNull(shaper, "shaper"); this.inlets = new SeaInletPlanner(settings); - if (lattice.cell() != CELL) { - throw new IllegalArgumentException("Window lattice cell " + lattice.cell() + " must be " + CELL); - } if (fine.cell() != FINE_CELL) { throw new IllegalArgumentException("Window fine lattice cell " + fine.cell() + " must be " + FINE_CELL); } @@ -87,7 +83,7 @@ public final class WindowPlanner { int cellX = Math.multiplyExact(key.wx() - 1, WINDOW_CELLS); int cellZ = Math.multiplyExact(key.wz() - 1, WINDOW_CELLS); HydrologySampleBuffer samples = new HydrologySampleBuffer(SPAN * SPAN); - lattice.fill(cellX, cellZ, SPAN, SPAN, samples); + fine.fill(cellX * FINE_STRIDE, cellZ * FINE_STRIDE, SPAN, SPAN, FINE_STRIDE, samples); DemGrid raw = new DemGrid(cellX * CELL, cellZ * CELL, CELL, SPAN, SPAN); System.arraycopy(samples.height(), 0, raw.values(), 0, SPAN * SPAN); List lakes = regionLakes(plans.values()); diff --git a/core/src/main/java/art/arcane/iris/generation/hydrology/underground/SeaInletPlanner.java b/core/src/main/java/art/arcane/iris/generation/hydrology/underground/SeaInletPlanner.java index 1ad8dea8a..be18dcdbe 100644 --- a/core/src/main/java/art/arcane/iris/generation/hydrology/underground/SeaInletPlanner.java +++ b/core/src/main/java/art/arcane/iris/generation/hydrology/underground/SeaInletPlanner.java @@ -33,6 +33,7 @@ public final class SeaInletPlanner { private static final long SALT_HEADROOM = 0x53454148454144L; private static final long SALT_DEPTH = 0x53454144455054L; private static final long SALT_DENSITY = 0x53454144454e53L; + private static final double SQUARED_MARGIN = 1e-9D; private final HydrologySettings settings; @@ -403,22 +404,36 @@ public final class SeaInletPlanner { double dx = bx - ax; double dz = bz - az; double span = dx * dx + dz * dz; + double outside = radius > 0f ? squaredAbove(radius) : Double.POSITIVE_INFINITY; + double inside = radius > 0f ? squaredBelow(radius) : Double.NEGATIVE_INFINITY; + double wetOutside = wetRadius > 0f ? squaredAbove(wetRadius) : Double.POSITIVE_INFINITY; + double wetInside = wetRadius > 0f ? squaredBelow(wetRadius) : Double.NEGATIVE_INFINITY; for (int j = minJ; j <= maxJ; j++) { double pz = grid.originZ() + (double) j * cell; for (int i = minI; i <= maxI; i++) { double px = grid.originX() + (double) i * cell; double t = span <= 0D ? 0D : Math.clamp(((px - ax) * dx + (pz - az) * dz) / span, 0D, 1D); - double distance = Math.hypot(px - (ax + t * dx), pz - (az + t * dz)); - if (distance > radius) { + double ex = px - (ax + t * dx); + double ez = pz - (az + t * dz); + double squared = ex * ex + ez * ez; + if (squared > outside || !(squared < inside) && Math.hypot(ex, ez) > radius) { continue; } int index = j * grid.width() + i; cover[index] = Math.min(cover[index], bed); - wet[index] |= distance <= wetRadius; + wet[index] |= squared < wetInside || !(squared > wetOutside) && Math.hypot(ex, ez) <= wetRadius; } } } + private static double squaredAbove(float radius) { + return (double) radius * radius * (1D + SQUARED_MARGIN); + } + + private static double squaredBelow(float radius) { + return (double) radius * radius * (1D - SQUARED_MARGIN); + } + private static float pathLength(DemGrid grid, List path) { float length = 0f; for (int station = 1; station < path.size(); station++) { diff --git a/core/src/main/java/art/arcane/iris/world/pregen/AsyncPregenMethod.java b/core/src/main/java/art/arcane/iris/world/pregen/AsyncPregenMethod.java index fe1fab2df..4b728610a 100644 --- a/core/src/main/java/art/arcane/iris/world/pregen/AsyncPregenMethod.java +++ b/core/src/main/java/art/arcane/iris/world/pregen/AsyncPregenMethod.java @@ -268,6 +268,7 @@ public class AsyncPregenMethod implements PregeneratorMethod { @Override public void onRegionBounds(int minRegionX, int minRegionZ, int maxRegionX, int maxRegionZ) { retention = createRetention(minRegionX, minRegionZ, maxRegionX, maxRegionZ); + hydrology.bound(minRegionX, minRegionZ, maxRegionX, maxRegionZ); } private PregenChunkRetention createRetention(int minRegionX, int minRegionZ, int maxRegionX, int maxRegionZ) { diff --git a/core/src/main/java/art/arcane/iris/world/pregen/MedievalPregenMethod.java b/core/src/main/java/art/arcane/iris/world/pregen/MedievalPregenMethod.java index 4646ecf74..ae8f5be1a 100644 --- a/core/src/main/java/art/arcane/iris/world/pregen/MedievalPregenMethod.java +++ b/core/src/main/java/art/arcane/iris/world/pregen/MedievalPregenMethod.java @@ -219,6 +219,11 @@ public class MedievalPregenMethod implements PregeneratorMethod { return false; } + @Override + public void onRegionBounds(int minRegionX, int minRegionZ, int maxRegionX, int maxRegionZ) { + hydrology.bound(minRegionX, minRegionZ, maxRegionX, maxRegionZ); + } + @Override public void onRegionsAhead(List regions) { if (!closing.get()) { diff --git a/core/src/main/java/art/arcane/iris/world/pregen/PregenHydrology.java b/core/src/main/java/art/arcane/iris/world/pregen/PregenHydrology.java index 28d5921e2..2c0cc3489 100644 --- a/core/src/main/java/art/arcane/iris/world/pregen/PregenHydrology.java +++ b/core/src/main/java/art/arcane/iris/world/pregen/PregenHydrology.java @@ -13,6 +13,7 @@ public final class PregenHydrology implements AutoCloseable { private final Supplier engine; private HydrologyRuntime owner; private HydrologyPregeneration epoch; + private int[] bounds; private boolean closed; public PregenHydrology(Supplier engine) { @@ -28,12 +29,21 @@ public final class PregenHydrology implements AutoCloseable { withdraw(); owner = runtime; epoch = runtime == null ? null : runtime.openPregeneration(); + applyBounds(); } if (epoch != null) { epoch.prepare(regions); } } + public synchronized void bound(int minRegionX, int minRegionZ, int maxRegionX, int maxRegionZ) { + if (closed) { + return; + } + bounds = new int[]{minRegionX, minRegionZ, maxRegionX, maxRegionZ}; + applyBounds(); + } + @Override public synchronized void close() { closed = true; @@ -41,6 +51,12 @@ public final class PregenHydrology implements AutoCloseable { owner = null; } + private void applyBounds() { + if (epoch != null && bounds != null) { + epoch.bound(bounds[0], bounds[1], bounds[2], bounds[3]); + } + } + private void withdraw() { if (epoch != null) { epoch.close(); diff --git a/core/src/test/java/art/arcane/iris/generation/hydrology/engine/FineLatticeSourceTest.java b/core/src/test/java/art/arcane/iris/generation/hydrology/engine/FineLatticeSourceTest.java deleted file mode 100644 index b22468d07..000000000 --- a/core/src/test/java/art/arcane/iris/generation/hydrology/engine/FineLatticeSourceTest.java +++ /dev/null @@ -1,80 +0,0 @@ -package art.arcane.iris.generation.hydrology.engine; - -import art.arcane.iris.generation.hydrology.grid.HeightLattice; -import art.arcane.iris.generation.hydrology.grid.HydrologySampleBuffer; -import art.arcane.iris.generation.hydrology.grid.HydrologyTerrainSource; -import org.junit.Test; - -import java.util.concurrent.atomic.AtomicLong; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertThrows; - -public class FineLatticeSourceTest { - @Test - public void windowLatticeReadsItsNodesFromTheFineLattice() { - CountingTerrain terrain = new CountingTerrain(); - HeightLattice fine = new HeightLattice(terrain, 4, false, 1 << 16); - HeightLattice window = new HeightLattice(new FineLatticeSource(fine, terrain), 8, false, 1 << 16); - - fine.fill(0, 0, 64, 64, new HydrologySampleBuffer(64 * 64)); - assertEquals(4096L, terrain.samples.get()); - HydrologySampleBuffer held = new HydrologySampleBuffer(32 * 32); - window.fill(0, 0, 32, 32, held); - assertEquals(4096L, terrain.samples.get()); - assertHeights(held, 0, 0, 32); - - HydrologySampleBuffer fresh = new HydrologySampleBuffer(16 * 16); - window.fill(32, 0, 16, 16, fresh); - assertEquals(4096L + 256L, terrain.samples.get()); - assertHeights(fresh, 32, 0, 16); - fine.fill(64, 0, 32, 32, new HydrologySampleBuffer(32 * 32)); - assertEquals(4096L + 1024L, terrain.samples.get()); - - FineLatticeSource source = new FineLatticeSource(fine, terrain); - assertThrows(IllegalArgumentException.class, () -> source.sample(new int[]{8}, new int[]{8}, new HydrologySampleBuffer(1), true)); - assertThrows(IllegalArgumentException.class, () -> source.sample(new int[]{6}, new int[]{8}, new HydrologySampleBuffer(1), false)); - } - - private static void assertHeights(HydrologySampleBuffer samples, int cellX0, int cellZ0, int side) { - for (int index = 0; index < side * side; index++) { - int x = (cellX0 + index % side) * 8; - int z = (cellZ0 + index / side) * 8; - assertEquals(x + "," + z, Float.floatToRawIntBits(CountingTerrain.height(x, z)), Float.floatToRawIntBits(samples.height()[index])); - } - } - - private static final class CountingTerrain implements HydrologyTerrainSource { - private final AtomicLong samples = new AtomicLong(); - - private static float height(int x, int z) { - return 64f + (float) Math.sin(x * 0.013D) * 9f + (float) Math.cos(z * 0.021D) * 7f; - } - - @Override - public void sample(int[] xs, int[] zs, HydrologySampleBuffer out, boolean baseOnly) { - samples.addAndGet(xs.length); - for (int index = 0; index < xs.length; index++) { - out.height()[index] = height(xs[index], zs[index]); - out.permission()[index] = HydrologySampleBuffer.ALLOW_STREAMS | HydrologySampleBuffer.ALLOW_PONDS; - out.rainfall()[index] = 1f; - out.pondDensity()[index] = 1f; - } - } - - @Override - public int seaLevel() { - return 40; - } - - @Override - public int minY() { - return 0; - } - - @Override - public int maxY() { - return 384; - } - } -} diff --git a/core/src/test/java/art/arcane/iris/generation/hydrology/engine/HydrologyRuntimeTest.java b/core/src/test/java/art/arcane/iris/generation/hydrology/engine/HydrologyRuntimeTest.java index a97fa21dc..7e38638fa 100644 --- a/core/src/test/java/art/arcane/iris/generation/hydrology/engine/HydrologyRuntimeTest.java +++ b/core/src/test/java/art/arcane/iris/generation/hydrology/engine/HydrologyRuntimeTest.java @@ -67,6 +67,7 @@ import java.util.concurrent.atomic.AtomicReference; import java.util.function.BooleanSupplier; import java.util.stream.Stream; +import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; @@ -414,6 +415,35 @@ public class HydrologyRuntimeTest { assertEquals(3, prefetches.get()); } + @Test(timeout = 180_000L) + public void boundedPregenerationPrefetchesOnlyWindowsItsChunksNeed() { + FakePlanners planners = new FakePlanners(settings()); + HydrologyPlanCache cache = spy(new HydrologyPlanCache(planners.settings, planners::region, planners::window, + pool(planners, 2), () -> false)); + List prepared = Collections.synchronizedList(new ArrayList<>()); + doAnswer(invocation -> { + prepared.add(new int[]{invocation.getArgument(0), invocation.getArgument(1), invocation.getArgument(2), invocation.getArgument(3), + invocation.getArgument(4)}); + return CompletableFuture.completedFuture(null); + }).when(cache).prepareWindowsAround(anyInt(), anyInt(), anyInt(), anyInt(), anyInt()); + HydrologyRuntime runtime = new HydrologyRuntime(planners.settings, cache, planners::natural, () -> false, planners.ticker::get); + runtimes.add(runtime); + int chunks = WindowKey.SIZE >> 4; + HydrologyPregeneration pregeneration = runtime.openPregeneration(); + pregeneration.bound(0, 0, 1, 1); + + runtime.raster(0, 0); + assertArrayEquals(new int[]{-chunks, -chunks, 3 * chunks - 1, 3 * chunks - 1, 0}, prepared.getLast()); + runtime.raster(chunks + 5, chunks + 5); + assertArrayEquals(new int[]{-chunks, -chunks, 3 * chunks - 1, 3 * chunks - 1, 0}, prepared.getLast()); + runtime.raster(-3 * chunks, 0); + assertArrayEquals(new int[]{-3 * chunks, 0, -3 * chunks, 0, 2}, prepared.getLast()); + + pregeneration.close(); + runtime.raster(1, 1); + assertArrayEquals(new int[]{1, 1, 1, 1, 2}, prepared.getLast()); + } + @Test(timeout = 180_000L) public void preparePregenerationPlansRegionsInOrder() throws Exception { FakePlanners planners = new FakePlanners(settings()); diff --git a/core/src/test/java/art/arcane/iris/generation/hydrology/engine/IrisHydrologyTerrainSourceTest.java b/core/src/test/java/art/arcane/iris/generation/hydrology/engine/IrisHydrologyTerrainSourceTest.java index 552d4bcbb..b411a91a2 100644 --- a/core/src/test/java/art/arcane/iris/generation/hydrology/engine/IrisHydrologyTerrainSourceTest.java +++ b/core/src/test/java/art/arcane/iris/generation/hydrology/engine/IrisHydrologyTerrainSourceTest.java @@ -154,10 +154,9 @@ public class IrisHydrologyTerrainSourceTest { HydrologySampleBuffer rect = new HydrologySampleBuffer(32 * 32); fine.fill(-600, 400, 32, 32, rect); assertMatchesScalar(reference, "fine rect", rect, nodes(-600, 400, 32, 1, 4), false); - HeightLattice window = new HeightLattice(new FineLatticeSource(fine, source), 8, false, 1 << 16); HydrologySampleBuffer strided = new HydrologySampleBuffer(16 * 16); - window.fill(-150, 300, 16, 16, 3, strided); - assertMatchesScalar(reference, "window strided", strided, nodes(-150, 300, 16, 3, 8), false); + fine.fill(-300, 600, 16, 16, 6, strided); + assertMatchesScalar(reference, "fine strided", strided, nodes(-300, 600, 16, 6, 4), false); HeightLattice coarse = new HeightLattice(source, 64, true, 1 << 12); HydrologySampleBuffer coarseBase = new HydrologySampleBuffer(16 * 16); coarse.fill(-8, -8, 16, 16, coarseBase); diff --git a/core/src/test/java/art/arcane/iris/generation/hydrology/grid/HeightLatticeTest.java b/core/src/test/java/art/arcane/iris/generation/hydrology/grid/HeightLatticeTest.java index 0016c469a..5e4da3c2d 100644 --- a/core/src/test/java/art/arcane/iris/generation/hydrology/grid/HeightLatticeTest.java +++ b/core/src/test/java/art/arcane/iris/generation/hydrology/grid/HeightLatticeTest.java @@ -287,6 +287,31 @@ public class HeightLatticeTest { assertThrows(IllegalArgumentException.class, () -> lattice.fill(cellXs, cellZs, new HydrologySampleBuffer(5))); } + @Test + public void sampledColumnsAreReadOnceAndMatchTheSource() { + CountingSource source = new CountingSource(); + HeightLattice lattice = new HeightLattice(source, 4, false, 1 << 16); + int[] xs = {5, -13, 5, 1027, -13, 6}; + int[] zs = {-2, 77, -2, 3, 77, -2}; + HydrologySampleBuffer first = new HydrologySampleBuffer(xs.length); + lattice.sampleColumns(xs, zs, first); + assertEquals(4, source.samples()); + HydrologySampleBuffer second = new HydrologySampleBuffer(xs.length); + lattice.sampleColumns(xs, zs, second); + assertEquals(4, source.samples()); + HydrologySampleBuffer expected = new HydrologySampleBuffer(xs.length); + new CountingSource().sample(xs, zs, expected, false); + for (int index = 0; index < xs.length; index++) { + assertEquals(Float.floatToIntBits(expected.height()[index]), Float.floatToIntBits(first.height()[index])); + assertEquals(Float.floatToIntBits(expected.height()[index]), Float.floatToIntBits(second.height()[index])); + assertEquals(expected.ocean()[index], second.ocean()[index]); + assertEquals(expected.permission()[index], second.permission()[index]); + assertEquals(expected.fluidProfile()[index], second.fluidProfile()[index]); + } + lattice.sampleColumns(new int[0], new int[0], new HydrologySampleBuffer(0)); + assertEquals(1, source.calls()); + } + private static void fillWindow(HeightLattice lattice, int wx, int wz, int cell, boolean baseOnly) { assertFill(lattice, cell, baseOnly, (wx - 1) * WINDOW, (wz - 1) * WINDOW, AREA, AREA); } diff --git a/core/src/test/java/art/arcane/iris/generation/hydrology/plan/HydrologyPlanCacheTest.java b/core/src/test/java/art/arcane/iris/generation/hydrology/plan/HydrologyPlanCacheTest.java index 040a6bfa4..63c7275ff 100644 --- a/core/src/test/java/art/arcane/iris/generation/hydrology/plan/HydrologyPlanCacheTest.java +++ b/core/src/test/java/art/arcane/iris/generation/hydrology/plan/HydrologyPlanCacheTest.java @@ -1045,7 +1045,7 @@ public class HydrologyPlanCacheTest { } private static WindowPlanner windowPlanner(HydrologySettings settings, SyntheticTerrain terrain) { - return new WindowPlanner(settings, new HeightLattice(terrain, WindowPlanner.CELL, false, HydrologyPlanCache.WINDOW_LATTICE_CELLS), + return new WindowPlanner(settings, new HeightLattice(terrain, WindowPlanner.FINE_CELL, false, HydrologyPlanCache.FINE_LATTICE_CELLS), new PondPlanner(settings), new ReachShaper(settings)); } diff --git a/core/src/test/java/art/arcane/iris/generation/hydrology/plan/HydrologyPlanCodecTest.java b/core/src/test/java/art/arcane/iris/generation/hydrology/plan/HydrologyPlanCodecTest.java index dba3ff4a1..274a2ffce 100644 --- a/core/src/test/java/art/arcane/iris/generation/hydrology/plan/HydrologyPlanCodecTest.java +++ b/core/src/test/java/art/arcane/iris/generation/hydrology/plan/HydrologyPlanCodecTest.java @@ -81,7 +81,7 @@ public class HydrologyPlanCodecTest { assertEquals(planned, HydrologyPlanCodec.decodeRegion(settings, HydrologyPlanCodec.encode(settings, planned)).orElseThrow()); regions.add(planned); } - WindowPlan planned = new WindowPlanner(settings, new HeightLattice(terrain, WindowPlanner.CELL, false, 1 << 18), + WindowPlan planned = new WindowPlanner(settings, new HeightLattice(terrain, WindowPlanner.FINE_CELL, false, 1 << 21), new PondPlanner(settings), new ReachShaper(settings)) .plan(LAND, regions); assertFalse(planned.reaches().isEmpty()); diff --git a/core/src/test/java/art/arcane/iris/generation/hydrology/plan/WindowPlannerTest.java b/core/src/test/java/art/arcane/iris/generation/hydrology/plan/WindowPlannerTest.java index a1829ff59..1efdfb02f 100644 --- a/core/src/test/java/art/arcane/iris/generation/hydrology/plan/WindowPlannerTest.java +++ b/core/src/test/java/art/arcane/iris/generation/hydrology/plan/WindowPlannerTest.java @@ -83,7 +83,7 @@ public class WindowPlannerTest { new int[]{head, head}, new float[]{2f, 2f}, new float[]{2f, 2f}, new ReachStyle[]{ReachStyle.RUN, ReachStyle.RUN}, 1000f); return new ShapedNetwork(List.of(outlet), List.of(pond.withOutletReachId(outlet.id()))); }).when(shaper).shape(any(), any(), any(), any(), any(), any(), anyLong()); - WindowPlanner planner = new WindowPlanner(settings, new HeightLattice(terrain, WindowPlanner.CELL, false, 1 << 18), + WindowPlanner planner = new WindowPlanner(settings, new HeightLattice(terrain, WindowPlanner.FINE_CELL, false, 1 << 18), ponds, shaper); WindowComputation settled = planner.compute(ORIGIN, noRegions(ORIGIN)); @@ -587,7 +587,7 @@ public class WindowPlannerTest { } return network; }).when(shaper).shape(any(), any(), any(), any(), any(), any(), anyLong()); - return new WindowPlanner(settings, new HeightLattice(terrain, WindowPlanner.CELL, false, 1 << 18), + return new WindowPlanner(settings, new HeightLattice(terrain, WindowPlanner.FINE_CELL, false, 1 << 21), new PondPlanner(settings), shaper); } @@ -715,7 +715,7 @@ public class WindowPlannerTest { } private static WindowPlanner planner(HydrologySettings settings, SyntheticTerrain terrain) { - return new WindowPlanner(settings, new HeightLattice(terrain, WindowPlanner.CELL, false, 1 << 18), + return new WindowPlanner(settings, new HeightLattice(terrain, WindowPlanner.FINE_CELL, false, 1 << 21), new PondPlanner(settings), new ReachShaper(settings)); } diff --git a/probe/src/main/java/art/arcane/iris/probe/HydrologyPlanHarness.java b/probe/src/main/java/art/arcane/iris/probe/HydrologyPlanHarness.java index 89d776000..ddc37a008 100644 --- a/probe/src/main/java/art/arcane/iris/probe/HydrologyPlanHarness.java +++ b/probe/src/main/java/art/arcane/iris/probe/HydrologyPlanHarness.java @@ -195,7 +195,8 @@ final class HydrologyPlanHarness { double landTiles(WindowKey key) { int cells = WindowKey.SIZE / WindowPlanner.CELL; HydrologySampleBuffer samples = new HydrologySampleBuffer(cells * cells); - planners.windowLattice().fill(key.wx() * cells, key.wz() * cells, cells, cells, samples); + int stride = WindowPlanner.CELL / WindowPlanner.FINE_CELL; + planners.fineLattice().fill(key.wx() * cells * stride, key.wz() * cells * stride, cells, cells, stride, samples); int land = 0; for (int cell = 0; cell < cells * cells; cell++) { if (!samples.ocean()[cell]) {