mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-10-27 06:20:02 +00:00
Enforce seed cohesion
- Scope cached generation fields to their seed and context. - Warm decorators and ores with their canonical generation seeds. - Stabilize object traversal and procedural placement choices. - Isolate smart-bore variants from shared source objects. - Pin noise and full-engine output across generation orders. - Verify same-jar generation through cold server restarts.
This commit is contained in:
1 parent
6b82ea99e2
commit
4ef6fc7cc6
50 files changed
+1734
-259
No files matched your search
@@ -51,7 +51,7 @@ public final class FloatingIslandSample {
|
||||
private static final double CARVE_MAX_VERTICAL_RUN_FRACTION = 0.18D;
|
||||
private static final ThreadLocal<int[]> LAST_REJECT = ThreadLocal.withInitial(() -> new int[1]);
|
||||
private static final ThreadLocal<double[]> LAST_DENSITY = ThreadLocal.withInitial(() -> new double[2]);
|
||||
private static final ThreadLocal<HashMap<Long, FloatingIslandSample>> CHUNK_MEMO = ThreadLocal.withInitial(HashMap::new);
|
||||
private static final ThreadLocal<ChunkMemo> CHUNK_MEMO = ThreadLocal.withInitial(ChunkMemo::new);
|
||||
private static final ThreadLocal<IdentityHashMap<CNG, Long2DoubleOpenHashMap>> FOOTPRINT_MEMO = ThreadLocal.withInitial(IdentityHashMap::new);
|
||||
private static final AtomicBoolean NULL_CNG_WARNED = new AtomicBoolean(false);
|
||||
|
||||
@@ -68,21 +68,32 @@ public final class FloatingIslandSample {
|
||||
}
|
||||
|
||||
public static void clearThreadCaches() {
|
||||
CHUNK_MEMO.remove();
|
||||
FOOTPRINT_MEMO.remove();
|
||||
LAST_REJECT.remove();
|
||||
LAST_DENSITY.remove();
|
||||
}
|
||||
|
||||
public static void clearChunkMemo() {
|
||||
CHUNK_MEMO.get().clear();
|
||||
CHUNK_MEMO.get().samples.clear();
|
||||
FOOTPRINT_MEMO.get().clear();
|
||||
}
|
||||
|
||||
public static FloatingIslandSample sampleMemoized(IrisBiome parent, int wx, int wz, int chunkHeight, long baseSeed, IrisData data, Engine engine, FloatingIslandBoundarySampler boundarySampler) {
|
||||
long key = (((long) wx) << 32) ^ (wz & 0xFFFFFFFFL);
|
||||
HashMap<Long, FloatingIslandSample> memo = CHUNK_MEMO.get();
|
||||
if (memo.containsKey(key)) {
|
||||
return memo.get(key);
|
||||
ChunkMemo memo = CHUNK_MEMO.get();
|
||||
if (memo.context == null || !memo.context.matches(wx >> 4, wz >> 4, chunkHeight, baseSeed,
|
||||
data, engine, boundarySampler)) {
|
||||
memo.samples.clear();
|
||||
FOOTPRINT_MEMO.get().clear();
|
||||
memo.context = new MemoContext(wx >> 4, wz >> 4, chunkHeight, baseSeed, data, engine, boundarySampler);
|
||||
}
|
||||
MemoizedSample cached = memo.samples.get(key);
|
||||
if (cached != null && cached.parent == parent) {
|
||||
return cached.sample;
|
||||
}
|
||||
FloatingIslandSample result = sample(parent, wx, wz, chunkHeight, baseSeed, data, engine, boundarySampler);
|
||||
memo.put(key, result);
|
||||
memo.samples.put(key, new MemoizedSample(parent, result));
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -799,4 +810,21 @@ public final class FloatingIslandSample {
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private static final class ChunkMemo {
|
||||
private final HashMap<Long, MemoizedSample> samples = new HashMap<>(256);
|
||||
private MemoContext context;
|
||||
}
|
||||
|
||||
private record MemoContext(int chunkX, int chunkZ, int height, long seed, IrisData data, Engine engine,
|
||||
FloatingIslandBoundarySampler boundarySampler) {
|
||||
private boolean matches(int chunkX, int chunkZ, int height, long seed, IrisData data, Engine engine,
|
||||
FloatingIslandBoundarySampler boundarySampler) {
|
||||
return this.chunkX == chunkX && this.chunkZ == chunkZ && this.height == height && this.seed == seed
|
||||
&& this.data == data && this.engine == engine && this.boundarySampler == boundarySampler;
|
||||
}
|
||||
}
|
||||
|
||||
private record MemoizedSample(IrisBiome parent, FloatingIslandSample sample) {
|
||||
}
|
||||
}
|
||||
@@ -105,7 +105,12 @@ public class IrisBiome extends IrisRegistrant implements Rarity {
|
||||
private final transient AtomicCache<Color> cacheColorDecoratorLoad = new AtomicCache<>();
|
||||
private final transient AtomicCache<Color> cacheColorLayerLoad = new AtomicCache<>();
|
||||
private final transient AtomicCache<Color> cacheColorDepositLoad = new AtomicCache<>();
|
||||
private final transient AtomicCache<CNG> childrenCell = new AtomicCache<>();
|
||||
@Getter(AccessLevel.NONE)
|
||||
@Setter(AccessLevel.NONE)
|
||||
private final transient LazyBoundedCache<ChildGeneratorKey, CNG> childrenGenerators = new LazyBoundedCache<>(8);
|
||||
@Getter(AccessLevel.NONE)
|
||||
@Setter(AccessLevel.NONE)
|
||||
private transient volatile CachedChildGenerator recentChildGenerator;
|
||||
@Getter(AccessLevel.NONE)
|
||||
private final transient LazyBoundedCache<Long, CNG> biomeGenerators =
|
||||
new LazyBoundedCache<>(BIOME_GENERATOR_CACHE_SIZE);
|
||||
@@ -512,11 +517,18 @@ public class IrisBiome extends IrisRegistrant implements Rarity {
|
||||
}
|
||||
|
||||
public CNG getChildrenGenerator(RNG random, int sig, double scale) {
|
||||
return childrenCell.aquire(() -> createChildrenGenerator(random, sig, scale));
|
||||
}
|
||||
|
||||
private CNG createChildrenGenerator(RNG random, int sig, double scale) {
|
||||
return getChildStyle().createScaledGenerator(deriveChildStyleSeed(random, sig), getLoader(), scale);
|
||||
long seed = deriveChildStyleSeed(random, sig).getSeed();
|
||||
IrisData data = getLoader();
|
||||
Engine engine = data == null ? null : data.getEngine();
|
||||
CachedChildGenerator recent = recentChildGenerator;
|
||||
if (recent != null && recent.key.matches(seed, scale, data, engine)) {
|
||||
return recent.generator;
|
||||
}
|
||||
ChildGeneratorKey key = new ChildGeneratorKey(seed, scale, data, engine);
|
||||
CNG generator = childrenGenerators.computeIfAbsent(key,
|
||||
ignored -> getChildStyle().createScaledGenerator(new RNG(seed), data, scale));
|
||||
recentChildGenerator = new CachedChildGenerator(key, generator);
|
||||
return generator;
|
||||
}
|
||||
|
||||
static RNG deriveChildStyleSeed(RNG random, int signature) {
|
||||
@@ -698,4 +710,27 @@ public class IrisBiome extends IrisRegistrant implements Rarity {
|
||||
this.generator = generator;
|
||||
}
|
||||
}
|
||||
|
||||
private record ChildGeneratorKey(long seed, double scale, IrisData data, Engine engine) {
|
||||
private boolean matches(long seed, double scale, IrisData data, Engine engine) {
|
||||
return this.seed == seed && Double.doubleToLongBits(this.scale) == Double.doubleToLongBits(scale)
|
||||
&& this.data == data && this.engine == engine;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object object) {
|
||||
return object instanceof ChildGeneratorKey other && matches(other.seed, other.scale, other.data, other.engine);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int hash = Long.hashCode(seed);
|
||||
hash = 31 * hash + Double.hashCode(scale);
|
||||
hash = 31 * hash + System.identityHashCode(data);
|
||||
return 31 * hash + System.identityHashCode(engine);
|
||||
}
|
||||
}
|
||||
|
||||
private record CachedChildGenerator(ChildGeneratorKey key, CNG generator) {
|
||||
}
|
||||
}
|
||||
@@ -33,6 +33,10 @@ import art.arcane.volmlib.util.math.Rarity;
|
||||
|
||||
import art.arcane.iris.pack.loading.IrisData;
|
||||
import art.arcane.volmlib.util.cache.AtomicCache;
|
||||
import art.arcane.iris.generation.cache.LazyBoundedCache;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import art.arcane.iris.generation.runtime.Engine;
|
||||
import art.arcane.iris.pack.schema.annotation.ArrayType;
|
||||
import art.arcane.volmlib.util.documentation.Description;
|
||||
@@ -43,7 +47,6 @@ import art.arcane.iris.pack.schema.annotation.RegistryListResource;
|
||||
import art.arcane.iris.pack.schema.annotation.Snippet;
|
||||
import art.arcane.volmlib.util.noise.CNG;
|
||||
import art.arcane.volmlib.util.collection.KList;
|
||||
import art.arcane.volmlib.util.math.RNG;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
@@ -59,48 +62,59 @@ import lombok.experimental.Accessors;
|
||||
@Data
|
||||
public class IrisFloatingChildBiomes implements Rarity {
|
||||
private final transient AtomicCache<IrisBiome> resolvedBiome = new AtomicCache<>();
|
||||
private final transient AtomicCache<CNG> footprintCache = new AtomicCache<>();
|
||||
private final transient AtomicCache<FloatingIslandEdgeProfile> edgeTaperProfileCache = new AtomicCache<>();
|
||||
private final transient AtomicCache<CNG> pickerCache = new AtomicCache<>();
|
||||
private final transient AtomicCache<CNG> altitudeCache = new AtomicCache<>();
|
||||
private final transient AtomicCache<CNG> topShapeCache = new AtomicCache<>();
|
||||
private final transient AtomicCache<CNG> bottomCache = new AtomicCache<>();
|
||||
private final transient AtomicCache<CNG> wallWarpCache = new AtomicCache<>();
|
||||
private final transient AtomicCache<CNG> carveCache = new AtomicCache<>();
|
||||
private final transient AtomicCache<IrisCaveProfileSampler> carvingProfileSamplerCache = new AtomicCache<>(true);
|
||||
@Getter(AccessLevel.NONE)
|
||||
@Setter(AccessLevel.NONE)
|
||||
private final transient LazyBoundedCache<RuntimeKey, FloatingIslandEdgeProfile> edgeTaperProfiles = new LazyBoundedCache<>(8);
|
||||
@Getter(AccessLevel.NONE)
|
||||
@Setter(AccessLevel.NONE)
|
||||
private transient volatile CachedEdgeProfile recentEdgeProfile;
|
||||
@Getter(AccessLevel.NONE)
|
||||
@Setter(AccessLevel.NONE)
|
||||
private final transient LazyBoundedCache<RuntimeKey, IrisCaveProfileSampler> carvingProfileSamplers = new LazyBoundedCache<>(8);
|
||||
@Getter(AccessLevel.NONE)
|
||||
@Setter(AccessLevel.NONE)
|
||||
private transient volatile CachedCarvingProfile recentCarvingProfile;
|
||||
private final transient AtomicCache<IrisObjectScale> shrinkScaleCache = new AtomicCache<>();
|
||||
|
||||
public CNG getFootprintCng(long baseSeed, IrisData data) {
|
||||
return footprintCache.aquire(() -> getFootprintStyle().create(new RNG(baseSeed ^ 0xF007B17DL), data));
|
||||
return getFootprintStyle().create(baseSeed ^ 0xF007B17DL, data);
|
||||
}
|
||||
|
||||
public FloatingIslandEdgeProfile getEdgeTaperProfile(long baseSeed, IrisData data) {
|
||||
return edgeTaperProfileCache.aquire(() -> {
|
||||
Engine engine = data == null ? null : data.getEngine();
|
||||
CachedEdgeProfile recent = recentEdgeProfile;
|
||||
if (recent != null && recent.key.matches(baseSeed, data, engine)) {
|
||||
return recent.profile;
|
||||
}
|
||||
RuntimeKey key = new RuntimeKey(baseSeed, data, engine);
|
||||
FloatingIslandEdgeProfile profile = edgeTaperProfiles.computeIfAbsent(key, ignored -> {
|
||||
int width = FloatingIslandEdgeProfile.clampWidth(getEdgeTaperWidth());
|
||||
double amplitude = FloatingIslandEdgeProfile.clampVariationAmplitude(
|
||||
getEdgeTaperVariationAmplitude(), width);
|
||||
IrisGeneratorStyle style = getEdgeTaperVariationStyle();
|
||||
CNG variation = amplitude > 0.0D && style != null
|
||||
? style.create(new RNG(baseSeed ^ 0xED6E7A9E5L), data)
|
||||
? style.create(baseSeed ^ 0xED6E7A9E5L, data)
|
||||
: null;
|
||||
return new FloatingIslandEdgeProfile(width, getEdgeTaperExponent(), amplitude, variation);
|
||||
});
|
||||
recentEdgeProfile = new CachedEdgeProfile(key, profile);
|
||||
return profile;
|
||||
}
|
||||
|
||||
public CNG getPickerCng(long baseSeed, IrisData data) {
|
||||
return pickerCache.aquire(() -> getPickerStyle().create(new RNG(baseSeed ^ 0x91C4E72DL), data));
|
||||
return getPickerStyle().create(baseSeed ^ 0x91C4E72DL, data);
|
||||
}
|
||||
|
||||
public CNG getAltitudeCng(long baseSeed, IrisData data) {
|
||||
return altitudeCache.aquire(() -> getAltitudeStyle().create(new RNG(baseSeed ^ 0xA17DEBBL), data));
|
||||
return getAltitudeStyle().create(baseSeed ^ 0xA17DEBBL, data);
|
||||
}
|
||||
|
||||
public CNG getTopShapeCng(long baseSeed, IrisData data) {
|
||||
return topShapeCache.aquire(() -> getTopShapeStyle().create(new RNG(baseSeed ^ 0x70970601DEFL), data));
|
||||
return getTopShapeStyle().create(baseSeed ^ 0x70970601DEFL, data);
|
||||
}
|
||||
|
||||
public CNG getBottomCng(long baseSeed, IrisData data) {
|
||||
return bottomCache.aquire(() -> getBottomStyle().create(new RNG(baseSeed ^ 0xB0770075CAFEL), data));
|
||||
return getBottomStyle().create(baseSeed ^ 0xB0770075CAFEL, data);
|
||||
}
|
||||
|
||||
public CNG getWallWarpCng(long baseSeed, IrisData data) {
|
||||
@@ -108,7 +122,7 @@ public class IrisFloatingChildBiomes implements Rarity {
|
||||
if (style == null) {
|
||||
return null;
|
||||
}
|
||||
return wallWarpCache.aquire(() -> style.create(new RNG(baseSeed ^ 0xA117BA17E0FL), data));
|
||||
return style.create(baseSeed ^ 0xA117BA17E0FL, data);
|
||||
}
|
||||
|
||||
public CNG getCarveCng(long baseSeed, IrisData data) {
|
||||
@@ -116,7 +130,7 @@ public class IrisFloatingChildBiomes implements Rarity {
|
||||
if (style == null) {
|
||||
return null;
|
||||
}
|
||||
return carveCache.aquire(() -> style.create(new RNG(baseSeed ^ 0xCA5EC1EE5EL), data));
|
||||
return style.create(baseSeed ^ 0xCA5EC1EE5EL, data);
|
||||
}
|
||||
|
||||
public IrisCaveProfileSampler getCarvingProfileSampler(Engine engine, IrisData data) {
|
||||
@@ -124,7 +138,12 @@ public class IrisFloatingChildBiomes implements Rarity {
|
||||
return null;
|
||||
}
|
||||
|
||||
return carvingProfileSamplerCache.aquire(() -> {
|
||||
CachedCarvingProfile recent = recentCarvingProfile;
|
||||
if (recent != null && recent.key.matches(0L, data, engine)) {
|
||||
return recent.sampler;
|
||||
}
|
||||
RuntimeKey key = new RuntimeKey(0L, data, engine);
|
||||
IrisCaveProfileSampler sampler = carvingProfileSamplers.computeIfAbsent(key, ignored -> {
|
||||
IrisBiome resolved = resolveCarvingBiome(carving, engine, data);
|
||||
if (resolved == null || resolved.getCaveProfile() == null || !resolved.getCaveProfile().isEnabled()) {
|
||||
return null;
|
||||
@@ -132,6 +151,8 @@ public class IrisFloatingChildBiomes implements Rarity {
|
||||
|
||||
return new IrisCaveProfileSampler(engine, resolved.getCaveProfile());
|
||||
});
|
||||
recentCarvingProfile = new CachedCarvingProfile(key, sampler);
|
||||
return sampler;
|
||||
}
|
||||
|
||||
public boolean hasCarvingReference() {
|
||||
@@ -379,4 +400,28 @@ public class IrisFloatingChildBiomes implements Rarity {
|
||||
return loaded;
|
||||
});
|
||||
}
|
||||
|
||||
private record RuntimeKey(long seed, IrisData data, Engine engine) {
|
||||
private boolean matches(long seed, IrisData data, Engine engine) {
|
||||
return this.seed == seed && this.data == data && this.engine == engine;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object object) {
|
||||
return object instanceof RuntimeKey other && matches(other.seed, other.data, other.engine);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int hash = Long.hashCode(seed);
|
||||
hash = 31 * hash + System.identityHashCode(data);
|
||||
return 31 * hash + System.identityHashCode(engine);
|
||||
}
|
||||
}
|
||||
|
||||
private record CachedEdgeProfile(RuntimeKey key, FloatingIslandEdgeProfile profile) {
|
||||
}
|
||||
|
||||
private record CachedCarvingProfile(RuntimeKey key, IrisCaveProfileSampler sampler) {
|
||||
}
|
||||
}
|
||||
@@ -6,18 +6,28 @@ import art.arcane.iris.generation.geometry.IrisBlockVector;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
public class VectorMap<T> implements Iterable<Map.Entry<IrisBlockVector, T>> {
|
||||
private final Map<Key, Map<Key, T>> map = new KMap<>();
|
||||
private transient volatile long modificationRevision;
|
||||
private final AtomicLong modificationRevision = new AtomicLong();
|
||||
private transient volatile OrderedSnapshot<T> orderedSnapshot;
|
||||
|
||||
public long modificationRevision() {
|
||||
return modificationRevision;
|
||||
return modificationRevision.get();
|
||||
}
|
||||
|
||||
private void changed() {
|
||||
modificationRevision.incrementAndGet();
|
||||
orderedSnapshot = null;
|
||||
}
|
||||
|
||||
public int size() {
|
||||
@@ -30,7 +40,7 @@ public class VectorMap<T> implements Iterable<Map.Entry<IrisBlockVector, T>> {
|
||||
|
||||
public boolean containsKey(@NonNull IrisBlockVector vector) {
|
||||
if (map.isEmpty()) return false;
|
||||
var chunk = map.get(chunk(vector));
|
||||
Map<Key, T> chunk = map.get(chunk(vector));
|
||||
return chunk != null && chunk.containsKey(relative(vector));
|
||||
}
|
||||
|
||||
@@ -40,14 +50,14 @@ public class VectorMap<T> implements Iterable<Map.Entry<IrisBlockVector, T>> {
|
||||
|
||||
public @Nullable T get(@NonNull IrisBlockVector vector) {
|
||||
if (map.isEmpty()) return null;
|
||||
var chunk = map.get(chunk(vector));
|
||||
Map<Key, T> chunk = map.get(chunk(vector));
|
||||
return chunk == null ? null : chunk.get(relative(vector));
|
||||
}
|
||||
|
||||
public @Nullable T put(@NonNull IrisBlockVector vector, @NonNull T value) {
|
||||
T previous = map.computeIfAbsent(chunk(vector), k -> new KMap<>())
|
||||
.put(relative(vector), value);
|
||||
modificationRevision++;
|
||||
changed();
|
||||
return previous;
|
||||
}
|
||||
|
||||
@@ -59,7 +69,7 @@ public class VectorMap<T> implements Iterable<Map.Entry<IrisBlockVector, T>> {
|
||||
return mappingFunction.apply(vector);
|
||||
});
|
||||
if (inserted[0]) {
|
||||
modificationRevision++;
|
||||
changed();
|
||||
}
|
||||
return value;
|
||||
}
|
||||
@@ -77,7 +87,7 @@ public class VectorMap<T> implements Iterable<Map.Entry<IrisBlockVector, T>> {
|
||||
});
|
||||
|
||||
if (removed[0] != null) {
|
||||
modificationRevision++;
|
||||
changed();
|
||||
}
|
||||
|
||||
return (T) removed[0];
|
||||
@@ -89,22 +99,47 @@ public class VectorMap<T> implements Iterable<Map.Entry<IrisBlockVector, T>> {
|
||||
|
||||
public void clear() {
|
||||
if (!map.isEmpty()) {
|
||||
modificationRevision++;
|
||||
map.clear();
|
||||
changed();
|
||||
}
|
||||
map.clear();
|
||||
}
|
||||
|
||||
public void forEach(@NonNull BiConsumer<@NonNull IrisBlockVector, @NonNull T> consumer) {
|
||||
map.forEach((chunk, values) -> {
|
||||
int rX = chunk.x << 10;
|
||||
int rY = chunk.y << 10;
|
||||
int rZ = chunk.z << 10;
|
||||
Cursor cursor = cursor();
|
||||
while (cursor.next()) {
|
||||
consumer.accept(cursor.key().clone(), cursor.value());
|
||||
}
|
||||
}
|
||||
|
||||
values.forEach((relative, value) -> consumer.accept(
|
||||
relative.resolve(rX, rY, rZ),
|
||||
value
|
||||
));
|
||||
});
|
||||
private List<OrderedEntry<T>> orderedEntries() {
|
||||
long revision = modificationRevision.get();
|
||||
OrderedSnapshot<T> cached = orderedSnapshot;
|
||||
if (cached != null && cached.revision() == revision) {
|
||||
return cached.entries();
|
||||
}
|
||||
synchronized (this) {
|
||||
revision = modificationRevision.get();
|
||||
cached = orderedSnapshot;
|
||||
if (cached != null && cached.revision() == revision) {
|
||||
return cached.entries();
|
||||
}
|
||||
List<OrderedEntry<T>> entries = new ArrayList<>();
|
||||
map.forEach((chunk, values) -> values.forEach((relative, value) -> entries.add(
|
||||
new OrderedEntry<>((chunk.x << 10) + relative.x, (chunk.y << 10) + relative.y,
|
||||
(chunk.z << 10) + relative.z, value))));
|
||||
entries.sort(Comparator.comparingInt((OrderedEntry<T> entry) -> entry.x())
|
||||
.thenComparingInt(OrderedEntry::y).thenComparingInt(OrderedEntry::z));
|
||||
if (modificationRevision.get() == revision) {
|
||||
orderedSnapshot = new OrderedSnapshot<>(revision, entries);
|
||||
}
|
||||
return entries;
|
||||
}
|
||||
}
|
||||
|
||||
private record OrderedEntry<T>(int x, int y, int z, T value) {
|
||||
}
|
||||
|
||||
private record OrderedSnapshot<T>(long revision, List<OrderedEntry<T>> entries) {
|
||||
}
|
||||
|
||||
private static Key chunk(IrisBlockVector vector) {
|
||||
@@ -139,32 +174,21 @@ public class VectorMap<T> implements Iterable<Map.Entry<IrisBlockVector, T>> {
|
||||
}
|
||||
|
||||
public final class Cursor {
|
||||
private final Iterator<Map.Entry<Key, Map<Key, T>>> chunkIterator = map.entrySet().iterator();
|
||||
private final List<OrderedEntry<T>> entries = orderedEntries();
|
||||
private final IrisBlockVector position = new IrisBlockVector(0, 0, 0);
|
||||
private Iterator<Map.Entry<Key, T>> relativeIterator;
|
||||
private int rX, rY, rZ;
|
||||
private int index;
|
||||
private T value;
|
||||
|
||||
public boolean next() {
|
||||
while (relativeIterator == null || !relativeIterator.hasNext()) {
|
||||
if (!chunkIterator.hasNext()) {
|
||||
value = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
Map.Entry<Key, Map<Key, T>> chunk = chunkIterator.next();
|
||||
rX = chunk.getKey().x << 10;
|
||||
rY = chunk.getKey().y << 10;
|
||||
rZ = chunk.getKey().z << 10;
|
||||
relativeIterator = chunk.getValue().entrySet().iterator();
|
||||
if (index >= entries.size()) {
|
||||
value = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
Map.Entry<Key, T> entry = relativeIterator.next();
|
||||
Key relative = entry.getKey();
|
||||
position.setX(rX + relative.x);
|
||||
position.setY(rY + relative.y);
|
||||
position.setZ(rZ + relative.z);
|
||||
value = entry.getValue();
|
||||
OrderedEntry<T> entry = entries.get(index++);
|
||||
position.setX(entry.x());
|
||||
position.setY(entry.y());
|
||||
position.setZ(entry.z());
|
||||
value = entry.value();
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -181,79 +205,62 @@ public class VectorMap<T> implements Iterable<Map.Entry<IrisBlockVector, T>> {
|
||||
}
|
||||
|
||||
public class EntryIterator implements Iterator<Map.Entry<IrisBlockVector, T>> {
|
||||
private final Iterator<Map.Entry<Key, Map<Key, T>>> chunkIterator = map.entrySet().iterator();
|
||||
private Iterator<Map.Entry<Key, T>> relativeIterator;
|
||||
private int rX, rY, rZ;
|
||||
private final List<OrderedEntry<T>> entries = orderedEntries();
|
||||
private int index;
|
||||
private OrderedEntry<T> last;
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return advance();
|
||||
return index < entries.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map.Entry<IrisBlockVector, T> next() {
|
||||
if (!advance()) throw new NoSuchElementException();
|
||||
|
||||
var entry = relativeIterator.next();
|
||||
return Map.entry(entry.getKey().resolve(rX, rY, rZ), entry.getValue());
|
||||
}
|
||||
|
||||
private boolean advance() {
|
||||
while (relativeIterator == null || !relativeIterator.hasNext()) {
|
||||
if (!chunkIterator.hasNext()) return false;
|
||||
var chunk = chunkIterator.next();
|
||||
rX = chunk.getKey().x << 10;
|
||||
rY = chunk.getKey().y << 10;
|
||||
rZ = chunk.getKey().z << 10;
|
||||
relativeIterator = chunk.getValue().entrySet().iterator();
|
||||
if (!hasNext()) {
|
||||
throw new NoSuchElementException();
|
||||
}
|
||||
|
||||
return true;
|
||||
OrderedEntry<T> entry = entries.get(index++);
|
||||
last = entry;
|
||||
return Map.entry(new IrisBlockVector(entry.x(), entry.y(), entry.z()), entry.value());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove() {
|
||||
if (relativeIterator == null) throw new IllegalStateException("No element to remove");
|
||||
relativeIterator.remove();
|
||||
modificationRevision++;
|
||||
if (last == null) {
|
||||
throw new IllegalStateException("No element to remove");
|
||||
}
|
||||
VectorMap.this.remove(new IrisBlockVector(last.x(), last.y(), last.z()));
|
||||
last = null;
|
||||
}
|
||||
}
|
||||
|
||||
public class KeyIterator implements Iterator<IrisBlockVector>, Iterable<IrisBlockVector> {
|
||||
private final Iterator<Map.Entry<Key, Map<Key, T>>> chunkIterator = map.entrySet().iterator();
|
||||
private Iterator<Key> relativeIterator;
|
||||
private int rX, rY, rZ;
|
||||
private final List<OrderedEntry<T>> entries = orderedEntries();
|
||||
private int index;
|
||||
private OrderedEntry<T> last;
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return advance();
|
||||
return index < entries.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public IrisBlockVector next() {
|
||||
if (!advance()) throw new NoSuchElementException();
|
||||
|
||||
return relativeIterator.next().resolve(rX, rY, rZ);
|
||||
}
|
||||
|
||||
private boolean advance() {
|
||||
while (relativeIterator == null || !relativeIterator.hasNext()) {
|
||||
if (!chunkIterator.hasNext()) return false;
|
||||
var chunk = chunkIterator.next();
|
||||
rX = chunk.getKey().x << 10;
|
||||
rY = chunk.getKey().y << 10;
|
||||
rZ = chunk.getKey().z << 10;
|
||||
relativeIterator = chunk.getValue().keySet().iterator();
|
||||
if (!hasNext()) {
|
||||
throw new NoSuchElementException();
|
||||
}
|
||||
|
||||
return true;
|
||||
OrderedEntry<T> entry = entries.get(index++);
|
||||
last = entry;
|
||||
return new IrisBlockVector(entry.x(), entry.y(), entry.z());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove() {
|
||||
if (relativeIterator == null) throw new IllegalStateException("No element to remove");
|
||||
relativeIterator.remove();
|
||||
modificationRevision++;
|
||||
if (last == null) {
|
||||
throw new IllegalStateException("No element to remove");
|
||||
}
|
||||
VectorMap.this.remove(new IrisBlockVector(last.x(), last.y(), last.z()));
|
||||
last = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -263,35 +270,31 @@ public class VectorMap<T> implements Iterable<Map.Entry<IrisBlockVector, T>> {
|
||||
}
|
||||
|
||||
public class ValueIterator implements Iterator<T>, Iterable<T> {
|
||||
private final Iterator<Map<Key, T>> chunkIterator = map.values().iterator();
|
||||
private Iterator<T> relativeIterator;
|
||||
private final List<OrderedEntry<T>> entries = orderedEntries();
|
||||
private int index;
|
||||
private OrderedEntry<T> last;
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return advance();
|
||||
return index < entries.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public T next() {
|
||||
if (!advance()) throw new NoSuchElementException();
|
||||
|
||||
return relativeIterator.next();
|
||||
}
|
||||
|
||||
private boolean advance() {
|
||||
while (relativeIterator == null || !relativeIterator.hasNext()) {
|
||||
if (!chunkIterator.hasNext()) return false;
|
||||
relativeIterator = chunkIterator.next().values().iterator();
|
||||
if (!hasNext()) {
|
||||
throw new NoSuchElementException();
|
||||
}
|
||||
|
||||
return true;
|
||||
last = entries.get(index++);
|
||||
return last.value();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove() {
|
||||
if (relativeIterator == null) throw new IllegalStateException("No element to remove");
|
||||
relativeIterator.remove();
|
||||
modificationRevision++;
|
||||
if (last == null) {
|
||||
throw new IllegalStateException("No element to remove");
|
||||
}
|
||||
VectorMap.this.remove(new IrisBlockVector(last.x(), last.y(), last.z()));
|
||||
last = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -35,7 +35,7 @@ public class FloatingDecorator {
|
||||
int xf, int zf, int realX, int realZ,
|
||||
int height, int max, Hunk<NativeBlockState> data, RNG rng,
|
||||
Runnable candidatesNullCallback) {
|
||||
RNG gRNG = new RNG(DecoratorCore.partSeed(engine.getSeedManager().getDecorator(), part));
|
||||
RNG gRNG = new RNG(DecoratorCore.partSeed(engine.getSeedManager().getComponent(), part));
|
||||
IrisDecorator decorator = DecoratorCore.pickDecorator(target, part, gRNG, rng, engine.getData(), realX, realZ);
|
||||
|
||||
if (decorator == null) {
|
||||
@@ -45,11 +45,11 @@ public class FloatingDecorator {
|
||||
|
||||
if (!decorator.isStacking()) {
|
||||
DecoratorCore.placeFloatingSimple(decorator, xf, zf, realX, realZ, height, max, data,
|
||||
rng, engine.getData(), engine.getMantle());
|
||||
gRNG, engine.getData(), engine.getMantle());
|
||||
return max > 1 ? 1 : 0;
|
||||
}
|
||||
|
||||
return DecoratorCore.placeFloatingStacked(decorator, xf, zf, realX, realZ, height, max, data,
|
||||
rng, engine.getData(), engine.getMantle());
|
||||
gRNG, engine.getData(), engine.getMantle());
|
||||
}
|
||||
}
|
||||
@@ -60,13 +60,13 @@ public class IrisCeilingDecorator extends IrisEngineDecorator {
|
||||
return;
|
||||
}
|
||||
}
|
||||
DecoratorCore.placeSingleAt(decorator, x, z, realX, height, realZ, data, rng, getData(), true, getEngine().getMantle());
|
||||
DecoratorCore.placeSingleAt(decorator, x, z, realX, height, realZ, data, partRNG, getData(), true, getEngine().getMantle());
|
||||
return;
|
||||
}
|
||||
|
||||
DecoratorCore.PlaceOpts opts = DecoratorCore.SCRATCH_OPTS.get();
|
||||
opts.reset();
|
||||
opts.caveSkipFluid = caveSkipFluid;
|
||||
DecoratorCore.placeStackDown(decorator, x, z, realX, realZ, height, 0, data, rng, getData(), max, opts, getEngine().getMantle());
|
||||
DecoratorCore.placeStackDown(decorator, x, z, realX, realZ, height, 0, data, partRNG, getData(), max, opts, getEngine().getMantle());
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,8 @@
|
||||
package art.arcane.iris.generation.decoration;
|
||||
|
||||
import art.arcane.iris.generation.biome.IrisBiome;
|
||||
import art.arcane.iris.generation.runtime.Engine;
|
||||
import art.arcane.iris.generation.cache.LazyBoundedCache;
|
||||
import art.arcane.iris.generation.block.IrisBlockData;
|
||||
import art.arcane.iris.generation.noise.IrisGeneratorStyle;
|
||||
import art.arcane.iris.generation.noise.NoiseStyle;
|
||||
@@ -50,9 +52,8 @@ import lombok.experimental.Accessors;
|
||||
@Description("A biome decorator is used for placing flowers, grass, cacti and so on")
|
||||
@Data
|
||||
public class IrisDecorator {
|
||||
private final transient AtomicCache<CNG> layerGenerator = new AtomicCache<>();
|
||||
private final transient AtomicCache<CNG> varianceGenerator = new AtomicCache<>();
|
||||
private final transient AtomicCache<CNG> heightGenerator = new AtomicCache<>();
|
||||
private final transient LazyBoundedCache<GeneratorKey, Generators> generators = new LazyBoundedCache<>(32);
|
||||
private transient volatile Generators recentGenerators;
|
||||
private final transient AtomicCache<KList<NativeBlockState>> blockData = new AtomicCache<>();
|
||||
private final transient AtomicCache<KList<NativeBlockState>> blockDataTops = new AtomicCache<>();
|
||||
private final transient AtomicCache<NativeBlockState[]> blockDataArray = new AtomicCache<>();
|
||||
@@ -129,33 +130,34 @@ public class IrisDecorator {
|
||||
}
|
||||
|
||||
public CNG getHeightGenerator(RNG rng, IrisData data) {
|
||||
CNG cached = heightGenerator.getIfPresent();
|
||||
|
||||
if (cached != null) {
|
||||
return cached;
|
||||
}
|
||||
|
||||
return heightGenerator.aquire(() ->
|
||||
AtomicCache<CNG> cache = generators(rng, data).height;
|
||||
CNG cached = cache.getIfPresent();
|
||||
return cached != null ? cached : cache.aquire(() ->
|
||||
heightVariance.create(rng.nextParallelRNG(getBlockData(data).size() + stackMax + stackMin), data));
|
||||
}
|
||||
|
||||
public CNG getGenerator(RNG rng, IrisData data) {
|
||||
CNG cached = layerGenerator.getIfPresent();
|
||||
|
||||
if (cached != null) {
|
||||
return cached;
|
||||
}
|
||||
|
||||
return layerGenerator.aquire(() -> style.create(rng.nextParallelRNG(getBlockData(data).size()), data));
|
||||
AtomicCache<CNG> cache = generators(rng, data).layer;
|
||||
CNG cached = cache.getIfPresent();
|
||||
return cached != null ? cached : cache.aquire(() -> style.create(rng.nextParallelRNG(getBlockData(data).size()), data));
|
||||
}
|
||||
|
||||
public CNG getVarianceGenerator(RNG rng, IrisData data) {
|
||||
CNG cached = varianceGenerator.getIfPresent();
|
||||
return cached != null ? cached : buildVarianceGeneratorOnce(rng, data);
|
||||
AtomicCache<CNG> cache = generators(rng, data).variance;
|
||||
CNG cached = cache.getIfPresent();
|
||||
return cached != null ? cached : cache.aquire(() -> createVarianceGenerator(rng, data));
|
||||
}
|
||||
|
||||
private CNG buildVarianceGeneratorOnce(RNG rng, IrisData data) {
|
||||
return varianceGenerator.aquire(() -> createVarianceGenerator(rng, data));
|
||||
private Generators generators(RNG rng, IrisData data) {
|
||||
long seed = rng.getSeed();
|
||||
Engine engine = data == null ? null : data.getEngine();
|
||||
Generators recent = recentGenerators;
|
||||
if (recent != null && recent.key.seed == seed && recent.key.data == data && recent.key.engine == engine) {
|
||||
return recent;
|
||||
}
|
||||
Generators selected = generators.computeIfAbsent(new GeneratorKey(seed, data, engine), Generators::new);
|
||||
recentGenerators = selected;
|
||||
return selected;
|
||||
}
|
||||
|
||||
private CNG createVarianceGenerator(RNG rng, IrisData data) {
|
||||
@@ -358,4 +360,29 @@ public class IrisDecorator {
|
||||
public boolean isStacking() {
|
||||
return getStackMax() > 1;
|
||||
}
|
||||
|
||||
private static final class Generators {
|
||||
private final GeneratorKey key;
|
||||
private final AtomicCache<CNG> layer = new AtomicCache<>();
|
||||
private final AtomicCache<CNG> variance = new AtomicCache<>();
|
||||
private final AtomicCache<CNG> height = new AtomicCache<>();
|
||||
|
||||
private Generators(GeneratorKey key) {
|
||||
this.key = key;
|
||||
}
|
||||
}
|
||||
|
||||
private record GeneratorKey(long seed, IrisData data, Engine engine) {
|
||||
@Override
|
||||
public boolean equals(Object other) {
|
||||
return other instanceof GeneratorKey key
|
||||
&& seed == key.seed && data == key.data && engine == key.engine;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return 31 * (31 * Long.hashCode(seed) + System.identityHashCode(data))
|
||||
+ System.identityHashCode(engine);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -39,6 +39,8 @@ import art.arcane.volmlib.util.collection.KList;
|
||||
import art.arcane.volmlib.util.collection.KSet;
|
||||
import art.arcane.volmlib.util.math.BlockPosition;
|
||||
import art.arcane.volmlib.util.math.RNG;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import lombok.AllArgsConstructor;
|
||||
@@ -269,7 +271,7 @@ public class IrisDepositGenerator {
|
||||
return new IrisObject(1, 1, 1);
|
||||
}
|
||||
|
||||
KSet<BlockPosition> cells = new KSet<>();
|
||||
Set<BlockPosition> cells = new LinkedHashSet<>();
|
||||
vanillaEllipsoidCells(rng, size, (x, y, z) -> cells.add(new BlockPosition(x, y, z)));
|
||||
return objectFromCells(cells, rng, rdata);
|
||||
}
|
||||
@@ -407,7 +409,7 @@ public class IrisDepositGenerator {
|
||||
}
|
||||
|
||||
IrisObject generateVanillaScattered(RNG rng, IrisData rdata, int size) {
|
||||
KSet<BlockPosition> cells = new KSet<>();
|
||||
Set<BlockPosition> cells = new LinkedHashSet<>();
|
||||
int candidates = rng.nextInt(Math.max(0, size) + 1);
|
||||
for (int i = 0; i < candidates; i++) {
|
||||
int magnitude = Math.min(i, 7);
|
||||
@@ -419,7 +421,7 @@ public class IrisDepositGenerator {
|
||||
return objectFromCells(cells, rng, rdata);
|
||||
}
|
||||
|
||||
private IrisObject objectFromCells(KSet<BlockPosition> cells, RNG rng, IrisData rdata) {
|
||||
private IrisObject objectFromCells(Set<BlockPosition> cells, RNG rng, IrisData rdata) {
|
||||
if (cells.isEmpty()) {
|
||||
return new IrisObject(1, 1, 1);
|
||||
}
|
||||
|
||||
@@ -34,6 +34,10 @@ public abstract class IrisEngineDecorator extends EngineAssignedComponent implem
|
||||
this.part = part;
|
||||
}
|
||||
|
||||
public static long seedForPart(long baseSeed, IrisDecorationPart part) {
|
||||
return DecoratorCore.partSeed(baseSeed, part);
|
||||
}
|
||||
|
||||
@BlockCoordinates
|
||||
protected RNG getRNG(int x, int z) {
|
||||
long seed = DecoratorCore.partSeed(getSeed(), part);
|
||||
|
||||
@@ -24,7 +24,8 @@ import art.arcane.iris.generation.terrain.IrisMaterialPalette;
|
||||
import art.arcane.iris.pack.value.IrisRange;
|
||||
|
||||
import art.arcane.iris.pack.loading.IrisData;
|
||||
import art.arcane.volmlib.util.cache.AtomicCache;
|
||||
import art.arcane.iris.generation.cache.LazyBoundedCache;
|
||||
import art.arcane.iris.generation.runtime.Engine;
|
||||
import art.arcane.volmlib.util.documentation.Description;
|
||||
import art.arcane.iris.pack.schema.annotation.MaxNumber;
|
||||
import art.arcane.iris.pack.schema.annotation.MinNumber;
|
||||
@@ -55,10 +56,11 @@ public class IrisOreGenerator {
|
||||
@Description("Vertical band (min, max) this ore can generate in, in engine-local Y where 0 is the bottom of the dimension, not world Y.")
|
||||
private IrisRange range = new IrisRange(30, 80);
|
||||
|
||||
private transient AtomicCache<OreChance> chanceCache = new AtomicCache<>();
|
||||
private final transient LazyBoundedCache<ChanceKey, OreChance> chanceCache = new LazyBoundedCache<>(32);
|
||||
private transient volatile CachedChance recentChance;
|
||||
|
||||
public void warm(RNG rng, IrisData data) {
|
||||
chanceCache.aquire(() -> OreChance.of(chanceStyle.create(rng, data)));
|
||||
chance(rng, data);
|
||||
palette.getLayerGenerator(rng, data);
|
||||
}
|
||||
|
||||
@@ -71,18 +73,43 @@ public class IrisOreGenerator {
|
||||
return null;
|
||||
}
|
||||
|
||||
OreChance chance = chanceCache.getIfPresent();
|
||||
if (chance == null) {
|
||||
chance = chanceCache.aquire(() -> OreChance.of(chanceStyle.create(rng, data)));
|
||||
}
|
||||
|
||||
if (chance.sample(x, y, z) > threshold) {
|
||||
if (chance(rng, data).sample(x, y, z) > threshold) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return palette.get(rng, x, y, z, data);
|
||||
}
|
||||
|
||||
private OreChance chance(RNG rng, IrisData data) {
|
||||
long seed = rng.getSeed();
|
||||
Engine engine = data == null ? null : data.getEngine();
|
||||
CachedChance recent = recentChance;
|
||||
if (recent != null && recent.key.seed == seed && recent.key.data == data && recent.key.engine == engine) {
|
||||
return recent.chance;
|
||||
}
|
||||
ChanceKey key = new ChanceKey(seed, data, engine);
|
||||
OreChance selected = chanceCache.computeIfAbsent(key, ignored -> OreChance.of(chanceStyle.create(rng, data)));
|
||||
recentChance = new CachedChance(key, selected);
|
||||
return selected;
|
||||
}
|
||||
|
||||
private record ChanceKey(long seed, IrisData data, Engine engine) {
|
||||
@Override
|
||||
public boolean equals(Object other) {
|
||||
return other instanceof ChanceKey key
|
||||
&& seed == key.seed && data == key.data && engine == key.engine;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return 31 * (31 * Long.hashCode(seed) + System.identityHashCode(data))
|
||||
+ System.identityHashCode(engine);
|
||||
}
|
||||
}
|
||||
|
||||
private record CachedChance(ChanceKey key, OreChance chance) {
|
||||
}
|
||||
|
||||
/**
|
||||
* The chance field, with its single value resolved once when it is constant (a FLAT style),
|
||||
* which bands such as a full deepslate layer sample at every block.
|
||||
|
||||
@@ -53,7 +53,7 @@ public class IrisSeaFloorDecorator extends IrisEngineDecorator {
|
||||
&& !decorator.getSlopeCondition().isValid(getComplex().getSlopeStream().get(realX, realZ))) {
|
||||
return;
|
||||
}
|
||||
NativeBlockState block = decorator.getBlockData100(biome, rng, realX, height, realZ, getData());
|
||||
NativeBlockState block = decorator.getBlockData100(biome, partRNG, realX, height, realZ, getData());
|
||||
if (block != null) {
|
||||
data.set(x, height, z, block);
|
||||
}
|
||||
@@ -61,13 +61,13 @@ public class IrisSeaFloorDecorator extends IrisEngineDecorator {
|
||||
}
|
||||
|
||||
int stack = Math.min(limit - height,
|
||||
DecoratorCore.computeStack(decorator, rng, realX, realZ, getData(), max - height));
|
||||
DecoratorCore.computeStack(decorator, partRNG, realX, realZ, getData(), max - height));
|
||||
for (int i = 0; i < stack; i++) {
|
||||
int h = height + i;
|
||||
double threshold = stack == 1 ? 1.0 : ((double) i) / (stack - 1);
|
||||
NativeBlockState block = threshold >= decorator.getTopThreshold()
|
||||
? decorator.getBlockDataForTop(biome, rng, realX, h, realZ, getData())
|
||||
: decorator.getBlockData100(biome, rng, realX, h, realZ, getData());
|
||||
? decorator.getBlockDataForTop(biome, partRNG, realX, h, realZ, getData())
|
||||
: decorator.getBlockData100(biome, partRNG, realX, h, realZ, getData());
|
||||
if (block == null) {
|
||||
break;
|
||||
}
|
||||
|
||||
+4
-4
@@ -51,7 +51,7 @@ public class IrisSeaSurfaceDecorator extends IrisEngineDecorator {
|
||||
if (!decorator.isStacking()) {
|
||||
int targetY = height + 1;
|
||||
if (DecoratorCore.canReplaceStackTarget(data.get(x, targetY, z), false)) {
|
||||
NativeBlockState block = decorator.getBlockData100(biome, rng, realX, height, realZ, getData());
|
||||
NativeBlockState block = decorator.getBlockData100(biome, partRNG, realX, height, realZ, getData());
|
||||
if (block != null) {
|
||||
data.set(x, targetY, z, block);
|
||||
}
|
||||
@@ -60,7 +60,7 @@ public class IrisSeaSurfaceDecorator extends IrisEngineDecorator {
|
||||
}
|
||||
|
||||
int stack = Math.min(maxBaseY - height,
|
||||
DecoratorCore.computeStack(decorator, rng, realX, realZ, getData(), max - height));
|
||||
DecoratorCore.computeStack(decorator, partRNG, realX, realZ, getData(), max - height));
|
||||
for (int i = 0; i < stack; i++) {
|
||||
int h = height + i;
|
||||
int targetY = h + 1;
|
||||
@@ -69,8 +69,8 @@ public class IrisSeaSurfaceDecorator extends IrisEngineDecorator {
|
||||
}
|
||||
double threshold = stack == 1 ? 1.0 : ((double) i) / (stack - 1);
|
||||
NativeBlockState block = threshold >= decorator.getTopThreshold()
|
||||
? decorator.getBlockDataForTop(biome, rng, realX, h, realZ, getData())
|
||||
: decorator.getBlockData100(biome, rng, realX, h, realZ, getData());
|
||||
? decorator.getBlockDataForTop(biome, partRNG, realX, h, realZ, getData())
|
||||
: decorator.getBlockData100(biome, partRNG, realX, h, realZ, getData());
|
||||
if (block == null) {
|
||||
break;
|
||||
}
|
||||
|
||||
+5
-5
@@ -102,7 +102,7 @@ public class IrisShoreLineDecorator extends IrisEngineDecorator {
|
||||
|| !DecoratorCore.canReplaceStackTarget(data.get(x, targetY, z), false)) {
|
||||
return;
|
||||
}
|
||||
NativeBlockState block = decorator.getBlockData100(biome, rng, realX, height, realZ, getData());
|
||||
NativeBlockState block = decorator.getBlockData100(biome, partRNG, realX, height, realZ, getData());
|
||||
if (block != null && DecoratorCore.isValidShorelineSupport(decorator, block, support)
|
||||
&& IrisSugarCane.canPlace(block, data, x, targetY, z, realX, realZ, getEngine())) {
|
||||
data.set(x, targetY, z, block);
|
||||
@@ -111,7 +111,7 @@ public class IrisShoreLineDecorator extends IrisEngineDecorator {
|
||||
return;
|
||||
}
|
||||
|
||||
int stack = decorator.getHeight(rng, realX, realZ, getData());
|
||||
int stack = decorator.getHeight(partRNG, realX, realZ, getData());
|
||||
if (decorator.isScaleStack()) {
|
||||
stack = (int) Math.ceil((double) (max - height) * ((double) stack / 100));
|
||||
} else {
|
||||
@@ -124,7 +124,7 @@ public class IrisShoreLineDecorator extends IrisEngineDecorator {
|
||||
return;
|
||||
}
|
||||
|
||||
NativeBlockState block = decorator.getBlockDataForTop(biome, rng, realX, height, realZ, getData());
|
||||
NativeBlockState block = decorator.getBlockDataForTop(biome, partRNG, realX, height, realZ, getData());
|
||||
if (block != null && DecoratorCore.isValidShorelineSupport(decorator, block, support)
|
||||
&& IrisSugarCane.canPlace(block, data, x, targetY, z, realX, realZ, getEngine())) {
|
||||
data.set(x, targetY, z, block);
|
||||
@@ -142,8 +142,8 @@ public class IrisShoreLineDecorator extends IrisEngineDecorator {
|
||||
}
|
||||
double threshold = ((double) i) / (stack - 1);
|
||||
NativeBlockState block = threshold >= decorator.getTopThreshold()
|
||||
? decorator.getBlockDataForTop(biome, rng, realX, h, realZ, getData())
|
||||
: decorator.getBlockData100(biome, rng, realX, h, realZ, getData());
|
||||
? decorator.getBlockDataForTop(biome, partRNG, realX, h, realZ, getData())
|
||||
: decorator.getBlockData100(biome, partRNG, realX, h, realZ, getData());
|
||||
if (block == null) {
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -86,13 +86,13 @@ public class IrisSurfaceDecorator extends IrisEngineDecorator {
|
||||
opts.fluidHeight = fluidHeight;
|
||||
opts.caveSkipFluid = caveSkipFluid;
|
||||
opts.mantle = getEngine().getMantle();
|
||||
DecoratorCore.placeStackUp(decorator, x, z, realX, realZ, height, max, data, rng, getData(), opts);
|
||||
DecoratorCore.placeStackUp(decorator, x, z, realX, realZ, height, max, data, partRNG, getData(), opts);
|
||||
aquaticSnapshot.restoreIfUnsupported(data, x, z);
|
||||
return;
|
||||
}
|
||||
|
||||
DecoratorCore.placeSurfaceSingle(decorator, x, z, realX, height, realZ,
|
||||
data, rng, getData(), underwater, caveSkipFluid, getEngine().getMantle());
|
||||
data, partRNG, getData(), underwater, caveSkipFluid, getEngine().getMantle());
|
||||
aquaticSnapshot.restoreIfUnsupported(data, x, z);
|
||||
}
|
||||
|
||||
|
||||
+3
@@ -25,6 +25,7 @@ import art.arcane.volmlib.util.math.Vector3i;
|
||||
import art.arcane.volmlib.util.math.RNG;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -42,6 +43,8 @@ final class RuinDecoratorApplier {
|
||||
case BASE_SCATTER -> baseScatterCandidates(canvas, Math.max(1, decorator.getScatterRadius()));
|
||||
};
|
||||
|
||||
candidates.sort(Comparator.comparingInt(Vector3i::getX)
|
||||
.thenComparingInt(Vector3i::getY).thenComparingInt(Vector3i::getZ));
|
||||
RNG paletteRng = new RNG(seed);
|
||||
for (Vector3i v : candidates) {
|
||||
if (!rng.chance(decorator.getChance())) {
|
||||
|
||||
@@ -24,7 +24,15 @@ import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
public class TreeBlockCanvas {
|
||||
public record Vec(int x, int y, int z) {
|
||||
public record Vec(int x, int y, int z) implements Comparable<Vec> {
|
||||
@Override
|
||||
public int compareTo(Vec other) {
|
||||
int comparison = Integer.compare(x, other.x);
|
||||
if (comparison == 0) {
|
||||
comparison = Integer.compare(y, other.y);
|
||||
}
|
||||
return comparison == 0 ? Integer.compare(z, other.z) : comparison;
|
||||
}
|
||||
}
|
||||
|
||||
public enum Role {
|
||||
|
||||
+23
-8
@@ -23,6 +23,9 @@ import art.arcane.volmlib.util.collection.KList;
|
||||
import art.arcane.volmlib.util.collection.KMap;
|
||||
import art.arcane.volmlib.util.math.RNG;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@@ -90,7 +93,7 @@ public final class TreeDecoratorApplier {
|
||||
private static void trunkSurface(TreeBlockCanvas canvas, IrisTreeDecorator dec, int idx, RNG rng) {
|
||||
int[][] sides = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}};
|
||||
String[] facings = {"east", "west", "south", "north"};
|
||||
for (TreeBlockCanvas.Vec v : new KList<>(canvas.getTrunk())) {
|
||||
for (TreeBlockCanvas.Vec v : sortedPositions(canvas.getTrunk())) {
|
||||
for (int i = 0; i < sides.length; i++) {
|
||||
int nx = v.x() + sides[i][0];
|
||||
int nz = v.z() + sides[i][1];
|
||||
@@ -105,7 +108,7 @@ public final class TreeDecoratorApplier {
|
||||
private static void leafSurface(TreeBlockCanvas canvas, IrisTreeDecorator dec, int idx, RNG rng) {
|
||||
int[][] sides = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}};
|
||||
String[] facings = {"east", "west", "south", "north"};
|
||||
for (TreeBlockCanvas.Vec v : new KList<>(canvas.getLeaf())) {
|
||||
for (TreeBlockCanvas.Vec v : sortedPositions(canvas.getLeaf())) {
|
||||
for (int i = 0; i < sides.length; i++) {
|
||||
int nx = v.x() + sides[i][0];
|
||||
int nz = v.z() + sides[i][1];
|
||||
@@ -118,7 +121,7 @@ public final class TreeDecoratorApplier {
|
||||
}
|
||||
|
||||
private static void branchSurface(TreeBlockCanvas canvas, IrisTreeDecorator dec, int idx, RNG rng) {
|
||||
for (TreeBlockCanvas.Vec v : new KList<>(canvas.getTrunk())) {
|
||||
for (TreeBlockCanvas.Vec v : sortedPositions(canvas.getTrunk())) {
|
||||
int ay = v.y() + 1;
|
||||
if (canvas.has(v.x(), ay, v.z()) || rng.nextDouble() > dec.getChance()) {
|
||||
continue;
|
||||
@@ -136,7 +139,7 @@ public final class TreeDecoratorApplier {
|
||||
colTop.put(key, v.y());
|
||||
}
|
||||
}
|
||||
for (Map.Entry<Long, Integer> e : colTop.entrySet()) {
|
||||
for (Map.Entry<Long, Integer> e : sortedColumns(colTop)) {
|
||||
int x = unpackX(e.getKey());
|
||||
int z = unpackZ(e.getKey());
|
||||
int aboveY = e.getValue() + 1;
|
||||
@@ -149,7 +152,7 @@ public final class TreeDecoratorApplier {
|
||||
|
||||
private static void canopyBottom(TreeBlockCanvas canvas, IrisTreeDecorator dec, int idx, RNG rng) {
|
||||
KMap<Long, Integer> colBottom = columnLeafBottoms(canvas);
|
||||
for (Map.Entry<Long, Integer> e : colBottom.entrySet()) {
|
||||
for (Map.Entry<Long, Integer> e : sortedColumns(colBottom)) {
|
||||
int x = unpackX(e.getKey());
|
||||
int z = unpackZ(e.getKey());
|
||||
int belowY = e.getValue() - 1;
|
||||
@@ -163,7 +166,7 @@ public final class TreeDecoratorApplier {
|
||||
private static void canopyHang(TreeBlockCanvas canvas, IrisTreeDecorator dec, int idx, RNG rng) {
|
||||
int maxLen = Math.max(1, dec.getLength());
|
||||
KMap<Long, Integer> colBottom = columnLeafBottoms(canvas);
|
||||
for (Map.Entry<Long, Integer> e : colBottom.entrySet()) {
|
||||
for (Map.Entry<Long, Integer> e : sortedColumns(colBottom)) {
|
||||
if (rng.nextDouble() > dec.getChance()) {
|
||||
continue;
|
||||
}
|
||||
@@ -182,7 +185,7 @@ public final class TreeDecoratorApplier {
|
||||
|
||||
private static void trunkBase(TreeBlockCanvas canvas, IrisTreeDecorator dec, int idx, RNG rng) {
|
||||
int[][] sides = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};
|
||||
for (TreeBlockCanvas.Vec v : new KList<>(canvas.getTrunk())) {
|
||||
for (TreeBlockCanvas.Vec v : sortedPositions(canvas.getTrunk())) {
|
||||
if (v.y() != 0) {
|
||||
continue;
|
||||
}
|
||||
@@ -205,7 +208,7 @@ public final class TreeDecoratorApplier {
|
||||
if (maxY == Integer.MIN_VALUE) {
|
||||
return;
|
||||
}
|
||||
for (TreeBlockCanvas.Vec v : new KList<>(canvas.getTrunk())) {
|
||||
for (TreeBlockCanvas.Vec v : sortedPositions(canvas.getTrunk())) {
|
||||
if (v.y() != maxY) {
|
||||
continue;
|
||||
}
|
||||
@@ -269,6 +272,18 @@ public final class TreeDecoratorApplier {
|
||||
return colBottom;
|
||||
}
|
||||
|
||||
private static List<TreeBlockCanvas.Vec> sortedPositions(Collection<TreeBlockCanvas.Vec> positions) {
|
||||
List<TreeBlockCanvas.Vec> sorted = new ArrayList<>(positions);
|
||||
sorted.sort(null);
|
||||
return sorted;
|
||||
}
|
||||
|
||||
private static List<Map.Entry<Long, Integer>> sortedColumns(Map<Long, Integer> columns) {
|
||||
List<Map.Entry<Long, Integer>> sorted = new ArrayList<>(columns.entrySet());
|
||||
sorted.sort(Comparator.comparingLong(Map.Entry::getKey));
|
||||
return sorted;
|
||||
}
|
||||
|
||||
private static String facingAway(int cx, int cz, int x, int z) {
|
||||
int dx = x - cx;
|
||||
int dz = z - cz;
|
||||
|
||||
@@ -44,15 +44,13 @@ public final class TreeSupport {
|
||||
int best = Integer.MAX_VALUE;
|
||||
for (TreeBlockCanvas.Vec wood : canvas.getTrunk()) {
|
||||
int cd = chebyshev(leaf, wood);
|
||||
if (cd < best) {
|
||||
if (cd < best || cd == best && nearest != null && wood.compareTo(nearest) < 0) {
|
||||
best = cd;
|
||||
nearest = wood;
|
||||
if (cd <= 1) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (nearest != null && best > worstGap) {
|
||||
if (nearest != null && (best > worstGap
|
||||
|| best == worstGap && worstLeaf != null && leaf.compareTo(worstLeaf) < 0)) {
|
||||
worstGap = best;
|
||||
worstLeaf = leaf;
|
||||
worstWood = nearest;
|
||||
|
||||
@@ -24,6 +24,10 @@ import com.dfsek.paralithic.eval.parser.Parser;
|
||||
import com.dfsek.paralithic.eval.parser.Scope;
|
||||
import art.arcane.iris.pack.loading.IrisRegistrant;
|
||||
import art.arcane.volmlib.util.cache.AtomicCache;
|
||||
import art.arcane.iris.generation.cache.LazyBoundedCache;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import art.arcane.iris.generation.noise.IrisExpressionFunction.FunctionContext;
|
||||
import art.arcane.iris.pack.schema.annotation.ArrayType;
|
||||
import art.arcane.volmlib.util.documentation.Description;
|
||||
@@ -58,7 +62,9 @@ public class IrisExpression extends IrisRegistrant {
|
||||
private String expression;
|
||||
|
||||
private transient AtomicCache<Expression> expressionCache = new AtomicCache<>();
|
||||
private transient AtomicCache<ProceduralStream<Double>> streamCache = new AtomicCache<>();
|
||||
@Getter(AccessLevel.NONE)
|
||||
@Setter(AccessLevel.NONE)
|
||||
private final transient LazyBoundedCache<Long, ProceduralStream<Double>> streams = new LazyBoundedCache<>(8);
|
||||
|
||||
private Expression expression() {
|
||||
return expressionCache.aquire(() -> {
|
||||
@@ -94,8 +100,11 @@ public class IrisExpression extends IrisRegistrant {
|
||||
}
|
||||
|
||||
public ProceduralStream<Double> stream(RNG rng) {
|
||||
return streamCache.aquire(() -> ProceduralStream.of((x, z) -> evaluate(rng, x, z),
|
||||
(x, y, z) -> evaluate(rng, x, y, z), Interpolated.DOUBLE));
|
||||
return streams.computeIfAbsent(rng.getSeed(), seed -> {
|
||||
RNG streamRng = new RNG(seed);
|
||||
return ProceduralStream.of((x, z) -> evaluate(streamRng, x, z),
|
||||
(x, y, z) -> evaluate(streamRng, x, y, z), Interpolated.DOUBLE);
|
||||
});
|
||||
}
|
||||
|
||||
public double evaluate(RNG rng, double x, double z) {
|
||||
|
||||
@@ -10,7 +10,11 @@ import art.arcane.volmlib.util.documentation.Description;
|
||||
import art.arcane.iris.pack.schema.annotation.MinNumber;
|
||||
import art.arcane.iris.pack.schema.annotation.Required;
|
||||
import art.arcane.iris.pack.schema.annotation.Snippet;
|
||||
import art.arcane.volmlib.util.collection.KMap;
|
||||
import art.arcane.iris.generation.cache.LazyBoundedCache;
|
||||
import art.arcane.iris.generation.runtime.Engine;
|
||||
import art.arcane.volmlib.util.stream.ProceduralStream;
|
||||
|
||||
import java.util.Objects;
|
||||
import art.arcane.volmlib.util.math.RNG;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.AllArgsConstructor;
|
||||
@@ -48,7 +52,10 @@ public class IrisExpressionFunction implements DynamicFunction {
|
||||
|
||||
@Getter(AccessLevel.NONE)
|
||||
@Setter(AccessLevel.NONE)
|
||||
private transient final KMap<FunctionContext, Provider> cache = new KMap<>();
|
||||
private transient final LazyBoundedCache<FunctionKey, Provider> providers = new LazyBoundedCache<>(8);
|
||||
@Getter(AccessLevel.NONE)
|
||||
@Setter(AccessLevel.NONE)
|
||||
private transient volatile CachedProvider recentProvider;
|
||||
private transient IrisData data;
|
||||
|
||||
public boolean isValid() {
|
||||
@@ -74,19 +81,53 @@ public class IrisExpressionFunction implements DynamicFunction {
|
||||
|
||||
@Override
|
||||
public double eval(@Nullable Context raw, double... args) {
|
||||
return cache.computeIfAbsent((FunctionContext) raw, context -> {
|
||||
assert context != null;
|
||||
if (engineStreamValue != null) {
|
||||
var stream = engineStreamValue.get(data.getEngine());
|
||||
return d -> stream.get(d[0], d[1]);
|
||||
}
|
||||
FunctionContext context = Objects.requireNonNull((FunctionContext) raw, "Expression context");
|
||||
IrisData currentData = data;
|
||||
Engine engine = currentData == null ? null : currentData.getEngine();
|
||||
long seed = context.rng.getSeed();
|
||||
CachedProvider recent = recentProvider;
|
||||
if (recent != null && recent.key.matches(seed, currentData, engine)) {
|
||||
return recent.provider.eval(args);
|
||||
}
|
||||
FunctionKey key = new FunctionKey(seed, currentData, engine);
|
||||
Provider provider = providers.computeIfAbsent(key, ignored -> createProvider(key));
|
||||
recentProvider = new CachedProvider(key, provider);
|
||||
return provider.eval(args);
|
||||
}
|
||||
|
||||
if (styleValue != null) {
|
||||
return styleValue.createNoCache(context.rng, data)::noise;
|
||||
private Provider createProvider(FunctionKey key) {
|
||||
if (engineStreamValue != null) {
|
||||
if (key.engine == null) {
|
||||
throw new IllegalStateException("Expression function '" + name + "' requires an active Iris engine.");
|
||||
}
|
||||
ProceduralStream<Double> stream = engineStreamValue.get(key.engine);
|
||||
return coordinates -> stream.get(coordinates[0], coordinates[1]);
|
||||
}
|
||||
if (styleValue != null) {
|
||||
return styleValue.create(new RNG(key.seed), key.data, key.engine)::noise;
|
||||
}
|
||||
return coordinates -> Double.NaN;
|
||||
}
|
||||
|
||||
return d -> Double.NaN;
|
||||
}).eval(args);
|
||||
private record FunctionKey(long seed, IrisData data, Engine engine) {
|
||||
private boolean matches(long seed, IrisData data, Engine engine) {
|
||||
return this.seed == seed && this.data == data && this.engine == engine;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object object) {
|
||||
return object instanceof FunctionKey other && matches(other.seed, other.data, other.engine);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int hash = Long.hashCode(seed);
|
||||
hash = 31 * hash + System.identityHashCode(data);
|
||||
return 31 * hash + System.identityHashCode(engine);
|
||||
}
|
||||
}
|
||||
|
||||
private record CachedProvider(FunctionKey key, Provider provider) {
|
||||
}
|
||||
|
||||
public record FunctionContext(@NonNull RNG rng) implements Context {
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
package art.arcane.iris.generation.noise;
|
||||
|
||||
import art.arcane.iris.pack.loading.IrisRegistrant;
|
||||
import art.arcane.volmlib.util.cache.AtomicCache;
|
||||
import art.arcane.iris.generation.cache.LazyBoundedCache;
|
||||
import art.arcane.iris.generation.runtime.Engine;
|
||||
import art.arcane.iris.pack.loading.IrisData;
|
||||
import art.arcane.iris.pack.schema.annotation.ArrayType;
|
||||
@@ -54,7 +54,12 @@ public class IrisGenerator extends IrisRegistrant {
|
||||
private static final int SURFACE_CACHE_STRIPES = 16;
|
||||
private static final int SURFACE_CACHE_STRIPE_SIZE = 1024;
|
||||
|
||||
private final transient AtomicCache<CellGenerator> cellGen = new AtomicCache<>();
|
||||
@Getter(AccessLevel.NONE)
|
||||
@Setter(AccessLevel.NONE)
|
||||
private final transient LazyBoundedCache<Long, CellGenerator> cellGenerators = new LazyBoundedCache<>(8);
|
||||
@Getter(AccessLevel.NONE)
|
||||
@Setter(AccessLevel.NONE)
|
||||
private transient volatile CachedCellGenerator recentCellGenerator;
|
||||
@Getter(AccessLevel.NONE)
|
||||
@Setter(AccessLevel.NONE)
|
||||
private transient volatile SurfaceCache surfaceSamples;
|
||||
@@ -115,7 +120,17 @@ public class IrisGenerator extends IrisRegistrant {
|
||||
}
|
||||
|
||||
public CellGenerator getCellGenerator(long seed) {
|
||||
return cellGen.aquire(() -> new CellGenerator(new RNG(seed + 239466)));
|
||||
CachedCellGenerator recent = recentCellGenerator;
|
||||
if (recent != null && recent.seed == seed) {
|
||||
return recent.generator;
|
||||
}
|
||||
CellGenerator generator = cellGenerators.computeIfAbsent(seed, ignored -> {
|
||||
CellGenerator created = new CellGenerator(new RNG(seed + 239466));
|
||||
created.setShuffle(getCellFractureShuffle());
|
||||
return created;
|
||||
});
|
||||
recentCellGenerator = new CachedCellGenerator(seed, generator);
|
||||
return generator;
|
||||
}
|
||||
|
||||
public <T> T fit(T[] v, long superSeed, double rx, double rz) {
|
||||
@@ -296,7 +311,6 @@ public class IrisGenerator extends IrisRegistrant {
|
||||
}
|
||||
|
||||
public double cell(double rx, double rz, double v, double superSeed) {
|
||||
getCellGenerator(getSeed() + 46222).setShuffle(getCellFractureShuffle());
|
||||
double fractureX = rx / getCellFractureZoom();
|
||||
double fractureZ = rz / getCellFractureZoom();
|
||||
return getCellGenerator(getSeed() + 46222).getDistance(fractureX, fractureZ) > getCellPercentSize() ? (v * getCellFractureHeight()) : v;
|
||||
@@ -363,4 +377,7 @@ public class IrisGenerator extends IrisRegistrant {
|
||||
entries.set(slot, sample);
|
||||
}
|
||||
}
|
||||
|
||||
private record CachedCellGenerator(long seed, CellGenerator generator) {
|
||||
}
|
||||
}
|
||||
@@ -58,6 +58,9 @@ public class IrisGeneratorStyle {
|
||||
@Setter(AccessLevel.NONE)
|
||||
private final transient LazyBoundedCache<GeneratorCacheKey, CNG> generatorCache =
|
||||
new LazyBoundedCache<>(GENERATOR_CACHE_SIZE);
|
||||
@Getter(AccessLevel.NONE)
|
||||
@Setter(AccessLevel.NONE)
|
||||
private transient volatile CachedGenerator recentGenerator;
|
||||
@Description("The base noise style. Used when neither expression nor imageMap is set; a failed expression also falls back to this style.")
|
||||
private NoiseStyle style = NoiseStyle.FLAT;
|
||||
|
||||
@@ -124,7 +127,7 @@ public class IrisGeneratorStyle {
|
||||
}
|
||||
|
||||
private int hash() {
|
||||
return Objects.hash(expression, imageMapHash(), multiplier, fracture != null ? fracture.hash() : 0, exponent, cacheSize, zoom, cellularZoom, cellularFrequency, style);
|
||||
return Objects.hash(expression, imageMapHash(), multiplier, fracture != null ? fracture.hash() : 0, exponent, cacheSize, zoom, cellularZoom, cellularFrequency, style == null ? null : style.name());
|
||||
}
|
||||
|
||||
public int prebakeSignature() {
|
||||
@@ -233,10 +236,24 @@ public class IrisGeneratorStyle {
|
||||
return create(rng, data, resolveEngine(data));
|
||||
}
|
||||
|
||||
public CNG create(long seed, IrisData data) {
|
||||
return cachedGenerator(seed, data, resolveEngine(data));
|
||||
}
|
||||
|
||||
public CNG create(RNG rng, IrisData data, Engine engine) {
|
||||
GeneratorCacheKey key = new GeneratorCacheKey(data, engine, rng.getSeed());
|
||||
return generatorCache.computeIfAbsent(key,
|
||||
ignored -> createNoCache(rng, data, 1, 0, false, engine));
|
||||
return cachedGenerator(rng.getSeed(), data, engine);
|
||||
}
|
||||
|
||||
private CNG cachedGenerator(long seed, IrisData data, Engine engine) {
|
||||
CachedGenerator recent = recentGenerator;
|
||||
if (recent != null && recent.key.matches(data, engine, seed)) {
|
||||
return recent.generator;
|
||||
}
|
||||
GeneratorCacheKey key = new GeneratorCacheKey(data, engine, seed);
|
||||
CNG generator = generatorCache.computeIfAbsent(key,
|
||||
ignored -> createNoCache(new RNG(seed), data, 1, 0, false, engine));
|
||||
recentGenerator = new CachedGenerator(key, generator);
|
||||
return generator;
|
||||
}
|
||||
|
||||
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
|
||||
@@ -295,6 +312,10 @@ public class IrisGeneratorStyle {
|
||||
this.seed = seed;
|
||||
}
|
||||
|
||||
private boolean matches(IrisData data, Engine engine, long seed) {
|
||||
return this.data == data && this.engine == engine && this.seed == seed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object other) {
|
||||
if (this == other) {
|
||||
@@ -313,4 +334,7 @@ public class IrisGeneratorStyle {
|
||||
return 31 * result + Long.hashCode(seed);
|
||||
}
|
||||
}
|
||||
|
||||
private record CachedGenerator(GeneratorCacheKey key, CNG generator) {
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,7 @@ package art.arcane.iris.generation.runtime;
|
||||
import art.arcane.iris.pack.loading.IrisData;
|
||||
import art.arcane.iris.generation.biome.IrisBiome;
|
||||
import art.arcane.iris.generation.decoration.IrisDecorator;
|
||||
import art.arcane.iris.generation.decoration.IrisEngineDecorator;
|
||||
import art.arcane.iris.structure.object.IrisObjectPlacement;
|
||||
import art.arcane.iris.generation.decoration.IrisOreGenerator;
|
||||
import art.arcane.iris.generation.decoration.IrisProceduralObjects;
|
||||
@@ -47,8 +48,8 @@ public final class GenerationCacheWarmer {
|
||||
biomes.sort(Comparator.comparing(IrisBiome::getLoadKey));
|
||||
for (IrisBiome biome : biomes) {
|
||||
warmPlacements(biome.getObjects(), root, counter, data, engine);
|
||||
warmDecorators(biome.getDecorators(), root, counter, data);
|
||||
warmOres(biome.getOres(), root, counter, data);
|
||||
warmDecorators(biome.getDecorators(), engine.getSeedManager().getComponent(), counter, data);
|
||||
warmOres(biome.getOres(), engine.getSeedManager().getTerrain(), counter, data);
|
||||
warmProcedural(biome.getProceduralObjects(), root, counter, data);
|
||||
}
|
||||
|
||||
@@ -56,11 +57,11 @@ public final class GenerationCacheWarmer {
|
||||
regions.sort(Comparator.comparing(IrisRegion::getLoadKey));
|
||||
for (IrisRegion region : regions) {
|
||||
warmPlacements(region.getObjects(), root, counter, data, engine);
|
||||
warmOres(region.getOres(), root, counter, data);
|
||||
warmOres(region.getOres(), engine.getSeedManager().getTerrain(), counter, data);
|
||||
warmProcedural(region.getProceduralObjects(), root, counter, data);
|
||||
}
|
||||
|
||||
warmOres(engine.getDimension().getOres(), root, counter, data);
|
||||
warmOres(engine.getDimension().getOres(), engine.getSeedManager().getTerrain(), counter, data);
|
||||
|
||||
IrisLogging.debug("[IrisEngine timing] cache warm " + counter[0] + " configs=" + (M.ms() - start) + "ms");
|
||||
}
|
||||
@@ -80,7 +81,7 @@ public final class GenerationCacheWarmer {
|
||||
}
|
||||
}
|
||||
|
||||
private static void warmDecorators(KList<IrisDecorator> decorators, RNG root, int[] counter, IrisData data) {
|
||||
private static void warmDecorators(KList<IrisDecorator> decorators, long componentSeed, int[] counter, IrisData data) {
|
||||
if (decorators == null) {
|
||||
return;
|
||||
}
|
||||
@@ -88,14 +89,15 @@ public final class GenerationCacheWarmer {
|
||||
if (decorator == null) {
|
||||
continue;
|
||||
}
|
||||
RNG rng = root.nextParallelRNG(counter[0]++);
|
||||
counter[0]++;
|
||||
RNG rng = new RNG(IrisEngineDecorator.seedForPart(componentSeed, decorator.getPartOf()));
|
||||
decorator.getHeightGenerator(rng, data);
|
||||
decorator.getGenerator(rng, data);
|
||||
decorator.getVarianceGenerator(rng, data);
|
||||
}
|
||||
}
|
||||
|
||||
private static void warmOres(KList<IrisOreGenerator> ores, RNG root, int[] counter, IrisData data) {
|
||||
private static void warmOres(KList<IrisOreGenerator> ores, long terrainSeed, int[] counter, IrisData data) {
|
||||
if (ores == null) {
|
||||
return;
|
||||
}
|
||||
@@ -103,7 +105,8 @@ public final class GenerationCacheWarmer {
|
||||
if (ore == null) {
|
||||
continue;
|
||||
}
|
||||
ore.warm(root.nextParallelRNG(counter[0]++), data);
|
||||
counter[0]++;
|
||||
ore.warm(new RNG(terrainSeed), data);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -67,6 +67,7 @@ import art.arcane.iris.pack.datapack.DataVersion;
|
||||
import art.arcane.iris.pack.datapack.IDataFixer;
|
||||
import art.arcane.iris.pack.datapack.IDataFixer.Dimension;
|
||||
import art.arcane.volmlib.util.cache.AtomicCache;
|
||||
import art.arcane.iris.generation.cache.LazyBoundedCache;
|
||||
import art.arcane.iris.world.history.GenerationRegistryContractFactory;
|
||||
import art.arcane.iris.pack.schema.annotation.ArrayType;
|
||||
import art.arcane.volmlib.util.documentation.Description;
|
||||
@@ -89,6 +90,9 @@ import art.arcane.volmlib.util.math.Position2;
|
||||
import art.arcane.volmlib.util.math.RNG;
|
||||
import art.arcane.volmlib.util.noise.CNG;
|
||||
import lombok.Data;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.ToString;
|
||||
@@ -130,7 +134,12 @@ public class IrisDimension extends IrisRegistrant {
|
||||
private final transient AtomicCache<IrisStaticObjectLayer> staticObjectLayer = new AtomicCache<>();
|
||||
private final transient AtomicCache<CNG> rockLayerGenerator = new AtomicCache<>();
|
||||
private final transient AtomicCache<CNG> fluidLayerGenerator = new AtomicCache<>();
|
||||
private final transient AtomicCache<CNG> coordFracture = new AtomicCache<>();
|
||||
@Getter(AccessLevel.NONE)
|
||||
@Setter(AccessLevel.NONE)
|
||||
private final transient LazyBoundedCache<Long, CNG> coordinateFractures = new LazyBoundedCache<>(8);
|
||||
@Getter(AccessLevel.NONE)
|
||||
@Setter(AccessLevel.NONE)
|
||||
private transient volatile CachedCoordinateFracture recentCoordinateFracture;
|
||||
private final transient AtomicCache<Double> sinr = new AtomicCache<>();
|
||||
private final transient AtomicCache<Double> cosr = new AtomicCache<>();
|
||||
private final transient AtomicCache<Double> rad = new AtomicCache<>();
|
||||
@@ -537,12 +546,15 @@ public class IrisDimension extends IrisRegistrant {
|
||||
}
|
||||
|
||||
public CNG getCoordFracture(RNG rng, int signature) {
|
||||
return coordFracture.aquire(() ->
|
||||
{
|
||||
CNG coordFracture = CNG.signature(rng.nextParallelRNG(signature));
|
||||
coordFracture.scale(0.012 / coordFractureZoom);
|
||||
return coordFracture;
|
||||
});
|
||||
long seed = rng.getSeed() + signature;
|
||||
CachedCoordinateFracture recent = recentCoordinateFracture;
|
||||
if (recent != null && recent.seed == seed) {
|
||||
return recent.generator;
|
||||
}
|
||||
CNG generator = coordinateFractures.computeIfAbsent(seed, ignored ->
|
||||
CNG.signature(new RNG(seed)).scale(0.012 / coordFractureZoom));
|
||||
recentCoordinateFracture = new CachedCoordinateFracture(seed, generator);
|
||||
return generator;
|
||||
}
|
||||
|
||||
public double getDimensionAngle() {
|
||||
@@ -1301,4 +1313,7 @@ public class IrisDimension extends IrisRegistrant {
|
||||
IO.writeAll(dimTypeVanilla, json);
|
||||
}
|
||||
}
|
||||
|
||||
private record CachedCoordinateFracture(long seed, CNG generator) {
|
||||
}
|
||||
}
|
||||
@@ -67,7 +67,6 @@ public class IrisMaterialPalette implements StructurePalette {
|
||||
@Getter(AccessLevel.NONE)
|
||||
@Setter(AccessLevel.NONE)
|
||||
private final transient AtomicReference<CachedLayerGenerator> recentLayerGenerator = new AtomicReference<>();
|
||||
private final transient AtomicCache<CNG> heightGenerator = new AtomicCache<>();
|
||||
@Description("The style of noise")
|
||||
private IrisGeneratorStyle style = NoiseStyle.STATIC.style();
|
||||
@MinNumber(0.0001)
|
||||
|
||||
@@ -138,6 +138,7 @@ public class IrisObject extends IrisRegistrant {
|
||||
@Getter
|
||||
@Setter
|
||||
protected transient volatile boolean smartBored = false;
|
||||
transient volatile IrisObjectShaping.SmartBoreVariant smartBoreVariant;
|
||||
@Setter
|
||||
protected transient AtomicCache<AxisAlignedBB> aabb = new AtomicCache<>();
|
||||
transient final AtomicCache<KList<IrisBlockVector>> surfaceSupportOffsets = new AtomicCache<>();
|
||||
@@ -335,7 +336,8 @@ public class IrisObject extends IrisRegistrant {
|
||||
}
|
||||
|
||||
public int place(int x, int yv, int z, IObjectPlacer oplacer, IrisObjectPlacement config, RNG rng, BiConsumer<BlockPosition, NativeBlockState> listener, CarveResult c, IrisData rdata) {
|
||||
return new IrisObjectPlacementRunner(this).place(x, yv, z, oplacer, config, rng, listener, c, rdata);
|
||||
IrisObject placedObject = config.isSmartBore() ? IrisObjectShaping.smartBoredVariant(this) : this;
|
||||
return new IrisObjectPlacementRunner(placedObject).place(x, yv, z, oplacer, config, rng, listener, c, rdata);
|
||||
}
|
||||
|
||||
KList<IrisBlockVector> getSurfaceSupportOffsets() {
|
||||
|
||||
@@ -49,6 +49,7 @@ import art.arcane.volmlib.util.math.Position2;
|
||||
import art.arcane.volmlib.util.math.RNG;
|
||||
import art.arcane.volmlib.util.matter.MatterMarker;
|
||||
|
||||
import java.util.Comparator;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
@@ -123,10 +124,6 @@ final class IrisObjectPlacementRunner {
|
||||
}
|
||||
}
|
||||
|
||||
if (config.isSmartBore()) {
|
||||
IrisObjectShaping.ensureSmartBored(self);
|
||||
}
|
||||
|
||||
boolean warped = !config.getWarp().isFlat();
|
||||
// Placement-invariant hoists off the per-voxel loop: the warp CNG resolution went
|
||||
// through a keyed cache probe per block, and the two boolean gates below cut per-block
|
||||
@@ -902,7 +899,9 @@ final class IrisObjectPlacementRunner {
|
||||
if (heightmap != null) {
|
||||
RNG rngx = rng.nextParallelRNG(3468854);
|
||||
|
||||
for (Position2 i : heightmap.k()) {
|
||||
KList<Position2> snowColumns = heightmap.k();
|
||||
snowColumns.sort(Comparator.comparingInt(Position2::getX).thenComparingInt(Position2::getZ));
|
||||
for (Position2 i : snowColumns) {
|
||||
int vx = i.getX();
|
||||
int vy = heightmap.get(i);
|
||||
int vz = i.getZ();
|
||||
|
||||
@@ -39,6 +39,41 @@ final class IrisObjectShaping {
|
||||
private IrisObjectShaping() {
|
||||
}
|
||||
|
||||
static IrisObject smartBoredVariant(IrisObject self) {
|
||||
SmartBoreVariant cached = self.smartBoreVariant;
|
||||
if (cached != null && cached.matches(self)) {
|
||||
return cached.object();
|
||||
}
|
||||
self.writeLock.lock();
|
||||
try {
|
||||
cached = self.smartBoreVariant;
|
||||
if (cached != null && cached.matches(self)) {
|
||||
return cached.object();
|
||||
}
|
||||
IrisObject variant = self.copy();
|
||||
ensureSmartBored(variant);
|
||||
self.smartBoreVariant = new SmartBoreVariant(self.blocks, self.states,
|
||||
self.blocks.modificationRevision(), self.states.modificationRevision(),
|
||||
self.w, self.h, self.d, self.center.getX(), self.center.getY(), self.center.getZ(), variant);
|
||||
return variant;
|
||||
} finally {
|
||||
self.writeLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
record SmartBoreVariant(VectorMap<NativeBlockState> blocks, VectorMap<TileData> states,
|
||||
long blockRevision, long stateRevision, int width, int height, int depth,
|
||||
int centerX, int centerY, int centerZ, IrisObject object) {
|
||||
boolean matches(IrisObject source) {
|
||||
return blocks == source.blocks && states == source.states
|
||||
&& blockRevision == source.blocks.modificationRevision()
|
||||
&& stateRevision == source.states.modificationRevision()
|
||||
&& width == source.w && height == source.h && depth == source.d
|
||||
&& centerX == source.center.getX() && centerY == source.center.getY()
|
||||
&& centerZ == source.center.getZ();
|
||||
}
|
||||
}
|
||||
|
||||
static void ensureSmartBored(IrisObject self) {
|
||||
if (self.smartBored) {
|
||||
return;
|
||||
|
||||
Reference in new issue
Block a user