mirror of
https://github.com/PolyhedralDev/Terra.git
synced 2026-06-18 14:50:56 +00:00
multithreaded jankiness
This commit is contained in:
@@ -12,7 +12,8 @@ import com.dfsek.terra.api.world.generation.GenerationPhase;
|
|||||||
import com.dfsek.terra.biome.UserDefinedBiome;
|
import com.dfsek.terra.biome.UserDefinedBiome;
|
||||||
import com.dfsek.terra.biome.grid.master.TerraBiomeGrid;
|
import com.dfsek.terra.biome.grid.master.TerraBiomeGrid;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.Collections;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
@@ -20,35 +21,42 @@ import java.util.Random;
|
|||||||
public class CarverCache {
|
public class CarverCache {
|
||||||
|
|
||||||
private final World w;
|
private final World w;
|
||||||
private final Map<Long, List<Worm.WormPoint>> carvers = new HashMap<>();
|
private final Map<Long, List<Worm.WormPoint>> carvers;
|
||||||
private final TerraPlugin main;
|
private final TerraPlugin main;
|
||||||
|
|
||||||
public CarverCache(World w, TerraPlugin main) {
|
public CarverCache(World w, TerraPlugin main) {
|
||||||
this.w = w;
|
this.w = w;
|
||||||
this.main = main;
|
this.main = main;
|
||||||
|
carvers = Collections.synchronizedMap(new LinkedHashMap<Long, List<Worm.WormPoint>>() {
|
||||||
|
@Override
|
||||||
|
protected boolean removeEldestEntry(Map.Entry eldest) {
|
||||||
|
return this.size() > main.getTerraConfig().getCarverCacheSize();
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<Worm.WormPoint> getPoints(int chunkX, int chunkZ, UserDefinedCarver carver) {
|
public List<Worm.WormPoint> getPoints(int chunkX, int chunkZ, UserDefinedCarver carver) {
|
||||||
if(carvers.size() > main.getTerraConfig().getCarverCacheSize()) carvers.clear();
|
synchronized(carvers) {
|
||||||
return carvers.computeIfAbsent((((long) chunkX) << 32) | (chunkZ & 0xffffffffL), key -> {
|
return carvers.computeIfAbsent((((long) chunkX) << 32) | (chunkZ & 0xffffffffL), key -> {
|
||||||
TerraBiomeGrid grid = main.getWorld(w).getGrid();
|
TerraBiomeGrid grid = main.getWorld(w).getGrid();
|
||||||
if(carver.isChunkCarved(w, chunkX, chunkZ, new FastRandom(MathUtil.getCarverChunkSeed(chunkX, chunkZ, w.getSeed() + carver.hashCode())))) {
|
if(carver.isChunkCarved(w, chunkX, chunkZ, new FastRandom(MathUtil.getCarverChunkSeed(chunkX, chunkZ, w.getSeed() + carver.hashCode())))) {
|
||||||
long seed = MathUtil.getCarverChunkSeed(chunkX, chunkZ, w.getSeed());
|
long seed = MathUtil.getCarverChunkSeed(chunkX, chunkZ, w.getSeed());
|
||||||
carver.getSeedVar().setValue(seed);
|
carver.getSeedVar().setValue(seed);
|
||||||
Random r = new FastRandom(seed);
|
Random r = new FastRandom(seed);
|
||||||
Worm carving = carver.getWorm(seed, new Vector3((chunkX << 4) + r.nextInt(16), carver.getConfig().getHeight().get(r), (chunkZ << 4) + r.nextInt(16)));
|
Worm carving = carver.getWorm(seed, new Vector3((chunkX << 4) + r.nextInt(16), carver.getConfig().getHeight().get(r), (chunkZ << 4) + r.nextInt(16)));
|
||||||
List<Worm.WormPoint> points = new GlueList<>();
|
List<Worm.WormPoint> points = new GlueList<>();
|
||||||
for(int i = 0; i < carving.getLength(); i++) {
|
for(int i = 0; i < carving.getLength(); i++) {
|
||||||
carving.step();
|
carving.step();
|
||||||
Biome biome = grid.getBiome(carving.getRunning().toLocation(w), GenerationPhase.POPULATE);
|
Biome biome = grid.getBiome(carving.getRunning().toLocation(w), GenerationPhase.POPULATE);
|
||||||
if(!((UserDefinedBiome) biome).getConfig().getCarvers().containsKey(carver)) { // Stop if we enter a biome this carver is not present in
|
if(!((UserDefinedBiome) biome).getConfig().getCarvers().containsKey(carver)) { // Stop if we enter a biome this carver is not present in
|
||||||
return new GlueList<>();
|
return new GlueList<>();
|
||||||
|
}
|
||||||
|
points.add(carving.getPoint());
|
||||||
}
|
}
|
||||||
points.add(carving.getPoint());
|
return points;
|
||||||
}
|
}
|
||||||
return points;
|
return new GlueList<>();
|
||||||
}
|
});
|
||||||
return new GlueList<>();
|
}
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,12 +6,13 @@ import java.io.IOException;
|
|||||||
import java.util.concurrent.ThreadLocalRandom;
|
import java.util.concurrent.ThreadLocalRandom;
|
||||||
|
|
||||||
public class RegionGenerator {
|
public class RegionGenerator {
|
||||||
public static void main(String[] args) throws IOException {
|
public static void main(String[] args) throws IOException, InterruptedException {
|
||||||
long seed;
|
long seed;
|
||||||
if(args.length == 1) seed = Long.parseLong(args[0]);
|
if(args.length == 1) seed = Long.parseLong(args[0]);
|
||||||
else seed = ThreadLocalRandom.current().nextLong();
|
else seed = ThreadLocalRandom.current().nextLong();
|
||||||
|
|
||||||
Generator generator = new Generator(seed);
|
StandalonePlugin plugin = new StandalonePlugin();
|
||||||
|
Generator generator = new Generator(seed, plugin);
|
||||||
|
|
||||||
generator.generate();
|
generator.generate();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package com.dfsek.terra.async;
|
||||||
|
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
public class Counter {
|
||||||
|
private final AtomicInteger integer = new AtomicInteger();
|
||||||
|
private final Consumer<Integer> update;
|
||||||
|
|
||||||
|
public Counter(Consumer<Integer> update) {
|
||||||
|
this.update = update;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void add() {
|
||||||
|
update.accept(integer.addAndGet(1));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
package com.dfsek.terra.async;
|
||||||
|
|
||||||
|
import com.dfsek.terra.DirectUtils;
|
||||||
|
import com.dfsek.terra.api.math.MathUtil;
|
||||||
|
import com.dfsek.terra.api.util.FastRandom;
|
||||||
|
import com.dfsek.terra.platform.DirectChunkData;
|
||||||
|
import com.dfsek.terra.platform.DirectWorld;
|
||||||
|
import com.dfsek.terra.region.Generator;
|
||||||
|
import net.querz.mca.MCAUtil;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.util.concurrent.RecursiveAction;
|
||||||
|
|
||||||
|
public class GenerationWorker extends RecursiveAction {
|
||||||
|
private final int x;
|
||||||
|
private final int z;
|
||||||
|
private final DirectWorld world;
|
||||||
|
private final Generator generator;
|
||||||
|
private final Counter counter;
|
||||||
|
|
||||||
|
public GenerationWorker(int x, int z, DirectWorld world, Generator generator, Counter counter) {
|
||||||
|
this.x = x;
|
||||||
|
this.z = z;
|
||||||
|
this.world = world;
|
||||||
|
this.generator = generator;
|
||||||
|
this.counter = counter;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void compute() {
|
||||||
|
for(int cx = x * 32; cx < x * 32 + 32; cx++) {
|
||||||
|
for(int cz = z * 32; cz <= z * 32 + 32; cz++) {
|
||||||
|
DirectChunkData chunkData = (DirectChunkData) world.getChunkAt(cx, cz);
|
||||||
|
generator.getGenerator().generateChunkData(world, null, cx, cz, chunkData);
|
||||||
|
|
||||||
|
generator.getCavePopulator().populate(world, new FastRandom(MathUtil.getCarverChunkSeed(cx, cz, world.getSeed())), chunkData);
|
||||||
|
generator.getStructurePopulator().populate(world, new FastRandom(MathUtil.getCarverChunkSeed(cx, cz, world.getSeed())), chunkData);
|
||||||
|
generator.getOrePopulator().populate(world, new FastRandom(MathUtil.getCarverChunkSeed(cx, cz, world.getSeed())), chunkData);
|
||||||
|
generator.getFloraPopulator().populate(world, new FastRandom(MathUtil.getCarverChunkSeed(cx, cz, world.getSeed())), chunkData);
|
||||||
|
generator.getTreePopulator().populate(world, new FastRandom(MathUtil.getCarverChunkSeed(cx, cz, world.getSeed())), chunkData);
|
||||||
|
|
||||||
|
counter.add();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
long regionID = DirectUtils.regionID(x, z);
|
||||||
|
try {
|
||||||
|
File file = new File("region", MCAUtil.createNameFromRegionLocation(x, z));
|
||||||
|
file.getParentFile().mkdirs();
|
||||||
|
MCAUtil.write(world.getFiles().get(regionID), file);
|
||||||
|
} catch(Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
world.getFiles().remove(regionID);
|
||||||
|
System.out.println("Saved region [" + x + "," + z + "]. " + world.getFiles().size() + " regions remain loaded.");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -33,8 +33,7 @@ public class Data implements BlockData, MaterialData {
|
|||||||
} else {
|
} else {
|
||||||
this.data = tag;
|
this.data = tag;
|
||||||
}
|
}
|
||||||
String id = data.getString("Name");
|
noProp = data.getString("Name");
|
||||||
noProp = id;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,9 @@ public class DirectBlock implements Block {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setBlockData(BlockData data, boolean physics) {
|
public void setBlockData(BlockData data, boolean physics) {
|
||||||
world.compute(FastMath.floorDiv(pos.getBlockX(), 16), FastMath.floorDiv(pos.getBlockZ(), 16)).setBlockStateAt(pos.getBlockX(), pos.getBlockY(), pos.getBlockZ(), ((Data) data).getHandle(), false);
|
synchronized(world) {
|
||||||
|
world.compute(FastMath.floorDiv(pos.getBlockX(), 16), FastMath.floorDiv(pos.getBlockZ(), 16)).setBlockStateAt(pos.getBlockX(), pos.getBlockY(), pos.getBlockZ(), ((Data) data).getHandle(), false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -16,6 +16,8 @@ import net.querz.mca.MCAUtil;
|
|||||||
import net.querz.nbt.tag.CompoundTag;
|
import net.querz.nbt.tag.CompoundTag;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
@@ -23,7 +25,7 @@ import java.util.UUID;
|
|||||||
public class DirectWorld implements World {
|
public class DirectWorld implements World {
|
||||||
private final long seed;
|
private final long seed;
|
||||||
private final GenWrapper generator;
|
private final GenWrapper generator;
|
||||||
private final Map<Long, MCAFile> files = new HashMap<>();
|
private final Map<Long, MCAFile> files = Collections.synchronizedMap(new HashMap<>());
|
||||||
|
|
||||||
public DirectWorld(long seed, GenWrapper generator) {
|
public DirectWorld(long seed, GenWrapper generator) {
|
||||||
this.seed = seed;
|
this.seed = seed;
|
||||||
@@ -102,7 +104,20 @@ public class DirectWorld implements World {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public MCAFile compute(int x, int z) {
|
public MCAFile compute(int x, int z) {
|
||||||
return files.computeIfAbsent(DirectUtils.regionID(x, z), k -> new MCAFile(MCAUtil.chunkToRegion(x), MCAUtil.chunkToRegion(z)));
|
synchronized(files) {
|
||||||
|
return files.computeIfAbsent(DirectUtils.regionID(x, z), k -> {
|
||||||
|
File test = new File("region", MCAUtil.createNameFromChunkLocation(x, z));
|
||||||
|
if(test.exists()) {
|
||||||
|
try {
|
||||||
|
System.out.println("Re-loading " + MCAUtil.createNameFromChunkLocation(x, z));
|
||||||
|
return MCAUtil.read(test);
|
||||||
|
} catch(IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new MCAFile(MCAUtil.chunkToRegion(x), MCAUtil.chunkToRegion(z));
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public CompoundTag getData(int x, int y, int z) {
|
public CompoundTag getData(int x, int y, int z) {
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
package com.dfsek.terra.region;
|
package com.dfsek.terra.region;
|
||||||
|
|
||||||
import com.dfsek.terra.StandalonePlugin;
|
import com.dfsek.terra.StandalonePlugin;
|
||||||
import com.dfsek.terra.api.math.MathUtil;
|
|
||||||
import com.dfsek.terra.api.util.FastRandom;
|
|
||||||
import com.dfsek.terra.api.util.GlueList;
|
import com.dfsek.terra.api.util.GlueList;
|
||||||
|
import com.dfsek.terra.async.Counter;
|
||||||
|
import com.dfsek.terra.async.GenerationWorker;
|
||||||
import com.dfsek.terra.generation.MasterChunkGenerator;
|
import com.dfsek.terra.generation.MasterChunkGenerator;
|
||||||
import com.dfsek.terra.generation.math.SamplerCache;
|
import com.dfsek.terra.generation.math.SamplerCache;
|
||||||
import com.dfsek.terra.platform.DirectChunkData;
|
|
||||||
import com.dfsek.terra.platform.DirectWorld;
|
import com.dfsek.terra.platform.DirectWorld;
|
||||||
import com.dfsek.terra.platform.GenWrapper;
|
import com.dfsek.terra.platform.GenWrapper;
|
||||||
import com.dfsek.terra.population.CavePopulator;
|
import com.dfsek.terra.population.CavePopulator;
|
||||||
@@ -21,67 +20,69 @@ import java.io.File;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ForkJoinTask;
|
||||||
|
import java.util.concurrent.atomic.AtomicLong;
|
||||||
|
|
||||||
public class Generator {
|
public class Generator {
|
||||||
private final long seed;
|
private final long seed;
|
||||||
|
FloraPopulator floraPopulator;
|
||||||
|
StructurePopulator structurePopulator;
|
||||||
|
TreePopulator treePopulator;
|
||||||
|
OrePopulator orePopulator;
|
||||||
|
CavePopulator cavePopulator;
|
||||||
|
MasterChunkGenerator generator;
|
||||||
|
|
||||||
public Generator(long seed) {
|
public Generator(long seed, StandalonePlugin plugin) {
|
||||||
|
plugin.load();
|
||||||
|
floraPopulator = new FloraPopulator(plugin);
|
||||||
|
structurePopulator = new StructurePopulator(plugin);
|
||||||
|
treePopulator = new TreePopulator(plugin);
|
||||||
|
orePopulator = new OrePopulator(plugin);
|
||||||
|
cavePopulator = new CavePopulator(plugin);
|
||||||
|
generator = new MasterChunkGenerator(plugin.getRegistry().get("DEFAULT"), plugin, new SamplerCache(plugin));
|
||||||
this.seed = seed;
|
this.seed = seed;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void generate() throws IOException {
|
public void generate() throws IOException {
|
||||||
|
|
||||||
int rad = 25;
|
int rad = 64;
|
||||||
|
System.out.println("Total mem: " + Runtime.getRuntime().maxMemory() / 1024 / 1024 / 1024 + "GB");
|
||||||
StandalonePlugin plugin = new StandalonePlugin();
|
|
||||||
plugin.load();
|
|
||||||
|
|
||||||
|
|
||||||
MasterChunkGenerator generator = new MasterChunkGenerator(plugin.getRegistry().get("DEFAULT"), plugin, new SamplerCache(plugin));
|
|
||||||
GenWrapper wrapper = new GenWrapper(generator);
|
GenWrapper wrapper = new GenWrapper(generator);
|
||||||
|
|
||||||
FloraPopulator floraPopulator = new FloraPopulator(plugin);
|
|
||||||
StructurePopulator structurePopulator = new StructurePopulator(plugin);
|
|
||||||
TreePopulator treePopulator = new TreePopulator(plugin);
|
|
||||||
OrePopulator orePopulator = new OrePopulator(plugin);
|
|
||||||
CavePopulator cavePopulator = new CavePopulator(plugin);
|
|
||||||
|
|
||||||
int count = 0;
|
|
||||||
|
|
||||||
List<Double> times = new GlueList<>();
|
|
||||||
|
|
||||||
DirectWorld world = new DirectWorld(seed, wrapper);
|
DirectWorld world = new DirectWorld(seed, wrapper);
|
||||||
|
|
||||||
for(int cx = -rad; cx <= rad; cx++) {
|
AtomicLong l = new AtomicLong(System.nanoTime());
|
||||||
for(int cz = -rad; cz <= rad; cz++) {
|
|
||||||
long start = System.nanoTime();
|
|
||||||
|
|
||||||
DirectChunkData chunkData = (DirectChunkData) world.getChunkAt(cx, cz);
|
Counter counter = new Counter((id) -> {
|
||||||
generator.generateChunkData(world, null, cx, cz, chunkData);
|
if(id % 200 == 0) {
|
||||||
|
long c = System.nanoTime();
|
||||||
|
|
||||||
cavePopulator.populate(world, new FastRandom(MathUtil.getCarverChunkSeed(cx, cz, seed)), chunkData);
|
long diff = c - l.get();
|
||||||
structurePopulator.populate(world, new FastRandom(MathUtil.getCarverChunkSeed(cx, cz, seed)), chunkData);
|
|
||||||
orePopulator.populate(world, new FastRandom(MathUtil.getCarverChunkSeed(cx, cz, seed)), chunkData);
|
double ms = (double) diff / 1000000;
|
||||||
floraPopulator.populate(world, new FastRandom(MathUtil.getCarverChunkSeed(cx, cz, seed)), chunkData);
|
|
||||||
treePopulator.populate(world, new FastRandom(MathUtil.getCarverChunkSeed(cx, cz, seed)), chunkData);
|
System.out.println("[" + Thread.currentThread().getName() + "] Generated " + id + " chunks. " + (200d / ms) * 1000 + " cps. " + world.getFiles().size() + " Regions loaded.");
|
||||||
|
l.set(System.nanoTime());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
long end = System.nanoTime() - start;
|
List<GenerationWorker> workers = new GlueList<>();
|
||||||
count++;
|
|
||||||
times.add((double) end / 1000000);
|
|
||||||
|
|
||||||
if(count % 200 == 0) {
|
|
||||||
double total = 0;
|
|
||||||
for(double d : times) total += d;
|
|
||||||
double avg = total / count;
|
|
||||||
|
|
||||||
double pct = ((double) count / (rad * rad * 2 * 2)) * 100D;
|
for(int x = -rad / 32; x <= rad / 32; x++) {
|
||||||
|
for(int z = -rad / 32; z <= rad / 32; z++) {
|
||||||
plugin.getLogger().info("Generated " + count + " chunks. " + (1 / avg) * 1000 + "cps; " + pct + "%");
|
workers.add(new GenerationWorker(x, z, world, this, counter));
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
ForkJoinTask.invokeAll(workers);
|
||||||
|
|
||||||
|
|
||||||
|
System.out.println("Saving...");
|
||||||
|
|
||||||
for(Map.Entry<Long, MCAFile> entry : world.getFiles().entrySet()) {
|
for(Map.Entry<Long, MCAFile> entry : world.getFiles().entrySet()) {
|
||||||
if(entry.getValue() == null) continue;
|
if(entry.getValue() == null) continue;
|
||||||
entry.getValue().cleanupPalettesAndBlockStates();
|
entry.getValue().cleanupPalettesAndBlockStates();
|
||||||
@@ -91,5 +92,35 @@ public class Generator {
|
|||||||
file.getParentFile().mkdirs();
|
file.getParentFile().mkdirs();
|
||||||
MCAUtil.write(entry.getValue(), file);
|
MCAUtil.write(entry.getValue(), file);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
System.out.println("Done in " + (System.nanoTime() - l.get()) / 1000000000 + "s");
|
||||||
|
}
|
||||||
|
|
||||||
|
public StructurePopulator getStructurePopulator() {
|
||||||
|
return structurePopulator;
|
||||||
|
}
|
||||||
|
|
||||||
|
public OrePopulator getOrePopulator() {
|
||||||
|
return orePopulator;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CavePopulator getCavePopulator() {
|
||||||
|
return cavePopulator;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TreePopulator getTreePopulator() {
|
||||||
|
return treePopulator;
|
||||||
|
}
|
||||||
|
|
||||||
|
public FloraPopulator getFloraPopulator() {
|
||||||
|
return floraPopulator;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getSeed() {
|
||||||
|
return seed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MasterChunkGenerator getGenerator() {
|
||||||
|
return generator;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user