- improved performance of chunk-gen operations

- optimized caching for heightmap & rivermap regions
- hopefully fixed some deadlock issues
- reverted custom mushrooms for now
This commit is contained in:
dags- 2020-03-18 18:12:50 +00:00
parent f7f1c17779
commit eea88bd0e2
21 changed files with 511 additions and 219 deletions

View File

@ -30,24 +30,25 @@ import com.terraforged.core.world.climate.Climate;
import com.terraforged.core.world.heightmap.Levels;
import com.terraforged.core.world.terrain.Terrains;
import net.minecraft.block.BlockState;
import net.minecraft.world.chunk.IChunk;
import net.minecraft.world.gen.GenerationSettings;
public class SurfaceContext extends DecoratorContext {
public final long seed;
public final int seaLevel;
public final BlockState solid;
public final BlockState fluid;
public final int seaLevel;
public final long seed;
public final ChunkSurfaceBuffer buffer;
public final CachedSurface cached = new CachedSurface();
public double noise;
public SurfaceContext(IChunk chunk, Levels levels, Terrains terrain, Climate climate, GenerationSettings settings, long seed) {
super(chunk, levels, terrain, climate);
public SurfaceContext(ChunkSurfaceBuffer buffer, Levels levels, Terrains terrain, Climate climate, GenerationSettings settings, long seed) {
super(buffer, levels, terrain, climate);
this.solid = settings.getDefaultBlock();
this.fluid = settings.getDefaultFluid();
this.seed = seed;
this.buffer = buffer;
this.seaLevel = levels.waterLevel;
this.seed = seed;
}
}

View File

@ -124,6 +124,11 @@ public class Cell<T extends Tag> {
void visit(Cell<T> cell, int dx, int dz);
}
public interface ContextVisitor<C, T extends Tag> {
void visit(Cell<T> cell, int dx, int dz, C ctx);
}
public interface ZoomVisitor<T extends Tag> {
void visit(Cell<T> cell, float x, float z);

View File

@ -27,14 +27,15 @@ package com.terraforged.core.region;
import com.terraforged.core.cell.Cell;
import com.terraforged.core.cell.Extent;
import com.terraforged.core.world.decorator.Decorator;
import com.terraforged.core.filter.Filterable;
import com.terraforged.core.region.chunk.ChunkGenTask;
import com.terraforged.core.region.chunk.ChunkReader;
import com.terraforged.core.region.chunk.ChunkWriter;
import com.terraforged.core.region.chunk.ChunkZoomTask;
import com.terraforged.core.util.concurrent.batcher.Batcher;
import com.terraforged.core.world.decorator.Decorator;
import com.terraforged.core.world.heightmap.Heightmap;
import com.terraforged.core.world.river.RiverRegionList;
import com.terraforged.core.world.terrain.Terrain;
import java.util.Collection;
@ -137,6 +138,41 @@ public class Region implements Extent {
}
}
public void generateBase(Heightmap heightmap) {
for (int cz = 0; cz < chunkSize.total; cz++) {
for (int cx = 0; cx < chunkSize.total; cx++) {
int index = chunkSize.indexOf(cx, cz);
GenChunk chunk = computeChunk(index, cx, cz);
for (int dz = 0; dz < 16; dz++) {
for (int dx = 0; dx < 16; dx++) {
float x = chunk.getBlockX() + dx;
float z = chunk.getBlockZ() + dz;
Cell<Terrain> cell = chunk.genCell(dx, dz);
heightmap.applyBase(cell, x, z);
}
}
}
}
}
public void generateRivers(Heightmap heightmap, RiverRegionList rivers) {
for (int cz = 0; cz < chunkSize.total; cz++) {
for (int cx = 0; cx < chunkSize.total; cx++) {
int index = chunkSize.indexOf(cx, cz);
GenChunk chunk = computeChunk(index, cx, cz);
for (int dz = 0; dz < 16; dz++) {
for (int dx = 0; dx < 16; dx++) {
float x = chunk.getBlockX() + dx;
float z = chunk.getBlockZ() + dz;
Cell<Terrain> cell = chunk.genCell(dx, dz);
heightmap.applyRivers(cell, x, z, rivers);
heightmap.applyClimate(cell, x, z);
}
}
}
}
}
public void generate(Heightmap heightmap, Batcher batcher) {
for (int cz = 0; cz < chunkSize.total; cz++) {
for (int cx = 0; cx < chunkSize.total; cx++) {

View File

@ -26,7 +26,8 @@
package com.terraforged.core.region;
import com.terraforged.core.region.chunk.ChunkReader;
import com.terraforged.core.util.Cache;
import com.terraforged.core.util.concurrent.cache.Cache;
import com.terraforged.core.util.concurrent.cache.CacheEntry;
import com.terraforged.core.world.heightmap.RegionExtent;
import me.dags.noise.util.NoiseUtil;
@ -37,13 +38,12 @@ public class RegionCache implements RegionExtent {
private final boolean queuing;
private final RegionGenerator renderer;
private final Cache<Long, CompletableFuture<Region>> cache;
private final ThreadLocal<Region> cachedRegion = new ThreadLocal<>();
private final Cache<Long, CacheEntry<Region>> cache;
public RegionCache(boolean queueNeighbours, RegionGenerator renderer) {
this.renderer = renderer;
this.queuing = queueNeighbours;
this.cache = Cache.concurrent(180, 60, TimeUnit.SECONDS);
this.cache = new Cache<>(80, 60, TimeUnit.SECONDS);
}
@Override
@ -53,13 +53,7 @@ public class RegionCache implements RegionExtent {
@Override
public CompletableFuture<Region> getRegionAsync(int regionX, int regionZ) {
long id = NoiseUtil.seed(regionX, regionZ);
CompletableFuture<Region> future = cache.get(id);
if (future == null) {
future = renderer.getRegionAsync(regionX, regionZ);
cache.put(id, future);
}
return future;
return renderer.generate(regionX, regionZ);
}
@Override
@ -72,28 +66,18 @@ public class RegionCache implements RegionExtent {
@Override
public Region getRegion(int regionX, int regionZ) {
Region cached = cachedRegion.get();
if (cached != null && regionX == cached.getRegionX() && regionZ == cached.getRegionZ()) {
return cached;
}
long id = NoiseUtil.seed(regionX, regionZ);
CompletableFuture<Region> futureRegion = cache.get(id);
if (futureRegion == null) {
cached = renderer.generateRegion(regionX, regionZ);
cache.put(id, CompletableFuture.completedFuture(cached));
} else {
cached = futureRegion.join();
}
Region region = queueRegion(regionX, regionZ).get();
if (queuing) {
queueNeighbours(regionX, regionZ);
}
cachedRegion.set(cached);
return region;
}
return cached;
public CacheEntry<Region> queueRegion(int regionX, int regionZ) {
long id = NoiseUtil.seed(regionX, regionZ);
return cache.computeIfAbsent(id, l -> renderer.generateCached(regionX, regionZ));
}
private void queueNeighbours(int regionX, int regionZ) {
@ -102,7 +86,7 @@ public class RegionCache implements RegionExtent {
if (x == 0 && z == 0) {
continue;
}
getRegionAsync(regionX + x, regionZ + z);
queueRegion(regionX + x, regionZ + z);
}
}
}

View File

@ -26,12 +26,12 @@
package com.terraforged.core.region;
import com.terraforged.core.region.legacy.LegacyRegion;
import com.terraforged.core.util.concurrent.ObjectPool;
import com.terraforged.core.util.concurrent.ThreadPool;
import com.terraforged.core.util.concurrent.batcher.Batcher;
import com.terraforged.core.util.concurrent.cache.CacheEntry;
import com.terraforged.core.world.WorldGenerator;
import com.terraforged.core.world.WorldGeneratorFactory;
import com.terraforged.core.world.heightmap.RegionExtent;
import com.terraforged.core.world.river.RiverRegionList;
import java.util.concurrent.CompletableFuture;
@ -41,14 +41,14 @@ public class RegionGenerator implements RegionExtent {
private final int border;
private final RegionFactory regions;
private final ThreadPool threadPool;
private final ObjectPool<WorldGenerator> genPool;
private final ThreadLocal<WorldGenerator> genPool;
private RegionGenerator(Builder builder) {
this.factor = builder.factor;
this.border = builder.border;
this.threadPool = builder.threadPool;
this.regions = builder.regionFactory;
this.genPool = new ObjectPool<>(50, builder.factory);
this.genPool = ThreadLocal.withInitial(builder.factory);
}
public RegionCache toCache() {
@ -75,24 +75,26 @@ public class RegionGenerator implements RegionExtent {
}
public CompletableFuture<Region> generate(int regionX, int regionZ) {
return CompletableFuture.supplyAsync(() -> generateRegion(regionX, regionZ));
return CompletableFuture.supplyAsync(() -> generateRegion(regionX, regionZ), threadPool);
}
public CompletableFuture<Region> generate(float centerX, float centerZ, float zoom, boolean filter) {
return CompletableFuture.supplyAsync(() -> generateRegion(centerX, centerZ, zoom, filter));
return CompletableFuture.supplyAsync(() -> generateRegion(centerX, centerZ, zoom, filter), threadPool);
}
public CacheEntry<Region> generateCached(int regionX, int regionZ) {
return CacheEntry.supplyAsync(() -> generateRegion(regionX, regionZ), threadPool);
}
public Region generateRegion(int regionX, int regionZ) {
try (ObjectPool.Item<WorldGenerator> item = genPool.get()) {
WorldGenerator generator = item.getValue();
WorldGenerator generator = genPool.get();
Region region = regions.create(regionX, regionZ, factor, border);
try (Batcher batcher = threadPool.batcher(region.getChunkCount())) {
region.generate(generator.getHeightmap(), batcher);
}
RiverRegionList rivers = generator.getHeightmap().getRiverManager().getRivers(region);
region.generateBase(generator.getHeightmap());
region.generateRivers(generator.getHeightmap(), rivers);
postProcess(region, generator);
return region;
}
}
private void postProcess(Region region, WorldGenerator generator) {
generator.getFilters().apply(region);
@ -100,17 +102,20 @@ public class RegionGenerator implements RegionExtent {
}
public Region generateRegion(float centerX, float centerZ, float zoom, boolean filter) {
try (ObjectPool.Item<WorldGenerator> item = genPool.get()) {
WorldGenerator generator = item.getValue();
WorldGenerator generator = genPool.get();
Region region = regions.create(0, 0, factor, border);
try (Batcher batcher = threadPool.batcher(region.getChunkCount())) {
region.generateZoom(generator.getHeightmap(), centerX, centerZ, zoom, batcher);
}
region.check();
float translateX = centerX - ((region.getBlockSize().size * zoom) / 2F);
float translateZ = centerZ - ((region.getBlockSize().size * zoom) / 2F);
region.generate(chunk -> {
chunk.generate((cell, dx, dz) -> {
float x = ((chunk.getBlockX() + dx) * zoom) + translateX;
float z = ((chunk.getBlockZ() + dz) * zoom) + translateZ;
generator.getHeightmap().apply(cell, x, z);
});
});
postProcess(region, generator, centerX, centerZ, zoom, filter);
return region;
}
}
private void postProcess(Region region, WorldGenerator generator, float centerX, float centerZ, float zoom, boolean filter) {
if (filter) {

View File

@ -65,4 +65,12 @@ public interface ChunkReader extends ChunkHolder {
}
}
}
default <C> void iterate(C context, Cell.ContextVisitor<C, Terrain> visitor) {
for (int dz = 0; dz < 16; dz++) {
for (int dx = 0; dx < 16; dx++) {
visitor.visit(getCell(dx, dz), dx, dz, context);
}
}
}
}

View File

@ -1,102 +0,0 @@
/*
*
* MIT License
*
* Copyright (c) 2020 TerraForged
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package com.terraforged.core.util;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;
public class Cache<K, V> {
private final long lifespan;
private final long interval;
private final Map<K, CachedValue<V>> cache;
private long lastUpdate = 0L;
public Cache(long lifespan, long interval, TimeUnit unit) {
this(lifespan, interval, unit, () -> new HashMap<>(100));
}
public Cache(long lifespan, long interval, TimeUnit unit, Supplier<Map<K, CachedValue<V>>> backing) {
this.lifespan = unit.toMillis(lifespan);
this.interval = unit.toMillis(interval);
this.cache = backing.get();
}
public V get(K id) {
update();
CachedValue<V> value = cache.get(id);
if (value != null) {
return value.getValue();
}
return null;
}
public void put(K id, V region) {
update();
cache.put(id, new CachedValue<>(region));
}
public void drop(K id) {
update();
}
public boolean contains(K id) {
return cache.containsKey(id);
}
private void update() {
long time = System.currentTimeMillis();
if (time - lastUpdate < interval) {
return;
}
cache.values().removeIf(value -> time - value.time >= lifespan);
lastUpdate = time;
}
public static class CachedValue<T> {
private final T value;
private long time;
private CachedValue(T value) {
this.value = value;
this.time = System.currentTimeMillis();
}
private T getValue() {
time = System.currentTimeMillis();
return value;
}
}
public static <K, V> Cache<K, V> concurrent(long lifespan, long interval, TimeUnit unit) {
return new Cache<>(lifespan, interval, unit, () -> new ConcurrentHashMap<>(100));
}
}

View File

@ -55,7 +55,7 @@ public class ThreadPool implements Executor {
public ThreadPool(int size) {
this.poolSize = size;
this.service = ThreadPool.createService(size);
this.service = ThreadPool.createService(size, "TerraForged");
}
public void shutdown() {
@ -81,6 +81,12 @@ public class ThreadPool implements Executor {
return new AsyncBatcher(service, size);
}
public static ThreadPool getCurrent() {
synchronized (lock) {
return instance;
}
}
public static ThreadPool getFixed(int size) {
synchronized (lock) {
if (instance.poolSize != size) {
@ -120,17 +126,17 @@ public class ThreadPool implements Executor {
private static int defaultPoolSize() {
int threads = Runtime.getRuntime().availableProcessors();
return Math.max(2, (int) (threads / 3F) * 2);
return Math.max(1, (int) ((threads / 3F) * 2F));
}
private static ExecutorService createService(int size) {
public static ExecutorService createService(int size, String name) {
ThreadPoolExecutor service = new ThreadPoolExecutor(
size,
size,
30,
TimeUnit.SECONDS,
new LinkedBlockingQueue<>(),
new ThreadPoolFactory()
new WorkerFactory(name)
);
service.allowCoreThreadTimeOut(true);
return service;

View File

@ -4,20 +4,22 @@ import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicInteger;
// As DefaultThreadPool but with custom thread names
public class ThreadPoolFactory implements ThreadFactory {
public class WorkerFactory implements ThreadFactory {
private final String prefix;
private final ThreadGroup group;
private final AtomicInteger threadNumber = new AtomicInteger(1);
public ThreadPoolFactory() {
public WorkerFactory(String name) {
group = Thread.currentThread().getThreadGroup();
prefix = name + "-Worker-";
}
@Override
public Thread newThread(Runnable task) {
Thread thread = new Thread(group, task);
thread.setDaemon(true);
thread.setName("TerraForged-Worker-" + threadNumber.getAndIncrement());
thread.setName(prefix + threadNumber.getAndIncrement());
return thread;
}
}

View File

@ -0,0 +1,54 @@
package com.terraforged.core.util.concurrent.cache;
import com.terraforged.core.util.concurrent.ThreadPool;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.function.Supplier;
public class Cache<K, V extends ExpiringEntry> implements Runnable {
private final long expireMS;
private final long intervalMS;
private final Executor executor;
private final Map<K, V> map = new ConcurrentHashMap<>();
private volatile long timestamp = 0L;
public Cache(long expireTime, long interval, TimeUnit unit) {
this.expireMS = unit.toMillis(expireTime);
this.intervalMS = unit.toMillis(interval);
this.executor = ThreadPool.getCurrent();
}
public V computeIfAbsent(K k, Supplier<V> supplier) {
return computeIfAbsent(k, o -> supplier.get());
}
public V computeIfAbsent(K k, Function<K, V> func) {
V v = map.computeIfAbsent(k, func);
queueUpdate();
return v;
}
private void queueUpdate() {
long now = System.currentTimeMillis();
if (now - timestamp > intervalMS) {
timestamp = now;
executor.execute(this);
}
}
@Override
public void run() {
final long now = timestamp;
map.forEach((key, val) -> {
if (now - val.getTimestamp() > expireMS) {
map.remove(key);
}
});
}
}

View File

@ -0,0 +1,37 @@
package com.terraforged.core.util.concurrent.cache;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletionException;
import java.util.concurrent.Executor;
import java.util.concurrent.FutureTask;
public class CacheEntry<T> extends FutureTask<T> implements ExpiringEntry {
private volatile long timestamp;
private CacheEntry(Callable<T> supplier) {
super(supplier);
this.timestamp = System.currentTimeMillis();
}
@Override
public long getTimestamp() {
return timestamp;
}
@Override
public T get() {
try {
this.timestamp = System.currentTimeMillis();
return super.get();
} catch (Throwable t) {
throw new CompletionException(t);
}
}
public static <T> CacheEntry<T> supplyAsync(Callable<T> supplier, Executor executor) {
CacheEntry<T> entry = new CacheEntry<>(supplier);
executor.execute(entry);
return entry;
}
}

View File

@ -0,0 +1,6 @@
package com.terraforged.core.util.concurrent.cache;
public interface ExpiringEntry {
long getTimestamp();
}

View File

@ -31,14 +31,24 @@ import com.terraforged.core.cell.Populator;
import com.terraforged.core.region.Size;
import com.terraforged.core.util.concurrent.ObjectPool;
import com.terraforged.core.world.climate.Climate;
import com.terraforged.core.world.river.RiverManager;
import com.terraforged.core.world.river.RiverRegionList;
import com.terraforged.core.world.terrain.Terrain;
public interface Heightmap extends Populator, Extent {
Climate getClimate();
RiverManager getRiverManager();
void visit(Cell<Terrain> cell, float x, float z);
void applyBase(Cell<Terrain> cell, float x, float z);
void applyRivers(Cell<Terrain> cell, float x, float z, RiverRegionList rivers);
void applyClimate(Cell<Terrain> cell, float x, float z);
@Override
default void visit(int minX, int minZ, int maxX, int maxZ, Cell.Visitor<Terrain> visitor) {
int chunkSize = Size.chunkToBlock(1);

View File

@ -37,6 +37,7 @@ import com.terraforged.core.world.continent.ContinentLerper2;
import com.terraforged.core.world.continent.ContinentLerper3;
import com.terraforged.core.world.continent.ContinentModule;
import com.terraforged.core.world.river.RiverManager;
import com.terraforged.core.world.river.RiverRegionList;
import com.terraforged.core.world.terrain.Terrain;
import com.terraforged.core.world.terrain.TerrainPopulator;
import com.terraforged.core.world.terrain.Terrains;
@ -165,19 +166,29 @@ public class WorldHeightmap implements Heightmap {
@Override
public void apply(Cell<Terrain> cell, float x, float z) {
applyBase(cell, x, z);
applyRivers(cell, x, z, riverManager.getRivers((int) x, (int) z));
applyClimate(cell, x, z);
}
@Override
public void applyBase(Cell<Terrain> cell, float x, float z) {
// initial type
cell.tag = terrain.steppe;
// basic shapes
continentModule.apply(cell, x, z);
regionModule.apply(cell, x, z);
// apply actuall heightmap
// apply actual heightmap
root.apply(cell, x, z);
}
// apply rivers
riverManager.apply(cell, x, z);
@Override
public void applyRivers(Cell<Terrain> cell, float x, float z, RiverRegionList rivers) {
rivers.apply(cell, x, z);
}
@Override
public void applyClimate(Cell<Terrain> cell, float x, float z) {
// apply climate data
if (cell.value <= levels.water) {
climate.apply(cell, x, z, false);
@ -207,6 +218,11 @@ public class WorldHeightmap implements Heightmap {
return climate;
}
@Override
public RiverManager getRiverManager() {
return riverManager;
}
public Populator getPopulator(Terrain terrain) {
return terrainProvider.getPopulator(terrain);
}

View File

@ -26,13 +26,15 @@
package com.terraforged.core.world.river;
import com.terraforged.core.cell.Cell;
import com.terraforged.core.util.Cache;
import com.terraforged.core.region.Region;
import com.terraforged.core.util.concurrent.cache.Cache;
import com.terraforged.core.util.concurrent.cache.CacheEntry;
import com.terraforged.core.world.GeneratorContext;
import com.terraforged.core.world.heightmap.Heightmap;
import com.terraforged.core.world.terrain.Terrain;
import me.dags.noise.util.NoiseUtil;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.TimeUnit;
public class RiverManager {
@ -42,7 +44,7 @@ public class RiverManager {
private final Heightmap heightmap;
private final GeneratorContext context;
private final RiverContext riverContext;
private final Cache<Long, RiverRegion> cache = new Cache<>(60, 60, TimeUnit.SECONDS, () -> new ConcurrentHashMap<>());
private final Cache<Long, CacheEntry<RiverRegion>> cache = new Cache<>(120, 60, TimeUnit.SECONDS);
public RiverManager(Heightmap heightmap, GeneratorContext context) {
RiverConfig primary = RiverConfig.builder(context.levels)
@ -77,6 +79,35 @@ public class RiverManager {
this.riverContext = new RiverContext(context.settings.rivers.riverFrequency, primary, secondary, tertiary, lakes);
}
public RiverRegionList getRivers(Region region) {
return getRivers(region.getBlockX(), region.getBlockZ());
}
public RiverRegionList getRivers(int blockX, int blockZ) {
int rx = RiverRegion.blockToRegion(blockX);
int rz = RiverRegion.blockToRegion(blockZ);
// check which quarter of the region pos (x,y) is in & get the neighbouring regions' relative coords
int qx = blockX < RiverRegion.regionToBlock(rx) + QUAD_SIZE ? -1 : 1;
int qz = blockZ < RiverRegion.regionToBlock(rz) + QUAD_SIZE ? -1 : 1;
// relative positions of neighbouring regions
int minX = Math.min(0, qx);
int minZ = Math.min(0, qz);
int maxX = Math.max(0, qx);
int maxZ = Math.max(0, qz);
RiverRegionList list = new RiverRegionList();
for (int dz = minZ; dz <= maxZ; dz++) {
for (int dx = minX; dx <= maxX; dx++) {
CacheEntry<RiverRegion> entry = getRegion(rx + dx, rz + dz);
list.add(entry);
}
}
return list;
}
public void apply(Cell<Terrain> cell, float x, float z) {
int rx = RiverRegion.blockToRegion((int) x);
int rz = RiverRegion.blockToRegion((int) z);
@ -91,20 +122,32 @@ public class RiverManager {
int maxX = Math.max(0, qx);
int maxZ = Math.max(0, qz);
// queue up the 4 nearest reiver regions
int index = 0;
CacheEntry<RiverRegion>[] entries = new CacheEntry[4];
for (int dz = minZ; dz <= maxZ; dz++) {
for (int dx = minX; dx <= maxX; dx++) {
getRegion(rx + dx, rz + dz).apply(cell, x, z);
entries[index++] = getRegion(rx + dx, rz + dz);
}
}
int count = 0;
while (count < index) {
for (CacheEntry<RiverRegion> entry : entries) {
if (entry.isDone()) {
count++;
entry.get().apply(cell, x, z);
}
}
}
}
private RiverRegion getRegion(int rx, int rz) {
private CacheEntry<RiverRegion> getRegion(int rx, int rz) {
long id = NoiseUtil.seed(rx, rz);
RiverRegion region = cache.get(id);
if (region == null) {
region = new RiverRegion(rx, rz, heightmap, context, riverContext);
cache.put(id, region);
return cache.computeIfAbsent(id, l -> generateRegion(rx, rz));
}
return region;
private CacheEntry<RiverRegion> generateRegion(int rx, int rz) {
return CacheEntry.supplyAsync(() -> new RiverRegion(rx, rz, heightmap, context, riverContext), ForkJoinPool.commonPool());
}
}

View File

@ -27,6 +27,7 @@ package com.terraforged.core.world.river;
import com.terraforged.core.cell.Cell;
import com.terraforged.core.util.concurrent.ObjectPool;
import com.terraforged.core.util.concurrent.cache.ExpiringEntry;
import com.terraforged.core.world.GeneratorContext;
import com.terraforged.core.world.heightmap.Heightmap;
import com.terraforged.core.world.terrain.Terrain;
@ -40,12 +41,14 @@ import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.TimeUnit;
public class RiverRegion {
public class RiverRegion implements ExpiringEntry {
public static final int SCALE = 12;
private static final double MIN_FORK_ANGLE = 20D;
private static final double MAX_FORK_ANGLE = 60D;
private static final long EXPIRE_TIME = TimeUnit.SECONDS.toMillis(60);
private final Domain domain;
private final Terrains terrains;
@ -68,6 +71,8 @@ public class RiverRegion {
private final int forkAttempts;
private final int tertiaryAttempts;
private final long timestamp = System.currentTimeMillis() + EXPIRE_TIME;
public RiverRegion(int regionX, int regionZ, Heightmap heightmap, GeneratorContext context, RiverContext riverContext) {
int seed = new Random(NoiseUtil.seed(regionX, regionZ)).nextInt();
this.lake = riverContext.lakes;
@ -96,6 +101,11 @@ public class RiverRegion {
}
}
@Override
public long getTimestamp() {
return timestamp;
}
public void apply(Cell<Terrain> cell, float x, float z) {
float px = domain.getX(x, z);
float pz = domain.getY(x, z);

View File

@ -0,0 +1,27 @@
package com.terraforged.core.world.river;
import com.terraforged.core.cell.Cell;
import com.terraforged.core.util.concurrent.cache.CacheEntry;
import com.terraforged.core.world.terrain.Terrain;
public class RiverRegionList {
private int index = 0;
private final CacheEntry<RiverRegion>[] regions = new CacheEntry[4];
protected void add(CacheEntry<RiverRegion> entry) {
regions[index++] = entry;
}
public void apply(Cell<Terrain> cell, float x, float z) {
int complete = 0;
while (complete < regions.length) {
for (CacheEntry<RiverRegion> entry : regions) {
if (entry.isDone()) {
complete++;
entry.get().apply(cell, x, z);
}
}
}
}
}

View File

@ -0,0 +1,71 @@
package com.terraforged.mod.chunk;
import com.terraforged.api.chunk.ChunkDelegate;
import net.minecraft.block.BlockState;
import net.minecraft.block.Blocks;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.biome.BiomeContainer;
import net.minecraft.world.chunk.ChunkPrimer;
import net.minecraft.world.chunk.ChunkSection;
import net.minecraft.world.chunk.IChunk;
import net.minecraft.world.gen.Heightmap;
/**
* A ChunkPrimer wrapper that handles setting BlockStates within the chunk & updating heightmaps accordingly
*/
public class FastChunk implements ChunkDelegate {
private final int blockX;
private final int blockZ;
private final ChunkPrimer primer;
private final Heightmap worldSurface;
private final Heightmap oceanSurface;
private final BlockPos.Mutable mutable = new BlockPos.Mutable();
private FastChunk(ChunkPrimer primer) {
this.primer = primer;
this.blockX = primer.getPos().getXStart();
this.blockZ = primer.getPos().getZStart();
this.worldSurface = primer.getHeightmap(Heightmap.Type.WORLD_SURFACE_WG);
this.oceanSurface = primer.getHeightmap(Heightmap.Type.OCEAN_FLOOR_WG);
}
@Override
public IChunk getDelegate() {
return primer;
}
@Override
public BlockState setBlockState(BlockPos pos, BlockState state, boolean falling) {
ChunkSection section = primer.getSection(pos.getY() >> 4);
section.lock();
int dx = pos.getX() & 15;
int dy = pos.getY() & 15;
int dz = pos.getZ() & 15;
BlockState replaced = section.setBlockState(dx, dy, dz, state, false);
if (state.getBlock() != Blocks.AIR) {
mutable.setPos(blockX + dx, pos.getY(), blockZ + dz);
if (state.getLightValue(primer, mutable) != 0) {
primer.addLightPosition(mutable);
}
worldSurface.update(dx, pos.getY(), dz, state);
oceanSurface.update(dx, pos.getY(), dz, state);
}
section.unlock();
return replaced;
}
public void setBiomes(BiomeContainer biomes) {
primer.func_225548_a_(biomes);
}
public static IChunk wrap(IChunk chunk) {
if (chunk instanceof FastChunk) {
return chunk;
}
if (chunk instanceof ChunkPrimer) {
return new FastChunk((ChunkPrimer) chunk);
}
return chunk;
}
}

View File

@ -118,6 +118,11 @@ public class TerraChunkGenerator extends ObfHelperChunkGenerator<GenerationSetti
@Override
public void generateStructures(BiomeManager unused, IChunk chunk, ChunkGenerator<?> generator, TemplateManager templates) {
ChunkPos pos = chunk.getPos();
int regionX = regionCache.chunkToRegion(pos.x);
int regionZ = regionCache.chunkToRegion(pos.z);
// start generating the heightmap as early as possible
regionCache.queueRegion(regionX, regionZ);
super.generateStructures(unused, chunk, this, templates);
}
@ -145,40 +150,41 @@ public class TerraChunkGenerator extends ObfHelperChunkGenerator<GenerationSetti
@Override
public final void generateBase(IWorld world, IChunk chunk) {
DecoratorContext context = new DecoratorContext(chunk, getContext().levels, getContext().terrain, getContext().factory.getClimate());
TerraContainer container = getBiomeContainer(chunk);
container.getChunkReader().iterate((cell, dx, dz) -> {
int px = context.blockX + dx;
int pz = context.blockZ + dz;
DecoratorContext context = new DecoratorContext(FastChunk.wrap(chunk), getContext().levels, getContext().terrain, getContext().factory.getClimate());
container.getChunkReader().iterate(context, (cell, dx, dz, ctx) -> {
int px = ctx.blockX + dx;
int pz = ctx.blockZ + dz;
int py = (int) (cell.value * getMaxHeight());
context.cell = cell;
context.biome = container.getBiome(dx, dz);
ChunkPopulator.INSTANCE.decorate(chunk, context, px, py, pz);
ctx.cell = cell;
ctx.biome = container.getBiome(dx, dz);
ChunkPopulator.INSTANCE.decorate(ctx.chunk, ctx, px, py, pz);
});
terrainHelper.flatten(world, chunk, context.blockX, context.blockZ);
}
@Override
public final void generateSurface(WorldGenRegion world, IChunk chunk) {
ChunkSurfaceBuffer buffer = new ChunkSurfaceBuffer(chunk);
SurfaceContext context = getContext().surface(buffer, getSettings());
TerraContainer container = getBiomeContainer(chunk);
container.getChunkReader().iterate((cell, dx, dz) -> {
int px = context.blockX + dx;
int pz = context.blockZ + dz;
int top = chunk.getTopBlockY(Heightmap.Type.WORLD_SURFACE_WG, dx, dz) + 1;
ChunkSurfaceBuffer buffer = new ChunkSurfaceBuffer(FastChunk.wrap(chunk));
SurfaceContext context = getContext().surface(buffer, getSettings());
buffer.setSurfaceLevel(top);
container.getChunkReader().iterate(context, (cell, dx, dz, ctx) -> {
int px = ctx.blockX + dx;
int pz = ctx.blockZ + dz;
int top = ctx.chunk.getTopBlockY(Heightmap.Type.WORLD_SURFACE_WG, dx, dz) + 1;
context.cell = cell;
context.biome = container.getBiome(dx, dz);
context.noise = getSurfaceNoise(px, pz) * 15D;
ctx.buffer.setSurfaceLevel(top);
getSurfaceManager().getSurface(context).buildSurface(px, pz, top, context);
ctx.cell = cell;
ctx.biome = container.getBiome(dx, dz);
ctx.noise = getSurfaceNoise(px, pz) * 15D;
getSurfaceManager().getSurface(ctx).buildSurface(px, pz, top, ctx);
int py = (int) (cell.value * getMaxHeight());
for (ColumnDecorator processor : getBaseDecorators()) {
processor.decorate(buffer, context, px, py, pz);
processor.decorate(ctx.buffer, ctx, px, py, pz);
}
});
}
@ -214,14 +220,14 @@ public class TerraChunkGenerator extends ObfHelperChunkGenerator<GenerationSetti
@Override
public final void postProcess(ChunkReader chunk, TerraContainer container, DecoratorContext context) {
chunk.iterate((cell, dx, dz) -> {
int px = context.blockX + dx;
int pz = context.blockZ + dz;
int py = context.chunk.getTopBlockY(Heightmap.Type.WORLD_SURFACE_WG, dx, dz);
context.cell = cell;
context.biome = container.getBiome(dx, dz);
chunk.iterate(context, (cell, dx, dz, ctx) -> {
int px = ctx.blockX + dx;
int pz = ctx.blockZ + dz;
int py = ctx.chunk.getTopBlockY(Heightmap.Type.WORLD_SURFACE_WG, dx, dz);
ctx.cell = cell;
ctx.biome = container.getBiome(dx, dz);
for (ColumnDecorator decorator : getPostProcessors()) {
decorator.decorate(context.chunk, context, px, py, pz);
decorator.decorate(ctx.chunk, ctx, px, py, pz);
}
});
}
@ -292,6 +298,8 @@ public class TerraChunkGenerator extends ObfHelperChunkGenerator<GenerationSetti
TerraContainer container = getBiomeProvider().createBiomeContainer(view);
if (chunk instanceof ChunkPrimer) {
((ChunkPrimer) chunk).func_225548_a_(container);
} else if (chunk instanceof FastChunk) {
((FastChunk) chunk).setBiomes(container);
}
return container;
@ -306,8 +314,12 @@ public class TerraChunkGenerator extends ObfHelperChunkGenerator<GenerationSetti
modifiers = new FeatureModifiers();
}
// block ugly features
if (context.terraSettings.features.strataDecorator) {
// block stone blobs if strata enabled
modifiers.getPredicates().add(Matchers.stoneBlobs(), FeaturePredicate.DENY);
}
// block ugly features
modifiers.getPredicates().add(Matchers.sedimentDisks(), FeaturePredicate.DENY);
modifiers.getPredicates().add(FeatureMatcher.of(Feature.LAKE), FeaturePredicate.DENY);
modifiers.getPredicates().add(FeatureMatcher.of(Feature.SPRING_FEATURE), FeaturePredicate.DENY);

View File

@ -26,6 +26,7 @@
package com.terraforged.mod.chunk;
import com.terraforged.api.chunk.column.DecoratorContext;
import com.terraforged.api.chunk.surface.ChunkSurfaceBuffer;
import com.terraforged.api.chunk.surface.SurfaceContext;
import com.terraforged.core.world.GeneratorContext;
import com.terraforged.core.world.WorldGeneratorFactory;
@ -62,7 +63,7 @@ public class TerraContext extends GeneratorContext {
return new DecoratorContext(chunk, levels, terrain, factory.getClimate());
}
public SurfaceContext surface(IChunk chunk, GenerationSettings settings) {
return new SurfaceContext(chunk, levels, terrain, factory.getClimate(), settings, world.getSeed());
public SurfaceContext surface(ChunkSurfaceBuffer buffer, GenerationSettings settings) {
return new SurfaceContext(buffer, levels, terrain, factory.getClimate(), settings, world.getSeed());
}
}

View File

@ -17,13 +17,73 @@
"config": {
"features": [
{
"name": "terraforged:brown_mushroom",
"config": {},
"name": "minecraft:huge_brown_mushroom",
"config": {
"cap_provider": {
"type": "minecraft:simple_state_provider",
"state": {
"Name": "minecraft:brown_mushroom_block",
"Properties": {
"east": "true",
"south": "true",
"north": "true",
"west": "true",
"up": "true",
"down": "false"
}
}
},
"stem_provider": {
"type": "minecraft:simple_state_provider",
"state": {
"Name": "minecraft:mushroom_stem",
"Properties": {
"east": "true",
"south": "true",
"north": "true",
"west": "true",
"up": "false",
"down": "false"
}
}
},
"foliage_radius": 3
},
"chance": 0.025
},
{
"name": "terraforged:red_mushroom",
"config": {},
"name": "minecraft:huge_red_mushroom",
"config": {
"cap_provider": {
"type": "minecraft:simple_state_provider",
"state": {
"Name": "minecraft:red_mushroom_block",
"Properties": {
"east": "true",
"south": "true",
"north": "true",
"west": "true",
"up": "true",
"down": "false"
}
}
},
"stem_provider": {
"type": "minecraft:simple_state_provider",
"state": {
"Name": "minecraft:mushroom_stem",
"Properties": {
"east": "true",
"south": "true",
"north": "true",
"west": "true",
"up": "false",
"down": "false"
}
}
},
"foliage_radius": 2
},
"chance": 0.05
},
{