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
|
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||||
"version": "0.2.0",
|
"version": "0.2.0",
|
||||||
"configurations": [
|
"configurations": [
|
||||||
|
{
|
||||||
|
"type": "java",
|
||||||
|
"name": "CodeLens (Launch) - UniBiomeIslandGeneratorTest",
|
||||||
|
"request": "launch",
|
||||||
|
"mainClass": "ca.recrown.islandsurvivalcraft.world.generation.UniBiomeIslandGeneratorTest",
|
||||||
|
"projectName": "IslandSurvivalCraft"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "java",
|
"type": "java",
|
||||||
"name": "Debug (Launch) - Current File",
|
"name": "Debug (Launch) - Current File",
|
||||||
@ -16,7 +23,9 @@
|
|||||||
"request": "attach",
|
"request": "attach",
|
||||||
"hostName": "localhost",
|
"hostName": "localhost",
|
||||||
"port": 25566,
|
"port": 25566,
|
||||||
"sourcePaths": ["src/main/java"],
|
"sourcePaths": [
|
||||||
|
"src/main/java"
|
||||||
|
],
|
||||||
"preLaunchTask": "start test server",
|
"preLaunchTask": "start test server",
|
||||||
"postDebugTask": "stop test server"
|
"postDebugTask": "stop test server"
|
||||||
}
|
}
|
||||||
|
10
pom.xml
10
pom.xml
@ -14,6 +14,12 @@
|
|||||||
</repository>
|
</repository>
|
||||||
</repositories>
|
</repositories>
|
||||||
<dependencies>
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.junit.jupiter</groupId>
|
||||||
|
<artifactId>junit-jupiter-api</artifactId>
|
||||||
|
<version>5.6.2</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.junit.jupiter</groupId>
|
<groupId>org.junit.jupiter</groupId>
|
||||||
<artifactId>junit-jupiter-engine</artifactId>
|
<artifactId>junit-jupiter-engine</artifactId>
|
||||||
@ -41,6 +47,10 @@
|
|||||||
<artifactId>maven-surefire-plugin</artifactId>
|
<artifactId>maven-surefire-plugin</artifactId>
|
||||||
<version>2.22.2</version>
|
<version>2.22.2</version>
|
||||||
</plugin>
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<artifactId>maven-failsafe-plugin</artifactId>
|
||||||
|
<version>2.22.2</version>
|
||||||
|
</plugin>
|
||||||
</plugins>
|
</plugins>
|
||||||
</build>
|
</build>
|
||||||
<properties>
|
<properties>
|
||||||
|
@ -11,6 +11,7 @@ public class IslandSurvivalCraft extends JavaPlugin {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onEnable() {
|
public void onEnable() {
|
||||||
|
|
||||||
super.onEnable();
|
super.onEnable();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@ import java.util.ArrayList;
|
|||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map.Entry;
|
import java.util.Map.Entry;
|
||||||
|
|
||||||
import ca.recrown.islandsurvivalcraft.types.Point2;
|
import ca.recrown.islandsurvivalcraft.datatypes.Point2;
|
||||||
|
|
||||||
public class Utilities {
|
public class Utilities {
|
||||||
public final static int CHUNK_SIZE = 16;
|
public final static int CHUNK_SIZE = 16;
|
||||||
|
@ -1,22 +1,20 @@
|
|||||||
package ca.recrown.islandsurvivalcraft.caching;
|
package ca.recrown.islandsurvivalcraft.caching;
|
||||||
|
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
|
||||||
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||||
import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock;
|
import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock;
|
||||||
import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock;
|
import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock;
|
||||||
|
|
||||||
public class Cache<Key, Value> {
|
public class Cache<K, V> {
|
||||||
private final int maxSize;
|
private final int maxSize;
|
||||||
private final ConcurrentHashMap<Key, CacheValue<Value>> data;
|
private final ConcurrentHashMap<K, CacheValue<K, V>> data;
|
||||||
private final ConcurrentLinkedQueue<Key> occurrenceOrder;
|
private final UsageStack<K, V> usage = new UsageStack<>();
|
||||||
private ReentrantReadWriteLock cleaningLock = new ReentrantReadWriteLock(true);
|
private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
|
||||||
private WriteLock writeLock = cleaningLock.writeLock();
|
private final WriteLock writeLock = lock.writeLock();
|
||||||
private ReadLock readLock = cleaningLock.readLock();
|
private final ReadLock readLock = lock.readLock();
|
||||||
|
|
||||||
public Cache(int maxSize) {
|
public Cache(int maxSize) {
|
||||||
data = new ConcurrentHashMap<>(maxSize + 1, 0.75f, 6);
|
data = new ConcurrentHashMap<>(maxSize + 1, 0.75f, 6);
|
||||||
occurrenceOrder = new ConcurrentLinkedQueue<>();
|
|
||||||
this.maxSize = maxSize;
|
this.maxSize = maxSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -24,30 +22,24 @@ public class Cache<Key, Value> {
|
|||||||
this(1024);
|
this(1024);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setValue(Key key, Value value) {
|
public void set(K key, V 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();
|
writeLock.lock();
|
||||||
try {
|
try {
|
||||||
occ = potential.occurrence;
|
if (data.containsKey(key)) {
|
||||||
if (occ < 1) {
|
data.get(key).value = value;
|
||||||
data.remove(potentialKey);
|
} 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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
writeLock.unlock();
|
writeLock.unlock();
|
||||||
}
|
}
|
||||||
} while (occ > 0);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -57,22 +49,26 @@ public class Cache<Key, Value> {
|
|||||||
* @param key the key associated with the value.
|
* @param key the key associated with the value.
|
||||||
* @return the value associated to the key.
|
* @return the value associated to the key.
|
||||||
*/
|
*/
|
||||||
public Value getValue(Key key) {
|
public V get(K key) {
|
||||||
CacheValue<Value> res = null;
|
|
||||||
readLock.lock();
|
readLock.lock();
|
||||||
|
CacheValue<K, V> value = null;
|
||||||
try {
|
try {
|
||||||
res = data.get(key);
|
if (!data.containsKey(key)) return null;
|
||||||
if (res == null) return null;
|
value = data.get(key);
|
||||||
res.occurrence++;
|
usage.tryMoveToTop(value);
|
||||||
occurrenceOrder.add(key);
|
|
||||||
} finally {
|
} finally {
|
||||||
readLock.unlock();
|
readLock.unlock();
|
||||||
}
|
}
|
||||||
return res.data;
|
return value.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void clearCache() {
|
public void clearCache() {
|
||||||
|
writeLock.lock();
|
||||||
|
try {
|
||||||
data.clear();
|
data.clear();
|
||||||
occurrenceOrder.clear();
|
usage.clear();
|
||||||
|
} finally {
|
||||||
|
writeLock.unlock();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,10 +1,7 @@
|
|||||||
package ca.recrown.islandsurvivalcraft.caching;
|
package ca.recrown.islandsurvivalcraft.caching;
|
||||||
|
|
||||||
class CacheValue<ValueType> {
|
class CacheValue<KeyType, ValueType> {
|
||||||
public volatile int occurrence = 1;
|
public volatile KeyType key;
|
||||||
public final ValueType data;
|
public volatile ValueType value;
|
||||||
|
public volatile CacheValue<KeyType, ValueType> front, back;
|
||||||
public CacheValue(ValueType value) {
|
|
||||||
this.data = value;
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -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;
|
import java.util.Objects;
|
||||||
|
|
@ -1,6 +1,6 @@
|
|||||||
package ca.recrown.islandsurvivalcraft.floodfill;
|
package ca.recrown.islandsurvivalcraft.floodfill;
|
||||||
|
|
||||||
import ca.recrown.islandsurvivalcraft.types.Point2;
|
import ca.recrown.islandsurvivalcraft.datatypes.Point2;
|
||||||
|
|
||||||
public interface Floodable {
|
public interface Floodable {
|
||||||
/**
|
/**
|
||||||
|
@ -3,7 +3,7 @@ package ca.recrown.islandsurvivalcraft.floodfill;
|
|||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
|
|
||||||
import ca.recrown.islandsurvivalcraft.types.Point2;
|
import ca.recrown.islandsurvivalcraft.datatypes.Point2;
|
||||||
|
|
||||||
public class Flooder {
|
public class Flooder {
|
||||||
private final Point2 start;
|
private final Point2 start;
|
||||||
|
@ -2,7 +2,7 @@ package ca.recrown.islandsurvivalcraft.pathfinding;
|
|||||||
|
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
import ca.recrown.islandsurvivalcraft.types.Point2;
|
import ca.recrown.islandsurvivalcraft.datatypes.Point2;
|
||||||
|
|
||||||
public class DFSNode extends Point2 implements Comparable<DFSNode> {
|
public class DFSNode extends Point2 implements Comparable<DFSNode> {
|
||||||
private DFSNode goal;
|
private DFSNode goal;
|
||||||
|
@ -6,7 +6,7 @@ import java.util.Queue;
|
|||||||
|
|
||||||
import org.apache.commons.lang.NullArgumentException;
|
import org.apache.commons.lang.NullArgumentException;
|
||||||
|
|
||||||
import ca.recrown.islandsurvivalcraft.types.Point2;
|
import ca.recrown.islandsurvivalcraft.datatypes.Point2;
|
||||||
|
|
||||||
public class DepthFirstSearch {
|
public class DepthFirstSearch {
|
||||||
private Queue<DFSNode> queue;
|
private Queue<DFSNode> queue;
|
||||||
|
@ -4,7 +4,7 @@ import java.util.Random;
|
|||||||
|
|
||||||
import org.bukkit.util.noise.SimplexOctaveGenerator;
|
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.caching.Cache;
|
||||||
import ca.recrown.islandsurvivalcraft.pathfinding.CoordinateValidatable;
|
import ca.recrown.islandsurvivalcraft.pathfinding.CoordinateValidatable;
|
||||||
import ca.recrown.islandsurvivalcraft.pathfinding.DepthFirstSearch;
|
import ca.recrown.islandsurvivalcraft.pathfinding.DepthFirstSearch;
|
||||||
@ -24,11 +24,11 @@ public class IslandWorldMapper implements CoordinateValidatable {
|
|||||||
private final float shallowPortion = 0.015f;
|
private final float shallowPortion = 0.015f;
|
||||||
private final DepthFirstSearch dfs;
|
private final DepthFirstSearch dfs;
|
||||||
|
|
||||||
public IslandWorldMapper(Random random) {
|
public IslandWorldMapper(Random random, Cache<Point2, Double> blockValueCache) {
|
||||||
dfs = new DepthFirstSearch(this);
|
dfs = new DepthFirstSearch(this);
|
||||||
this.noiseGenerator = new SimplexOctaveGenerator(random, noiseOctaves);
|
this.noiseGenerator = new SimplexOctaveGenerator(random, noiseOctaves);
|
||||||
noiseGenerator.setScale(scale);
|
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) {
|
public double getWorldBlockValue(int worldX, int worldZ) {
|
||||||
Point2 p = new Point2(worldX, worldZ);
|
Point2 p = new Point2(worldX, worldZ);
|
||||||
|
|
||||||
Double res = blockValueCache.getValue(p);
|
Double res = blockValueCache.get(p);
|
||||||
if (res == null) {
|
if (res == null) {
|
||||||
double portionSea = 1f - (this.islandBlockGenerationPercent / 100f);
|
double portionSea = 1f - (this.islandBlockGenerationPercent / 100f);
|
||||||
double shift = 1f - 2 * portionSea;
|
double shift = 1f - 2 * portionSea;
|
||||||
@ -133,7 +133,7 @@ public class IslandWorldMapper implements CoordinateValidatable {
|
|||||||
} else {
|
} else {
|
||||||
res = noise / maxPos;
|
res = noise / maxPos;
|
||||||
}
|
}
|
||||||
blockValueCache.setValue(p, res);
|
blockValueCache.set(p, res);
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,6 +3,8 @@ package ca.recrown.islandsurvivalcraft.world.generation;
|
|||||||
import org.bukkit.World;
|
import org.bukkit.World;
|
||||||
import org.bukkit.block.Biome;
|
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.BiomeSelector;
|
||||||
import ca.recrown.islandsurvivalcraft.world.IslandWorldMapper;
|
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.
|
* 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.
|
* 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.
|
* 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 biomesArray The array of biomes that are to be saturated.
|
||||||
* @param world The current world.
|
* @param world The current world.
|
||||||
* @param chunkX The X coordinate of the chunk.
|
* @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 localZ The Z coordinate of the column within the chunk.
|
||||||
* @param mapper The island mapper to be used.
|
* @param mapper The island mapper to be used.
|
||||||
* @param tempGenerator The temperature generator 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);
|
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);
|
||||||
|
|
||||||
/**
|
|
||||||
* 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);
|
|
||||||
}
|
}
|
@ -7,43 +7,34 @@ import org.bukkit.Material;
|
|||||||
import org.bukkit.World;
|
import org.bukkit.World;
|
||||||
import org.bukkit.block.Biome;
|
import org.bukkit.block.Biome;
|
||||||
import org.bukkit.event.EventHandler;
|
import org.bukkit.event.EventHandler;
|
||||||
|
import org.bukkit.event.EventPriority;
|
||||||
import org.bukkit.event.Listener;
|
import org.bukkit.event.Listener;
|
||||||
import org.bukkit.event.world.ChunkLoadEvent;
|
import org.bukkit.event.world.ChunkLoadEvent;
|
||||||
import org.bukkit.generator.ChunkGenerator;
|
import org.bukkit.generator.ChunkGenerator;
|
||||||
|
|
||||||
import ca.recrown.islandsurvivalcraft.Utilities;
|
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.BiomeSelector;
|
||||||
import ca.recrown.islandsurvivalcraft.world.IslandWorldMapper;
|
import ca.recrown.islandsurvivalcraft.world.IslandWorldMapper;
|
||||||
import ca.recrown.islandsurvivalcraft.world.shaders.WorldHeightShader;
|
import ca.recrown.islandsurvivalcraft.world.shaders.WorldHeightShader;
|
||||||
|
|
||||||
public class IslandWorldChunkGenerator extends ChunkGenerator implements Listener {
|
public class IslandWorldChunkGenerator extends ChunkGenerator implements Listener {
|
||||||
private final BiomeGenerator biomeGenerator;
|
private final Cache<Point2, Double> blockValueCache = new Cache<>(102400);
|
||||||
private final TemperatureMapGenerator temperatureGenerator = new TemperatureMapGenerator();
|
private final Cache<Point2, Biome> biomeCache = new Cache<>(102400);
|
||||||
private volatile Random random;
|
private final Cache<Point2, Boolean> chunkExistenceCache = new Cache<>(16384);
|
||||||
private volatile IslandWorldMapper mapper;
|
private volatile World currentWorld;
|
||||||
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();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ChunkData generateChunkData(World world, Random random, int x, int z, BiomeGrid biome) {
|
public ChunkData generateChunkData(World world, Random random, int x, int z, BiomeGrid biome) {
|
||||||
if (this.random != random) {
|
this.currentWorld = world;
|
||||||
this.random = random;
|
IslandWorldMapper mapper = new IslandWorldMapper(random, blockValueCache);
|
||||||
mapper = new IslandWorldMapper(random);
|
TemperatureMapGenerator temperatureMapGenerator = new TemperatureMapGenerator(world.getSeed());
|
||||||
temperatureGenerator.setSeed(world.getSeed());
|
BiomeSelector biomeSelector = new BiomeSelector(random);
|
||||||
biomeSelector = new BiomeSelector(random);
|
|
||||||
biomeSelector.initialize();
|
biomeSelector.initialize();
|
||||||
this.world = world;
|
WorldHeightShader heightShader = new WorldHeightShader(world.getSeed(), mapper, world.getSeaLevel(), world.getMaxHeight(), 3);
|
||||||
this.heightShader = new WorldHeightShader(world.getSeed(), mapper, world.getSeaLevel(), world.getMaxHeight(), 3);
|
BiomeGenerator biomeGenerator = new UniBiomeIslandGenerator();
|
||||||
}
|
|
||||||
int maxHeight = world.getMaxHeight();
|
int maxHeight = world.getMaxHeight();
|
||||||
ChunkData chunkData = createChunkData(world);
|
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 localX = 0; localX < Utilities.CHUNK_SIZE; localX++) {
|
||||||
for (int localZ = 0; localZ < Utilities.CHUNK_SIZE; localZ++) {
|
for (int localZ = 0; localZ < Utilities.CHUNK_SIZE; localZ++) {
|
||||||
if (biomes[localX][localZ] == null) {
|
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.");
|
if (biomes[localX][localZ] == null) throw new IllegalStateException("Biome was null.");
|
||||||
for (int y = 0; y < maxHeight; y++) {
|
for (int y = 0; y < maxHeight; y++) {
|
||||||
@ -68,12 +59,13 @@ public class IslandWorldChunkGenerator extends ChunkGenerator implements Listene
|
|||||||
return chunkData;
|
return chunkData;
|
||||||
}
|
}
|
||||||
|
|
||||||
@EventHandler
|
@EventHandler(priority = EventPriority.MONITOR)
|
||||||
public void onChunkLoaded(ChunkLoadEvent event) {
|
public void onChunkLoaded(ChunkLoadEvent event) {
|
||||||
if (event.isNewChunk()) {
|
if (event.isNewChunk()) {
|
||||||
if (world == event.getWorld()) {
|
if (currentWorld == event.getWorld()) {
|
||||||
Chunk chunk = event.getChunk();
|
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 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.caching.Cache;
|
||||||
|
|
||||||
class TemperatureMapGenerator {
|
class TemperatureMapGenerator {
|
||||||
@ -30,10 +30,10 @@ class TemperatureMapGenerator {
|
|||||||
|
|
||||||
public float getTemperature(int worldX, int worldZ) {
|
public float getTemperature(int worldX, int worldZ) {
|
||||||
Point2 loc = new Point2(worldX/4, worldZ/4);
|
Point2 loc = new Point2(worldX/4, worldZ/4);
|
||||||
Float val = temperatureCache.getValue(loc);
|
Float val = temperatureCache.get(loc);
|
||||||
if (val == null) {
|
if (val == null) {
|
||||||
val = (float) noiseGenerator.noise(worldX/4, worldZ/4, frequency, amplitude, true);
|
val = (float) noiseGenerator.noise(worldX/4, worldZ/4, frequency, amplitude, true);
|
||||||
temperatureCache.setValue(loc, val);
|
temperatureCache.set(loc, val);
|
||||||
}
|
}
|
||||||
return val;
|
return val;
|
||||||
}
|
}
|
||||||
|
@ -4,27 +4,25 @@ import org.bukkit.World;
|
|||||||
import org.bukkit.block.Biome;
|
import org.bukkit.block.Biome;
|
||||||
|
|
||||||
import ca.recrown.islandsurvivalcraft.Utilities;
|
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.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.CoordinateTargetValidatable;
|
||||||
import ca.recrown.islandsurvivalcraft.pathfinding.CoordinateValidatable;
|
|
||||||
import ca.recrown.islandsurvivalcraft.pathfinding.DepthFirstSearch;
|
import ca.recrown.islandsurvivalcraft.pathfinding.DepthFirstSearch;
|
||||||
import ca.recrown.islandsurvivalcraft.world.BiomeSelector;
|
import ca.recrown.islandsurvivalcraft.world.BiomeSelector;
|
||||||
import ca.recrown.islandsurvivalcraft.world.IslandWorldMapper;
|
import ca.recrown.islandsurvivalcraft.world.IslandWorldMapper;
|
||||||
|
|
||||||
public class UniBiomeIslandGenerator implements BiomeGenerator {
|
public class UniBiomeIslandGenerator implements BiomeGenerator {
|
||||||
private final Cache<Point2, Biome> biomeCache = new Cache<>(12800);
|
|
||||||
private final Cache<Point2, Boolean> existenceCache = new Cache<>(12800);
|
|
||||||
|
|
||||||
@Override
|
@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 worldX = 16 * chunkX + localX;
|
||||||
int worldZ = 16 * chunkZ + localZ;
|
int worldZ = 16 * chunkZ + localZ;
|
||||||
Point2 chunkCoords = Utilities.worldToChunkCoordinates(new Point2(worldX, worldZ));
|
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.
|
//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) {
|
if (biome != null) {
|
||||||
biomes[localX][localZ] = biome;
|
biomes[localX][localZ] = biome;
|
||||||
return;
|
return;
|
||||||
@ -32,7 +30,7 @@ public class UniBiomeIslandGenerator implements BiomeGenerator {
|
|||||||
|
|
||||||
//Fine, check if it's ocean.
|
//Fine, check if it's ocean.
|
||||||
if (!mapper.isIsland(worldX, worldZ)) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -40,7 +38,7 @@ public class UniBiomeIslandGenerator implements BiomeGenerator {
|
|||||||
DepthFirstSearch search = new DepthFirstSearch();
|
DepthFirstSearch search = new DepthFirstSearch();
|
||||||
search.setValidatable(mapper);
|
search.setValidatable(mapper);
|
||||||
|
|
||||||
IslandInfo islandInfo = new IslandInfo(mapper, world);
|
IslandInfo islandInfo = new IslandInfo(mapper, world, biomeCache, chunkGenCache);
|
||||||
search.setStartPosition(worldX, worldZ);
|
search.setStartPosition(worldX, worldZ);
|
||||||
if (!search.findTarget(islandInfo)) {
|
if (!search.findTarget(islandInfo)) {
|
||||||
float temp = tempGen.getTemperature(worldX, worldZ);
|
float temp = tempGen.getTemperature(worldX, worldZ);
|
||||||
@ -49,105 +47,105 @@ public class UniBiomeIslandGenerator implements BiomeGenerator {
|
|||||||
if (islandInfo.shallow == null) islandInfo.shallow = biomeSelector.getOceanBiome(temp);
|
if (islandInfo.shallow == null) islandInfo.shallow = biomeSelector.getOceanBiome(temp);
|
||||||
}
|
}
|
||||||
|
|
||||||
PropagatorInfo propInfo = new PropagatorInfo(islandInfo, biomes, new Point2(chunkX, chunkZ), mapper);
|
PropagatorInfo propInfo = new PropagatorInfo(islandInfo, biomes, new Point2(chunkX, chunkZ), mapper, biomeCache);
|
||||||
search.setValidatable(propInfo);
|
Flooder flooder = new Flooder(new Point2(worldX, worldZ), propInfo);
|
||||||
search.setStartPosition(worldX, worldZ);
|
flooder.start();
|
||||||
search.findTarget(propInfo);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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);
|
Point2 worldCoords = new Point2(worldX, worldZ);
|
||||||
Biome res = null;
|
Biome res = null;
|
||||||
|
|
||||||
|
|
||||||
res = biomeCache.getValue(worldCoords);
|
res = biomeCache.get(worldCoords);
|
||||||
if (res != null) return res;
|
if (res != null) return res;
|
||||||
|
|
||||||
Point2 chunkCoords = Utilities.worldToChunkCoordinates(worldCoords);
|
Point2 chunkCoords = Utilities.worldToChunkCoordinates(worldCoords);
|
||||||
Boolean chunkExists = existenceCache.getValue(chunkCoords);
|
Boolean chunkExists = existenceCache.get(chunkCoords);
|
||||||
if (chunkExists == null) {
|
if (chunkExists == null) {
|
||||||
chunkExists = world.isChunkGenerated(worldX, worldZ);
|
chunkExists = world.isChunkGenerated(worldX, worldZ);
|
||||||
existenceCache.setValue(chunkCoords, chunkExists);
|
existenceCache.set(chunkCoords, chunkExists);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (chunkExists) {
|
if (chunkExists) {
|
||||||
res = world.getBiome(worldX, 0, worldZ);
|
res = world.getBiome(worldX, 0, worldZ);
|
||||||
biomeCache.setValue(worldCoords, res);
|
biomeCache.set(worldCoords, res);
|
||||||
}
|
}
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void chunkGenerated(int chunkX, int chunkZ) {
|
private void setCacheBiome(int worldX, int worldZ, Biome biome, Biome[][] localBiomes, Cache<Point2, Biome> biomeCache) {
|
||||||
existenceCache.setValue(new Point2(chunkX, chunkZ), true);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setCacheBiome(int worldX, int worldZ, Biome biome, Biome[][] biomes) {
|
|
||||||
Point2 worldCoords = new Point2(worldX, worldZ);
|
Point2 worldCoords = new Point2(worldX, worldZ);
|
||||||
if (biomes != null) {
|
if (localBiomes != null) {
|
||||||
Point2 localCoords = Utilities.worldToLocalChunkCoordinates(worldCoords);
|
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 {
|
private class IslandInfo implements CoordinateTargetValidatable {
|
||||||
public final IslandWorldMapper mapper;
|
public final IslandWorldMapper mapper;
|
||||||
public final World world;
|
public final World world;
|
||||||
|
private final Cache<Point2, Biome> biomeCache;
|
||||||
|
private final Cache<Point2, Boolean> chunkGenCache;
|
||||||
public Biome main, shore, shallow;
|
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.mapper = mapper;
|
||||||
this.world = world;
|
this.world = world;
|
||||||
|
this.biomeCache = biomeCache;
|
||||||
|
this.chunkGenCache = chunkGenCache;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isCoordinateTarget(int x, int y) {
|
public boolean isCoordinateTarget(int x, int y) {
|
||||||
if (mapper.isLand(x, y)) {
|
if (mapper.isLand(x, y)) {
|
||||||
if (mapper.isShore(x, y)) {
|
if (mapper.isShore(x, y)) {
|
||||||
shore = getSavedBiome(world, x, y);
|
shore = getSavedBiome(world, x, y, biomeCache, chunkGenCache);
|
||||||
} else {
|
} else {
|
||||||
main = getSavedBiome(world, x, y);
|
main = getSavedBiome(world, x, y, biomeCache, chunkGenCache);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
shallow = getSavedBiome(world, x, y);
|
shallow = getSavedBiome(world, x, y, biomeCache, chunkGenCache);
|
||||||
}
|
}
|
||||||
return main != null && shore != null && shallow != null;
|
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 shallow, shore, main;
|
||||||
private final Biome[][] biomes;
|
private final Biome[][] biomes;
|
||||||
private final Point2 chunkCoords;
|
private final Point2 chunkCoords;
|
||||||
private final IslandWorldMapper mapper;
|
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.shallow = islandInfo.shallow;
|
||||||
this.shore = islandInfo.shore;
|
this.shore = islandInfo.shore;
|
||||||
this.main = islandInfo.main;
|
this.main = islandInfo.main;
|
||||||
this.biomes = biomes;
|
this.biomes = biomes;
|
||||||
this.chunkCoords = chunkCoords;
|
this.chunkCoords = chunkCoords;
|
||||||
this.mapper = mapper;
|
this.mapper = mapper;
|
||||||
|
this.biomeCache = biomeCache;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean validate(int x, int y) {
|
public boolean flood(Point2 point) {
|
||||||
Point2 chunkCoords = Utilities.worldToChunkCoordinates(new Point2(x, y));
|
Point2 chunkCoords = Utilities.worldToChunkCoordinates(point);
|
||||||
return this.chunkCoords.fastEquals(chunkCoords) && mapper.validate(x, y);
|
if (!this.chunkCoords.fastEquals(chunkCoords) || !mapper.validate(point.x, point.y)) return false;
|
||||||
}
|
int x = point.x;
|
||||||
|
int y = point.y;
|
||||||
@Override
|
|
||||||
public boolean isCoordinateTarget(int x, int y) {
|
|
||||||
if (mapper.isLand(x, y)) {
|
if (mapper.isLand(x, y)) {
|
||||||
if (mapper.isShore(x, y)) {
|
if (mapper.isShore(x, y)) {
|
||||||
setCacheBiome(x, y, shore, biomes);
|
setCacheBiome(x, y, shore, biomes, biomeCache);
|
||||||
} else {
|
} else {
|
||||||
setCacheBiome(x, y, main, biomes);
|
setCacheBiome(x, y, main, biomes, biomeCache);
|
||||||
}
|
}
|
||||||
} else {
|
} 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;
|
||||||
import org.junit.jupiter.api.TestInstance.Lifecycle;
|
import org.junit.jupiter.api.TestInstance.Lifecycle;
|
||||||
|
|
||||||
import ca.recrown.islandsurvivalcraft.types.Point2;
|
import ca.recrown.islandsurvivalcraft.datatypes.Point2;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Unit test for simple App.
|
* Unit test for simple App.
|
||||||
|
@ -2,9 +2,14 @@ package ca.recrown.islandsurvivalcraft.caching;
|
|||||||
|
|
||||||
import static org.junit.Assert.assertSame;
|
import static org.junit.Assert.assertSame;
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
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 static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
import java.util.Random;
|
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.AfterEach;
|
||||||
import org.junit.jupiter.api.BeforeAll;
|
import org.junit.jupiter.api.BeforeAll;
|
||||||
@ -28,35 +33,35 @@ public class CacheTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testBasicCaching() {
|
public void testBasicCaching() {
|
||||||
String val = integerCache.getValue(0);
|
String val = integerCache.get(0);
|
||||||
assertTrue(val == null);
|
assertTrue(val == null);
|
||||||
val = "first";
|
val = "first";
|
||||||
integerCache.setValue(0, val);
|
integerCache.set(0, val);
|
||||||
assertTrue(integerCache.getValue(0) != null);
|
assertTrue(integerCache.get(0) != null);
|
||||||
assertSame(val, integerCache.getValue(0));
|
assertSame(val, integerCache.get(0));
|
||||||
|
|
||||||
val = integerCache.getValue(1);
|
val = integerCache.get(1);
|
||||||
assertTrue(val == null);
|
assertTrue(val == null);
|
||||||
val = "second";
|
val = "second";
|
||||||
integerCache.setValue(1, val);
|
integerCache.set(1, val);
|
||||||
assertTrue(integerCache.getValue(1) != null);
|
assertTrue(integerCache.get(1) != null);
|
||||||
assertSame(val, integerCache.getValue(1));
|
assertSame(val, integerCache.get(1));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testUsageBasedClean() {
|
public void testUsageBasedClean() {
|
||||||
integerCache.setValue(0, "first");
|
integerCache.set(0, "first");
|
||||||
integerCache.setValue(1, "second");
|
integerCache.set(1, "second");
|
||||||
integerCache.setValue(2, "third");
|
integerCache.set(2, "third");
|
||||||
|
|
||||||
assertEquals("first", integerCache.getValue(0));
|
assertEquals("first", integerCache.get(0));
|
||||||
|
|
||||||
integerCache.setValue(3, "fourth");
|
integerCache.set(3, "fourth");
|
||||||
assertEquals("first", integerCache.getValue(0));
|
assertEquals("first", integerCache.get(0));
|
||||||
|
|
||||||
integerCache.setValue(4, "fifth");
|
integerCache.set(4, "fifth");
|
||||||
assertTrue(integerCache.getValue(3) != null);
|
assertTrue(integerCache.get(3) != null);
|
||||||
assertTrue(integerCache.getValue(0) != null);
|
assertTrue(integerCache.get(0) != null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -64,20 +69,20 @@ public class CacheTest {
|
|||||||
int amount = 1024;
|
int amount = 1024;
|
||||||
Random random = new Random();
|
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];
|
int[] values = new int[amount];
|
||||||
for (int i = 0; i < amount; i++) {
|
for (int i = 0; i < amount; i++) {
|
||||||
values[i] = random.nextInt();
|
values[i] = random.nextInt();
|
||||||
largeCache.setValue(i, values[i]);
|
largeCache.set(i, values[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 0; i < amount /2; i++) {
|
for (int i = 0; i < amount / 2; i++) {
|
||||||
assertEquals(null, largeCache.getValue(i), "Current accessor: " + i);
|
assertEquals(null, largeCache.get(i), "Current accessor: " + i);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = amount /2; i < amount; i++) {
|
for (int i = amount / 2; i < amount; i++) {
|
||||||
assertEquals(values[i], largeCache.getValue(i), "Current accessor: " + i);
|
assertEquals(values[i], largeCache.get(i), "Current accessor: " + i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -86,23 +91,194 @@ public class CacheTest {
|
|||||||
int amount = 1024;
|
int amount = 1024;
|
||||||
Random random = new Random();
|
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];
|
int[] values = new int[amount];
|
||||||
for (int i = 0; i < amount; i++) {
|
for (int i = 0; i < amount; i++) {
|
||||||
values[i] = random.nextInt();
|
values[i] = random.nextInt();
|
||||||
largeCache.setValue(i, values[i]);
|
largeCache.set(i, values[i]);
|
||||||
largeCache.getValue(0);
|
largeCache.get(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 1; i < (amount/2) + 1; i++) {
|
for (int i = 1; i < (amount / 2) + 1; i++) {
|
||||||
assertEquals(null, largeCache.getValue(i), "Current accessor: " + i);
|
assertEquals(null, largeCache.get(i), "Current accessor: " + i);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = (amount /2) + 1; i < amount; i++) {
|
for (int i = (amount / 2) + 1; i < amount; i++) {
|
||||||
assertEquals(values[i], largeCache.getValue(i), "Current accessor: " + 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;
|
||||||
import org.junit.jupiter.api.TestInstance.Lifecycle;
|
import org.junit.jupiter.api.TestInstance.Lifecycle;
|
||||||
|
|
||||||
import ca.recrown.islandsurvivalcraft.types.Point2;
|
import ca.recrown.islandsurvivalcraft.datatypes.Point2;
|
||||||
|
|
||||||
@TestInstance(Lifecycle.PER_CLASS)
|
@TestInstance(Lifecycle.PER_CLASS)
|
||||||
public class FloodFillTest {
|
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…
x
Reference in New Issue
Block a user