- 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:
parent
f7f1c17779
commit
eea88bd0e2
@ -30,24 +30,25 @@ import com.terraforged.core.world.climate.Climate;
|
|||||||
import com.terraforged.core.world.heightmap.Levels;
|
import com.terraforged.core.world.heightmap.Levels;
|
||||||
import com.terraforged.core.world.terrain.Terrains;
|
import com.terraforged.core.world.terrain.Terrains;
|
||||||
import net.minecraft.block.BlockState;
|
import net.minecraft.block.BlockState;
|
||||||
import net.minecraft.world.chunk.IChunk;
|
|
||||||
import net.minecraft.world.gen.GenerationSettings;
|
import net.minecraft.world.gen.GenerationSettings;
|
||||||
|
|
||||||
public class SurfaceContext extends DecoratorContext {
|
public class SurfaceContext extends DecoratorContext {
|
||||||
|
|
||||||
|
public final long seed;
|
||||||
|
public final int seaLevel;
|
||||||
public final BlockState solid;
|
public final BlockState solid;
|
||||||
public final BlockState fluid;
|
public final BlockState fluid;
|
||||||
public final int seaLevel;
|
public final ChunkSurfaceBuffer buffer;
|
||||||
public final long seed;
|
|
||||||
public final CachedSurface cached = new CachedSurface();
|
public final CachedSurface cached = new CachedSurface();
|
||||||
|
|
||||||
public double noise;
|
public double noise;
|
||||||
|
|
||||||
public SurfaceContext(IChunk chunk, Levels levels, Terrains terrain, Climate climate, GenerationSettings settings, long seed) {
|
public SurfaceContext(ChunkSurfaceBuffer buffer, Levels levels, Terrains terrain, Climate climate, GenerationSettings settings, long seed) {
|
||||||
super(chunk, levels, terrain, climate);
|
super(buffer, levels, terrain, climate);
|
||||||
this.solid = settings.getDefaultBlock();
|
this.solid = settings.getDefaultBlock();
|
||||||
this.fluid = settings.getDefaultFluid();
|
this.fluid = settings.getDefaultFluid();
|
||||||
this.seed = seed;
|
this.buffer = buffer;
|
||||||
this.seaLevel = levels.waterLevel;
|
this.seaLevel = levels.waterLevel;
|
||||||
|
this.seed = seed;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -124,6 +124,11 @@ public class Cell<T extends Tag> {
|
|||||||
void visit(Cell<T> cell, int dx, int dz);
|
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> {
|
public interface ZoomVisitor<T extends Tag> {
|
||||||
|
|
||||||
void visit(Cell<T> cell, float x, float z);
|
void visit(Cell<T> cell, float x, float z);
|
||||||
|
@ -27,14 +27,15 @@ package com.terraforged.core.region;
|
|||||||
|
|
||||||
import com.terraforged.core.cell.Cell;
|
import com.terraforged.core.cell.Cell;
|
||||||
import com.terraforged.core.cell.Extent;
|
import com.terraforged.core.cell.Extent;
|
||||||
import com.terraforged.core.world.decorator.Decorator;
|
|
||||||
import com.terraforged.core.filter.Filterable;
|
import com.terraforged.core.filter.Filterable;
|
||||||
import com.terraforged.core.region.chunk.ChunkGenTask;
|
import com.terraforged.core.region.chunk.ChunkGenTask;
|
||||||
import com.terraforged.core.region.chunk.ChunkReader;
|
import com.terraforged.core.region.chunk.ChunkReader;
|
||||||
import com.terraforged.core.region.chunk.ChunkWriter;
|
import com.terraforged.core.region.chunk.ChunkWriter;
|
||||||
import com.terraforged.core.region.chunk.ChunkZoomTask;
|
import com.terraforged.core.region.chunk.ChunkZoomTask;
|
||||||
import com.terraforged.core.util.concurrent.batcher.Batcher;
|
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.heightmap.Heightmap;
|
||||||
|
import com.terraforged.core.world.river.RiverRegionList;
|
||||||
import com.terraforged.core.world.terrain.Terrain;
|
import com.terraforged.core.world.terrain.Terrain;
|
||||||
|
|
||||||
import java.util.Collection;
|
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) {
|
public void generate(Heightmap heightmap, Batcher batcher) {
|
||||||
for (int cz = 0; cz < chunkSize.total; cz++) {
|
for (int cz = 0; cz < chunkSize.total; cz++) {
|
||||||
for (int cx = 0; cx < chunkSize.total; cx++) {
|
for (int cx = 0; cx < chunkSize.total; cx++) {
|
||||||
|
@ -26,7 +26,8 @@
|
|||||||
package com.terraforged.core.region;
|
package com.terraforged.core.region;
|
||||||
|
|
||||||
import com.terraforged.core.region.chunk.ChunkReader;
|
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 com.terraforged.core.world.heightmap.RegionExtent;
|
||||||
import me.dags.noise.util.NoiseUtil;
|
import me.dags.noise.util.NoiseUtil;
|
||||||
|
|
||||||
@ -37,13 +38,12 @@ public class RegionCache implements RegionExtent {
|
|||||||
|
|
||||||
private final boolean queuing;
|
private final boolean queuing;
|
||||||
private final RegionGenerator renderer;
|
private final RegionGenerator renderer;
|
||||||
private final Cache<Long, CompletableFuture<Region>> cache;
|
private final Cache<Long, CacheEntry<Region>> cache;
|
||||||
private final ThreadLocal<Region> cachedRegion = new ThreadLocal<>();
|
|
||||||
|
|
||||||
public RegionCache(boolean queueNeighbours, RegionGenerator renderer) {
|
public RegionCache(boolean queueNeighbours, RegionGenerator renderer) {
|
||||||
this.renderer = renderer;
|
this.renderer = renderer;
|
||||||
this.queuing = queueNeighbours;
|
this.queuing = queueNeighbours;
|
||||||
this.cache = Cache.concurrent(180, 60, TimeUnit.SECONDS);
|
this.cache = new Cache<>(80, 60, TimeUnit.SECONDS);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -53,13 +53,7 @@ public class RegionCache implements RegionExtent {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CompletableFuture<Region> getRegionAsync(int regionX, int regionZ) {
|
public CompletableFuture<Region> getRegionAsync(int regionX, int regionZ) {
|
||||||
long id = NoiseUtil.seed(regionX, regionZ);
|
return renderer.generate(regionX, regionZ);
|
||||||
CompletableFuture<Region> future = cache.get(id);
|
|
||||||
if (future == null) {
|
|
||||||
future = renderer.getRegionAsync(regionX, regionZ);
|
|
||||||
cache.put(id, future);
|
|
||||||
}
|
|
||||||
return future;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -72,28 +66,18 @@ public class RegionCache implements RegionExtent {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Region getRegion(int regionX, int regionZ) {
|
public Region getRegion(int regionX, int regionZ) {
|
||||||
Region cached = cachedRegion.get();
|
Region region = queueRegion(regionX, regionZ).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();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (queuing) {
|
if (queuing) {
|
||||||
queueNeighbours(regionX, regionZ);
|
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) {
|
private void queueNeighbours(int regionX, int regionZ) {
|
||||||
@ -102,7 +86,7 @@ public class RegionCache implements RegionExtent {
|
|||||||
if (x == 0 && z == 0) {
|
if (x == 0 && z == 0) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
getRegionAsync(regionX + x, regionZ + z);
|
queueRegion(regionX + x, regionZ + z);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -26,12 +26,12 @@
|
|||||||
package com.terraforged.core.region;
|
package com.terraforged.core.region;
|
||||||
|
|
||||||
import com.terraforged.core.region.legacy.LegacyRegion;
|
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.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.WorldGenerator;
|
||||||
import com.terraforged.core.world.WorldGeneratorFactory;
|
import com.terraforged.core.world.WorldGeneratorFactory;
|
||||||
import com.terraforged.core.world.heightmap.RegionExtent;
|
import com.terraforged.core.world.heightmap.RegionExtent;
|
||||||
|
import com.terraforged.core.world.river.RiverRegionList;
|
||||||
|
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
|
||||||
@ -41,14 +41,14 @@ public class RegionGenerator implements RegionExtent {
|
|||||||
private final int border;
|
private final int border;
|
||||||
private final RegionFactory regions;
|
private final RegionFactory regions;
|
||||||
private final ThreadPool threadPool;
|
private final ThreadPool threadPool;
|
||||||
private final ObjectPool<WorldGenerator> genPool;
|
private final ThreadLocal<WorldGenerator> genPool;
|
||||||
|
|
||||||
private RegionGenerator(Builder builder) {
|
private RegionGenerator(Builder builder) {
|
||||||
this.factor = builder.factor;
|
this.factor = builder.factor;
|
||||||
this.border = builder.border;
|
this.border = builder.border;
|
||||||
this.threadPool = builder.threadPool;
|
this.threadPool = builder.threadPool;
|
||||||
this.regions = builder.regionFactory;
|
this.regions = builder.regionFactory;
|
||||||
this.genPool = new ObjectPool<>(50, builder.factory);
|
this.genPool = ThreadLocal.withInitial(builder.factory);
|
||||||
}
|
}
|
||||||
|
|
||||||
public RegionCache toCache() {
|
public RegionCache toCache() {
|
||||||
@ -75,23 +75,25 @@ public class RegionGenerator implements RegionExtent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public CompletableFuture<Region> generate(int regionX, int regionZ) {
|
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) {
|
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) {
|
public Region generateRegion(int regionX, int regionZ) {
|
||||||
try (ObjectPool.Item<WorldGenerator> item = genPool.get()) {
|
WorldGenerator generator = genPool.get();
|
||||||
WorldGenerator generator = item.getValue();
|
Region region = regions.create(regionX, regionZ, factor, border);
|
||||||
Region region = regions.create(regionX, regionZ, factor, border);
|
RiverRegionList rivers = generator.getHeightmap().getRiverManager().getRivers(region);
|
||||||
try (Batcher batcher = threadPool.batcher(region.getChunkCount())) {
|
region.generateBase(generator.getHeightmap());
|
||||||
region.generate(generator.getHeightmap(), batcher);
|
region.generateRivers(generator.getHeightmap(), rivers);
|
||||||
}
|
postProcess(region, generator);
|
||||||
postProcess(region, generator);
|
return region;
|
||||||
return region;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void postProcess(Region region, WorldGenerator generator) {
|
private void postProcess(Region region, WorldGenerator generator) {
|
||||||
@ -100,16 +102,19 @@ public class RegionGenerator implements RegionExtent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public Region generateRegion(float centerX, float centerZ, float zoom, boolean filter) {
|
public Region generateRegion(float centerX, float centerZ, float zoom, boolean filter) {
|
||||||
try (ObjectPool.Item<WorldGenerator> item = genPool.get()) {
|
WorldGenerator generator = genPool.get();
|
||||||
WorldGenerator generator = item.getValue();
|
Region region = regions.create(0, 0, factor, border);
|
||||||
Region region = regions.create(0, 0, factor, border);
|
float translateX = centerX - ((region.getBlockSize().size * zoom) / 2F);
|
||||||
try (Batcher batcher = threadPool.batcher(region.getChunkCount())) {
|
float translateZ = centerZ - ((region.getBlockSize().size * zoom) / 2F);
|
||||||
region.generateZoom(generator.getHeightmap(), centerX, centerZ, zoom, batcher);
|
region.generate(chunk -> {
|
||||||
}
|
chunk.generate((cell, dx, dz) -> {
|
||||||
region.check();
|
float x = ((chunk.getBlockX() + dx) * zoom) + translateX;
|
||||||
postProcess(region, generator, centerX, centerZ, zoom, filter);
|
float z = ((chunk.getBlockZ() + dz) * zoom) + translateZ;
|
||||||
return region;
|
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) {
|
private void postProcess(Region region, WorldGenerator generator, float centerX, float centerZ, float zoom, boolean filter) {
|
||||||
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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));
|
|
||||||
}
|
|
||||||
}
|
|
@ -55,7 +55,7 @@ public class ThreadPool implements Executor {
|
|||||||
|
|
||||||
public ThreadPool(int size) {
|
public ThreadPool(int size) {
|
||||||
this.poolSize = size;
|
this.poolSize = size;
|
||||||
this.service = ThreadPool.createService(size);
|
this.service = ThreadPool.createService(size, "TerraForged");
|
||||||
}
|
}
|
||||||
|
|
||||||
public void shutdown() {
|
public void shutdown() {
|
||||||
@ -81,6 +81,12 @@ public class ThreadPool implements Executor {
|
|||||||
return new AsyncBatcher(service, size);
|
return new AsyncBatcher(service, size);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static ThreadPool getCurrent() {
|
||||||
|
synchronized (lock) {
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static ThreadPool getFixed(int size) {
|
public static ThreadPool getFixed(int size) {
|
||||||
synchronized (lock) {
|
synchronized (lock) {
|
||||||
if (instance.poolSize != size) {
|
if (instance.poolSize != size) {
|
||||||
@ -120,17 +126,17 @@ public class ThreadPool implements Executor {
|
|||||||
|
|
||||||
private static int defaultPoolSize() {
|
private static int defaultPoolSize() {
|
||||||
int threads = Runtime.getRuntime().availableProcessors();
|
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(
|
ThreadPoolExecutor service = new ThreadPoolExecutor(
|
||||||
size,
|
size,
|
||||||
size,
|
size,
|
||||||
30,
|
30,
|
||||||
TimeUnit.SECONDS,
|
TimeUnit.SECONDS,
|
||||||
new LinkedBlockingQueue<>(),
|
new LinkedBlockingQueue<>(),
|
||||||
new ThreadPoolFactory()
|
new WorkerFactory(name)
|
||||||
);
|
);
|
||||||
service.allowCoreThreadTimeOut(true);
|
service.allowCoreThreadTimeOut(true);
|
||||||
return service;
|
return service;
|
||||||
|
@ -4,20 +4,22 @@ import java.util.concurrent.ThreadFactory;
|
|||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
// As DefaultThreadPool but with custom thread names
|
// 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 ThreadGroup group;
|
||||||
private final AtomicInteger threadNumber = new AtomicInteger(1);
|
private final AtomicInteger threadNumber = new AtomicInteger(1);
|
||||||
|
|
||||||
public ThreadPoolFactory() {
|
public WorkerFactory(String name) {
|
||||||
group = Thread.currentThread().getThreadGroup();
|
group = Thread.currentThread().getThreadGroup();
|
||||||
|
prefix = name + "-Worker-";
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Thread newThread(Runnable task) {
|
public Thread newThread(Runnable task) {
|
||||||
Thread thread = new Thread(group, task);
|
Thread thread = new Thread(group, task);
|
||||||
thread.setDaemon(true);
|
thread.setDaemon(true);
|
||||||
thread.setName("TerraForged-Worker-" + threadNumber.getAndIncrement());
|
thread.setName(prefix + threadNumber.getAndIncrement());
|
||||||
return thread;
|
return thread;
|
||||||
}
|
}
|
||||||
}
|
}
|
54
TerraForgedCore/src/main/java/com/terraforged/core/util/concurrent/cache/Cache.java
vendored
Normal file
54
TerraForgedCore/src/main/java/com/terraforged/core/util/concurrent/cache/Cache.java
vendored
Normal 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);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
37
TerraForgedCore/src/main/java/com/terraforged/core/util/concurrent/cache/CacheEntry.java
vendored
Normal file
37
TerraForgedCore/src/main/java/com/terraforged/core/util/concurrent/cache/CacheEntry.java
vendored
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
6
TerraForgedCore/src/main/java/com/terraforged/core/util/concurrent/cache/ExpiringEntry.java
vendored
Normal file
6
TerraForgedCore/src/main/java/com/terraforged/core/util/concurrent/cache/ExpiringEntry.java
vendored
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
package com.terraforged.core.util.concurrent.cache;
|
||||||
|
|
||||||
|
public interface ExpiringEntry {
|
||||||
|
|
||||||
|
long getTimestamp();
|
||||||
|
}
|
@ -31,14 +31,24 @@ import com.terraforged.core.cell.Populator;
|
|||||||
import com.terraforged.core.region.Size;
|
import com.terraforged.core.region.Size;
|
||||||
import com.terraforged.core.util.concurrent.ObjectPool;
|
import com.terraforged.core.util.concurrent.ObjectPool;
|
||||||
import com.terraforged.core.world.climate.Climate;
|
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;
|
import com.terraforged.core.world.terrain.Terrain;
|
||||||
|
|
||||||
public interface Heightmap extends Populator, Extent {
|
public interface Heightmap extends Populator, Extent {
|
||||||
|
|
||||||
Climate getClimate();
|
Climate getClimate();
|
||||||
|
|
||||||
|
RiverManager getRiverManager();
|
||||||
|
|
||||||
void visit(Cell<Terrain> cell, float x, float z);
|
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
|
@Override
|
||||||
default void visit(int minX, int minZ, int maxX, int maxZ, Cell.Visitor<Terrain> visitor) {
|
default void visit(int minX, int minZ, int maxX, int maxZ, Cell.Visitor<Terrain> visitor) {
|
||||||
int chunkSize = Size.chunkToBlock(1);
|
int chunkSize = Size.chunkToBlock(1);
|
||||||
|
@ -37,6 +37,7 @@ import com.terraforged.core.world.continent.ContinentLerper2;
|
|||||||
import com.terraforged.core.world.continent.ContinentLerper3;
|
import com.terraforged.core.world.continent.ContinentLerper3;
|
||||||
import com.terraforged.core.world.continent.ContinentModule;
|
import com.terraforged.core.world.continent.ContinentModule;
|
||||||
import com.terraforged.core.world.river.RiverManager;
|
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.Terrain;
|
||||||
import com.terraforged.core.world.terrain.TerrainPopulator;
|
import com.terraforged.core.world.terrain.TerrainPopulator;
|
||||||
import com.terraforged.core.world.terrain.Terrains;
|
import com.terraforged.core.world.terrain.Terrains;
|
||||||
@ -165,19 +166,29 @@ public class WorldHeightmap implements Heightmap {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void apply(Cell<Terrain> cell, float x, float z) {
|
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
|
// initial type
|
||||||
cell.tag = terrain.steppe;
|
cell.tag = terrain.steppe;
|
||||||
|
|
||||||
// basic shapes
|
// basic shapes
|
||||||
continentModule.apply(cell, x, z);
|
continentModule.apply(cell, x, z);
|
||||||
regionModule.apply(cell, x, z);
|
regionModule.apply(cell, x, z);
|
||||||
|
// apply actual heightmap
|
||||||
// apply actuall heightmap
|
|
||||||
root.apply(cell, x, z);
|
root.apply(cell, x, z);
|
||||||
|
}
|
||||||
|
|
||||||
// apply rivers
|
@Override
|
||||||
riverManager.apply(cell, x, z);
|
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
|
// apply climate data
|
||||||
if (cell.value <= levels.water) {
|
if (cell.value <= levels.water) {
|
||||||
climate.apply(cell, x, z, false);
|
climate.apply(cell, x, z, false);
|
||||||
@ -207,6 +218,11 @@ public class WorldHeightmap implements Heightmap {
|
|||||||
return climate;
|
return climate;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RiverManager getRiverManager() {
|
||||||
|
return riverManager;
|
||||||
|
}
|
||||||
|
|
||||||
public Populator getPopulator(Terrain terrain) {
|
public Populator getPopulator(Terrain terrain) {
|
||||||
return terrainProvider.getPopulator(terrain);
|
return terrainProvider.getPopulator(terrain);
|
||||||
}
|
}
|
||||||
|
@ -26,13 +26,15 @@
|
|||||||
package com.terraforged.core.world.river;
|
package com.terraforged.core.world.river;
|
||||||
|
|
||||||
import com.terraforged.core.cell.Cell;
|
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.GeneratorContext;
|
||||||
import com.terraforged.core.world.heightmap.Heightmap;
|
import com.terraforged.core.world.heightmap.Heightmap;
|
||||||
import com.terraforged.core.world.terrain.Terrain;
|
import com.terraforged.core.world.terrain.Terrain;
|
||||||
import me.dags.noise.util.NoiseUtil;
|
import me.dags.noise.util.NoiseUtil;
|
||||||
|
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ForkJoinPool;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
public class RiverManager {
|
public class RiverManager {
|
||||||
@ -42,7 +44,7 @@ public class RiverManager {
|
|||||||
private final Heightmap heightmap;
|
private final Heightmap heightmap;
|
||||||
private final GeneratorContext context;
|
private final GeneratorContext context;
|
||||||
private final RiverContext riverContext;
|
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) {
|
public RiverManager(Heightmap heightmap, GeneratorContext context) {
|
||||||
RiverConfig primary = RiverConfig.builder(context.levels)
|
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);
|
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) {
|
public void apply(Cell<Terrain> cell, float x, float z) {
|
||||||
int rx = RiverRegion.blockToRegion((int) x);
|
int rx = RiverRegion.blockToRegion((int) x);
|
||||||
int rz = RiverRegion.blockToRegion((int) z);
|
int rz = RiverRegion.blockToRegion((int) z);
|
||||||
@ -91,20 +122,32 @@ public class RiverManager {
|
|||||||
int maxX = Math.max(0, qx);
|
int maxX = Math.max(0, qx);
|
||||||
int maxZ = Math.max(0, qz);
|
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 dz = minZ; dz <= maxZ; dz++) {
|
||||||
for (int dx = minX; dx <= maxX; dx++) {
|
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);
|
long id = NoiseUtil.seed(rx, rz);
|
||||||
RiverRegion region = cache.get(id);
|
return cache.computeIfAbsent(id, l -> generateRegion(rx, rz));
|
||||||
if (region == null) {
|
}
|
||||||
region = new RiverRegion(rx, rz, heightmap, context, riverContext);
|
|
||||||
cache.put(id, region);
|
private CacheEntry<RiverRegion> generateRegion(int rx, int rz) {
|
||||||
}
|
return CacheEntry.supplyAsync(() -> new RiverRegion(rx, rz, heightmap, context, riverContext), ForkJoinPool.commonPool());
|
||||||
return region;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -27,6 +27,7 @@ package com.terraforged.core.world.river;
|
|||||||
|
|
||||||
import com.terraforged.core.cell.Cell;
|
import com.terraforged.core.cell.Cell;
|
||||||
import com.terraforged.core.util.concurrent.ObjectPool;
|
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.GeneratorContext;
|
||||||
import com.terraforged.core.world.heightmap.Heightmap;
|
import com.terraforged.core.world.heightmap.Heightmap;
|
||||||
import com.terraforged.core.world.terrain.Terrain;
|
import com.terraforged.core.world.terrain.Terrain;
|
||||||
@ -40,12 +41,14 @@ import java.util.Collections;
|
|||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
public class RiverRegion {
|
public class RiverRegion implements ExpiringEntry {
|
||||||
|
|
||||||
public static final int SCALE = 12;
|
public static final int SCALE = 12;
|
||||||
private static final double MIN_FORK_ANGLE = 20D;
|
private static final double MIN_FORK_ANGLE = 20D;
|
||||||
private static final double MAX_FORK_ANGLE = 60D;
|
private static final double MAX_FORK_ANGLE = 60D;
|
||||||
|
private static final long EXPIRE_TIME = TimeUnit.SECONDS.toMillis(60);
|
||||||
|
|
||||||
private final Domain domain;
|
private final Domain domain;
|
||||||
private final Terrains terrains;
|
private final Terrains terrains;
|
||||||
@ -68,6 +71,8 @@ public class RiverRegion {
|
|||||||
private final int forkAttempts;
|
private final int forkAttempts;
|
||||||
private final int tertiaryAttempts;
|
private final int tertiaryAttempts;
|
||||||
|
|
||||||
|
private final long timestamp = System.currentTimeMillis() + EXPIRE_TIME;
|
||||||
|
|
||||||
public RiverRegion(int regionX, int regionZ, Heightmap heightmap, GeneratorContext context, RiverContext riverContext) {
|
public RiverRegion(int regionX, int regionZ, Heightmap heightmap, GeneratorContext context, RiverContext riverContext) {
|
||||||
int seed = new Random(NoiseUtil.seed(regionX, regionZ)).nextInt();
|
int seed = new Random(NoiseUtil.seed(regionX, regionZ)).nextInt();
|
||||||
this.lake = riverContext.lakes;
|
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) {
|
public void apply(Cell<Terrain> cell, float x, float z) {
|
||||||
float px = domain.getX(x, z);
|
float px = domain.getX(x, z);
|
||||||
float pz = domain.getY(x, z);
|
float pz = domain.getY(x, z);
|
||||||
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -118,6 +118,11 @@ public class TerraChunkGenerator extends ObfHelperChunkGenerator<GenerationSetti
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void generateStructures(BiomeManager unused, IChunk chunk, ChunkGenerator<?> generator, TemplateManager templates) {
|
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);
|
super.generateStructures(unused, chunk, this, templates);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -145,40 +150,41 @@ public class TerraChunkGenerator extends ObfHelperChunkGenerator<GenerationSetti
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final void generateBase(IWorld world, IChunk chunk) {
|
public final void generateBase(IWorld world, IChunk chunk) {
|
||||||
DecoratorContext context = new DecoratorContext(chunk, getContext().levels, getContext().terrain, getContext().factory.getClimate());
|
|
||||||
TerraContainer container = getBiomeContainer(chunk);
|
TerraContainer container = getBiomeContainer(chunk);
|
||||||
container.getChunkReader().iterate((cell, dx, dz) -> {
|
DecoratorContext context = new DecoratorContext(FastChunk.wrap(chunk), getContext().levels, getContext().terrain, getContext().factory.getClimate());
|
||||||
int px = context.blockX + dx;
|
container.getChunkReader().iterate(context, (cell, dx, dz, ctx) -> {
|
||||||
int pz = context.blockZ + dz;
|
int px = ctx.blockX + dx;
|
||||||
|
int pz = ctx.blockZ + dz;
|
||||||
int py = (int) (cell.value * getMaxHeight());
|
int py = (int) (cell.value * getMaxHeight());
|
||||||
context.cell = cell;
|
ctx.cell = cell;
|
||||||
context.biome = container.getBiome(dx, dz);
|
ctx.biome = container.getBiome(dx, dz);
|
||||||
ChunkPopulator.INSTANCE.decorate(chunk, context, px, py, pz);
|
ChunkPopulator.INSTANCE.decorate(ctx.chunk, ctx, px, py, pz);
|
||||||
});
|
});
|
||||||
terrainHelper.flatten(world, chunk, context.blockX, context.blockZ);
|
terrainHelper.flatten(world, chunk, context.blockX, context.blockZ);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final void generateSurface(WorldGenRegion world, IChunk chunk) {
|
public final void generateSurface(WorldGenRegion world, IChunk chunk) {
|
||||||
ChunkSurfaceBuffer buffer = new ChunkSurfaceBuffer(chunk);
|
|
||||||
SurfaceContext context = getContext().surface(buffer, getSettings());
|
|
||||||
TerraContainer container = getBiomeContainer(chunk);
|
TerraContainer container = getBiomeContainer(chunk);
|
||||||
container.getChunkReader().iterate((cell, dx, dz) -> {
|
ChunkSurfaceBuffer buffer = new ChunkSurfaceBuffer(FastChunk.wrap(chunk));
|
||||||
int px = context.blockX + dx;
|
SurfaceContext context = getContext().surface(buffer, getSettings());
|
||||||
int pz = context.blockZ + dz;
|
|
||||||
int top = chunk.getTopBlockY(Heightmap.Type.WORLD_SURFACE_WG, dx, dz) + 1;
|
|
||||||
|
|
||||||
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;
|
ctx.buffer.setSurfaceLevel(top);
|
||||||
context.biome = container.getBiome(dx, dz);
|
|
||||||
context.noise = getSurfaceNoise(px, pz) * 15D;
|
|
||||||
|
|
||||||
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());
|
int py = (int) (cell.value * getMaxHeight());
|
||||||
for (ColumnDecorator processor : getBaseDecorators()) {
|
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
|
@Override
|
||||||
public final void postProcess(ChunkReader chunk, TerraContainer container, DecoratorContext context) {
|
public final void postProcess(ChunkReader chunk, TerraContainer container, DecoratorContext context) {
|
||||||
chunk.iterate((cell, dx, dz) -> {
|
chunk.iterate(context, (cell, dx, dz, ctx) -> {
|
||||||
int px = context.blockX + dx;
|
int px = ctx.blockX + dx;
|
||||||
int pz = context.blockZ + dz;
|
int pz = ctx.blockZ + dz;
|
||||||
int py = context.chunk.getTopBlockY(Heightmap.Type.WORLD_SURFACE_WG, dx, dz);
|
int py = ctx.chunk.getTopBlockY(Heightmap.Type.WORLD_SURFACE_WG, dx, dz);
|
||||||
context.cell = cell;
|
ctx.cell = cell;
|
||||||
context.biome = container.getBiome(dx, dz);
|
ctx.biome = container.getBiome(dx, dz);
|
||||||
for (ColumnDecorator decorator : getPostProcessors()) {
|
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);
|
TerraContainer container = getBiomeProvider().createBiomeContainer(view);
|
||||||
if (chunk instanceof ChunkPrimer) {
|
if (chunk instanceof ChunkPrimer) {
|
||||||
((ChunkPrimer) chunk).func_225548_a_(container);
|
((ChunkPrimer) chunk).func_225548_a_(container);
|
||||||
|
} else if (chunk instanceof FastChunk) {
|
||||||
|
((FastChunk) chunk).setBiomes(container);
|
||||||
}
|
}
|
||||||
|
|
||||||
return container;
|
return container;
|
||||||
@ -306,8 +314,12 @@ public class TerraChunkGenerator extends ObfHelperChunkGenerator<GenerationSetti
|
|||||||
modifiers = new FeatureModifiers();
|
modifiers = new FeatureModifiers();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (context.terraSettings.features.strataDecorator) {
|
||||||
|
// block stone blobs if strata enabled
|
||||||
|
modifiers.getPredicates().add(Matchers.stoneBlobs(), FeaturePredicate.DENY);
|
||||||
|
}
|
||||||
|
|
||||||
// block ugly features
|
// block ugly features
|
||||||
modifiers.getPredicates().add(Matchers.stoneBlobs(), FeaturePredicate.DENY);
|
|
||||||
modifiers.getPredicates().add(Matchers.sedimentDisks(), FeaturePredicate.DENY);
|
modifiers.getPredicates().add(Matchers.sedimentDisks(), FeaturePredicate.DENY);
|
||||||
modifiers.getPredicates().add(FeatureMatcher.of(Feature.LAKE), FeaturePredicate.DENY);
|
modifiers.getPredicates().add(FeatureMatcher.of(Feature.LAKE), FeaturePredicate.DENY);
|
||||||
modifiers.getPredicates().add(FeatureMatcher.of(Feature.SPRING_FEATURE), FeaturePredicate.DENY);
|
modifiers.getPredicates().add(FeatureMatcher.of(Feature.SPRING_FEATURE), FeaturePredicate.DENY);
|
||||||
|
@ -26,6 +26,7 @@
|
|||||||
package com.terraforged.mod.chunk;
|
package com.terraforged.mod.chunk;
|
||||||
|
|
||||||
import com.terraforged.api.chunk.column.DecoratorContext;
|
import com.terraforged.api.chunk.column.DecoratorContext;
|
||||||
|
import com.terraforged.api.chunk.surface.ChunkSurfaceBuffer;
|
||||||
import com.terraforged.api.chunk.surface.SurfaceContext;
|
import com.terraforged.api.chunk.surface.SurfaceContext;
|
||||||
import com.terraforged.core.world.GeneratorContext;
|
import com.terraforged.core.world.GeneratorContext;
|
||||||
import com.terraforged.core.world.WorldGeneratorFactory;
|
import com.terraforged.core.world.WorldGeneratorFactory;
|
||||||
@ -62,7 +63,7 @@ public class TerraContext extends GeneratorContext {
|
|||||||
return new DecoratorContext(chunk, levels, terrain, factory.getClimate());
|
return new DecoratorContext(chunk, levels, terrain, factory.getClimate());
|
||||||
}
|
}
|
||||||
|
|
||||||
public SurfaceContext surface(IChunk chunk, GenerationSettings settings) {
|
public SurfaceContext surface(ChunkSurfaceBuffer buffer, GenerationSettings settings) {
|
||||||
return new SurfaceContext(chunk, levels, terrain, factory.getClimate(), settings, world.getSeed());
|
return new SurfaceContext(buffer, levels, terrain, factory.getClimate(), settings, world.getSeed());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,13 +17,73 @@
|
|||||||
"config": {
|
"config": {
|
||||||
"features": [
|
"features": [
|
||||||
{
|
{
|
||||||
"name": "terraforged:brown_mushroom",
|
"name": "minecraft:huge_brown_mushroom",
|
||||||
"config": {},
|
"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
|
"chance": 0.025
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "terraforged:red_mushroom",
|
"name": "minecraft:huge_red_mushroom",
|
||||||
"config": {},
|
"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
|
"chance": 0.05
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
Loading…
Reference in New Issue
Block a user