diff --git a/common/addons/config-noise-function/src/main/java/com/dfsek/terra/addons/noise/NoiseAddon.java b/common/addons/config-noise-function/src/main/java/com/dfsek/terra/addons/noise/NoiseAddon.java index e2fa8956c..e4dc09b56 100644 --- a/common/addons/config-noise-function/src/main/java/com/dfsek/terra/addons/noise/NoiseAddon.java +++ b/common/addons/config-noise-function/src/main/java/com/dfsek/terra/addons/noise/NoiseAddon.java @@ -17,6 +17,7 @@ import com.dfsek.terra.addons.manifest.api.AddonInitializer; import com.dfsek.terra.addons.noise.config.CubicSplinePointTemplate; import com.dfsek.terra.addons.noise.config.DimensionApplicableNoiseSampler; import com.dfsek.terra.addons.noise.config.templates.BinaryArithmeticTemplate; +import com.dfsek.terra.addons.noise.config.templates.CacheSamplerTemplate; import com.dfsek.terra.addons.noise.config.templates.DerivativeNoiseSamplerTemplate; import com.dfsek.terra.addons.noise.config.templates.DomainWarpTemplate; import com.dfsek.terra.addons.noise.config.templates.FunctionTemplate; @@ -150,6 +151,8 @@ public class NoiseAddon implements AddonInitializer { noiseRegistry.register(addon.key("MAX"), () -> new BinaryArithmeticTemplate<>(MaxSampler::new)); noiseRegistry.register(addon.key("MIN"), () -> new BinaryArithmeticTemplate<>(MinSampler::new)); + noiseRegistry.register(addon.key("CACHE"), () -> new CacheSamplerTemplate(plugin.getGenerationThreads())); + Map packSamplers = new LinkedHashMap<>(); Map packFunctions = new LinkedHashMap<>(); diff --git a/common/addons/config-noise-function/src/main/java/com/dfsek/terra/addons/noise/config/templates/CacheSamplerTemplate.java b/common/addons/config-noise-function/src/main/java/com/dfsek/terra/addons/noise/config/templates/CacheSamplerTemplate.java new file mode 100644 index 000000000..ca3b7d0b0 --- /dev/null +++ b/common/addons/config-noise-function/src/main/java/com/dfsek/terra/addons/noise/config/templates/CacheSamplerTemplate.java @@ -0,0 +1,26 @@ +package com.dfsek.terra.addons.noise.config.templates; + +import com.dfsek.tectonic.api.config.template.annotations.Default; +import com.dfsek.tectonic.api.config.template.annotations.Value; + +import com.dfsek.terra.addons.noise.samplers.CacheSampler; +import com.dfsek.terra.addons.noise.samplers.LinearHeightmapSampler; +import com.dfsek.terra.api.noise.NoiseSampler; + + +public class CacheSamplerTemplate extends SamplerTemplate { + @Value("sampler") + @Default + private NoiseSampler sampler; + + private final int generationThreads; + + public CacheSamplerTemplate(int generationThreads) { + this.generationThreads = generationThreads; + } + + @Override + public NoiseSampler get() { + return new CacheSampler(sampler, getDimensions(), generationThreads); + } +} diff --git a/common/addons/config-noise-function/src/main/java/com/dfsek/terra/addons/noise/samplers/CacheSampler.java b/common/addons/config-noise-function/src/main/java/com/dfsek/terra/addons/noise/samplers/CacheSampler.java new file mode 100644 index 000000000..17eb71f00 --- /dev/null +++ b/common/addons/config-noise-function/src/main/java/com/dfsek/terra/addons/noise/samplers/CacheSampler.java @@ -0,0 +1,125 @@ +package com.dfsek.terra.addons.noise.samplers; + +import com.dfsek.terra.api.noise.DerivativeNoiseSampler; +import com.dfsek.terra.api.noise.NoiseSampler; + +import com.github.benmanes.caffeine.cache.Caffeine; +import com.github.benmanes.caffeine.cache.LoadingCache; + + +public class CacheSampler implements DerivativeNoiseSampler { + + private final NoiseSampler sampler; + private final LoadingCache cache2D; + private final LoadingCache cache3D; + private final LoadingCache cache2DDirv; + private final LoadingCache cache3DDirv; + + public CacheSampler(NoiseSampler sampler, int dimensions, int generationThreads) { + this.sampler = sampler; + if (dimensions == 2) { + this.cache2D = Caffeine + .newBuilder() + .initialCapacity(0) + .maximumSize(256L * generationThreads) // 1 full chunk (high res) + .build(vec -> sampler.noise(vec.seed, vec.x, vec.z)); + cache3D = null; + cache3DDirv = null; + if (DerivativeNoiseSampler.isDifferentiable(sampler)) { + this.cache2DDirv = Caffeine + .newBuilder() + .initialCapacity(0) + .maximumSize(256L * generationThreads) // 1 full chunk (high res) + .build(vec -> ((DerivativeNoiseSampler) sampler).noised(vec.seed, vec.x, vec.z)); + } else { + cache2DDirv = null; + } + } else { + this.cache3D = Caffeine + .newBuilder() + .initialCapacity(0) + .maximumSize(256L * generationThreads) // 1 full chunk (high res) + .build(vec -> sampler.noise(vec.seed, vec.x, vec.y, vec.z)); + cache2D = null; + cache2DDirv = null; + if (DerivativeNoiseSampler.isDifferentiable(sampler)) { + this.cache3DDirv = Caffeine + .newBuilder() + .initialCapacity(0) + .maximumSize(256L * generationThreads) // 1 full chunk (high res) + .build(vec -> ((DerivativeNoiseSampler) sampler).noised(vec.seed, vec.x, vec.y, vec.z)); + } else { + cache3DDirv = null; + } + } + } + + @Override + public boolean isDifferentiable() { + return DerivativeNoiseSampler.isDifferentiable(sampler); + } + + @Override + public double[] noised(long seed, double x, double y) { + return cache2DDirv.get(new DoubleSeededVector2(x, y, seed)); + } + + @Override + public double[] noised(long seed, double x, double y, double z) { + return cache3DDirv.get(new DoubleSeededVector3(x, y, z, seed)); + } + + @Override + public double noise(long seed, double x, double y) { + DoubleSeededVector2 vec = new DoubleSeededVector2(x, y, seed); + if (cache2DDirv != null && cache2DDirv.estimatedSize() != 0) { + return cache2DDirv.get(vec)[0]; + } + return cache2D.get(vec); + } + + @Override + public double noise(long seed, double x, double y, double z) { + DoubleSeededVector3 vec = new DoubleSeededVector3(x, y, z, seed); + if (cache3DDirv != null && cache3DDirv.estimatedSize() != 0) { + return cache3DDirv.get(vec)[0]; + } + return cache3D.get(vec); + } + + private record DoubleSeededVector3(double x, double y, double z, long seed) { + @Override + public boolean equals(Object obj) { + if(obj instanceof DoubleSeededVector3 that) { + return this.y == that.y && this.z == that.z && this.x == that.x && this.seed == that.seed; + } + return false; + } + + @Override + public int hashCode() { + int code = (int) Double.doubleToLongBits(x); + code = 31 * code + (int) Double.doubleToLongBits(y); + code = 31 * code + (int) Double.doubleToLongBits(z); + return 31 * code + (Long.hashCode(seed)); + } + } + + + private record DoubleSeededVector2(double x, double z, long seed) { + @Override + public boolean equals(Object obj) { + if(obj instanceof DoubleSeededVector2 that) { + return this.z == that.z && this.x == that.x && this.seed == that.seed; + } + return false; + } + + @Override + public int hashCode() { + int code = (int) Double.doubleToLongBits(x); + code = 31 * code + (int) Double.doubleToLongBits(z); + return 31 * code + (Long.hashCode(seed)); + } + } +} diff --git a/common/api/src/main/java/com/dfsek/terra/api/Platform.java b/common/api/src/main/java/com/dfsek/terra/api/Platform.java index 95b00c575..cabc44bbd 100644 --- a/common/api/src/main/java/com/dfsek/terra/api/Platform.java +++ b/common/api/src/main/java/com/dfsek/terra/api/Platform.java @@ -81,4 +81,7 @@ public interface Platform extends LoaderRegistrar { @NotNull @Contract(pure = true) Profiler getProfiler(); + + @Contract(pure = true) + int getGenerationThreads(); } diff --git a/common/api/src/main/java/com/dfsek/terra/api/world/biome/generation/BiomeProvider.java b/common/api/src/main/java/com/dfsek/terra/api/world/biome/generation/BiomeProvider.java index d049cd43b..743d1d671 100644 --- a/common/api/src/main/java/com/dfsek/terra/api/world/biome/generation/BiomeProvider.java +++ b/common/api/src/main/java/com/dfsek/terra/api/world/biome/generation/BiomeProvider.java @@ -7,6 +7,8 @@ package com.dfsek.terra.api.world.biome.generation; +import com.dfsek.terra.api.Platform; + import org.jetbrains.annotations.Contract; import java.util.Optional; @@ -91,11 +93,11 @@ public interface BiomeProvider { return StreamSupport.stream(getBiomes().spliterator(), false); } - default CachingBiomeProvider caching() { + default CachingBiomeProvider caching(Platform platform) { if(this instanceof CachingBiomeProvider cachingBiomeProvider) { return cachingBiomeProvider; } - return new CachingBiomeProvider(this); + return new CachingBiomeProvider(this, platform.getGenerationThreads()); } diff --git a/common/api/src/main/java/com/dfsek/terra/api/world/biome/generation/CachingBiomeProvider.java b/common/api/src/main/java/com/dfsek/terra/api/world/biome/generation/CachingBiomeProvider.java index a07e9efd9..0a3f7db1e 100644 --- a/common/api/src/main/java/com/dfsek/terra/api/world/biome/generation/CachingBiomeProvider.java +++ b/common/api/src/main/java/com/dfsek/terra/api/world/biome/generation/CachingBiomeProvider.java @@ -21,19 +21,20 @@ public class CachingBiomeProvider implements BiomeProvider, Handle { private final LoadingCache cache; private final LoadingCache> baseCache; - protected CachingBiomeProvider(BiomeProvider delegate) { + protected CachingBiomeProvider(BiomeProvider delegate, int generationThreads) { this.delegate = delegate; this.res = delegate.resolution(); + int size = generationThreads * 98304; this.cache = Caffeine .newBuilder() .scheduler(Scheduler.disabledScheduler()) - .initialCapacity(98304) - .maximumSize(98304) // 1 full chunk (high res) + .initialCapacity(size) + .maximumSize(size) // 1 full chunk (high res) .build(vec -> delegate.getBiome(vec.x * res, vec.y * res, vec.z * res, vec.seed)); this.baseCache = Caffeine .newBuilder() - .maximumSize(256) // 1 full chunk (high res) + .maximumSize(256L * generationThreads) // 1 full chunk (high res) .build(vec -> delegate.getBaseBiome(vec.x * res, vec.z * res, vec.seed)); } @@ -77,7 +78,7 @@ public class CachingBiomeProvider implements BiomeProvider, Handle { int code = x; code = 31 * code + y; code = 31 * code + z; - return 31 * code + ((int) (seed ^ (seed >>> 32))); + return 31 * code + (Long.hashCode(seed)); } } @@ -95,7 +96,7 @@ public class CachingBiomeProvider implements BiomeProvider, Handle { public int hashCode() { int code = x; code = 31 * code + z; - return 31 * code + ((int) (seed ^ (seed >>> 32))); + return 31 * code + (Long.hashCode(seed)); } } } diff --git a/common/implementation/base/src/main/java/com/dfsek/terra/AbstractPlatform.java b/common/implementation/base/src/main/java/com/dfsek/terra/AbstractPlatform.java index 86d22bc2a..6e4e8ad73 100644 --- a/common/implementation/base/src/main/java/com/dfsek/terra/AbstractPlatform.java +++ b/common/implementation/base/src/main/java/com/dfsek/terra/AbstractPlatform.java @@ -310,6 +310,24 @@ public abstract class AbstractPlatform implements Platform { } } + public static int getGenerationThreadsWithReflection(String className, String fieldName, String project) { + try { + Class aClass = Class.forName(className); + int threads = aClass.getField(fieldName).getInt(null); + logger.info("{} found, setting {} generation threads.", project, threads); + return threads; + } catch(ClassNotFoundException e) { + logger.info("{} not found.", project); + } catch(NoSuchFieldException e) { + logger.warn("{} found, but {} field not found this probably means {0} has changed its code and " + + "Terra has not updated to reflect that.", project, fieldName); + } catch(IllegalAccessException e) { + logger.error("Failed to access {} field in {}, assuming 1 generation thread.", fieldName, project, e); + } + return 0; + + } + @Override public void register(TypeRegistry registry) { loaders.register(registry); @@ -339,4 +357,9 @@ public abstract class AbstractPlatform implements Platform { public @NotNull Profiler getProfiler() { return profiler; } + + @Override + public int getGenerationThreads() { + return 1; + } } diff --git a/common/implementation/base/src/main/java/com/dfsek/terra/config/pack/ConfigPackImpl.java b/common/implementation/base/src/main/java/com/dfsek/terra/config/pack/ConfigPackImpl.java index 82ec7d95b..a03584e3f 100644 --- a/common/implementation/base/src/main/java/com/dfsek/terra/config/pack/ConfigPackImpl.java +++ b/common/implementation/base/src/main/java/com/dfsek/terra/config/pack/ConfigPackImpl.java @@ -232,7 +232,7 @@ public class ConfigPackImpl implements ConfigPack { ConfigPackPostTemplate packPostTemplate = new ConfigPackPostTemplate(); selfLoader.load(packPostTemplate, packManifest); seededBiomeProvider = - template.getBiomeCache() ? packPostTemplate.getProviderBuilder().caching() : packPostTemplate.getProviderBuilder(); + template.getBiomeCache() ? packPostTemplate.getProviderBuilder().caching(platform) : packPostTemplate.getProviderBuilder(); checkDeadEntries(); } diff --git a/platforms/bukkit/common/src/main/java/com/dfsek/terra/bukkit/PlatformImpl.java b/platforms/bukkit/common/src/main/java/com/dfsek/terra/bukkit/PlatformImpl.java index 45a82c5cb..f26698b39 100644 --- a/platforms/bukkit/common/src/main/java/com/dfsek/terra/bukkit/PlatformImpl.java +++ b/platforms/bukkit/common/src/main/java/com/dfsek/terra/bukkit/PlatformImpl.java @@ -51,7 +51,13 @@ public class PlatformImpl extends AbstractPlatform { private final TerraBukkitPlugin plugin; + private int generationThreads; + public PlatformImpl(TerraBukkitPlugin plugin) { + generationThreads = getGenerationThreadsWithReflection("ca.spottedleaf.moonrise.common.util.MoonriseCommon", "WORKER_THREADS", "Moonrise"); + if (generationThreads == 0) { + generationThreads = 1; + } this.plugin = plugin; load(); } @@ -108,6 +114,11 @@ public class PlatformImpl extends AbstractPlatform { return itemHandle; } + @Override + public int getGenerationThreads() { + return generationThreads; + } + @Override public void register(TypeRegistry registry) { super.register(registry); diff --git a/platforms/cli/src/main/java/com/dfsek/terra/cli/CLIPlatform.java b/platforms/cli/src/main/java/com/dfsek/terra/cli/CLIPlatform.java index ca58f1f5d..3d366d0e6 100644 --- a/platforms/cli/src/main/java/com/dfsek/terra/cli/CLIPlatform.java +++ b/platforms/cli/src/main/java/com/dfsek/terra/cli/CLIPlatform.java @@ -22,6 +22,8 @@ public class CLIPlatform extends AbstractPlatform { private final CLIWorldHandle worldHandle = new CLIWorldHandle(); private final CLIItemHandle itemHandle = new CLIItemHandle(); + private final int generationThreads = Runtime.getRuntime().availableProcessors() - 1; + public CLIPlatform() { LOGGER.info("Root directory: {}", getDataFolder().getAbsoluteFile()); load(); @@ -58,4 +60,9 @@ public class CLIPlatform extends AbstractPlatform { super.register(registry); registry.registerLoader(PlatformBiome.class, (TypeLoader) (annotatedType, o, configLoader, depthTracker) -> () -> o); } + + @Override + public int getGenerationThreads() { + return generationThreads; + } } diff --git a/platforms/mixin-lifecycle/src/main/java/com/dfsek/terra/lifecycle/LifecyclePlatform.java b/platforms/mixin-lifecycle/src/main/java/com/dfsek/terra/lifecycle/LifecyclePlatform.java index 391842311..bbcd1b4bb 100644 --- a/platforms/mixin-lifecycle/src/main/java/com/dfsek/terra/lifecycle/LifecyclePlatform.java +++ b/platforms/mixin-lifecycle/src/main/java/com/dfsek/terra/lifecycle/LifecyclePlatform.java @@ -3,6 +3,9 @@ package com.dfsek.terra.lifecycle; import ca.solostudios.strata.Versions; import ca.solostudios.strata.parser.tokenizer.ParseException; import ca.solostudios.strata.version.Version; + +import com.dfsek.terra.api.util.reflection.ReflectionUtil; + import net.minecraft.MinecraftVersion; import net.minecraft.enchantment.Enchantment; import net.minecraft.registry.Registry; @@ -37,8 +40,15 @@ public abstract class LifecyclePlatform extends ModPlatform { private static final AtomicReference> NOISE = new AtomicReference<>(); private static final AtomicReference> ENCHANTMENT = new AtomicReference<>(); private static MinecraftServer server; + private int generationThreads; public LifecyclePlatform() { + generationThreads = getGenerationThreadsWithReflection("com.ishland.c2me.base.common.GlobalExecutors", "GLOBAL_EXECUTOR_PARALLELISM", "C2ME"); + if (generationThreads == 0) { + generationThreads = getGenerationThreadsWithReflection("ca.spottedleaf.moonrise.common.util.MoonriseCommon", "WORKER_THREADS", "Moonrise"); + } if (generationThreads == 0) { + generationThreads = 1; + } CommonPlatform.initialize(this); load(); } @@ -55,6 +65,8 @@ public abstract class LifecyclePlatform extends ModPlatform { ENCHANTMENT.set(enchantmentRegistry); } + + @Override public MinecraftServer getServer() { return server;