Attempted at making all things world gen thread proof.

Specifically, caching is tested thread proof.
This commit is contained in:
Harrison Deng 2020-04-26 01:28:45 -05:00
parent f20515fd45
commit 98e4265db7
10 changed files with 85 additions and 89 deletions

View File

@ -3,15 +3,13 @@ package ca.recrown.islandsurvivalcraft.Types;
import java.util.Objects; import java.util.Objects;
public class Point2 { public class Point2 {
public int x, y; public final int x, y;
private final int hash;
public Point2(int x, int y) { public Point2(int x, int y) {
this.x = x; this.x = x;
this.y = y; this.y = y;
} this.hash = Objects.hash(x, y);
public Point2() {
this(0, 0);
} }
@Override @Override
@ -23,8 +21,12 @@ public class Point2 {
return false; return false;
} }
public boolean fastEquals(Point2 point) {
return point.hashCode() == this.hash && x == point.x && y == point.y;
}
@Override @Override
public int hashCode() { public int hashCode() {
return Objects.hash(x, y); return hash;
} }
} }

View File

@ -2,16 +2,19 @@ package ca.recrown.islandsurvivalcraft.caching;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.locks.ReentrantLock;
public class Cache<Key, Value> { public class Cache<Key, Value> {
private final int maxSize; private final int maxSize;
private final ConcurrentHashMap<Key, CacheValue<Value>> data; private final ConcurrentHashMap<Key, CacheValue<Value>> data;
private final ConcurrentLinkedQueue<Key> occurrenceOrder; private final ConcurrentLinkedQueue<Key> occurrenceOrder;
private ReentrantLock cleaningLock;
public Cache(int maxSize) { public Cache(int maxSize) {
data = new ConcurrentHashMap<>(maxSize); data = new ConcurrentHashMap<>(maxSize);
occurrenceOrder = new ConcurrentLinkedQueue<>(); occurrenceOrder = new ConcurrentLinkedQueue<>();
this.maxSize = maxSize; this.maxSize = maxSize;
cleaningLock = new ReentrantLock(true);
} }
public Cache() { public Cache() {
@ -19,17 +22,26 @@ public class Cache<Key, Value> {
} }
public void setValue(Key key, Value value) { public void setValue(Key key, Value value) {
data.put(key, new CacheValue<>(value)); CacheValue<Value> previous = data.get(key);
CacheValue<Value> fresh = new CacheValue<>(value);
if (previous != null) fresh.occurrence += previous.occurrence;
occurrenceOrder.add(key); occurrenceOrder.add(key);
data.put(key, fresh);
if (data.size() > maxSize) { if (data.size() > maxSize) {
int occ = 0; int occ = 0;
do { do {
Key potentialKey = occurrenceOrder.poll(); cleaningLock.lock();
CacheValue<Value> potential = data.get(potentialKey); try {
potential.decreaseOccurence(); Key potentialKey = occurrenceOrder.poll();
occ = potential.getOccurrence(); CacheValue<Value> potential = data.get(potentialKey);
if (occ < 1) { potential.occurrence--;
data.remove(potentialKey); occ = potential.occurrence;
if (occ < 1) {
data.remove(potentialKey);
}
} finally {
cleaningLock.unlock();
} }
} while (occ > 0); } while (occ > 0);
} }
@ -43,12 +55,18 @@ public class Cache<Key, Value> {
* @return the value associated to the key. * @return the value associated to the key.
*/ */
public Value getValue(Key key) { public Value getValue(Key key) {
CacheValue<Value> res = data.get(key); CacheValue<Value> res = null;
if (res == null) return null; cleaningLock.lock();
try {
res = data.get(key);
if (res == null) return null;
res.occurrence++;
occurrenceOrder.add(key);
} finally {
cleaningLock.unlock();
}
res.increaseOccurrence(); return res.data;
occurrenceOrder.add(key);
return res.getValue();
} }
public void clearCache() { public void clearCache() {

View File

@ -1,32 +1,10 @@
package ca.recrown.islandsurvivalcraft.caching; package ca.recrown.islandsurvivalcraft.caching;
public class CacheValue <ValueType> { class CacheValue<ValueType> {
private int occurrence = 1; public volatile int occurrence = 1;
private final ValueType value; public final ValueType data;
/**
* @return the occurrence
*/
public int getOccurrence() {
return occurrence;
}
public void increaseOccurrence() {
occurrence++;
}
public void decreaseOccurence() {
occurrence--;
}
public CacheValue(ValueType value) { public CacheValue(ValueType value) {
this.value = value; this.data = value;
}
/**
* @return the value
*/
public ValueType getValue() {
return value;
} }
} }

View File

@ -1,7 +1,8 @@
package ca.recrown.islandsurvivalcraft.world; package ca.recrown.islandsurvivalcraft.world;
import java.util.HashMap;
import java.util.Random; import java.util.Random;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import org.bukkit.World; import org.bukkit.World;
@ -13,19 +14,19 @@ import ca.recrown.islandsurvivalcraft.world.generation.IslandWorldGenerator;
* Uses IslandWorldGenerator as the container for each world. * Uses IslandWorldGenerator as the container for each world.
*/ */
public class IslandWorldGeneratorAlternator { public class IslandWorldGeneratorAlternator {
private HashMap<Long, IslandWorldGenerator> chunkGenerator; private final ConcurrentHashMap<UUID, IslandWorldGenerator> chunkGenerators;
private IslandBiomeGenerator islandBiomeGenerator; private final IslandBiomeGenerator islandBiomeGenerator;
public IslandWorldGeneratorAlternator(IslandBiomeGenerator biomeGenerator) { public IslandWorldGeneratorAlternator(IslandBiomeGenerator biomeGenerator) {
chunkGenerator = new HashMap<>(); this.chunkGenerators = new ConcurrentHashMap<>();
this.islandBiomeGenerator = biomeGenerator; this.islandBiomeGenerator = biomeGenerator;
} }
public synchronized IslandWorldGenerator getIslandChunkGeneratorSystem(World world, Random random) { public IslandWorldGenerator getIslandChunkGeneratorSystem(World world, Random random) {
long tid = Thread.currentThread().getId(); UUID wUuid = world.getUID();
if (!chunkGenerator.containsKey(tid)) { if (!chunkGenerators.containsKey(wUuid)) {
chunkGenerator.put(tid, new IslandWorldGenerator(world, islandBiomeGenerator.getInstance(), random)); chunkGenerators.put(wUuid, new IslandWorldGenerator(world, islandBiomeGenerator.getInstance(), random));
} }
return chunkGenerator.get(tid); return chunkGenerators.get(wUuid);
} }
} }

View File

@ -10,9 +10,9 @@ import ca.recrown.islandsurvivalcraft.pathfinding.CoordinateValidatable;
import ca.recrown.islandsurvivalcraft.pathfinding.DepthFirstSearch; import ca.recrown.islandsurvivalcraft.pathfinding.DepthFirstSearch;
public class IslandWorldMapper implements CoordinateValidatable { public class IslandWorldMapper implements CoordinateValidatable {
private Cache<Point2, Double> blockValueCache; private final Cache<Point2, Double> blockValueCache;
private SimplexOctaveGenerator noiseGenerator; private final SimplexOctaveGenerator noiseGenerator;
private final int noiseOctaves = 4; private final int noiseOctaves = 4;
private final float islandBlockGenerationPercent = 16; private final float islandBlockGenerationPercent = 16;
private final float exaggerationFactor = 1.2f; private final float exaggerationFactor = 1.2f;
@ -28,7 +28,7 @@ public class IslandWorldMapper implements CoordinateValidatable {
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<>(4096); blockValueCache = new Cache<>(32768);
} }
/** /**
@ -113,6 +113,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.getValue(p);
if (res == null) { if (res == null) {
double portionSea = 1f - (this.islandBlockGenerationPercent / 100f); double portionSea = 1f - (this.islandBlockGenerationPercent / 100f);

View File

@ -5,7 +5,7 @@ import java.util.Random;
import org.bukkit.util.noise.SimplexOctaveGenerator; import org.bukkit.util.noise.SimplexOctaveGenerator;
public class BedrockGenerator { public class BedrockGenerator {
private SimplexOctaveGenerator noiseGenerator; private final SimplexOctaveGenerator noiseGenerator;
private final int maxBedrockHeight; private final int maxBedrockHeight;
private final int minBedrockHeight; private final int minBedrockHeight;

View File

@ -1,7 +1,5 @@
package ca.recrown.islandsurvivalcraft.world.generation; package ca.recrown.islandsurvivalcraft.world.generation;
import java.util.Arrays;
import org.bukkit.World; import org.bukkit.World;
import org.bukkit.block.Biome; import org.bukkit.block.Biome;
@ -16,32 +14,31 @@ import ca.recrown.islandsurvivalcraft.world.IslandWorldMapper;
//Note: technically, the validators have to be run on land, and so, some condition checks may not be nessecary. //Note: technically, the validators have to be run on land, and so, some condition checks may not be nessecary.
public class BiomePerIslandGenerator implements IslandBiomeGenerator { public class BiomePerIslandGenerator implements IslandBiomeGenerator {
private boolean initialized; private volatile boolean initialized;
private final TemperatureMapGenerator temperatureMapGenerator; private final TemperatureMapGenerator temperatureMapGenerator;
private final Cache<Point2, Biome[][]> chunkBiomesCache; private final Cache<Point2, Biome[][]> chunkBiomesCache;
private final Cache<Point2, Boolean> chunkGenStatusCache; private final Cache<Point2, Boolean> chunkGenStatusCache;
private IslandWorldMapper worldIslandMap; private volatile IslandWorldMapper worldIslandMap;
private BiomeSelector biomeSelector; private volatile BiomeSelector biomeSelector;
private World world; private volatile World world;
private final DepthFirstSearch freshCachePropagator; private final DepthFirstSearch freshCachePropagator;
private final DepthFirstSearch existenceChecker; private final DepthFirstSearch existenceChecker;
private FreshCachePropagationInfo freshCachePropInfo; private final FreshCachePropagationInfo freshCachePropInfo;
private PreviousGenerationInfo existenceInfo; private final PreviousGenerationInfo existenceInfo;
private Point2 currChunkCoords; private volatile Point2 currChunkCoords;
private final Biome[][] localChunkCache; private volatile Biome[][] localChunkCache;
float temperature; float temperature;
public BiomePerIslandGenerator() { public BiomePerIslandGenerator() {
this.temperatureMapGenerator = new TemperatureMapGenerator(); this.temperatureMapGenerator = new TemperatureMapGenerator();
chunkBiomesCache = new Cache<>(1024); chunkBiomesCache = new Cache<>(512);
chunkGenStatusCache = new Cache<>(1024); chunkGenStatusCache = new Cache<>(512);
freshCachePropInfo = new FreshCachePropagationInfo(); freshCachePropInfo = new FreshCachePropagationInfo();
freshCachePropagator = new DepthFirstSearch(freshCachePropInfo); freshCachePropagator = new DepthFirstSearch(freshCachePropInfo);
existenceInfo = new PreviousGenerationInfo(); existenceInfo = new PreviousGenerationInfo();
existenceChecker = new DepthFirstSearch(existenceInfo); existenceChecker = new DepthFirstSearch(existenceInfo);
localChunkCache = new Biome[16][16]; localChunkCache = new Biome[16][16];
currChunkCoords = new Point2();
} }
@Override @Override
@ -61,12 +58,9 @@ public class BiomePerIslandGenerator implements IslandBiomeGenerator {
@Override @Override
public Biome GenerateBiome(int chunkX, int chunkZ, int localX, int localZ) { public Biome GenerateBiome(int chunkX, int chunkZ, int localX, int localZ) {
if (chunkX != currChunkCoords.x || chunkZ != currChunkCoords.y) { if (currChunkCoords == null || chunkX != currChunkCoords.x || chunkZ != currChunkCoords.y) {
currChunkCoords.x = chunkX; currChunkCoords = new Point2(chunkX, chunkZ);
currChunkCoords.y = chunkZ; localChunkCache = new Biome[16][16];
for (int i = 0; i < 16; i++) {
Arrays.fill(localChunkCache[i], null);
}
} }
int worldX = Utilities.addMagnitude(16 * chunkX, localX); int worldX = Utilities.addMagnitude(16 * chunkX, localX);
int worldZ = Utilities.addMagnitude(16 * chunkZ, localZ); int worldZ = Utilities.addMagnitude(16 * chunkZ, localZ);
@ -96,7 +90,7 @@ public class BiomePerIslandGenerator implements IslandBiomeGenerator {
int localZ = Math.abs(worldZ % 16); int localZ = Math.abs(worldZ % 16);
Point2 chunkCoords = new Point2(worldX / 16, worldZ / 16); Point2 chunkCoords = new Point2(worldX / 16, worldZ / 16);
if (chunkCoords.equals(this.currChunkCoords) && localChunkCache[localX][localZ] != null) { if (chunkCoords.fastEquals(this.currChunkCoords) && localChunkCache[localX][localZ] != null) {
return localChunkCache[localX][localZ]; return localChunkCache[localX][localZ];
} }
@ -127,7 +121,7 @@ public class BiomePerIslandGenerator implements IslandBiomeGenerator {
int localZ = Math.abs(worldZ % 16); int localZ = Math.abs(worldZ % 16);
Point2 chunkCoords = new Point2(worldX / 16, worldZ / 16); Point2 chunkCoords = new Point2(worldX / 16, worldZ / 16);
if (chunkCoords.equals(this.currChunkCoords)) localChunkCache[localX][localZ] = biome; if (chunkCoords.fastEquals(this.currChunkCoords)) localChunkCache[localX][localZ] = biome;
Biome[][] chunkBiomes = chunkBiomesCache.getValue(chunkCoords); Biome[][] chunkBiomes = chunkBiomesCache.getValue(chunkCoords);
if (chunkBiomes == null) chunkBiomes = new Biome[16][16]; if (chunkBiomes == null) chunkBiomes = new Biome[16][16];
@ -157,7 +151,7 @@ public class BiomePerIslandGenerator implements IslandBiomeGenerator {
@Override @Override
public boolean validate(int x, int y) { public boolean validate(int x, int y) {
Point2 chunkCoords = new Point2(x / 16, y / 16); Point2 chunkCoords = new Point2(x / 16, y / 16);
return chunkCoords.x == currChunkCoords.x && chunkCoords.y == currChunkCoords.y && worldIslandMap.isIsland(x, y); return chunkCoords.fastEquals(currChunkCoords) && worldIslandMap.isIsland(x, y);
} }
public boolean allBiomesAcquired() { public boolean allBiomesAcquired() {

View File

@ -9,6 +9,7 @@ import org.bukkit.block.Biome;
import org.bukkit.generator.ChunkGenerator.BiomeGrid; import org.bukkit.generator.ChunkGenerator.BiomeGrid;
import org.bukkit.generator.ChunkGenerator.ChunkData; import org.bukkit.generator.ChunkGenerator.ChunkData;
import ca.recrown.islandsurvivalcraft.Utilities;
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;
@ -44,8 +45,9 @@ public class IslandWorldGenerator {
} }
public void GenerateChunk(int chunkX, int chunkZ, int localX, int localZ, ChunkData chunk, BiomeGrid biomeGrid) { public void GenerateChunk(int chunkX, int chunkZ, int localX, int localZ, ChunkData chunk, BiomeGrid biomeGrid) {
int worldX = 16 * chunkX + localX; int worldX = Utilities.addMagnitude(16 * chunkX, localX);
int worldZ = 16 * chunkZ + localZ; int worldZ = Utilities.addMagnitude(16 * chunkZ, localZ);
// gets the bedrock. // gets the bedrock.
int bedrockHeight = bedrockGenerator.getBedrockHeight(worldX, worldZ); int bedrockHeight = bedrockGenerator.getBedrockHeight(worldX, worldZ);

View File

@ -12,7 +12,7 @@ class TemperatureMapGenerator {
private final double frequency = 0.5D; private final double frequency = 0.5D;
private final double amplitude = 0.5D; private final double amplitude = 0.5D;
private SimplexOctaveGenerator noiseGenerator; private volatile SimplexOctaveGenerator noiseGenerator;
public TemperatureMapGenerator() { public TemperatureMapGenerator() {
temperatureCache = new Cache<>(1024); temperatureCache = new Cache<>(1024);

View File

@ -9,12 +9,12 @@ import org.bukkit.util.noise.SimplexOctaveGenerator;
import ca.recrown.islandsurvivalcraft.world.IslandWorldMapper; import ca.recrown.islandsurvivalcraft.world.IslandWorldMapper;
public class WorldHeightShader { public class WorldHeightShader {
private Random random; private final Random random;
private SimplexOctaveGenerator noiseGenerator; private final SimplexOctaveGenerator noiseGenerator;
private IslandWorldMapper islandLocator; private final IslandWorldMapper islandLocator;
private int seaLevel; private final int seaLevel;
private int worldHeight; private final int worldHeight;
private int minimumHeight; private final int minimumHeight;
private final float probabilityOfAdditive = 0.75f; private final float probabilityOfAdditive = 0.75f;
public WorldHeightShader(long seed, IslandWorldMapper islandLocator, int seaLevel, int worldHeight, int minimumHeight) { public WorldHeightShader(long seed, IslandWorldMapper islandLocator, int seaLevel, int worldHeight, int minimumHeight) {