mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-10-26 22:10:18 +00:00
Optimize world generation
- Limit terrain cleanup to affected columns and batch cave boundary reads. - Reuse offline generation workers and native air states. - Gather history writes and defer unpublished append synchronization until atomic publication. - Cover terrain parity, worker lifecycle, history durability, and publication failures.
This commit is contained in:
1 parent
7986698c1c
commit
38cf4ac7c7
25 files changed
+1362
-226
No files matched your search
@@ -4,6 +4,8 @@ import art.arcane.volmlib.nativelib.terrain.NativeBlockState;
|
||||
import art.arcane.iris.generation.hydrology.cave.HydrologyCaveCell;
|
||||
import art.arcane.iris.world.storage.matter.PreObjectMatterCell;
|
||||
import art.arcane.volmlib.util.function.Consumer4;
|
||||
import art.arcane.volmlib.util.hunk.bits.DataContainer;
|
||||
import art.arcane.volmlib.util.hunk.storage.PaletteOrHunk;
|
||||
import art.arcane.volmlib.util.mantle.runtime.MantleChunk;
|
||||
import art.arcane.volmlib.util.mantle.runtime.Mantle;
|
||||
import art.arcane.volmlib.util.matter.Matter;
|
||||
@@ -11,6 +13,7 @@ import art.arcane.volmlib.util.matter.MatterCavern;
|
||||
import art.arcane.volmlib.util.matter.MatterSlice;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.Arrays;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@@ -67,6 +70,82 @@ public final class TerrainMatterView {
|
||||
}
|
||||
}
|
||||
|
||||
public static MatterCavern[] getComposedFace(MantleChunk<Matter> chunk, Face face, int height) {
|
||||
Objects.requireNonNull(face, "Terrain face");
|
||||
if (height < 0) {
|
||||
throw new IllegalArgumentException("Terrain face height must be nonnegative");
|
||||
}
|
||||
MatterCavern[] result = new MatterCavern[Math.multiplyExact(height, 16)];
|
||||
if (chunk == null || height == 0) {
|
||||
return result;
|
||||
}
|
||||
PreObjectMatterCell[] journal = new PreObjectMatterCell[256];
|
||||
HydrologyCaveCell[] hydrology = new HydrologyCaveCell[256];
|
||||
MatterCavern[] caverns = new MatterCavern[256];
|
||||
synchronized (chunk) {
|
||||
for (int baseY = 0; baseY < height; baseY += 16) {
|
||||
int section = baseY >> 4;
|
||||
if (!chunk.exists(section)) {
|
||||
continue;
|
||||
}
|
||||
Matter matter = chunk.get(section);
|
||||
if (matter == null) {
|
||||
continue;
|
||||
}
|
||||
copyFace(matter.getSlice(PreObjectMatterCell.class), face, journal);
|
||||
copyFace(matter.getSlice(HydrologyCaveCell.class), face, hydrology);
|
||||
copyFace(matter.getSlice(MatterCavern.class), face, caverns);
|
||||
int count = Math.min(16, height - baseY) * 16;
|
||||
for (int index = 0; index < count; index++) {
|
||||
PreObjectMatterCell cell = journal[index];
|
||||
HydrologyCaveCell effectiveHydrology = cell != null && cell.hydrologyCaptured()
|
||||
? cell.hydrology() : hydrology[index];
|
||||
result[baseY * 16 + index] = effectiveHydrology != null
|
||||
? effectiveHydrology.asCavern()
|
||||
: cell != null && cell.cavernCaptured() ? cell.cavern() : caverns[index];
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static <T> void copyFace(MatterSlice<T> slice, Face face, T[] destination) {
|
||||
if (slice == null) {
|
||||
Arrays.fill(destination, null);
|
||||
return;
|
||||
}
|
||||
if (slice instanceof PaletteOrHunk<?> storage && storage.isPalette()
|
||||
&& slice.getWidth() == 16 && slice.getHeight() == 16 && slice.getDepth() == 16) {
|
||||
DataContainer<T> palette = (DataContainer<T>) storage.palette();
|
||||
palette.copyTo(face.positions, destination);
|
||||
return;
|
||||
}
|
||||
for (int index = 0; index < destination.length; index++) {
|
||||
int position = face.positions[index];
|
||||
destination[index] = slice.get(position & 15, (position >> 4) & 15, position >> 8);
|
||||
}
|
||||
}
|
||||
|
||||
public enum Face {
|
||||
WEST(true, 0),
|
||||
EAST(true, 15),
|
||||
NORTH(false, 0),
|
||||
SOUTH(false, 15);
|
||||
|
||||
private final int[] positions = new int[256];
|
||||
|
||||
Face(boolean fixedX, int coordinate) {
|
||||
for (int y = 0; y < 16; y++) {
|
||||
for (int offset = 0; offset < 16; offset++) {
|
||||
int x = fixedX ? coordinate : offset;
|
||||
int z = fixedX ? offset : coordinate;
|
||||
positions[y * 16 + offset] = (z << 8) | (y << 4) | x;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static <T> void iterate(MantleChunk<Matter> chunk, Class<T> type,
|
||||
Consumer4<Integer, Integer, Integer, T> consumer) {
|
||||
Objects.requireNonNull(chunk, "Terrain mantle chunk");
|
||||
|
||||
@@ -532,44 +532,46 @@ public class IrisCarveModifier extends EngineAssignedModifier<NativeBlockState>
|
||||
return;
|
||||
}
|
||||
|
||||
int height = maxY + 1;
|
||||
MatterCavern[] ownWest = west == null ? null : TerrainMatterView.getComposedFace(mc, TerrainMatterView.Face.WEST, height);
|
||||
MatterCavern[] ownEast = east == null ? null : TerrainMatterView.getComposedFace(mc, TerrainMatterView.Face.EAST, height);
|
||||
MatterCavern[] ownNorth = north == null ? null : TerrainMatterView.getComposedFace(mc, TerrainMatterView.Face.NORTH, height);
|
||||
MatterCavern[] ownSouth = south == null ? null : TerrainMatterView.getComposedFace(mc, TerrainMatterView.Face.SOUTH, height);
|
||||
MatterCavern[] fromWest = west == null ? null : TerrainMatterView.getComposedFace(west, TerrainMatterView.Face.EAST, height);
|
||||
MatterCavern[] fromEast = east == null ? null : TerrainMatterView.getComposedFace(east, TerrainMatterView.Face.WEST, height);
|
||||
MatterCavern[] fromNorth = north == null ? null : TerrainMatterView.getComposedFace(north, TerrainMatterView.Face.SOUTH, height);
|
||||
MatterCavern[] fromSouth = south == null ? null : TerrainMatterView.getComposedFace(south, TerrainMatterView.Face.NORTH, height);
|
||||
for (int yy = 1; yy <= maxY; yy++) {
|
||||
for (int offset = 0; offset < 16; offset++) {
|
||||
int index = yy * 16 + offset;
|
||||
if (west != null) {
|
||||
tryAddBoundaryWall(mc, west, walls, boundaryMasks, 0, yy, offset, 15, offset);
|
||||
tryAddBoundaryWall(ownWest[index], fromWest[index], walls, boundaryMasks, 0, yy, offset);
|
||||
}
|
||||
if (east != null) {
|
||||
tryAddBoundaryWall(mc, east, walls, boundaryMasks, 15, yy, offset, 0, offset);
|
||||
tryAddBoundaryWall(ownEast[index], fromEast[index], walls, boundaryMasks, 15, yy, offset);
|
||||
}
|
||||
if (north != null) {
|
||||
tryAddBoundaryWall(mc, north, walls, boundaryMasks, offset, yy, 0, offset, 15);
|
||||
tryAddBoundaryWall(ownNorth[index], fromNorth[index], walls, boundaryMasks, offset, yy, 0);
|
||||
}
|
||||
if (south != null) {
|
||||
tryAddBoundaryWall(mc, south, walls, boundaryMasks, offset, yy, 15, offset, 0);
|
||||
tryAddBoundaryWall(ownSouth[index], fromSouth[index], walls, boundaryMasks, offset, yy, 15);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void tryAddBoundaryWall(
|
||||
MantleChunk<Matter> mc,
|
||||
MantleChunk<Matter> neighborChunk,
|
||||
MatterCavern current,
|
||||
MatterCavern neighbor,
|
||||
CarveWallBuffer walls,
|
||||
CarveColumnMask[] boundaryMasks,
|
||||
int localX,
|
||||
int yy,
|
||||
int localZ,
|
||||
int neighborX,
|
||||
int neighborZ
|
||||
int localZ
|
||||
) {
|
||||
if (composedCavernAt(mc, localX, yy, localZ) != null) {
|
||||
if (current != null || neighbor == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
MatterCavern neighbor = composedCavernAt(neighborChunk, neighborX, yy, neighborZ);
|
||||
if (neighbor == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
walls.put(localX, yy, localZ, neighbor);
|
||||
int columnIndex = PowerOfTwoCoordinates.packLocal16(localX, localZ);
|
||||
boundaryMasks[columnIndex].add(yy);
|
||||
|
||||
@@ -30,11 +30,9 @@ import art.arcane.iris.generation.concurrent.BurstExecutor;
|
||||
import art.arcane.volmlib.util.scheduling.PrecisionStopwatch;
|
||||
import art.arcane.volmlib.nativelib.terrain.NativeBlockState;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import it.unimi.dsi.fastutil.ints.IntArrayList;
|
||||
|
||||
public class IrisPerfectionModifier extends EngineAssignedModifier<NativeBlockState> {
|
||||
private static final BoundBlockState AIR = BoundBlockState.of("AIR");
|
||||
@@ -86,84 +84,67 @@ public class IrisPerfectionModifier extends EngineAssignedModifier<NativeBlockSt
|
||||
if (getDimension().isHideOresForHiddenOre()) {
|
||||
hideOres(output, multicore);
|
||||
}
|
||||
AtomicBoolean changed = new AtomicBoolean(true);
|
||||
BurstExecutor burst = burst().burst(multicore);
|
||||
while (changed.get()) {
|
||||
changed.set(false);
|
||||
for (int i = 0; i < 16; i++) {
|
||||
int finalI = i;
|
||||
burst.queue(() -> {
|
||||
List<Integer> surfaces = new ArrayList<>();
|
||||
List<Integer> ceilings = new ArrayList<>();
|
||||
for (int j = 0; j < 16; j++) {
|
||||
surfaces.clear();
|
||||
ceilings.clear();
|
||||
int top = getHeight(output, finalI, j);
|
||||
boolean inside = true;
|
||||
surfaces.add(top);
|
||||
|
||||
for (int k = top; k >= 0; k--) {
|
||||
NativeBlockState b = output.get(finalI, k, j);
|
||||
if (IrisSpeleothems.isSpike(b)) {
|
||||
b = normalizeSpike(b, output, finalI, j, k, AIR.get(), WATER.get());
|
||||
}
|
||||
boolean now = b != null && !(B.isAir(b) || B.isFluid(b));
|
||||
|
||||
if (now != inside) {
|
||||
inside = now;
|
||||
|
||||
if (inside) {
|
||||
surfaces.add(k);
|
||||
} else {
|
||||
ceilings.add(k + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (int k : surfaces) {
|
||||
NativeBlockState tip = output.get(finalI, k, j);
|
||||
|
||||
if (tip == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
boolean remove = false;
|
||||
boolean remove2 = false;
|
||||
|
||||
if (B.isDecorant(tip)) {
|
||||
NativeBlockState bel = output.get(finalI, k - 1, j);
|
||||
|
||||
if (bel == null) {
|
||||
remove = true;
|
||||
} else if (!B.canPlaceOnto(tip, bel)) {
|
||||
remove = true;
|
||||
} else if (IrisProceduralBlocks.hasProperty(bel, "half")) {
|
||||
NativeBlockState bb = output.get(finalI, k - 2, j);
|
||||
if (bb == null || !B.canPlaceOnto(bel, bb)) {
|
||||
remove = true;
|
||||
remove2 = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (remove) {
|
||||
changed.set(true);
|
||||
output.set(finalI, k, j, AIR.get());
|
||||
|
||||
if (remove2) {
|
||||
output.set(finalI, k - 1, j, AIR.get());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
burst.complete();
|
||||
for (int index = 0; index < 16; index++) {
|
||||
int columnX = index;
|
||||
burst.queue(() -> perfectSlice(output, columnX));
|
||||
}
|
||||
burst.complete();
|
||||
|
||||
getEngine().getMetrics().getPerfection().put(p.getMilliseconds());
|
||||
}
|
||||
|
||||
private void perfectSlice(Hunk<NativeBlockState> output, int x) {
|
||||
IntArrayList surfaces = new IntArrayList();
|
||||
for (int z = 0; z < 16; z++) {
|
||||
boolean changed;
|
||||
do {
|
||||
surfaces.clear();
|
||||
int top = getHeight(output, x, z);
|
||||
boolean inside = true;
|
||||
surfaces.add(top);
|
||||
for (int y = top; y >= 0; y--) {
|
||||
NativeBlockState block = output.get(x, y, z);
|
||||
if (IrisSpeleothems.isSpike(block)) {
|
||||
block = normalizeSpike(block, output, x, z, y, AIR.get(), WATER.get());
|
||||
}
|
||||
boolean now = block != null && !(B.isAir(block) || B.isFluid(block));
|
||||
if (now != inside) {
|
||||
inside = now;
|
||||
if (inside) {
|
||||
surfaces.add(y);
|
||||
}
|
||||
}
|
||||
}
|
||||
changed = false;
|
||||
for (int index = 0; index < surfaces.size(); index++) {
|
||||
int y = surfaces.getInt(index);
|
||||
NativeBlockState tip = output.get(x, y, z);
|
||||
if (!B.isDecorant(tip)) {
|
||||
continue;
|
||||
}
|
||||
NativeBlockState below = output.get(x, y - 1, z);
|
||||
boolean remove = below == null || !B.canPlaceOnto(tip, below);
|
||||
boolean removeBelow = false;
|
||||
if (!remove && IrisProceduralBlocks.hasProperty(below, "half")) {
|
||||
NativeBlockState support = output.get(x, y - 2, z);
|
||||
if (support == null || !B.canPlaceOnto(below, support)) {
|
||||
remove = true;
|
||||
removeBelow = true;
|
||||
}
|
||||
}
|
||||
if (remove) {
|
||||
changed = true;
|
||||
output.set(x, y, z, AIR.get());
|
||||
if (removeBelow) {
|
||||
output.set(x, y - 1, z, AIR.get());
|
||||
}
|
||||
}
|
||||
}
|
||||
} while (changed);
|
||||
}
|
||||
}
|
||||
|
||||
static NativeBlockState normalizeSpike(NativeBlockState state, Hunk<NativeBlockState> output,
|
||||
int x, int z, int y, NativeBlockState air, NativeBlockState water) {
|
||||
if (IrisSpeleothems.isSupported(state, output, x, z, y)) {
|
||||
|
||||
@@ -53,7 +53,8 @@ public final class GenerationHistory {
|
||||
GenerationHistoryStore store,
|
||||
ChunkGenerationOwnership ownership,
|
||||
GenerationKernelRegistry kernels,
|
||||
Optional<FreshPublication> publication
|
||||
Optional<FreshPublication> publication,
|
||||
boolean unpublished
|
||||
) throws IOException {
|
||||
this.paths = Objects.requireNonNull(paths, "paths");
|
||||
this.packs = Objects.requireNonNull(packs, "packs");
|
||||
@@ -62,8 +63,8 @@ public final class GenerationHistory {
|
||||
this.kernels = Objects.requireNonNull(kernels, "kernels");
|
||||
this.boundaries = new GenerationBoundaryStore(paths.dimensionRoot());
|
||||
this.terrainSignatures = new TerrainBoundarySignatureStore(paths.dimensionRoot());
|
||||
this.semantics = GenerationSemanticIndex.loadRequired(paths.dimensionRoot());
|
||||
this.savedBiomes = SavedBiomeStore.open(paths.dimensionRoot());
|
||||
this.semantics = GenerationSemanticIndex.loadRequired(paths.dimensionRoot(), unpublished);
|
||||
this.savedBiomes = SavedBiomeStore.open(paths.dimensionRoot(), unpublished);
|
||||
this.admission = new GenerationAdmission(paths.dimensionRoot());
|
||||
this.boundaryCache = boundedCache(MAXIMUM_CACHED_BOUNDARIES);
|
||||
this.terrainSignatureCache = boundedCache(MAXIMUM_CACHED_TERRAIN_SIGNATURES);
|
||||
@@ -108,6 +109,25 @@ public final class GenerationHistory {
|
||||
GenerationKernelRegistry.Version version,
|
||||
GenerationKernelRegistry kernels
|
||||
) throws IOException {
|
||||
return create(new FreshCreation(dimensionRoot, packSource, packFingerprint, worldSeed, dimensionContract, registryContract),
|
||||
version, kernels, false);
|
||||
}
|
||||
|
||||
public static GenerationHistory createUnpublished(FreshCreation creation) throws IOException {
|
||||
Objects.requireNonNull(creation, "creation");
|
||||
Files.createDirectory(creation.dimensionRoot());
|
||||
GenerationKernelRegistry kernels = GenerationKernelRegistry.standard();
|
||||
return create(creation, kernels.current(), kernels, true);
|
||||
}
|
||||
|
||||
private static GenerationHistory create(FreshCreation creation, GenerationKernelRegistry.Version version,
|
||||
GenerationKernelRegistry kernels, boolean unpublished) throws IOException {
|
||||
Path dimensionRoot = creation.dimensionRoot();
|
||||
Path packSource = creation.packSource();
|
||||
String packFingerprint = creation.packFingerprint();
|
||||
long worldSeed = creation.worldSeed();
|
||||
GenerationEpoch.DimensionContract dimensionContract = creation.dimensionContract();
|
||||
GenerationRegistryContract registryContract = creation.registryContract();
|
||||
GenerationHistoryPaths paths = GenerationHistoryPaths.forDimension(dimensionRoot);
|
||||
requireSafeStatePaths(paths);
|
||||
if (Files.exists(paths.manifest(), LinkOption.NOFOLLOW_LINKS)
|
||||
@@ -141,7 +161,7 @@ public final class GenerationHistory {
|
||||
GenerationSemanticIndex.initialize(paths.dimensionRoot());
|
||||
GenerationHistoryStore store = GenerationHistoryStore.initialize(paths.generationRoot(), epoch);
|
||||
return new GenerationHistory(paths, packs, store, ownership, kernels,
|
||||
Optional.of(new FreshPublication(publishedPack, epoch)));
|
||||
Optional.of(new FreshPublication(publishedPack, epoch)), unpublished);
|
||||
}
|
||||
|
||||
public static GenerationHistory open(Path dimensionRoot) throws IOException {
|
||||
@@ -183,7 +203,7 @@ public final class GenerationHistory {
|
||||
GenerationPackRepository packs = new GenerationPackRepository(paths.dimensionRoot());
|
||||
GenerationHistoryStore store = GenerationHistoryStore.open(paths.generationRoot());
|
||||
ChunkGenerationOwnership ownership = ChunkGenerationOwnership.load(paths.ownershipRoot());
|
||||
return new GenerationHistory(paths, packs, store, ownership, kernels, Optional.empty());
|
||||
return new GenerationHistory(paths, packs, store, ownership, kernels, Optional.empty(), false);
|
||||
}
|
||||
|
||||
public static Optional<GenerationHistory> openIfPresent(Path dimensionRoot) throws IOException {
|
||||
@@ -1344,4 +1364,15 @@ public final class GenerationHistory {
|
||||
public interface BoundarySignatureCapture {
|
||||
TerrainBoundarySignatureStore.SignatureSampler capture(GenerationBoundary boundary) throws IOException;
|
||||
}
|
||||
public record FreshCreation(Path dimensionRoot, Path packSource, String packFingerprint, long worldSeed,
|
||||
GenerationEpoch.DimensionContract dimensionContract, GenerationRegistryContract registryContract) {
|
||||
public FreshCreation {
|
||||
Objects.requireNonNull(dimensionRoot, "dimensionRoot");
|
||||
Objects.requireNonNull(packSource, "packSource");
|
||||
Objects.requireNonNull(packFingerprint, "packFingerprint");
|
||||
Objects.requireNonNull(dimensionContract, "dimensionContract");
|
||||
Objects.requireNonNull(registryContract, "registryContract");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -92,6 +92,7 @@ public final class GenerationSemanticIndex {
|
||||
private final ShardPublisher publisher;
|
||||
private final PointerPublisher pointerPublisher;
|
||||
private final CatalogPublisher catalogPublisher;
|
||||
private final boolean unpublished;
|
||||
private long regionDecodeCount;
|
||||
private volatile IOException journalFailure;
|
||||
private long appendingRegionKey;
|
||||
@@ -101,7 +102,8 @@ public final class GenerationSemanticIndex {
|
||||
Path dimensionRoot,
|
||||
ShardPublisher publisher,
|
||||
PointerPublisher pointerPublisher,
|
||||
CatalogPublisher catalogPublisher
|
||||
CatalogPublisher catalogPublisher,
|
||||
boolean unpublished
|
||||
) {
|
||||
this.dimensionRoot = dimensionRoot.toAbsolutePath().normalize();
|
||||
directory = this.dimensionRoot.resolve("iris").resolve("generation").resolve("semantics");
|
||||
@@ -115,6 +117,7 @@ public final class GenerationSemanticIndex {
|
||||
this.publisher = publisher;
|
||||
this.pointerPublisher = pointerPublisher;
|
||||
this.catalogPublisher = catalogPublisher;
|
||||
this.unpublished = unpublished;
|
||||
}
|
||||
|
||||
public static GenerationSemanticIndex load(Path dimensionRoot) throws IOException {
|
||||
@@ -126,7 +129,8 @@ public final class GenerationSemanticIndex {
|
||||
Objects.requireNonNull(dimensionRoot, "dimensionRoot"),
|
||||
RegionShard::publish,
|
||||
ShardPointer::publish,
|
||||
Catalog::publish
|
||||
Catalog::publish,
|
||||
false
|
||||
);
|
||||
index.ensureStorageDirectory();
|
||||
Path catalog = index.directory.resolve(CATALOG_FILE_NAME);
|
||||
@@ -138,7 +142,13 @@ public final class GenerationSemanticIndex {
|
||||
}
|
||||
|
||||
public static GenerationSemanticIndex loadRequired(Path dimensionRoot) throws IOException {
|
||||
GenerationSemanticIndex index = load(dimensionRoot);
|
||||
return loadRequired(dimensionRoot, false);
|
||||
}
|
||||
|
||||
static GenerationSemanticIndex loadRequired(Path dimensionRoot, boolean unpublished) throws IOException {
|
||||
GenerationSemanticIndex index = new GenerationSemanticIndex(Objects.requireNonNull(dimensionRoot, "dimensionRoot"),
|
||||
RegionShard::publish, ShardPointer::publish, Catalog::publish, unpublished);
|
||||
index.loadShards();
|
||||
Path catalog = index.directory.resolve(CATALOG_FILE_NAME);
|
||||
if (!Files.isRegularFile(catalog, LinkOption.NOFOLLOW_LINKS)) {
|
||||
throw new IOException("Generation semantic shard catalog is missing: " + catalog);
|
||||
@@ -167,7 +177,8 @@ public final class GenerationSemanticIndex {
|
||||
Objects.requireNonNull(dimensionRoot, "dimensionRoot"),
|
||||
Objects.requireNonNull(publisher, "publisher"),
|
||||
Objects.requireNonNull(pointerPublisher, "pointerPublisher"),
|
||||
Objects.requireNonNull(catalogPublisher, "catalogPublisher")
|
||||
Objects.requireNonNull(catalogPublisher, "catalogPublisher"),
|
||||
false
|
||||
);
|
||||
index.loadShards();
|
||||
return index;
|
||||
@@ -254,7 +265,7 @@ public final class GenerationSemanticIndex {
|
||||
appendingBase = baseRegion;
|
||||
publicationLock.unlock();
|
||||
try {
|
||||
SemanticJournal.append(directory, regionX, regionZ, updates);
|
||||
SemanticJournal.append(directory, regionX, regionZ, updates, unpublished);
|
||||
} catch (JournalAppendFailure failure) {
|
||||
journalFailure = failure;
|
||||
throw failure;
|
||||
@@ -1928,7 +1939,8 @@ public final class GenerationSemanticIndex {
|
||||
Path directory,
|
||||
int regionX,
|
||||
int regionZ,
|
||||
List<ChunkGenerationSemantics> claims
|
||||
List<ChunkGenerationSemantics> claims,
|
||||
boolean unpublished
|
||||
) throws IOException {
|
||||
Path file = directory.resolve(journalFileName(regionX, regionZ));
|
||||
boolean created = !Files.exists(file, LinkOption.NOFOLLOW_LINKS);
|
||||
@@ -1961,14 +1973,18 @@ public final class GenerationSemanticIndex {
|
||||
channel.write(entry);
|
||||
}
|
||||
}
|
||||
Durability.force(channel);
|
||||
if (!unpublished) {
|
||||
Durability.force(channel);
|
||||
}
|
||||
if (created) {
|
||||
RegionShard.forceDirectory(directory);
|
||||
}
|
||||
} catch (IOException failure) {
|
||||
try {
|
||||
channel.truncate(size);
|
||||
Durability.force(channel);
|
||||
if (!unpublished) {
|
||||
Durability.force(channel);
|
||||
}
|
||||
if (created) {
|
||||
RegionShard.forceDirectory(directory);
|
||||
}
|
||||
|
||||
@@ -49,14 +49,16 @@ public final class SavedBiomeStore {
|
||||
|
||||
private final Path dimensionRoot;
|
||||
private final Path directory;
|
||||
private final boolean unpublished;
|
||||
private final WriteStripe[] regionLocks = new WriteStripe[64];
|
||||
private final LinkedHashMap<Long, RegionIndex> regions = new LinkedHashMap<>(64, 0.75F, true);
|
||||
private final LinkedHashMap<Long, Optional<SavedBiomeChunk>> chunks = new LinkedHashMap<>(128, 0.75F, true);
|
||||
private long cachedBytes;
|
||||
private volatile IOException writeFailure;
|
||||
|
||||
private SavedBiomeStore(Path dimensionRoot) throws IOException {
|
||||
private SavedBiomeStore(Path dimensionRoot, boolean unpublished) throws IOException {
|
||||
this.dimensionRoot = Objects.requireNonNull(dimensionRoot, "dimensionRoot").toAbsolutePath().normalize();
|
||||
this.unpublished = unpublished;
|
||||
directory = this.dimensionRoot.resolve("iris/generation/biomes");
|
||||
requireSafeAncestors();
|
||||
for (int index = 0; index < regionLocks.length; index++) {
|
||||
@@ -65,7 +67,11 @@ public final class SavedBiomeStore {
|
||||
}
|
||||
|
||||
public static SavedBiomeStore open(Path dimensionRoot) throws IOException {
|
||||
return new SavedBiomeStore(dimensionRoot);
|
||||
return open(dimensionRoot, false);
|
||||
}
|
||||
|
||||
static SavedBiomeStore open(Path dimensionRoot, boolean unpublished) throws IOException {
|
||||
return new SavedBiomeStore(dimensionRoot, unpublished);
|
||||
}
|
||||
|
||||
public Path storageDirectory() {
|
||||
@@ -195,11 +201,14 @@ public final class SavedBiomeStore {
|
||||
RegionIndex region = region(first.chunkX(), first.chunkZ());
|
||||
ensureRegionFile(region);
|
||||
long offset = 0L;
|
||||
boolean durable = false;
|
||||
boolean appendCompleted = false;
|
||||
try (RandomAccessFile output = new RandomAccessFile(region.path.toFile(), "rw")) {
|
||||
offset = output.length();
|
||||
long length = 0L;
|
||||
ByteBuffer[] records = new ByteBuffer[claimed.size()];
|
||||
int recordIndex = 0;
|
||||
for (PendingWrite pending : claimed.values()) {
|
||||
records[recordIndex++] = ByteBuffer.wrap(pending.record);
|
||||
length += pending.record.length;
|
||||
}
|
||||
if (offset + length > MAXIMUM_REGION_BYTES) {
|
||||
@@ -207,15 +216,24 @@ public final class SavedBiomeStore {
|
||||
}
|
||||
try {
|
||||
output.seek(offset);
|
||||
for (PendingWrite pending : claimed.values()) {
|
||||
output.write(pending.record);
|
||||
FileChannel channel = output.getChannel();
|
||||
int firstBuffer = 0;
|
||||
while (firstBuffer < records.length) {
|
||||
channel.write(records, firstBuffer, records.length - firstBuffer);
|
||||
while (firstBuffer < records.length && !records[firstBuffer].hasRemaining()) {
|
||||
firstBuffer++;
|
||||
}
|
||||
}
|
||||
output.getChannel().force(true);
|
||||
durable = true;
|
||||
if (!unpublished) {
|
||||
output.getChannel().force(true);
|
||||
}
|
||||
appendCompleted = true;
|
||||
} catch (IOException failure) {
|
||||
try {
|
||||
output.setLength(offset);
|
||||
output.getChannel().force(true);
|
||||
if (!unpublished) {
|
||||
output.getChannel().force(true);
|
||||
}
|
||||
} catch (IOException rollback) {
|
||||
failure.addSuppressed(rollback);
|
||||
writeFailure = failure;
|
||||
@@ -223,7 +241,7 @@ public final class SavedBiomeStore {
|
||||
throw failure;
|
||||
}
|
||||
} catch (IOException failure) {
|
||||
if (durable) {
|
||||
if (appendCompleted) {
|
||||
writeFailure = failure;
|
||||
}
|
||||
throw failure;
|
||||
|
||||
Reference in new issue
Block a user