update most things to use new maybe and either

This commit is contained in:
dfsek
2025-12-29 18:59:57 -07:00
parent d52cd0d7cf
commit 16705057e0
11 changed files with 155 additions and 91 deletions
@@ -9,6 +9,7 @@ import com.dfsek.terra.addons.biome.extrusion.api.Extrusion;
import com.dfsek.terra.addons.biome.extrusion.utils.ExtrusionPipeline; import com.dfsek.terra.addons.biome.extrusion.utils.ExtrusionPipeline;
import com.dfsek.terra.addons.biome.extrusion.utils.ExtrusionPipelineFactory; import com.dfsek.terra.addons.biome.extrusion.utils.ExtrusionPipelineFactory;
import com.dfsek.terra.api.util.Column; import com.dfsek.terra.api.util.Column;
import com.dfsek.terra.api.util.generic.data.types.Maybe;
import com.dfsek.terra.api.world.biome.Biome; import com.dfsek.terra.api.world.biome.Biome;
import com.dfsek.terra.api.world.biome.generation.BiomeProvider; import com.dfsek.terra.api.world.biome.generation.BiomeProvider;
@@ -39,11 +40,11 @@ public class BiomeExtrusionProvider implements BiomeProvider {
public Column<Biome> getColumn(int x, int z, long seed, int min, int max) { public Column<Biome> getColumn(int x, int z, long seed, int min, int max) {
return delegate.getBaseBiome(x, z, seed) return delegate.getBaseBiome(x, z, seed)
.map(base -> (Column<Biome>) new BaseBiomeColumn(this, base, min, max, x, z, seed)) .map(base -> (Column<Biome>) new BaseBiomeColumn(this, base, min, max, x, z, seed))
.orElseGet(() -> BiomeProvider.super.getColumn(x, z, seed, min, max)); .get(() -> BiomeProvider.super.getColumn(x, z, seed, min, max));
} }
@Override @Override
public Optional<Biome> getBaseBiome(int x, int z, long seed) { public Maybe<Biome> getBaseBiome(int x, int z, long seed) {
return delegate.getBaseBiome(x, z, seed); return delegate.getBaseBiome(x, z, seed);
} }
@@ -11,6 +11,7 @@ import java.util.Optional;
import com.dfsek.terra.addons.image.colorsampler.ColorSampler; import com.dfsek.terra.addons.image.colorsampler.ColorSampler;
import com.dfsek.terra.addons.image.converter.ColorConverter; import com.dfsek.terra.addons.image.converter.ColorConverter;
import com.dfsek.terra.api.util.generic.data.types.Maybe;
import com.dfsek.terra.api.world.biome.Biome; import com.dfsek.terra.api.world.biome.Biome;
import com.dfsek.terra.api.world.biome.generation.BiomeProvider; import com.dfsek.terra.api.world.biome.generation.BiomeProvider;
@@ -40,8 +41,8 @@ public class ImageBiomeProvider implements BiomeProvider {
} }
@Override @Override
public Optional<Biome> getBaseBiome(int x, int z, long seed) { public Maybe<Biome> getBaseBiome(int x, int z, long seed) {
return Optional.of(getBiome(x, z)); return Maybe.just(getBiome(x, z));
} }
@Override @Override
@@ -8,6 +8,9 @@
package com.dfsek.terra.addons.biome.pipeline; package com.dfsek.terra.addons.biome.pipeline;
import com.dfsek.seismic.type.sampler.Sampler; import com.dfsek.seismic.type.sampler.Sampler;
import com.dfsek.terra.api.util.generic.data.types.Maybe;
import com.github.benmanes.caffeine.cache.Caffeine; import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.LoadingCache; import com.github.benmanes.caffeine.cache.LoadingCache;
@@ -106,8 +109,8 @@ public class PipelineBiomeProvider implements BiomeProvider {
} }
@Override @Override
public Optional<Biome> getBaseBiome(int x, int z, long seed) { public Maybe<Biome> getBaseBiome(int x, int z, long seed) {
return Optional.of(getBiome(x, z, seed)); return Maybe.just(getBiome(x, z, seed));
} }
@Override @Override
@@ -8,18 +8,13 @@
package com.dfsek.terra.addons.biome.single; package com.dfsek.terra.addons.biome.single;
import java.util.Collections; import java.util.Collections;
import java.util.Optional;
import com.dfsek.terra.api.util.generic.data.types.Maybe;
import com.dfsek.terra.api.world.biome.Biome; import com.dfsek.terra.api.world.biome.Biome;
import com.dfsek.terra.api.world.biome.generation.BiomeProvider; import com.dfsek.terra.api.world.biome.generation.BiomeProvider;
public class SingleBiomeProvider implements BiomeProvider { public record SingleBiomeProvider(Biome biome) implements BiomeProvider {
private final Biome biome;
public SingleBiomeProvider(Biome biome) {
this.biome = biome;
}
@Override @Override
public Biome getBiome(int x, int y, int z, long seed) { public Biome getBiome(int x, int y, int z, long seed) {
@@ -27,8 +22,8 @@ public class SingleBiomeProvider implements BiomeProvider {
} }
@Override @Override
public Optional<Biome> getBaseBiome(int x, int z, long seed) { public Maybe<Biome> getBaseBiome(int x, int z, long seed) {
return Optional.of(biome); return Maybe.just(biome);
} }
@Override @Override
@@ -30,7 +30,7 @@ public class ElevationInterpolator {
gens[x + 1 + smooth][z + 1 + smooth] = gens[x + 1 + smooth][z + 1 + smooth] =
provider provider
.getBaseBiome(bx, bz, seed) .getBaseBiome(bx, bz, seed)
.orElseGet(() -> provider.getBiome(bx, 0, bz, seed)) // kind of a hack .get(() -> provider.getBiome(bx, 0, bz, seed)) // kind of a hack
.getContext() .getContext()
.get(noisePropertiesKey); .get(noisePropertiesKey);
} }
@@ -2,10 +2,13 @@ package com.dfsek.terra.addons.commands.locate;
import com.dfsek.seismic.type.vector.Vector2Int; import com.dfsek.seismic.type.vector.Vector2Int;
import com.dfsek.seismic.type.vector.Vector3Int; import com.dfsek.seismic.type.vector.Vector3Int;
import com.dfsek.terra.api.util.generic.data.types.Either; import com.dfsek.terra.api.util.generic.data.types.Either;
import com.dfsek.terra.api.util.generic.data.types.Maybe;
import com.dfsek.terra.api.world.biome.Biome; import com.dfsek.terra.api.world.biome.Biome;
import com.dfsek.terra.api.world.biome.generation.BiomeProvider; import com.dfsek.terra.api.world.biome.generation.BiomeProvider;
import com.dfsek.terra.api.world.info.WorldProperties; import com.dfsek.terra.api.world.info.WorldProperties;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.util.Optional; import java.util.Optional;
@@ -14,6 +17,7 @@ import java.util.function.Predicate;
import java.util.stream.IntStream; import java.util.stream.IntStream;
import java.util.stream.Stream; import java.util.stream.Stream;
public class BiomeLocator { public class BiomeLocator {
/** /**
@@ -27,9 +31,10 @@ public class BiomeLocator {
* @param step The search step/increment. Higher values are faster but less accurate. * @param step The search step/increment. Higher values are faster but less accurate.
* @param filter The condition to match the biome. * @param filter The condition to match the biome.
* @param search3D If true, searches the entire vertical column at each step. If false, only checks originY. * @param search3D If true, searches the entire vertical column at each step. If false, only checks originY.
*
* @return An Optional containing the location of the found biome, or empty if not found. * @return An Optional containing the location of the found biome, or empty if not found.
*/ */
public static Optional<Either<Vector3Int, Vector2Int>> search( public static Maybe<Either<Vector3Int, Vector2Int>> search(
@NotNull BiomeProvider provider, @NotNull BiomeProvider provider,
@NotNull WorldProperties properties, @NotNull WorldProperties properties,
int originX, int originX,
@@ -44,14 +49,13 @@ public class BiomeLocator {
int maxHeight = properties.getMaxHeight(); int maxHeight = properties.getMaxHeight();
// 1. Check the exact center first // 1. Check the exact center first
Optional<Either<Vector3Int, Vector2Int>> centerResult = check(provider, seed, originX, originZ, step, filter, search3D, minHeight, maxHeight); return
if (centerResult.isPresent()) { check(provider, seed, originX, originZ, step, filter, search3D, minHeight, maxHeight)
return centerResult;
}
// 2. Begin Parallel Square Spiral Search // 2. Begin Parallel Square Spiral Search
// We iterate rings sequentially to guarantee finding the *nearest* result. // We iterate rings sequentially to guarantee finding the *nearest* result.
// However, we process all points within a specific ring in parallel. // However, we process all points within a specific ring in parallel.
.or(() -> {
for(int r = step; r <= radius; r += step) { for(int r = step; r <= radius; r += step) {
final int currentRadius = r; final int currentRadius = r;
final int minX = -currentRadius; final int minX = -currentRadius;
@@ -85,23 +89,23 @@ public class BiomeLocator {
minHeight, minHeight,
maxHeight maxHeight
)) ))
.filter(Optional::isPresent) .flatMap(Maybe::toStream)
.map(Optional::get)
.findFirst(); // findFirst() respects encounter order (North -> East -> South -> West) .findFirst(); // findFirst() respects encounter order (North -> East -> South -> West)
if(ringResult.isPresent()) { if(ringResult.isPresent()) {
return ringResult; return Maybe.fromOptional(ringResult);
} }
} }
return Maybe.nothing();
});
return Optional.empty();
} }
/** /**
* Helper to check a specific coordinate column or point. * Helper to check a specific coordinate column or point.
* This logic is executed inside the worker threads. * This logic is executed inside the worker threads.
*/ */
private static Optional<Either<Vector3Int, Vector2Int>> check( private static Maybe<Either<Vector3Int, Vector2Int>> check(
BiomeProvider provider, BiomeProvider provider,
long seed, long seed,
int x, int x,
@@ -116,10 +120,10 @@ public class BiomeLocator {
// Iterate from bottom to top of the world using the step // Iterate from bottom to top of the world using the step
for(int y = minHeight; y < maxHeight; y += step) { for(int y = minHeight; y < maxHeight; y += step) {
if(filter.test(provider.getBiome(x, y, z, seed))) { if(filter.test(provider.getBiome(x, y, z, seed))) {
return Optional.of(Either.left(Vector3Int.of(x, y, z))); return Maybe.just(Either.left(Vector3Int.of(x, y, z)));
} }
} }
return Optional.empty(); return Maybe.nothing();
} else { } else {
// 2D Mode: Check only the base biome // 2D Mode: Check only the base biome
// We use a flatMap approach here to be safe with Optionals inside the stream // We use a flatMap approach here to be safe with Optionals inside the stream
@@ -3,6 +3,10 @@ package com.dfsek.terra.addons.commands.locate;
import com.dfsek.seismic.type.vector.Vector2Int; import com.dfsek.seismic.type.vector.Vector2Int;
import com.dfsek.seismic.type.vector.Vector3Int; import com.dfsek.seismic.type.vector.Vector3Int;
import com.dfsek.terra.api.util.function.FunctionUtils;
import com.dfsek.terra.api.util.generic.data.types.Maybe;
import org.incendo.cloud.CommandManager; import org.incendo.cloud.CommandManager;
import org.incendo.cloud.component.DefaultValue; import org.incendo.cloud.component.DefaultValue;
import org.incendo.cloud.context.CommandContext; import org.incendo.cloud.context.CommandContext;
@@ -26,6 +30,8 @@ import com.dfsek.terra.api.util.reflection.TypeKey;
import com.dfsek.terra.api.world.World; import com.dfsek.terra.api.world.World;
import com.dfsek.terra.api.world.biome.Biome; import com.dfsek.terra.api.world.biome.Biome;
import static com.dfsek.terra.api.util.function.FunctionUtils.collapse;
public class LocateCommandAddon implements AddonInitializer { public class LocateCommandAddon implements AddonInitializer {
@Inject @Inject
@@ -83,7 +89,7 @@ public class LocateCommandAddon implements AddonInitializer {
context.sender().sendMessage( context.sender().sendMessage(
"Searching for " + targetBiome.getID() + " within " + radius + " blocks" + modeMsg + "..."); "Searching for " + targetBiome.getID() + " within " + radius + " blocks" + modeMsg + "...");
Optional<Either<Vector3Int, Vector2Int>> result; Maybe<Either<Vector3Int, Vector2Int>> result;
// 3. Execute Search Loop // 3. Execute Search Loop
while(true) { while(true) {
@@ -100,7 +106,7 @@ public class LocateCommandAddon implements AddonInitializer {
// Exit Conditions: // Exit Conditions:
// 1. Found a result // 1. Found a result
if(result.isPresent()) { if(result.isJust()) {
break; break;
} }
// 2. Not in auto mode (only run once) // 2. Not in auto mode (only run once)
@@ -118,22 +124,11 @@ public class LocateCommandAddon implements AddonInitializer {
} }
// 4. Handle Result // 4. Handle Result
if(result.isPresent()) { context.sender().sendMessage(collapse(result.map(location -> location.collect(
Either<Vector3Int, Vector2Int> location = result.get(); left -> String.format("%d, %d, %d", left.getX(), left.getY(), left.getZ()),
String coords; right -> String.format("%d, ~, %d", right.getX(), right.getZ())))
.map(coords -> "Found " + targetBiome.getID() + " at [" + coords + "]")
if(location.hasLeft()) { // 3D Result .toEither("Could not find " + targetBiome.getID() + " within " + radius + " blocks.")));
Vector3Int vec = location.getLeft().get();
coords = String.format("%d, %d, %d", vec.getX(), vec.getY(), vec.getZ());
} else { // 2D Result
Vector2Int vec = location.getRight().get();
coords = String.format("%d, ~, %d", vec.getX(), vec.getZ());
}
context.sender().sendMessage("Found " + targetBiome.getID() + " at [" + coords + "]");
} else {
context.sender().sendMessage("Could not find " + targetBiome.getID() + " within " + radius + " blocks.");
}
}) })
.permission("terra.locate.biome") .permission("terra.locate.biome")
); );
@@ -22,6 +22,10 @@ public final class FunctionUtils {
return (Either<L, T>) o.map(Either::right).orElseGet(() -> Either.left(de)); return (Either<L, T>) o.map(Either::right).orElseGet(() -> Either.left(de));
} }
public static <T> T collapse(Either<T, T> either) {
return either.collect(Function.identity(), Function.identity());
}
public static <T, U> Function<T, Either<Exception, U>> liftTry(Function<T, U> f) { public static <T, U> Function<T, Either<Exception, U>> liftTry(Function<T, U> f) {
return s -> { return s -> {
try { try {
@@ -4,7 +4,9 @@ import com.dfsek.terra.api.util.generic.control.Monad;
import java.util.Optional; import java.util.Optional;
import java.util.function.Function; import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier; import java.util.function.Supplier;
import java.util.stream.Stream;
public interface Maybe<T> extends Monad<T, Maybe<?>> { public interface Maybe<T> extends Monad<T, Maybe<?>> {
@@ -21,6 +23,11 @@ public interface Maybe<T> extends Monad<T, Maybe<?>> {
<L> Either<L, T> toEither(L l); <L> Either<L, T> toEither(L l);
T get(Supplier<T> def); T get(Supplier<T> def);
boolean isJust();
@Override
<U> Maybe<U> map(Function<T, U> map);
default T get(T def) { default T get(T def) {
return get(() -> def); return get(() -> def);
} }
@@ -29,6 +36,24 @@ public interface Maybe<T> extends Monad<T, Maybe<?>> {
return bind(ignore -> m); return bind(ignore -> m);
} }
Maybe<T> or(Supplier<Maybe<T>> or);
default Maybe<T> or(Maybe<T> or) {
return or(() -> or);
}
default Stream<T> toStream() {
return map(Stream::of).get(Stream.empty());
}
default Maybe<T> filter(Predicate<T> filter) {
return bind(o -> filter.test(o) ? this : nothing());
}
static <T> Maybe<T> fromOptional(Optional<T> op) {
return op.map(Maybe::just).orElseGet(Maybe::nothing);
}
static <T1> Maybe<T1> just(T1 t) { static <T1> Maybe<T1> just(T1 t) {
record Just<T>(T value) implements Maybe<T> { record Just<T>(T value) implements Maybe<T> {
@@ -47,6 +72,21 @@ public interface Maybe<T> extends Monad<T, Maybe<?>> {
return value; return value;
} }
@Override
public boolean isJust() {
return true;
}
@Override
public <U> Maybe<U> map(Function<T, U> map) {
return just(map.apply(value));
}
@Override
public Maybe<T> or(Supplier<Maybe<T>> or) {
return this;
}
@Override @Override
public <T2> Maybe<T2> bind(Function<T, Monad<T2, Maybe<?>>> map) { public <T2> Maybe<T2> bind(Function<T, Monad<T2, Maybe<?>>> map) {
return (Maybe<T2>) map.apply(value); return (Maybe<T2>) map.apply(value);
@@ -77,6 +117,22 @@ public interface Maybe<T> extends Monad<T, Maybe<?>> {
public T get(Supplier<T> def) { public T get(Supplier<T> def) {
return def.get(); return def.get();
} }
@Override
public boolean isJust() {
return false;
}
@Override
@SuppressWarnings("unchecked")
public <U> Maybe<U> map(Function<T, U> map) {
return (Maybe<U>) this;
}
@Override
public Maybe<T> or(Supplier<Maybe<T>> or) {
return or.get();
}
} }
return new Nothing<>(); return new Nothing<>();
} }
@@ -9,6 +9,9 @@ package com.dfsek.terra.api.world.biome.generation;
import com.dfsek.seismic.type.vector.Vector3; import com.dfsek.seismic.type.vector.Vector3;
import com.dfsek.seismic.type.vector.Vector3Int; import com.dfsek.seismic.type.vector.Vector3Int;
import com.dfsek.terra.api.util.generic.data.types.Maybe;
import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.Contract;
import java.util.Optional; import java.util.Optional;
@@ -64,8 +67,8 @@ public interface BiomeProvider {
return getBiome(vector3.getX(), vector3.getY(), vector3.getZ(), seed); return getBiome(vector3.getX(), vector3.getY(), vector3.getZ(), seed);
} }
default Optional<Biome> getBaseBiome(int x, int z, long seed) { default Maybe<Biome> getBaseBiome(int x, int z, long seed) {
return Optional.empty(); return Maybe.nothing();
} }
@@ -1,5 +1,7 @@
package com.dfsek.terra.api.world.biome.generation; package com.dfsek.terra.api.world.biome.generation;
import com.dfsek.terra.api.util.generic.data.types.Maybe;
import com.github.benmanes.caffeine.cache.Caffeine; import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.LoadingCache; import com.github.benmanes.caffeine.cache.LoadingCache;
import com.github.benmanes.caffeine.cache.Scheduler; import com.github.benmanes.caffeine.cache.Scheduler;
@@ -25,14 +27,14 @@ public class CachingBiomeProvider implements BiomeProvider, Handle {
protected final BiomeProvider delegate; protected final BiomeProvider delegate;
private final int res; private final int res;
private final ThreadLocal<Pair.Mutable<SeededVector3Key, LoadingCache<SeededVector3Key, Biome>>> cache; private final ThreadLocal<Pair.Mutable<SeededVector3Key, LoadingCache<SeededVector3Key, Biome>>> cache;
private final ThreadLocal<Pair.Mutable<SeededVector2Key, LoadingCache<SeededVector2Key, Optional<Biome>>>> baseCache; private final ThreadLocal<Pair.Mutable<SeededVector2Key, LoadingCache<SeededVector2Key, Maybe<Biome>>>> baseCache;
protected CachingBiomeProvider(BiomeProvider delegate) { protected CachingBiomeProvider(BiomeProvider delegate) {
this.delegate = delegate; this.delegate = delegate;
this.res = delegate.resolution(); this.res = delegate.resolution();
this.baseCache = ThreadLocal.withInitial(() -> { this.baseCache = ThreadLocal.withInitial(() -> {
LoadingCache<SeededVector2Key, Optional<Biome>> cache = Caffeine LoadingCache<SeededVector2Key, Maybe<Biome>> cache = Caffeine
.newBuilder() .newBuilder()
.executor(CACHE_EXECUTOR) .executor(CACHE_EXECUTOR)
.scheduler(Scheduler.systemScheduler()) .scheduler(Scheduler.systemScheduler())
@@ -56,7 +58,7 @@ public class CachingBiomeProvider implements BiomeProvider, Handle {
} }
private Optional<Biome> sampleBiome(SeededVector2Key vec) { private Maybe<Biome> sampleBiome(SeededVector2Key vec) {
this.baseCache.get().setLeft(new SeededVector2Key(0, 0, 0)); this.baseCache.get().setLeft(new SeededVector2Key(0, 0, 0));
return this.delegate.getBaseBiome(vec.x * res, vec.z * res, vec.seed); return this.delegate.getBaseBiome(vec.x * res, vec.z * res, vec.seed);
} }
@@ -80,8 +82,8 @@ public class CachingBiomeProvider implements BiomeProvider, Handle {
} }
@Override @Override
public Optional<Biome> getBaseBiome(int x, int z, long seed) { public Maybe<Biome> getBaseBiome(int x, int z, long seed) {
Mutable<SeededVector2Key, LoadingCache<SeededVector2Key, Optional<Biome>>> cachePair = baseCache.get(); Mutable<SeededVector2Key, LoadingCache<SeededVector2Key, Maybe<Biome>>> cachePair = baseCache.get();
SeededVector2Key mutableKey = cachePair.getLeft(); SeededVector2Key mutableKey = cachePair.getLeft();
mutableKey.set(x, z, seed); mutableKey.set(x, z, seed);
return cachePair.getRight().get(mutableKey); return cachePair.getRight().get(mutableKey);