mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-10-12 03:24:14 +00:00
Trim generation hot path allocations
- Replace the six per-voxel IrisPosition probes in MantleWriter neighborhood hollowing with an origin-relative primitive key set - Look mask membership up through the same primitive key set instead of allocating an IrisPosition per candidate voxel - Give the squared-distance helper a fixed arity so ballooning no longer depends on escape analysis to drop its varargs array - Resolve each Matter slice once per write instead of paying a hasSlice probe and a getSlice lookup for the same class - Read the river water surface through the primitive stream accessor instead of boxing a Double per column - Add nullable primary surface layer accessors so the terrain actuator stops building two Optionals per column - Add MantleWriterNeighborhoodTest, MantleWriterNeighborhoodBenchmarkTest and HydrologyColumnSampleAccessorTest
This commit is contained in:
@@ -22,6 +22,7 @@ import art.arcane.iris.engine.framework.Engine;
|
||||
import art.arcane.iris.engine.framework.EngineAssignedActuator;
|
||||
import art.arcane.iris.core.loader.IrisData;
|
||||
import art.arcane.iris.engine.IrisComplex;
|
||||
import art.arcane.iris.util.project.stream.ProceduralStream;
|
||||
import art.arcane.iris.engine.UpperDimensionContext;
|
||||
import art.arcane.iris.engine.hydrology.HydrologyColumnLayer;
|
||||
import art.arcane.iris.engine.hydrology.HydrologyColumnSample;
|
||||
@@ -84,6 +85,7 @@ public class IrisTerrainNormalActuator extends EngineAssignedActuator<PlatformBl
|
||||
IrisDimension dimension = getDimension();
|
||||
IrisData data = getData();
|
||||
IrisComplex complex = getComplex();
|
||||
ProceduralStream<Double> riverWaterSurfaceStream = complex.getRiverWaterSurfaceStream();
|
||||
RNG localRng = rng;
|
||||
boolean bedrockEnabled = dimension.isBedrock();
|
||||
boolean hideOres = dimension.isHideOresForHiddenOre();
|
||||
@@ -117,7 +119,7 @@ public class IrisTerrainNormalActuator extends EngineAssignedActuator<PlatformBl
|
||||
int he = Math.min(chunkHeight, context.getRoundedHeight(xf, zf));
|
||||
int surfaceFluidHeight = Math.min(
|
||||
chunkHeight,
|
||||
(int) Math.round(complex.getRiverWaterSurfaceStream().get(realX, realZ))
|
||||
(int) Math.round(riverWaterSurfaceStream.getDouble(realX, realZ))
|
||||
);
|
||||
int hf = Math.max(surfaceFluidHeight, he);
|
||||
if (hf < 0) {
|
||||
@@ -128,10 +130,10 @@ public class IrisTerrainNormalActuator extends EngineAssignedActuator<PlatformBl
|
||||
HydrologyColumnSample hydrology = complex.sampleHydrologyColumn(realX, realZ);
|
||||
HydrologyColumnLayer hydrologyFluid = hydrology == null
|
||||
? null
|
||||
: hydrology.primarySurfaceFluidLayer().orElse(null);
|
||||
: hydrology.primarySurfaceFluidLayerOrNull();
|
||||
HydrologyColumnLayer hydrologyTerrain = hydrology == null
|
||||
? null
|
||||
: hydrology.primarySurfaceLayer().orElse(null);
|
||||
: hydrology.primarySurfaceLayerOrNull();
|
||||
int cut = exposeCutStrata
|
||||
&& hydrologyTerrain != null
|
||||
&& hydrologyTerrain.terrainOwned()
|
||||
|
||||
@@ -92,10 +92,18 @@ public record HydrologyColumnSample(
|
||||
}
|
||||
|
||||
public Optional<HydrologyColumnLayer> primarySurfaceLayer() {
|
||||
return selectSurfaceLayer(false);
|
||||
return Optional.ofNullable(selectSurfaceLayer(false));
|
||||
}
|
||||
|
||||
public Optional<HydrologyColumnLayer> primarySurfaceFluidLayer() {
|
||||
return Optional.ofNullable(selectSurfaceLayer(true));
|
||||
}
|
||||
|
||||
public HydrologyColumnLayer primarySurfaceLayerOrNull() {
|
||||
return selectSurfaceLayer(false);
|
||||
}
|
||||
|
||||
public HydrologyColumnLayer primarySurfaceFluidLayerOrNull() {
|
||||
return selectSurfaceLayer(true);
|
||||
}
|
||||
|
||||
@@ -120,11 +128,10 @@ public record HydrologyColumnSample(
|
||||
}
|
||||
|
||||
public int terrainHeight() {
|
||||
Optional<HydrologyColumnLayer> layer = primarySurfaceLayer();
|
||||
if (layer.isEmpty()) {
|
||||
HydrologyColumnLayer primary = selectSurfaceLayer(false);
|
||||
if (primary == null) {
|
||||
return naturalHeight;
|
||||
}
|
||||
HydrologyColumnLayer primary = layer.get();
|
||||
if (primary.channel()) {
|
||||
return primary.bedY();
|
||||
}
|
||||
@@ -147,7 +154,7 @@ public record HydrologyColumnSample(
|
||||
return new HydrologyRenderSample(x, z, features);
|
||||
}
|
||||
|
||||
private Optional<HydrologyColumnLayer> selectSurfaceLayer(boolean fluidOnly) {
|
||||
private HydrologyColumnLayer selectSurfaceLayer(boolean fluidOnly) {
|
||||
HydrologyColumnLayer selected = null;
|
||||
for (HydrologyColumnLayer layer : layers) {
|
||||
if (layer.oceanApron() || !layer.feature().type().isSurface()) {
|
||||
@@ -164,7 +171,7 @@ public record HydrologyColumnSample(
|
||||
selected = layer;
|
||||
}
|
||||
}
|
||||
return Optional.ofNullable(selected);
|
||||
return selected;
|
||||
}
|
||||
|
||||
private static int surfaceRolePriority(HydrologyColumnLayer layer) {
|
||||
|
||||
@@ -23,6 +23,7 @@ import art.arcane.iris.spi.IrisLogging;
|
||||
import art.arcane.iris.util.project.matter.TileWrapper;
|
||||
import art.arcane.iris.util.project.matter.PreObjectMatterCell;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
|
||||
import art.arcane.iris.core.IrisSettings;
|
||||
import art.arcane.iris.core.link.Identifier;
|
||||
import art.arcane.iris.core.tools.WorldMaintenance;
|
||||
@@ -175,7 +176,7 @@ public class MantleWriter implements ObjectPassPlacer, AutoCloseable {
|
||||
}
|
||||
}
|
||||
|
||||
private static Set<IrisPosition> getBallooned(Set<IrisPosition> vset, double radius) {
|
||||
static Set<IrisPosition> getBallooned(Set<IrisPosition> vset, double radius) {
|
||||
Set<IrisPosition> returnset = new HashSet<>();
|
||||
int ceilrad = (int) Math.ceil(radius);
|
||||
double r2 = Math.pow(radius, 2);
|
||||
@@ -198,30 +199,83 @@ public class MantleWriter implements ObjectPassPlacer, AutoCloseable {
|
||||
return returnset;
|
||||
}
|
||||
|
||||
private static Set<IrisPosition> getHollowed(Set<IrisPosition> vset) {
|
||||
static Set<IrisPosition> getHollowed(Set<IrisPosition> vset) {
|
||||
PositionLookup lookup = PositionLookup.of(vset);
|
||||
Set<IrisPosition> returnset = new KSet<>();
|
||||
for (IrisPosition v : vset) {
|
||||
double x = v.getX();
|
||||
double y = v.getY();
|
||||
double z = v.getZ();
|
||||
if (!(vset.contains(new IrisPosition(x + 1, y, z))
|
||||
&& vset.contains(new IrisPosition(x - 1, y, z))
|
||||
&& vset.contains(new IrisPosition(x, y + 1, z))
|
||||
&& vset.contains(new IrisPosition(x, y - 1, z))
|
||||
&& vset.contains(new IrisPosition(x, y, z + 1))
|
||||
&& vset.contains(new IrisPosition(x, y, z - 1)))) {
|
||||
int x = v.getX();
|
||||
int y = v.getY();
|
||||
int z = v.getZ();
|
||||
if (!(lookup.contains(x + 1, y, z)
|
||||
&& lookup.contains(x - 1, y, z)
|
||||
&& lookup.contains(x, y + 1, z)
|
||||
&& lookup.contains(x, y - 1, z)
|
||||
&& lookup.contains(x, y, z + 1)
|
||||
&& lookup.contains(x, y, z - 1))) {
|
||||
returnset.add(v);
|
||||
}
|
||||
}
|
||||
return returnset;
|
||||
}
|
||||
|
||||
private static double hypot(double... pars) {
|
||||
double sum = 0;
|
||||
for (double d : pars) {
|
||||
sum += Math.pow(d, 2);
|
||||
private static double hypot(double x, double y, double z) {
|
||||
return Math.pow(x, 2) + Math.pow(y, 2) + Math.pow(z, 2);
|
||||
}
|
||||
|
||||
record PositionLookup(LongOpenHashSet keys, Set<IrisPosition> positions, int originX, int originY, int originZ) {
|
||||
private static final int AXIS_BITS = 21;
|
||||
private static final long AXIS_LIMIT = 1L << AXIS_BITS;
|
||||
|
||||
static PositionLookup of(Set<IrisPosition> positions) {
|
||||
if (positions.isEmpty()) {
|
||||
return new PositionLookup(new LongOpenHashSet(), positions, 0, 0, 0);
|
||||
}
|
||||
|
||||
int minX = Integer.MAX_VALUE;
|
||||
int minY = Integer.MAX_VALUE;
|
||||
int minZ = Integer.MAX_VALUE;
|
||||
int maxX = Integer.MIN_VALUE;
|
||||
int maxY = Integer.MIN_VALUE;
|
||||
int maxZ = Integer.MIN_VALUE;
|
||||
for (IrisPosition position : positions) {
|
||||
minX = Math.min(minX, position.getX());
|
||||
minY = Math.min(minY, position.getY());
|
||||
minZ = Math.min(minZ, position.getZ());
|
||||
maxX = Math.max(maxX, position.getX());
|
||||
maxY = Math.max(maxY, position.getY());
|
||||
maxZ = Math.max(maxZ, position.getZ());
|
||||
}
|
||||
|
||||
if ((long) maxX - minX >= AXIS_LIMIT - 2L
|
||||
|| (long) maxY - minY >= AXIS_LIMIT - 2L
|
||||
|| (long) maxZ - minZ >= AXIS_LIMIT - 2L) {
|
||||
return new PositionLookup(null, positions, 0, 0, 0);
|
||||
}
|
||||
|
||||
LongOpenHashSet keys = new LongOpenHashSet(positions.size());
|
||||
for (IrisPosition position : positions) {
|
||||
keys.add(key(position.getX() - minX, position.getY() - minY, position.getZ() - minZ));
|
||||
}
|
||||
return new PositionLookup(keys, positions, minX, minY, minZ);
|
||||
}
|
||||
|
||||
boolean contains(int x, int y, int z) {
|
||||
if (keys == null) {
|
||||
return positions.contains(new IrisPosition(x, y, z));
|
||||
}
|
||||
|
||||
long dx = (long) x - originX;
|
||||
long dy = (long) y - originY;
|
||||
long dz = (long) z - originZ;
|
||||
if ((dx | dy | dz) < 0L || dx >= AXIS_LIMIT || dy >= AXIS_LIMIT || dz >= AXIS_LIMIT) {
|
||||
return false;
|
||||
}
|
||||
return keys.contains(key(dx, dy, dz));
|
||||
}
|
||||
|
||||
private static long key(long x, long y, long z) {
|
||||
return (x << (AXIS_BITS * 2)) | (y << AXIS_BITS) | z;
|
||||
}
|
||||
return sum;
|
||||
}
|
||||
|
||||
private static double lengthSq(double x, double y, double z) {
|
||||
@@ -294,9 +348,7 @@ public class MantleWriter implements ObjectPassPlacer, AutoCloseable {
|
||||
if (hasProtectedHydrology(matter, x, y, z)) {
|
||||
return false;
|
||||
}
|
||||
MatterSlice<MatterCavern> cavernSlice = matter.hasSlice(MatterCavern.class)
|
||||
? matter.getSlice(MatterCavern.class)
|
||||
: null;
|
||||
MatterSlice<MatterCavern> cavernSlice = matter.getSlice(MatterCavern.class);
|
||||
MatterCavern existing = cavernSlice == null
|
||||
? null
|
||||
: cavernSlice.get(x & 15, y & 15, z & 15);
|
||||
@@ -328,15 +380,13 @@ public class MantleWriter implements ObjectPassPlacer, AutoCloseable {
|
||||
if (hasProtectedHydrology(matter, x, y, z)) {
|
||||
return false;
|
||||
}
|
||||
if (matter.hasSlice(PlatformBlockState.class)) {
|
||||
MatterSlice<PlatformBlockState> blockSlice = matter.getSlice(PlatformBlockState.class);
|
||||
if (blockSlice != null) {
|
||||
capturePreObjectOriginal(matter, x, y, z, PlatformBlockState.class);
|
||||
matter.<PlatformBlockState>getSlice(PlatformBlockState.class)
|
||||
.set(x & 15, y & 15, z & 15, null);
|
||||
blockSlice.set(x & 15, y & 15, z & 15, null);
|
||||
}
|
||||
clearDeferredPlacement(matter, x, y, z);
|
||||
MatterSlice<MatterCavern> cavernSlice = matter.hasSlice(MatterCavern.class)
|
||||
? matter.getSlice(MatterCavern.class)
|
||||
: null;
|
||||
MatterSlice<MatterCavern> cavernSlice = matter.getSlice(MatterCavern.class);
|
||||
if (cavernSlice != null && cavernSlice.get(x & 15, y & 15, z & 15) != null) {
|
||||
return false;
|
||||
}
|
||||
@@ -363,10 +413,10 @@ public class MantleWriter implements ObjectPassPlacer, AutoCloseable {
|
||||
if (hasProtectedHydrology(matter, x, y, z)) {
|
||||
return;
|
||||
}
|
||||
if (matter.hasSlice(PlatformBlockState.class)) {
|
||||
MatterSlice<PlatformBlockState> blockSlice = matter.getSlice(PlatformBlockState.class);
|
||||
if (blockSlice != null) {
|
||||
capturePreObjectOriginal(matter, x, y, z, PlatformBlockState.class);
|
||||
matter.<PlatformBlockState>getSlice(PlatformBlockState.class)
|
||||
.set(x & 15, y & 15, z & 15, null);
|
||||
blockSlice.set(x & 15, y & 15, z & 15, null);
|
||||
}
|
||||
clearDeferredPlacement(matter, x, y, z);
|
||||
capturePreObjectOriginal(matter, x, y, z, MatterCavern.class);
|
||||
@@ -394,10 +444,10 @@ public class MantleWriter implements ObjectPassPlacer, AutoCloseable {
|
||||
if (hasProtectedHydrology(matter, x, y, z)) {
|
||||
return;
|
||||
}
|
||||
if (matter.hasSlice(PlatformBlockState.class)) {
|
||||
MatterSlice<PlatformBlockState> blockSlice = matter.getSlice(PlatformBlockState.class);
|
||||
if (blockSlice != null) {
|
||||
capturePreObjectOriginal(matter, x, y, z, PlatformBlockState.class);
|
||||
matter.<PlatformBlockState>getSlice(PlatformBlockState.class)
|
||||
.set(x & 15, y & 15, z & 15, null);
|
||||
blockSlice.set(x & 15, y & 15, z & 15, null);
|
||||
}
|
||||
clearDeferredPlacement(matter, x, y, z);
|
||||
}
|
||||
@@ -651,17 +701,18 @@ public class MantleWriter implements ObjectPassPlacer, AutoCloseable {
|
||||
}
|
||||
|
||||
private static boolean hasProtectedHydrology(Matter matter, int x, int y, int z) {
|
||||
if (!matter.hasSlice(HydrologyCaveCell.class)) {
|
||||
MatterSlice<HydrologyCaveCell> slice = matter.getSlice(HydrologyCaveCell.class);
|
||||
if (slice == null) {
|
||||
return false;
|
||||
}
|
||||
HydrologyCaveCell hydrology = matter.<HydrologyCaveCell>getSlice(HydrologyCaveCell.class)
|
||||
.get(x & 15, y & 15, z & 15);
|
||||
HydrologyCaveCell hydrology = slice.get(x & 15, y & 15, z & 15);
|
||||
return hydrology != null && hydrology.protectsPlacement();
|
||||
}
|
||||
|
||||
private static void clearDeferredPlacement(Matter matter, int x, int y, int z) {
|
||||
if (matter.hasSlice(Identifier.class)) {
|
||||
matter.<Identifier>getSlice(Identifier.class).set(x & 15, y & 15, z & 15, null);
|
||||
MatterSlice<Identifier> slice = matter.getSlice(Identifier.class);
|
||||
if (slice != null) {
|
||||
slice.set(x & 15, y & 15, z & 15, null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -728,11 +779,11 @@ public class MantleWriter implements ObjectPassPlacer, AutoCloseable {
|
||||
}
|
||||
|
||||
private static PreObjectMatterCell preObjectCell(Matter matter, int x, int y, int z) {
|
||||
if (matter == null || !matter.hasSlice(PreObjectMatterCell.class)) {
|
||||
if (matter == null) {
|
||||
return null;
|
||||
}
|
||||
return matter.<PreObjectMatterCell>getSlice(PreObjectMatterCell.class)
|
||||
.get(x & 15, y & 15, z & 15);
|
||||
MatterSlice<PreObjectMatterCell> slice = matter.getSlice(PreObjectMatterCell.class);
|
||||
return slice == null ? null : slice.get(x & 15, y & 15, z & 15);
|
||||
}
|
||||
|
||||
private static <T> T prerequisiteValue(Matter matter, int x, int y, int z, Class<T> type) {
|
||||
@@ -740,17 +791,13 @@ public class MantleWriter implements ObjectPassPlacer, AutoCloseable {
|
||||
if (cell != null && cell.captures(type)) {
|
||||
return cell.original(type);
|
||||
}
|
||||
if (!matter.hasSlice(type)) {
|
||||
return null;
|
||||
}
|
||||
return matter.<T>getSlice(type).get(x & 15, y & 15, z & 15);
|
||||
MatterSlice<T> slice = matter.getSlice(type);
|
||||
return slice == null ? null : slice.get(x & 15, y & 15, z & 15);
|
||||
}
|
||||
|
||||
private static Object rawValue(Matter matter, int x, int y, int z, Class<?> type) {
|
||||
if (!matter.hasSlice(type)) {
|
||||
return null;
|
||||
}
|
||||
return matter.getSlice(type).get(x, y, z);
|
||||
MatterSlice<?> slice = matter.getSlice(type);
|
||||
return slice == null ? null : slice.get(x, y, z);
|
||||
}
|
||||
|
||||
private boolean allowsWrite(int blockX, int blockZ) {
|
||||
@@ -762,10 +809,10 @@ public class MantleWriter implements ObjectPassPlacer, AutoCloseable {
|
||||
private static void restoreRaw(Matter matter, int x, int y, int z, Class<?> type, Object value) {
|
||||
MatterSlice<Object> slice;
|
||||
if (value == null) {
|
||||
if (!matter.hasSlice(type)) {
|
||||
slice = (MatterSlice<Object>) matter.getSlice(type);
|
||||
if (slice == null) {
|
||||
return;
|
||||
}
|
||||
slice = (MatterSlice<Object>) matter.getSlice(type);
|
||||
} else {
|
||||
slice = (MatterSlice<Object>) matter.slice(type);
|
||||
}
|
||||
@@ -951,9 +998,7 @@ public class MantleWriter implements ObjectPassPlacer, AutoCloseable {
|
||||
continue;
|
||||
}
|
||||
|
||||
MatterSlice<MatterCavern> cavernSlice = matter.hasSlice(MatterCavern.class)
|
||||
? matter.getSlice(MatterCavern.class)
|
||||
: null;
|
||||
MatterSlice<MatterCavern> cavernSlice = matter.getSlice(MatterCavern.class);
|
||||
MatterSlice<HydrologyCaveCell> hydrologySlice = matter.hasSlice(HydrologyCaveCell.class)
|
||||
? matter.getSlice(HydrologyCaveCell.class)
|
||||
: null;
|
||||
@@ -1277,10 +1322,11 @@ public class MantleWriter implements ObjectPassPlacer, AutoCloseable {
|
||||
setConsumer(vset, data);
|
||||
}
|
||||
|
||||
private static Set<IrisPosition> getMasked(Set<IrisPosition> vectors, Set<IrisPosition> masks, double radius) {
|
||||
static Set<IrisPosition> getMasked(Set<IrisPosition> vectors, Set<IrisPosition> masks, double radius) {
|
||||
Set<IrisPosition> vset = new KSet<>();
|
||||
int ceil = (int) Math.ceil(radius);
|
||||
double r2 = Math.pow(radius, 2);
|
||||
PositionLookup maskLookup = PositionLookup.of(masks);
|
||||
|
||||
for (IrisPosition v : vectors) {
|
||||
int tipX = v.getX();
|
||||
@@ -1290,7 +1336,7 @@ public class MantleWriter implements ObjectPassPlacer, AutoCloseable {
|
||||
for (int x = -ceil; x <= ceil; x++) {
|
||||
for (int y = -ceil; y <= ceil; y++) {
|
||||
for (int z = -ceil; z <= ceil; z++) {
|
||||
if (hypot(x, y, z) > r2 || !masks.contains(new IrisPosition(x, y, z)))
|
||||
if (hypot(x, y, z) > r2 || !maskLookup.contains(x, y, z))
|
||||
continue;
|
||||
vset.add(new IrisPosition(tipX + x, tipY + y, tipZ + z));
|
||||
}
|
||||
|
||||
+153
@@ -0,0 +1,153 @@
|
||||
package art.arcane.iris.engine.hydrology;
|
||||
|
||||
import org.junit.Assume;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.lang.management.ManagementFactory;
|
||||
import java.lang.management.ThreadMXBean;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertSame;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class HydrologyColumnSampleAccessorTest {
|
||||
private static final int WARMUP = 20_000;
|
||||
private static final int ROUNDS = 20_000;
|
||||
private static final int PASSES = 5;
|
||||
|
||||
@Test
|
||||
public void theNullableSurfaceAccessorsAgreeWithTheOptionalOnes() {
|
||||
HydrologyColumnSample sample = sample();
|
||||
|
||||
assertSame(sample.primarySurfaceLayer().orElse(null), sample.primarySurfaceLayerOrNull());
|
||||
assertSame(sample.primarySurfaceFluidLayer().orElse(null), sample.primarySurfaceFluidLayerOrNull());
|
||||
assertNotNull(sample.primarySurfaceLayerOrNull());
|
||||
assertNotNull(sample.primarySurfaceFluidLayerOrNull());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void aColumnWithoutSurfaceLayersReportsNothingOnBothAccessors() {
|
||||
HydrologyColumnSample empty = new HydrologyColumnSample(0, 0, 70, 62, false, "overworld/plains", List.of());
|
||||
|
||||
assertNull(empty.primarySurfaceLayerOrNull());
|
||||
assertNull(empty.primarySurfaceFluidLayerOrNull());
|
||||
assertTrue(empty.primarySurfaceLayer().isEmpty());
|
||||
assertTrue(empty.primarySurfaceFluidLayer().isEmpty());
|
||||
assertEquals(70, empty.terrainHeight());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void terrainHeightStillFollowsThePrimarySurfaceLayer() {
|
||||
HydrologyColumnSample sample = sample();
|
||||
|
||||
assertEquals(sample.primarySurfaceLayerOrNull().bedY(), sample.terrainHeight());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void theNullableAccessorNeverAllocatesMoreThanTheOptionalOne() {
|
||||
HydrologyColumnSample sample = sample();
|
||||
|
||||
Measurement nullable = best(() -> sample.primarySurfaceLayerOrNull());
|
||||
Measurement optional = best(() -> sample.primarySurfaceLayer().orElse(null));
|
||||
|
||||
System.out.println("[bench] primarySurfaceLayer nullable=" + nullable + " optional=" + optional);
|
||||
|
||||
Assume.assumeTrue("per-thread allocation counters are unavailable on this JVM", nullable.bytes() >= 0L);
|
||||
assertTrue("the nullable accessor allocated " + nullable.bytes()
|
||||
+ " bytes against " + optional.bytes() + " for the Optional accessor",
|
||||
nullable.bytes() <= optional.bytes());
|
||||
}
|
||||
|
||||
private static HydrologyColumnSample sample() {
|
||||
List<HydrologyColumnLayer> layers = new ArrayList<>();
|
||||
layers.add(layer(1L, HydrologyFeatureType.RIFFLE, 66, 68, true, false, true, true));
|
||||
layers.add(layer(2L, HydrologyFeatureType.SURFACE_POOL, 67, 68, false, true, false, false));
|
||||
layers.add(layer(3L, HydrologyFeatureType.MOUTH, 65, 67, true, false, true, true));
|
||||
return new HydrologyColumnSample(4, 9, 74, 62, false, "overworld/river", layers);
|
||||
}
|
||||
|
||||
private static HydrologyColumnLayer layer(
|
||||
long id,
|
||||
HydrologyFeatureType type,
|
||||
int bedY,
|
||||
int fluidHeadY,
|
||||
boolean channel,
|
||||
boolean shore,
|
||||
boolean connectedFluid,
|
||||
boolean fluidOwned
|
||||
) {
|
||||
return new HydrologyColumnLayer(
|
||||
new HydrologyFeatureRef(id, type, id, id, 4, fluidHeadY, 9, 1, 0, false),
|
||||
bedY,
|
||||
fluidHeadY,
|
||||
fluidHeadY,
|
||||
channel,
|
||||
shore,
|
||||
false,
|
||||
connectedFluid,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
fluidOwned,
|
||||
false,
|
||||
"overworld/river",
|
||||
"overworld/river-surface",
|
||||
"overworld/river-mouth",
|
||||
"overworld/river-shore",
|
||||
"overworld/river-bank",
|
||||
"overworld/river-cave");
|
||||
}
|
||||
|
||||
private static Measurement best(Supplier<HydrologyColumnLayer> work) {
|
||||
for (int round = 0; round < WARMUP; round++) {
|
||||
work.get();
|
||||
}
|
||||
|
||||
long bytes = Long.MAX_VALUE;
|
||||
long nanos = Long.MAX_VALUE;
|
||||
for (int pass = 0; pass < PASSES; pass++) {
|
||||
Measurement measured = measure(work);
|
||||
if (measured.bytes() < 0L) {
|
||||
return measured;
|
||||
}
|
||||
bytes = Math.min(bytes, measured.bytes());
|
||||
nanos = Math.min(nanos, measured.nanos());
|
||||
}
|
||||
return new Measurement(bytes, nanos);
|
||||
}
|
||||
|
||||
private static Measurement measure(Supplier<HydrologyColumnLayer> work) {
|
||||
HydrologyColumnLayer sink = null;
|
||||
long startBytes = allocatedBytes();
|
||||
long startNanos = System.nanoTime();
|
||||
for (int round = 0; round < ROUNDS; round++) {
|
||||
sink = work.get();
|
||||
}
|
||||
long elapsedNanos = System.nanoTime() - startNanos;
|
||||
long endBytes = allocatedBytes();
|
||||
assertNotNull(sink);
|
||||
long bytes = startBytes < 0L || endBytes < 0L ? -1L : (endBytes - startBytes) / ROUNDS;
|
||||
|
||||
return new Measurement(bytes, elapsedNanos / ROUNDS);
|
||||
}
|
||||
|
||||
private static long allocatedBytes() {
|
||||
ThreadMXBean threads = ManagementFactory.getThreadMXBean();
|
||||
if (!(threads instanceof com.sun.management.ThreadMXBean hotspot) || !hotspot.isThreadAllocatedMemoryEnabled()) {
|
||||
return -1L;
|
||||
}
|
||||
return hotspot.getThreadAllocatedBytes(Thread.currentThread().threadId());
|
||||
}
|
||||
|
||||
private record Measurement(long bytes, long nanos) {
|
||||
@Override
|
||||
public String toString() {
|
||||
return "{bytes/op=" + bytes + ", ns/op=" + nanos + "}";
|
||||
}
|
||||
}
|
||||
}
|
||||
+125
@@ -0,0 +1,125 @@
|
||||
package art.arcane.iris.engine.mantle;
|
||||
|
||||
import art.arcane.iris.engine.object.IrisPosition;
|
||||
import org.junit.Assume;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.lang.management.ManagementFactory;
|
||||
import java.lang.management.ThreadMXBean;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class MantleWriterNeighborhoodBenchmarkTest {
|
||||
private static final int WARMUP = 400;
|
||||
private static final int ROUNDS = 40;
|
||||
private static final int PASSES = 6;
|
||||
private static final double RADIUS = 5.0D;
|
||||
|
||||
@Test
|
||||
public void hollowingAllocatesLessThanThePositionProbeImplementation() {
|
||||
Set<IrisPosition> ballooned = MantleWriter.getBallooned(line(), RADIUS);
|
||||
assertTrue("benchmark input is too small to be meaningful: " + ballooned.size(), ballooned.size() > 4_000);
|
||||
|
||||
Comparison comparison = compare(
|
||||
() -> MantleWriter.getHollowed(ballooned),
|
||||
() -> MantleWriterNeighborhoodTest.referenceHollowed(ballooned));
|
||||
|
||||
System.out.println("[bench] getHollowed voxels=" + ballooned.size()
|
||||
+ " primitiveKeys=" + comparison.candidate()
|
||||
+ " positionProbes=" + comparison.reference());
|
||||
|
||||
assertEquals(comparison.reference().result(), comparison.candidate().result());
|
||||
|
||||
Assume.assumeTrue("per-thread allocation counters are unavailable on this JVM",
|
||||
comparison.candidate().bytes() >= 0L);
|
||||
assertTrue("primitive-keyed hollowing allocated " + comparison.candidate().bytes()
|
||||
+ " bytes against " + comparison.reference().bytes() + " for the position-probe implementation",
|
||||
comparison.candidate().bytes() < comparison.reference().bytes());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void ballooningMatchesTheVarargsDistanceImplementationExactly() {
|
||||
Set<IrisPosition> seeds = line();
|
||||
|
||||
Comparison comparison = compare(
|
||||
() -> MantleWriter.getBallooned(seeds, RADIUS),
|
||||
() -> MantleWriterNeighborhoodTest.referenceBallooned(seeds, RADIUS));
|
||||
|
||||
System.out.println("[bench] getBallooned seeds=" + seeds.size()
|
||||
+ " fixedArityDistance=" + comparison.candidate()
|
||||
+ " varargsDistance=" + comparison.reference());
|
||||
|
||||
assertEquals(comparison.reference().result(), comparison.candidate().result());
|
||||
}
|
||||
|
||||
private static Set<IrisPosition> line() {
|
||||
Set<IrisPosition> positions = new LinkedHashSet<>();
|
||||
for (int step = 0; step <= 48; step++) {
|
||||
positions.add(new IrisPosition(step, 60 + (step % 5), step / 2));
|
||||
}
|
||||
return positions;
|
||||
}
|
||||
|
||||
private static Comparison compare(Supplier<Set<IrisPosition>> candidate, Supplier<Set<IrisPosition>> reference) {
|
||||
for (int round = 0; round < WARMUP; round++) {
|
||||
candidate.get();
|
||||
reference.get();
|
||||
}
|
||||
|
||||
Measurement bestCandidate = null;
|
||||
Measurement bestReference = null;
|
||||
for (int pass = 0; pass < PASSES; pass++) {
|
||||
bestCandidate = best(bestCandidate, measure(candidate));
|
||||
bestReference = best(bestReference, measure(reference));
|
||||
}
|
||||
|
||||
return new Comparison(bestCandidate, bestReference);
|
||||
}
|
||||
|
||||
private static Measurement best(Measurement current, Measurement measured) {
|
||||
if (current == null) {
|
||||
return measured;
|
||||
}
|
||||
|
||||
return new Measurement(
|
||||
measured.result(),
|
||||
current.bytes() < 0L || measured.bytes() < 0L ? -1L : Math.min(current.bytes(), measured.bytes()),
|
||||
Math.min(current.nanos(), measured.nanos()));
|
||||
}
|
||||
|
||||
private static Measurement measure(Supplier<Set<IrisPosition>> work) {
|
||||
Set<IrisPosition> result = null;
|
||||
long startBytes = allocatedBytes();
|
||||
long startNanos = System.nanoTime();
|
||||
for (int round = 0; round < ROUNDS; round++) {
|
||||
result = work.get();
|
||||
}
|
||||
long elapsedNanos = System.nanoTime() - startNanos;
|
||||
long endBytes = allocatedBytes();
|
||||
long bytes = startBytes < 0L || endBytes < 0L ? -1L : (endBytes - startBytes) / ROUNDS;
|
||||
|
||||
return new Measurement(result, bytes, elapsedNanos / ROUNDS);
|
||||
}
|
||||
|
||||
private static long allocatedBytes() {
|
||||
ThreadMXBean threads = ManagementFactory.getThreadMXBean();
|
||||
if (!(threads instanceof com.sun.management.ThreadMXBean hotspot) || !hotspot.isThreadAllocatedMemoryEnabled()) {
|
||||
return -1L;
|
||||
}
|
||||
return hotspot.getThreadAllocatedBytes(Thread.currentThread().threadId());
|
||||
}
|
||||
|
||||
private record Comparison(Measurement candidate, Measurement reference) {
|
||||
}
|
||||
|
||||
private record Measurement(Set<IrisPosition> result, long bytes, long nanos) {
|
||||
@Override
|
||||
public String toString() {
|
||||
return "{bytes/op=" + bytes + ", ns/op=" + nanos + ", voxels=" + result.size() + "}";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,184 @@
|
||||
package art.arcane.iris.engine.mantle;
|
||||
|
||||
import art.arcane.iris.engine.object.IrisPosition;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class MantleWriterNeighborhoodTest {
|
||||
@Test
|
||||
public void balloonedVoxelsMatchTheReferenceSphereForFractionalRadii() {
|
||||
for (double radius : new double[]{0.0D, 0.5D, 1.0D, 1.5D, 2.0D, 3.7D, 5.0D}) {
|
||||
Set<IrisPosition> seeds = line(-3, 60, 7, 4, 63, 9);
|
||||
assertEquals("radius " + radius, referenceBallooned(seeds, radius), MantleWriter.getBallooned(seeds, radius));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void hollowedVoxelsMatchTheReferenceSurfaceExtraction() {
|
||||
Set<IrisPosition> ballooned = MantleWriter.getBallooned(line(0, 40, 0, 6, 44, 3), 4.0D);
|
||||
|
||||
assertEquals(referenceHollowed(ballooned), MantleWriter.getHollowed(ballooned));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void hollowingASolidBlockKeepsOnlyItsShell() {
|
||||
Set<IrisPosition> solid = new LinkedHashSet<>();
|
||||
for (int x = 0; x < 5; x++) {
|
||||
for (int y = 0; y < 5; y++) {
|
||||
for (int z = 0; z < 5; z++) {
|
||||
solid.add(new IrisPosition(x, y, z));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Set<IrisPosition> hollow = MantleWriter.getHollowed(solid);
|
||||
|
||||
assertEquals(98, hollow.size());
|
||||
assertFalse(hollow.contains(new IrisPosition(2, 2, 2)));
|
||||
assertTrue(hollow.contains(new IrisPosition(0, 2, 2)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void aSetTooWideToPackFallsBackToPositionEqualityWithoutChangingTheResult() {
|
||||
Set<IrisPosition> spread = new LinkedHashSet<>();
|
||||
spread.add(new IrisPosition(-20_000_000, 60, 0));
|
||||
spread.add(new IrisPosition(20_000_000, 60, 0));
|
||||
spread.add(new IrisPosition(20_000_001, 60, 0));
|
||||
|
||||
assertEquals(referenceHollowed(spread), MantleWriter.getHollowed(spread));
|
||||
assertEquals(spread, MantleWriter.getHollowed(spread));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void maskedVoxelsMatchTheReferenceMaskExpansion() {
|
||||
Set<IrisPosition> seeds = line(0, 70, 0, 3, 70, 4);
|
||||
Set<IrisPosition> masks = new LinkedHashSet<>();
|
||||
masks.add(new IrisPosition(0, 0, 0));
|
||||
masks.add(new IrisPosition(1, 0, 0));
|
||||
masks.add(new IrisPosition(0, 1, 0));
|
||||
masks.add(new IrisPosition(-1, 0, 1));
|
||||
masks.add(new IrisPosition(0, 0, -2));
|
||||
|
||||
assertEquals(referenceMasked(seeds, masks, 3.0D), MantleWriter.getMasked(seeds, masks, 3.0D));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void anEmptyMaskProducesNoVoxels() {
|
||||
assertTrue(MantleWriter.getMasked(line(0, 70, 0, 2, 70, 2), new LinkedHashSet<>(), 2.0D).isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void aPositionLookupReportsMembershipOutsideItsBoundingBoxAsAbsent() {
|
||||
Set<IrisPosition> positions = new LinkedHashSet<>();
|
||||
positions.add(new IrisPosition(10, 20, 30));
|
||||
MantleWriter.PositionLookup lookup = MantleWriter.PositionLookup.of(positions);
|
||||
|
||||
assertTrue(lookup.contains(10, 20, 30));
|
||||
assertFalse(lookup.contains(9, 20, 30));
|
||||
assertFalse(lookup.contains(10, 20, 31));
|
||||
assertFalse(lookup.contains(Integer.MIN_VALUE, 20, 30));
|
||||
assertFalse(lookup.contains(Integer.MAX_VALUE, 20, 30));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void anEmptyPositionLookupContainsNothing() {
|
||||
MantleWriter.PositionLookup lookup = MantleWriter.PositionLookup.of(new LinkedHashSet<>());
|
||||
|
||||
assertFalse(lookup.contains(0, 0, 0));
|
||||
assertFalse(lookup.contains(-1, -1, -1));
|
||||
}
|
||||
|
||||
private static Set<IrisPosition> line(int x1, int y1, int z1, int x2, int y2, int z2) {
|
||||
Set<IrisPosition> positions = new LinkedHashSet<>();
|
||||
int steps = Math.max(Math.max(Math.abs(x2 - x1), Math.abs(y2 - y1)), Math.abs(z2 - z1));
|
||||
for (int step = 0; step <= steps; step++) {
|
||||
double t = steps == 0 ? 0D : (double) step / steps;
|
||||
positions.add(new IrisPosition(
|
||||
(int) Math.round(x1 + t * (x2 - x1)),
|
||||
(int) Math.round(y1 + t * (y2 - y1)),
|
||||
(int) Math.round(z1 + t * (z2 - z1))));
|
||||
}
|
||||
return positions;
|
||||
}
|
||||
|
||||
static Set<IrisPosition> referenceBallooned(Set<IrisPosition> vset, double radius) {
|
||||
Set<IrisPosition> returnset = new HashSet<>();
|
||||
int ceilrad = (int) Math.ceil(radius);
|
||||
double r2 = Math.pow(radius, 2);
|
||||
|
||||
for (IrisPosition v : vset) {
|
||||
int tipx = v.getX();
|
||||
int tipy = v.getY();
|
||||
int tipz = v.getZ();
|
||||
|
||||
for (int loopx = tipx - ceilrad; loopx <= tipx + ceilrad; loopx++) {
|
||||
for (int loopy = tipy - ceilrad; loopy <= tipy + ceilrad; loopy++) {
|
||||
for (int loopz = tipz - ceilrad; loopz <= tipz + ceilrad; loopz++) {
|
||||
if (referenceHypot(loopx - tipx, loopy - tipy, loopz - tipz) <= r2) {
|
||||
returnset.add(new IrisPosition(loopx, loopy, loopz));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return returnset;
|
||||
}
|
||||
|
||||
static Set<IrisPosition> referenceHollowed(Set<IrisPosition> vset) {
|
||||
Set<IrisPosition> returnset = new LinkedHashSet<>();
|
||||
for (IrisPosition v : vset) {
|
||||
double x = v.getX();
|
||||
double y = v.getY();
|
||||
double z = v.getZ();
|
||||
if (!(vset.contains(new IrisPosition(x + 1, y, z))
|
||||
&& vset.contains(new IrisPosition(x - 1, y, z))
|
||||
&& vset.contains(new IrisPosition(x, y + 1, z))
|
||||
&& vset.contains(new IrisPosition(x, y - 1, z))
|
||||
&& vset.contains(new IrisPosition(x, y, z + 1))
|
||||
&& vset.contains(new IrisPosition(x, y, z - 1)))) {
|
||||
returnset.add(v);
|
||||
}
|
||||
}
|
||||
return returnset;
|
||||
}
|
||||
|
||||
static Set<IrisPosition> referenceMasked(Set<IrisPosition> vectors, Set<IrisPosition> masks, double radius) {
|
||||
Set<IrisPosition> vset = new LinkedHashSet<>();
|
||||
int ceil = (int) Math.ceil(radius);
|
||||
double r2 = Math.pow(radius, 2);
|
||||
|
||||
for (IrisPosition v : vectors) {
|
||||
int tipX = v.getX();
|
||||
int tipY = v.getY();
|
||||
int tipZ = v.getZ();
|
||||
|
||||
for (int x = -ceil; x <= ceil; x++) {
|
||||
for (int y = -ceil; y <= ceil; y++) {
|
||||
for (int z = -ceil; z <= ceil; z++) {
|
||||
if (referenceHypot(x, y, z) > r2 || !masks.contains(new IrisPosition(x, y, z))) {
|
||||
continue;
|
||||
}
|
||||
vset.add(new IrisPosition(tipX + x, tipY + y, tipZ + z));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return vset;
|
||||
}
|
||||
|
||||
private static double referenceHypot(double... pars) {
|
||||
double sum = 0;
|
||||
for (double d : pars) {
|
||||
sum += Math.pow(d, 2);
|
||||
}
|
||||
return sum;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user