diff --git a/src/main/java/net/minestom/server/instance/palette/AdaptivePalette.java b/src/main/java/net/minestom/server/instance/palette/AdaptivePalette.java index 4de00254c..b5016e132 100644 --- a/src/main/java/net/minestom/server/instance/palette/AdaptivePalette.java +++ b/src/main/java/net/minestom/server/instance/palette/AdaptivePalette.java @@ -1,5 +1,8 @@ package net.minestom.server.instance.palette; +import it.unimi.dsi.fastutil.ints.IntOpenHashSet; +import it.unimi.dsi.fastutil.ints.IntSet; +import net.minestom.server.utils.MathUtils; import net.minestom.server.utils.binary.BinaryWriter; import org.jetbrains.annotations.NotNull; @@ -15,7 +18,7 @@ final class AdaptivePalette implements Palette { final int defaultBitsPerEntry; final int bitsIncrement; - private SpecializedPalette palette; + SpecializedPalette palette; AdaptivePalette(int dimension, int maxBitsPerEntry, int bitsPerEntry, int bitsIncrement) { this.dimensionBitCount = validateDimension(dimension); @@ -121,11 +124,16 @@ final class AdaptivePalette implements Palette { final int count = flexiblePalette.count(); if (count == 0) { return (this.palette = new FilledPalette(dimension, 0)); - } else if (count == flexiblePalette.maxSize()) { - var palette = flexiblePalette.paletteToValueList; - if (palette.size() == 2 && palette.getInt(0) == 0) { - // first element is air, second should be the value the palette is filled with - return (this.palette = new FilledPalette(dimension, palette.getInt(1))); + } else { + // Find all entries and compress the palette + IntSet entries = new IntOpenHashSet(flexiblePalette.paletteToValueList.size()); + currentPalette.getAll((x, y, z, value) -> entries.add(value)); + final int bitsPerEntry = MathUtils.bitsToRepresent(entries.size()); + if (bitsPerEntry == 1) { + return (this.palette = new FilledPalette(dimension, entries.iterator().nextInt())); + } else { + flexiblePalette.resize(bitsPerEntry); + return flexiblePalette; } } } diff --git a/src/main/java/net/minestom/server/instance/palette/FlexiblePalette.java b/src/main/java/net/minestom/server/instance/palette/FlexiblePalette.java index cb75e8cd6..3fb9a937d 100644 --- a/src/main/java/net/minestom/server/instance/palette/FlexiblePalette.java +++ b/src/main/java/net/minestom/server/instance/palette/FlexiblePalette.java @@ -311,7 +311,7 @@ final class FlexiblePalette implements SpecializedPalette, Cloneable { } } - private void resize(int newBitsPerEntry) { + void resize(int newBitsPerEntry) { FlexiblePalette palette = new FlexiblePalette(adaptivePalette, fixBitsPerEntry(newBitsPerEntry)); palette.lastPaletteIndex = lastPaletteIndex; palette.paletteToValueList = paletteToValueList; diff --git a/src/test/java/net/minestom/server/instance/palette/PaletteOptimizationTest.java b/src/test/java/net/minestom/server/instance/palette/PaletteOptimizationTest.java new file mode 100644 index 000000000..3604ca643 --- /dev/null +++ b/src/test/java/net/minestom/server/instance/palette/PaletteOptimizationTest.java @@ -0,0 +1,74 @@ +package net.minestom.server.instance.palette; + +import net.minestom.server.utils.binary.BinaryWriter; +import org.junit.jupiter.api.Test; + +import java.util.Random; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class PaletteOptimizationTest { + + @Test + public void empty() { + var palette = createPalette(); + paletteEquals(palette.palette, palette.optimizedPalette()); + } + + @Test + public void single() { + var palette = createPalette(); + palette.set(0, 0, 0, 1); + paletteEquals(palette.palette, palette.optimizedPalette()); + } + + @Test + public void random() { + var random = new Random(12345); + var palette = createPalette(); + palette.setAll((x, y, z) -> random.nextInt(256)); + paletteEquals(palette.palette, palette.optimizedPalette()); + palette.setAll((x, y, z) -> random.nextInt(2)); + paletteEquals(palette.palette, palette.optimizedPalette()); + } + + @Test + public void manualFill() { + var palette = createPalette(); + palette.setAll((x, y, z) -> 1); + paletteEquals(palette.palette, palette.optimizedPalette()); + palette.setAll((x, y, z) -> 2); + paletteEquals(palette.palette, palette.optimizedPalette()); + palette.setAll((x, y, z) -> 0); + paletteEquals(palette.palette, palette.optimizedPalette()); + } + + AdaptivePalette createPalette() { + return (AdaptivePalette) Palette.blocks(); + } + + void paletteEquals(Palette palette, Palette optimized) { + // Verify content + assertEquals(palette.dimension(), optimized.dimension()); + for (int y = 0; y < palette.dimension(); y++) { + for (int z = 0; z < palette.dimension(); z++) { + for (int x = 0; x < palette.dimension(); x++) { + assertEquals(palette.get(x, y, z), optimized.get(x, y, z)); + } + } + } + // Verify size + { + var writer = new BinaryWriter(4096); + palette.write(writer); + int length1 = writer.toByteArray().length; + writer = new BinaryWriter(4096); + optimized.write(writer); + int length2 = writer.toByteArray().length; + + System.out.println("debug: " + Thread.currentThread().getStackTrace()[2].getMethodName() + " " + length1 + " " + length2); + assertTrue(length1 >= length2, "Optimized palette is bigger than the original one: " + length1 + " : " + length2); + } + } +} diff --git a/src/test/java/net/minestom/server/instance/PaletteTest.java b/src/test/java/net/minestom/server/instance/palette/PaletteTest.java similarity index 99% rename from src/test/java/net/minestom/server/instance/PaletteTest.java rename to src/test/java/net/minestom/server/instance/palette/PaletteTest.java index 998243dc8..7c17db3c9 100644 --- a/src/test/java/net/minestom/server/instance/PaletteTest.java +++ b/src/test/java/net/minestom/server/instance/palette/PaletteTest.java @@ -1,8 +1,7 @@ -package net.minestom.server.instance; +package net.minestom.server.instance.palette; import net.minestom.server.coordinate.Point; import net.minestom.server.coordinate.Vec; -import net.minestom.server.instance.palette.Palette; import org.junit.jupiter.api.Test; import java.util.HashSet;