From eea88bd0e27f93f041255279f0078f3405ce8a73 Mon Sep 17 00:00:00 2001 From: dags- Date: Wed, 18 Mar 2020 18:12:50 +0000 Subject: [PATCH] - improved performance of chunk-gen operations - optimized caching for heightmap & rivermap regions - hopefully fixed some deadlock issues - reverted custom mushrooms for now --- .../api/chunk/surface/SurfaceContext.java | 13 +-- .../java/com/terraforged/core/cell/Cell.java | 5 + .../com/terraforged/core/region/Region.java | 38 ++++++- .../terraforged/core/region/RegionCache.java | 40 +++---- .../core/region/RegionGenerator.java | 55 +++++----- .../core/region/chunk/ChunkReader.java | 8 ++ .../java/com/terraforged/core/util/Cache.java | 102 ------------------ .../core/util/concurrent/ThreadPool.java | 14 ++- ...eadPoolFactory.java => WorkerFactory.java} | 8 +- .../core/util/concurrent/cache/Cache.java | 54 ++++++++++ .../util/concurrent/cache/CacheEntry.java | 37 +++++++ .../util/concurrent/cache/ExpiringEntry.java | 6 ++ .../core/world/heightmap/Heightmap.java | 10 ++ .../core/world/heightmap/WorldHeightmap.java | 26 ++++- .../core/world/river/RiverManager.java | 65 +++++++++-- .../core/world/river/RiverRegion.java | 12 ++- .../core/world/river/RiverRegionList.java | 27 +++++ .../com/terraforged/mod/chunk/FastChunk.java | 71 ++++++++++++ .../mod/chunk/TerraChunkGenerator.java | 66 +++++++----- .../terraforged/mod/chunk/TerraContext.java | 5 +- .../terraforged/features/trees/dark_oak.json | 68 +++++++++++- 21 files changed, 511 insertions(+), 219 deletions(-) delete mode 100644 TerraForgedCore/src/main/java/com/terraforged/core/util/Cache.java rename TerraForgedCore/src/main/java/com/terraforged/core/util/concurrent/{ThreadPoolFactory.java => WorkerFactory.java} (70%) create mode 100644 TerraForgedCore/src/main/java/com/terraforged/core/util/concurrent/cache/Cache.java create mode 100644 TerraForgedCore/src/main/java/com/terraforged/core/util/concurrent/cache/CacheEntry.java create mode 100644 TerraForgedCore/src/main/java/com/terraforged/core/util/concurrent/cache/ExpiringEntry.java create mode 100644 TerraForgedCore/src/main/java/com/terraforged/core/world/river/RiverRegionList.java create mode 100644 TerraForgedMod/src/main/java/com/terraforged/mod/chunk/FastChunk.java diff --git a/TerraForgedAPI/src/main/java/com/terraforged/api/chunk/surface/SurfaceContext.java b/TerraForgedAPI/src/main/java/com/terraforged/api/chunk/surface/SurfaceContext.java index ff6e9cf..923351a 100644 --- a/TerraForgedAPI/src/main/java/com/terraforged/api/chunk/surface/SurfaceContext.java +++ b/TerraForgedAPI/src/main/java/com/terraforged/api/chunk/surface/SurfaceContext.java @@ -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; } } diff --git a/TerraForgedCore/src/main/java/com/terraforged/core/cell/Cell.java b/TerraForgedCore/src/main/java/com/terraforged/core/cell/Cell.java index b672304..f1b99fe 100644 --- a/TerraForgedCore/src/main/java/com/terraforged/core/cell/Cell.java +++ b/TerraForgedCore/src/main/java/com/terraforged/core/cell/Cell.java @@ -124,6 +124,11 @@ public class Cell { void visit(Cell cell, int dx, int dz); } + public interface ContextVisitor { + + void visit(Cell cell, int dx, int dz, C ctx); + } + public interface ZoomVisitor { void visit(Cell cell, float x, float z); diff --git a/TerraForgedCore/src/main/java/com/terraforged/core/region/Region.java b/TerraForgedCore/src/main/java/com/terraforged/core/region/Region.java index c5998c8..6a071bf 100644 --- a/TerraForgedCore/src/main/java/com/terraforged/core/region/Region.java +++ b/TerraForgedCore/src/main/java/com/terraforged/core/region/Region.java @@ -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 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 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++) { diff --git a/TerraForgedCore/src/main/java/com/terraforged/core/region/RegionCache.java b/TerraForgedCore/src/main/java/com/terraforged/core/region/RegionCache.java index 5320537..5aadeb2 100644 --- a/TerraForgedCore/src/main/java/com/terraforged/core/region/RegionCache.java +++ b/TerraForgedCore/src/main/java/com/terraforged/core/region/RegionCache.java @@ -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> cache; - private final ThreadLocal cachedRegion = new ThreadLocal<>(); + private final Cache> 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 getRegionAsync(int regionX, int regionZ) { - long id = NoiseUtil.seed(regionX, regionZ); - CompletableFuture 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 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 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); } } } diff --git a/TerraForgedCore/src/main/java/com/terraforged/core/region/RegionGenerator.java b/TerraForgedCore/src/main/java/com/terraforged/core/region/RegionGenerator.java index efb736a..5cf914c 100644 --- a/TerraForgedCore/src/main/java/com/terraforged/core/region/RegionGenerator.java +++ b/TerraForgedCore/src/main/java/com/terraforged/core/region/RegionGenerator.java @@ -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 genPool; + private final ThreadLocal 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,23 +75,25 @@ public class RegionGenerator implements RegionExtent { } public CompletableFuture generate(int regionX, int regionZ) { - return CompletableFuture.supplyAsync(() -> generateRegion(regionX, regionZ)); + return CompletableFuture.supplyAsync(() -> generateRegion(regionX, regionZ), threadPool); } public CompletableFuture 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 generateCached(int regionX, int regionZ) { + return CacheEntry.supplyAsync(() -> generateRegion(regionX, regionZ), threadPool); } public Region generateRegion(int regionX, int regionZ) { - try (ObjectPool.Item item = genPool.get()) { - WorldGenerator generator = item.getValue(); - Region region = regions.create(regionX, regionZ, factor, border); - try (Batcher batcher = threadPool.batcher(region.getChunkCount())) { - region.generate(generator.getHeightmap(), batcher); - } - postProcess(region, generator); - return region; - } + WorldGenerator generator = genPool.get(); + Region region = regions.create(regionX, regionZ, factor, border); + 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) { @@ -100,16 +102,19 @@ public class RegionGenerator implements RegionExtent { } public Region generateRegion(float centerX, float centerZ, float zoom, boolean filter) { - try (ObjectPool.Item item = genPool.get()) { - WorldGenerator generator = item.getValue(); - 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(); - postProcess(region, generator, centerX, centerZ, zoom, filter); - return region; - } + WorldGenerator generator = genPool.get(); + Region region = regions.create(0, 0, factor, border); + 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) { diff --git a/TerraForgedCore/src/main/java/com/terraforged/core/region/chunk/ChunkReader.java b/TerraForgedCore/src/main/java/com/terraforged/core/region/chunk/ChunkReader.java index 8e71e04..11e39b8 100644 --- a/TerraForgedCore/src/main/java/com/terraforged/core/region/chunk/ChunkReader.java +++ b/TerraForgedCore/src/main/java/com/terraforged/core/region/chunk/ChunkReader.java @@ -65,4 +65,12 @@ public interface ChunkReader extends ChunkHolder { } } } + + default void iterate(C context, Cell.ContextVisitor visitor) { + for (int dz = 0; dz < 16; dz++) { + for (int dx = 0; dx < 16; dx++) { + visitor.visit(getCell(dx, dz), dx, dz, context); + } + } + } } diff --git a/TerraForgedCore/src/main/java/com/terraforged/core/util/Cache.java b/TerraForgedCore/src/main/java/com/terraforged/core/util/Cache.java deleted file mode 100644 index 172b7ad..0000000 --- a/TerraForgedCore/src/main/java/com/terraforged/core/util/Cache.java +++ /dev/null @@ -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 { - - private final long lifespan; - private final long interval; - private final Map> 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>> backing) { - this.lifespan = unit.toMillis(lifespan); - this.interval = unit.toMillis(interval); - this.cache = backing.get(); - } - - public V get(K id) { - update(); - CachedValue 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 { - - 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 Cache concurrent(long lifespan, long interval, TimeUnit unit) { - return new Cache<>(lifespan, interval, unit, () -> new ConcurrentHashMap<>(100)); - } -} diff --git a/TerraForgedCore/src/main/java/com/terraforged/core/util/concurrent/ThreadPool.java b/TerraForgedCore/src/main/java/com/terraforged/core/util/concurrent/ThreadPool.java index e12bcf5..fab1fe7 100644 --- a/TerraForgedCore/src/main/java/com/terraforged/core/util/concurrent/ThreadPool.java +++ b/TerraForgedCore/src/main/java/com/terraforged/core/util/concurrent/ThreadPool.java @@ -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; diff --git a/TerraForgedCore/src/main/java/com/terraforged/core/util/concurrent/ThreadPoolFactory.java b/TerraForgedCore/src/main/java/com/terraforged/core/util/concurrent/WorkerFactory.java similarity index 70% rename from TerraForgedCore/src/main/java/com/terraforged/core/util/concurrent/ThreadPoolFactory.java rename to TerraForgedCore/src/main/java/com/terraforged/core/util/concurrent/WorkerFactory.java index 3b501b8..fe5e925 100644 --- a/TerraForgedCore/src/main/java/com/terraforged/core/util/concurrent/ThreadPoolFactory.java +++ b/TerraForgedCore/src/main/java/com/terraforged/core/util/concurrent/WorkerFactory.java @@ -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; } } diff --git a/TerraForgedCore/src/main/java/com/terraforged/core/util/concurrent/cache/Cache.java b/TerraForgedCore/src/main/java/com/terraforged/core/util/concurrent/cache/Cache.java new file mode 100644 index 0000000..c2aa989 --- /dev/null +++ b/TerraForgedCore/src/main/java/com/terraforged/core/util/concurrent/cache/Cache.java @@ -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 implements Runnable { + + private final long expireMS; + private final long intervalMS; + private final Executor executor; + private final Map 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 supplier) { + return computeIfAbsent(k, o -> supplier.get()); + } + + public V computeIfAbsent(K k, Function 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); + } + }); + } +} diff --git a/TerraForgedCore/src/main/java/com/terraforged/core/util/concurrent/cache/CacheEntry.java b/TerraForgedCore/src/main/java/com/terraforged/core/util/concurrent/cache/CacheEntry.java new file mode 100644 index 0000000..9e806c6 --- /dev/null +++ b/TerraForgedCore/src/main/java/com/terraforged/core/util/concurrent/cache/CacheEntry.java @@ -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 extends FutureTask implements ExpiringEntry { + + private volatile long timestamp; + + private CacheEntry(Callable 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 CacheEntry supplyAsync(Callable supplier, Executor executor) { + CacheEntry entry = new CacheEntry<>(supplier); + executor.execute(entry); + return entry; + } +} diff --git a/TerraForgedCore/src/main/java/com/terraforged/core/util/concurrent/cache/ExpiringEntry.java b/TerraForgedCore/src/main/java/com/terraforged/core/util/concurrent/cache/ExpiringEntry.java new file mode 100644 index 0000000..e780212 --- /dev/null +++ b/TerraForgedCore/src/main/java/com/terraforged/core/util/concurrent/cache/ExpiringEntry.java @@ -0,0 +1,6 @@ +package com.terraforged.core.util.concurrent.cache; + +public interface ExpiringEntry { + + long getTimestamp(); +} diff --git a/TerraForgedCore/src/main/java/com/terraforged/core/world/heightmap/Heightmap.java b/TerraForgedCore/src/main/java/com/terraforged/core/world/heightmap/Heightmap.java index 4ac91dc..d244a5e 100644 --- a/TerraForgedCore/src/main/java/com/terraforged/core/world/heightmap/Heightmap.java +++ b/TerraForgedCore/src/main/java/com/terraforged/core/world/heightmap/Heightmap.java @@ -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 cell, float x, float z); + void applyBase(Cell cell, float x, float z); + + void applyRivers(Cell cell, float x, float z, RiverRegionList rivers); + + void applyClimate(Cell cell, float x, float z); + @Override default void visit(int minX, int minZ, int maxX, int maxZ, Cell.Visitor visitor) { int chunkSize = Size.chunkToBlock(1); diff --git a/TerraForgedCore/src/main/java/com/terraforged/core/world/heightmap/WorldHeightmap.java b/TerraForgedCore/src/main/java/com/terraforged/core/world/heightmap/WorldHeightmap.java index 2610adf..f054790 100644 --- a/TerraForgedCore/src/main/java/com/terraforged/core/world/heightmap/WorldHeightmap.java +++ b/TerraForgedCore/src/main/java/com/terraforged/core/world/heightmap/WorldHeightmap.java @@ -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 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 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 cell, float x, float z, RiverRegionList rivers) { + rivers.apply(cell, x, z); + } + @Override + public void applyClimate(Cell 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); } diff --git a/TerraForgedCore/src/main/java/com/terraforged/core/world/river/RiverManager.java b/TerraForgedCore/src/main/java/com/terraforged/core/world/river/RiverManager.java index d982a39..c8fbbca 100644 --- a/TerraForgedCore/src/main/java/com/terraforged/core/world/river/RiverManager.java +++ b/TerraForgedCore/src/main/java/com/terraforged/core/world/river/RiverManager.java @@ -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 cache = new Cache<>(60, 60, TimeUnit.SECONDS, () -> new ConcurrentHashMap<>()); + private final Cache> 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 entry = getRegion(rx + dx, rz + dz); + list.add(entry); + } + } + + return list; + } + public void apply(Cell 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[] 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 entry : entries) { + if (entry.isDone()) { + count++; + entry.get().apply(cell, x, z); + } } } } - private RiverRegion getRegion(int rx, int rz) { + private CacheEntry 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 region; + return cache.computeIfAbsent(id, l -> generateRegion(rx, rz)); + } + + private CacheEntry generateRegion(int rx, int rz) { + return CacheEntry.supplyAsync(() -> new RiverRegion(rx, rz, heightmap, context, riverContext), ForkJoinPool.commonPool()); } } diff --git a/TerraForgedCore/src/main/java/com/terraforged/core/world/river/RiverRegion.java b/TerraForgedCore/src/main/java/com/terraforged/core/world/river/RiverRegion.java index 7f12cd0..5e10e09 100644 --- a/TerraForgedCore/src/main/java/com/terraforged/core/world/river/RiverRegion.java +++ b/TerraForgedCore/src/main/java/com/terraforged/core/world/river/RiverRegion.java @@ -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 cell, float x, float z) { float px = domain.getX(x, z); float pz = domain.getY(x, z); diff --git a/TerraForgedCore/src/main/java/com/terraforged/core/world/river/RiverRegionList.java b/TerraForgedCore/src/main/java/com/terraforged/core/world/river/RiverRegionList.java new file mode 100644 index 0000000..3a5092c --- /dev/null +++ b/TerraForgedCore/src/main/java/com/terraforged/core/world/river/RiverRegionList.java @@ -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[] regions = new CacheEntry[4]; + + protected void add(CacheEntry entry) { + regions[index++] = entry; + } + + public void apply(Cell cell, float x, float z) { + int complete = 0; + while (complete < regions.length) { + for (CacheEntry entry : regions) { + if (entry.isDone()) { + complete++; + entry.get().apply(cell, x, z); + } + } + } + } +} diff --git a/TerraForgedMod/src/main/java/com/terraforged/mod/chunk/FastChunk.java b/TerraForgedMod/src/main/java/com/terraforged/mod/chunk/FastChunk.java new file mode 100644 index 0000000..7a51b98 --- /dev/null +++ b/TerraForgedMod/src/main/java/com/terraforged/mod/chunk/FastChunk.java @@ -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; + } +} diff --git a/TerraForgedMod/src/main/java/com/terraforged/mod/chunk/TerraChunkGenerator.java b/TerraForgedMod/src/main/java/com/terraforged/mod/chunk/TerraChunkGenerator.java index bf09d7e..3da3775 100644 --- a/TerraForgedMod/src/main/java/com/terraforged/mod/chunk/TerraChunkGenerator.java +++ b/TerraForgedMod/src/main/java/com/terraforged/mod/chunk/TerraChunkGenerator.java @@ -118,6 +118,11 @@ public class TerraChunkGenerator extends ObfHelperChunkGenerator 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 { - 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 { - 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