New cache system.

Caching itself is tested.

Implemented into classes (untested).
This commit is contained in:
Harrison Deng 2020-04-25 12:06:41 -05:00
parent 1c5baf8762
commit 91644f9ba0
12 changed files with 228 additions and 216 deletions

View File

@ -0,0 +1,30 @@
package ca.recrown.islandsurvivalcraft.Types;
import java.util.Objects;
public class Point2 {
public int x, y;
public Point2(int x, int y) {
this.x = x;
this.y = y;
}
public Point2() {
this(0, 0);
}
@Override
public boolean equals(Object obj) {
if (obj instanceof Point2) {
Point2 p = (Point2) obj;
return p.x == this.x && p.y == this.y;
}
return false;
}
@Override
public int hashCode() {
return Objects.hash(x, y);
}
}

View File

@ -3,50 +3,80 @@ package ca.recrown.islandsurvivalcraft.caching;
import java.util.HashMap;
import java.util.PriorityQueue;
public class Cache <CacheValueType> {
private final int maxCacheSize;
private final HashMap<Identifier, CacheValue<CacheValueType>> dataCache;
private final PriorityQueue<Identifier> ids;
private final float freshImportanceFactor;
public class Cache<Key, Value> {
private final int maxSize;
private final float initialUsageFactor;
private final HashMap<Key, CacheDataSet<Key, Value>> data;
private final PriorityQueue<UsageTracker<Key>> usageTrackers;
public Cache(int maxCacheSize, float freshImportanceFactor) {
dataCache = new HashMap<>(maxCacheSize);
ids = new PriorityQueue<>(maxCacheSize);
this.maxCacheSize = maxCacheSize;
this.freshImportanceFactor = freshImportanceFactor;
public Cache(int maxSize, float initialUsageFactor) {
data = new HashMap<>(maxSize);
usageTrackers = new PriorityQueue<>(maxSize);
this.maxSize = maxSize;
this.initialUsageFactor = initialUsageFactor;
}
public Cache(int maxSize) {
this(maxSize, 1f);
}
public Cache(float initialUsageFactor) {
this(1024, initialUsageFactor);
}
public Cache() {
this(1024, 1f);
this(1024);
}
public Cache(int maxCacheSize) {
this(maxCacheSize, 1f);
public void setValue(Key key, Value value) {
CacheDataSet<Key, Value> dataSet = null;
if (data.containsKey(key)) {
dataSet = data.get(key);
usageTrackers.remove(dataSet.getUsageTracker());
} else {
if (usageTrackers.size() >= maxSize) {
data.remove(usageTrackers.poll().getKey());
}
int currentLowest = 0;
if (!usageTrackers.isEmpty()) {
currentLowest = usageTrackers.peek().getUsage();
}
dataSet = new CacheDataSet<>(new UsageTracker<>(key, (int) initialUsageFactor * currentLowest));
data.put(key, dataSet);
}
dataSet.getUsageTracker().increaseUsage();
usageTrackers.add(dataSet.getUsageTracker());
dataSet.setValue(value);
}
public synchronized CacheValue<CacheValueType> retrieveCache(Identifier identifier) {
if (dataCache.containsKey(identifier)) {
ids.remove(identifier);
CacheValue<CacheValueType> val = dataCache.get(identifier);
val.identifier.usage++;
ids.add(val.identifier);
return val;
}
if (ids.size() >= maxCacheSize) {
dataCache.remove(ids.poll());
}
CacheValue<CacheValueType> value = new CacheValue<>(identifier);
if (!ids.isEmpty()) {
identifier.usage += ids.peek().usage * freshImportanceFactor;
}
ids.add(identifier);
dataCache.put(identifier, value);
return value;
/**
* Retrieves cached value associated to the key.
* Uses equals.
* If does not exist, returns null.
* @param key the key associated with the value.
* @return the value associated to the key.
*/
public Value getValue(Key key) {
CacheDataSet<Key, Value> dataSet = null;
dataSet = data.get(key);
if (dataSet == null) return null;
usageTrackers.remove(dataSet.getUsageTracker());
dataSet.getUsageTracker().increaseUsage();
usageTrackers.add(dataSet.getUsageTracker());
return dataSet.getValue();
}
public boolean hasValue(Key key) {
if (data.containsKey(key)) {
return data.get(key).getValue() != null;
}
return false;
}
public void clearCache() {
ids.clear();
dataCache.clear();
data.clear();
usageTrackers.clear();
}
}

View File

@ -0,0 +1,31 @@
package ca.recrown.islandsurvivalcraft.caching;
public class CacheDataSet<Key, Value> {
private final UsageTracker<Key> usage;
private Value val;
public CacheDataSet(UsageTracker<Key> usageTracker) {
this.usage = usageTracker;
}
/**
* @return the usage tracker.
*/
public UsageTracker<Key> getUsageTracker() {
return usage;
}
/**
* @return the data
*/
public Value getValue() {
return val;
}
/**
* @param data the data to set
*/
public void setValue(Value data) {
this.val = data;
}
}

View File

@ -1,26 +0,0 @@
package ca.recrown.islandsurvivalcraft.caching;
public class CacheValue <ValueType> {
private volatile ValueType value;
protected final Identifier identifier;
private volatile boolean fresh = true;
public CacheValue(Identifier identifier) {
this.identifier = identifier;
}
public boolean isEmpty() {
return fresh;
}
public synchronized void setValue(ValueType value) {
this.fresh = false;
this.value = value;
identifier.usage ++;
}
public ValueType getValue() {
identifier.usage++;
return value;
}
}

View File

@ -1,43 +0,0 @@
package ca.recrown.islandsurvivalcraft.caching;
import java.util.Objects;
public class CoordinateIdentifier extends Identifier {
private final int x;
private final int y;
private final int hash;
public CoordinateIdentifier(int x, int y) {
this.x = x;
this.y = y;
hash = Objects.hash(x, y);
}
@Override
public boolean equals(Object obj) {
if (obj instanceof CoordinateIdentifier) {
CoordinateIdentifier other = (CoordinateIdentifier) obj;
return x == other.x && y == other.y;
}
return false;
}
@Override
public int hashCode() {
return hash;
}
/**
* @return the x
*/
public int getX() {
return x;
}
/**
* @return the y
*/
public int getY() {
return y;
}
}

View File

@ -1,16 +0,0 @@
package ca.recrown.islandsurvivalcraft.caching;
public abstract class Identifier implements Comparable<Identifier> {
protected volatile int usage;
@Override
public abstract boolean equals(Object obj);
@Override
public abstract int hashCode();
@Override
public synchronized int compareTo(Identifier o) {
return usage - o.usage;
}
}

View File

@ -0,0 +1,34 @@
package ca.recrown.islandsurvivalcraft.caching;
public class UsageTracker<Key> implements Comparable<UsageTracker<Key>> {
private int usage;
private final Key key;
public UsageTracker(Key key, int initialUsage) {
this.key = key;
this.usage = initialUsage;
}
@Override
public int compareTo(UsageTracker<Key> o) {
return usage - o.usage;
}
public void increaseUsage() {
usage++;
}
/**
* @return the usage
*/
public int getUsage() {
return usage;
}
/**
* @return the key
*/
public Key getKey() {
return key;
}
}

View File

@ -4,14 +4,13 @@ import java.util.Random;
import org.bukkit.util.noise.SimplexOctaveGenerator;
import ca.recrown.islandsurvivalcraft.Types.Point2;
import ca.recrown.islandsurvivalcraft.caching.Cache;
import ca.recrown.islandsurvivalcraft.caching.CacheValue;
import ca.recrown.islandsurvivalcraft.caching.CoordinateIdentifier;
import ca.recrown.islandsurvivalcraft.pathfinding.CoordinateValidatable;
import ca.recrown.islandsurvivalcraft.pathfinding.DepthFirstSearch;
public class IslandWorldMapper implements CoordinateValidatable {
private Cache<Double> blockValueCache;
private Cache<Point2, Double> blockValueCache;
private SimplexOctaveGenerator noiseGenerator;
private final int noiseOctaves = 4;
@ -113,8 +112,9 @@ public class IslandWorldMapper implements CoordinateValidatable {
* @return a value representing the island at the given point.
*/
public double getWorldBlockValue(int worldX, int worldZ) {
CacheValue<Double> cacheVal = blockValueCache.retrieveCache(new CoordinateIdentifier(worldX, worldZ));
if (cacheVal.isEmpty()) {
Point2 p = new Point2(worldX, worldZ);
Double res = blockValueCache.getValue(p);
if (res == null) {
double portionSea = 1f - (this.islandBlockGenerationPercent / 100f);
double shift = 1f - 2 * portionSea;
double rawNoise = noiseGenerator.noise(worldX, worldZ, noiseFrequency, noiseAmplitude, true);
@ -126,17 +126,17 @@ public class IslandWorldMapper implements CoordinateValidatable {
}
double maxNeg = -1 + shift;
double maxPos = 1 + shift;
double res = 0;
if (noise < 0) {
res = - noise / maxNeg;
} else {
res = noise / maxPos;
}
cacheVal.setValue(res);
blockValueCache.setValue(p, res);
return res;
}
return cacheVal.getValue();
return res;
}
/**

View File

@ -1,13 +1,10 @@
package ca.recrown.islandsurvivalcraft.world.generation;
import org.bukkit.Chunk;
import org.bukkit.World;
import org.bukkit.block.Biome;
import ca.recrown.islandsurvivalcraft.Types.Point2;
import ca.recrown.islandsurvivalcraft.caching.Cache;
import ca.recrown.islandsurvivalcraft.caching.CacheValue;
import ca.recrown.islandsurvivalcraft.caching.CoordinateIdentifier;
import ca.recrown.islandsurvivalcraft.pathfinding.CoordinateTargetValidatable;
import ca.recrown.islandsurvivalcraft.pathfinding.CoordinateValidatable;
import ca.recrown.islandsurvivalcraft.pathfinding.DepthFirstSearch;
@ -18,8 +15,8 @@ import ca.recrown.islandsurvivalcraft.world.IslandWorldMapper;
public class BiomePerIslandGenerator implements IslandBiomeGenerator {
private boolean initialized;
private final TemperatureMapGenerator temperatureMapGenerator;
private final Cache<Biome[][]> chunkBiomesCache;
private final Cache<Boolean> chunkGenStatusCache;
private final Cache<Point2, Biome[][]> chunkBiomesCache;
private final Cache<Point2, Boolean> chunkGenStatusCache;
private IslandWorldMapper worldIslandMap;
private BiomeSelector biomeSelector;
private World world;
@ -27,18 +24,20 @@ public class BiomePerIslandGenerator implements IslandBiomeGenerator {
private final DepthFirstSearch existenceChecker;
private FreshCachePropagationInfo freshCachePropInfo;
private PreviousGenerationInfo existenceInfo;
CoordinateIdentifier currChunkCoords;
private Point2 currChunkCoords;
private final Biome[][] localChunkCache;
float temperature;
public BiomePerIslandGenerator() {
this.temperatureMapGenerator = new TemperatureMapGenerator();
chunkBiomesCache = new Cache<>(1024);
chunkBiomesCache = new Cache<>(1024, 1.5f);
chunkGenStatusCache = new Cache<>(1024);
freshCachePropInfo = new FreshCachePropagationInfo();
freshCachePropagator = new DepthFirstSearch(freshCachePropInfo);
existenceInfo = new PreviousGenerationInfo();
existenceChecker = new DepthFirstSearch(existenceInfo);
localChunkCache = new Biome[16][16];
}
@Override
@ -58,8 +57,9 @@ public class BiomePerIslandGenerator implements IslandBiomeGenerator {
@Override
public Biome GenerateBiome(int chunkX, int chunkZ, int localX, int localZ) {
if (currChunkCoords == null || chunkX != currChunkCoords.getX() || chunkZ != currChunkCoords.getY()) {
currChunkCoords = new CoordinateIdentifier(chunkX, chunkZ);
if (currChunkCoords == null || chunkX != currChunkCoords.x || chunkZ != currChunkCoords.y) {
currChunkCoords.x = chunkX;
currChunkCoords.y = chunkZ;
}
int worldX = localX + 16 * chunkX;
int worldZ = localZ + 16 * chunkZ;
@ -81,28 +81,31 @@ public class BiomePerIslandGenerator implements IslandBiomeGenerator {
}
freshCachePropagator.setStartPosition(worldX, worldZ);
freshCachePropagator.findTarget(freshCachePropInfo);
return getBiome(worldX, worldZ);
return localChunkCache[localX][localZ];
}
private Biome getBiome(int worldX, int worldZ) {
int localX = Math.abs(worldX % 16);
int localZ = Math.abs(worldZ % 16);
CoordinateIdentifier chunkCoords = new CoordinateIdentifier(worldX / 16, worldZ / 16);
Point2 chunkCoords = new Point2(worldX / 16, worldZ / 16);
CacheValue<Biome[][]> chunkBiomes = chunkBiomesCache.retrieveCache(chunkCoords);
if (!chunkBiomes.isEmpty()) {
Biome biome = chunkBiomes.getValue()[localX][localZ];
Biome[][] biomes = chunkBiomesCache.getValue(chunkCoords);
if (biomes != null) {
Biome biome = biomes[localX][localZ];
if (biome != null) return biome;
}
CacheValue<Boolean> chunkGenStatus = chunkGenStatusCache.retrieveCache(chunkCoords);
if (chunkGenStatus.isEmpty())
chunkGenStatus.setValue(world.isChunkGenerated(chunkCoords.getX(), chunkCoords.getY()));
Boolean chunkGenStat = chunkGenStatusCache.getValue(chunkCoords);
if (chunkGenStat == null) {
chunkGenStatusCache.setValue(chunkCoords, world.isChunkGenerated(chunkCoords.x, chunkCoords.y));
}
if (chunkGenStatus.getValue()) {
if (chunkBiomes.isEmpty()) chunkBiomes.setValue(new Biome[16][16]);
chunkBiomes.getValue()[localX][localZ] = world.getBiome(worldX, 0, worldZ);
return chunkBiomes.getValue()[localX][localZ];
if (chunkGenStat) {
if (biomes == null) biomes = new Biome[16][16];
biomes[localX][localZ] = world.getBiome(worldX, 0, worldZ);
chunkBiomesCache.setValue(chunkCoords, biomes);
return biomes[localX][localZ];
}
return null;
}
@ -110,11 +113,14 @@ public class BiomePerIslandGenerator implements IslandBiomeGenerator {
private void setCacheBiome(int worldX, int worldZ, Biome biome) {
int localX = Math.abs(worldX % 16);
int localZ = Math.abs(worldZ % 16);
CoordinateIdentifier chunkCoords = new CoordinateIdentifier(worldX / 16, worldZ / 16);
Point2 chunkCoords = new Point2(worldX / 16, worldZ / 16);
CacheValue<Biome[][]> chunkBiomes = chunkBiomesCache.retrieveCache(chunkCoords);
if (chunkBiomes.isEmpty()) chunkBiomes.setValue(new Biome[16][16]);
chunkBiomes.getValue()[localX][localZ] = biome;
if (chunkCoords.equals(this.currChunkCoords)) localChunkCache[localX][localZ] = biome;
Biome[][] chunkBiomes = chunkBiomesCache.getValue(chunkCoords);
if (chunkBiomes == null) chunkBiomes = new Biome[16][16];
chunkBiomesCache.setValue(chunkCoords, chunkBiomes);
}
private class FreshCachePropagationInfo implements CoordinateTargetValidatable, CoordinateValidatable {
@ -138,7 +144,7 @@ public class BiomePerIslandGenerator implements IslandBiomeGenerator {
@Override
public boolean validate(int x, int y) {
return x / 16 == currChunkCoords.getX() && y / 16 == currChunkCoords.getY() && worldIslandMap.isIsland(x, y);
return x / 16 == currChunkCoords.x && y / 16 == currChunkCoords.y && worldIslandMap.isIsland(x, y);
}
public boolean allBiomesAcquired() {

View File

@ -4,12 +4,11 @@ import java.util.Random;
import org.bukkit.util.noise.SimplexOctaveGenerator;
import ca.recrown.islandsurvivalcraft.Types.Point2;
import ca.recrown.islandsurvivalcraft.caching.Cache;
import ca.recrown.islandsurvivalcraft.caching.CacheValue;
import ca.recrown.islandsurvivalcraft.caching.CoordinateIdentifier;
class TemperatureMapGenerator {
private final Cache<Float> temperatureCache;
private final Cache<Point2, Float> temperatureCache;
private final double frequency = 0.5D;
private final double amplitude = 0.5D;
@ -30,8 +29,9 @@ class TemperatureMapGenerator {
}
public float getTemperature(int worldX, int worldZ) {
CacheValue<Float> val = temperatureCache.retrieveCache(new CoordinateIdentifier(worldX/4, worldZ/4));
if (val.isEmpty()) val.setValue((float) noiseGenerator.noise(worldX/4, worldZ/4, frequency, amplitude, true));
return val.getValue();
Point2 loc = new Point2(worldX/4, worldZ/4);
Float val = temperatureCache.getValue(loc);
if (val == null) temperatureCache.setValue(loc, (float) noiseGenerator.noise(worldX/4, worldZ/4, frequency, amplitude, true));
return val;
}
}

View File

@ -13,7 +13,7 @@ import org.junit.jupiter.api.TestInstance.Lifecycle;
@TestInstance(Lifecycle.PER_CLASS)
public class CacheTest {
Cache<Integer> integerCache;
Cache<Integer, String> integerCache;
@BeforeAll
public void setUp() {
@ -27,37 +27,35 @@ public class CacheTest {
@Test
public void testBasicCaching() {
CoordinateIdentifier coordsA = new CoordinateIdentifier(3, 0);
CoordinateIdentifier coordsB = new CoordinateIdentifier(4, 0);
String val = integerCache.getValue(0);
assertTrue(val == null);
val = "first";
integerCache.setValue(0, val);
assertTrue(integerCache.hasValue(0));
assertSame(val, integerCache.getValue(0));
CacheValue<Integer> val = integerCache.retrieveCache(coordsA);
assertTrue(val.isEmpty());
val.setValue(0);
assertFalse(val.isEmpty());
assertSame(val, integerCache.retrieveCache(coordsA));
val = integerCache.retrieveCache(coordsB);
assertTrue(val.isEmpty());
val.setValue(1);
assertFalse(val.isEmpty());
assertSame(val, integerCache.retrieveCache(coordsB));
val = integerCache.getValue(1);
assertTrue(val == null);
val = "second";
integerCache.setValue(1, val);
assertTrue(integerCache.hasValue(1));
assertSame(val, integerCache.getValue(1));
}
@Test
public void testUsageBasedClean() {
integerCache.retrieveCache(new CoordinateIdentifier(1, 0)).setValue(1);
integerCache.retrieveCache(new CoordinateIdentifier(2, 0)).setValue(2);
integerCache.retrieveCache(new CoordinateIdentifier(3, 0)).setValue(3);
integerCache.setValue(0, "first");
integerCache.setValue(1, "second");
integerCache.setValue(2, "third");
integerCache.retrieveCache(new CoordinateIdentifier(1, 0)).getValue();
assertEquals("first", integerCache.getValue(0));
integerCache.retrieveCache(new CoordinateIdentifier(4, 0)).setValue(4);
integerCache.setValue(3, "fourth");
assertEquals(1, integerCache.retrieveCache(new CoordinateIdentifier(1, 0)).getValue());
assertEquals("first", integerCache.getValue(0));
assertTrue(integerCache.hasValue(1));
assertFalse(integerCache.retrieveCache(new CoordinateIdentifier(1, 0)).isEmpty());
integerCache.retrieveCache(new CoordinateIdentifier(5, 0)).setValue(5);
assertFalse(integerCache.retrieveCache(new CoordinateIdentifier(1, 0)).isEmpty());
integerCache.setValue(5, "sixth");
assertFalse(integerCache.hasValue(2));
}
}

View File

@ -1,32 +0,0 @@
package ca.recrown.islandsurvivalcraft.caching;
import static org.junit.jupiter.api.Assertions.assertEquals;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInstance;
import org.junit.jupiter.api.TestInstance.Lifecycle;
@TestInstance(Lifecycle.PER_CLASS)
public class CoordinateIdentifierTest {
Cache<Integer> integerCache;
@BeforeAll
public void setUp() {
integerCache = new Cache<>(4);
}
@AfterEach
public void cleanUp() {
integerCache.clearCache();
}
@Test
public void testIdentityChecking() {
integerCache.retrieveCache(new CoordinateIdentifier(1, 2)).setValue(8);
integerCache.retrieveCache(new CoordinateIdentifier(1, 2)).setValue(4);
assertEquals(4, integerCache.retrieveCache(new CoordinateIdentifier(1, 2)).getValue());
}
}