From ca3f5da5de6efeb53cfd30c16dc8acf43b62b4a8 Mon Sep 17 00:00:00 2001 From: James Seibel Date: Sun, 16 Nov 2025 15:30:16 -0600 Subject: [PATCH] Add unit test for data source merging speed --- .../testItems/wrappers/TestBiomeWrapper.java | 40 +++ .../wrappers/TestBlockStateWrapper.java | 76 ++++++ ...epoTests.java => DataSourceRepoTests.java} | 4 +- .../java/tests/DataSourceUpdateSpeedTest.java | 235 ++++++++++++++++++ 4 files changed, 353 insertions(+), 2 deletions(-) create mode 100644 core/src/test/java/testItems/wrappers/TestBiomeWrapper.java create mode 100644 core/src/test/java/testItems/wrappers/TestBlockStateWrapper.java rename core/src/test/java/tests/{DhFullDataSourceRepoTests.java => DataSourceRepoTests.java} (99%) create mode 100644 core/src/test/java/tests/DataSourceUpdateSpeedTest.java diff --git a/core/src/test/java/testItems/wrappers/TestBiomeWrapper.java b/core/src/test/java/testItems/wrappers/TestBiomeWrapper.java new file mode 100644 index 000000000..3c1d0b216 --- /dev/null +++ b/core/src/test/java/testItems/wrappers/TestBiomeWrapper.java @@ -0,0 +1,40 @@ +package testItems.wrappers; + +import com.seibel.distanthorizons.core.wrapperInterfaces.world.IBiomeWrapper; + +public class TestBiomeWrapper implements IBiomeWrapper +{ + private final String name; + + + public TestBiomeWrapper(String name) + { this.name = name; } + + + @Override + public String getName() + { return this.name; } + @Override + public String getSerialString() + { return this.name; } + + @Override + public Object getWrappedMcObject() + { return this; } + + + @Override + public int hashCode() + { return this.name.hashCode(); } + @Override + public boolean equals(Object obj) + { + if (!(obj instanceof TestBiomeWrapper)) + { + return false; + } + + return this.name.equals(((TestBiomeWrapper)obj).name); + } + +} diff --git a/core/src/test/java/testItems/wrappers/TestBlockStateWrapper.java b/core/src/test/java/testItems/wrappers/TestBlockStateWrapper.java new file mode 100644 index 000000000..bba7a607e --- /dev/null +++ b/core/src/test/java/testItems/wrappers/TestBlockStateWrapper.java @@ -0,0 +1,76 @@ +package testItems.wrappers; + +import com.seibel.distanthorizons.core.wrapperInterfaces.block.IBlockStateWrapper; + +import java.awt.*; + +public class TestBlockStateWrapper implements IBlockStateWrapper +{ + private final String name; + + + public TestBlockStateWrapper(String name) + { this.name = name; } + + + + @Override + public boolean isAir() + { return false; } + @Override + public boolean isSolid() + { return true; } + @Override + public boolean isLiquid() + { return false; } + @Override + public String getSerialString() + { return this.name; } + @Override + public int getOpacity() + { return 15; } + @Override + public int getLightEmission() + { return 0; } + @Override + public byte getMaterialId() + { return 0; } + @Override + public boolean isBeaconBlock() + { return false; } + @Override + public boolean isBeaconTintBlock() + { return false; } + @Override + public boolean allowsBeaconBeamPassage() + { return false; } + @Override + public boolean isBeaconBaseBlock() + { return false; } + @Override + public Color getMapColor() + { return Color.MAGENTA; } + @Override + public Color getBeaconTintColor() + { return Color.MAGENTA; } + + @Override + public Object getWrappedMcObject() + { return this; } + + + @Override + public int hashCode() + { return this.name.hashCode(); } + @Override + public boolean equals(Object obj) + { + if (!(obj instanceof TestBlockStateWrapper)) + { + return false; + } + + return this.name.equals(((TestBlockStateWrapper)obj).name); + } + +} diff --git a/core/src/test/java/tests/DhFullDataSourceRepoTests.java b/core/src/test/java/tests/DataSourceRepoTests.java similarity index 99% rename from core/src/test/java/tests/DhFullDataSourceRepoTests.java rename to core/src/test/java/tests/DataSourceRepoTests.java index 0b9400b50..2ec1e5d04 100644 --- a/core/src/test/java/tests/DhFullDataSourceRepoTests.java +++ b/core/src/test/java/tests/DataSourceRepoTests.java @@ -30,7 +30,6 @@ import com.seibel.distanthorizons.core.sql.repo.FullDataSourceV2Repo; import com.seibel.distanthorizons.core.util.FullDataPointUtil; import com.seibel.distanthorizons.core.util.LodUtil; import com.seibel.distanthorizons.core.util.ThreadUtil; -import com.seibel.distanthorizons.core.util.objects.DataCorruptedException; import it.unimi.dsi.fastutil.bytes.ByteArrayList; import it.unimi.dsi.fastutil.longs.LongArrayList; import org.junit.Assert; @@ -50,7 +49,7 @@ import java.util.concurrent.atomic.LongAdder; * Can also be used to test if there are memory leaks in SQLite * and if the {@link FullDataSourceV2DTO}/{@link FullDataSourceV2}'s are using pooled objects correctly */ -public class DhFullDataSourceRepoTests +public class DataSourceRepoTests { public static String DATABASE_TYPE = "jdbc:sqlite"; public static String DB_FILE_NAME = "test.sqlite"; @@ -395,4 +394,5 @@ public class DhFullDataSourceRepoTests } + } diff --git a/core/src/test/java/tests/DataSourceUpdateSpeedTest.java b/core/src/test/java/tests/DataSourceUpdateSpeedTest.java new file mode 100644 index 000000000..ac37fbbd8 --- /dev/null +++ b/core/src/test/java/tests/DataSourceUpdateSpeedTest.java @@ -0,0 +1,235 @@ +/* + * This file is part of the Distant Horizons mod + * licensed under the GNU LGPL v3 License. + * + * Copyright (C) 2020 James Seibel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package tests; + +import com.seibel.distanthorizons.api.enums.config.EDhApiWorldCompressionMode; +import com.seibel.distanthorizons.api.enums.worldGeneration.EDhApiWorldGenerationStep; +import com.seibel.distanthorizons.core.dataObjects.fullData.FullDataPointIdMap; +import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2; +import com.seibel.distanthorizons.core.pos.DhSectionPos; +import com.seibel.distanthorizons.core.util.FullDataPointUtil; +import com.seibel.distanthorizons.core.util.LodUtil; +import com.seibel.distanthorizons.core.util.objects.DataCorruptedException; +import it.unimi.dsi.fastutil.bytes.ByteArrayList; +import it.unimi.dsi.fastutil.longs.LongArrayList; +import org.junit.Assert; +import testItems.wrappers.TestBiomeWrapper; +import testItems.wrappers.TestBlockStateWrapper; + +import java.text.NumberFormat; +import java.util.Arrays; +import java.util.Random; + +/** + * + */ +public class DataSourceUpdateSpeedTest +{ + //@Test + public void test() throws DataCorruptedException + { + Random seededRandom = new Random(3); + + + //===================// + // parent datasource // + //===================// + + long parentPos = DhSectionPos.encode((byte)7, 0, 0); + + FullDataSourceV2 parentDataSource; + { + FullDataPointIdMap dataMapping = new FullDataPointIdMap(parentPos); + LongArrayList[] fullDataArray = new LongArrayList[FullDataSourceV2.WIDTH * FullDataSourceV2.WIDTH]; + + for (int arrayIndex = 0; arrayIndex < FullDataSourceV2.WIDTH * FullDataSourceV2.WIDTH; arrayIndex++) + { + fullDataArray[arrayIndex] = new LongArrayList(1); + + // random column heights so we can differentiate + // columns from each other + int columnCount = Math.abs(seededRandom.nextInt() % 31) + 1; + int lastMaxY = 4000; + for (int colIndex = columnCount; colIndex >= 0; colIndex--) + { + int height = (Math.abs(seededRandom.nextInt()) % 20) + 1; + lastMaxY -= height; + + long datapoint = FullDataPointUtil.encode( + colIndex, // id + height, // height + lastMaxY, // relative min Y + LodUtil.MIN_MC_LIGHT, // block light + LodUtil.MIN_MC_LIGHT // sky light + ); + fullDataArray[arrayIndex].add(datapoint); + + dataMapping.addIfNotPresentAndGetId( + new TestBiomeWrapper(colIndex+""), + new TestBlockStateWrapper(colIndex+"")); + } + + FullDataSourceV2.throwIfDataColumnInWrongOrder(parentPos, fullDataArray[arrayIndex]); + } + + byte[] columnGenStep = new byte[FullDataSourceV2.WIDTH * FullDataSourceV2.WIDTH]; + Arrays.fill(columnGenStep, EDhApiWorldGenerationStep.FEATURES.value); + + byte[] columnWorldCompressionMode = new byte[FullDataSourceV2.WIDTH * FullDataSourceV2.WIDTH]; + Arrays.fill(columnWorldCompressionMode, EDhApiWorldCompressionMode.VISUALLY_EQUAL.value); + + parentDataSource = FullDataSourceV2.createWithData(parentPos, dataMapping, fullDataArray, columnGenStep, columnWorldCompressionMode); + } + + + + + //==================// + // child datasource // + //==================// + + long childPos = DhSectionPos.encode((byte)6, 0, 0); + + FullDataSourceV2 childDataSource; + { + FullDataPointIdMap dataMapping = new FullDataPointIdMap(childPos); + LongArrayList[] fullDataArray = new LongArrayList[FullDataSourceV2.WIDTH * FullDataSourceV2.WIDTH]; + + for (int arrayIndex = 0; arrayIndex < FullDataSourceV2.WIDTH * FullDataSourceV2.WIDTH; arrayIndex++) + { + fullDataArray[arrayIndex] = new LongArrayList(1); + + // random column heights so we can differentiate + // columns from each other + int columnCount = Math.abs(seededRandom.nextInt() % 31) + 1; + int lastMaxY = 4000; + for (int colIndex = columnCount; colIndex >= 0; colIndex--) + { + int height = (Math.abs(seededRandom.nextInt()) % 20) + 1; + lastMaxY -= height; + + long datapoint = FullDataPointUtil.encode( + colIndex, // id + height, // height + lastMaxY, // relative min Y + LodUtil.MAX_MC_LIGHT, // block light + LodUtil.MAX_MC_LIGHT // sky light + ); + fullDataArray[arrayIndex].add(datapoint); + + dataMapping.addIfNotPresentAndGetId( + new TestBiomeWrapper(colIndex+""), + new TestBlockStateWrapper(colIndex+"")); + } + + FullDataSourceV2.throwIfDataColumnInWrongOrder(childPos, fullDataArray[arrayIndex]); + } + + byte[] columnGenStep = new byte[FullDataSourceV2.WIDTH * FullDataSourceV2.WIDTH]; + Arrays.fill(columnGenStep, EDhApiWorldGenerationStep.FEATURES.value); + + byte[] columnWorldCompressionMode = new byte[FullDataSourceV2.WIDTH * FullDataSourceV2.WIDTH]; + Arrays.fill(columnWorldCompressionMode, EDhApiWorldCompressionMode.VISUALLY_EQUAL.value); + + + + childDataSource = FullDataSourceV2.createWithData(childPos, dataMapping, fullDataArray, columnGenStep, columnWorldCompressionMode); + } + + + + //=========================// + // (optional) loop forever // + //=========================// + + // this can be set to "true" + // so we can profile the DTO creation process and + // see if there are any object leaks and how the GC + // handles it + if (true) + { + System.out.println("Starting long update test for time testing..."); + + long lastLogMsTime = 0; + + long totalNano = 0; + + NumberFormat numberFormat = NumberFormat.getNumberInstance(); + + // run for a long time + for (int i = 1; i < 100_000_000; i++) + { + long startNano = System.nanoTime(); + + boolean updated = parentDataSource.updateFromDataSource(childDataSource); + //Assert.assertTrue(updated); + + long updateTimeNano = System.nanoTime() - startNano; + totalNano += updateTimeNano; + + long nowMs = System.currentTimeMillis(); + if (nowMs - lastLogMsTime > 5_000) + { + lastLogMsTime = nowMs; + + double avgMs = ((totalNano / 1_000_000.0)/i); + double totalMs = totalNano / 1_000_000.0; + System.out.println("count: "+numberFormat.format(i)+"\tavg ms: "+numberFormat.format(avgMs)+"\ttotal ms: "+numberFormat.format(totalMs)); + } + } + } + } + + + //================// + // helper methods // + //================// + + private static void assertArraysAreEqual(ByteArrayList expectedArray, ByteArrayList actualArray) + { + Assert.assertEquals("size mismatch", expectedArray.size(), actualArray.size()); + + for (int i = 0; i < expectedArray.size(); i++) + { + byte expectedNumb = expectedArray.getByte(i); + byte actualNumb = actualArray.getByte(i); + + Assert.assertEquals("value mismatch at index ["+i+"]", expectedNumb, actualNumb); + } + } + + private static void assertArraysAreEqual(LongArrayList expectedArray, LongArrayList actualArray) + { assertArraysAreEqual(null, expectedArray, actualArray); } + private static void assertArraysAreEqual(String message, LongArrayList expectedArray, LongArrayList actualArray) + { + Assert.assertEquals(message + "size mismatch", expectedArray.size(), actualArray.size()); + + for (int i = 0; i < expectedArray.size(); i++) + { + long expectedNumb = expectedArray.getLong(i); + long actualNumb = actualArray.getLong(i); + + Assert.assertEquals(message + "value mismatch at index ["+i+"]", expectedNumb, actualNumb); + } + } + + + +}