Reworked thread safe cache solution.

Also performed refactoring.

And added some threaded tests.
This commit is contained in:
2020-04-28 16:53:23 -05:00
parent 7c073cdc6e
commit b30d689183
22 changed files with 1800 additions and 177 deletions

View File

@@ -9,7 +9,7 @@ import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInstance;
import org.junit.jupiter.api.TestInstance.Lifecycle;
import ca.recrown.islandsurvivalcraft.types.Point2;
import ca.recrown.islandsurvivalcraft.datatypes.Point2;
/**
* Unit test for simple App.

View File

@@ -2,9 +2,14 @@ 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 java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
@@ -28,35 +33,35 @@ public class CacheTest {
@Test
public void testBasicCaching() {
String val = integerCache.getValue(0);
String val = integerCache.get(0);
assertTrue(val == null);
val = "first";
integerCache.setValue(0, val);
assertTrue(integerCache.getValue(0) != null);
assertSame(val, integerCache.getValue(0));
integerCache.set(0, val);
assertTrue(integerCache.get(0) != null);
assertSame(val, integerCache.get(0));
val = integerCache.getValue(1);
val = integerCache.get(1);
assertTrue(val == null);
val = "second";
integerCache.setValue(1, val);
assertTrue(integerCache.getValue(1) != null);
assertSame(val, integerCache.getValue(1));
integerCache.set(1, val);
assertTrue(integerCache.get(1) != null);
assertSame(val, integerCache.get(1));
}
@Test
public void testUsageBasedClean() {
integerCache.setValue(0, "first");
integerCache.setValue(1, "second");
integerCache.setValue(2, "third");
assertEquals("first", integerCache.getValue(0));
integerCache.set(0, "first");
integerCache.set(1, "second");
integerCache.set(2, "third");
integerCache.setValue(3, "fourth");
assertEquals("first", integerCache.getValue(0));
assertEquals("first", integerCache.get(0));
integerCache.setValue(4, "fifth");
assertTrue(integerCache.getValue(3) != null);
assertTrue(integerCache.getValue(0) != null);
integerCache.set(3, "fourth");
assertEquals("first", integerCache.get(0));
integerCache.set(4, "fifth");
assertTrue(integerCache.get(3) != null);
assertTrue(integerCache.get(0) != null);
}
@Test
@@ -64,20 +69,20 @@ public class CacheTest {
int amount = 1024;
Random random = new Random();
Cache<Integer, Integer> largeCache = new Cache<>(amount/2);
Cache<Integer, Integer> largeCache = new Cache<>(amount / 2);
int[] values = new int[amount];
for (int i = 0; i < amount; i++) {
values[i] = random.nextInt();
largeCache.setValue(i, values[i]);
largeCache.set(i, values[i]);
}
for (int i = 0; i < amount /2; i++) {
assertEquals(null, largeCache.getValue(i), "Current accessor: " + i);
for (int i = 0; i < amount / 2; i++) {
assertEquals(null, largeCache.get(i), "Current accessor: " + i);
}
for (int i = amount /2; i < amount; i++) {
assertEquals(values[i], largeCache.getValue(i), "Current accessor: " + i);
for (int i = amount / 2; i < amount; i++) {
assertEquals(values[i], largeCache.get(i), "Current accessor: " + i);
}
}
@@ -86,23 +91,194 @@ public class CacheTest {
int amount = 1024;
Random random = new Random();
Cache<Integer, Integer> largeCache = new Cache<>(amount/2);
Cache<Integer, Integer> largeCache = new Cache<>(amount / 2);
int[] values = new int[amount];
for (int i = 0; i < amount; i++) {
values[i] = random.nextInt();
largeCache.setValue(i, values[i]);
largeCache.getValue(0);
largeCache.set(i, values[i]);
largeCache.get(0);
}
for (int i = 1; i < (amount/2) + 1; i++) {
assertEquals(null, largeCache.getValue(i), "Current accessor: " + i);
for (int i = 1; i < (amount / 2) + 1; i++) {
assertEquals(null, largeCache.get(i), "Current accessor: " + i);
}
for (int i = (amount /2) + 1; i < amount; i++) {
assertEquals(values[i], largeCache.getValue(i), "Current accessor: " + i);
for (int i = (amount / 2) + 1; i < amount; i++) {
assertEquals(values[i], largeCache.get(i), "Current accessor: " + i);
}
assertEquals(values[0], largeCache.getValue(0));
assertEquals(values[0], largeCache.get(0));
}
@Test
public void testMultithreadingWriteConsistency() {
int size = 16384;
int[] answers = new int[size];
Random rand = new Random();
for (int i = 0; i < answers.length; i++) {
answers[i] = rand.nextInt();
}
Cache<Integer, Integer> lCache = new Cache<>(size);
Runnable write = new Runnable() {
@Override
public void run() {
for (int i = 0; i < size; i++) {
lCache.set(i, answers[i]);
}
}
};
Thread firstThread = new Thread(write);
firstThread.start();
Thread secondThread = new Thread(write);
secondThread.start();
Thread thirdThread = new Thread(write);
thirdThread.start();
try {
firstThread.join();
secondThread.join();
thirdThread.join();
} catch (InterruptedException e) {
assertFalse(false, e.getCause().getMessage());
}
for (int i = 0; i < size; i++) {
assertEquals(answers[i], lCache.get(i), "Accessor at: " + i);
}
}
@Test
public void testMultithreadingReadConsistency() {
int size = 16384;
Cache<Integer, Integer> lCache = new Cache<>(size);
int[] answers = new int[size];
Random rand = new Random();
for (int i = 0; i < answers.length; i++) {
answers[i] = rand.nextInt();
lCache.set(i, answers[i]);
}
Runnable read = new Runnable() {
@Override
public void run() {
for (int i = 0; i < size; i++) {
assertEquals(answers[i], lCache.get(i), "Accessor at: " + i);
}
}
};
Thread firstThread = new Thread(read);
firstThread.start();
Thread secondThread = new Thread(read);
secondThread.start();
Thread thirdThread = new Thread(read);
thirdThread.start();
try {
firstThread.join();
secondThread.join();
thirdThread.join();
} catch (InterruptedException e) {
assertFalse(false, e.getCause().getMessage());
}
}
@Test
public void testMulthreadedReadWrite() {
int size = 16384;
Cache<Integer, Integer> lCache = new Cache<>(size);
int[] answers = new int[size];
Random rand = new Random();
for (int i = 0; i < answers.length; i++) {
answers[i] = rand.nextInt();
lCache.set(i, answers[i]);
}
Runnable readWrite = new Runnable() {
@Override
public void run() {
for (int i = 0; i < size / 2; i++) {
if (lCache.get(i) == null) {
lCache.set(i, answers[i]);
} else {
assertEquals(answers[i], lCache.get(i), "Accessor at: " + i);
}
}
}
};
Runnable readAll = new Runnable() {
@Override
public void run() {
for (int i = 0; i < size; i++) {
assertEquals(answers[i], lCache.get(i), "Accessor at: " + i);
}
}
};
Thread firstThread = new Thread(readWrite);
firstThread.start();
Thread secondThread = new Thread(readWrite);
secondThread.start();
Thread thirdThread = new Thread(readAll);
try {
firstThread.join();
secondThread.join();
thirdThread.start();
thirdThread.join();
} catch (InterruptedException e) {
assertFalse(false, e.getCause().getMessage());
}
}
@Test
public void testMultiThreadConsistency() {
int size = 51200;
Cache<Integer, Integer> lCache = new Cache<>(size / 2);
int[] answers = new int[size];
Random rand = new Random();
ConcurrentHashMap<Integer, Integer> expectedStoredValues = new ConcurrentHashMap<>();
for (int i = 0; i < answers.length; i++) {
answers[i] = rand.nextInt();
lCache.set(i, answers[i]);
}
Runnable readWriteCheck = new Runnable(){
@Override
public void run() {
for (int i = 0; i < size; i++) {
if (lCache.get(i) != null) {
assertEquals(answers[i], lCache.get(i), "Accessor at: " + i);
} else {
lCache.set(i, answers[i]);
expectedStoredValues.put(i, answers[i]);
}
}
}
};
ExecutorService executorService = Executors.newFixedThreadPool(6);
executorService.submit(readWriteCheck);
executorService.submit(readWriteCheck);
executorService.submit(readWriteCheck);
executorService.submit(readWriteCheck);
executorService.submit(readWriteCheck);
executorService.submit(readWriteCheck);
try {
executorService.shutdown();
assertTrue(executorService.awaitTermination(1, TimeUnit.MINUTES), "Timed out.");
} catch (InterruptedException e) {
assertFalse(false, e.getCause().getMessage());
}
}
}

View File

@@ -13,7 +13,7 @@ import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInstance;
import org.junit.jupiter.api.TestInstance.Lifecycle;
import ca.recrown.islandsurvivalcraft.types.Point2;
import ca.recrown.islandsurvivalcraft.datatypes.Point2;
@TestInstance(Lifecycle.PER_CLASS)
public class FloodFillTest {

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,109 @@
package ca.recrown.islandsurvivalcraft.world.generation;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import org.bukkit.block.Biome;
import org.junit.Test;
import org.junit.jupiter.api.TestInstance;
import org.junit.jupiter.api.TestInstance.Lifecycle;
import ca.recrown.islandsurvivalcraft.Utilities;
import ca.recrown.islandsurvivalcraft.caching.Cache;
import ca.recrown.islandsurvivalcraft.datatypes.Point2;
import ca.recrown.islandsurvivalcraft.world.BiomeSelector;
import ca.recrown.islandsurvivalcraft.world.IslandWorldMapper;
@TestInstance(Lifecycle.PER_CLASS)
public class UniBiomeIslandGeneratorTest {
private final int SEED = 249398015;
private final DummyWorld dummyWorld = new DummyWorld();
private final Cache<Point2, Double> blockValueCache = new Cache<>(102400);
private final Cache<Point2, Biome> biomeCache = new Cache<>(102400);
private final Cache<Point2, Boolean> chunkExistenceCache = new Cache<>(16384);
private final Random rand = new Random(SEED);
private class BiomeGenTask implements Runnable {
private final int amount;
private final int shift;
public BiomeGenTask(int amount, int shift) {
this.shift = shift;
this.amount = amount;
}
@Override
public void run() {
for (int x = 0; x < amount; x++) {
generateBiome(x, shift);
}
}
public void generateBiome(int chunkX, int chunkZ) {
IslandWorldMapper mapper = new IslandWorldMapper(rand, blockValueCache);
TemperatureMapGenerator temperatureMapGenerator = new TemperatureMapGenerator(SEED);
BiomeSelector biomeSelector = new BiomeSelector(rand);
biomeSelector.initialize();
BiomeGenerator biomeGenerator = new UniBiomeIslandGenerator();
Biome[][] biomes = new Biome[Utilities.CHUNK_SIZE][Utilities.CHUNK_SIZE];
for (int localX = 0; localX < Utilities.CHUNK_SIZE; localX++) {
for (int localZ = 0; localZ < Utilities.CHUNK_SIZE; localZ++) {
if (biomes[localX][localZ] == null) {
biomeGenerator.generateBiomeColumn(biomes, dummyWorld, chunkX, chunkZ, localX, localZ, mapper,
biomeSelector, temperatureMapGenerator, biomeCache, chunkExistenceCache);
}
if (biomes[localX][localZ] == null)
throw new IllegalStateException("Biome was null.");
}
}
}
}
@Test
public void testBiomeGenerationMultithread() {
int chunksToDo = 280;
Runnable g1 = new BiomeGenTask(chunksToDo, 0);
Runnable g2 = new BiomeGenTask(chunksToDo, 1);
Runnable g3 = new BiomeGenTask(chunksToDo, 2);
Runnable g4 = new BiomeGenTask(chunksToDo, 3);
Runnable g5 = new BiomeGenTask(chunksToDo, 4);
Runnable g6 = new BiomeGenTask(chunksToDo, 5);
ExecutorService ex = Executors.newFixedThreadPool(6);
ex.execute(g1);
ex.execute(g2);
ex.execute(g3);
ex.execute(g4);
ex.execute(g5);
ex.execute(g6);
try {
ex.shutdown();
assertTrue(ex.awaitTermination(1, TimeUnit.MINUTES), "timed out.");
} catch (InterruptedException e) {
assertFalse(false);
}
}
@Test
public void testBiomeGenerationSingleThread() {
Runnable g1 = new BiomeGenTask(1680, 0);
ExecutorService ex = Executors.newFixedThreadPool(6);
ex.execute(g1);
try {
ex.shutdown();
assertTrue(ex.awaitTermination(1, TimeUnit.MINUTES));
} catch (InterruptedException e) {
assertFalse(false, e.getCause().getMessage());
}
}
}