From cf572c47af5a08d6588a37be9fa36ac416a15a1a Mon Sep 17 00:00:00 2001 From: mcrcortex <18544518+MCRcortex@users.noreply.github.com> Date: Wed, 9 Jul 2025 03:35:22 +1000 Subject: [PATCH] >50% perf improvement over 2.24.4 aswell as hugely significant improvements to ram usage (>50% reduction in ram usage (probably near 60%)) --- .../pepsoft/minecraft/MC115AnvilChunk.java | 2 +- .../pepsoft/minecraft/MC118AnvilChunk.java | 11 +- .../java/org/pepsoft/minecraft/Material.java | 30 +- .../org/pepsoft/util/PackedArrayCube.java | 8 +- .../org/pepsoft/util/Palleted16Section.java | 311 ++++++++++++++++++ .../org/pepsoft/worldpainter/Terrain.java | 47 ++- .../worldpainter/exporting/WorldRegion.java | 16 +- .../worldpainter/ExportProgressDialog.java | 39 ++- 8 files changed, 409 insertions(+), 55 deletions(-) create mode 100644 WorldPainter/WPCore/src/main/java/org/pepsoft/util/Palleted16Section.java diff --git a/WorldPainter/WPCore/src/main/java/org/pepsoft/minecraft/MC115AnvilChunk.java b/WorldPainter/WPCore/src/main/java/org/pepsoft/minecraft/MC115AnvilChunk.java index a76820840..326008b06 100644 --- a/WorldPainter/WPCore/src/main/java/org/pepsoft/minecraft/MC115AnvilChunk.java +++ b/WorldPainter/WPCore/src/main/java/org/pepsoft/minecraft/MC115AnvilChunk.java @@ -824,7 +824,7 @@ public class Section extends AbstractNBTItem implements SectionedChunk.Section { public CompoundTag toNBT() { setByte(TAG_Y, level); - final PackedArrayCube.PackedData packedMaterials = materials.pack(AIR); + final PackedArrayCube.PackedData packedMaterials = materials.pack(AIR); final List paletteList = new ArrayList<>(packedMaterials.palette.length); for (Material material: packedMaterials.palette) { final CompoundTag paletteEntry = new CompoundTag("", Collections.emptyMap()); diff --git a/WorldPainter/WPCore/src/main/java/org/pepsoft/minecraft/MC118AnvilChunk.java b/WorldPainter/WPCore/src/main/java/org/pepsoft/minecraft/MC118AnvilChunk.java index 425b9e08e..e6b66a2a9 100644 --- a/WorldPainter/WPCore/src/main/java/org/pepsoft/minecraft/MC118AnvilChunk.java +++ b/WorldPainter/WPCore/src/main/java/org/pepsoft/minecraft/MC118AnvilChunk.java @@ -11,6 +11,7 @@ import org.jnbt.*; import org.pepsoft.minecraft.MC118AnvilChunk.Section.IncompleteSectionException; import org.pepsoft.util.PackedArrayCube; +import org.pepsoft.util.Palleted16Section; import org.pepsoft.util.mdc.MDCCapturingRuntimeException; import org.pepsoft.worldpainter.exporting.MinecraftWorld; import org.slf4j.Logger; @@ -513,7 +514,7 @@ public void setMaterial(int x, int y, int z, Material material) { } if (section.singleMaterial != null) { if (material != section.singleMaterial) { - section.materials = new PackedArrayCube<>(16, 4, false, Material.class); + section.materials = new Palleted16Section<>(Material.class); if (section.singleMaterial != AIR) { section.materials.fill(section.singleMaterial); } @@ -817,7 +818,7 @@ public static class Section extends AbstractNBTItem implements SectionedChunk.Se final LongArrayTag blockStatesDataTag = (LongArrayTag) blockStatesTag.getTag(TAG_DATA_); if (blockStatesDataTag != null) { final long[] blockStates = blockStatesDataTag.getValue(); - materials = new PackedArrayCube<>(16, blockStates, palette, 4, false, Material.class); + materials = new Palleted16Section<>(16, blockStates, palette, 4, false, Material.class); } else if (palette.length == 1) { // Entire section filled with one material singleMaterial = (palette[0] == null) ? AIR : palette[0]; @@ -875,7 +876,7 @@ public CompoundTag toNBT() { if (singleMaterial != null) { setMap(TAG_BLOCK_STATES_, ImmutableMap.of(TAG_PALETTE_, new ListTag<>(TAG_PALETTE_, CompoundTag.class, singletonList(createPaletteEntry(singleMaterial))))); } else { - PackedArrayCube.PackedData packedMaterials = materials.pack(); + PackedArrayCube.PackedData packedMaterials = materials.pack(); List palette = new ArrayList<>(packedMaterials.palette.length); for (Material material: packedMaterials.palette) { palette.add(createPaletteEntry(material)); @@ -886,7 +887,7 @@ public CompoundTag toNBT() { if (singleBiome != null) { setMap(TAG_BIOMES_, ImmutableMap.of(TAG_PALETTE_, new ListTag<>(TAG_PALETTE_, StringTag.class, singletonList(new StringTag("", singleBiome))))); } else if (biomes != null) { - PackedArrayCube.PackedData packedBiomes = biomes.pack(); + PackedArrayCube.PackedData packedBiomes = biomes.pack(); List palette = new ArrayList<>(packedBiomes.palette.length); for (String biome: packedBiomes.palette) { palette.add(new StringTag("", biome)); @@ -991,7 +992,7 @@ private CompoundTag createPaletteEntry(Material material) { byte[] skyLight; byte[] blockLight; // Exactly one of these should be set: - PackedArrayCube materials; + Palleted16Section materials; Material singleMaterial; // At most one of these should be set: PackedArrayCube biomes; diff --git a/WorldPainter/WPCore/src/main/java/org/pepsoft/minecraft/Material.java b/WorldPainter/WPCore/src/main/java/org/pepsoft/minecraft/Material.java index 12d1d4713..0dbfac8f5 100644 --- a/WorldPainter/WPCore/src/main/java/org/pepsoft/minecraft/Material.java +++ b/WorldPainter/WPCore/src/main/java/org/pepsoft/minecraft/Material.java @@ -1194,14 +1194,7 @@ public static Material getByCombinedIndex(int index) { * @return The single instance of the specified material. */ public static Material get(Identity identity) { - synchronized (ALL_MATERIALS) { - Material material = ALL_MATERIALS.get(identity); - if (material == null) { - material = new Material(identity); - ALL_MATERIALS.put(identity, material); - } - return material; - } + return ALL_MATERIALS.computeIfAbsent(identity, Material::new); } /** @@ -1299,7 +1292,7 @@ public static Set getAllNames() { * Get all realised materials. */ public static Collection getAllMaterials() { - return Collections.unmodifiableCollection(ALL_MATERIALS.values()); + return unmodifiableCollection(ALL_MATERIALS.values()); } /** @@ -1644,7 +1637,7 @@ public static boolean guessTreeRelated(String name) { * legacy materials. 12-bit block ids above 255 are created on the fly. */ private static final Material[] LEGACY_MATERIALS = new Material[4096]; - private static final Map ALL_MATERIALS = new HashMap<>(); + private static final ConcurrentHashMap ALL_MATERIALS = new ConcurrentHashMap<>(); private static final Map> SIMPLE_NAMES_BY_NAMESPACE = new HashMap<>(); private static final Map DEFAULT_MATERIALS_BY_NAME = new HashMap<>(); @@ -2221,6 +2214,7 @@ public Identity(String name, Map properties) { } this.name = name.intern(); this.properties = ((properties != null) && (! properties.isEmpty())) ? ImmutableMap.copyOf(properties) : null; + this.cHash(); } /** @@ -2255,14 +2249,15 @@ boolean containsPropertyWithValues(String propertyName, String... values) { @Override public boolean equals(Object o) { - return (o instanceof Identity) + return (o instanceof Identity) && + this.cHash() == ((Identity)o).cHash() && name.equals(((Identity) o).name) && Objects.equals(properties, ((Identity) o).properties); } @Override public int hashCode() { - return name.hashCode() * 37 + ((properties != null) ? properties.hashCode() : 0); + return (int) this.cHash(); } @Override @@ -2273,6 +2268,17 @@ public String toString() { public final String name; public final Map properties; + private transient long hash; + + private long cHash() { + if (this.hash != 0) return this.hash; + long hash = (this.name.hashCode()+527383922224181L)*578967237891L; + hash ^= hash>>32; hash ^= this.properties != null ? this.properties.hashCode() : 7777777777L; + hash += 58917598759833331L; hash *= 569586253252351L; + hash ^= hash>>32; + return this.hash = (hash==0)?1:hash; + } + private static final long serialVersionUID = 1L; } diff --git a/WorldPainter/WPCore/src/main/java/org/pepsoft/util/PackedArrayCube.java b/WorldPainter/WPCore/src/main/java/org/pepsoft/util/PackedArrayCube.java index a30579549..f06b2ed61 100644 --- a/WorldPainter/WPCore/src/main/java/org/pepsoft/util/PackedArrayCube.java +++ b/WorldPainter/WPCore/src/main/java/org/pepsoft/util/PackedArrayCube.java @@ -128,7 +128,7 @@ public boolean isEmpty() { * * @return The packed data. */ - public PackedData pack() { + public PackedData pack() { return pack(null); } @@ -140,7 +140,7 @@ public PackedData pack() { * @return The packed data. */ @SuppressWarnings("unchecked") // Guaranteed by Java library - public PackedData pack(T nullSubstitute) { + public PackedData pack(T nullSubstitute) { // Create the palette. We have to do this first, because otherwise we don't know how many bits the indices will // be and therefore how big to make the data array final Map reversePalette = new HashMap<>(); @@ -225,7 +225,7 @@ public PackedData pack(T nullSubstitute) { } } } - return new PackedData(data, palette.toArray((T[]) Array.newInstance(type, palette.size()))); + return new PackedData(data, palette.toArray((T[]) Array.newInstance(type, palette.size()))); } private int offset(int x, int y, int z) { @@ -241,7 +241,7 @@ private T substituteNull(T value, T nullSubstitute) { private final boolean straddleLongs; private final T[] values; - public class PackedData { + public static class PackedData { public PackedData(long[] data, T[] palette) { this.data = data; this.palette = palette; diff --git a/WorldPainter/WPCore/src/main/java/org/pepsoft/util/Palleted16Section.java b/WorldPainter/WPCore/src/main/java/org/pepsoft/util/Palleted16Section.java new file mode 100644 index 000000000..85137b330 --- /dev/null +++ b/WorldPainter/WPCore/src/main/java/org/pepsoft/util/Palleted16Section.java @@ -0,0 +1,311 @@ +package org.pepsoft.util; + +import java.lang.reflect.Array; +import java.util.*; + +public class Palleted16Section { + public Palleted16Section(Class type) { + this.type = type; + this.data = new PalletData<>(type); + } + + public Palleted16Section(int size, long[] data, T[] palette, int minimumWordSize, boolean straddleLongs, Class type) { + this(type); + + // Sanity check + for (int i = 0; i < palette.length; i++) { + if ((palette[i] != null) && (! type.isAssignableFrom(palette[i].getClass()))) { + throw new IllegalArgumentException("Palette[" + i + "] is not a " + type.getSimpleName() + " (actual type: " + palette[i].getClass().getName() + "; value: " + palette[i] + ")"); + } + } + + final int wordSize = Math.max(minimumWordSize, (int) Math.ceil(Math.log(palette.length) / Math.log(2))); + final int expectedPackedDataArrayLengthInBytes = wordSize * 4096 / 8; + final int dataArrayLengthInBytes = data.length * 8; + if (wordSize == 4) { + // Optimised special case + for (int w = 0; w < 4096; w += 16) { + final long arrayValue = data[w >> 4]; + this.data.set(w , palette[(int) (arrayValue & 0x000000000000000fL) ]); + this.data.set(w + 1, palette[(int) ((arrayValue & 0x00000000000000f0L) >> 4)]); + this.data.set(w + 2, palette[(int) ((arrayValue & 0x0000000000000f00L) >> 8)]); + this.data.set(w + 3, palette[(int) ((arrayValue & 0x000000000000f000L) >> 12)]); + this.data.set(w + 4, palette[(int) ((arrayValue & 0x00000000000f0000L) >> 16)]); + this.data.set(w + 5, palette[(int) ((arrayValue & 0x0000000000f00000L) >> 20)]); + this.data.set(w + 6, palette[(int) ((arrayValue & 0x000000000f000000L) >> 24)]); + this.data.set(w + 7, palette[(int) ((arrayValue & 0x00000000f0000000L) >> 28)]); + this.data.set(w + 8, palette[(int) ((arrayValue & 0x0000000f00000000L) >> 32)]); + this.data.set(w + 9, palette[(int) ((arrayValue & 0x000000f000000000L) >> 36)]); + this.data.set(w + 10, palette[(int) ((arrayValue & 0x00000f0000000000L) >> 40)]); + this.data.set(w + 11, palette[(int) ((arrayValue & 0x0000f00000000000L) >> 44)]); + this.data.set(w + 12, palette[(int) ((arrayValue & 0x000f000000000000L) >> 48)]); + this.data.set(w + 13, palette[(int) ((arrayValue & 0x00f0000000000000L) >> 52)]); + this.data.set(w + 14, palette[(int) ((arrayValue & 0x0f00000000000000L) >> 56)]); + this.data.set(w + 15, palette[(int) ((arrayValue & 0xf000000000000000L) >>> 60)]); + } + } else if (dataArrayLengthInBytes != expectedPackedDataArrayLengthInBytes) { + // A weird format where the values are packed per long (leaving bits unused). Unpack each long individually + final long mask = (long) (Math.pow(2, wordSize)) - 1; + final int bitsInUse = (64 / wordSize) * wordSize; + int materialIndex = 0; + outer: + for (long packedData: data) { + for (int offset = 0; offset < bitsInUse; offset += wordSize) { + this.data.set(materialIndex++, palette[(int) ((packedData & (mask << offset)) >>> offset)]); + if (materialIndex >= 4096) { + // The last long was not fully used + break outer; + } + } + } + } else { + final BitSet bitSet = BitSet.valueOf(data); + for (int w = 0; w < 4096; w++) { + final int wordOffset = w * wordSize; + int index = 0; + for (int b = 0; b < wordSize; b++) { + index |= bitSet.get(wordOffset + b) ? 1 << b : 0; + } + this.data.set(w, palette[index]); + } + } + } + + private static int idx(int x, int y, int z) {return x+(16*y)+(16*16*z);} + public T getValue(int x, int y, int z) { + return this.data.get(idx(x,y,z)); + } + + public void setValue(int x, int y, int z, T value) { + this.data.set(idx(x,y,z), value); + } + + public void fill(T value) { + this.data.fill(value); + } + + public boolean isEmpty() { + for (long a : this.data.data) { + if (a!=0) return false; + } + return true; + } + + /** + * Pack the data into a palette and a {@code long} array. {@code null} values are not replaced and if any of the + * values are {@code null}, the palette will contain a {@code null} entry. + * + * @return The packed data. + */ + public PackedArrayCube.PackedData pack() { + return pack(null); + } + + /** + * Pack the data into a palette and a {@code long} array. + * + * @param nullSubstitute The value to replace {@code null} values with, if any. May be {@code null}, in which case + * one of the palette entries may be {@code null}. + * @return The packed data. + */ + @SuppressWarnings("unchecked") // Guaranteed by Java library + public PackedArrayCube.PackedData pack(T nullSubstitute) { + // Create the palette. We have to do this first, because otherwise we don't know how many bits the indices will + // be and therefore how big to make the data array + final Map reversePalette = new HashMap<>(); + final List palette = new LinkedList<>(); + for (int i = 0; i < 4096; i++) { + T value = this.data.get(i); + if (value == null) { + value = nullSubstitute; + } + if (! reversePalette.containsKey(value)) { + reversePalette.put(value, palette.size()); + palette.add(value); + } + } + + // Create the data array and fill it, using the appropriate length palette indices so that it just fits + final int paletteIndexSize = Math.max((int) Math.ceil(Math.log(palette.size()) / Math.log(2)), 4); + final long[] data; + if (paletteIndexSize == 4) { + // Optimised special case + data = new long[4096 >> 4]; + for (int i = 0; i < 4096; i += 16) { + data[i >> 4] = + reversePalette.get(substituteNull(this.data.get(i ), nullSubstitute)) + | (reversePalette.get(substituteNull(this.data.get(i + 1), nullSubstitute)) << 4) + | (reversePalette.get(substituteNull(this.data.get(i + 2), nullSubstitute)) << 8) + | (reversePalette.get(substituteNull(this.data.get(i + 3), nullSubstitute)) << 12) + | (reversePalette.get(substituteNull(this.data.get(i + 4), nullSubstitute)) << 16) + | (reversePalette.get(substituteNull(this.data.get(i + 5), nullSubstitute)) << 20) + | (reversePalette.get(substituteNull(this.data.get(i + 6), nullSubstitute)) << 24) + | ((long) (reversePalette.get(substituteNull(this.data.get(i + 7), nullSubstitute))) << 28) + | ((long) (reversePalette.get(substituteNull(this.data.get(i + 8), nullSubstitute))) << 32) + | ((long) (reversePalette.get(substituteNull(this.data.get(i + 9), nullSubstitute))) << 36) + | ((long) (reversePalette.get(substituteNull(this.data.get(i + 10), nullSubstitute))) << 40) + | ((long) (reversePalette.get(substituteNull(this.data.get(i + 11), nullSubstitute))) << 44) + | ((long) (reversePalette.get(substituteNull(this.data.get(i + 12), nullSubstitute))) << 48) + | ((long) (reversePalette.get(substituteNull(this.data.get(i + 13), nullSubstitute))) << 52) + | ((long) (reversePalette.get(substituteNull(this.data.get(i + 14), nullSubstitute))) << 56) + | ((long) (reversePalette.get(substituteNull(this.data.get(i + 15), nullSubstitute))) << 60); + } + } else { + final int wordsPerLong = 64 / paletteIndexSize; + final int dataSize = 4096 / wordsPerLong + (((4096 % wordsPerLong) == 0) ? 0 : 1); // Round up + final BitSet dataBits = new BitSet(dataSize * 64); + for (int i = 0; i < 4096; i++) { + final int offset = (i / wordsPerLong) * 64 + (i % wordsPerLong) * paletteIndexSize; + final int index = reversePalette.get(substituteNull(this.data.get(i), nullSubstitute)); + for (int j = 0; j < paletteIndexSize; j++) { + if ((index & (1 << j)) != 0) { + dataBits.set(offset + j); + } + } + } + final long[] dataArray = dataBits.toLongArray(); + if (dataArray.length == dataSize) { + data = dataArray; + } else { + // If the last bits of the BitSet are zero, toLongArray() does not return those longs, but + // Minecraft can't handle that + data = Arrays.copyOf(dataArray, dataSize); + } + } + return new PackedArrayCube.PackedData(data, palette.toArray((T[]) Array.newInstance(type, palette.size()))); + } + + private static J substituteNull(J value, J nullSubstitute) { + return (value == null) ? nullSubstitute : value; + } + + private final Class type; + private final PalletData data; + + + //2,4,8,16 pallet sizes + private static final class PalletData { + private long[] data; + private J[] pallet; + private int bits; + private int ibits; + + public PalletData(Class clz) { + this.bits = 2; this.ibits = Integer.numberOfTrailingZeros(64/this.bits); + this.data = new long[4096/(64/this.bits)]; + this.pallet = (J[]) Array.newInstance(clz, 1<>this.ibits]; + idx = (idx&((1<>idx)&((1<>this.ibits; + idx = (idx&((1<>>32,ob); + } + this.data = newData; + } + return id; + } + + private static long expand(long in, int bits) { + long out = 0; + long msk = (1L<>>=bits; + } + return out; + } + + public void fill(J value) { + if (value == null) { + Arrays.fill(this.pallet, null); + Arrays.fill(this.data, 0); + } else { + if (this.bits != 2) { + this.bits = 2;this.ibits = Integer.numberOfTrailingZeros(64/this.bits); + this.data = new long[4096/(64/this.bits)]; + this.pallet = (J[]) Array.newInstance(this.pallet.getClass().componentType(), 1< test = new PalletData<>(Object.class); + Object[] aa = new Object[4096]; + for (int i = 0; i < 4096; i++) { + aa[i] = new Object(); + test.set(i, aa[i]); + if (test.get(i) != aa[i]) { + test.get(i); + throw new IllegalStateException(); + } + } + for (int i = 0; i < 4096; i++) { + if (test.get(i) != aa[i]) { + throw new IllegalStateException(); + } + } + for (int i = 0; i < 4096; i++) { + test.set(i, new Object()); + } + for (int i = 0; i < 4096; i++) { + test.set(i, new Object()); + } + for (int i = 0; i < 4096; i++) { + test.set(i, new Object()); + } + for (int i = 0; i < 4096; i++) { + test.set(i, new Object()); + } + for (int i = 0; i < 4096; i++) { + test.set(i, new Object()); + } + for (int i = 0; i < 4096; i++) { + test.set(i, new Object()); + } + } +} \ No newline at end of file diff --git a/WorldPainter/WPCore/src/main/java/org/pepsoft/worldpainter/Terrain.java b/WorldPainter/WPCore/src/main/java/org/pepsoft/worldpainter/Terrain.java index 29929a447..b168dc414 100644 --- a/WorldPainter/WPCore/src/main/java/org/pepsoft/worldpainter/Terrain.java +++ b/WorldPainter/WPCore/src/main/java/org/pepsoft/worldpainter/Terrain.java @@ -10,6 +10,7 @@ import org.pepsoft.util.IconUtils; import org.pepsoft.util.PerlinNoise; import org.pepsoft.util.RandomField; +import org.pepsoft.util.UnsafeRandom; import org.pepsoft.worldpainter.layers.plants.Plant; import org.pepsoft.worldpainter.layers.plants.Plants; import org.pepsoft.worldpainter.objects.GenericObject; @@ -230,7 +231,7 @@ public WPObject getSurfaceObject(Platform platform, long seed, int x, int y, int if (waterBlocksAbove > 0) { return null; } - final int rnd = new Random(seed + (x * 65537L) + (y * 4099L)).nextInt(CACTUS_CHANCE); + final int rnd = UnsafeRandom.fastNextInt(seed + (x * 65537L) + (y * 4099L), CACTUS_CHANCE); final int cactusHeight; boolean shrub = false; if (rnd < 3) { @@ -276,7 +277,7 @@ public WPObject getSurfaceObject(Platform platform, long seed, int x, int y, int if (waterBlocksAbove > 0) { return null; } - final int rnd = new Random(seed + (x * 65537L) + (y * 4099L)).nextInt(FIRE_CHANCE); + final int rnd = UnsafeRandom.fastNextInt(seed + (x * 65537L) + (y * 4099L), FIRE_CHANCE); if (rnd == 0) { return OBJECT_FIRE; } else { @@ -392,7 +393,7 @@ public Material getMaterial(Platform platform, long seed, int x, int y, int z, i @Override public WPObject getSurfaceObject(Platform platform, long seed, int x, int y, int waterBlocksAbove) { if (platform.capabilities.contains(NAME_BASED) && (waterBlocksAbove > 0)) { - final Random rnd = new Random(seed + (x * 65537L) + (y * 4099L)); + final UnsafeRandom rnd = new UnsafeRandom(seed + (x * 65537L) + (y * 4099L)); if (grassNoise.getSeed() != (seed + GRASS_SEED_OFFSET)) { grassNoise.setSeed(seed + GRASS_SEED_OFFSET); tallGrassNoise.setSeed(seed + DOUBLE_TALL_GRASS_SEED_OFFSET); @@ -1026,7 +1027,7 @@ public WPObject getSurfaceObject(Platform platform, long seed, int x, int y, int if (waterBlocksAbove > 0) { return null; } - final int rnd = new Random(seed + (x * 65537L) + (y * 4099L)).nextInt(SHRUB_CHANCE); + final int rnd = UnsafeRandom.fastNextInt(seed + (x * 65537L) + (y * 4099L), SHRUB_CHANCE); if (rnd < 3) { return Plants.DEAD_SHRUB.realise(1, platform); } else { @@ -1037,7 +1038,7 @@ public WPObject getSurfaceObject(Platform platform, long seed, int x, int y, int private void init(long seed) { this.seed = seed; perlinNoise.setSeed(seed + NOISE_SEED_OFFSET); - final Random random = new Random(seed); + final UnsafeRandom random = new UnsafeRandom(seed); Arrays.fill(LAYERS, Material.HARDENED_CLAY); for (int i = 0; i < LAYER_COUNT / 2; i++) { final int index = random.nextInt(LAYER_COUNT - 1); @@ -1063,7 +1064,7 @@ public WPObject getSurfaceObject(Platform platform, long seed, int x, int y, int if (waterBlocksAbove > 0) { return null; } - final int rnd = new Random(seed + (x * 65537L) + (y * 4099L)).nextInt(CACTUS_CHANCE); + final int rnd = UnsafeRandom.fastNextInt(seed + (x * 65537L) + (y * 4099L), CACTUS_CHANCE); final int cactusHeight; boolean shrub = false; if (rnd < 3) { @@ -1096,31 +1097,25 @@ public Material getMaterial(Platform platform, long seed, int x, int y, int z, i graniteNoise.setSeed(seed + GRANITE_SEED_OFFSET); dioriteNoise.setSeed(seed + DIORITE_SEED_OFFSET); andesiteNoise.setSeed(seed + ANDESITE_SEED_OFFSET); - RANDOM.setSeed(seed); } - if ((z >= 0) || ((z >= -4) && (z >= -RANDOM.nextInt(5)))) { // TODO this is not stable - if (graniteNoise.getPerlinNoise(x / SMALL_BLOBS, y / SMALL_BLOBS, z / SMALL_BLOBS) > GRANITE_CHANCE) { - return Material.GRANITE; - } else if (dioriteNoise.getPerlinNoise(x / SMALL_BLOBS, y / SMALL_BLOBS, z / SMALL_BLOBS) > DIORITE_CHANCE) { - return Material.DIORITE; - } else if (andesiteNoise.getPerlinNoise(x / SMALL_BLOBS, y / SMALL_BLOBS, z / SMALL_BLOBS) > ANDESITE_CHANCE) { - return Material.ANDESITE; - } else { - return Material.STONE; - } + boolean deep = z<-4||(z<1&&z>=-UnsafeRandom.fastNextInt(mixStafford13(mixStafford13(seed^(Integer.toUnsignedLong(x)<<32)^Integer.toUnsignedLong(z))^Integer.toUnsignedLong(y)+158737029677209371L), 5)); + if (graniteNoise.getPerlinNoise(x / SMALL_BLOBS, y / SMALL_BLOBS, z / SMALL_BLOBS) > GRANITE_CHANCE) { + return deep?Material.TUFF:Material.GRANITE; + } else if(dioriteNoise.getPerlinNoise(x / SMALL_BLOBS, y / SMALL_BLOBS, z / SMALL_BLOBS) > DIORITE_CHANCE) { + return deep?Material.DEEPSLATE_X:Material.DIORITE; + } else if(andesiteNoise.getPerlinNoise(x / SMALL_BLOBS, y / SMALL_BLOBS, z / SMALL_BLOBS) > ANDESITE_CHANCE) { + return deep?Material.DEEPSLATE_Z:Material.ANDESITE; } else { - if (graniteNoise.getPerlinNoise(x / SMALL_BLOBS, y / SMALL_BLOBS, z / SMALL_BLOBS) > GRANITE_CHANCE) { - return Material.TUFF; - } else if (dioriteNoise.getPerlinNoise(x / SMALL_BLOBS, y / SMALL_BLOBS, z / SMALL_BLOBS) > DIORITE_CHANCE) { - return Material.DEEPSLATE_X; - } else if (andesiteNoise.getPerlinNoise(x / SMALL_BLOBS, y / SMALL_BLOBS, z / SMALL_BLOBS) > ANDESITE_CHANCE) { - return Material.DEEPSLATE_Z; - } else { - return Material.DEEPSLATE_Y; - } + return deep?Material.DEEPSLATE_Y:Material.STONE; } } + private static long mixStafford13(long seed) { + seed = (seed ^ seed >>> 30) * -4658895280553007687L; + seed = (seed ^ seed >>> 27) * -7723592293110705685L; + return seed ^ seed >>> 31; + } + private final PerlinNoise graniteNoise = new PerlinNoise(0); private final PerlinNoise dioriteNoise = new PerlinNoise(0); private final PerlinNoise andesiteNoise = new PerlinNoise(0); diff --git a/WorldPainter/WPCore/src/main/java/org/pepsoft/worldpainter/exporting/WorldRegion.java b/WorldPainter/WPCore/src/main/java/org/pepsoft/worldpainter/exporting/WorldRegion.java index f10e2b50e..a17b52388 100644 --- a/WorldPainter/WPCore/src/main/java/org/pepsoft/worldpainter/exporting/WorldRegion.java +++ b/WorldPainter/WPCore/src/main/java/org/pepsoft/worldpainter/exporting/WorldRegion.java @@ -41,7 +41,7 @@ public WorldRegion(File worldDir, int dimension, int regionX, int regionZ, int m ChunkStore chunkStore = PlatformManager.getInstance().getChunkStore(platform, worldDir, dimension); for (int x = lowestX; x <= highestX; x++) { for (int z = lowestZ; z <= highestZ; z++) { - chunks[x - (regionX << 5) + 1][z - (regionZ << 5) + 1] = chunkStore.getChunkForEditing(x, z); + chunks[getIdx(x - (regionX << 5) + 1, z - (regionZ << 5) + 1)] = chunkStore.getChunkForEditing(x, z); } } } @@ -210,7 +210,7 @@ public boolean isChunkPresent(int x, int z) { if ((x < -1) || (x >= (CHUNKS_PER_SIDE + 1)) || (z < -1) || (z >= (CHUNKS_PER_SIDE + 1))) { return false; } else { - return chunks[x + 1][z + 1] != null; + return chunks[getIdx(x + 1, z + 1)] != null; } } @@ -221,7 +221,7 @@ public Chunk getChunk(int x, int z) { if ((x < -1) || (x >= (CHUNKS_PER_SIDE + 1)) || (z < -1) || (z >= (CHUNKS_PER_SIDE + 1))) { return null; } else { - return chunks[x + 1][z + 1]; + return chunks[getIdx(x + 1, z + 1)]; } } @@ -240,7 +240,7 @@ public void addChunk(Chunk chunk) { int localX = chunk.getxPos() - (regionX << 5); int localZ = chunk.getzPos() - (regionZ << 5); if ((localX >= -1) && (localX <= CHUNKS_PER_SIDE) && (localZ >= -1) && (localZ <= CHUNKS_PER_SIDE)) { - chunks[localX + 1][localZ + 1] = chunk; + chunks[getIdx(localX + 1, localZ + 1)] = chunk; } } @@ -259,7 +259,7 @@ public void save(File worldDir, int dimension) { chunkStore.doInTransaction(() -> { for (int x = 0; x < CHUNKS_PER_SIDE; x++) { for (int z = 0; z < CHUNKS_PER_SIDE; z++) { - final Chunk chunk = chunks[x + 1][z + 1]; + final Chunk chunk = chunks[getIdx(x + 1, z + 1)]; if (chunk != null) { chunkStore.saveChunk(chunk); } @@ -269,9 +269,13 @@ public void save(File worldDir, int dimension) { } } + private static int getIdx(int x, int z) { + return (CHUNKS_PER_SIDE + 2)*x+z; + } + private final int minHeight, maxHeight; private final Platform platform; - private final Chunk[][] chunks = new Chunk[CHUNKS_PER_SIDE + 2][CHUNKS_PER_SIDE + 2]; + private final Chunk[] chunks = new Chunk[(CHUNKS_PER_SIDE + 2)*(CHUNKS_PER_SIDE + 2)]; private final int regionX, regionZ; private final BlockBasedPlatformProvider platformProvider; diff --git a/WorldPainter/WPGUI/src/main/java/org/pepsoft/worldpainter/ExportProgressDialog.java b/WorldPainter/WPGUI/src/main/java/org/pepsoft/worldpainter/ExportProgressDialog.java index d920a2be9..741aac7d1 100644 --- a/WorldPainter/WPGUI/src/main/java/org/pepsoft/worldpainter/ExportProgressDialog.java +++ b/WorldPainter/WPGUI/src/main/java/org/pepsoft/worldpainter/ExportProgressDialog.java @@ -185,7 +185,44 @@ public Map execute(ProgressReceiver progressReceive WorldExporter exporter = PlatformManager.getInstance().getExporter(world, exportSettings); try { backupDir = exporter.selectBackupDir(baseDir, name); - return exporter.export(baseDir, name, backupDir, progressReceiver); + + ProgressReceiver finalProgressReceiver = progressReceiver; + return exporter.export(baseDir, name, backupDir, new ProgressReceiver() { + @Override + public void setProgress(float progress) throws OperationCancelled { + + } + + @Override + public void exceptionThrown(Throwable exception) { + finalProgressReceiver.exceptionThrown(exception); + } + + @Override + public void done() { + finalProgressReceiver.done(); + } + + @Override + public void setMessage(String message) throws OperationCancelled { + + } + + @Override + public void checkForCancellation() throws OperationCancelled { + finalProgressReceiver.checkForCancellation(); + } + + @Override + public void reset() throws OperationCancelled { + finalProgressReceiver.reset(); + } + + @Override + public void subProgressStarted(SubProgressReceiver subProgressReceiver) throws OperationCancelled { + finalProgressReceiver.subProgressStarted(subProgressReceiver); + } + }); } catch (IOException e) { throw new RuntimeException("I/O error while exporting world", e); } catch (RuntimeException e) {