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.HashMap;
import java.util.PriorityQueue; import java.util.PriorityQueue;
public class Cache <CacheValueType> { public class Cache<Key, Value> {
private final int maxCacheSize; private final int maxSize;
private final HashMap<Identifier, CacheValue<CacheValueType>> dataCache; private final float initialUsageFactor;
private final PriorityQueue<Identifier> ids; private final HashMap<Key, CacheDataSet<Key, Value>> data;
private final float freshImportanceFactor; private final PriorityQueue<UsageTracker<Key>> usageTrackers;
public Cache(int maxCacheSize, float freshImportanceFactor) { public Cache(int maxSize, float initialUsageFactor) {
dataCache = new HashMap<>(maxCacheSize); data = new HashMap<>(maxSize);
ids = new PriorityQueue<>(maxCacheSize); usageTrackers = new PriorityQueue<>(maxSize);
this.maxCacheSize = maxCacheSize; this.maxSize = maxSize;
this.freshImportanceFactor = freshImportanceFactor; this.initialUsageFactor = initialUsageFactor;
}
public Cache(int maxSize) {
this(maxSize, 1f);
}
public Cache(float initialUsageFactor) {
this(1024, initialUsageFactor);
} }
public Cache() { public Cache() {
this(1024, 1f); this(1024);
} }
public Cache(int maxCacheSize) { public void setValue(Key key, Value value) {
this(maxCacheSize, 1f); 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() { public void clearCache() {
ids.clear(); data.clear();
dataCache.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 org.bukkit.util.noise.SimplexOctaveGenerator;
import ca.recrown.islandsurvivalcraft.Types.Point2;
import ca.recrown.islandsurvivalcraft.caching.Cache; 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.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<Double> blockValueCache; private Cache<Point2, Double> blockValueCache;
private SimplexOctaveGenerator noiseGenerator; private SimplexOctaveGenerator noiseGenerator;
private final int noiseOctaves = 4; private final int noiseOctaves = 4;
@ -113,8 +112,9 @@ public class IslandWorldMapper implements CoordinateValidatable {
* @return a value representing the island at the given point. * @return a value representing the island at the given point.
*/ */
public double getWorldBlockValue(int worldX, int worldZ) { public double getWorldBlockValue(int worldX, int worldZ) {
CacheValue<Double> cacheVal = blockValueCache.retrieveCache(new CoordinateIdentifier(worldX, worldZ)); Point2 p = new Point2(worldX, worldZ);
if (cacheVal.isEmpty()) { Double res = blockValueCache.getValue(p);
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;
double rawNoise = noiseGenerator.noise(worldX, worldZ, noiseFrequency, noiseAmplitude, true); double rawNoise = noiseGenerator.noise(worldX, worldZ, noiseFrequency, noiseAmplitude, true);
@ -126,17 +126,17 @@ public class IslandWorldMapper implements CoordinateValidatable {
} }
double maxNeg = -1 + shift; double maxNeg = -1 + shift;
double maxPos = 1 + shift; double maxPos = 1 + shift;
double res = 0;
if (noise < 0) { if (noise < 0) {
res = - noise / maxNeg; res = - noise / maxNeg;
} else { } else {
res = noise / maxPos; 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; package ca.recrown.islandsurvivalcraft.world.generation;
import org.bukkit.Chunk;
import org.bukkit.World; import org.bukkit.World;
import org.bukkit.block.Biome; import org.bukkit.block.Biome;
import ca.recrown.islandsurvivalcraft.Types.Point2;
import ca.recrown.islandsurvivalcraft.caching.Cache; 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.CoordinateTargetValidatable;
import ca.recrown.islandsurvivalcraft.pathfinding.CoordinateValidatable; import ca.recrown.islandsurvivalcraft.pathfinding.CoordinateValidatable;
import ca.recrown.islandsurvivalcraft.pathfinding.DepthFirstSearch; import ca.recrown.islandsurvivalcraft.pathfinding.DepthFirstSearch;
@ -18,8 +15,8 @@ import ca.recrown.islandsurvivalcraft.world.IslandWorldMapper;
public class BiomePerIslandGenerator implements IslandBiomeGenerator { public class BiomePerIslandGenerator implements IslandBiomeGenerator {
private boolean initialized; private boolean initialized;
private final TemperatureMapGenerator temperatureMapGenerator; private final TemperatureMapGenerator temperatureMapGenerator;
private final Cache<Biome[][]> chunkBiomesCache; private final Cache<Point2, Biome[][]> chunkBiomesCache;
private final Cache<Boolean> chunkGenStatusCache; private final Cache<Point2, Boolean> chunkGenStatusCache;
private IslandWorldMapper worldIslandMap; private IslandWorldMapper worldIslandMap;
private BiomeSelector biomeSelector; private BiomeSelector biomeSelector;
private World world; private World world;
@ -27,18 +24,20 @@ public class BiomePerIslandGenerator implements IslandBiomeGenerator {
private final DepthFirstSearch existenceChecker; private final DepthFirstSearch existenceChecker;
private FreshCachePropagationInfo freshCachePropInfo; private FreshCachePropagationInfo freshCachePropInfo;
private PreviousGenerationInfo existenceInfo; private PreviousGenerationInfo existenceInfo;
CoordinateIdentifier currChunkCoords; private Point2 currChunkCoords;
private final 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<>(1024, 1.5f);
chunkGenStatusCache = new Cache<>(1024); chunkGenStatusCache = new Cache<>(1024);
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];
} }
@Override @Override
@ -58,8 +57,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 (currChunkCoords == null || chunkX != currChunkCoords.getX() || chunkZ != currChunkCoords.getY()) { if (currChunkCoords == null || chunkX != currChunkCoords.x || chunkZ != currChunkCoords.y) {
currChunkCoords = new CoordinateIdentifier(chunkX, chunkZ); currChunkCoords.x = chunkX;
currChunkCoords.y = chunkZ;
} }
int worldX = localX + 16 * chunkX; int worldX = localX + 16 * chunkX;
int worldZ = localZ + 16 * chunkZ; int worldZ = localZ + 16 * chunkZ;
@ -81,28 +81,31 @@ public class BiomePerIslandGenerator implements IslandBiomeGenerator {
} }
freshCachePropagator.setStartPosition(worldX, worldZ); freshCachePropagator.setStartPosition(worldX, worldZ);
freshCachePropagator.findTarget(freshCachePropInfo); freshCachePropagator.findTarget(freshCachePropInfo);
return getBiome(worldX, worldZ); return localChunkCache[localX][localZ];
} }
private Biome getBiome(int worldX, int worldZ) { private Biome getBiome(int worldX, int worldZ) {
int localX = Math.abs(worldX % 16); int localX = Math.abs(worldX % 16);
int localZ = Math.abs(worldZ % 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); Biome[][] biomes = chunkBiomesCache.getValue(chunkCoords);
if (!chunkBiomes.isEmpty()) {
Biome biome = chunkBiomes.getValue()[localX][localZ]; if (biomes != null) {
Biome biome = biomes[localX][localZ];
if (biome != null) return biome; if (biome != null) return biome;
} }
CacheValue<Boolean> chunkGenStatus = chunkGenStatusCache.retrieveCache(chunkCoords); Boolean chunkGenStat = chunkGenStatusCache.getValue(chunkCoords);
if (chunkGenStatus.isEmpty()) if (chunkGenStat == null) {
chunkGenStatus.setValue(world.isChunkGenerated(chunkCoords.getX(), chunkCoords.getY())); chunkGenStatusCache.setValue(chunkCoords, world.isChunkGenerated(chunkCoords.x, chunkCoords.y));
}
if (chunkGenStatus.getValue()) { if (chunkGenStat) {
if (chunkBiomes.isEmpty()) chunkBiomes.setValue(new Biome[16][16]); if (biomes == null) biomes = new Biome[16][16];
chunkBiomes.getValue()[localX][localZ] = world.getBiome(worldX, 0, worldZ); biomes[localX][localZ] = world.getBiome(worldX, 0, worldZ);
return chunkBiomes.getValue()[localX][localZ]; chunkBiomesCache.setValue(chunkCoords, biomes);
return biomes[localX][localZ];
} }
return null; return null;
} }
@ -110,11 +113,14 @@ public class BiomePerIslandGenerator implements IslandBiomeGenerator {
private void setCacheBiome(int worldX, int worldZ, Biome biome) { private void setCacheBiome(int worldX, int worldZ, Biome biome) {
int localX = Math.abs(worldX % 16); int localX = Math.abs(worldX % 16);
int localZ = Math.abs(worldZ % 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 (chunkCoords.equals(this.currChunkCoords)) localChunkCache[localX][localZ] = biome;
if (chunkBiomes.isEmpty()) chunkBiomes.setValue(new Biome[16][16]);
chunkBiomes.getValue()[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 { private class FreshCachePropagationInfo implements CoordinateTargetValidatable, CoordinateValidatable {
@ -138,7 +144,7 @@ public class BiomePerIslandGenerator implements IslandBiomeGenerator {
@Override @Override
public boolean validate(int x, int y) { 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() { public boolean allBiomesAcquired() {

View File

@ -4,12 +4,11 @@ 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.caching.Cache; import ca.recrown.islandsurvivalcraft.caching.Cache;
import ca.recrown.islandsurvivalcraft.caching.CacheValue;
import ca.recrown.islandsurvivalcraft.caching.CoordinateIdentifier;
class TemperatureMapGenerator { class TemperatureMapGenerator {
private final Cache<Float> temperatureCache; private final Cache<Point2, Float> temperatureCache;
private final double frequency = 0.5D; private final double frequency = 0.5D;
private final double amplitude = 0.5D; private final double amplitude = 0.5D;
@ -30,8 +29,9 @@ class TemperatureMapGenerator {
} }
public float getTemperature(int worldX, int worldZ) { public float getTemperature(int worldX, int worldZ) {
CacheValue<Float> val = temperatureCache.retrieveCache(new CoordinateIdentifier(worldX/4, worldZ/4)); Point2 loc = new Point2(worldX/4, worldZ/4);
if (val.isEmpty()) val.setValue((float) noiseGenerator.noise(worldX/4, worldZ/4, frequency, amplitude, true)); Float val = temperatureCache.getValue(loc);
return val.getValue(); 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) @TestInstance(Lifecycle.PER_CLASS)
public class CacheTest { public class CacheTest {
Cache<Integer> integerCache; Cache<Integer, String> integerCache;
@BeforeAll @BeforeAll
public void setUp() { public void setUp() {
@ -27,37 +27,35 @@ public class CacheTest {
@Test @Test
public void testBasicCaching() { public void testBasicCaching() {
CoordinateIdentifier coordsA = new CoordinateIdentifier(3, 0); String val = integerCache.getValue(0);
CoordinateIdentifier coordsB = new CoordinateIdentifier(4, 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); val = integerCache.getValue(1);
assertTrue(val.isEmpty()); assertTrue(val == null);
val.setValue(0); val = "second";
assertFalse(val.isEmpty()); integerCache.setValue(1, val);
assertSame(val, integerCache.retrieveCache(coordsA)); assertTrue(integerCache.hasValue(1));
assertSame(val, integerCache.getValue(1));
val = integerCache.retrieveCache(coordsB);
assertTrue(val.isEmpty());
val.setValue(1);
assertFalse(val.isEmpty());
assertSame(val, integerCache.retrieveCache(coordsB));
} }
@Test @Test
public void testUsageBasedClean() { public void testUsageBasedClean() {
integerCache.retrieveCache(new CoordinateIdentifier(1, 0)).setValue(1); integerCache.setValue(0, "first");
integerCache.retrieveCache(new CoordinateIdentifier(2, 0)).setValue(2); integerCache.setValue(1, "second");
integerCache.retrieveCache(new CoordinateIdentifier(3, 0)).setValue(3); 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.setValue(5, "sixth");
assertFalse(integerCache.hasValue(2));
integerCache.retrieveCache(new CoordinateIdentifier(5, 0)).setValue(5);
assertFalse(integerCache.retrieveCache(new CoordinateIdentifier(1, 0)).isEmpty());
} }
} }

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());
}
}