Reworked thread safe cache solution.
Also performed refactoring. And added some threaded tests.
This commit is contained in:
parent
7c073cdc6e
commit
b30d689183
11
.vscode/launch.json
vendored
11
.vscode/launch.json
vendored
@ -4,6 +4,13 @@
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"type": "java",
|
||||
"name": "CodeLens (Launch) - UniBiomeIslandGeneratorTest",
|
||||
"request": "launch",
|
||||
"mainClass": "ca.recrown.islandsurvivalcraft.world.generation.UniBiomeIslandGeneratorTest",
|
||||
"projectName": "IslandSurvivalCraft"
|
||||
},
|
||||
{
|
||||
"type": "java",
|
||||
"name": "Debug (Launch) - Current File",
|
||||
@ -16,7 +23,9 @@
|
||||
"request": "attach",
|
||||
"hostName": "localhost",
|
||||
"port": 25566,
|
||||
"sourcePaths": ["src/main/java"],
|
||||
"sourcePaths": [
|
||||
"src/main/java"
|
||||
],
|
||||
"preLaunchTask": "start test server",
|
||||
"postDebugTask": "stop test server"
|
||||
}
|
||||
|
14
pom.xml
14
pom.xml
@ -11,9 +11,15 @@
|
||||
<repository>
|
||||
<id>papermc</id>
|
||||
<url>https://papermc.io/repo/repository/maven-public/</url>
|
||||
</repository>
|
||||
</repository>
|
||||
</repositories>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter-api</artifactId>
|
||||
<version>5.6.2</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter-engine</artifactId>
|
||||
@ -25,7 +31,7 @@
|
||||
<artifactId>paper-api</artifactId>
|
||||
<version>1.15.2-R0.1-SNAPSHOT</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<build>
|
||||
<plugins>
|
||||
@ -41,6 +47,10 @@
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<version>2.22.2</version>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<artifactId>maven-failsafe-plugin</artifactId>
|
||||
<version>2.22.2</version>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
<properties>
|
||||
|
@ -11,6 +11,7 @@ public class IslandSurvivalCraft extends JavaPlugin {
|
||||
|
||||
@Override
|
||||
public void onEnable() {
|
||||
|
||||
super.onEnable();
|
||||
}
|
||||
|
||||
|
@ -4,7 +4,7 @@ import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import ca.recrown.islandsurvivalcraft.types.Point2;
|
||||
import ca.recrown.islandsurvivalcraft.datatypes.Point2;
|
||||
|
||||
public class Utilities {
|
||||
public final static int CHUNK_SIZE = 16;
|
||||
|
@ -1,22 +1,20 @@
|
||||
package ca.recrown.islandsurvivalcraft.caching;
|
||||
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||
import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock;
|
||||
import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock;
|
||||
|
||||
public class Cache<Key, Value> {
|
||||
public class Cache<K, V> {
|
||||
private final int maxSize;
|
||||
private final ConcurrentHashMap<Key, CacheValue<Value>> data;
|
||||
private final ConcurrentLinkedQueue<Key> occurrenceOrder;
|
||||
private ReentrantReadWriteLock cleaningLock = new ReentrantReadWriteLock(true);
|
||||
private WriteLock writeLock = cleaningLock.writeLock();
|
||||
private ReadLock readLock = cleaningLock.readLock();
|
||||
private final ConcurrentHashMap<K, CacheValue<K, V>> data;
|
||||
private final UsageStack<K, V> usage = new UsageStack<>();
|
||||
private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
|
||||
private final WriteLock writeLock = lock.writeLock();
|
||||
private final ReadLock readLock = lock.readLock();
|
||||
|
||||
public Cache(int maxSize) {
|
||||
data = new ConcurrentHashMap<>(maxSize + 1, 0.75f, 6);
|
||||
occurrenceOrder = new ConcurrentLinkedQueue<>();
|
||||
this.maxSize = maxSize;
|
||||
}
|
||||
|
||||
@ -24,29 +22,23 @@ public class Cache<Key, Value> {
|
||||
this(1024);
|
||||
}
|
||||
|
||||
public void setValue(Key key, Value value) {
|
||||
CacheValue<Value> previous = data.get(key);
|
||||
CacheValue<Value> fresh = new CacheValue<>(value);
|
||||
if (previous != null) fresh.occurrence += previous.occurrence;
|
||||
occurrenceOrder.add(key);
|
||||
data.put(key, fresh);
|
||||
|
||||
if (data.size() > maxSize) {
|
||||
int occ = 0;
|
||||
do {
|
||||
Key potentialKey = occurrenceOrder.poll();
|
||||
CacheValue<Value> potential = data.get(potentialKey);
|
||||
potential.occurrence--;
|
||||
writeLock.lock();
|
||||
try {
|
||||
occ = potential.occurrence;
|
||||
if (occ < 1) {
|
||||
data.remove(potentialKey);
|
||||
}
|
||||
} finally {
|
||||
writeLock.unlock();
|
||||
public void set(K key, V value) {
|
||||
writeLock.lock();
|
||||
try {
|
||||
if (data.containsKey(key)) {
|
||||
data.get(key).value = value;
|
||||
} else {
|
||||
CacheValue<K, V> val = new CacheValue<>();
|
||||
val.key = key;
|
||||
val.value = value;
|
||||
data.put(key, val);
|
||||
usage.add(val);
|
||||
if (data.size() > maxSize) {
|
||||
data.remove(usage.pop().key);
|
||||
}
|
||||
} while (occ > 0);
|
||||
}
|
||||
} finally {
|
||||
writeLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@ -57,22 +49,26 @@ public class Cache<Key, Value> {
|
||||
* @param key the key associated with the value.
|
||||
* @return the value associated to the key.
|
||||
*/
|
||||
public Value getValue(Key key) {
|
||||
CacheValue<Value> res = null;
|
||||
public V get(K key) {
|
||||
readLock.lock();
|
||||
CacheValue<K, V> value = null;
|
||||
try {
|
||||
res = data.get(key);
|
||||
if (res == null) return null;
|
||||
res.occurrence++;
|
||||
occurrenceOrder.add(key);
|
||||
if (!data.containsKey(key)) return null;
|
||||
value = data.get(key);
|
||||
usage.tryMoveToTop(value);
|
||||
} finally {
|
||||
readLock.unlock();
|
||||
}
|
||||
return res.data;
|
||||
return value.value;
|
||||
}
|
||||
|
||||
public void clearCache() {
|
||||
data.clear();
|
||||
occurrenceOrder.clear();
|
||||
writeLock.lock();
|
||||
try {
|
||||
data.clear();
|
||||
usage.clear();
|
||||
} finally {
|
||||
writeLock.unlock();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,10 +1,7 @@
|
||||
package ca.recrown.islandsurvivalcraft.caching;
|
||||
|
||||
class CacheValue<ValueType> {
|
||||
public volatile int occurrence = 1;
|
||||
public final ValueType data;
|
||||
|
||||
public CacheValue(ValueType value) {
|
||||
this.data = value;
|
||||
}
|
||||
class CacheValue<KeyType, ValueType> {
|
||||
public volatile KeyType key;
|
||||
public volatile ValueType value;
|
||||
public volatile CacheValue<KeyType, ValueType> front, back;
|
||||
}
|
@ -0,0 +1,89 @@
|
||||
package ca.recrown.islandsurvivalcraft.caching;
|
||||
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
public class UsageStack<K, V> {
|
||||
private volatile CacheValue<K, V> first, last;
|
||||
private final ReentrantLock lock = new ReentrantLock();
|
||||
|
||||
private void addToTop(CacheValue<K, V> value) {
|
||||
if (first != null) {
|
||||
first.front = value;
|
||||
value.back = first;
|
||||
} else {
|
||||
last = value;
|
||||
}
|
||||
value.front = null;
|
||||
first = value;
|
||||
}
|
||||
|
||||
private void removeValue(CacheValue<K, V> value) {
|
||||
if (value.front != null) {
|
||||
value.front.back = value.back;
|
||||
} else {
|
||||
first = value.back;
|
||||
}
|
||||
if (value.back != null) {
|
||||
value.back.front = value.front;
|
||||
} else {
|
||||
last = value.front;
|
||||
}
|
||||
value.front = null;
|
||||
value.back = null;
|
||||
}
|
||||
|
||||
public void moveToTop(CacheValue<K, V> value) {
|
||||
lock.lock();
|
||||
try {
|
||||
removeValue(value);
|
||||
addToTop(value);
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean tryMoveToTop(CacheValue<K, V> value) {
|
||||
boolean success = false;
|
||||
success = lock.tryLock();
|
||||
if (success) {
|
||||
try {
|
||||
removeValue(value);
|
||||
addToTop(value);
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
public void add(CacheValue<K, V> value) {
|
||||
lock.lock();
|
||||
try {
|
||||
addToTop(value);
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
public CacheValue<K, V> pop() {
|
||||
CacheValue<K, V> cacheValue;
|
||||
lock.lock();
|
||||
try {
|
||||
cacheValue = last;
|
||||
removeValue(last);
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
return cacheValue;
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
lock.lock();
|
||||
try {
|
||||
first = null;
|
||||
last = null;
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package ca.recrown.islandsurvivalcraft.types;
|
||||
package ca.recrown.islandsurvivalcraft.datatypes;
|
||||
|
||||
import java.util.Objects;
|
||||
|
@ -1,6 +1,6 @@
|
||||
package ca.recrown.islandsurvivalcraft.floodfill;
|
||||
|
||||
import ca.recrown.islandsurvivalcraft.types.Point2;
|
||||
import ca.recrown.islandsurvivalcraft.datatypes.Point2;
|
||||
|
||||
public interface Floodable {
|
||||
/**
|
||||
|
@ -3,7 +3,7 @@ package ca.recrown.islandsurvivalcraft.floodfill;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
|
||||
import ca.recrown.islandsurvivalcraft.types.Point2;
|
||||
import ca.recrown.islandsurvivalcraft.datatypes.Point2;
|
||||
|
||||
public class Flooder {
|
||||
private final Point2 start;
|
||||
|
@ -2,7 +2,7 @@ package ca.recrown.islandsurvivalcraft.pathfinding;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import ca.recrown.islandsurvivalcraft.types.Point2;
|
||||
import ca.recrown.islandsurvivalcraft.datatypes.Point2;
|
||||
|
||||
public class DFSNode extends Point2 implements Comparable<DFSNode> {
|
||||
private DFSNode goal;
|
||||
|
@ -6,7 +6,7 @@ import java.util.Queue;
|
||||
|
||||
import org.apache.commons.lang.NullArgumentException;
|
||||
|
||||
import ca.recrown.islandsurvivalcraft.types.Point2;
|
||||
import ca.recrown.islandsurvivalcraft.datatypes.Point2;
|
||||
|
||||
public class DepthFirstSearch {
|
||||
private Queue<DFSNode> queue;
|
||||
|
@ -4,7 +4,7 @@ import java.util.Random;
|
||||
|
||||
import org.bukkit.util.noise.SimplexOctaveGenerator;
|
||||
|
||||
import ca.recrown.islandsurvivalcraft.types.Point2;
|
||||
import ca.recrown.islandsurvivalcraft.datatypes.Point2;
|
||||
import ca.recrown.islandsurvivalcraft.caching.Cache;
|
||||
import ca.recrown.islandsurvivalcraft.pathfinding.CoordinateValidatable;
|
||||
import ca.recrown.islandsurvivalcraft.pathfinding.DepthFirstSearch;
|
||||
@ -24,11 +24,11 @@ public class IslandWorldMapper implements CoordinateValidatable {
|
||||
private final float shallowPortion = 0.015f;
|
||||
private final DepthFirstSearch dfs;
|
||||
|
||||
public IslandWorldMapper(Random random) {
|
||||
public IslandWorldMapper(Random random, Cache<Point2, Double> blockValueCache) {
|
||||
dfs = new DepthFirstSearch(this);
|
||||
this.noiseGenerator = new SimplexOctaveGenerator(random, noiseOctaves);
|
||||
noiseGenerator.setScale(scale);
|
||||
blockValueCache = new Cache<>(65536);
|
||||
this.blockValueCache = blockValueCache;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -114,7 +114,7 @@ public class IslandWorldMapper implements CoordinateValidatable {
|
||||
public double getWorldBlockValue(int worldX, int worldZ) {
|
||||
Point2 p = new Point2(worldX, worldZ);
|
||||
|
||||
Double res = blockValueCache.getValue(p);
|
||||
Double res = blockValueCache.get(p);
|
||||
if (res == null) {
|
||||
double portionSea = 1f - (this.islandBlockGenerationPercent / 100f);
|
||||
double shift = 1f - 2 * portionSea;
|
||||
@ -133,7 +133,7 @@ public class IslandWorldMapper implements CoordinateValidatable {
|
||||
} else {
|
||||
res = noise / maxPos;
|
||||
}
|
||||
blockValueCache.setValue(p, res);
|
||||
blockValueCache.set(p, res);
|
||||
return res;
|
||||
}
|
||||
|
||||
|
@ -3,6 +3,8 @@ package ca.recrown.islandsurvivalcraft.world.generation;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.block.Biome;
|
||||
|
||||
import ca.recrown.islandsurvivalcraft.caching.Cache;
|
||||
import ca.recrown.islandsurvivalcraft.datatypes.Point2;
|
||||
import ca.recrown.islandsurvivalcraft.world.BiomeSelector;
|
||||
import ca.recrown.islandsurvivalcraft.world.IslandWorldMapper;
|
||||
|
||||
@ -13,6 +15,7 @@ public interface BiomeGenerator {
|
||||
* The array should store the columns of biomes for the entire chunk.
|
||||
* It doesn't need to be populated on the first call as this method will be called once for every column in the chunk.
|
||||
* However, if some biomes can be set without repetative calls, doing so will prevent this method from being called for those locals.
|
||||
*
|
||||
* @param biomesArray The array of biomes that are to be saturated.
|
||||
* @param world The current world.
|
||||
* @param chunkX The X coordinate of the chunk.
|
||||
@ -21,13 +24,8 @@ public interface BiomeGenerator {
|
||||
* @param localZ The Z coordinate of the column within the chunk.
|
||||
* @param mapper The island mapper to be used.
|
||||
* @param tempGenerator The temperature generator to be used.
|
||||
* @param biomeCache Cache for biomes.
|
||||
* @param chunkGenCache Cache for whether or not the chunk is generated.
|
||||
*/
|
||||
public void generateBiomeColumn(Biome[][] biomesArray, World world, int chunkX, int chunkZ, int localX, int localZ, IslandWorldMapper mapper, BiomeSelector biomeSelector, TemperatureMapGenerator tempGenerator);
|
||||
|
||||
/**
|
||||
* Called when Bukkit designates a new chunk as generated.
|
||||
* @param chunkX The X coordinate of the chunk.
|
||||
* @param chunkZ The Z coordinate of the chunk.
|
||||
*/
|
||||
public void chunkGenerated(int chunkX, int chunkZ);
|
||||
public void generateBiomeColumn(Biome[][] biomesArray, World world, int chunkX, int chunkZ, int localX, int localZ, IslandWorldMapper mapper, BiomeSelector biomeSelector, TemperatureMapGenerator tempGenerator, Cache<Point2, Biome> biomeCache, Cache<Point2, Boolean> chunkGenCache);
|
||||
}
|
@ -7,43 +7,34 @@ import org.bukkit.Material;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.block.Biome;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.world.ChunkLoadEvent;
|
||||
import org.bukkit.generator.ChunkGenerator;
|
||||
|
||||
import ca.recrown.islandsurvivalcraft.Utilities;
|
||||
import ca.recrown.islandsurvivalcraft.caching.Cache;
|
||||
import ca.recrown.islandsurvivalcraft.datatypes.Point2;
|
||||
import ca.recrown.islandsurvivalcraft.world.BiomeSelector;
|
||||
import ca.recrown.islandsurvivalcraft.world.IslandWorldMapper;
|
||||
import ca.recrown.islandsurvivalcraft.world.shaders.WorldHeightShader;
|
||||
|
||||
public class IslandWorldChunkGenerator extends ChunkGenerator implements Listener {
|
||||
private final BiomeGenerator biomeGenerator;
|
||||
private final TemperatureMapGenerator temperatureGenerator = new TemperatureMapGenerator();
|
||||
private volatile Random random;
|
||||
private volatile IslandWorldMapper mapper;
|
||||
private volatile BiomeSelector biomeSelector;
|
||||
private volatile World world;
|
||||
private volatile WorldHeightShader heightShader;
|
||||
|
||||
public IslandWorldChunkGenerator(BiomeGenerator biomeGenerator) {
|
||||
this.biomeGenerator = biomeGenerator;
|
||||
}
|
||||
|
||||
public IslandWorldChunkGenerator() {
|
||||
this.biomeGenerator = new UniBiomeIslandGenerator();
|
||||
}
|
||||
private final Cache<Point2, Double> blockValueCache = new Cache<>(102400);
|
||||
private final Cache<Point2, Biome> biomeCache = new Cache<>(102400);
|
||||
private final Cache<Point2, Boolean> chunkExistenceCache = new Cache<>(16384);
|
||||
private volatile World currentWorld;
|
||||
|
||||
@Override
|
||||
public ChunkData generateChunkData(World world, Random random, int x, int z, BiomeGrid biome) {
|
||||
if (this.random != random) {
|
||||
this.random = random;
|
||||
mapper = new IslandWorldMapper(random);
|
||||
temperatureGenerator.setSeed(world.getSeed());
|
||||
biomeSelector = new BiomeSelector(random);
|
||||
biomeSelector.initialize();
|
||||
this.world = world;
|
||||
this.heightShader = new WorldHeightShader(world.getSeed(), mapper, world.getSeaLevel(), world.getMaxHeight(), 3);
|
||||
}
|
||||
this.currentWorld = world;
|
||||
IslandWorldMapper mapper = new IslandWorldMapper(random, blockValueCache);
|
||||
TemperatureMapGenerator temperatureMapGenerator = new TemperatureMapGenerator(world.getSeed());
|
||||
BiomeSelector biomeSelector = new BiomeSelector(random);
|
||||
biomeSelector.initialize();
|
||||
WorldHeightShader heightShader = new WorldHeightShader(world.getSeed(), mapper, world.getSeaLevel(), world.getMaxHeight(), 3);
|
||||
BiomeGenerator biomeGenerator = new UniBiomeIslandGenerator();
|
||||
|
||||
int maxHeight = world.getMaxHeight();
|
||||
ChunkData chunkData = createChunkData(world);
|
||||
|
||||
@ -51,7 +42,7 @@ public class IslandWorldChunkGenerator extends ChunkGenerator implements Listene
|
||||
for (int localX = 0; localX < Utilities.CHUNK_SIZE; localX++) {
|
||||
for (int localZ = 0; localZ < Utilities.CHUNK_SIZE; localZ++) {
|
||||
if (biomes[localX][localZ] == null) {
|
||||
biomeGenerator.generateBiomeColumn(biomes, world, x, z, localX, localZ, mapper, biomeSelector, temperatureGenerator);
|
||||
biomeGenerator.generateBiomeColumn(biomes, world, x, z, localX, localZ, mapper, biomeSelector, temperatureMapGenerator, biomeCache, chunkExistenceCache);
|
||||
}
|
||||
if (biomes[localX][localZ] == null) throw new IllegalStateException("Biome was null.");
|
||||
for (int y = 0; y < maxHeight; y++) {
|
||||
@ -68,12 +59,13 @@ public class IslandWorldChunkGenerator extends ChunkGenerator implements Listene
|
||||
return chunkData;
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
@EventHandler(priority = EventPriority.MONITOR)
|
||||
public void onChunkLoaded(ChunkLoadEvent event) {
|
||||
if (event.isNewChunk()) {
|
||||
if (world == event.getWorld()) {
|
||||
if (currentWorld == event.getWorld()) {
|
||||
Chunk chunk = event.getChunk();
|
||||
biomeGenerator.chunkGenerated(chunk.getX(), chunk.getZ());
|
||||
|
||||
chunkExistenceCache.set(new Point2(chunk.getX(), chunk.getZ()), true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4,7 +4,7 @@ import java.util.Random;
|
||||
|
||||
import org.bukkit.util.noise.SimplexOctaveGenerator;
|
||||
|
||||
import ca.recrown.islandsurvivalcraft.types.Point2;
|
||||
import ca.recrown.islandsurvivalcraft.datatypes.Point2;
|
||||
import ca.recrown.islandsurvivalcraft.caching.Cache;
|
||||
|
||||
class TemperatureMapGenerator {
|
||||
@ -30,10 +30,10 @@ class TemperatureMapGenerator {
|
||||
|
||||
public float getTemperature(int worldX, int worldZ) {
|
||||
Point2 loc = new Point2(worldX/4, worldZ/4);
|
||||
Float val = temperatureCache.getValue(loc);
|
||||
Float val = temperatureCache.get(loc);
|
||||
if (val == null) {
|
||||
val = (float) noiseGenerator.noise(worldX/4, worldZ/4, frequency, amplitude, true);
|
||||
temperatureCache.setValue(loc, val);
|
||||
temperatureCache.set(loc, val);
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
@ -4,27 +4,25 @@ import org.bukkit.World;
|
||||
import org.bukkit.block.Biome;
|
||||
|
||||
import ca.recrown.islandsurvivalcraft.Utilities;
|
||||
import ca.recrown.islandsurvivalcraft.types.Point2;
|
||||
import ca.recrown.islandsurvivalcraft.datatypes.Point2;
|
||||
import ca.recrown.islandsurvivalcraft.caching.Cache;
|
||||
import ca.recrown.islandsurvivalcraft.floodfill.Floodable;
|
||||
import ca.recrown.islandsurvivalcraft.floodfill.Flooder;
|
||||
import ca.recrown.islandsurvivalcraft.pathfinding.CoordinateTargetValidatable;
|
||||
import ca.recrown.islandsurvivalcraft.pathfinding.CoordinateValidatable;
|
||||
import ca.recrown.islandsurvivalcraft.pathfinding.DepthFirstSearch;
|
||||
import ca.recrown.islandsurvivalcraft.world.BiomeSelector;
|
||||
import ca.recrown.islandsurvivalcraft.world.IslandWorldMapper;
|
||||
|
||||
public class UniBiomeIslandGenerator implements BiomeGenerator {
|
||||
private final Cache<Point2, Biome> biomeCache = new Cache<>(12800);
|
||||
private final Cache<Point2, Boolean> existenceCache = new Cache<>(12800);
|
||||
|
||||
@Override
|
||||
public void generateBiomeColumn(Biome[][] biomes, World world, int chunkX, int chunkZ, int localX, int localZ, IslandWorldMapper mapper, BiomeSelector biomeSelector, TemperatureMapGenerator tempGen) {
|
||||
public void generateBiomeColumn(Biome[][] biomes, World world, int chunkX, int chunkZ, int localX, int localZ, IslandWorldMapper mapper, BiomeSelector biomeSelector, TemperatureMapGenerator tempGen, Cache<Point2, Biome> biomeCache, Cache<Point2, Boolean> chunkGenCache) {
|
||||
int worldX = 16 * chunkX + localX;
|
||||
int worldZ = 16 * chunkZ + localZ;
|
||||
Point2 chunkCoords = Utilities.worldToChunkCoordinates(new Point2(worldX, worldZ));
|
||||
existenceCache.setValue(chunkCoords, false);
|
||||
chunkGenCache.set(chunkCoords, false);
|
||||
|
||||
//Check if we can just give it something in cache.
|
||||
Biome biome = getSavedBiome(world, worldX, worldZ);
|
||||
Biome biome = getSavedBiome(world, worldX, worldZ, biomeCache, chunkGenCache);
|
||||
if (biome != null) {
|
||||
biomes[localX][localZ] = biome;
|
||||
return;
|
||||
@ -32,7 +30,7 @@ public class UniBiomeIslandGenerator implements BiomeGenerator {
|
||||
|
||||
//Fine, check if it's ocean.
|
||||
if (!mapper.isIsland(worldX, worldZ)) {
|
||||
setCacheBiome(worldX, worldZ, biomeSelector.getOceanBiome(tempGen.getTemperature(worldX, worldZ)), biomes);
|
||||
setCacheBiome(worldX, worldZ, biomeSelector.getOceanBiome(tempGen.getTemperature(worldX, worldZ)), biomes, biomeCache);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -40,7 +38,7 @@ public class UniBiomeIslandGenerator implements BiomeGenerator {
|
||||
DepthFirstSearch search = new DepthFirstSearch();
|
||||
search.setValidatable(mapper);
|
||||
|
||||
IslandInfo islandInfo = new IslandInfo(mapper, world);
|
||||
IslandInfo islandInfo = new IslandInfo(mapper, world, biomeCache, chunkGenCache);
|
||||
search.setStartPosition(worldX, worldZ);
|
||||
if (!search.findTarget(islandInfo)) {
|
||||
float temp = tempGen.getTemperature(worldX, worldZ);
|
||||
@ -49,105 +47,105 @@ public class UniBiomeIslandGenerator implements BiomeGenerator {
|
||||
if (islandInfo.shallow == null) islandInfo.shallow = biomeSelector.getOceanBiome(temp);
|
||||
}
|
||||
|
||||
PropagatorInfo propInfo = new PropagatorInfo(islandInfo, biomes, new Point2(chunkX, chunkZ), mapper);
|
||||
search.setValidatable(propInfo);
|
||||
search.setStartPosition(worldX, worldZ);
|
||||
search.findTarget(propInfo);
|
||||
PropagatorInfo propInfo = new PropagatorInfo(islandInfo, biomes, new Point2(chunkX, chunkZ), mapper, biomeCache);
|
||||
Flooder flooder = new Flooder(new Point2(worldX, worldZ), propInfo);
|
||||
flooder.start();
|
||||
}
|
||||
|
||||
private Biome getSavedBiome(World world, int worldX, int worldZ) {
|
||||
private Biome getSavedBiome(World world, int worldX, int worldZ, Cache<Point2, Biome> biomeCache, Cache<Point2, Boolean> existenceCache) {
|
||||
Point2 worldCoords = new Point2(worldX, worldZ);
|
||||
Biome res = null;
|
||||
|
||||
|
||||
res = biomeCache.getValue(worldCoords);
|
||||
res = biomeCache.get(worldCoords);
|
||||
if (res != null) return res;
|
||||
|
||||
Point2 chunkCoords = Utilities.worldToChunkCoordinates(worldCoords);
|
||||
Boolean chunkExists = existenceCache.getValue(chunkCoords);
|
||||
Boolean chunkExists = existenceCache.get(chunkCoords);
|
||||
if (chunkExists == null) {
|
||||
chunkExists = world.isChunkGenerated(worldX, worldZ);
|
||||
existenceCache.setValue(chunkCoords, chunkExists);
|
||||
existenceCache.set(chunkCoords, chunkExists);
|
||||
}
|
||||
|
||||
if (chunkExists) {
|
||||
res = world.getBiome(worldX, 0, worldZ);
|
||||
biomeCache.setValue(worldCoords, res);
|
||||
biomeCache.set(worldCoords, res);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
public void chunkGenerated(int chunkX, int chunkZ) {
|
||||
existenceCache.setValue(new Point2(chunkX, chunkZ), true);
|
||||
}
|
||||
|
||||
private void setCacheBiome(int worldX, int worldZ, Biome biome, Biome[][] biomes) {
|
||||
private void setCacheBiome(int worldX, int worldZ, Biome biome, Biome[][] localBiomes, Cache<Point2, Biome> biomeCache) {
|
||||
Point2 worldCoords = new Point2(worldX, worldZ);
|
||||
if (biomes != null) {
|
||||
if (localBiomes != null) {
|
||||
Point2 localCoords = Utilities.worldToLocalChunkCoordinates(worldCoords);
|
||||
biomes[localCoords.x][localCoords.y] = biome;
|
||||
localBiomes[localCoords.x][localCoords.y] = biome;
|
||||
}
|
||||
biomeCache.setValue(worldCoords, biome);
|
||||
biomeCache.set(worldCoords, biome);
|
||||
}
|
||||
|
||||
private class IslandInfo implements CoordinateTargetValidatable {
|
||||
public final IslandWorldMapper mapper;
|
||||
public final World world;
|
||||
private final Cache<Point2, Biome> biomeCache;
|
||||
private final Cache<Point2, Boolean> chunkGenCache;
|
||||
public Biome main, shore, shallow;
|
||||
|
||||
public IslandInfo(IslandWorldMapper mapper, World world) {
|
||||
public IslandInfo(IslandWorldMapper mapper, World world, Cache<Point2, Biome> biomeCache, Cache<Point2, Boolean> chunkGenCache) {
|
||||
this.mapper = mapper;
|
||||
this.world = world;
|
||||
this.biomeCache = biomeCache;
|
||||
this.chunkGenCache = chunkGenCache;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCoordinateTarget(int x, int y) {
|
||||
if (mapper.isLand(x, y)) {
|
||||
if (mapper.isShore(x, y)) {
|
||||
shore = getSavedBiome(world, x, y);
|
||||
shore = getSavedBiome(world, x, y, biomeCache, chunkGenCache);
|
||||
} else {
|
||||
main = getSavedBiome(world, x, y);
|
||||
main = getSavedBiome(world, x, y, biomeCache, chunkGenCache);
|
||||
}
|
||||
} else {
|
||||
shallow = getSavedBiome(world, x, y);
|
||||
shallow = getSavedBiome(world, x, y, biomeCache, chunkGenCache);
|
||||
}
|
||||
return main != null && shore != null && shallow != null;
|
||||
}
|
||||
}
|
||||
|
||||
private class PropagatorInfo implements CoordinateTargetValidatable, CoordinateValidatable {
|
||||
private class PropagatorInfo implements Floodable {
|
||||
private final Biome shallow, shore, main;
|
||||
private final Biome[][] biomes;
|
||||
private final Point2 chunkCoords;
|
||||
private final IslandWorldMapper mapper;
|
||||
private final Cache<Point2, Biome> biomeCache;
|
||||
|
||||
public PropagatorInfo(IslandInfo islandInfo, Biome[][] biomes, Point2 chunkCoords, IslandWorldMapper mapper) {
|
||||
public PropagatorInfo(IslandInfo islandInfo, Biome[][] biomes, Point2 chunkCoords, IslandWorldMapper mapper, Cache<Point2, Biome> biomeCache) {
|
||||
this.shallow = islandInfo.shallow;
|
||||
this.shore = islandInfo.shore;
|
||||
this.main = islandInfo.main;
|
||||
this.biomes = biomes;
|
||||
this.chunkCoords = chunkCoords;
|
||||
this.mapper = mapper;
|
||||
this.biomeCache = biomeCache;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean validate(int x, int y) {
|
||||
Point2 chunkCoords = Utilities.worldToChunkCoordinates(new Point2(x, y));
|
||||
return this.chunkCoords.fastEquals(chunkCoords) && mapper.validate(x, y);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCoordinateTarget(int x, int y) {
|
||||
public boolean flood(Point2 point) {
|
||||
Point2 chunkCoords = Utilities.worldToChunkCoordinates(point);
|
||||
if (!this.chunkCoords.fastEquals(chunkCoords) || !mapper.validate(point.x, point.y)) return false;
|
||||
int x = point.x;
|
||||
int y = point.y;
|
||||
if (mapper.isLand(x, y)) {
|
||||
if (mapper.isShore(x, y)) {
|
||||
setCacheBiome(x, y, shore, biomes);
|
||||
setCacheBiome(x, y, shore, biomes, biomeCache);
|
||||
} else {
|
||||
setCacheBiome(x, y, main, biomes);
|
||||
setCacheBiome(x, y, main, biomes, biomeCache);
|
||||
}
|
||||
} else {
|
||||
setCacheBiome(x, y, shallow, biomes);
|
||||
setCacheBiome(x, y, shallow, biomes, biomeCache);
|
||||
}
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -9,7 +9,7 @@ import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.TestInstance;
|
||||
import org.junit.jupiter.api.TestInstance.Lifecycle;
|
||||
|
||||
import ca.recrown.islandsurvivalcraft.types.Point2;
|
||||
import ca.recrown.islandsurvivalcraft.datatypes.Point2;
|
||||
|
||||
/**
|
||||
* Unit test for simple App.
|
||||
|
@ -2,9 +2,14 @@ package ca.recrown.islandsurvivalcraft.caching;
|
||||
|
||||
import static org.junit.Assert.assertSame;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import java.util.Random;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
@ -28,35 +33,35 @@ public class CacheTest {
|
||||
|
||||
@Test
|
||||
public void testBasicCaching() {
|
||||
String val = integerCache.getValue(0);
|
||||
String val = integerCache.get(0);
|
||||
assertTrue(val == null);
|
||||
val = "first";
|
||||
integerCache.setValue(0, val);
|
||||
assertTrue(integerCache.getValue(0) != null);
|
||||
assertSame(val, integerCache.getValue(0));
|
||||
integerCache.set(0, val);
|
||||
assertTrue(integerCache.get(0) != null);
|
||||
assertSame(val, integerCache.get(0));
|
||||
|
||||
val = integerCache.getValue(1);
|
||||
val = integerCache.get(1);
|
||||
assertTrue(val == null);
|
||||
val = "second";
|
||||
integerCache.setValue(1, val);
|
||||
assertTrue(integerCache.getValue(1) != null);
|
||||
assertSame(val, integerCache.getValue(1));
|
||||
integerCache.set(1, val);
|
||||
assertTrue(integerCache.get(1) != null);
|
||||
assertSame(val, integerCache.get(1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUsageBasedClean() {
|
||||
integerCache.setValue(0, "first");
|
||||
integerCache.setValue(1, "second");
|
||||
integerCache.setValue(2, "third");
|
||||
|
||||
assertEquals("first", integerCache.getValue(0));
|
||||
integerCache.set(0, "first");
|
||||
integerCache.set(1, "second");
|
||||
integerCache.set(2, "third");
|
||||
|
||||
integerCache.setValue(3, "fourth");
|
||||
assertEquals("first", integerCache.getValue(0));
|
||||
assertEquals("first", integerCache.get(0));
|
||||
|
||||
integerCache.setValue(4, "fifth");
|
||||
assertTrue(integerCache.getValue(3) != null);
|
||||
assertTrue(integerCache.getValue(0) != null);
|
||||
integerCache.set(3, "fourth");
|
||||
assertEquals("first", integerCache.get(0));
|
||||
|
||||
integerCache.set(4, "fifth");
|
||||
assertTrue(integerCache.get(3) != null);
|
||||
assertTrue(integerCache.get(0) != null);
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -64,20 +69,20 @@ public class CacheTest {
|
||||
int amount = 1024;
|
||||
Random random = new Random();
|
||||
|
||||
Cache<Integer, Integer> largeCache = new Cache<>(amount/2);
|
||||
Cache<Integer, Integer> largeCache = new Cache<>(amount / 2);
|
||||
|
||||
int[] values = new int[amount];
|
||||
for (int i = 0; i < amount; i++) {
|
||||
values[i] = random.nextInt();
|
||||
largeCache.setValue(i, values[i]);
|
||||
largeCache.set(i, values[i]);
|
||||
}
|
||||
|
||||
for (int i = 0; i < amount /2; i++) {
|
||||
assertEquals(null, largeCache.getValue(i), "Current accessor: " + i);
|
||||
for (int i = 0; i < amount / 2; i++) {
|
||||
assertEquals(null, largeCache.get(i), "Current accessor: " + i);
|
||||
}
|
||||
|
||||
for (int i = amount /2; i < amount; i++) {
|
||||
assertEquals(values[i], largeCache.getValue(i), "Current accessor: " + i);
|
||||
for (int i = amount / 2; i < amount; i++) {
|
||||
assertEquals(values[i], largeCache.get(i), "Current accessor: " + i);
|
||||
}
|
||||
}
|
||||
|
||||
@ -86,23 +91,194 @@ public class CacheTest {
|
||||
int amount = 1024;
|
||||
Random random = new Random();
|
||||
|
||||
Cache<Integer, Integer> largeCache = new Cache<>(amount/2);
|
||||
Cache<Integer, Integer> largeCache = new Cache<>(amount / 2);
|
||||
|
||||
int[] values = new int[amount];
|
||||
for (int i = 0; i < amount; i++) {
|
||||
values[i] = random.nextInt();
|
||||
largeCache.setValue(i, values[i]);
|
||||
largeCache.getValue(0);
|
||||
largeCache.set(i, values[i]);
|
||||
largeCache.get(0);
|
||||
}
|
||||
|
||||
for (int i = 1; i < (amount/2) + 1; i++) {
|
||||
assertEquals(null, largeCache.getValue(i), "Current accessor: " + i);
|
||||
for (int i = 1; i < (amount / 2) + 1; i++) {
|
||||
assertEquals(null, largeCache.get(i), "Current accessor: " + i);
|
||||
}
|
||||
|
||||
for (int i = (amount /2) + 1; i < amount; i++) {
|
||||
assertEquals(values[i], largeCache.getValue(i), "Current accessor: " + i);
|
||||
for (int i = (amount / 2) + 1; i < amount; i++) {
|
||||
assertEquals(values[i], largeCache.get(i), "Current accessor: " + i);
|
||||
}
|
||||
|
||||
assertEquals(values[0], largeCache.getValue(0));
|
||||
assertEquals(values[0], largeCache.get(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMultithreadingWriteConsistency() {
|
||||
int size = 16384;
|
||||
int[] answers = new int[size];
|
||||
Random rand = new Random();
|
||||
|
||||
for (int i = 0; i < answers.length; i++) {
|
||||
answers[i] = rand.nextInt();
|
||||
}
|
||||
Cache<Integer, Integer> lCache = new Cache<>(size);
|
||||
Runnable write = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
for (int i = 0; i < size; i++) {
|
||||
lCache.set(i, answers[i]);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Thread firstThread = new Thread(write);
|
||||
firstThread.start();
|
||||
Thread secondThread = new Thread(write);
|
||||
secondThread.start();
|
||||
Thread thirdThread = new Thread(write);
|
||||
thirdThread.start();
|
||||
|
||||
try {
|
||||
firstThread.join();
|
||||
secondThread.join();
|
||||
thirdThread.join();
|
||||
} catch (InterruptedException e) {
|
||||
assertFalse(false, e.getCause().getMessage());
|
||||
}
|
||||
|
||||
for (int i = 0; i < size; i++) {
|
||||
assertEquals(answers[i], lCache.get(i), "Accessor at: " + i);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMultithreadingReadConsistency() {
|
||||
int size = 16384;
|
||||
Cache<Integer, Integer> lCache = new Cache<>(size);
|
||||
int[] answers = new int[size];
|
||||
Random rand = new Random();
|
||||
|
||||
for (int i = 0; i < answers.length; i++) {
|
||||
answers[i] = rand.nextInt();
|
||||
lCache.set(i, answers[i]);
|
||||
}
|
||||
|
||||
Runnable read = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
for (int i = 0; i < size; i++) {
|
||||
assertEquals(answers[i], lCache.get(i), "Accessor at: " + i);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Thread firstThread = new Thread(read);
|
||||
firstThread.start();
|
||||
Thread secondThread = new Thread(read);
|
||||
secondThread.start();
|
||||
Thread thirdThread = new Thread(read);
|
||||
thirdThread.start();
|
||||
|
||||
try {
|
||||
firstThread.join();
|
||||
secondThread.join();
|
||||
thirdThread.join();
|
||||
} catch (InterruptedException e) {
|
||||
assertFalse(false, e.getCause().getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMulthreadedReadWrite() {
|
||||
int size = 16384;
|
||||
Cache<Integer, Integer> lCache = new Cache<>(size);
|
||||
int[] answers = new int[size];
|
||||
Random rand = new Random();
|
||||
|
||||
for (int i = 0; i < answers.length; i++) {
|
||||
answers[i] = rand.nextInt();
|
||||
lCache.set(i, answers[i]);
|
||||
}
|
||||
|
||||
Runnable readWrite = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
for (int i = 0; i < size / 2; i++) {
|
||||
if (lCache.get(i) == null) {
|
||||
lCache.set(i, answers[i]);
|
||||
} else {
|
||||
assertEquals(answers[i], lCache.get(i), "Accessor at: " + i);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Runnable readAll = new Runnable() {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
for (int i = 0; i < size; i++) {
|
||||
assertEquals(answers[i], lCache.get(i), "Accessor at: " + i);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Thread firstThread = new Thread(readWrite);
|
||||
firstThread.start();
|
||||
Thread secondThread = new Thread(readWrite);
|
||||
secondThread.start();
|
||||
Thread thirdThread = new Thread(readAll);
|
||||
|
||||
try {
|
||||
firstThread.join();
|
||||
secondThread.join();
|
||||
thirdThread.start();
|
||||
thirdThread.join();
|
||||
} catch (InterruptedException e) {
|
||||
assertFalse(false, e.getCause().getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMultiThreadConsistency() {
|
||||
int size = 51200;
|
||||
Cache<Integer, Integer> lCache = new Cache<>(size / 2);
|
||||
int[] answers = new int[size];
|
||||
Random rand = new Random();
|
||||
ConcurrentHashMap<Integer, Integer> expectedStoredValues = new ConcurrentHashMap<>();
|
||||
|
||||
for (int i = 0; i < answers.length; i++) {
|
||||
answers[i] = rand.nextInt();
|
||||
lCache.set(i, answers[i]);
|
||||
}
|
||||
|
||||
Runnable readWriteCheck = new Runnable(){
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
for (int i = 0; i < size; i++) {
|
||||
if (lCache.get(i) != null) {
|
||||
assertEquals(answers[i], lCache.get(i), "Accessor at: " + i);
|
||||
} else {
|
||||
lCache.set(i, answers[i]);
|
||||
expectedStoredValues.put(i, answers[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
ExecutorService executorService = Executors.newFixedThreadPool(6);
|
||||
executorService.submit(readWriteCheck);
|
||||
executorService.submit(readWriteCheck);
|
||||
executorService.submit(readWriteCheck);
|
||||
executorService.submit(readWriteCheck);
|
||||
executorService.submit(readWriteCheck);
|
||||
executorService.submit(readWriteCheck);
|
||||
|
||||
try {
|
||||
executorService.shutdown();
|
||||
assertTrue(executorService.awaitTermination(1, TimeUnit.MINUTES), "Timed out.");
|
||||
} catch (InterruptedException e) {
|
||||
assertFalse(false, e.getCause().getMessage());
|
||||
}
|
||||
}
|
||||
}
|
@ -13,7 +13,7 @@ import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.TestInstance;
|
||||
import org.junit.jupiter.api.TestInstance.Lifecycle;
|
||||
|
||||
import ca.recrown.islandsurvivalcraft.types.Point2;
|
||||
import ca.recrown.islandsurvivalcraft.datatypes.Point2;
|
||||
|
||||
@TestInstance(Lifecycle.PER_CLASS)
|
||||
public class FloodFillTest {
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,109 @@
|
||||
package ca.recrown.islandsurvivalcraft.world.generation;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import java.util.Random;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.bukkit.block.Biome;
|
||||
import org.junit.Test;
|
||||
import org.junit.jupiter.api.TestInstance;
|
||||
import org.junit.jupiter.api.TestInstance.Lifecycle;
|
||||
|
||||
import ca.recrown.islandsurvivalcraft.Utilities;
|
||||
import ca.recrown.islandsurvivalcraft.caching.Cache;
|
||||
import ca.recrown.islandsurvivalcraft.datatypes.Point2;
|
||||
import ca.recrown.islandsurvivalcraft.world.BiomeSelector;
|
||||
import ca.recrown.islandsurvivalcraft.world.IslandWorldMapper;
|
||||
|
||||
@TestInstance(Lifecycle.PER_CLASS)
|
||||
public class UniBiomeIslandGeneratorTest {
|
||||
private final int SEED = 249398015;
|
||||
private final DummyWorld dummyWorld = new DummyWorld();
|
||||
private final Cache<Point2, Double> blockValueCache = new Cache<>(102400);
|
||||
private final Cache<Point2, Biome> biomeCache = new Cache<>(102400);
|
||||
private final Cache<Point2, Boolean> chunkExistenceCache = new Cache<>(16384);
|
||||
private final Random rand = new Random(SEED);
|
||||
|
||||
private class BiomeGenTask implements Runnable {
|
||||
private final int amount;
|
||||
private final int shift;
|
||||
|
||||
public BiomeGenTask(int amount, int shift) {
|
||||
this.shift = shift;
|
||||
this.amount = amount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
for (int x = 0; x < amount; x++) {
|
||||
generateBiome(x, shift);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void generateBiome(int chunkX, int chunkZ) {
|
||||
IslandWorldMapper mapper = new IslandWorldMapper(rand, blockValueCache);
|
||||
TemperatureMapGenerator temperatureMapGenerator = new TemperatureMapGenerator(SEED);
|
||||
BiomeSelector biomeSelector = new BiomeSelector(rand);
|
||||
biomeSelector.initialize();
|
||||
BiomeGenerator biomeGenerator = new UniBiomeIslandGenerator();
|
||||
|
||||
Biome[][] biomes = new Biome[Utilities.CHUNK_SIZE][Utilities.CHUNK_SIZE];
|
||||
for (int localX = 0; localX < Utilities.CHUNK_SIZE; localX++) {
|
||||
for (int localZ = 0; localZ < Utilities.CHUNK_SIZE; localZ++) {
|
||||
if (biomes[localX][localZ] == null) {
|
||||
biomeGenerator.generateBiomeColumn(biomes, dummyWorld, chunkX, chunkZ, localX, localZ, mapper,
|
||||
biomeSelector, temperatureMapGenerator, biomeCache, chunkExistenceCache);
|
||||
}
|
||||
if (biomes[localX][localZ] == null)
|
||||
throw new IllegalStateException("Biome was null.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBiomeGenerationMultithread() {
|
||||
int chunksToDo = 280;
|
||||
Runnable g1 = new BiomeGenTask(chunksToDo, 0);
|
||||
Runnable g2 = new BiomeGenTask(chunksToDo, 1);
|
||||
Runnable g3 = new BiomeGenTask(chunksToDo, 2);
|
||||
Runnable g4 = new BiomeGenTask(chunksToDo, 3);
|
||||
Runnable g5 = new BiomeGenTask(chunksToDo, 4);
|
||||
Runnable g6 = new BiomeGenTask(chunksToDo, 5);
|
||||
|
||||
ExecutorService ex = Executors.newFixedThreadPool(6);
|
||||
ex.execute(g1);
|
||||
ex.execute(g2);
|
||||
ex.execute(g3);
|
||||
ex.execute(g4);
|
||||
ex.execute(g5);
|
||||
ex.execute(g6);
|
||||
|
||||
try {
|
||||
ex.shutdown();
|
||||
assertTrue(ex.awaitTermination(1, TimeUnit.MINUTES), "timed out.");
|
||||
} catch (InterruptedException e) {
|
||||
assertFalse(false);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBiomeGenerationSingleThread() {
|
||||
Runnable g1 = new BiomeGenTask(1680, 0);
|
||||
|
||||
ExecutorService ex = Executors.newFixedThreadPool(6);
|
||||
ex.execute(g1);
|
||||
|
||||
try {
|
||||
ex.shutdown();
|
||||
assertTrue(ex.awaitTermination(1, TimeUnit.MINUTES));
|
||||
} catch (InterruptedException e) {
|
||||
assertFalse(false, e.getCause().getMessage());
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user