- simplified ThreadPool further

- dispose heightmap Region when all chunks have been visited
- reduce allocations caused by the depth buffer in strata gen
- added temporary server props fix
This commit is contained in:
dags- 2020-03-20 13:28:12 +00:00
parent 1c045f91e7
commit f0d8cf3a76
20 changed files with 200 additions and 146 deletions

View File

@ -28,6 +28,7 @@ package com.terraforged.api.chunk.column;
import com.terraforged.api.chunk.ChunkContext;
import com.terraforged.core.cell.Cell;
import com.terraforged.core.world.climate.Climate;
import com.terraforged.core.world.geology.DepthBuffer;
import com.terraforged.core.world.heightmap.Levels;
import com.terraforged.core.world.terrain.Terrain;
import com.terraforged.core.world.terrain.Terrains;
@ -40,6 +41,7 @@ public class DecoratorContext extends ChunkContext {
public final Levels levels;
public final Climate climate;
public final Terrains terrains;
public final DepthBuffer depthBuffer = new DepthBuffer();
public final BlockPos.Mutable pos = new BlockPos.Mutable();
public Biome biome;

View File

@ -56,7 +56,7 @@ public class Cache {
this.context = new GeneratorContext(terrain, settings);
this.renderer = RegionGenerator.builder()
.factory(new WorldGeneratorFactory(context))
.pool(ThreadPool.getCommon())
.pool(ThreadPool.getPool())
.size(3, 2)
.build();
}

View File

@ -30,15 +30,18 @@ import com.terraforged.core.cell.Extent;
import com.terraforged.core.filter.Filterable;
import com.terraforged.core.region.chunk.ChunkReader;
import com.terraforged.core.region.chunk.ChunkWriter;
import com.terraforged.core.util.concurrent.Disposable;
import com.terraforged.core.world.decorator.Decorator;
import com.terraforged.core.world.heightmap.Heightmap;
import com.terraforged.core.world.rivermap.RiverRegionList;
import com.terraforged.core.world.terrain.Terrain;
import me.dags.noise.util.NoiseUtil;
import java.util.Collection;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
public class Region implements Extent {
public class Region implements Extent, Disposable {
private final int regionX;
private final int regionZ;
@ -51,8 +54,15 @@ public class Region implements Extent {
private final Size chunkSize;
private final GenCell[] blocks;
private final GenChunk[] chunks;
private final int disposableChunks;
private final Disposable.Listener<Region> disposalListener;
private final AtomicInteger disposedChunks = new AtomicInteger();
public Region(int regionX, int regionZ, int size, int borderChunks) {
this(regionX, regionZ, size, borderChunks, region -> {});
}
public Region(int regionX, int regionZ, int size, int borderChunks, Disposable.Listener<Region> disposalListener) {
this.regionX = regionX;
this.regionZ = regionZ;
this.chunkX = regionX << size;
@ -62,10 +72,25 @@ public class Region implements Extent {
this.border = borderChunks;
this.chunkSize = Size.chunks(size, borderChunks);
this.blockSize = Size.blocks(size, borderChunks);
this.disposalListener = disposalListener;
this.disposableChunks = chunkSize.size * chunkSize.size;
this.blocks = new GenCell[blockSize.total * blockSize.total];
this.chunks = new GenChunk[chunkSize.total * chunkSize.total];
}
@Override
public void dispose() {
int disposed = disposedChunks.incrementAndGet();
if (disposed < disposableChunks) {
return;
}
disposalListener.onDispose(this);
}
public long getRegionId() {
return NoiseUtil.seed(getRegionX(), getRegionZ());
}
public int getRegionX() {
return regionX;
}
@ -333,6 +358,11 @@ public class Region implements Extent {
return blockZ;
}
@Override
public void dispose() {
Region.this.dispose();
}
@Override
public Cell<Terrain> getCell(int blockX, int blockZ) {
int relX = regionBlockX + (blockX & 15);

View File

@ -26,6 +26,7 @@
package com.terraforged.core.region;
import com.terraforged.core.region.chunk.ChunkReader;
import com.terraforged.core.util.concurrent.Disposable;
import com.terraforged.core.util.concurrent.cache.Cache;
import com.terraforged.core.util.concurrent.cache.CacheEntry;
import com.terraforged.core.world.heightmap.RegionExtent;
@ -34,32 +35,37 @@ import me.dags.noise.util.NoiseUtil;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
public class RegionCache implements RegionExtent {
public class RegionCache implements RegionExtent, Disposable.Listener<Region> {
private final boolean queuing;
private final RegionGenerator renderer;
private final RegionGenerator generator;
private final Cache<CacheEntry<Region>> cache;
public RegionCache(boolean queueNeighbours, RegionGenerator renderer) {
this.renderer = renderer;
public RegionCache(boolean queueNeighbours, RegionGenerator generator) {
this.queuing = queueNeighbours;
this.cache = new Cache<>(80, 60, TimeUnit.SECONDS);
this.generator = new RegionGenerator(generator, this);
this.cache = new Cache<>(60, 30, TimeUnit.SECONDS);
}
@Override
public void onDispose(Region region) {
cache.remove(region.getRegionId());
}
@Override
public int chunkToRegion(int coord) {
return renderer.chunkToRegion(coord);
return generator.chunkToRegion(coord);
}
@Override
public CompletableFuture<Region> getRegionAsync(int regionX, int regionZ) {
return renderer.generate(regionX, regionZ);
return generator.generate(regionX, regionZ);
}
@Override
public ChunkReader getChunk(int chunkX, int chunkZ) {
int regionX = renderer.chunkToRegion(chunkX);
int regionZ = renderer.chunkToRegion(chunkZ);
int regionX = generator.chunkToRegion(chunkX);
int regionZ = generator.chunkToRegion(chunkZ);
Region region = getRegion(regionX, regionZ);
return region.getChunk(chunkX, chunkZ);
}
@ -77,7 +83,7 @@ public class RegionCache implements RegionExtent {
public CacheEntry<Region> queueRegion(int regionX, int regionZ) {
long id = NoiseUtil.seed(regionX, regionZ);
return cache.computeIfAbsent(id, l -> renderer.generateCached(regionX, regionZ));
return cache.computeIfAbsent(id, l -> generator.generateCached(regionX, regionZ));
}
private void queueNeighbours(int regionX, int regionZ) {

View File

@ -1,6 +1,8 @@
package com.terraforged.core.region;
import com.terraforged.core.util.concurrent.Disposable;
public interface RegionFactory {
Region create(int regionX, int regionZ, int size, int borderChunks);
Region create(int regionX, int regionZ, int size, int borderChunks, Disposable.Listener<Region> listener);
}

View File

@ -26,6 +26,7 @@
package com.terraforged.core.region;
import com.terraforged.core.region.legacy.LegacyRegion;
import com.terraforged.core.util.concurrent.Disposable;
import com.terraforged.core.util.concurrent.ThreadPool;
import com.terraforged.core.util.concurrent.cache.CacheEntry;
import com.terraforged.core.world.WorldGenerator;
@ -42,6 +43,7 @@ public class RegionGenerator implements RegionExtent {
private final RegionFactory regions;
private final ThreadPool threadPool;
private final ThreadLocal<WorldGenerator> genPool;
private final Disposable.Listener<Region> disposalListener;
private RegionGenerator(Builder builder) {
this.factor = builder.factor;
@ -49,6 +51,16 @@ public class RegionGenerator implements RegionExtent {
this.threadPool = builder.threadPool;
this.regions = builder.regionFactory;
this.genPool = ThreadLocal.withInitial(builder.factory);
this.disposalListener = region -> {};
}
protected RegionGenerator(RegionGenerator from, Disposable.Listener<Region> listener) {
this.factor = from.factor;
this.border = from.border;
this.threadPool = from.threadPool;
this.regions = from.regions;
this.genPool = from.genPool;
this.disposalListener = listener;
}
public RegionCache toCache() {
@ -88,7 +100,7 @@ public class RegionGenerator implements RegionExtent {
public Region generateRegion(int regionX, int regionZ) {
WorldGenerator generator = genPool.get();
Region region = regions.create(regionX, regionZ, factor, border);
Region region = regions.create(regionX, regionZ, factor, border, disposalListener);
RiverRegionList rivers = generator.getHeightmap().getRiverMap().getRivers(region);
region.generateBase(generator.getHeightmap());
region.generateRivers(generator.getHeightmap(), rivers);
@ -103,7 +115,7 @@ public class RegionGenerator implements RegionExtent {
public Region generateRegion(float centerX, float centerZ, float zoom, boolean filter) {
WorldGenerator generator = genPool.get();
Region region = regions.create(0, 0, factor, border);
Region region = regions.create(0, 0, factor, border, disposalListener);
region.generateZoom(generator.getHeightmap(), centerX, centerZ, zoom);
postProcess(region, generator, centerX, centerZ, zoom, filter);
return region;

View File

@ -26,8 +26,9 @@
package com.terraforged.core.region.chunk;
import com.terraforged.core.cell.Extent;
import com.terraforged.core.util.concurrent.Disposable;
public interface ChunkHolder extends Extent {
public interface ChunkHolder extends Extent, Disposable {
int getChunkX();

View File

@ -1,6 +1,7 @@
package com.terraforged.core.region.legacy;
import com.terraforged.core.region.Region;
import com.terraforged.core.util.concurrent.Disposable;
/**
* This is here to provide compatibility for versions 0.0.2 and below which contained a
@ -9,8 +10,8 @@ import com.terraforged.core.region.Region;
*/
public class LegacyRegion extends Region {
public LegacyRegion(int regionX, int regionZ, int size, int borderChunks) {
super(regionX, regionZ, size, borderChunks);
public LegacyRegion(int regionX, int regionZ, int size, int borderChunks, Disposable.Listener<Region> listener) {
super(regionX, regionZ, size, borderChunks, listener);
}
/**

View File

@ -0,0 +1,11 @@
package com.terraforged.core.util.concurrent;
public interface Disposable {
void dispose();
interface Listener<T> {
void onDispose(T t);
}
}

View File

@ -33,42 +33,20 @@ import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.Future;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class ThreadPool implements Executor {
public static final int DEFAULT_POOL_SIZE = defaultPoolSize();
private static final ThreadPool instance = new ThreadPool("TerraForged", defaultPoolSize());
private static final Object lock = new Object();
private static final ForkJoinPool defaultPool = ThreadPool.createPool(2, "TFCore");
private static ThreadPool instance = new ThreadPool(defaultPoolSize());
private final int poolSize;
private final ExecutorService service;
private ThreadPool() {
this.service = defaultPool;
this.poolSize = -1;
}
public ThreadPool(int size) {
this.poolSize = size;
this.service = ThreadPool.createExecutor(size, "TerraForged");
}
private ThreadPool(ForkJoinPool pool) {
this.service = pool;
this.poolSize = pool.getPoolSize();
private ThreadPool(String name, int size) {
this.service = ThreadPool.createPool(size, name);
}
public void shutdown() {
if (poolSize > 0) {
service.shutdown();
}
}
@Override
public void execute(Runnable command) {
@ -87,51 +65,16 @@ public class ThreadPool implements Executor {
return new AsyncBatcher(service, size);
}
public static ThreadPool getCurrent() {
synchronized (lock) {
public static ThreadPool getPool() {
return instance;
}
}
public static ThreadPool getFixed(int size) {
synchronized (lock) {
if (instance.poolSize != size) {
instance.shutdown();
instance = new ThreadPool(size);
}
return instance;
}
}
public static ThreadPool getFixed() {
synchronized (lock) {
if (instance.poolSize == -1) {
instance = new ThreadPool(ThreadPool.DEFAULT_POOL_SIZE);
}
return instance;
}
}
public static ThreadPool getCommon() {
synchronized (lock) {
if (instance.poolSize != -1) {
instance.shutdown();
instance = new ThreadPool();
}
return instance;
}
}
public static ForkJoinPool getDefaultPool() {
return defaultPool;
public static ThreadPool create(int size) {
return new ThreadPool("Pool", Math.max(1, size));
}
public static void shutdownCurrent() {
synchronized (lock) {
instance.shutdown();
// replace with the common pool
instance = new ThreadPool();
}
}
private static int defaultPoolSize() {
@ -139,19 +82,6 @@ public class ThreadPool implements Executor {
return Math.max(1, (int) ((threads / 3F) * 2F));
}
public static ExecutorService createExecutor(int size, String name) {
ThreadPoolExecutor service = new ThreadPoolExecutor(
size,
size,
30,
TimeUnit.SECONDS,
new LinkedBlockingQueue<>(),
new WorkerFactory(name)
);
service.allowCoreThreadTimeOut(true);
return service;
}
public static ForkJoinPool createPool(int size, String name) {
return new ForkJoinPool(size, new WorkerFactory.ForkJoin(name), null, true);
}

View File

@ -4,7 +4,6 @@ import com.terraforged.core.util.concurrent.ThreadPool;
import java.util.concurrent.TimeUnit;
import java.util.function.LongFunction;
import java.util.function.Supplier;
public class Cache<V extends ExpiringEntry> implements Runnable {
@ -20,8 +19,8 @@ public class Cache<V extends ExpiringEntry> implements Runnable {
this.map = new SynchronizedLongMap<>(100);
}
public V computeIfAbsent(long key, Supplier<V> supplier) {
return computeIfAbsent(key, o -> supplier.get());
public void remove(long key) {
map.remove(key);
}
public V computeIfAbsent(long key, LongFunction<V> func) {
@ -34,7 +33,7 @@ public class Cache<V extends ExpiringEntry> implements Runnable {
long now = System.currentTimeMillis();
if (now - timestamp > intervalMS) {
timestamp = now;
ThreadPool.getDefaultPool().execute(this);
ThreadPool.getPool().execute(this);
}
}

View File

@ -0,0 +1,31 @@
package com.terraforged.core.world.geology;
public class DepthBuffer {
private float sum;
private float[] buffer;
public void init(int size) {
sum = 0F;
if (buffer == null || buffer.length < size) {
buffer = new float[size];
}
}
public float getSum() {
return sum;
}
public float get(int index) {
return buffer[index];
}
public float getDepth(int index) {
return buffer[index] / sum;
}
public void set(int index, float value) {
sum += value;
buffer[index] = value;
}
}

View File

@ -44,14 +44,19 @@ public class Strata<T> {
this.heightMod = heightMod;
}
public boolean downwards(final int x, final int y, final int z, Stratum.Visitor<T> visitor) {
DepthBuffer depthBuffer = new DepthBuffer(strata, x, z);
public boolean downwards(final int x, final int y, final int z, final Stratum.Visitor<T> visitor) {
DepthBuffer buffer = new DepthBuffer();
initBuffer(buffer, x, z);
return downwards(x, y, z, buffer, visitor);
}
public boolean downwards(final int x, final int y, final int z, final DepthBuffer buffer, Stratum.Visitor<T> visitor) {
initBuffer(buffer, x, z);
int py = y;
T last = null;
float sum = depthBuffer.sum;
for (int i = 0; i < strata.size(); i++) {
float depth = depthBuffer.buffer[i] / sum;
float depth = buffer.get(i);
int height = NoiseUtil.round(depth * y);
T value = strata.get(i).getValue();
last = value;
@ -76,45 +81,15 @@ public class Strata<T> {
return true;
}
public boolean upwards(int x, int y, int z, Stratum.Visitor<T> visitor) {
DepthBuffer depthBuffer = new DepthBuffer(strata, x, z);
int py = 0;
float sum = depthBuffer.sum;
for (int i = strata.size() - 1; i > 0; i--) {
float depth = depthBuffer.buffer[i] / sum;
int height = NoiseUtil.round(depth * y);
T value = strata.get(i).getValue();
for (int dy = 0; dy < height; dy++) {
boolean cont = visitor.visit(py, value);
if (!cont) {
return false;
}
if (++py > y) {
return false;
}
}
}
return true;
}
private int getYOffset(int x, int z) {
return (int) (64 * heightMod.getValue(x, z));
}
private static class DepthBuffer {
private final float sum;
private final float[] buffer;
private <T> DepthBuffer(List<Stratum<T>> strata, int x, int z) {
buffer = new float[strata.size()];
float sum = 0F;
private void initBuffer(DepthBuffer buffer, int x, int z) {
buffer.init(strata.size());
for (int i = 0; i < strata.size(); i++) {
float depth = strata.get(i).getDepth(x, z);
sum += depth;
buffer[i] = depth;
}
this.sum = sum;
buffer.set(i, depth);
}
}

View File

@ -151,6 +151,6 @@ public class RiverMap {
}
private CacheEntry<RiverRegion> generateRegion(int rx, int rz) {
return CacheEntry.supplyAsync(() -> new RiverRegion(rx, rz, heightmap, context, riverMapConfig), ThreadPool.getDefaultPool());
return CacheEntry.supplyAsync(() -> new RiverRegion(rx, rz, heightmap, context, riverMapConfig), ThreadPool.getPool());
}
}

View File

@ -55,7 +55,7 @@ public class FastChunk implements ChunkDelegate {
section.unlock();
return replaced;
}
return Blocks.AIR.getDefaultState();
return Blocks.VOID_AIR.getDefaultState();
}
public void setBiomes(BiomeContainer biomes) {

View File

@ -216,6 +216,9 @@ public class TerraChunkGenerator extends ObfHelperChunkGenerator<GenerationSetti
// bake biome array & discard gen data
((ChunkPrimer) chunk).func_225548_a_(container.bakeBiomes(Environment.isVanillaBiomes()));
// marks the heightmap data for this chunk for removal
container.getChunkReader().dispose();
}
@Override
@ -383,7 +386,7 @@ public class TerraChunkGenerator extends ObfHelperChunkGenerator<GenerationSetti
protected RegionCache createRegionCache(TerraContext context) {
return RegionGenerator.builder()
.legacy(context.terraSettings.version == 0)
.pool(ThreadPool.getFixed())
.pool(ThreadPool.getPool())
.factory(context.factory)
.size(3, 2)
.build()

View File

@ -52,7 +52,7 @@ public class TestChunkGenerator extends TerraChunkGenerator {
protected RegionCache createRegionCache(TerraContext context) {
return RegionGenerator.builder()
.factory(new WorldGeneratorFactory(context, new TestHeightMap(context)))
.pool(ThreadPool.getFixed())
.pool(ThreadPool.getPool())
.size(3, 2)
.build()
.toCache(false);

View File

@ -47,7 +47,7 @@ public class GeologyDecorator implements ColumnDecorator {
@Override
public void decorate(ChunkSurfaceBuffer buffer, DecoratorContext context, int x, int y, int z) {
int top = buffer.getSurfaceBottom();
geology.getGeology(context.biome).getStrata(x, z).downwards(x, top, z, (py, state) -> {
geology.getGeology(context.biome).getStrata(x, z).downwards(x, top, z, context.depthBuffer, (py, state) -> {
context.pos.setPos(x, py, z);
buffer.getDelegate().setBlockState(context.pos, state, false);
return true;

View File

@ -165,7 +165,7 @@ public class Preview extends Button {
RegionGenerator renderer = RegionGenerator.builder()
.factory(new WorldGeneratorFactory(context))
.pool(ThreadPool.getCommon())
.pool(ThreadPool.getPool())
.size(FACTOR, 0)
.build();

View File

@ -0,0 +1,51 @@
package com.terraforged.mod.server;
import net.minecraft.server.ServerPropertiesProvider;
import net.minecraft.server.dedicated.DedicatedServer;
import net.minecraft.server.dedicated.PropertyManager;
import net.minecraft.server.dedicated.ServerProperties;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.event.lifecycle.FMLDedicatedServerSetupEvent;
import java.lang.reflect.Field;
import java.util.Optional;
import java.util.Properties;
@Mod.EventBusSubscriber(value = Dist.DEDICATED_SERVER, bus = Mod.EventBusSubscriber.Bus.MOD)
public class ServerPropertiesFix {
@SubscribeEvent
public static void setup(FMLDedicatedServerSetupEvent event) {
DedicatedServer server = event.getServerSupplier().get();
get(server, DedicatedServer.class, ServerPropertiesProvider.class).ifPresent(provider -> provider.func_219033_a(props -> {
return get(props, PropertyManager.class, Properties.class).flatMap(properties -> {
String world = properties.getProperty("mod-level-type");
if (world != null && !world.isEmpty()) {
properties.setProperty("level-type", world);
return Optional.of(new ServerProperties(properties));
}
return Optional.empty();
}).orElse(props);
}));
}
private static <T> Optional<T> get(Object owner, Class<?> target, Class<T> type) {
for (Field field : target.getDeclaredFields()) {
if (field.getType() == type) {
try {
field.setAccessible(true);
Object value = field.get(owner);
if (value != null) {
return Optional.of(type.cast(value));
}
} catch (IllegalAccessException e) {
e.printStackTrace();
break;
}
}
}
return Optional.empty();
}
}