Revamped caching again, this time, without sorting.

Implemented cache changes.
This commit is contained in:
Harrison Deng 2020-04-25 17:48:54 -05:00
parent 91644f9ba0
commit 629660c8fc
6 changed files with 121 additions and 130 deletions

View File

@ -1,55 +1,61 @@
package ca.recrown.islandsurvivalcraft.caching;
import java.util.HashMap;
import java.util.PriorityQueue;
import java.util.LinkedList;
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 maxSize, float initialUsageFactor) {
data = new HashMap<>(maxSize);
usageTrackers = new PriorityQueue<>(maxSize);
this.maxSize = maxSize;
this.initialUsageFactor = initialUsageFactor;
}
private final HashMap<Key, Value> data;
private final HashMap<Key, Integer> occurrences;
private final LinkedList<Key> usageTrackers;
public Cache(int maxSize) {
this(maxSize, 1f);
}
public Cache(float initialUsageFactor) {
this(1024, initialUsageFactor);
data = new HashMap<>(maxSize);
usageTrackers = new LinkedList<>();
this.maxSize = maxSize;
this.occurrences = new HashMap<>();
}
public Cache() {
this(1024);
}
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());
public synchronized void setValue(Key key, Value value) {
if (!data.containsKey(key)) {
if (data.size() >= maxSize) {
while (occurrences.get(usageTrackers.peek()) > 1) {
occurencesTrackedPoll();
}
data.remove(usageTrackers.poll());
}
int currentLowest = 0;
if (!usageTrackers.isEmpty()) {
currentLowest = usageTrackers.peek().getUsage();
}
dataSet = new CacheDataSet<>(new UsageTracker<>(key, (int) initialUsageFactor * currentLowest));
data.put(key, dataSet);
occurrencesTrackedAdd(key);
}
dataSet.getUsageTracker().increaseUsage();
usageTrackers.add(dataSet.getUsageTracker());
dataSet.setValue(value);
data.put(key, value);
}
private void occurrencesTrackedAdd(Key key) {
int occ = 0;
if (occurrences.get(key) != null) {
occ = occurrences.get(key);
}
occ++;
occurrences.put(key, occ);
usageTrackers.add(key);
}
private Key occurencesTrackedPoll() {
Key key = usageTrackers.poll();
int occ = 0;
if (occurrences.get(key) != null) {
occ = occurrences.get(key);
}
occ--;
if (occ < 0) throw new IllegalStateException();
occurrences.put(key, occ);
return key;
}
/**
* Retrieves cached value associated to the key.
* Uses equals.
@ -58,21 +64,8 @@ public class Cache<Key, 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;
occurrencesTrackedAdd(key);
return data.get(key);
}
public void clearCache() {

View File

@ -1,31 +0,0 @@
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,34 +0,0 @@
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

@ -1,8 +1,11 @@
package ca.recrown.islandsurvivalcraft.world.generation;
import java.util.Arrays;
import org.bukkit.World;
import org.bukkit.block.Biome;
import ca.recrown.islandsurvivalcraft.Utilities;
import ca.recrown.islandsurvivalcraft.Types.Point2;
import ca.recrown.islandsurvivalcraft.caching.Cache;
import ca.recrown.islandsurvivalcraft.pathfinding.CoordinateTargetValidatable;
@ -31,13 +34,14 @@ public class BiomePerIslandGenerator implements IslandBiomeGenerator {
public BiomePerIslandGenerator() {
this.temperatureMapGenerator = new TemperatureMapGenerator();
chunkBiomesCache = new Cache<>(1024, 1.5f);
chunkBiomesCache = new Cache<>(1024);
chunkGenStatusCache = new Cache<>(1024);
freshCachePropInfo = new FreshCachePropagationInfo();
freshCachePropagator = new DepthFirstSearch(freshCachePropInfo);
existenceInfo = new PreviousGenerationInfo();
existenceChecker = new DepthFirstSearch(existenceInfo);
localChunkCache = new Biome[16][16];
currChunkCoords = new Point2();
}
@Override
@ -57,17 +61,20 @@ public class BiomePerIslandGenerator implements IslandBiomeGenerator {
@Override
public Biome GenerateBiome(int chunkX, int chunkZ, int localX, int localZ) {
if (currChunkCoords == null || chunkX != currChunkCoords.x || chunkZ != currChunkCoords.y) {
if (chunkX != currChunkCoords.x || chunkZ != currChunkCoords.y) {
currChunkCoords.x = chunkX;
currChunkCoords.y = chunkZ;
for (int i = 0; i < 16; i++) {
Arrays.fill(localChunkCache[i], null);
}
}
int worldX = localX + 16 * chunkX;
int worldZ = localZ + 16 * chunkZ;
int worldX = Utilities.addMagnitude(16 * chunkX, localX);
int worldZ = Utilities.addMagnitude(16 * chunkZ, localZ);
Biome cachedBiome = getBiome(worldX, worldZ);
if (cachedBiome != null) return cachedBiome;
temperature = temperatureMapGenerator.getTemperature(worldX, worldZ);
if (!worldIslandMap.isIsland(worldX, worldZ)) {
return biomeSelector.getOceanBiome(temperature);
}
@ -81,7 +88,7 @@ public class BiomePerIslandGenerator implements IslandBiomeGenerator {
}
freshCachePropagator.setStartPosition(worldX, worldZ);
freshCachePropagator.findTarget(freshCachePropInfo);
return localChunkCache[localX][localZ];
return getBiome(worldX, worldZ);
}
private Biome getBiome(int worldX, int worldZ) {
@ -89,6 +96,10 @@ public class BiomePerIslandGenerator implements IslandBiomeGenerator {
int localZ = Math.abs(worldZ % 16);
Point2 chunkCoords = new Point2(worldX / 16, worldZ / 16);
if (chunkCoords.equals(this.currChunkCoords) && localChunkCache[localX][localZ] != null) {
return localChunkCache[localX][localZ];
}
Biome[][] biomes = chunkBiomesCache.getValue(chunkCoords);
if (biomes != null) {
@ -98,7 +109,8 @@ public class BiomePerIslandGenerator implements IslandBiomeGenerator {
Boolean chunkGenStat = chunkGenStatusCache.getValue(chunkCoords);
if (chunkGenStat == null) {
chunkGenStatusCache.setValue(chunkCoords, world.isChunkGenerated(chunkCoords.x, chunkCoords.y));
chunkGenStat = world.isChunkGenerated(chunkCoords.x, chunkCoords.y);
chunkGenStatusCache.setValue(chunkCoords, chunkGenStat);
}
if (chunkGenStat) {
@ -119,7 +131,7 @@ public class BiomePerIslandGenerator implements IslandBiomeGenerator {
Biome[][] chunkBiomes = chunkBiomesCache.getValue(chunkCoords);
if (chunkBiomes == null) chunkBiomes = new Biome[16][16];
chunkBiomes[localX][localZ] = biome;
chunkBiomesCache.setValue(chunkCoords, chunkBiomes);
}
@ -144,7 +156,8 @@ public class BiomePerIslandGenerator implements IslandBiomeGenerator {
@Override
public boolean validate(int x, int y) {
return x / 16 == currChunkCoords.x && y / 16 == currChunkCoords.y && worldIslandMap.isIsland(x, y);
Point2 chunkCoords = new Point2(x / 16, y / 16);
return chunkCoords.x == currChunkCoords.x && chunkCoords.y == currChunkCoords.y && worldIslandMap.isIsland(x, y);
}
public boolean allBiomesAcquired() {

View File

@ -31,7 +31,10 @@ class TemperatureMapGenerator {
public float getTemperature(int worldX, int worldZ) {
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));
if (val == null) {
val = (float) noiseGenerator.noise(worldX/4, worldZ/4, frequency, amplitude, true);
temperatureCache.setValue(loc, val);
}
return val;
}
}

View File

@ -2,9 +2,10 @@ package ca.recrown.islandsurvivalcraft.caching;
import static org.junit.Assert.assertSame;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.util.Random;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
@ -31,14 +32,14 @@ public class CacheTest {
assertTrue(val == null);
val = "first";
integerCache.setValue(0, val);
assertTrue(integerCache.hasValue(0));
assertTrue(integerCache.getValue(0) != null);
assertSame(val, integerCache.getValue(0));
val = integerCache.getValue(1);
assertTrue(val == null);
val = "second";
integerCache.setValue(1, val);
assertTrue(integerCache.hasValue(1));
assertTrue(integerCache.getValue(1) != null);
assertSame(val, integerCache.getValue(1));
}
@ -51,11 +52,57 @@ public class CacheTest {
assertEquals("first", integerCache.getValue(0));
integerCache.setValue(3, "fourth");
assertEquals("first", integerCache.getValue(0));
assertTrue(integerCache.hasValue(1));
integerCache.setValue(5, "sixth");
assertFalse(integerCache.hasValue(2));
integerCache.setValue(4, "fifth");
assertTrue(integerCache.getValue(3) != null);
assertTrue(integerCache.getValue(0) != null);
}
@Test
public void testUsageLargeData() {
int amount = 1024;
Random random = new Random();
Cache<Integer, Integer> largeCache = new Cache<>(amount/2);
int[] expected = new int[amount];
for (int i = 0; i < amount; i++) {
expected[i] = random.nextInt();
largeCache.setValue(i, expected[i]);
}
for (int i = 0; i < amount /2; i++) {
assertEquals(null, largeCache.getValue(i), "Current accessor: " + i);
}
for (int i = amount /2; i < amount; i++) {
assertEquals(expected[i], largeCache.getValue(i), "Current accessor: " + i);
}
}
@Test
public void testUsageLargeDataImportance() {
int amount = 1024;
Random random = new Random();
Cache<Integer, Integer> largeCache = new Cache<>(amount/2);
int[] expected = new int[amount];
for (int i = 0; i < amount; i++) {
expected[i] = random.nextInt();
largeCache.setValue(i, expected[i]);
largeCache.getValue(0);
}
for (int i = 1; i < (amount/2) + 1; i++) {
assertEquals(null, largeCache.getValue(i), "Current accessor: " + i);
}
for (int i = (amount /2) + 1; i < amount; i++) {
assertEquals(expected[i], largeCache.getValue(i), "Current accessor: " + i);
}
assertEquals(expected[0], largeCache.getValue(0));
}
}