Work on nodes

This commit is contained in:
mcrcortex
2024-08-06 12:49:28 +10:00
parent 2d6d948e80
commit 351fac9052
6 changed files with 172 additions and 25 deletions

View File

@@ -2,12 +2,11 @@ package me.cortex.voxy.client.core.rendering.hierachical2;
import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap;
import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
import me.cortex.voxy.client.core.rendering.building.BuiltSection;
import me.cortex.voxy.client.core.rendering.building.SectionPositionUpdateFilterer;
import me.cortex.voxy.client.core.rendering.section.AbstractSectionGeometryManager;
import me.cortex.voxy.common.util.HierarchicalBitSet;
import me.cortex.voxy.common.world.WorldSection;
import me.cortex.voxy.client.core.util.ExpandingObjectAllocationList;
import me.cortex.voxy.common.world.WorldEngine;
import me.jellysquid.mods.sodium.client.util.MathUtil;
import org.lwjgl.system.MemoryUtil;
@@ -15,10 +14,10 @@ import org.lwjgl.system.MemoryUtil;
public class HierarchicalNodeManager {
public static final int NODE_MSK = ((1<<24)-1);
public final int maxNodeCount;
private final long[] localNodeData;
private final AbstractSectionGeometryManager geometryManager;
private final HierarchicalBitSet allocationSet;
private final NodeStore nodeData;
private final Long2IntOpenHashMap activeSectionMap = new Long2IntOpenHashMap();
private final ExpandingObjectAllocationList<LeafExpansionRequest> leafRequests = new ExpandingObjectAllocationList<>(LeafExpansionRequest[]::new);
private final AbstractSectionGeometryManager geometryManager;
private final SectionPositionUpdateFilterer updateFilterer;
public HierarchicalNodeManager(int maxNodeCount, AbstractSectionGeometryManager geometryManager, SectionPositionUpdateFilterer updateFilterer) {
if (!MathUtil.isPowerOfTwo(maxNodeCount)) {
@@ -27,36 +26,105 @@ public class HierarchicalNodeManager {
if (maxNodeCount>(1<<24)) {
throw new IllegalArgumentException("Max node count cannot exceed 2^24");
}
this.activeSectionMap.defaultReturnValue(-1);
this.updateFilterer = updateFilterer;
this.allocationSet = new HierarchicalBitSet(maxNodeCount);
this.maxNodeCount = maxNodeCount;
this.localNodeData = new long[maxNodeCount*4];
this.nodeData = new NodeStore(maxNodeCount);
this.geometryManager = geometryManager;
for(int x = -1; x<=1;x++) {
for (int z = -1; z <= 1; z++) {
for (int y = -3; y <= 3; y++) {
updateFilterer.watch(0,x,y,z);
new Thread(()->{
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
for(int x = -1; x<=1;x++) {
for (int z = -1; z <= 1; z++) {
for (int y = -3; y <= 3; y++) {
updateFilterer.watch(4,x,y,z);
updateFilterer.unwatch(4,x,y,z);
}
}
}
}).start();
}
public void processRequestQueue(int count, long ptr) {
for (int requestIndex = 0; requestIndex < count; requestIndex++) {
int op = MemoryUtil.memGetInt(ptr + (requestIndex * 4L));
this.processRequest(op);
}
}
public void processRequestQueue(int count, long ptr) {
for (int i = 0; i < count; i++) {
int op = MemoryUtil.memGetInt(ptr+(i*4L));
int node = op&NODE_MSK;
private void processRequest(int op) {
int node = op&NODE_MSK;
if (!this.nodeData.nodeExists(node)) {
throw new IllegalStateException("Tried processing a node that doesnt exist: " + node);
}
if (this.nodeData.nodeRequestInFlight(node)) {
throw new IllegalStateException("Tried processing a node that already has a request in flight: " + node + " pos: " + WorldEngine.pprintPos(this.nodeData.nodePosition(node)));
}
this.nodeData.markRequestInFlight(node);
long pos = this.nodeData.nodePosition(node);
//2 branches, either its a leaf node -> emit a leaf request
// or the nodes geometry must be empty (i.e. culled from the graph/tree) so add to tracker and watch
if (this.nodeData.isLeafNode(node)) {
//TODO: the localNodeData should have a bitset of what children are definitely empty
// use that to msk the request, HOWEVER there is a race condition e.g.
// leaf node is requested and has only 1 child marked as non empty
// however then an update occures and a different child now becomes non empty,
// this will trigger a processBuildResult for parent
// so need to ensure that when that happens, if the parent has an inflight leaf expansion request
// for the leaf request to be updated to account for the new maybe child node
// NOTE: a section can have empty geometry but some of its children might not, so need to mark and
// submit a node at that level but with empty section, (specially marked) so that the traversal
// can recurse into those children as needed
//Enqueue a leaf expansion request
var request = new LeafExpansionRequest(pos);
int requestId = this.leafRequests.put(request);
for (int i = 0; i < 8; i++) {
long childPos = makeChildPos(pos, i);
//Insert all the children into the tracking map with the node id
this.activeSectionMap.put(childPos, 0);
}
} else {
//Verify that the node section is not in the section store. if it is then it is a state desynchonization
// Note that a section can be "empty" but some of its children might not be
}
}
public void processBuildResult(BuiltSection section) {
if (!section.isEmpty()) {
this.geometryManager.uploadSection(section);
int nodeId = this.activeSectionMap.get(section.position);
if (nodeId == -1) {
//Not tracked or mapped to a node!!!
} else {
section.free();
//Part of a request (top bit is set to 1)
if ((nodeId&(1<<31))!=0) {
} else {
//Not part of a request, just a node update,
// however could result in a reallocation if it needs to mark a child position as being possibly visible
}
}
}
private static long makeChildPos(long basePos, int addin) {
int lvl = WorldEngine.getLevel(basePos);
if (lvl == 0) {
throw new IllegalArgumentException("Cannot create a child lower than lod level 0");
}
return WorldEngine.getWorldSectionId(lvl-1,
(WorldEngine.getX(basePos)<<1)|(addin&1),
(WorldEngine.getY(basePos)<<1)|((addin>>1)&1),
(WorldEngine.getZ(basePos)<<1)|((addin>>2)&1));
}
}

View File

@@ -0,0 +1,34 @@
package me.cortex.voxy.client.core.rendering.hierachical2;
import me.cortex.voxy.common.util.HierarchicalBitSet;
public final class NodeStore {
private final HierarchicalBitSet allocationSet;
private final long[] localNodeData;
public NodeStore(int maxNodeCount) {
this.localNodeData = new long[maxNodeCount*4];
this.allocationSet = new HierarchicalBitSet(maxNodeCount);
}
public long nodePosition(int nodeId) {
return this.localNodeData[nodeId<<2];
}
public boolean nodeExists(int nodeId) {
return false;
}
public void markRequestInFlight(int nodeId) {
}
public boolean nodeRequestInFlight(int nodeId) {
return false;
}
public boolean isLeafNode(int nodeId) {
return false;
}
}

View File

@@ -6,7 +6,7 @@ import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
import me.cortex.voxy.client.core.gl.GlBuffer;
import me.cortex.voxy.client.core.rendering.building.BuiltSection;
import me.cortex.voxy.client.core.rendering.util.DownloadStream;
import me.cortex.voxy.client.core.rendering.util.MarkedObjectList;
import me.cortex.voxy.client.core.util.MarkedCachedObjectList;
import me.cortex.voxy.client.core.rendering.util.UploadStream;
import me.cortex.voxy.common.util.HierarchicalBitSet;
import me.cortex.voxy.common.world.WorldEngine;
@@ -302,7 +302,7 @@ public class NodeManager {
//The request queue should be like some array that can reuse objects to prevent gc nightmare + like a bitset to find an avalible free slot
// hashmap might work bar the gc overhead
private final MarkedObjectList<LeafRequest> leafRequests = new MarkedObjectList<>(LeafRequest[]::new, LeafRequest::new);
private final MarkedCachedObjectList<LeafRequest> leafRequests = new MarkedCachedObjectList<>(LeafRequest[]::new, LeafRequest::new);
private static int pos2octnode(long pos) {
return (WorldEngine.getX(pos)&1)|((WorldEngine.getY(pos)&1)<<1)|((WorldEngine.getZ(pos)&1)<<2);

View File

@@ -106,7 +106,7 @@ public class MDICSectionRenderer extends AbstractSectionRenderer<MDICViewport, B
glBindVertexArray(RenderService.STATIC_VAO);//Needs to be before binding
this.bindRenderingBuffers();
glMultiDrawElementsIndirectCountARB(GL_TRIANGLES, GL_UNSIGNED_SHORT, 0, 4*3, Math.min((int)(this.geometryManager.getSectionCount()*4.4), 400_000), 0);
glMultiDrawElementsIndirectCountARB(GL_TRIANGLES, GL_UNSIGNED_SHORT, 0, 4*3, Math.min((int)(this.geometryManager.getSectionCount()*4.4+128), 400_000), 0);
glEnable(GL_CULL_FACE);
glBindVertexArray(0);

View File

@@ -0,0 +1,45 @@
package me.cortex.voxy.client.core.util;
import it.unimi.dsi.fastutil.ints.Int2ObjectFunction;
import me.cortex.voxy.common.util.HierarchicalBitSet;
public class ExpandingObjectAllocationList<T> {
private static final float GROWTH_FACTOR = 0.75f;
private final Int2ObjectFunction<T[]> arrayGenerator;
private final HierarchicalBitSet bitSet = new HierarchicalBitSet();
private T[] objects;//Should maybe make a getter function instead
public ExpandingObjectAllocationList(Int2ObjectFunction<T[]> arrayGenerator) {
this.arrayGenerator = arrayGenerator;
this.objects = this.arrayGenerator.apply(16);
}
public int put(T obj) {
//Gets an unused id for some entry in objects, if its null fill it
int id = this.bitSet.allocateNext();
if (this.objects.length <= id) {
//Resize and copy over the objects array
int newLen = this.objects.length + (int)Math.ceil(this.objects.length*GROWTH_FACTOR);
T[] newArr = this.arrayGenerator.apply(newLen);
System.arraycopy(this.objects, 0, newArr, 0, this.objects.length);
this.objects = newArr;
}
this.objects[id] = obj;
return id;
}
public void release(int id) {
if (!this.bitSet.free(id)) {
throw new IllegalArgumentException("Index " + id + " was already released");
}
}
public T get(int index) {
//Make the checking that index is allocated optional, as it might cause overhead due to multiple cacheline misses
if (!this.bitSet.isSet(index)) {
throw new IllegalArgumentException("Index " + index + " is not allocated");
}
return this.objects[index];
}
}

View File

@@ -1,11 +1,11 @@
package me.cortex.voxy.client.core.rendering.util;
package me.cortex.voxy.client.core.util;
import it.unimi.dsi.fastutil.ints.Int2ObjectFunction;
import me.cortex.voxy.common.util.HierarchicalBitSet;
import java.util.function.Supplier;
public class MarkedObjectList<T> {
public class MarkedCachedObjectList<T> {
private static final float GROWTH_FACTOR = 0.75f;
private final Int2ObjectFunction<T[]> arrayGenerator;
@@ -13,7 +13,7 @@ public class MarkedObjectList<T> {
private final HierarchicalBitSet bitSet = new HierarchicalBitSet();
private T[] objects;//Should maybe make a getter function instead
public MarkedObjectList(Int2ObjectFunction<T[]> arrayGenerator, Supplier<T> nullSupplier) {
public MarkedCachedObjectList(Int2ObjectFunction<T[]> arrayGenerator, Supplier<T> nullSupplier) {
this.arrayGenerator = arrayGenerator;
this.nullSupplier = nullSupplier;
this.objects = this.arrayGenerator.apply(16);