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

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; 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;
import ca.recrown.islandsurvivalcraft.Utilities;
import ca.recrown.islandsurvivalcraft.Types.Point2; import ca.recrown.islandsurvivalcraft.Types.Point2;
import ca.recrown.islandsurvivalcraft.caching.Cache; import ca.recrown.islandsurvivalcraft.caching.Cache;
import ca.recrown.islandsurvivalcraft.pathfinding.CoordinateTargetValidatable; import ca.recrown.islandsurvivalcraft.pathfinding.CoordinateTargetValidatable;
@ -31,13 +34,14 @@ public class BiomePerIslandGenerator implements IslandBiomeGenerator {
public BiomePerIslandGenerator() { public BiomePerIslandGenerator() {
this.temperatureMapGenerator = new TemperatureMapGenerator(); this.temperatureMapGenerator = new TemperatureMapGenerator();
chunkBiomesCache = new Cache<>(1024, 1.5f); chunkBiomesCache = new Cache<>(1024);
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]; localChunkCache = new Biome[16][16];
currChunkCoords = new Point2();
} }
@Override @Override
@ -57,17 +61,20 @@ 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.x || chunkZ != currChunkCoords.y) { if (chunkX != currChunkCoords.x || chunkZ != currChunkCoords.y) {
currChunkCoords.x = chunkX; currChunkCoords.x = chunkX;
currChunkCoords.y = chunkZ; currChunkCoords.y = chunkZ;
for (int i = 0; i < 16; i++) {
Arrays.fill(localChunkCache[i], null);
}
} }
int worldX = localX + 16 * chunkX; int worldX = Utilities.addMagnitude(16 * chunkX, localX);
int worldZ = localZ + 16 * chunkZ; int worldZ = Utilities.addMagnitude(16 * chunkZ, localZ);
Biome cachedBiome = getBiome(worldX, worldZ); Biome cachedBiome = getBiome(worldX, worldZ);
if (cachedBiome != null) return cachedBiome; if (cachedBiome != null) return cachedBiome;
temperature = temperatureMapGenerator.getTemperature(worldX, worldZ);
temperature = temperatureMapGenerator.getTemperature(worldX, worldZ);
if (!worldIslandMap.isIsland(worldX, worldZ)) { if (!worldIslandMap.isIsland(worldX, worldZ)) {
return biomeSelector.getOceanBiome(temperature); return biomeSelector.getOceanBiome(temperature);
} }
@ -81,7 +88,7 @@ public class BiomePerIslandGenerator implements IslandBiomeGenerator {
} }
freshCachePropagator.setStartPosition(worldX, worldZ); freshCachePropagator.setStartPosition(worldX, worldZ);
freshCachePropagator.findTarget(freshCachePropInfo); freshCachePropagator.findTarget(freshCachePropInfo);
return localChunkCache[localX][localZ]; return getBiome(worldX, worldZ);
} }
private Biome getBiome(int worldX, int worldZ) { private Biome getBiome(int worldX, int worldZ) {
@ -89,6 +96,10 @@ 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) {
return localChunkCache[localX][localZ];
}
Biome[][] biomes = chunkBiomesCache.getValue(chunkCoords); Biome[][] biomes = chunkBiomesCache.getValue(chunkCoords);
if (biomes != null) { if (biomes != null) {
@ -98,7 +109,8 @@ public class BiomePerIslandGenerator implements IslandBiomeGenerator {
Boolean chunkGenStat = chunkGenStatusCache.getValue(chunkCoords); Boolean chunkGenStat = chunkGenStatusCache.getValue(chunkCoords);
if (chunkGenStat == null) { 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) { if (chunkGenStat) {
@ -119,7 +131,7 @@ public class BiomePerIslandGenerator implements IslandBiomeGenerator {
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];
chunkBiomes[localX][localZ] = biome;
chunkBiomesCache.setValue(chunkCoords, chunkBiomes); chunkBiomesCache.setValue(chunkCoords, chunkBiomes);
} }
@ -144,7 +156,8 @@ 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.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() { public boolean allBiomesAcquired() {

View File

@ -31,7 +31,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.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; return val;
} }
} }

View File

@ -2,9 +2,10 @@ 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 org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
@ -31,14 +32,14 @@ public class CacheTest {
assertTrue(val == null); assertTrue(val == null);
val = "first"; val = "first";
integerCache.setValue(0, val); integerCache.setValue(0, val);
assertTrue(integerCache.hasValue(0)); assertTrue(integerCache.getValue(0) != null);
assertSame(val, integerCache.getValue(0)); assertSame(val, integerCache.getValue(0));
val = integerCache.getValue(1); val = integerCache.getValue(1);
assertTrue(val == null); assertTrue(val == null);
val = "second"; val = "second";
integerCache.setValue(1, val); integerCache.setValue(1, val);
assertTrue(integerCache.hasValue(1)); assertTrue(integerCache.getValue(1) != null);
assertSame(val, integerCache.getValue(1)); assertSame(val, integerCache.getValue(1));
} }
@ -51,11 +52,57 @@ public class CacheTest {
assertEquals("first", integerCache.getValue(0)); assertEquals("first", integerCache.getValue(0));
integerCache.setValue(3, "fourth"); integerCache.setValue(3, "fourth");
assertEquals("first", integerCache.getValue(0)); assertEquals("first", integerCache.getValue(0));
assertTrue(integerCache.hasValue(1));
integerCache.setValue(5, "sixth"); integerCache.setValue(4, "fifth");
assertFalse(integerCache.hasValue(2)); 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));
} }
} }