Make multi-level works again!
This commit is contained in:
@@ -6,6 +6,7 @@ import com.seibel.lod.core.a7.datatype.column.accessor.ColumnQuadView;
|
||||
import com.seibel.lod.core.a7.datatype.column.accessor.IColumnDatatype;
|
||||
import com.seibel.lod.core.a7.datatype.column.render.ColumnRenderBuffer;
|
||||
import com.seibel.lod.core.a7.datatype.full.ChunkSizedData;
|
||||
import com.seibel.lod.core.a7.datatype.full.FullDataSource;
|
||||
import com.seibel.lod.core.a7.datatype.transform.FullToColumnTransformer;
|
||||
import com.seibel.lod.core.a7.level.IClientLevel;
|
||||
import com.seibel.lod.core.a7.pos.DhSectionPos;
|
||||
@@ -47,6 +48,8 @@ public class ColumnRenderSource implements LodRenderSource, IColumnDatatype {
|
||||
public final long[] dataContainer;
|
||||
public final int[] airDataContainer;
|
||||
|
||||
public boolean isEmpty = true;
|
||||
|
||||
/**
|
||||
* Constructor of the ColumnDataType
|
||||
* @param maxVerticalSize the maximum vertical size of the container
|
||||
@@ -69,8 +72,12 @@ public class ColumnRenderSource implements LodRenderSource, IColumnDatatype {
|
||||
}
|
||||
private long[] readDataV1(DataInputStream inputData, int tempMaxVerticalData) throws IOException {
|
||||
int x = SECTION_SIZE * SECTION_SIZE * tempMaxVerticalData;
|
||||
byte[] data = new byte[x * Long.BYTES];
|
||||
short tempMinHeight = Short.reverseBytes(inputData.readShort());
|
||||
if (tempMinHeight == Short.MAX_VALUE) { //FIXME: Temp hack flag for marking a empty section
|
||||
return new long[x];
|
||||
}
|
||||
isEmpty = false;
|
||||
byte[] data = new byte[x * Long.BYTES];
|
||||
ByteBuffer bb = ByteBuffer.wrap(data).order(ByteOrder.LITTLE_ENDIAN);
|
||||
inputData.readFully(data);
|
||||
long[] result = new long[x];
|
||||
@@ -82,6 +89,7 @@ public class ColumnRenderSource implements LodRenderSource, IColumnDatatype {
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// Load from data stream with maxVerticalSize loaded from the data stream
|
||||
public ColumnRenderSource(DhSectionPos sectionPos, DataInputStream inputData, int version, ILevel level) throws IOException {
|
||||
this.sectionPos = sectionPos;
|
||||
@@ -186,6 +194,11 @@ public class ColumnRenderSource implements LodRenderSource, IColumnDatatype {
|
||||
output.writeByte(getDataDetail());
|
||||
output.writeByte((byte) verticalSize);
|
||||
// FIXME: yOffset is a int, but we only are writing a short.
|
||||
if (isEmpty) {
|
||||
output.writeByte(Short.MAX_VALUE & 0xFF);
|
||||
output.writeByte((Short.MAX_VALUE >> 8) & 0xFF);
|
||||
return false;
|
||||
}
|
||||
output.writeByte((byte) (yOffset & 0xFF));
|
||||
output.writeByte((byte) ((yOffset >> 8) & 0xFF));
|
||||
boolean allGenerated = true;
|
||||
@@ -258,7 +271,7 @@ public class ColumnRenderSource implements LodRenderSource, IColumnDatatype {
|
||||
|
||||
|
||||
private void tryBuildBuffer(IClientLevel level, LodQuadTree quadTree) {
|
||||
if (inBuildRenderBuffer == null && !ColumnRenderBuffer.isBusy()) {
|
||||
if (inBuildRenderBuffer == null && !ColumnRenderBuffer.isBusy() && !isEmpty) {
|
||||
ColumnRenderSource[] data = new ColumnRenderSource[ELodDirection.ADJ_DIRECTIONS.length];
|
||||
for (ELodDirection direction : ELodDirection.ADJ_DIRECTIONS) {
|
||||
LodRenderSection section = quadTree.getSection(sectionPos.getAdjacent(direction)); //FIXME: Handle traveling through different detail levels
|
||||
@@ -271,7 +284,7 @@ public class ColumnRenderSource implements LodRenderSource, IColumnDatatype {
|
||||
}
|
||||
private void cancelBuildBuffer() {
|
||||
if (inBuildRenderBuffer != null) {
|
||||
LOGGER.info("Cancelling build of render buffer for {}", sectionPos);
|
||||
//LOGGER.info("Cancelling build of render buffer for {}", sectionPos);
|
||||
inBuildRenderBuffer.cancel(true);
|
||||
inBuildRenderBuffer = null;
|
||||
}
|
||||
@@ -302,21 +315,28 @@ public class ColumnRenderSource implements LodRenderSource, IColumnDatatype {
|
||||
//FIXME: Temp Hack
|
||||
private long lastNs = -1;
|
||||
private static final long SWAP_TIMEOUT = /* 10 sec */ 10_000_000_000L;
|
||||
private static final long SWAP_BUSY_COLLISION_TIMEOUT = /* 1 sec */ 1_000_000_000L;
|
||||
|
||||
@Override
|
||||
public boolean trySwapRenderBuffer(LodQuadTree quadTree, AtomicReference<RenderBuffer> referenceSlot) {
|
||||
if (lastNs != -1 && System.nanoTime() - lastNs < SWAP_TIMEOUT) {
|
||||
return false;
|
||||
}
|
||||
if (inBuildRenderBuffer != null && inBuildRenderBuffer.isDone()) {
|
||||
lastNs = System.nanoTime();
|
||||
LOGGER.info("Swapping render buffer for {}", sectionPos);
|
||||
RenderBuffer oldBuffer = referenceSlot.getAndSet(inBuildRenderBuffer.join());
|
||||
if (oldBuffer instanceof ColumnRenderBuffer) usedBuffer = (ColumnRenderBuffer) oldBuffer;
|
||||
inBuildRenderBuffer = null;
|
||||
return true;
|
||||
} else if (inBuildRenderBuffer == null && !ColumnRenderBuffer.isBusy()) {
|
||||
tryBuildBuffer(level, quadTree);
|
||||
if (inBuildRenderBuffer != null) {
|
||||
if (inBuildRenderBuffer.isDone()) {
|
||||
lastNs = System.nanoTime();
|
||||
//LOGGER.info("Swapping render buffer for {}", sectionPos);
|
||||
RenderBuffer oldBuffer = referenceSlot.getAndSet(inBuildRenderBuffer.join());
|
||||
if (oldBuffer instanceof ColumnRenderBuffer) usedBuffer = (ColumnRenderBuffer) oldBuffer;
|
||||
inBuildRenderBuffer = null;
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
if (!isEmpty) {
|
||||
if (ColumnRenderBuffer.isBusy()) {
|
||||
lastNs += (long) (SWAP_BUSY_COLLISION_TIMEOUT * Math.random());
|
||||
} else tryBuildBuffer(level, quadTree);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -339,11 +359,14 @@ public class ColumnRenderSource implements LodRenderSource, IColumnDatatype {
|
||||
public void flushWrites(IClientLevel level) {
|
||||
boolean didSomething = false;
|
||||
while (!writeRequest.isEmpty()) {
|
||||
isEmpty = false;
|
||||
ChunkSizedData chunkData = writeRequest.poll();
|
||||
FullToColumnTransformer.writeFullDataChunkToColumnData(this, level, chunkData);
|
||||
didSomething = true;
|
||||
}
|
||||
if (didSomething) lastNs = -1; // Reset the timeout to allow rebuilding the buffer again
|
||||
if (didSomething) {
|
||||
lastNs = -1; // Reset the timeout to allow rebuilding the buffer again
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -191,7 +191,7 @@ public class ColumnRenderBuffer extends RenderBuffer {
|
||||
|
||||
public static CompletableFuture<ColumnRenderBuffer> build(IClientLevel clientLevel, ColumnRenderBuffer usedBuffer, ColumnRenderSource data, ColumnRenderSource[] adjData) {
|
||||
if (isBusy()) return null;
|
||||
LOGGER.info("RenderRegion startBuild @ {}", data.sectionPos);
|
||||
//LOGGER.info("RenderRegion startBuild @ {}", data.sectionPos);
|
||||
return CompletableFuture.supplyAsync(() -> {
|
||||
try {
|
||||
EVENT_LOGGER.trace("RenderRegion start QuadBuild @ {}", data.sectionPos);
|
||||
@@ -242,7 +242,7 @@ public class ColumnRenderBuffer extends RenderBuffer {
|
||||
throw e3;
|
||||
}
|
||||
}, BUFFER_UPLOADER).handle((v, e) -> {
|
||||
LOGGER.info("RenderRegion endBuild @ {}", data.sectionPos);
|
||||
//LOGGER.info("RenderRegion endBuild @ {}", data.sectionPos);
|
||||
if (e != null) {
|
||||
usedBuffer.close();
|
||||
return null;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.seibel.lod.core.a7.datatype.full;
|
||||
|
||||
import com.seibel.lod.core.a7.datatype.full.accessor.FullArrayView;
|
||||
import com.seibel.lod.core.a7.pos.DhLodPos;
|
||||
|
||||
public class ChunkSizedData extends FullArrayView {
|
||||
public final byte dataDetail;
|
||||
@@ -28,4 +29,8 @@ public class ChunkSizedData extends FullArrayView {
|
||||
public long emptyCount() {
|
||||
return 16*16 - nonEmptyCount();
|
||||
}
|
||||
|
||||
public DhLodPos getBBoxLodPos() {
|
||||
return new DhLodPos((byte) (dataDetail+4), x, z);
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import com.seibel.lod.core.a7.datatype.full.accessor.FullArrayView;
|
||||
import com.seibel.lod.core.a7.datatype.full.accessor.SingleFullArrayView;
|
||||
import com.seibel.lod.core.a7.level.ILevel;
|
||||
import com.seibel.lod.core.a7.pos.DhBlockPos2D;
|
||||
import com.seibel.lod.core.a7.pos.DhLodPos;
|
||||
import com.seibel.lod.core.a7.save.io.file.DataMetaFile;
|
||||
import com.seibel.lod.core.a7.datatype.LodDataSource;
|
||||
import com.seibel.lod.core.a7.util.IdMappingUtil;
|
||||
@@ -27,6 +28,7 @@ public class FullDataSource extends FullArrayView implements LodDataSource { //
|
||||
public static final long TYPE_ID = "FullDataSource".hashCode();
|
||||
private final DhSectionPos sectionPos;
|
||||
private int localVersion = 0;
|
||||
public boolean isEmpty = true;
|
||||
protected FullDataSource(DhSectionPos sectionPos) {
|
||||
super(new IdBiomeBlockStateMap(), new long[SECTION_SIZE*SECTION_SIZE][0], SECTION_SIZE);
|
||||
this.sectionPos = sectionPos;
|
||||
@@ -54,22 +56,47 @@ public class FullDataSource extends FullArrayView implements LodDataSource { //
|
||||
|
||||
@Override
|
||||
public void update(ChunkSizedData data) {
|
||||
if (getDataDetail() == 0 && data.dataDetail == 0) {
|
||||
LodUtil.assertTrue(sectionPos.getSectionBBoxPos().overlaps(data.getBBoxLodPos()));
|
||||
if (data.dataDetail == 0 && getDataDetail() == 0) {
|
||||
DhBlockPos2D chunkBlockPos = new DhBlockPos2D(data.x * 16, data.z * 16);
|
||||
DhBlockPos2D blockOffset = chunkBlockPos.subtract(sectionPos.getCorner().getCorner());
|
||||
LodUtil.assertTrue(blockOffset.x >= 0 && blockOffset.x < SECTION_SIZE && blockOffset.z >= 0 && blockOffset.z < SECTION_SIZE,
|
||||
"ChunkWrite of {} outside section {}. (cal offset {} larger than {})",
|
||||
new DHChunkPos(data.x, data.z), sectionPos, blockOffset, SECTION_SIZE);
|
||||
LodUtil.assertTrue(blockOffset.x >= 0 && blockOffset.x < SECTION_SIZE && blockOffset.z >= 0 && blockOffset.z < SECTION_SIZE);
|
||||
isEmpty = false;
|
||||
data.shadowCopyTo(this.subView(16, blockOffset.x, blockOffset.z));
|
||||
|
||||
for (int x = 0; x < 16; x++) {
|
||||
for (int z = 0; z < 16; z++) {
|
||||
SingleFullArrayView column = this.get(x+ blockOffset.x, z+ blockOffset.z);
|
||||
LodUtil.assertTrue(column.doesItExist());
|
||||
{ // DEBUG ASSERTION
|
||||
for (int x = 0; x < 16; x++) {
|
||||
for (int z = 0; z < 16; z++) {
|
||||
SingleFullArrayView column = this.get(x + blockOffset.x, z + blockOffset.z);
|
||||
LodUtil.assertTrue(column.doesItExist());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
} else if (data.dataDetail == 0 && getDataDetail() < 4) {
|
||||
int dataPerFull = 1 << getDataDetail();
|
||||
int fullSize = 16 / dataPerFull;
|
||||
DhLodPos dataOffset = data.getBBoxLodPos().getCorner(getDataDetail());
|
||||
DhLodPos baseOffset = sectionPos.getCorner(getDataDetail());
|
||||
int offsetX = dataOffset.x - baseOffset.x;
|
||||
int offsetZ = dataOffset.z - baseOffset.z;
|
||||
LodUtil.assertTrue(offsetX >= 0 && offsetX < SECTION_SIZE && offsetZ >= 0 && offsetZ < SECTION_SIZE);
|
||||
isEmpty = false;
|
||||
for (int ox = 0; ox < fullSize; ox++) {
|
||||
for (int oz = 0; oz < fullSize; oz++) {
|
||||
SingleFullArrayView column = this.get(ox + offsetX, oz + offsetZ);
|
||||
column.downsampleFrom(data.subView(dataPerFull, ox * dataPerFull, oz * dataPerFull));
|
||||
}
|
||||
}
|
||||
} else if (data.dataDetail == 0 && getDataDetail() >= 4) {
|
||||
//FIXME: TEMPORARY
|
||||
int chunkPerFull = 1 << (getDataDetail() - 4);
|
||||
if (data.x % chunkPerFull != 0 || data.z % chunkPerFull != 0) return;
|
||||
DhLodPos baseOffset = sectionPos.getCorner(getDataDetail());
|
||||
DhLodPos dataOffset = data.getBBoxLodPos().convertUpwardsTo(getDataDetail());
|
||||
int offsetX = dataOffset.x - baseOffset.x;
|
||||
int offsetZ = dataOffset.z - baseOffset.z;
|
||||
LodUtil.assertTrue(offsetX >= 0 && offsetX < SECTION_SIZE && offsetZ >= 0 && offsetZ < SECTION_SIZE);
|
||||
isEmpty = false;
|
||||
data.get(0,0).deepCopyTo(get(offsetX, offsetZ));
|
||||
} else {
|
||||
LodUtil.assertNotReach();
|
||||
//TODO;
|
||||
@@ -83,6 +110,10 @@ public class FullDataSource extends FullArrayView implements LodDataSource { //
|
||||
dos.writeInt(getDataDetail());
|
||||
dos.writeInt(size);
|
||||
dos.writeInt(level.getMinY());
|
||||
if (isEmpty) {
|
||||
dos.writeInt(0x00000001);
|
||||
return;
|
||||
}
|
||||
dos.writeInt(0xFFFFFFFF);
|
||||
// Data array length
|
||||
for (int x = 0; x < size; x++) {
|
||||
@@ -124,6 +155,11 @@ public class FullDataSource extends FullArrayView implements LodDataSource { //
|
||||
LOGGER.warn("Data minY mismatch: {} != {}. Will ignore data's y level", minY, level.getMinY());
|
||||
int end = dos.readInt();
|
||||
// Data array length
|
||||
if (end == 0x00000001) {
|
||||
// Section is empty
|
||||
return new FullDataSource(dataFile.pos);
|
||||
}
|
||||
// Non-empty section
|
||||
if (end != 0xFFFFFFFF) throw new IOException("invalid header end guard");
|
||||
long[][] data = new long[size*size][];
|
||||
for (int x = 0; x < size; x++) {
|
||||
@@ -154,9 +190,32 @@ public class FullDataSource extends FullArrayView implements LodDataSource { //
|
||||
super(mapping, data, SECTION_SIZE);
|
||||
LodUtil.assertTrue(data.length == SECTION_SIZE*SECTION_SIZE);
|
||||
this.sectionPos = pos;
|
||||
isEmpty = false;
|
||||
}
|
||||
|
||||
public static FullDataSource createEmpty(DhSectionPos pos) {
|
||||
return new FullDataSource(pos);
|
||||
}
|
||||
|
||||
|
||||
public static FullDataSource createFromLower(DhSectionPos pos, FullDataSource[] lower) {
|
||||
FullDataSource newData = new FullDataSource(pos);
|
||||
LodUtil.assertTrue(lower.length == 4, "Non direct convert from lower is not yet implemented");
|
||||
int halfSize = SECTION_SIZE/2;
|
||||
DhSectionPos cornerPos = pos.getChild(0);
|
||||
for (FullDataSource lowerSection : lower) {
|
||||
if (lowerSection==null || lowerSection.isEmpty) continue;
|
||||
int offsetX = lowerSection.sectionPos.sectionX == cornerPos.sectionX ? 0 : halfSize;
|
||||
int offsetZ = lowerSection.sectionPos.sectionZ == cornerPos.sectionZ ? 0 : halfSize;
|
||||
newData.isEmpty = false;
|
||||
for (int ox = 0; ox < halfSize; ox++) {
|
||||
for (int oz = 0; oz < halfSize; oz++) {
|
||||
FullArrayView quadView = lowerSection.subView(2, ox*2, oz*2);
|
||||
SingleFullArrayView target = newData.get(ox+offsetX, oz+offsetZ);
|
||||
target.downsampleFrom(quadView);
|
||||
}
|
||||
}
|
||||
}
|
||||
return newData;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ public class FullArrayView implements IFullDataView {
|
||||
|
||||
@Override
|
||||
public SingleFullArrayView get(int index) {
|
||||
return new SingleFullArrayView(mapping, dataArrays, index + offset);
|
||||
return get(index/size, index%size);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.seibel.lod.core.a7.datatype.full.accessor;
|
||||
|
||||
import com.seibel.lod.core.a7.datatype.full.IdBiomeBlockStateMap;
|
||||
import com.seibel.lod.core.util.LodUtil;
|
||||
|
||||
import java.util.Iterator;
|
||||
|
||||
@@ -13,7 +14,7 @@ public interface IFullDataView {
|
||||
default Iterator<SingleFullArrayView> iterator() {
|
||||
return new Iterator<SingleFullArrayView>() {
|
||||
private int index = 0;
|
||||
private final int size = width();
|
||||
private final int size = width()*width();
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return index < size;
|
||||
@@ -21,6 +22,7 @@ public interface IFullDataView {
|
||||
|
||||
@Override
|
||||
public SingleFullArrayView next() {
|
||||
LodUtil.assertTrue(hasNext(), "No more data to iterate!");
|
||||
return get(index++);
|
||||
}
|
||||
};
|
||||
|
||||
+21
-1
@@ -61,7 +61,7 @@ public class SingleFullArrayView implements IFullDataView {
|
||||
return this;
|
||||
}
|
||||
|
||||
//WARNING: It will potentially share the underlying array object!
|
||||
//WARNING: It may potentially share the underlying array object!
|
||||
public void shadowCopyTo(SingleFullArrayView target) {
|
||||
if (target.mapping.equals(mapping)) {
|
||||
target.dataArrays[target.offset] = dataArrays[offset];
|
||||
@@ -76,4 +76,24 @@ public class SingleFullArrayView implements IFullDataView {
|
||||
target.dataArrays[target.offset] = newData;
|
||||
}
|
||||
}
|
||||
public void deepCopyTo(SingleFullArrayView target) {
|
||||
if (target.mapping.equals(mapping)) {
|
||||
target.dataArrays[target.offset] = dataArrays[offset].clone();
|
||||
}
|
||||
else {
|
||||
int[] map = target.mapping.computeAndMergeMapFrom(mapping);
|
||||
long[] sourceData = dataArrays[offset];
|
||||
long[] newData = new long[sourceData.length];
|
||||
for (int i = 0; i < newData.length; i++) {
|
||||
newData[i] = FullFormat.remap(map, sourceData[i]);
|
||||
}
|
||||
target.dataArrays[target.offset] = newData;
|
||||
}
|
||||
}
|
||||
|
||||
public void downsampleFrom(IFullDataView source) {
|
||||
//TODO: Temp downsample method
|
||||
SingleFullArrayView firstColumn = source.get(0);
|
||||
firstColumn.deepCopyTo(this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,6 +33,8 @@ public class FullToColumnTransformer {
|
||||
final byte dataDetail = data.getDataDetail();
|
||||
final int vertSize = Config.Client.Graphics.Quality.verticalQuality.get().calculateMaxVerticalData(data.getDataDetail());
|
||||
final ColumnRenderSource columnSource = new ColumnRenderSource(pos, vertSize, level.getMinY());
|
||||
if (data.isEmpty) return columnSource;
|
||||
columnSource.isEmpty = false;
|
||||
|
||||
if (dataDetail == columnSource.getDataDetail()) {
|
||||
int baseX = pos.getCorner().getCorner().x;
|
||||
|
||||
@@ -149,12 +149,13 @@ public class GenerationQueue implements PlaceHolderQueue {
|
||||
|
||||
data.forEachPos((x,z) -> {
|
||||
ChunkSizedData chunkData = data.get(x,z);
|
||||
DhLodPos chunkDataPos = new DhLodPos((byte)(chunkData.dataDetail + 4), chunkData.x, chunkData.z).convertUpwardsTo(sectionDetail);
|
||||
DhSectionPos sectionPos = new DhSectionPos(chunkDataPos.detail, chunkDataPos.x, chunkDataPos.z);
|
||||
DhLodPos chunkDataPos = new DhLodPos((byte)(chunkData.dataDetail + 4), chunkData.x, chunkData.z);
|
||||
if (pos.getSectionBBoxPos().overlaps(chunkDataPos))
|
||||
writeConsumer.accept(pos, chunkData);
|
||||
//DhSectionPos sectionPos = new DhSectionPos(chunkDataPos.detail, chunkDataPos.x, chunkDataPos.z);
|
||||
//logger.info("Writing chunk {} with data detail {} to section {}",
|
||||
// new DhLodPos((byte)(chunkData.dataDetail + 4), chunkData.x, chunkData.z),
|
||||
// dataDetail, sectionPos);
|
||||
writeConsumer.accept(sectionPos, chunkData);
|
||||
});
|
||||
//
|
||||
// for (int dsx = 0; dsx < sectionCount; dsx++) {
|
||||
|
||||
@@ -41,6 +41,11 @@ public class DhLodPos {
|
||||
return new DhBlockPos2D(getX().toBlock(), getZ().toBlock());
|
||||
}
|
||||
|
||||
public DhLodPos getCorner(byte newDetail) {
|
||||
LodUtil.assertTrue(newDetail <= detail);
|
||||
return new DhLodPos(newDetail, x << (detail-newDetail), z << (detail-newDetail));
|
||||
}
|
||||
|
||||
public DhLodPos convertUpwardsTo(byte newDetail) {
|
||||
LodUtil.assertTrue(newDetail >= detail);
|
||||
return new DhLodPos(newDetail, Math.floorDiv(x, 1<<(newDetail-detail)), Math.floorDiv(z, 1<<(newDetail-detail)));
|
||||
|
||||
@@ -19,6 +19,7 @@ public class LodRenderSection {
|
||||
private LodRenderSource lodRenderSource;
|
||||
private CompletableFuture<LodRenderSource> loadFuture;
|
||||
private boolean isRenderEnabled = false;
|
||||
private IRenderSourceProvider provider = null;
|
||||
|
||||
// Create sub region
|
||||
public LodRenderSection(DhSectionPos pos) {
|
||||
@@ -27,28 +28,35 @@ public class LodRenderSection {
|
||||
|
||||
public void enableRender(IClientLevel level, LodQuadTree quadTree) {
|
||||
if (isRenderEnabled) return;
|
||||
if (lodRenderSource != null) {
|
||||
lodRenderSource.enableRender(level, quadTree);
|
||||
}
|
||||
loadFuture = provider.read(pos);
|
||||
isRenderEnabled = true;
|
||||
}
|
||||
public void disableRender() {
|
||||
if (!isRenderEnabled) return;
|
||||
if (lodRenderSource != null) {
|
||||
lodRenderSource.disableRender();
|
||||
lodRenderSource.dispose();
|
||||
lodRenderSource = null;
|
||||
}
|
||||
if (loadFuture != null) {
|
||||
loadFuture.cancel(true);
|
||||
loadFuture = null;
|
||||
}
|
||||
isRenderEnabled = false;
|
||||
}
|
||||
|
||||
public void load(IRenderSourceProvider renderDataProvider) {
|
||||
if (loadFuture != null || lodRenderSource != null) throw new IllegalStateException("Reloading is not supported!");
|
||||
loadFuture = renderDataProvider.read(pos);
|
||||
provider = renderDataProvider;
|
||||
}
|
||||
public void reload(IRenderSourceProvider renderDataProvider) {
|
||||
if (loadFuture != null) throw new IllegalStateException("This section is already loading!");
|
||||
if (lodRenderSource == null) throw new IllegalStateException("This section is not loaded!");
|
||||
lodRenderSource.dispose();
|
||||
lodRenderSource = null;
|
||||
if (loadFuture != null) {
|
||||
loadFuture.cancel(true);
|
||||
loadFuture = null;
|
||||
}
|
||||
if (lodRenderSource != null) {
|
||||
lodRenderSource.dispose();
|
||||
lodRenderSource = null;
|
||||
}
|
||||
loadFuture = renderDataProvider.read(pos);
|
||||
}
|
||||
|
||||
@@ -59,7 +67,8 @@ public class LodRenderSection {
|
||||
if (isRenderEnabled) {
|
||||
lodRenderSource.enableRender(level, quadTree);
|
||||
}
|
||||
} else if (lodRenderSource != null) {
|
||||
}
|
||||
if (lodRenderSource != null) {
|
||||
lodRenderSource.flushWrites(level);
|
||||
}
|
||||
}
|
||||
@@ -73,11 +82,11 @@ public class LodRenderSection {
|
||||
}
|
||||
|
||||
public boolean canRender() {
|
||||
return isLoaded() && isRenderEnabled && lodRenderSource.isRenderReady();
|
||||
return isLoaded() && isRenderEnabled && lodRenderSource != null && lodRenderSource.isRenderReady();
|
||||
}
|
||||
|
||||
public boolean isLoaded() {
|
||||
return lodRenderSource != null;
|
||||
return provider != null;
|
||||
}
|
||||
|
||||
//FIXME: Used by RenderBufferHandler
|
||||
@@ -86,7 +95,7 @@ public class LodRenderSection {
|
||||
}
|
||||
|
||||
public boolean isLoading() {
|
||||
return loadFuture != null;
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean isOutdated() {
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
package com.seibel.lod.core.a7.save.io.file;
|
||||
|
||||
import java.awt.*;
|
||||
import java.io.*;
|
||||
import java.lang.ref.*;
|
||||
import java.security.Provider;
|
||||
import java.sql.Ref;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
@@ -13,6 +16,8 @@ import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import com.seibel.lod.core.a7.datatype.LodDataSource;
|
||||
import com.seibel.lod.core.a7.datatype.DataSourceLoader;
|
||||
@@ -107,10 +112,26 @@ public class DataMetaFile extends MetaFile {
|
||||
}
|
||||
|
||||
// Make a new MetaFile. It doesn't load or write any metadata itself.
|
||||
public DataMetaFile(ILevel level, File path, DhSectionPos pos) {
|
||||
public DataMetaFile(ILevel level, File path, DhSectionPos pos, CompletableFuture<LodDataSource> creator) {
|
||||
super(path, pos);
|
||||
debugCheck();
|
||||
this.level = level;
|
||||
CompletableFuture<LodDataSource> future = new CompletableFuture<>();
|
||||
data.set(future);
|
||||
creator.thenApply((f) -> {
|
||||
applyWriteQueue(f);
|
||||
return f;
|
||||
}).whenComplete((f, e) -> {
|
||||
if (e != null) {
|
||||
LOGGER.error("Uncaught error on creation {}: ", path, e);
|
||||
future.complete(null);
|
||||
data.set(null);
|
||||
} else {
|
||||
future.complete(f);
|
||||
new DataObjTracker(f);
|
||||
data.set(new SoftReference<>(f));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public boolean isValid(int version) {
|
||||
|
||||
@@ -7,6 +7,7 @@ import com.seibel.lod.core.a7.datatype.full.ChunkSizedData;
|
||||
import com.seibel.lod.core.a7.datatype.full.FullDataSource;
|
||||
import com.seibel.lod.core.a7.datatype.full.FullFormat;
|
||||
import com.seibel.lod.core.a7.level.IServerLevel;
|
||||
import com.seibel.lod.core.a7.pos.DhLodPos;
|
||||
import com.seibel.lod.core.a7.pos.DhSectionPos;
|
||||
import com.seibel.lod.core.logging.DhLoggerBuilder;
|
||||
import com.seibel.lod.core.objects.DHChunkPos;
|
||||
@@ -22,14 +23,17 @@ import java.util.Comparator;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
public class LocalDataFileHandler implements IDataSourceProvider {
|
||||
// Note: Single main thread only for now. May make it multi-thread later, depending on the usage.
|
||||
private static final Logger LOGGER = DhLoggerBuilder.getLogger();
|
||||
final ExecutorService fileReaderThread = LodUtil.makeSingleThreadPool("FileReaderThread");
|
||||
final ExecutorService fileReaderThread = LodUtil.makeThreadPool(4, "FileReaderThread");
|
||||
final ConcurrentHashMap<DhSectionPos, DataMetaFile> files = new ConcurrentHashMap<>();
|
||||
final IServerLevel level;
|
||||
final File saveDir;
|
||||
AtomicInteger topDetailLevel = new AtomicInteger(-1);
|
||||
final int minDetailLevel = FullDataSource.SECTION_SIZE_OFFSET;
|
||||
|
||||
|
||||
public LocalDataFileHandler(IServerLevel level, File saveRootDir) {
|
||||
@@ -97,6 +101,7 @@ public class LocalDataFileHandler implements IDataSourceProvider {
|
||||
fileToUse = metaFiles.iterator().next();
|
||||
}
|
||||
// Add file to the list of files.
|
||||
topDetailLevel.updateAndGet(v -> Math.max(v, fileToUse.pos.sectionDetail));
|
||||
files.put(pos, fileToUse);
|
||||
}
|
||||
}
|
||||
@@ -106,6 +111,16 @@ public class LocalDataFileHandler implements IDataSourceProvider {
|
||||
*/
|
||||
@Override
|
||||
public CompletableFuture<LodDataSource> read(DhSectionPos pos) {
|
||||
topDetailLevel.updateAndGet(v -> Math.max(v, pos.sectionDetail));
|
||||
DataMetaFile metaFile = files.get(pos);
|
||||
if (metaFile == null) {
|
||||
return CompletableFuture.completedFuture(null);
|
||||
}
|
||||
return metaFile.loadOrGetCached(fileReaderThread);
|
||||
}
|
||||
|
||||
// This prevents new higher detail levels from being created by not updating the topDetailLevel.
|
||||
private CompletableFuture<LodDataSource> readWithoutUpdate(DhSectionPos pos) {
|
||||
DataMetaFile metaFile = files.get(pos);
|
||||
if (metaFile == null) {
|
||||
return CompletableFuture.completedFuture(null);
|
||||
@@ -118,25 +133,66 @@ public class LocalDataFileHandler implements IDataSourceProvider {
|
||||
*/
|
||||
@Override
|
||||
public void write(DhSectionPos sectionPos, ChunkSizedData chunkData) {
|
||||
DhLodPos chunkPos = new DhLodPos((byte) (chunkData.dataDetail+4), chunkData.x, chunkData.z);
|
||||
LodUtil.assertTrue(chunkPos.overlaps(sectionPos.getSectionBBoxPos()), "Chunk {} does not overlap section {}", chunkPos, sectionPos);
|
||||
chunkPos = chunkPos.convertUpwardsTo((byte) minDetailLevel); // TODO: Handle if chunkData has higher detail than lowestDetail.
|
||||
recursiveWrite(new DhSectionPos(chunkPos.detail, chunkPos.x, chunkPos.z), chunkData);
|
||||
}
|
||||
private void recursiveWrite(DhSectionPos sectionPos, ChunkSizedData chunkData) {
|
||||
DataMetaFile metaFile = files.get(sectionPos);
|
||||
if (metaFile != null) { // Fast path: if there is a file for this section, just write to it.
|
||||
metaFile.addToWriteQueue(chunkData);
|
||||
return;
|
||||
} else if (sectionPos.sectionDetail <= minDetailLevel) {
|
||||
File file = computeDefaultFilePath(sectionPos);
|
||||
//FIXME: Handle file already exists issue. Possibly by renaming the file.
|
||||
LodUtil.assertTrue(!file.exists(), "File {} already exist for path {}", file, sectionPos);
|
||||
CompletableFuture<LodDataSource> gen = new CompletableFuture<>();
|
||||
DataMetaFile newMetaFile = new DataMetaFile(level, file, sectionPos, gen);
|
||||
metaFile = files.putIfAbsent(sectionPos, newMetaFile); // This is a CAS with expected null value.
|
||||
if (metaFile == null) {
|
||||
newMetaFile.addToWriteQueue(chunkData);
|
||||
CompletableFuture.runAsync(() -> gen.complete(FullDataSource.createEmpty(sectionPos)), fileReaderThread)
|
||||
.exceptionally((e) -> {
|
||||
gen.completeExceptionally(e);
|
||||
return null;
|
||||
});
|
||||
} else {
|
||||
metaFile.addToWriteQueue(chunkData);
|
||||
gen.cancel(true);
|
||||
}
|
||||
} else {
|
||||
File file = computeDefaultFilePath(sectionPos);
|
||||
//FIXME: Handle file already exists issue. Possibly by renaming the file.
|
||||
LodUtil.assertTrue(!file.exists(), "File {} already exist for path {}", file, sectionPos);
|
||||
CompletableFuture<LodDataSource> gen = new CompletableFuture<>();
|
||||
DataMetaFile newMetaFile = new DataMetaFile(level, file, sectionPos, gen);
|
||||
metaFile = files.putIfAbsent(sectionPos, newMetaFile); // This is a CAS with expected null value.
|
||||
if (metaFile == null) {
|
||||
newMetaFile.addToWriteQueue(chunkData);
|
||||
// Create future that generate downsized file
|
||||
CompletableFuture<LodDataSource> subChunk0 = readWithoutUpdate(sectionPos.getChild(0));
|
||||
CompletableFuture<LodDataSource> subChunk1 = readWithoutUpdate(sectionPos.getChild(1));
|
||||
CompletableFuture<LodDataSource> subChunk2 = readWithoutUpdate(sectionPos.getChild(2));
|
||||
CompletableFuture<LodDataSource> subChunk3 = readWithoutUpdate(sectionPos.getChild(3));
|
||||
CompletableFuture.allOf(subChunk0, subChunk1, subChunk2, subChunk3)
|
||||
.thenAccept(v ->
|
||||
gen.complete(FullDataSource.createFromLower(sectionPos, new FullDataSource[]{
|
||||
(FullDataSource) subChunk0.join(),
|
||||
(FullDataSource) subChunk1.join(),
|
||||
(FullDataSource) subChunk2.join(),
|
||||
(FullDataSource) subChunk3.join()
|
||||
}))
|
||||
).exceptionally((e) -> {
|
||||
gen.completeExceptionally(e);
|
||||
return null;
|
||||
});
|
||||
} else {
|
||||
metaFile.addToWriteQueue(chunkData);
|
||||
gen.cancel(true);
|
||||
}
|
||||
}
|
||||
// Slow path: if there is no file for this section, create one.
|
||||
File file = computeDefaultFilePath(sectionPos);
|
||||
//FIXME: Handle file already exists issue. Possibly by renaming the file.
|
||||
LodUtil.assertTrue(!file.exists(), "File {} already exist for path {}", file, sectionPos);
|
||||
DataMetaFile newMetaFile = new DataMetaFile(level, file, sectionPos);
|
||||
//LOGGER.info("Created new Data file at {} for sect {}", newMetaFile.path, sectionPos);
|
||||
|
||||
// We add to the queue first so on CAS onto the map, no other thread
|
||||
// will see the new file without our write entry.
|
||||
newMetaFile.addToWriteQueue(chunkData);
|
||||
DataMetaFile casResult = files.putIfAbsent(sectionPos, newMetaFile); // This is a CAS with expected null value.
|
||||
if (casResult != null) { // another thread already created the file. CAS failed.
|
||||
// Drop our version and use the cas result.
|
||||
casResult.addToWriteQueue(chunkData);
|
||||
if (sectionPos.sectionDetail <= topDetailLevel.get()) {
|
||||
recursiveWrite(sectionPos.getParent(), chunkData);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,8 +4,10 @@ import com.google.common.collect.HashMultimap;
|
||||
import com.seibel.lod.core.a7.PlaceHolderQueue;
|
||||
import com.seibel.lod.core.a7.datatype.PlaceHolderRenderSource;
|
||||
import com.seibel.lod.core.a7.datatype.LodRenderSource;
|
||||
import com.seibel.lod.core.a7.datatype.column.ColumnRenderSource;
|
||||
import com.seibel.lod.core.a7.datatype.full.ChunkSizedData;
|
||||
import com.seibel.lod.core.a7.level.IClientLevel;
|
||||
import com.seibel.lod.core.a7.pos.DhLodPos;
|
||||
import com.seibel.lod.core.a7.save.io.file.IDataSourceProvider;
|
||||
import com.seibel.lod.core.a7.pos.DhSectionPos;
|
||||
import com.seibel.lod.core.logging.DhLoggerBuilder;
|
||||
@@ -132,10 +134,22 @@ public class RenderFileHandler implements IRenderSourceProvider {
|
||||
@Override
|
||||
public void write(DhSectionPos sectionPos, ChunkSizedData chunkData) {
|
||||
dataSourceProvider.write(sectionPos, chunkData);
|
||||
RenderMetaFile metaFile = files.get(sectionPos);
|
||||
recursive_write(sectionPos,chunkData);
|
||||
}
|
||||
|
||||
private void recursive_write(DhSectionPos sectPos, ChunkSizedData chunkData) {
|
||||
if (!sectPos.getSectionBBoxPos().overlaps(new DhLodPos((byte) (4 + chunkData.dataDetail), chunkData.x, chunkData.z))) return;
|
||||
if (sectPos.sectionDetail > ColumnRenderSource.SECTION_SIZE_OFFSET) {
|
||||
recursive_write(sectPos.getChild(0), chunkData);
|
||||
recursive_write(sectPos.getChild(1), chunkData);
|
||||
recursive_write(sectPos.getChild(2), chunkData);
|
||||
recursive_write(sectPos.getChild(3), chunkData);
|
||||
}
|
||||
RenderMetaFile metaFile = files.get(sectPos);
|
||||
if (metaFile != null) { // Fast path: if there is a file for this section, just write to it.
|
||||
metaFile.updateChunkIfNeeded(chunkData);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -307,7 +307,7 @@ public class BatchGenerator implements IChunkGenerator
|
||||
|
||||
@Override
|
||||
public byte getMaxGenerationGranularity() {
|
||||
return 16;
|
||||
return 8;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -30,7 +30,7 @@ import com.seibel.lod.core.enums.config.EHorizontalQuality;
|
||||
public class DetailDistanceUtil
|
||||
{
|
||||
private static byte minDetail = Config.Client.Graphics.Quality.drawResolution.get().detailLevel;
|
||||
private static final byte maxDetail = LodUtil.DETAIL_OPTIONS;
|
||||
private static final byte maxDetail = Byte.MAX_VALUE;
|
||||
private static final double minDistance = 0;
|
||||
private static double distanceUnit = 16 * Config.Client.Graphics.Quality.horizontalScale.get();
|
||||
private static double maxDistance = Config.Client.Graphics.Quality.lodChunkRenderDistance.get() * 16 * 2;
|
||||
|
||||
Reference in New Issue
Block a user