Attempted optimizations for world processing

This commit is contained in:
mcrcortex
2025-06-19 16:03:31 +10:00
parent 6326870525
commit 156b30756d
7 changed files with 82 additions and 40 deletions

View File

@@ -10,6 +10,7 @@ public class VoxelizedSection {
public int x; public int x;
public int y; public int y;
public int z; public int z;
public int lvl0NonAirCount;
public final long[] section; public final long[] section;
public VoxelizedSection(long[] section) { public VoxelizedSection(long[] section) {
this.section = section; this.section = section;
@@ -51,6 +52,7 @@ public class VoxelizedSection {
} }
public VoxelizedSection zero() { public VoxelizedSection zero() {
this.lvl0NonAirCount = 0;
Arrays.fill(this.section, 0); Arrays.fill(this.section, 0);
return this; return this;
} }

View File

@@ -143,7 +143,7 @@ public class WorldConversionFactory {
} }
int nonZeroCnt = 0;
if (blockContainer.data.storage instanceof PackedIntegerArray bStor) { if (blockContainer.data.storage instanceof PackedIntegerArray bStor) {
var bDat = bStor.getData(); var bDat = bStor.getData();
int iterPerLong = (64 / bStor.getElementBits()) - 1; int iterPerLong = (64 / bStor.getElementBits()) - 1;
@@ -168,7 +168,7 @@ public class WorldConversionFactory {
sample >>>= eBits; sample >>>= eBits;
byte light = lightSupplier.supply(i&0xF, (i>>8)&0xF, (i>>4)&0xF); byte light = lightSupplier.supply(i&0xF, (i>>8)&0xF, (i>>4)&0xF);
nonZeroCnt += (bId != 0)?1:0;
data[i] = Mapper.composeMappingId(light, bId, biomes[Integer.compress(i,0b1100_1100_1100)]); data[i] = Mapper.composeMappingId(light, bId, biomes[Integer.compress(i,0b1100_1100_1100)]);
} }
} else { } else {
@@ -181,12 +181,14 @@ public class WorldConversionFactory {
data[i] = Mapper.airWithLight(lightSupplier.supply(i&0xF, (i>>8)&0xF, (i>>4)&0xF)); data[i] = Mapper.airWithLight(lightSupplier.supply(i&0xF, (i>>8)&0xF, (i>>4)&0xF));
} }
} else { } else {
nonZeroCnt = 4096;
for (int i = 0; i <= 0xFFF; i++) { for (int i = 0; i <= 0xFFF; i++) {
byte light = lightSupplier.supply(i&0xF, (i>>8)&0xF, (i>>4)&0xF); byte light = lightSupplier.supply(i&0xF, (i>>8)&0xF, (i>>4)&0xF);
data[i] = Mapper.composeMappingId(light, bId, biomes[Integer.compress(i,0b1100_1100_1100)]); data[i] = Mapper.composeMappingId(light, bId, biomes[Integer.compress(i,0b1100_1100_1100)]);
} }
} }
} }
section.lvl0NonAirCount = nonZeroCnt;
return section; return section;
} }

View File

@@ -10,6 +10,8 @@ import me.cortex.voxy.commonImpl.VoxyCommon;
import org.lwjgl.system.MemoryUtil; import org.lwjgl.system.MemoryUtil;
public class SaveLoadSystem3 { public class SaveLoadSystem3 {
public static final int STORAGE_VERSION = 0;
private record SerializationCache(Long2ShortOpenHashMap lutMapCache, MemoryBuffer memoryBuffer) { private record SerializationCache(Long2ShortOpenHashMap lutMapCache, MemoryBuffer memoryBuffer) {
public SerializationCache() { public SerializationCache() {
this(new Long2ShortOpenHashMap(512), ThreadLocalMemoryBuffer.create(WorldSection.SECTION_VOLUME*2+WorldSection.SECTION_VOLUME*8+1024)); this(new Long2ShortOpenHashMap(512), ThreadLocalMemoryBuffer.create(WorldSection.SECTION_VOLUME*2+WorldSection.SECTION_VOLUME*8+1024));
@@ -60,6 +62,7 @@ public class SaveLoadSystem3 {
throw new IllegalStateException(); throw new IllegalStateException();
} }
//TODO: note! can actually have the first (last?) byte of metadata be the storage version!
long metadata = 0; long metadata = 0;
metadata |= Integer.toUnsignedLong(LUT.size());//Bottom 2 bytes metadata |= Integer.toUnsignedLong(LUT.size());//Bottom 2 bytes
metadata |= Byte.toUnsignedLong(section.getNonEmptyChildren())<<16;//Next byte metadata |= Byte.toUnsignedLong(section.getNonEmptyChildren())<<16;//Next byte
@@ -82,21 +85,26 @@ public class SaveLoadSystem3 {
return false; return false;
} }
long metadata = MemoryUtil.memGetLong(ptr); ptr += 8; final long metadata = MemoryUtil.memGetLong(ptr); ptr += 8;
section.nonEmptyChildren = (byte) ((metadata>>>16)&0xFF); section.nonEmptyChildren = (byte) ((metadata>>>16)&0xFF);
final long lutBasePtr = ptr + WorldSection.SECTION_VOLUME * 2;
if (section.lvl == 0) {
int nonEmptyBlockCount = 0; int nonEmptyBlockCount = 0;
long lutBasePtr = ptr + WorldSection.SECTION_VOLUME*2; final var blockData = section.data;
var blockData = section.data;
for (int i = 0; i < WorldSection.SECTION_VOLUME; i++) { for (int i = 0; i < WorldSection.SECTION_VOLUME; i++) {
short lutId = MemoryUtil.memGetShort(ptr); ptr+=2; final short lutId = MemoryUtil.memGetShort(ptr); ptr += 2;
long blockId = MemoryUtil.memGetLong(lutBasePtr+Short.toUnsignedLong(lutId)*8L); final long blockId = MemoryUtil.memGetLong(lutBasePtr + Short.toUnsignedLong(lutId) * 8L);
nonEmptyBlockCount += Mapper.isAir(blockId) ? 0 : 1; nonEmptyBlockCount += Mapper.isAir(blockId) ? 0 : 1;
blockData[i] = blockId; blockData[i] = blockId;
} }
section.nonEmptyBlockCount = nonEmptyBlockCount; section.nonEmptyBlockCount = nonEmptyBlockCount;
} else {
final var blockData = section.data;
for (int i = 0; i < WorldSection.SECTION_VOLUME; i++) {
blockData[i] = MemoryUtil.memGetLong(lutBasePtr + Short.toUnsignedLong(MemoryUtil.memGetShort(ptr)) * 8L);ptr += 2;
}
}
ptr = lutBasePtr + (metadata & 0xFFFF) * 8L; ptr = lutBasePtr + (metadata & 0xFFFF) * 8L;
return true; return true;
} }
} }

View File

@@ -51,7 +51,7 @@ public final class WorldSection {
//Serialized states //Serialized states
long metadata; long metadata;
long[] data = null; long[] data = null;
volatile int nonEmptyBlockCount = 0; volatile int nonEmptyBlockCount = 0;//Note: only needed for level 0 sections
volatile byte nonEmptyChildren; volatile byte nonEmptyChildren;
final ActiveSectionTracker tracker; final ActiveSectionTracker tracker;
@@ -120,7 +120,7 @@ public final class WorldSection {
public int acquire(int count) { public int acquire(int count) {
int state = ((int) ATOMIC_STATE_HANDLE.getAndAdd(this, count<<1)) + (count<<1); int state = ((int) ATOMIC_STATE_HANDLE.getAndAdd(this, count<<1)) + (count<<1);
if ((state & 1) == 0) { if ((state & 1) == 0) {
throw new IllegalStateException("Tried to acquire unloaded section"); throw new IllegalStateException("Tried to acquire unloaded section: " + WorldEngine.pprintPos(this.key));
} }
return state>>1; return state>>1;
} }

View File

@@ -6,7 +6,6 @@ import me.cortex.voxy.common.world.other.Mapper;
import static me.cortex.voxy.common.world.WorldEngine.*; import static me.cortex.voxy.common.world.WorldEngine.*;
public class WorldUpdater { public class WorldUpdater {
//TODO: move this to auxilery class so that it can take into account larger than 4 mip levels
//Executes an update to the world and automatically updates all the parent mip layers up to level 4 (e.g. where 1 chunk section is 1 block big) //Executes an update to the world and automatically updates all the parent mip layers up to level 4 (e.g. where 1 chunk section is 1 block big)
//NOTE: THIS RUNS ON THE THREAD IT WAS EXECUTED ON, when this method exits, the calling method may assume that VoxelizedSection is no longer needed //NOTE: THIS RUNS ON THE THREAD IT WAS EXECUTED ON, when this method exits, the calling method may assume that VoxelizedSection is no longer needed
@@ -23,7 +22,7 @@ public class WorldUpdater {
WorldSection previousSection = null; WorldSection previousSection = null;
final var vdat = section.section; final var vdat = section.section;
for (int lvl = 0; lvl < MAX_LOD_LAYER+1; lvl++) { for (int lvl = 0; lvl <= MAX_LOD_LAYER; lvl++) {
var worldSection = into.acquire(lvl, section.x >> (lvl + 1), section.y >> (lvl + 1), section.z >> (lvl + 1)); var worldSection = into.acquire(lvl, section.x >> (lvl + 1), section.y >> (lvl + 1), section.z >> (lvl + 1));
int emptinessStateChange = 0; int emptinessStateChange = 0;
@@ -41,15 +40,37 @@ public class WorldUpdater {
int by = (section.y&msk)<<(4-lvl); int by = (section.y&msk)<<(4-lvl);
int bz = (section.z&msk)<<(4-lvl); int bz = (section.z&msk)<<(4-lvl);
int nonAirCountDelta = 0; int airCount = 0;
boolean didStateChange = false; boolean didStateChange = false;
//TODO: remove the nonAirCountDelta stuff if level != 0
{//Do a bunch of funny math {//Do a bunch of funny math
var secD = worldSection.data; var secD = worldSection.data;
int baseVIdx = VoxelizedSection.getBaseIndexForLevel(lvl);
int baseSec = bx | (bz << 5) | (by << 10); int baseSec = bx | (bz << 5) | (by << 10);
if (lvl == 0) {
final int secMsk = 0b1100|(0xf << 5) | (0xf << 10);
final int iSecMsk1 = (~secMsk) + 1;
int secIdx = 0;
//TODO: manually unroll and do e.g. 4 iterations per loop
for (int i = 0; i <= 0xFFF; i+=4) {
int cSecIdx = secIdx + baseSec;
secIdx = (secIdx + iSecMsk1) & secMsk;
long oldId0 = secD[cSecIdx+0]; secD[cSecIdx+0] = vdat[i+0];
long oldId1 = secD[cSecIdx+1]; secD[cSecIdx+1] = vdat[i+1];
long oldId2 = secD[cSecIdx+2]; secD[cSecIdx+2] = vdat[i+2];
long oldId3 = secD[cSecIdx+3]; secD[cSecIdx+3] = vdat[i+3];
airCount += Mapper.isAir(oldId0)?1:0; didStateChange |= vdat[i+0] != oldId0;
airCount += Mapper.isAir(oldId1)?1:0; didStateChange |= vdat[i+1] != oldId1;
airCount += Mapper.isAir(oldId2)?1:0; didStateChange |= vdat[i+2] != oldId2;
airCount += Mapper.isAir(oldId3)?1:0; didStateChange |= vdat[i+3] != oldId3;
}
} else {
int baseVIdx = VoxelizedSection.getBaseIndexForLevel(lvl);
int secMsk = 0xF >> lvl; int secMsk = 0xF >> lvl;
secMsk |= (secMsk << 5) | (secMsk << 10); secMsk |= (secMsk << 5) | (secMsk << 10);
@@ -60,17 +81,18 @@ public class WorldUpdater {
for (int i = baseVIdx; i <= (0xFFF >> (lvl * 3)) + baseVIdx; i++) { for (int i = baseVIdx; i <= (0xFFF >> (lvl * 3)) + baseVIdx; i++) {
int cSecIdx = secIdx + baseSec; int cSecIdx = secIdx + baseSec;
secIdx = (secIdx + iSecMsk1) & secMsk; secIdx = (secIdx + iSecMsk1) & secMsk;
long newId = vdat[i]; long newId = vdat[i];
long oldId = secD[cSecIdx]; secD[cSecIdx] = newId; long oldId = secD[cSecIdx];
nonAirCountDelta += (Mapper.isAir(newId)?0:1)-(Mapper.isAir(oldId)?0:1);//its 0:1 cause its nonAir
didStateChange |= newId != oldId; didStateChange |= newId != oldId;
secD[cSecIdx] = newId;
}
} }
} }
if (lvl == 0) {
int nonAirCountDelta = section.lvl0NonAirCount-(4096-airCount);
if (nonAirCountDelta != 0) { if (nonAirCountDelta != 0) {
worldSection.addNonEmptyBlockCount(nonAirCountDelta); worldSection.addNonEmptyBlockCount(nonAirCountDelta);
if (lvl == 0) {
emptinessStateChange = worldSection.updateLvl0State() ? 2 : 0; emptinessStateChange = worldSection.updateLvl0State() ? 2 : 0;
} }
} }

View File

@@ -54,9 +54,8 @@ public class Mapper {
public static boolean isAir(long id) { public static boolean isAir(long id) {
int bId = getBlockId(id);
//Note: air can mean void, cave or normal air, as the block state is remapped during ingesting //Note: air can mean void, cave or normal air, as the block state is remapped during ingesting
return bId == 0; return (id&(((1L<<20)-1)<<27)) == 0;
} }
public static int getBlockId(long id) { public static int getBlockId(long id) {

View File

@@ -309,7 +309,16 @@ public class DHImporter implements IDataImporter {
if ((x+1)%16==0) { if ((x+1)%16==0) {
for (int sz = 0; sz < 4; sz++) { for (int sz = 0; sz < 4; sz++) {
for (int sy = 0; sy < this.worldHeightSections; sy++) { for (int sy = 0; sy < this.worldHeightSections; sy++) {
System.arraycopy(storage, (sz|(sy<<2))<<12, section.section, 0, 16 * 16 * 16); {
int base = (sz|(sy<<2))<<12;
int nonAirCount = 0;
final var dat = section.section;
for (int i = 0; i < 4096; i++) {
nonAirCount += Mapper.isAir(dat[i] = storage[i+base])?0:1;
}
section.lvl0NonAirCount = nonAirCount;
}
WorldConversionFactory.mipSection(section, this.engine.getMapper()); WorldConversionFactory.mipSection(section, this.engine.getMapper());
section.setPosition(X*4+(x>>4), sy+(this.bottomOfWorld>>4), (Z*4)+sz); section.setPosition(X*4+(x>>4), sy+(this.bottomOfWorld>>4), (Z*4)+sz);