Skip to content

Commit

Permalink
Simplify heightmap calculation and avoid loading empty sections (#3045)
Browse files Browse the repository at this point in the history
* Simplify heightmap calculation and avoid loading empty sections

* rename label
  • Loading branch information
SirYwell authored Dec 24, 2024
1 parent c8f1984 commit b95bcde
Show file tree
Hide file tree
Showing 9 changed files with 76 additions and 24 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -1154,6 +1154,13 @@ public boolean hasSection(int layer) {
return getSections(false)[layer] != null;
}

@Override
public boolean hasNonEmptySection(int layer) {
layer -= getMinSectionPosition();
LevelChunkSection section = getSections(false)[layer];
return section != null && !section.hasOnlyAir();
}

@Override
@SuppressWarnings("unchecked")
public synchronized boolean trim(boolean aggressive) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1152,6 +1152,13 @@ public boolean hasSection(int layer) {
return getSections(false)[layer] != null;
}

@Override
public boolean hasNonEmptySection(int layer) {
layer -= getMinSectionPosition();
LevelChunkSection section = getSections(false)[layer];
return section != null && !section.hasOnlyAir();
}

@Override
@SuppressWarnings("unchecked")
public synchronized boolean trim(boolean aggressive) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1155,6 +1155,13 @@ public boolean hasSection(int layer) {
return getSections(false)[layer] != null;
}

@Override
public boolean hasNonEmptySection(int layer) {
layer -= getMinSectionPosition();
LevelChunkSection section = getSections(false)[layer];
return section != null && !section.hasOnlyAir();
}

@Override
@SuppressWarnings("unchecked")
public synchronized boolean trim(boolean aggressive) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1150,6 +1150,13 @@ public boolean hasSection(int layer) {
return getSections(false)[layer] != null;
}

@Override
public boolean hasNonEmptySection(int layer) {
layer -= getMinSectionPosition();
LevelChunkSection section = getSections(false)[layer];
return section != null && !section.hasOnlyAir();
}

@Override
@SuppressWarnings("unchecked")
public synchronized boolean trim(boolean aggressive) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1151,6 +1151,13 @@ public boolean hasSection(int layer) {
return getSections(false)[layer] != null;
}

@Override
public boolean hasNonEmptySection(int layer) {
layer -= getMinSectionPosition();
LevelChunkSection section = getSections(false)[layer];
return section != null && !section.hasOnlyAir();
}

@Override
@SuppressWarnings("unchecked")
public synchronized boolean trim(boolean aggressive) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1149,6 +1149,13 @@ public boolean hasSection(int layer) {
return getSections(false)[layer] != null;
}

@Override
public boolean hasNonEmptySection(int layer) {
layer -= getMinSectionPosition();
LevelChunkSection section = getSections(false)[layer];
return section != null && !section.hasOnlyAir();
}

@Override
@SuppressWarnings("unchecked")
public synchronized boolean trim(boolean aggressive) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,10 @@ public class HeightmapProcessor implements IBatchProcessor {
private static final HeightMapType[] TYPES = HeightMapType.values();
private static final int BLOCKS_PER_Y_SHIFT = 8; // log2(256)
private static final int BLOCKS_PER_Y = 256; // 16 x 16
private static final boolean[] COMPLETE = new boolean[BLOCKS_PER_Y];
private static final char[] AIR_LAYER = new char[4096];
private static final int NEEDED_UPDATES = TYPES.length * BLOCKS_PER_Y;

static {
Arrays.fill(COMPLETE, true);
Arrays.fill(AIR_LAYER, (char) BlockTypesCache.ReservedIDs.AIR);
}

Expand All @@ -49,13 +48,12 @@ private static int index(int y, int offset) {
public IChunkSet processSet(IChunk chunk, IChunkGet get, IChunkSet set) {
// each heightmap gets one 16*16 array
int[][] heightmaps = new int[TYPES.length][BLOCKS_PER_Y];
boolean[][] updated = new boolean[TYPES.length][BLOCKS_PER_Y];
int skip = 0;
int allSkipped = (1 << TYPES.length) - 1; // lowest types.length bits are set
layer:
byte[] updated = new byte[BLOCKS_PER_Y];
int updateCount = 0; // count updates, this way we know when we're finished
layerIter:
for (int layer = maxY >> 4; layer >= minY >> 4; layer--) {
boolean hasSectionSet = set.hasSection(layer);
boolean hasSectionGet = get.hasSection(layer);
boolean hasSectionSet = set.hasNonEmptySection(layer);
boolean hasSectionGet = get.hasNonEmptySection(layer);
if (!(hasSectionSet || hasSectionGet)) {
continue;
}
Expand All @@ -78,7 +76,7 @@ public IChunkSet processSet(IChunk chunk, IChunkGet get, IChunkSet set) {
if (ordinal == BlockTypesCache.ReservedIDs.__RESERVED__) {
if (!hasSectionGet) {
if (!hasSectionSet) {
continue layer;
continue layerIter;
}
continue;
} else if (getSection == null) {
Expand All @@ -88,7 +86,7 @@ public IChunkSet processSet(IChunk chunk, IChunkGet get, IChunkSet set) {
|| Arrays.equals(getSection, AIR_LAYER)) {
hasSectionGet = false;
if (!hasSectionSet) {
continue layer;
continue layerIter;
}
continue;
}
Expand All @@ -103,30 +101,26 @@ public IChunkSet processSet(IChunk chunk, IChunkGet get, IChunkSet set) {
if (block == null) {
continue;
}
byte updateStateAtJ = updated[j];
for (int i = 0; i < TYPES.length; i++) {
if ((skip & (1 << i)) != 0) {
continue; // skip finished height map
int bitFlag = 1 << i;
if ((updateStateAtJ & bitFlag) != 0) {
continue; // skip finished height map at this column
}
HeightMapType type = TYPES[i];
// ignore if that position was already set
if (!updated[i][j] && type.includes(block)) {
if (type.includes(block)) {
// mc requires + 1, heightmaps are normalized internally, thus we need to "zero" them.
heightmaps[i][j] = ((layer - get.getMinSectionPosition()) << 4) + y + 1;
updated[i][j] = true; // mark as updated
updated[j] |= (byte) bitFlag; // mark as updated
if (++updateCount == NEEDED_UPDATES) {
break layerIter; // all heightmaps in all columns updated
}

}
}
}
}
for (int i = 0; i < updated.length; i++) {
if ((skip & (1 << i)) == 0 // if already true, skip array equality check
&& Arrays.equals(updated[i], COMPLETE)) {
skip |= 1 << i;
}
}
if (skip != allSkipped) {
continue;
}
break; // all maps are processed
}
for (int i = 0; i < TYPES.length; i++) {
set.setHeightMap(TYPES[i], heightmaps[i]);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,17 @@ public interface IBlocks extends Trimable {
*/
boolean hasSection(int layer);

/**
* {@return whether the chunk has a section that has any non-air/reserved blocks}
* This method might be conservative and return {@code true} even if the section is empty.
*
* @param layer the section's layer
* @since TODO
*/
default boolean hasNonEmptySection(int layer) {
return hasSection(layer);
}

/**
* Obtain the specified chunk section stored as an array of ordinals. Uses normal minecraft chunk-section position indices
* (length 4096). Operations synchronises on the section and will load the section into memory if not present. For chunk
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -882,6 +882,11 @@ public boolean hasSection(int layer) {
return chunkExisting != null && chunkExisting.hasSection(layer);
}

@Override
public boolean hasNonEmptySection(final int layer) {
return chunkExisting != null && chunkExisting.hasNonEmptySection(layer);
}

@Override
public synchronized void filterBlocks(Filter filter, ChunkFilterBlock block, @Nullable Region region, boolean full) {
final IChunkGet get = getOrCreateGet();
Expand Down

0 comments on commit b95bcde

Please sign in to comment.