Continue fixing gen related issues
This commit is contained in:
@@ -30,12 +30,14 @@ public class GenerationQueue implements AutoCloseable {
|
||||
if (generator.isBusy()) return;
|
||||
DhLodPos closest = null;
|
||||
long closestDist = Long.MAX_VALUE;
|
||||
int smallestDetail = Integer.MAX_VALUE;
|
||||
for (DhLodPos key : taskMap.keySet()) {
|
||||
if (key.detail > smallestDetail) continue;
|
||||
long dist = key.getCenter().distSquared(targetPos);
|
||||
if (dist < closestDist) {
|
||||
closest = key;
|
||||
closestDist = dist;
|
||||
}
|
||||
if (key.detail == smallestDetail && dist >= closestDist) continue;
|
||||
closest = key;
|
||||
closestDist = dist;
|
||||
smallestDetail = key.detail;
|
||||
}
|
||||
if (closest != null) {
|
||||
CompletableFuture<GenerationResult> future = taskMap.remove(closest);
|
||||
@@ -69,77 +71,39 @@ public class GenerationQueue implements AutoCloseable {
|
||||
if (minGenGranularity < 4 || maxGenGranularity < 4) {
|
||||
throw new IllegalStateException("Generation granularity must be at least 4!");
|
||||
}
|
||||
|
||||
byte minUnitDetail = (byte) (dataDetail + minGenGranularity);
|
||||
byte maxUnitDetail = (byte) (dataDetail + maxGenGranularity);
|
||||
LodUtil.assertTrue(pos.detail >= minUnitDetail && pos.detail <= maxUnitDetail);
|
||||
byte genGranularity = (byte) (pos.detail - dataDetail);
|
||||
DHChunkPos chunkPosMin = new DHChunkPos(pos.getCorner());
|
||||
logger.info("Generating section {} with granularity {} at {}", pos, genGranularity, chunkPosMin);
|
||||
int perCallChunksWidth = 1 << (genGranularity - 4);
|
||||
|
||||
byte granularity;
|
||||
int count;
|
||||
DHChunkPos chunkPosMin;
|
||||
if (pos.detail < minUnitDetail) {
|
||||
granularity = minGenGranularity;
|
||||
count = 1;
|
||||
chunkPosMin = new DHChunkPos(pos.convertUpwardsTo(minUnitDetail).getCorner());
|
||||
} else if (pos.detail > maxUnitDetail) {
|
||||
granularity = maxGenGranularity;
|
||||
count = 1 << (pos.detail - maxUnitDetail);
|
||||
chunkPosMin = new DHChunkPos(pos.getCorner());
|
||||
} else {
|
||||
granularity = (byte) (pos.detail - dataDetail);
|
||||
count = 1;
|
||||
chunkPosMin = new DHChunkPos(pos.getCorner());
|
||||
}
|
||||
assert granularity >= minGenGranularity && granularity <= maxGenGranularity;
|
||||
assert count > 0;
|
||||
assert granularity >= 4; // Thanks compiler. Guess having a 'always true' warning means I did it right.
|
||||
logger.info("Generating section {} of size {} with granularity {} at {}", pos, count, granularity, chunkPosMin);
|
||||
int perCallChunksWidth = 1 << (granularity - 4);
|
||||
final byte sectionDetail = (byte) (dataDetail + FullDataSource.SECTION_SIZE_OFFSET);
|
||||
|
||||
ArrayList<CompletableFuture<Collection<ChunkSizedData>>> futures = new ArrayList<>(count*count);
|
||||
for (int dx = 0; dx < count; dx++) {
|
||||
for (int dz = 0; dz < count; dz++) { // TODO: Unroll this loop to yield when generator is busy.
|
||||
DHChunkPos subCallChunkPosMin = new DHChunkPos(chunkPosMin.x + dx * perCallChunksWidth, chunkPosMin.z + dz * perCallChunksWidth);
|
||||
CompletableFuture<ArrayGridList<ChunkSizedData>> dataFuture = generator.generate(subCallChunkPosMin, granularity);
|
||||
futures.add(dataFuture.handle((data, ex) -> {
|
||||
if (ex != null) {
|
||||
if (ex instanceof CompletionException) {
|
||||
ex = ex.getCause();
|
||||
}
|
||||
if (ex instanceof InterruptedException) return null; // Ignore interrupted exceptions.
|
||||
if (ex instanceof UncheckedInterruptedException) return null; // Ignore unchecked interrupted exceptions.
|
||||
logger.error("Error generating data for section {}", pos, ex);
|
||||
return null;
|
||||
}
|
||||
LodUtil.assertTrue(data != null);
|
||||
if (data.gridSize < (1 << (granularity-4))) {
|
||||
logger.error(
|
||||
"Generator at {} returned {} by {} chunks but requested granularity was {}, which expect at least {} by {} chunks! ",
|
||||
pos, data.gridSize, data.gridSize, granularity, perCallChunksWidth, perCallChunksWidth);
|
||||
return null;
|
||||
}
|
||||
|
||||
DhLodPos minSectPos = new DhLodPos((byte)(dataDetail+4), data.getFirst().x, data.getFirst().z).convertUpwardsTo(sectionDetail);
|
||||
DhLodPos maxSectPos = new DhLodPos((byte)(dataDetail+4), data.getLast().x, data.getLast().z).convertUpwardsTo(sectionDetail);
|
||||
|
||||
int sectionCount = (maxSectPos.x - minSectPos.x) + 1;
|
||||
LodUtil.assertTrue(sectionCount > 0 && sectionCount == (maxSectPos.z - minSectPos.z) + 1);
|
||||
|
||||
logger.info("Writing {} by {} chunks (at {}) with data detail {} to {} by {} sections (at {})",
|
||||
data.gridSize, data.gridSize, subCallChunkPosMin, dataDetail,
|
||||
sectionCount, sectionCount, minSectPos);
|
||||
return data;
|
||||
}));
|
||||
}
|
||||
}
|
||||
CompletableFuture<ArrayGridList<ChunkSizedData>> dataFuture = generator.generate(chunkPosMin, genGranularity);
|
||||
inProgress.add(
|
||||
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).thenApply((v) -> {
|
||||
GenerationResult result = new GenerationResult();
|
||||
for (CompletableFuture<Collection<ChunkSizedData>> future : futures) {
|
||||
Collection<ChunkSizedData> data = future.join();
|
||||
if (data == null) continue;
|
||||
result.dataList.addAll(data);
|
||||
dataFuture.handle((data, ex) -> {
|
||||
if (ex != null) {
|
||||
if (ex instanceof CompletionException) {
|
||||
ex = ex.getCause();
|
||||
}
|
||||
if (ex instanceof InterruptedException) return null; // Ignore interrupted exceptions.
|
||||
if (ex instanceof UncheckedInterruptedException) return null; // Ignore unchecked interrupted exceptions.
|
||||
logger.error("Error generating data for section {}", pos, ex);
|
||||
throw new CompletionException("Generation failed", ex);
|
||||
}
|
||||
LodUtil.assertTrue(data != null);
|
||||
if (data.gridSize < (1 << (genGranularity-4))) {
|
||||
logger.error(
|
||||
"Generator at {} returned {} by {} chunks but requested granularity was {}, which expect at least {} by {} chunks! ",
|
||||
pos, data.gridSize, data.gridSize, genGranularity, perCallChunksWidth, perCallChunksWidth);
|
||||
throw new RuntimeException("Generation failed. Generator returned less data than requested!");
|
||||
}
|
||||
logger.info("Completed generating {} by {} chunks to sections that overlaps {}",
|
||||
data.gridSize, data.gridSize, pos);
|
||||
return data;
|
||||
}).thenApply((list) -> {
|
||||
GenerationResult result = new GenerationResult();
|
||||
result.dataList.addAll(list);
|
||||
return result;
|
||||
}).handle((r, e) -> {
|
||||
if (e!=null) resultFuture.completeExceptionally(e); else resultFuture.complete(r);
|
||||
@@ -148,17 +112,132 @@ public class GenerationQueue implements AutoCloseable {
|
||||
);
|
||||
}
|
||||
|
||||
// private void startFuture(DhLodPos pos, CompletableFuture<GenerationResult> resultFuture) {
|
||||
// byte dataDetail = generator.getDataDetail();
|
||||
// byte minGenGranularity = generator.getMinGenerationGranularity();
|
||||
// byte maxGenGranularity = generator.getMaxGenerationGranularity();
|
||||
// if (minGenGranularity < 4 || maxGenGranularity < 4) {
|
||||
// throw new IllegalStateException("Generation granularity must be at least 4!");
|
||||
// }
|
||||
//
|
||||
// byte minUnitDetail = (byte) (dataDetail + minGenGranularity);
|
||||
// byte maxUnitDetail = (byte) (dataDetail + maxGenGranularity);
|
||||
//
|
||||
// byte granularity;
|
||||
// int count;
|
||||
// DHChunkPos chunkPosMin;
|
||||
// if (pos.detail < minUnitDetail) {
|
||||
// granularity = minGenGranularity;
|
||||
// count = 1;
|
||||
// chunkPosMin = new DHChunkPos(pos.convertUpwardsTo(minUnitDetail).getCorner());
|
||||
// } else if (pos.detail > maxUnitDetail) {
|
||||
// granularity = maxGenGranularity;
|
||||
// count = 1 << (pos.detail - maxUnitDetail);
|
||||
// chunkPosMin = new DHChunkPos(pos.getCorner());
|
||||
// } else {
|
||||
// granularity = (byte) (pos.detail - dataDetail);
|
||||
// count = 1;
|
||||
// chunkPosMin = new DHChunkPos(pos.getCorner());
|
||||
// }
|
||||
// assert granularity >= minGenGranularity && granularity <= maxGenGranularity;
|
||||
// assert count > 0;
|
||||
// assert granularity >= 4; // Thanks compiler. Guess having a 'always true' warning means I did it right.
|
||||
// logger.info("Generating section {} of size {} with granularity {} at {}", pos, count, granularity, chunkPosMin);
|
||||
// int perCallChunksWidth = 1 << (granularity - 4);
|
||||
// final byte sectionDetail = (byte) (dataDetail + FullDataSource.SECTION_SIZE_OFFSET);
|
||||
//
|
||||
// ArrayList<CompletableFuture<Collection<ChunkSizedData>>> futures = new ArrayList<>(count*count);
|
||||
// for (int dx = 0; dx < count; dx++) {
|
||||
// for (int dz = 0; dz < count; dz++) { // TODO: Unroll this loop to yield when generator is busy.
|
||||
// DHChunkPos subCallChunkPosMin = new DHChunkPos(chunkPosMin.x + dx * perCallChunksWidth, chunkPosMin.z + dz * perCallChunksWidth);
|
||||
// CompletableFuture<ArrayGridList<ChunkSizedData>> dataFuture = generator.generate(subCallChunkPosMin, granularity);
|
||||
// futures.add(dataFuture.handle((data, ex) -> {
|
||||
// if (ex != null) {
|
||||
// if (ex instanceof CompletionException) {
|
||||
// ex = ex.getCause();
|
||||
// }
|
||||
// if (ex instanceof InterruptedException) return null; // Ignore interrupted exceptions.
|
||||
// if (ex instanceof UncheckedInterruptedException) return null; // Ignore unchecked interrupted exceptions.
|
||||
// logger.error("Error generating data for section {}", pos, ex);
|
||||
// return null;
|
||||
// }
|
||||
// LodUtil.assertTrue(data != null);
|
||||
// if (data.gridSize < (1 << (granularity-4))) {
|
||||
// logger.error(
|
||||
// "Generator at {} returned {} by {} chunks but requested granularity was {}, which expect at least {} by {} chunks! ",
|
||||
// pos, data.gridSize, data.gridSize, granularity, perCallChunksWidth, perCallChunksWidth);
|
||||
// return null;
|
||||
// }
|
||||
//
|
||||
// DhLodPos minSectPos = new DhLodPos((byte)(dataDetail+4), data.getFirst().x, data.getFirst().z).convertUpwardsTo(sectionDetail);
|
||||
// DhLodPos maxSectPos = new DhLodPos((byte)(dataDetail+4), data.getLast().x, data.getLast().z).convertUpwardsTo(sectionDetail);
|
||||
//
|
||||
// int sectionCount = (maxSectPos.x - minSectPos.x) + 1;
|
||||
// LodUtil.assertTrue(sectionCount > 0 && sectionCount == (maxSectPos.z - minSectPos.z) + 1);
|
||||
//
|
||||
// logger.info("Writing {} by {} chunks (at {}) with data detail {} to {} by {} sections (at {})",
|
||||
// data.gridSize, data.gridSize, subCallChunkPosMin, dataDetail,
|
||||
// sectionCount, sectionCount, minSectPos);
|
||||
// return data;
|
||||
// }));
|
||||
// }
|
||||
// }
|
||||
// inProgress.add(
|
||||
// CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).thenApply((v) -> {
|
||||
// GenerationResult result = new GenerationResult();
|
||||
// for (CompletableFuture<Collection<ChunkSizedData>> future : futures) {
|
||||
// Collection<ChunkSizedData> data = future.join();
|
||||
// if (data == null) continue;
|
||||
// result.dataList.addAll(data);
|
||||
// }
|
||||
// return result;
|
||||
// }).handle((r, e) -> {
|
||||
// if (e!=null) resultFuture.completeExceptionally(e); else resultFuture.complete(r);
|
||||
// return null;
|
||||
// })
|
||||
// );
|
||||
// }
|
||||
|
||||
public CompletableFuture<LodDataSource> generate(DhSectionPos sectionPos) {
|
||||
DhLodPos lodPos = sectionPos.getSectionBBoxPos();
|
||||
return cqcpTree.createOrUseExisting(lodPos, this::createFuture).thenApply(
|
||||
(result) -> {
|
||||
if (result == null || result.dataList.isEmpty()) return FullDataSource.createEmpty(sectionPos);
|
||||
FullDataSource newSource = FullDataSource.createEmpty(sectionPos);
|
||||
for (ChunkSizedData data : result.dataList) {
|
||||
newSource.update(data);
|
||||
}
|
||||
return newSource;
|
||||
});
|
||||
byte maxGen = (byte) (generator.getMaxGenerationGranularity() + generator.getDataDetail());
|
||||
if (sectionPos.sectionDetail > maxGen) {
|
||||
int count = 1 << (sectionPos.sectionDetail - maxGen);
|
||||
DhLodPos minPos = sectionPos.getCorner(maxGen);
|
||||
ArrayList<CompletableFuture<GenerationResult>> futures = new ArrayList<>(count*count);
|
||||
for (int x = 0; x < count; x++) {
|
||||
for (int z = 0; z < count; z++) {
|
||||
DhLodPos subPos = new DhLodPos(maxGen, minPos.x + x, minPos.z + z);
|
||||
futures.add(cqcpTree.createOrUseExisting(subPos, this::createFuture));
|
||||
}
|
||||
}
|
||||
// FIXME: Does `allOf` have correct behaviour when one of the futures fails?
|
||||
return CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]))
|
||||
.thenApply((v) -> {
|
||||
FullDataSource newSource = FullDataSource.createEmpty(sectionPos);
|
||||
for (CompletableFuture<GenerationResult> future : futures) {
|
||||
try {
|
||||
GenerationResult result = future.join();
|
||||
for (ChunkSizedData data : result.dataList) {
|
||||
if (data.getBBoxLodPos().overlaps(sectionPos.getSectionBBoxPos())) newSource.update(data);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// continue
|
||||
}
|
||||
}
|
||||
return newSource;
|
||||
});
|
||||
} else {
|
||||
DhLodPos lodPos = sectionPos.getSectionBBoxPos();
|
||||
return cqcpTree.createOrUseExisting(lodPos, this::createFuture).thenApply(
|
||||
(result) -> {
|
||||
if (result == null || result.dataList.isEmpty()) return FullDataSource.createEmpty(sectionPos);
|
||||
FullDataSource newSource = FullDataSource.createEmpty(sectionPos);
|
||||
for (ChunkSizedData data : result.dataList) {
|
||||
if (data.getBBoxLodPos().overlaps(sectionPos.getSectionBBoxPos())) newSource.update(data);
|
||||
}
|
||||
return newSource;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -101,12 +101,12 @@ public class DhClientServerLevel implements IClientLevel, IServerLevel {
|
||||
}
|
||||
tree.close();
|
||||
tree = null;
|
||||
generationQueue.removeGenerator();
|
||||
renderBufferHandler.close();
|
||||
renderBufferHandler = null;
|
||||
renderFileHandler.flushAndSave(); //Ignore the completion feature so that this action is async
|
||||
renderFileHandler.close();
|
||||
renderFileHandler = null;
|
||||
generationQueue.removeGenerator();
|
||||
try {
|
||||
worldGenerator.close();
|
||||
} catch (Exception e) {
|
||||
|
||||
+28
-29
@@ -4,14 +4,12 @@ import com.seibel.lod.core.a7.pos.DhLodPos;
|
||||
import com.seibel.lod.core.logging.DhLoggerBuilder;
|
||||
import com.seibel.lod.core.util.Atomics;
|
||||
import com.seibel.lod.core.util.LodUtil;
|
||||
import org.apache.commons.lang3.NotImplementedException;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.concurrent.atomic.AtomicReferenceArray;
|
||||
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||
@@ -295,34 +293,35 @@ public class ConcurrentQuadCombinableProviderTree<R extends CombinableResult<R>>
|
||||
|
||||
private RootMap<R> rebaseUpward(int targetLevel) {
|
||||
rootMapGlobalLock.writeLock().lock();
|
||||
RootMap<R> map = rootMap.get();
|
||||
if (map.topLevel >= targetLevel) {
|
||||
rootMapGlobalLock.writeLock().unlock();
|
||||
return map;
|
||||
}
|
||||
// At this point, we have exclusive access to the rootMap.
|
||||
map.clean(); // Clean the map. (Could actually be done with just readLock.)
|
||||
RootMap<R> newMap = new RootMap<>(map.topLevel + 1);
|
||||
if (map.roots.isEmpty()) return newMap; // If there are no roots, just return the new map.
|
||||
map.roots.forEach((pos, nodeRef) -> {
|
||||
Node<R> node = nodeRef.get();
|
||||
if (node == null) return; // If null, ignore that node.
|
||||
LodUtil.assertTrue(pos.detail+1 == newMap.topLevel);
|
||||
LodUtil.assertTrue(node.parent.get() == null);
|
||||
LodUtil.assertTrue(node.pos.equals(pos));
|
||||
DhLodPos newPos = pos.convertUpwardsTo((byte) (pos.detail+1));
|
||||
try {
|
||||
RootMap<R> map = rootMap.get();
|
||||
if (map.topLevel >= targetLevel) {
|
||||
return map;
|
||||
}
|
||||
// At this point, we have exclusive access to the rootMap.
|
||||
map.clean(); // Clean the map. (Could actually be done with just readLock.)
|
||||
RootMap<R> newMap = new RootMap<>(map.topLevel + 1);
|
||||
map.roots.forEach((pos, nodeRef) -> {
|
||||
Node<R> node = nodeRef.get();
|
||||
if (node == null) return; // If null, ignore that node.
|
||||
LodUtil.assertTrue(pos.detail+1 == newMap.topLevel);
|
||||
LodUtil.assertTrue(node.parent.get() == null);
|
||||
LodUtil.assertTrue(node.pos.equals(pos));
|
||||
DhLodPos newPos = pos.convertUpwardsTo((byte) (pos.detail+1));
|
||||
|
||||
// Create the parent node, or if it already exists, use it to set the child node's parent.
|
||||
// NOTE: While this section is protected by the rootMapGlobalLock, we still need to use the normal
|
||||
// CAS methods to setAndGet the parent node, as the parent node may be GC'd concurrently by other threads
|
||||
// who have just completed the node's future, and caused the GC parent chain up to the new map.
|
||||
Node<R> newParentNode = newMap.setIfNullAndGet(newPos, new Node<R>(newPos, null));
|
||||
node.parent.set(newParentNode);
|
||||
});
|
||||
boolean casWorked = rootMap.compareAndSet(map, newMap);
|
||||
LodUtil.assertTrue(casWorked);
|
||||
rootMapGlobalLock.writeLock().unlock();
|
||||
return newMap;
|
||||
// Create the parent node, or if it already exists, use it to set the child node's parent.
|
||||
// NOTE: While this section is protected by the rootMapGlobalLock, we still need to use the normal
|
||||
// CAS methods to setAndGet the parent node, as the parent node may be GC'd concurrently by other threads
|
||||
// who have just completed the node's future, and caused the GC parent chain up to the new map.
|
||||
Node<R> newParentNode = newMap.setIfNullAndGet(newPos, new Node<R>(newPos, null));
|
||||
node.parent.set(newParentNode);
|
||||
});
|
||||
boolean casWorked = rootMap.compareAndSet(map, newMap);
|
||||
LodUtil.assertTrue(casWorked);
|
||||
return newMap;
|
||||
} finally {
|
||||
rootMapGlobalLock.writeLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
public void cleanIfNeeded() {
|
||||
|
||||
@@ -7,7 +7,7 @@ package com.seibel.lod.core.config.types;
|
||||
*/
|
||||
public class ConfigUIComment extends AbstractConfigType<String, ConfigUIComment>{
|
||||
public ConfigUIComment(String value) {
|
||||
super(ConfigEntryAppearance.ONLY_SHOW, value);
|
||||
super(ConfigEntryAppearance.ONLY_SHOW, value, null); //TODO: Is the listener: null right?
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
Reference in New Issue
Block a user