diff --git a/src/main/java/me/cortex/voxy/client/core/rendering/building/RenderDataFactory.java b/src/main/java/me/cortex/voxy/client/core/rendering/building/RenderDataFactory.java index a541c4402..7e1574876 100644 --- a/src/main/java/me/cortex/voxy/client/core/rendering/building/RenderDataFactory.java +++ b/src/main/java/me/cortex/voxy/client/core/rendering/building/RenderDataFactory.java @@ -13,6 +13,8 @@ import me.cortex.voxy.commonImpl.VoxyCommon; import org.lwjgl.system.MemoryUtil; +import java.lang.foreign.MemorySegment; +import java.lang.foreign.ValueLayout; import java.util.Arrays; @@ -213,7 +215,7 @@ private static long packPartialQuadData(int modelId, long state, long metadata) return quadData; } - private int prepareSectionData(final long[] rawSectionData) { + private int prepareSectionData(final MemorySegment rawSectionData) { final var sectionData = this.sectionData; final var rawModelIds = this.modelMan._unsafeRawAccess(); long opaque = 0; @@ -225,7 +227,7 @@ private int prepareSectionData(final long[] rawSectionData) { int i = 0; for (int q = 0; q < 512; q++) { for (int j = 0; j < 64; i++, j++) { - long block = rawSectionData[i];//Get the block mapping + long block = rawSectionData.getAtIndex(ValueLayout.JAVA_LONG, i);//Get the block mapping if (Mapper.isAir(block)) {//If it is air, just emit lighting sectionData[i * 2] = (block & (0xFFL << 56)) >>> 1; sectionData[i * 2 + 1] = 0; @@ -297,7 +299,8 @@ private void acquireNeighborData(WorldSection section, int msk) { //Note this is not thread safe! (but eh, fk it) var raw = sec._unsafeGetRawDataArray(); for (int i = 0; i < 32*32; i++) { - this.neighboringFaces[i] = raw[(i<<5)+31];//pull the +x faces from the section + //pull the +x faces from the section + this.neighboringFaces[i] = raw.getAtIndex(ValueLayout.JAVA_LONG, (i<<5)+31); } sec.release(WorldSection.RELEASE_HINT_POSSIBLE_REUSE); } @@ -306,7 +309,8 @@ private void acquireNeighborData(WorldSection section, int msk) { //Note this is not thread safe! (but eh, fk it) var raw = sec._unsafeGetRawDataArray(); for (int i = 0; i < 32*32; i++) { - this.neighboringFaces[i+32*32] = raw[(i<<5)];//pull the -x faces from the section + //pull the -x faces from the section + this.neighboringFaces[i+32*32] = raw.getAtIndex(ValueLayout.JAVA_LONG, (i<<5)); } sec.release(WorldSection.RELEASE_HINT_POSSIBLE_REUSE); } @@ -316,7 +320,8 @@ private void acquireNeighborData(WorldSection section, int msk) { //Note this is not thread safe! (but eh, fk it) var raw = sec._unsafeGetRawDataArray(); for (int i = 0; i < 32*32; i++) { - this.neighboringFaces[i+32*32*2] = raw[i|(0x1F<<10)];//pull the +y faces from the section + //pull the +y faces from the section + this.neighboringFaces[i+32*32*2] = raw.getAtIndex(ValueLayout.JAVA_LONG, i|(0x1F<<10)); } sec.release(WorldSection.RELEASE_HINT_POSSIBLE_REUSE); } @@ -325,7 +330,8 @@ private void acquireNeighborData(WorldSection section, int msk) { //Note this is not thread safe! (but eh, fk it) var raw = sec._unsafeGetRawDataArray(); for (int i = 0; i < 32*32; i++) { - this.neighboringFaces[i+32*32*3] = raw[i];//pull the -y faces from the section + //pull the -y faces from the section + this.neighboringFaces[i+32*32*3] = raw.getAtIndex(ValueLayout.JAVA_LONG, i); } sec.release(WorldSection.RELEASE_HINT_POSSIBLE_REUSE); } @@ -335,7 +341,8 @@ private void acquireNeighborData(WorldSection section, int msk) { //Note this is not thread safe! (but eh, fk it) var raw = sec._unsafeGetRawDataArray(); for (int i = 0; i < 32*32; i++) { - this.neighboringFaces[i+32*32*4] = raw[Integer.expand(i,0b11111_00000_11111)|(0x1F<<5)];//pull the +z faces from the section + //pull the +z faces from the section + this.neighboringFaces[i+32*32*4] = raw.getAtIndex(ValueLayout.JAVA_LONG, Integer.expand(i,0b11111_00000_11111)|(0x1F<<5)); } sec.release(WorldSection.RELEASE_HINT_POSSIBLE_REUSE); } @@ -344,7 +351,8 @@ private void acquireNeighborData(WorldSection section, int msk) { //Note this is not thread safe! (but eh, fk it) var raw = sec._unsafeGetRawDataArray(); for (int i = 0; i < 32*32; i++) { - this.neighboringFaces[i+32*32*5] = raw[Integer.expand(i,0b11111_00000_11111)];//pull the -z faces from the section + //pull the -z faces from the section + this.neighboringFaces[i+32*32*5] = raw.getAtIndex(ValueLayout.JAVA_LONG, Integer.expand(i,0b11111_00000_11111)); } sec.release(WorldSection.RELEASE_HINT_POSSIBLE_REUSE); } diff --git a/src/main/java/me/cortex/voxy/client/core/rendering/building/RenderGenerationService.java b/src/main/java/me/cortex/voxy/client/core/rendering/building/RenderGenerationService.java index 42016a011..2aaed31c2 100644 --- a/src/main/java/me/cortex/voxy/client/core/rendering/building/RenderGenerationService.java +++ b/src/main/java/me/cortex/voxy/client/core/rendering/building/RenderGenerationService.java @@ -11,6 +11,8 @@ import me.cortex.voxy.common.world.WorldSection; import me.cortex.voxy.common.world.other.Mapper; +import java.lang.foreign.MemorySegment; +import java.lang.foreign.ValueLayout; import java.util.List; import java.util.concurrent.PriorityBlockingQueue; import java.util.concurrent.atomic.AtomicInteger; @@ -107,7 +109,12 @@ private void computeAndRequestRequiredModels(IntOpenHashSet seenMissedIds, int b private void computeAndRequestRequiredModels(IntOpenHashSet seenMissedIds, WorldSection section) { //Know this is... very much not safe, however it reduces allocation rates and other garbage, am sure its "fine" final var factory = this.modelBakery.factory; - for (long state : section._unsafeGetRawDataArray()) { + + MemorySegment data = section._unsafeGetRawDataArray(); + int dataLength = section._unsafeGetRawDataArrayLength(); + + for (int i = 0; i < dataLength; i++) { + long state = data.getAtIndex(ValueLayout.JAVA_LONG, i); int block = Mapper.getBlockId(state); if (block != 0 && !factory.hasModelForBlockId(block)) { if (seenMissedIds.add(block)) { diff --git a/src/main/java/me/cortex/voxy/common/config/section/SectionSerializationStorage.java b/src/main/java/me/cortex/voxy/common/config/section/SectionSerializationStorage.java index 555d6c59b..cbf0ff9c9 100644 --- a/src/main/java/me/cortex/voxy/common/config/section/SectionSerializationStorage.java +++ b/src/main/java/me/cortex/voxy/common/config/section/SectionSerializationStorage.java @@ -10,8 +10,8 @@ import me.cortex.voxy.common.world.WorldSection; import me.cortex.voxy.common.world.other.Mapper; +import java.lang.foreign.ValueLayout; import java.nio.ByteBuffer; -import java.util.Arrays; import java.util.function.LongConsumer; public class SectionSerializationStorage extends SectionStorage { @@ -30,7 +30,9 @@ public int loadSection(WorldSection into) { if (!SaveLoadSystem3.deserialize(into, data)) { this.backend.deleteSectionData(into.key); //TODO: regenerate the section from children - Arrays.fill(into._unsafeGetRawDataArray(), Mapper.AIR); + for (int i = 0; i < into._unsafeGetRawDataArrayLength(); i++) { + into._unsafeGetRawDataArray().setAtIndex(ValueLayout.JAVA_LONG, i, Mapper.AIR); + } Logger.error("Section " + into.lvl + ", " + into.x + ", " + into.y + ", " + into.z + " was unable to load, removing"); return -1; } else { diff --git a/src/main/java/me/cortex/voxy/common/world/ActiveSectionTracker.java b/src/main/java/me/cortex/voxy/common/world/ActiveSectionTracker.java index 66d7725af..6d1985338 100644 --- a/src/main/java/me/cortex/voxy/common/world/ActiveSectionTracker.java +++ b/src/main/java/me/cortex/voxy/common/world/ActiveSectionTracker.java @@ -6,9 +6,9 @@ import me.cortex.voxy.common.world.other.Mapper; import org.jetbrains.annotations.Nullable; +import java.lang.foreign.ValueLayout; import java.lang.invoke.MethodHandles; import java.lang.invoke.VarHandle; -import java.util.Arrays; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.locks.StampedLock; @@ -66,10 +66,18 @@ public ActiveSectionTracker(int numSlicesBits, SectionLoader loader, int cacheSi } public WorldSection acquire(int lvl, int x, int y, int z, boolean nullOnEmpty) { - return this.acquire(WorldEngine.getWorldSectionId(lvl, x, y, z), nullOnEmpty); + return this.acquire(lvl, x, y, z, nullOnEmpty, WorldSection.DEFAULT_ALLOCATOR); + } + + public WorldSection acquire(int lvl, int x, int y, int z, boolean nullOnEmpty, WorldSection.Allocator allocator) { + return this.acquire(WorldEngine.getWorldSectionId(lvl, x, y, z), nullOnEmpty, allocator); } public WorldSection acquire(long key, boolean nullOnEmpty) { + return this.acquire(key, nullOnEmpty, WorldSection.DEFAULT_ALLOCATOR); + } + + public WorldSection acquire(long key, boolean nullOnEmpty, WorldSection.Allocator allocator) { //TODO: add optional verification check to ensure this (or other critical systems) arnt being called on the render or server thread if (this.engine != null) this.engine.lastActiveTime = System.currentTimeMillis(); int index = this.getCacheArrayIndex(key); @@ -142,7 +150,7 @@ public WorldSection acquire(long key, boolean nullOnEmpty) { WorldEngine.getX(key), WorldEngine.getY(key), WorldEngine.getZ(key), - this); + this, allocator); status = this.loader.load(section); @@ -158,7 +166,12 @@ public WorldSection acquire(long key, boolean nullOnEmpty) { //We need to set the data to air as it is undefined state int sky = 15; int block = 0; - Arrays.fill(section.data, Mapper.composeMappingId((byte) (sky|(block<<4)),0,0)); + + long id = Mapper.composeMappingId((byte) (sky|(block<<4)),0,0); + + for (int i = 0; i < section.dataLength; i++) { + section.data.setAtIndex(ValueLayout.JAVA_LONG, i, id); + } } section.acquire(1); } diff --git a/src/main/java/me/cortex/voxy/common/world/SaveLoadSystem3.java b/src/main/java/me/cortex/voxy/common/world/SaveLoadSystem3.java index 0a6d31c2e..af5385e0b 100644 --- a/src/main/java/me/cortex/voxy/common/world/SaveLoadSystem3.java +++ b/src/main/java/me/cortex/voxy/common/world/SaveLoadSystem3.java @@ -7,6 +7,8 @@ import me.cortex.voxy.common.world.other.Mapper; import org.lwjgl.system.MemoryUtil; +import java.lang.foreign.ValueLayout; + public class SaveLoadSystem3 { public static final int STORAGE_VERSION = 0; @@ -38,6 +40,7 @@ public static int z2lin(int i) { public static MemoryBuffer serialize(WorldSection section) { var cache = CACHE.get(); var data = section.data; + var dataLength = section.dataLength; Long2ShortOpenHashMap LUT = cache.lutMapCache; LUT.clear(); @@ -48,9 +51,10 @@ public static MemoryBuffer serialize(WorldSection section) { long metadataPtr = ptr; ptr += 8; long blockPtr = ptr; ptr += WorldSection.SECTION_VOLUME*2; - long prev = data[0]; MemoryUtil.memPutLong(ptr, prev); ptr+=8; LUT.put(prev, (short) 0); + long prev = data.getAtIndex(ValueLayout.JAVA_LONG, 0); MemoryUtil.memPutLong(ptr, prev); ptr+=8; LUT.put(prev, (short) 0); short mapping = 0; - for (long block : data) { + for (int i = 0; i < dataLength; i++) { + long block = data.getAtIndex(ValueLayout.JAVA_LONG, i); if (prev != block) { prev = block; mapping = LUT.putIfAbsent(block, (short) LUT.size()); @@ -92,13 +96,16 @@ public static boolean deserialize(WorldSection section, MemoryBuffer data) { final long lutBasePtr = ptr + WorldSection.SECTION_VOLUME * 2; final var blockData = section.data; + final var blockDataLength = section.dataLength; for (int i = 0; i < WorldSection.SECTION_VOLUME; i++) { - blockData[i] = MemoryUtil.memGetLong(lutBasePtr + Short.toUnsignedLong(MemoryUtil.memGetShort(ptr)) * 8L);ptr += 2; + blockData.setAtIndex(ValueLayout.JAVA_LONG, i, MemoryUtil.memGetLong(lutBasePtr + Short.toUnsignedLong(MemoryUtil.memGetShort(ptr)) * 8L)); + ptr += 2; } if (section.lvl == 0) { int emptyBlockCount = 0; - for (long block : blockData) { + for (int i = 0; i < blockDataLength; i++) { + long block = blockData.getAtIndex(ValueLayout.JAVA_LONG, i); emptyBlockCount += Mapper.isAir(block) ? 1 : 0; } section.nonEmptyBlockCount = WorldSection.SECTION_VOLUME-emptyBlockCount; diff --git a/src/main/java/me/cortex/voxy/common/world/WorldSection.java b/src/main/java/me/cortex/voxy/common/world/WorldSection.java index c4a82828a..202df2302 100644 --- a/src/main/java/me/cortex/voxy/common/world/WorldSection.java +++ b/src/main/java/me/cortex/voxy/common/world/WorldSection.java @@ -3,9 +3,10 @@ import me.cortex.voxy.commonImpl.VoxyCommon; +import java.lang.foreign.MemorySegment; +import java.lang.foreign.ValueLayout; import java.lang.invoke.MethodHandles; import java.lang.invoke.VarHandle; -import java.util.Arrays; import java.util.concurrent.ConcurrentLinkedDeque; import java.util.concurrent.atomic.AtomicInteger; @@ -34,13 +35,37 @@ public final class WorldSection { } } + public static final Allocator DEFAULT_ALLOCATOR = new Allocator() { + //TODO: should make it dynamically adjust the size allowance based on memory pressure/WorldSection allocation rate (e.g. is it doing a world import) + private static final int ARRAY_REUSE_CACHE_SIZE = 400;//500;//32*32*32*8*ARRAY_REUSE_CACHE_SIZE == number of bytes + //TODO: maybe just swap this to a ConcurrentLinkedDeque + private static final AtomicInteger ARRAY_REUSE_CACHE_COUNT = new AtomicInteger(0); + private static final ConcurrentLinkedDeque ARRAY_REUSE_CACHE = new ConcurrentLinkedDeque<>(); - //TODO: should make it dynamically adjust the size allowance based on memory pressure/WorldSection allocation rate (e.g. is it doing a world import) - private static final int ARRAY_REUSE_CACHE_SIZE = 400;//500;//32*32*32*8*ARRAY_REUSE_CACHE_SIZE == number of bytes - //TODO: maybe just swap this to a ConcurrentLinkedDeque - private static final AtomicInteger ARRAY_REUSE_CACHE_COUNT = new AtomicInteger(0); - private static final ConcurrentLinkedDeque ARRAY_REUSE_CACHE = new ConcurrentLinkedDeque<>(); + @Override + public MemorySegment allocate(int lvl, int x, int y, int z, int size) { + // TODO: this is subtly incorrect as a general allocator, the polled array might not be of the size we need + long[] data = ARRAY_REUSE_CACHE.poll(); + if (data == null) { + data = new long[size]; + } else { + ARRAY_REUSE_CACHE_COUNT.decrementAndGet(); + } + + return MemorySegment.ofArray(data); + } + + @Override + public void release(MemorySegment segment) { + long[] data = (long[])segment.heapBase().orElseThrow(); + + if (ARRAY_REUSE_CACHE_COUNT.get() < ARRAY_REUSE_CACHE_SIZE) { + ARRAY_REUSE_CACHE.add(data); + ARRAY_REUSE_CACHE_COUNT.incrementAndGet(); + } + } + }; public final int lvl; public final int x; @@ -51,7 +76,8 @@ public final class WorldSection { //Serialized states long metadata; - long[] data = null; + MemorySegment data = null; + final int dataLength; volatile int nonEmptyBlockCount = 0;//Note: only needed for level 0 sections volatile byte nonEmptyChildren; @@ -59,34 +85,37 @@ public final class WorldSection { volatile boolean inSaveQueue; volatile boolean isDirty; + final Allocator allocator; + //When the first bit is set it means its loaded @SuppressWarnings("all") private volatile int atomicState = 1; - WorldSection(int lvl, int x, int y, int z, ActiveSectionTracker tracker) { + WorldSection(int lvl, int x, int y, int z, ActiveSectionTracker tracker, Allocator allocator) { this.lvl = lvl; this.x = x; this.y = y; this.z = z; this.key = WorldEngine.getWorldSectionId(lvl, x, y, z); this.tracker = tracker; + this.allocator = allocator; - this.data = ARRAY_REUSE_CACHE.poll(); - if (this.data == null) { - this.data = new long[32 * 32 * 32]; - } else { - ARRAY_REUSE_CACHE_COUNT.decrementAndGet(); - } + this.dataLength = 32 * 32 * 32; + this.data = this.allocator.allocate(lvl, x, y, z, this.dataLength); } void primeForReuse() { ATOMIC_STATE_HANDLE.set(this, 1); } - public long[] _unsafeGetRawDataArray() { + public MemorySegment _unsafeGetRawDataArray() { return this.data; } + public int _unsafeGetRawDataArrayLength() { + return this.dataLength; + } + @Override public int hashCode() { return ((x*1235641+y)*8127451+z)*918267913+lvl; @@ -180,10 +209,8 @@ void _releaseArray() { if (VERIFY_WORLD_SECTION_EXECUTION && this.data == null) { throw new IllegalStateException(); } - if (ARRAY_REUSE_CACHE_COUNT.get() < ARRAY_REUSE_CACHE_SIZE) { - ARRAY_REUSE_CACHE.add(this.data); - ARRAY_REUSE_CACHE_COUNT.incrementAndGet(); - } + + this.allocator.release(this.data); this.data = null; } @@ -209,15 +236,15 @@ public static int getIndex(int x, int y, int z) { public long set(int x, int y, int z, long id) { //TODO: this needs to update the block counts int idx = getIndex(x,y,z); - long old = this.data[idx]; - this.data[idx] = id; + long old = this.data.getAtIndex(ValueLayout.JAVA_LONG, idx); + this.data.setAtIndex(ValueLayout.JAVA_LONG, idx, id); return old; } //Generates a copy of the data array, this is to help with atomic operations like rendering public long[] copyData() { this.assertNotFree(); - return Arrays.copyOf(this.data, this.data.length); + return this.data.toArray(ValueLayout.JAVA_LONG); } public void copyDataTo(long[] cache) { @@ -226,8 +253,8 @@ public void copyDataTo(long[] cache) { public void copyDataTo(long[] cache, int dstOffset) { this.assertNotFree(); - if ((cache.length-dstOffset) < this.data.length) throw new IllegalArgumentException(); - System.arraycopy(this.data, 0, cache, dstOffset, this.data.length); + if ((cache.length-dstOffset) < this.dataLength) throw new IllegalArgumentException(); + MemorySegment.copy(this.data, ValueLayout.JAVA_LONG, 0, cache, dstOffset, dataLength); } public static int getChildIndex(int x, int y, int z) { @@ -285,7 +312,7 @@ public void _unsafeSetNonEmptyChildren(byte nonEmptyChildren) { } public static WorldSection _createRawUntrackedUnsafeSection(int lvl, int x, int y, int z) { - return new WorldSection(lvl, x, y, z, null); + return new WorldSection(lvl, x, y, z, null, DEFAULT_ALLOCATOR); } public void markDirty() { @@ -309,4 +336,10 @@ public boolean shouldSave() { public boolean isFreed() { return (((int)ATOMIC_STATE_HANDLE.get(this))&1)==0; } + + public interface Allocator { + MemorySegment allocate(int lvl, int x, int y, int z, int size); + void release(MemorySegment segment); + } + } \ No newline at end of file diff --git a/src/main/java/me/cortex/voxy/common/world/WorldUpdater.java b/src/main/java/me/cortex/voxy/common/world/WorldUpdater.java index 1ab705b92..d580e5355 100644 --- a/src/main/java/me/cortex/voxy/common/world/WorldUpdater.java +++ b/src/main/java/me/cortex/voxy/common/world/WorldUpdater.java @@ -4,6 +4,8 @@ import me.cortex.voxy.common.world.other.Mapper; import me.cortex.voxy.commonImpl.VoxyCommon; +import java.lang.foreign.ValueLayout; + import static me.cortex.voxy.common.world.WorldEngine.*; public class WorldUpdater { @@ -122,10 +124,14 @@ private static long insertSectionLvlIntoWorld(VoxelizedSection section, WorldSec 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]; + long oldId0 = secD.getAtIndex(ValueLayout.JAVA_LONG, cSecIdx+0); + secD.setAtIndex(ValueLayout.JAVA_LONG, cSecIdx+0, vdat[i+0]); + long oldId1 = secD.getAtIndex(ValueLayout.JAVA_LONG, cSecIdx+1); + secD.setAtIndex(ValueLayout.JAVA_LONG, cSecIdx+1, vdat[i+1]); + long oldId2 = secD.getAtIndex(ValueLayout.JAVA_LONG, cSecIdx+2); + secD.setAtIndex(ValueLayout.JAVA_LONG, cSecIdx+2, vdat[i+2]); + long oldId3 = secD.getAtIndex(ValueLayout.JAVA_LONG, cSecIdx+3); + secD.setAtIndex(ValueLayout.JAVA_LONG, 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; @@ -145,9 +151,9 @@ private static long insertSectionLvlIntoWorld(VoxelizedSection section, WorldSec int cSecIdx = secIdx + baseSec; secIdx = (secIdx + iSecMsk1) & secMsk; long newId = vdat[i]; - long oldId = secD[cSecIdx]; + long oldId = secD.getAtIndex(ValueLayout.JAVA_LONG, cSecIdx); didStateChange |= newId != oldId; - secD[cSecIdx] = newId; + secD.setAtIndex(ValueLayout.JAVA_LONG, cSecIdx, newId); } } }