mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-10-25 13:30:08 +00:00
Merged compact biome selection
- Replace expanded rarity lists with cumulative counts - Preserve ordinary selection mapping and bound extreme rarity storage
This commit is contained in:
commit
6276bb7327
6 files changed
+273
-140
No files matched your search
@@ -37,6 +37,7 @@ import art.arcane.iris.engine.image.IrisImageMapRuntime;
|
|||||||
import art.arcane.iris.engine.mantle.components.MantleHydrologyCaveVoxelView;
|
import art.arcane.iris.engine.mantle.components.MantleHydrologyCaveVoxelView;
|
||||||
import art.arcane.iris.engine.object.InferredType;
|
import art.arcane.iris.engine.object.InferredType;
|
||||||
import art.arcane.iris.engine.object.IrisBiome;
|
import art.arcane.iris.engine.object.IrisBiome;
|
||||||
|
import art.arcane.iris.engine.object.IrisRaritySelection;
|
||||||
import art.arcane.iris.engine.object.IrisDecorationPart;
|
import art.arcane.iris.engine.object.IrisDecorationPart;
|
||||||
import art.arcane.iris.engine.object.IrisDecorator;
|
import art.arcane.iris.engine.object.IrisDecorator;
|
||||||
import art.arcane.iris.engine.object.IrisGenerator;
|
import art.arcane.iris.engine.object.IrisGenerator;
|
||||||
@@ -1708,74 +1709,25 @@ public class IrisComplex implements DataProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static final class ChildSelectionPlan {
|
static final class ChildSelectionPlan {
|
||||||
private final IrisBiome[] mappedBiomes;
|
private final IrisRaritySelection<IrisBiome> selection;
|
||||||
private final int maxIndex;
|
|
||||||
|
|
||||||
private ChildSelectionPlan(IrisBiome[] mappedBiomes) {
|
private ChildSelectionPlan(IrisRaritySelection<IrisBiome> selection) {
|
||||||
this.mappedBiomes = mappedBiomes;
|
this.selection = selection;
|
||||||
this.maxIndex = mappedBiomes.length - 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static ChildSelectionPlan create(KList<IrisBiome> options) {
|
static ChildSelectionPlan create(KList<IrisBiome> options) {
|
||||||
if (options.isEmpty()) {
|
return new ChildSelectionPlan(IrisRaritySelection.create(options));
|
||||||
return new ChildSelectionPlan(new IrisBiome[0]);
|
|
||||||
}
|
|
||||||
|
|
||||||
int maxRarity = 1;
|
|
||||||
for (IrisBiome biome : options) {
|
|
||||||
if (biome != null && biome.getRarity() > maxRarity) {
|
|
||||||
maxRarity = biome.getRarity();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int rarityMax = maxRarity + 1;
|
|
||||||
boolean flip = false;
|
|
||||||
KList<IrisBiome> mapped = new KList<>();
|
|
||||||
for (IrisBiome biome : options) {
|
|
||||||
if (biome == null) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
int rarity = Math.max(1, biome.getRarity());
|
|
||||||
int count = rarityMax - rarity;
|
|
||||||
for (int index = 0; index < count; index++) {
|
|
||||||
flip = !flip;
|
|
||||||
if (flip) {
|
|
||||||
mapped.add(biome);
|
|
||||||
} else {
|
|
||||||
mapped.add(0, biome);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mapped.isEmpty()) {
|
|
||||||
IrisBiome[] fallback = new IrisBiome[]{options.get(0)};
|
|
||||||
return new ChildSelectionPlan(fallback);
|
|
||||||
}
|
|
||||||
|
|
||||||
IrisBiome[] mappedBiomes = mapped.toArray(new IrisBiome[0]);
|
|
||||||
return new ChildSelectionPlan(mappedBiomes);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
IrisBiome select(CNG childCell, double x, double z) {
|
IrisBiome select(CNG childCell, double x, double z) {
|
||||||
if (mappedBiomes.length == 0) {
|
long maximumIndex = selection.size() - 1L;
|
||||||
return null;
|
if (maximumIndex <= 0L) {
|
||||||
|
return selection.get(0L);
|
||||||
}
|
}
|
||||||
|
if (maximumIndex <= Integer.MAX_VALUE) {
|
||||||
if (mappedBiomes.length == 1) {
|
return selection.get(childCell.fit2D(0, (int) maximumIndex, x, z));
|
||||||
return mappedBiomes[0];
|
|
||||||
}
|
}
|
||||||
|
return selection.select(childCell.noiseFast2D(x, z));
|
||||||
int selectedIndex = childCell.fit2D(0, maxIndex, x, z);
|
|
||||||
if (selectedIndex < 0) {
|
|
||||||
return mappedBiomes[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (selectedIndex > maxIndex) {
|
|
||||||
return mappedBiomes[maxIndex];
|
|
||||||
}
|
|
||||||
|
|
||||||
return mappedBiomes[selectedIndex];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package art.arcane.iris.engine.object;
|
|||||||
|
|
||||||
import art.arcane.iris.core.loader.IrisData;
|
import art.arcane.iris.core.loader.IrisData;
|
||||||
import art.arcane.iris.engine.framework.Engine;
|
import art.arcane.iris.engine.framework.Engine;
|
||||||
import art.arcane.iris.util.project.interpolation.IrisInterpolation;
|
|
||||||
import art.arcane.volmlib.util.collection.KList;
|
import art.arcane.volmlib.util.collection.KList;
|
||||||
import art.arcane.iris.util.project.noise.CNG;
|
import art.arcane.iris.util.project.noise.CNG;
|
||||||
|
|
||||||
@@ -149,21 +148,20 @@ public final class IrisDimensionCarvingResolver {
|
|||||||
return parent;
|
return parent;
|
||||||
}
|
}
|
||||||
|
|
||||||
ParentSelectionPlan selectionPlan = state.selectionPlans.get(parent);
|
IrisRaritySelection<CarvingChoice> selectionPlan = state.selectionPlans.get(parent);
|
||||||
if (selectionPlan == null) {
|
if (selectionPlan == null) {
|
||||||
selectionPlan = buildSelectionPlan(engine, parent, parentBiome, entryIndex, state);
|
selectionPlan = buildSelectionPlan(engine, parent, parentBiome, entryIndex, state);
|
||||||
state.selectionPlans.put(parent, selectionPlan);
|
state.selectionPlans.put(parent, selectionPlan);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (selectionPlan.parentOnly) {
|
if (selectionPlan.size() <= 1L) {
|
||||||
return parent;
|
return parent;
|
||||||
}
|
}
|
||||||
|
|
||||||
long seed = resolveChildSeed(engine, state);
|
long seed = resolveChildSeed(engine, state);
|
||||||
CNG childGenerator = parent.getChildrenGenerator(seed, engine.getData());
|
CNG childGenerator = parent.getChildrenGenerator(seed, engine.getData());
|
||||||
double sample = childGenerator.noiseFast2D(worldX, worldZ);
|
double sample = childGenerator.noiseFast2D(worldX, worldZ);
|
||||||
int selectedIndex = (int) Math.round(IrisInterpolation.lerp(0, selectionPlan.maxIndex, sample));
|
CarvingChoice selected = selectionPlan.select(sample);
|
||||||
CarvingChoice selected = selectionPlan.get(selectedIndex);
|
|
||||||
if (selected == null || selected.entry == null) {
|
if (selected == null || selected.entry == null) {
|
||||||
return parent;
|
return parent;
|
||||||
}
|
}
|
||||||
@@ -171,7 +169,7 @@ public final class IrisDimensionCarvingResolver {
|
|||||||
return selected.entry;
|
return selected.entry;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ParentSelectionPlan buildSelectionPlan(
|
private static IrisRaritySelection<CarvingChoice> buildSelectionPlan(
|
||||||
Engine engine,
|
Engine engine,
|
||||||
IrisDimensionCarvingEntry parent,
|
IrisDimensionCarvingEntry parent,
|
||||||
IrisBiome parentBiome,
|
IrisBiome parentBiome,
|
||||||
@@ -201,42 +199,7 @@ public final class IrisDimensionCarvingResolver {
|
|||||||
}
|
}
|
||||||
|
|
||||||
options.add(new CarvingChoice(parent, rarity(parentBiome)));
|
options.add(new CarvingChoice(parent, rarity(parentBiome)));
|
||||||
if (options.size() <= 1) {
|
return IrisRaritySelection.create(options);
|
||||||
return ParentSelectionPlan.parentOnly();
|
|
||||||
}
|
|
||||||
|
|
||||||
CarvingChoice[] mappedChoices = buildRarityMappedChoices(options);
|
|
||||||
if (mappedChoices.length == 0) {
|
|
||||||
return ParentSelectionPlan.parentOnly();
|
|
||||||
}
|
|
||||||
|
|
||||||
return new ParentSelectionPlan(mappedChoices);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static CarvingChoice[] buildRarityMappedChoices(List<CarvingChoice> choices) {
|
|
||||||
int max = 1;
|
|
||||||
for (CarvingChoice choice : choices) {
|
|
||||||
if (choice.rarity > max) {
|
|
||||||
max = choice.rarity;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
max++;
|
|
||||||
List<CarvingChoice> mapped = new ArrayList<>();
|
|
||||||
boolean flip = false;
|
|
||||||
for (CarvingChoice choice : choices) {
|
|
||||||
int count = max - choice.rarity;
|
|
||||||
for (int index = 0; index < count; index++) {
|
|
||||||
flip = !flip;
|
|
||||||
if (flip) {
|
|
||||||
mapped.add(choice);
|
|
||||||
} else {
|
|
||||||
mapped.add(0, choice);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return mapped.toArray(new CarvingChoice[0]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int rarity(IrisBiome biome) {
|
private static int rarity(IrisBiome biome) {
|
||||||
@@ -285,7 +248,7 @@ public final class IrisDimensionCarvingResolver {
|
|||||||
|
|
||||||
public static final class State {
|
public static final class State {
|
||||||
private final Map<Integer, IrisDimensionCarvingEntry> rootEntriesByWorldY = new HashMap<>();
|
private final Map<Integer, IrisDimensionCarvingEntry> rootEntriesByWorldY = new HashMap<>();
|
||||||
private final Map<IrisDimensionCarvingEntry, ParentSelectionPlan> selectionPlans = new IdentityHashMap<>();
|
private final Map<IrisDimensionCarvingEntry, IrisRaritySelection<CarvingChoice>> selectionPlans = new IdentityHashMap<>();
|
||||||
private final Map<IrisDimensionCarvingEntry, IrisBiome> biomeCache = new IdentityHashMap<>();
|
private final Map<IrisDimensionCarvingEntry, IrisBiome> biomeCache = new IdentityHashMap<>();
|
||||||
private WeakReference<Engine> engineIdentity;
|
private WeakReference<Engine> engineIdentity;
|
||||||
private WeakReference<IrisDimension> dimensionIdentity;
|
private WeakReference<IrisDimension> dimensionIdentity;
|
||||||
@@ -316,44 +279,6 @@ public final class IrisDimensionCarvingResolver {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final class ParentSelectionPlan {
|
|
||||||
private final CarvingChoice[] mappedChoices;
|
|
||||||
private final int maxIndex;
|
|
||||||
private final boolean parentOnly;
|
|
||||||
|
|
||||||
private ParentSelectionPlan(CarvingChoice[] mappedChoices) {
|
|
||||||
this.mappedChoices = mappedChoices;
|
|
||||||
this.maxIndex = mappedChoices.length - 1;
|
|
||||||
this.parentOnly = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private ParentSelectionPlan() {
|
|
||||||
this.mappedChoices = null;
|
|
||||||
this.maxIndex = -1;
|
|
||||||
this.parentOnly = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static ParentSelectionPlan parentOnly() {
|
|
||||||
return new ParentSelectionPlan();
|
|
||||||
}
|
|
||||||
|
|
||||||
private CarvingChoice get(int index) {
|
|
||||||
if (mappedChoices == null || mappedChoices.length == 0) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (index < 0) {
|
|
||||||
return mappedChoices[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (index >= mappedChoices.length) {
|
|
||||||
return mappedChoices[mappedChoices.length - 1];
|
|
||||||
}
|
|
||||||
|
|
||||||
return mappedChoices[index];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final class CarvingChoice implements IRare {
|
private static final class CarvingChoice implements IRare {
|
||||||
private final IrisDimensionCarvingEntry entry;
|
private final IrisDimensionCarvingEntry entry;
|
||||||
private final int rarity;
|
private final int rarity;
|
||||||
|
|||||||
@@ -0,0 +1,60 @@
|
|||||||
|
package art.arcane.iris.engine.object;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public final class IrisRaritySelection<T extends IRare> {
|
||||||
|
private final List<T> choices;
|
||||||
|
private final long[] cumulativeCounts;
|
||||||
|
|
||||||
|
private IrisRaritySelection(List<T> choices, long[] cumulativeCounts) {
|
||||||
|
this.choices = choices;
|
||||||
|
this.cumulativeCounts = cumulativeCounts;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T extends IRare> IrisRaritySelection<T> create(List<T> options) {
|
||||||
|
ArrayList<T> choices = new ArrayList<>(options.size());
|
||||||
|
long maximumRarity = 1L;
|
||||||
|
for (T option : options) {
|
||||||
|
if (option != null) {
|
||||||
|
choices.add(option);
|
||||||
|
maximumRarity = Math.max(maximumRarity, option.getRarity());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
long[] cumulativeCounts = new long[choices.size()];
|
||||||
|
long total = 0L;
|
||||||
|
for (int index = 0; index < choices.size(); index++) {
|
||||||
|
total += maximumRarity + 1L - Math.max(1, choices.get(index).getRarity());
|
||||||
|
cumulativeCounts[index] = total;
|
||||||
|
}
|
||||||
|
return new IrisRaritySelection<>(List.copyOf(choices), cumulativeCounts);
|
||||||
|
}
|
||||||
|
|
||||||
|
public long size() {
|
||||||
|
return cumulativeCounts.length == 0 ? 0L : cumulativeCounts[cumulativeCounts.length - 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
public T select(double noiseValue) {
|
||||||
|
long maximumIndex = size() - 1L;
|
||||||
|
long selectedIndex = Math.round(noiseValue * maximumIndex);
|
||||||
|
return get(maximumIndex <= Integer.MAX_VALUE ? (int) selectedIndex : selectedIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
public T get(long index) {
|
||||||
|
long size = size();
|
||||||
|
if (size == 0L) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (choices.size() == 1) {
|
||||||
|
return choices.getFirst();
|
||||||
|
}
|
||||||
|
long selectedIndex = Math.max(0L, Math.min(size - 1L, index));
|
||||||
|
long frontSize = size / 2L;
|
||||||
|
long insertionIndex = selectedIndex < frontSize
|
||||||
|
? 2L * (frontSize - selectedIndex) - 1L
|
||||||
|
: 2L * (selectedIndex - frontSize);
|
||||||
|
int choiceIndex = Arrays.binarySearch(cumulativeCounts, insertionIndex + 1L);
|
||||||
|
return choices.get(choiceIndex < 0 ? -choiceIndex - 1 : choiceIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,8 +13,12 @@ import java.util.List;
|
|||||||
|
|
||||||
import static org.junit.Assert.assertNull;
|
import static org.junit.Assert.assertNull;
|
||||||
import static org.junit.Assert.assertSame;
|
import static org.junit.Assert.assertSame;
|
||||||
|
import static org.mockito.ArgumentMatchers.anyDouble;
|
||||||
|
import static org.mockito.ArgumentMatchers.anyInt;
|
||||||
import static org.mockito.Mockito.doReturn;
|
import static org.mockito.Mockito.doReturn;
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.never;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
|
||||||
public class IrisComplexImplodeParityTest {
|
public class IrisComplexImplodeParityTest {
|
||||||
private static Method childSelectionCreateMethod;
|
private static Method childSelectionCreateMethod;
|
||||||
@@ -48,6 +52,27 @@ public class IrisComplexImplodeParityTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void extremeCountsKeepTheRareBiomeReachableAcrossTheIntIndexBoundary() {
|
||||||
|
IrisBiome rare = createBiome(Integer.MAX_VALUE);
|
||||||
|
IrisBiome common = createBiome(1);
|
||||||
|
CNG generator = mock(CNG.class);
|
||||||
|
doReturn(1_073_741_824).when(generator).fit2D(0, Integer.MAX_VALUE, 12D, -32D);
|
||||||
|
IrisComplex.ChildSelectionPlan pair = IrisComplex.ChildSelectionPlan.create(new KList<>(rare, common));
|
||||||
|
|
||||||
|
assertSame(rare, pair.select(generator, 12D, -32D));
|
||||||
|
verify(generator).fit2D(0, Integer.MAX_VALUE, 12D, -32D);
|
||||||
|
verify(generator, never()).noiseFast2D(anyDouble(), anyDouble());
|
||||||
|
|
||||||
|
CNG largeGenerator = mock(CNG.class);
|
||||||
|
doReturn(0.5D).when(largeGenerator).noiseFast2D(12D, -32D);
|
||||||
|
IrisComplex.ChildSelectionPlan large = IrisComplex.ChildSelectionPlan.create(
|
||||||
|
new KList<>(rare, common, createBiome(2)));
|
||||||
|
assertSame(rare, large.select(largeGenerator, 12D, -32D));
|
||||||
|
verify(largeGenerator).noiseFast2D(12D, -32D);
|
||||||
|
verify(largeGenerator, never()).fit2D(anyInt(), anyInt(), anyDouble(), anyDouble());
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void emptySelectionPlanMatchesLegacyEmptyBehavior() throws Exception {
|
public void emptySelectionPlanMatchesLegacyEmptyBehavior() throws Exception {
|
||||||
KList<IrisBiome> options = new KList<>();
|
KList<IrisBiome> options = new KList<>();
|
||||||
|
|||||||
+30
@@ -35,11 +35,16 @@ import static org.junit.Assert.assertSame;
|
|||||||
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.assertTrue;
|
||||||
import static org.mockito.Answers.CALLS_REAL_METHODS;
|
import static org.mockito.Answers.CALLS_REAL_METHODS;
|
||||||
import static org.mockito.ArgumentMatchers.any;
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.ArgumentMatchers.anyDouble;
|
||||||
import static org.mockito.ArgumentMatchers.anyInt;
|
import static org.mockito.ArgumentMatchers.anyInt;
|
||||||
|
import static org.mockito.ArgumentMatchers.anyLong;
|
||||||
import static org.mockito.ArgumentMatchers.anyString;
|
import static org.mockito.ArgumentMatchers.anyString;
|
||||||
import static org.mockito.Mockito.doAnswer;
|
import static org.mockito.Mockito.doAnswer;
|
||||||
import static org.mockito.Mockito.doReturn;
|
import static org.mockito.Mockito.doReturn;
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.never;
|
||||||
|
import static org.mockito.Mockito.spy;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
|
||||||
public class IrisDimensionCarvingResolverParityTest {
|
public class IrisDimensionCarvingResolverParityTest {
|
||||||
private static final int MAX_CHILD_DEPTH = 32;
|
private static final int MAX_CHILD_DEPTH = 32;
|
||||||
@@ -71,6 +76,31 @@ public class IrisDimensionCarvingResolverParityTest {
|
|||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void extremeChildRarityRemainsReachableWithOneNoiseSample() {
|
||||||
|
Fixture fixture = createFixture();
|
||||||
|
Engine engine = fixture.engine;
|
||||||
|
IrisData data = engine.getData();
|
||||||
|
IrisBiome rare = data.getBiomeLoader().load("child-a");
|
||||||
|
IrisBiome common = data.getBiomeLoader().load("child-b");
|
||||||
|
IrisBiome parentBiome = data.getBiomeLoader().load("root-low");
|
||||||
|
doReturn(Integer.MAX_VALUE).when(rare).getRarity();
|
||||||
|
doReturn(1).when(common).getRarity();
|
||||||
|
doReturn(2).when(parentBiome).getRarity();
|
||||||
|
IrisDimensionCarvingEntry parent = spy(engine.getDimension().getCarvingEntryIndex().get("root-low"));
|
||||||
|
parent.setChildRecursionDepth(1);
|
||||||
|
CNG generator = mock(CNG.class);
|
||||||
|
doReturn(0.5D).when(generator).noiseFast2D(-17D, 23D);
|
||||||
|
doReturn(generator).when(parent).getChildrenGenerator(anyLong(), any(IrisData.class));
|
||||||
|
|
||||||
|
IrisDimensionCarvingEntry selected = IrisDimensionCarvingResolver.resolveFromRoot(
|
||||||
|
engine, parent, -17, 23, new IrisDimensionCarvingResolver.State());
|
||||||
|
|
||||||
|
assertSame(engine.getDimension().getCarvingEntryIndex().get("child-a"), selected);
|
||||||
|
verify(generator).noiseFast2D(-17D, 23D);
|
||||||
|
verify(generator, never()).fit2D(anyInt(), anyInt(), anyDouble(), anyDouble());
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void resolverStatefulOverloadsMatchLegacyResolverAcrossSampleGrid() {
|
public void resolverStatefulOverloadsMatchLegacyResolverAcrossSampleGrid() {
|
||||||
Fixture fixture = createFixture();
|
Fixture fixture = createFixture();
|
||||||
|
|||||||
@@ -0,0 +1,141 @@
|
|||||||
|
package art.arcane.iris.engine.object;
|
||||||
|
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Random;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertNull;
|
||||||
|
import static org.junit.Assert.assertSame;
|
||||||
|
|
||||||
|
public class IrisRaritySelectionTest {
|
||||||
|
@Test
|
||||||
|
public void everyIndexMatchesExpandedInsertionOrderForRandomizedChoices() {
|
||||||
|
Random random = new Random(718938L);
|
||||||
|
for (int scenario = 0; scenario < 500; scenario++) {
|
||||||
|
ArrayList<Choice> choices = new ArrayList<>();
|
||||||
|
int count = random.nextInt(12);
|
||||||
|
for (int index = 0; index < count; index++) {
|
||||||
|
choices.add(random.nextInt(7) == 0 ? null : new Choice(index, random.nextInt(35) - 3));
|
||||||
|
}
|
||||||
|
List<Choice> expanded = expand(choices);
|
||||||
|
IrisRaritySelection<Choice> selection = IrisRaritySelection.create(choices);
|
||||||
|
assertEquals(expanded.size(), selection.size());
|
||||||
|
if (expanded.isEmpty()) {
|
||||||
|
assertNull(selection.get(0));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
for (int index = 0; index < expanded.size(); index++) {
|
||||||
|
assertSame(expanded.get(index), selection.get(index));
|
||||||
|
}
|
||||||
|
assertSame(expanded.getFirst(), selection.get(Long.MIN_VALUE));
|
||||||
|
assertSame(expanded.getLast(), selection.get(Long.MAX_VALUE));
|
||||||
|
for (double sample : new double[]{-3D, -0D, 0.125D, 0.5D, 0.875D, 1D, 3D,
|
||||||
|
Double.NaN, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY}) {
|
||||||
|
int index = (int) Math.round(sample * (expanded.size() - 1));
|
||||||
|
assertSame(expanded.get(Math.max(0, Math.min(expanded.size() - 1, index))), selection.select(sample));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(timeout = 1000)
|
||||||
|
public void maximumRarityCountsStayPositiveWithoutExpandingBillionsOfEntries() {
|
||||||
|
Choice rare = new Choice(0, Integer.MAX_VALUE);
|
||||||
|
Choice common = new Choice(1, 1);
|
||||||
|
Choice next = new Choice(2, 2);
|
||||||
|
IrisRaritySelection<Choice> selection = IrisRaritySelection.create(List.of(rare, common, next));
|
||||||
|
|
||||||
|
assertEquals(4_294_967_294L, selection.size());
|
||||||
|
assertSame(next, selection.get(0L));
|
||||||
|
assertSame(common, selection.get(2_147_483_646L));
|
||||||
|
assertSame(rare, selection.get(2_147_483_647L));
|
||||||
|
assertSame(common, selection.get(2_147_483_648L));
|
||||||
|
assertSame(next, selection.get(selection.size() - 1L));
|
||||||
|
assertSame(rare, selection.select(0.5D));
|
||||||
|
assertSame(next, selection.select(0D));
|
||||||
|
assertSame(next, selection.select(1D));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void equalMaximumRaritiesRetainEveryChoice() {
|
||||||
|
Choice first = new Choice(0, Integer.MAX_VALUE);
|
||||||
|
Choice second = new Choice(1, Integer.MAX_VALUE);
|
||||||
|
Choice third = new Choice(2, Integer.MAX_VALUE);
|
||||||
|
IrisRaritySelection<Choice> selection = IrisRaritySelection.create(List.of(first, second, third));
|
||||||
|
|
||||||
|
assertEquals(3L, selection.size());
|
||||||
|
assertSame(second, selection.get(0L));
|
||||||
|
assertSame(first, selection.get(1L));
|
||||||
|
assertSame(third, selection.get(2L));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void duplicateChoicesKeepTheirSeparateInsertionPositions() {
|
||||||
|
Choice repeated = new Choice(0, 1);
|
||||||
|
Choice other = new Choice(1, 3);
|
||||||
|
List<Choice> choices = List.of(repeated, other, repeated);
|
||||||
|
IrisRaritySelection<Choice> selection = IrisRaritySelection.create(choices);
|
||||||
|
List<Choice> expanded = expand(choices);
|
||||||
|
for (int index = 0; index < expanded.size(); index++) {
|
||||||
|
assertSame(expanded.get(index), selection.get(index));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void deserializedRuntimeRarityValuesUseTheExistingMinimumClamp() {
|
||||||
|
Gson gson = new Gson();
|
||||||
|
IrisBiome zero = gson.fromJson("{\"rarity\":0}", IrisBiome.class);
|
||||||
|
IrisBiome negative = gson.fromJson("{\"rarity\":-2147483648}", IrisBiome.class);
|
||||||
|
IrisBiome maximum = gson.fromJson("{\"rarity\":2147483647}", IrisBiome.class);
|
||||||
|
IrisRaritySelection<IrisBiome> selection = IrisRaritySelection.create(List.of(zero, negative, maximum));
|
||||||
|
|
||||||
|
assertEquals(4_294_967_295L, selection.size());
|
||||||
|
assertSame(zero, selection.get(2_147_483_647L));
|
||||||
|
assertSame(maximum, selection.get(selection.size() - 1L));
|
||||||
|
assertEquals(0, zero.getRarity());
|
||||||
|
assertEquals(Integer.MIN_VALUE, negative.getRarity());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void nullAndEmptyOptionsDoNotProduceCandidates() {
|
||||||
|
assertNull(IrisRaritySelection.<Choice>create(List.of()).select(0.5D));
|
||||||
|
assertNull(IrisRaritySelection.<Choice>create(Arrays.asList(null, null)).get(0L));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<Choice> expand(List<Choice> choices) {
|
||||||
|
int maximum = 1;
|
||||||
|
for (Choice choice : choices) {
|
||||||
|
if (choice != null) {
|
||||||
|
maximum = Math.max(maximum, choice.rarity());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ArrayList<Choice> expanded = new ArrayList<>();
|
||||||
|
boolean append = false;
|
||||||
|
for (Choice choice : choices) {
|
||||||
|
if (choice == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
int count = maximum + 1 - Math.max(1, choice.rarity());
|
||||||
|
for (int index = 0; index < count; index++) {
|
||||||
|
append = !append;
|
||||||
|
if (append) {
|
||||||
|
expanded.add(choice);
|
||||||
|
} else {
|
||||||
|
expanded.add(0, choice);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return expanded;
|
||||||
|
}
|
||||||
|
|
||||||
|
private record Choice(int id, int rarity) implements IRare {
|
||||||
|
@Override
|
||||||
|
public int getRarity() {
|
||||||
|
return rarity;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in new issue
Block a user